No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

smd-applet.vala 32KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034
  1. // Released under the terms of GPLv3 or at your option any later version.
  2. // No warranties.
  3. // Copyright Enrico Tassi <gares@fettunta.org>
  4. errordomain Exit { ABORT }
  5. bool verbose = false;
  6. void debug(string message) {
  7. if (verbose) stderr.printf("DEBUG: %s\n",message);
  8. }
  9. // minimalistic NetworkManager interface
  10. [DBus (name = "org.freedesktop.NetworkManager")]
  11. interface NetworkManager : Object {
  12. public signal void state_changed(uint state); // throws IOErrror;
  13. public abstract uint state { owned get; }
  14. }
  15. const string NM_SERVICE = "org.freedesktop.NetworkManager";
  16. const string NM_PATH = "/org/freedesktop/NetworkManager";
  17. // Checks if there is a connection
  18. // 70 is NM_STATE_CONNECTED_GLOBAL in NetworkManager >= 0.9
  19. // 3 is NM_STATE_CONNECTED in NetworkManager < 0.9
  20. // We may want to check for >= 50 or 60, since they are respectively LOCAL
  21. // and SITE connections.
  22. static bool is_nm_connected(uint code) {
  23. return code == 3 || code == 70;
  24. }
  25. // a simple class to pass data from the child process to the
  26. // notifier
  27. class Event {
  28. public string message = null;
  29. public string message_icon = "gtk-about";
  30. public bool enter_network_error_mode = false;
  31. public bool enter_error_mode = false;
  32. public bool transient_error_message = false;
  33. // fields meaningful for the error mode only
  34. public string context = null;
  35. public string cause = null;
  36. public string permissions = null;
  37. public string mail_name = null;
  38. public string mail_body = null;
  39. public Gee.ArrayList<string> commands = null;
  40. // constructors
  41. public static Event error(string account, string host,
  42. string context, string cause, string? permissions, string? mail_name,
  43. string? mail_body, Gee.ArrayList<string> commands) {
  44. var e = new Event();
  45. e.message = "An error occurred, click on the icon for more details";
  46. e.message_icon = "error";
  47. e.enter_error_mode = true;
  48. e.cause = cause;
  49. e.context = context;
  50. e.permissions = permissions;
  51. e.mail_name = mail_name;
  52. e.mail_body = mail_body;
  53. e.commands = commands;
  54. return e;
  55. }
  56. public static Event generic_error(string cause) {
  57. var e = new Event();
  58. e.message = "A failure occurred: "+cause;
  59. e.message_icon = "dialog-warning";
  60. e.transient_error_message = true;
  61. return e;
  62. }
  63. public static Event network_error() {
  64. var e = new Event();
  65. e.message = "A persistent network failure occurred";
  66. e.message_icon = "dialog-warning";
  67. e.enter_network_error_mode = true;
  68. return e;
  69. }
  70. public static Event stats(
  71. string account,string host,int new_mails,int del_mails)
  72. {
  73. string preamble = "Synchronize with %s:\n".printf(account);
  74. var e = new Event();
  75. if (new_mails > 0 && del_mails > 0) {
  76. e.message = "%s%d new messages\n%d deleted messages".
  77. printf(preamble,new_mails,del_mails);
  78. } else if (new_mails > 0) {
  79. e.message = "%s%d new messages".printf(preamble,new_mails);
  80. } else {
  81. e.message = "%s%d deleted messages".printf(preamble,del_mails);
  82. }
  83. return e;
  84. }
  85. public bool is_error_event() {
  86. return (this.enter_error_mode ||
  87. this.enter_network_error_mode ||
  88. this.transient_error_message);
  89. }
  90. }
  91. static const string SMD_LOOP = "/bin/smd-loop";
  92. static const string SMD_PUSH = "/bin/smd-push";
  93. static const string SMD_APPLET_UI = "/share/syncmaildir-applet/smd-applet.ui";
  94. static const string SMD_APPLET_DESKTOP = "/share/applications/smd-applet.desktop";
  95. static const string GNOME_AUTOSTART_DISABLED ="X-GNOME-Autostart-enabled=false";
  96. static string SMD_LOGS_DIR;
  97. static string SMD_LOOP_CFG;
  98. static string SMD_PP_DEF_CFG;
  99. static string XDG_AUTORUN_FILE;
  100. // the main class containing all the data smd-applet will use
  101. class smdApplet : Gtk.Application {
  102. // =================== the constants ===============================
  103. // settings keys
  104. static const string key_newmail = "notify-new-mail";
  105. // paths, set by main() to something that depends on the
  106. // installation path
  107. public static string smd_loop_cmd = null;
  108. public static string smd_applet_ui = null;
  109. public static string smd_push_cmd = null;
  110. public static string smd_applet_desktop = null;
  111. // =================== the data =====================================
  112. // The builder
  113. Gtk.Builder builder = null;
  114. // main widgets
  115. Gtk.StatusIcon si = null;
  116. Gtk.Window win = null;
  117. Gtk.Window err_win = null;
  118. Gtk.Notebook notebook = null;
  119. Gtk.Switch sync_active = null;
  120. // Stuff for logs display
  121. Gtk.ComboBoxText cblogs = null;
  122. Gee.ArrayList<string> lognames = null;
  123. // the gsettings entry point
  124. GLib.Settings settings = null;
  125. // the thread to manage the child smd-loop instance
  126. GLib.Thread<void *> thread = null;
  127. bool thread_die = false;
  128. GLib.Pid pid; // smd-loop pid, initially set to 0
  129. // communication structure between the child process (managed by a thread
  130. // and the notifier timeout handler).
  131. Mutex events_lock = Mutex();
  132. Gee.ArrayList<Event> events = null;
  133. // if the program is stuck
  134. bool error_mode = false;
  135. bool network_error_mode = false;
  136. bool config_wait_mode;
  137. GLib.HashTable<Gtk.Widget,string> command_hash = null;
  138. // dbus connection to NetworkManager
  139. NetworkManager net_manager = null;
  140. // last persistent notification, to avoid its garbage collection
  141. // so that the callback is called when the button is clicked
  142. Notify.Notification notification = null;
  143. bool notification_server_has_persistence = false;
  144. #if !VALA_0_24
  145. [CCode (cheader_filename = "libnotify/notify.h", cname = "notify_get_server_caps")]
  146. extern static GLib.List<string> notify_get_server_caps();
  147. #endif
  148. static string menu_ui = """
  149. <interface>
  150. <menu id='app-menu'>
  151. <section>
  152. <item>
  153. <attribute name='label' translatable='yes'>_Quit</attribute>
  154. <attribute name='action'>app.quit</attribute>
  155. </item>
  156. </section>
  157. </menu>
  158. </interface>
  159. """;
  160. // ======================= constructor ================================
  161. // initialize data structures and build gtk+ widgets
  162. public smdApplet() throws Exit {
  163. set_application_id("org.syncmaildir");
  164. try { register(); }
  165. catch (GLib.Error e) { stderr.printf("%s\n",e.message); };
  166. // load the ui file
  167. builder = new Gtk.Builder ();
  168. try { builder.add_from_file (smd_applet_ui); }
  169. catch (GLib.Error e) {
  170. stderr.printf("%s\n",e.message);
  171. throw new Exit.ABORT("Unable to load the ui file");
  172. }
  173. try { builder.add_from_string (menu_ui, menu_ui.length); }
  174. catch (GLib.Error e) {
  175. stderr.printf("%s\n",e.message);
  176. throw new Exit.ABORT("Unable to load the menu ui string");
  177. }
  178. // events queue and mutex
  179. events = new Gee.ArrayList<Event>();
  180. // gsettings
  181. settings = new Settings("org.syncmaildir.applet");
  182. // connect to dbus
  183. try {
  184. net_manager=Bus.get_proxy_sync(BusType.SYSTEM,NM_SERVICE,NM_PATH);
  185. net_manager.state_changed.connect((s) => {
  186. if (is_nm_connected(s)) sync_active.set_active(true);
  187. else sync_active.set_active(false);
  188. });
  189. } catch (GLib.Error e) {
  190. stderr.printf("%s\n",e.message);
  191. net_manager=null;
  192. }
  193. // load widgets and attach callbacks
  194. var simple_mainwin = builder.get_object("wMain") as Gtk.Window;
  195. win = new Gtk.ApplicationWindow(this);
  196. simple_mainwin.get_child().reparent(win);
  197. var w = 0;
  198. var h = 0;
  199. simple_mainwin.get_size_request(out w, out h);
  200. win.set_size_request(w,h);
  201. win.set_title(simple_mainwin.get_title());
  202. err_win = builder.get_object("wError") as Gtk.Window;
  203. var lcopyright = builder.get_object("lCopyright") as Gtk.Label;
  204. lcopyright.set_text("Copyright " + SMDConf.COPYRIGHT);
  205. var logs_vb = builder.get_object("vbLog") as Gtk.Grid;
  206. cblogs = new Gtk.ComboBoxText();
  207. lognames = new Gee.ArrayList<string>();
  208. logs_vb.attach(cblogs,0,0,1,1);
  209. cblogs.show();
  210. update_logcontents();
  211. var bnotify = builder.get_object("sNotify") as Gtk.Switch;
  212. bnotify.set_active(settings.get_boolean(key_newmail));
  213. bnotify.notify["active"].connect(() => {
  214. settings.set_boolean(key_newmail,bnotify.active);
  215. });
  216. var bautostart = builder.get_object("sAutostart") as Gtk.Switch;
  217. try { string content;
  218. if (GLib.FileUtils.get_contents(XDG_AUTORUN_FILE,out content)){
  219. if (GLib.Regex.match_simple(GNOME_AUTOSTART_DISABLED,content))
  220. bautostart.set_active(false);
  221. else bautostart.set_active(true);
  222. } else bautostart.set_active(false);
  223. } catch (FileError e) { stderr.printf("%s\n",e.message); }
  224. bautostart.notify["active"].connect(() => {
  225. if (bautostart.active) {
  226. string content;
  227. try {
  228. GLib.FileUtils.get_contents(smd_applet_desktop,out content);
  229. GLib.FileUtils.set_contents(XDG_AUTORUN_FILE,content);
  230. } catch (FileError e) { stderr.printf("%s\n",e.message); }
  231. } else {
  232. GLib.FileUtils.remove(XDG_AUTORUN_FILE);
  233. }
  234. });
  235. var bc = builder.get_object("bClose") as Gtk.Button;
  236. bc.clicked.connect(close_err_action);
  237. var bel = builder.get_object("bEditLoopCfg") as Gtk.Button;
  238. bel.clicked.connect((b) => {
  239. // if not existent, create the template first
  240. try {
  241. if (!is_smd_loop_configured()){
  242. GLib.Process.spawn_command_line_sync(
  243. "%s -t".printf(smd_loop_cmd));
  244. }
  245. string cmd = "gnome-open %s".printf(SMD_LOOP_CFG);
  246. GLib.Process.spawn_command_line_async(cmd);
  247. is_smd_loop_configured();
  248. } catch (GLib.SpawnError e) {
  249. stderr.printf("%s\n",e.message);
  250. }
  251. });
  252. var bepp = builder.get_object("bEditPushPullCfg") as Gtk.Button;
  253. bepp.clicked.connect((b) => {
  254. // if not existent, create the template first
  255. try {
  256. if (!is_smd_pushpull_configured()){
  257. GLib.Process.spawn_command_line_sync(
  258. "%s -t".printf(smd_push_cmd));
  259. }
  260. string cmd = "gnome-open %s".printf(SMD_PP_DEF_CFG);
  261. GLib.Process.spawn_command_line_async(cmd);
  262. is_smd_pushpull_configured();
  263. } catch (GLib.SpawnError e) {
  264. stderr.printf("%s\n",e.message);
  265. }
  266. });
  267. // menu popped up when the user clicks on the notification area
  268. sync_active = builder.get_object("sSyncActive") as Gtk.Switch;
  269. sync_active.notify["active"].connect(() => {
  270. if (sync_active.get_active()) unpause();
  271. else pause();
  272. });
  273. notebook = builder.get_object("nMain") as Gtk.Notebook;
  274. update_loglist();
  275. GLib.Timeout.add(2000,(() => {
  276. if (win.visible) {
  277. update_loglist(); update_logcontents();
  278. }
  279. return true; }));
  280. // status icon
  281. si = new Gtk.StatusIcon.from_icon_name("mail-send-receive");
  282. si.set_visible(true);
  283. si.set_tooltip_text("smd-applet is running");
  284. si.activate.connect((s) => {
  285. if ( error_mode )
  286. err_win.reshow_with_initial_size();
  287. else {
  288. win.show();
  289. if ( config_wait_mode ) notebook.page = 1;
  290. else notebook.page = 0;
  291. }
  292. });
  293. si.popup_menu.connect(() => {
  294. sync_active.activate();
  295. });
  296. add_window(win);
  297. activate.connect(() => { });
  298. var quit = new SimpleAction("quit", null);
  299. var menu = builder.get_object("app-menu") as MenuModel;
  300. set_app_menu(menu);
  301. add_action(quit);
  302. quit.activate.connect(() => {
  303. thread_die = true;
  304. if ((int)pid != 0) {
  305. debug("sending SIGTERM to %d".printf(-(int)pid));
  306. Posix.kill((Posix.pid_t)(-(int)pid),Posix.SIGTERM);
  307. }
  308. this.quit();
  309. });
  310. // notification system
  311. #if VALA_0_24
  312. var l = Notify.get_server_caps ();
  313. #else
  314. var l = notify_get_server_caps ();
  315. #endif
  316. notification_server_has_persistence = (0 <= l.index("persistence"));
  317. // error mode data
  318. command_hash = new GLib.HashTable<Gtk.Widget,string>(
  319. GLib.direct_hash,GLib.str_equal);
  320. }
  321. // ===================== smd-loop handling ============================
  322. // This thread fills the event queue, parsing the
  323. // stdout of a child process
  324. private void *smdThread() {
  325. bool rc = true;
  326. while(rc && !thread_die){
  327. debug("(re)starting smd-loop");
  328. try { rc = run_smd_loop(); }
  329. catch (Exit e) { rc = false; } // unrecoverable error
  330. }
  331. return null;
  332. }
  333. // force starts the thread even if there is no connection
  334. private void start_smdThread(bool force = false) {
  335. // if no network, we do not start the thread and enter pause mode
  336. // immediately
  337. if (!force && net_manager != null && !is_nm_connected(net_manager.state)) {
  338. sync_active.set_active(false);
  339. si.set_from_stock("gtk-media-pause");
  340. } else {
  341. // the thread fills the event queue
  342. thread = new GLib.Thread<void *>(null, smdThread);
  343. }
  344. }
  345. private bool eval_smd_loop_error_message(
  346. string args, string account, string host) throws GLib.RegexError{
  347. var context = new GLib.Regex("context\\(([^\\)]+)\\)");
  348. var cause = new GLib.Regex("probable-cause\\(([^\\)]+)\\)");
  349. var human = new GLib.Regex("human-intervention\\(([^\\)]+)\\)");
  350. var actions=new GLib.Regex("suggested-actions\\((.*)\\) *$");
  351. GLib.MatchInfo i_ctx=null, i_cause=null, i_human=null, i_act=null;
  352. if (! context.match(args,0,out i_ctx)){
  353. stderr.printf("smd-loop error with no context: %s\n",args);
  354. return true;
  355. }
  356. if (! cause.match(args,0,out i_cause)){
  357. stderr.printf("smd-loop error with no cause: %s\n",args);
  358. return true;
  359. }
  360. if (! human.match(args,0,out i_human)){
  361. stderr.printf("smd-loop error with no human: %s\n",args);
  362. return true;
  363. }
  364. var has_actions = actions.match(args,0,out i_act);
  365. if ( i_human.fetch(1) != "necessary" && i_cause.fetch(1) == "network"){
  366. events_lock.lock();
  367. events.insert(events.size, Event.network_error());
  368. events_lock.unlock();
  369. return true;
  370. }
  371. if ( i_human.fetch(1) != "necessary" ){
  372. stderr.printf("smd-loop giving an avoidable error: %s\n", args);
  373. events_lock.lock();
  374. events.insert(events.size, Event.generic_error(i_cause.fetch(1)));
  375. events_lock.unlock();
  376. return true;
  377. }
  378. string permissions = null;
  379. string mail_name = null;
  380. string mail_body = null;
  381. var commands = new Gee.ArrayList<string>(str_equal);
  382. if (has_actions) {
  383. string acts = i_act.fetch(1);
  384. var r_perm = new GLib.Regex("display-permissions\\(([^\\)]+)\\)");
  385. var r_mail = new GLib.Regex("display-mail\\(([^\\)]+)\\)");
  386. var r_cmd = new GLib.Regex("run\\(([^\\)]+)\\)");
  387. int from = 0;
  388. for (;acts != null && acts.length > 0;){
  389. MatchInfo i_cmd = null;
  390. if ( r_perm.match(acts,0,out i_cmd) ){
  391. i_cmd.fetch_pos(0,null,out from);
  392. string file = i_cmd.fetch(1);
  393. string output = null;
  394. string err = null;
  395. try {
  396. GLib.Process.spawn_command_line_sync(
  397. "ls -ld " + file, out output, out err);
  398. permissions = output + err;
  399. } catch (GLib.SpawnError e) {
  400. stderr.printf("Spawning ls: %s\n",e.message);
  401. }
  402. } else if ( r_mail.match(acts,0,out i_cmd) ){
  403. i_cmd.fetch_pos(0,null,out from);
  404. string file = i_cmd.fetch(1);
  405. string output = "";
  406. string err = null;
  407. try {
  408. mail_name = file;
  409. GLib.Process.spawn_command_line_sync(
  410. "cat " + file, out output, out err);
  411. mail_body = output + err;
  412. } catch (GLib.SpawnError e) {
  413. stderr.printf("Spawning ls: %s\n",e.message);
  414. }
  415. } else if ( r_cmd.match(acts,0,out i_cmd) ){
  416. string command = i_cmd.fetch(1);
  417. i_cmd.fetch_pos(0,null,out from);
  418. commands.insert(commands.size,command);
  419. } else {
  420. stderr.printf("Unrecognized action: %s\n",acts);
  421. break;
  422. }
  423. acts = acts.substring(from);
  424. }
  425. }
  426. events_lock.lock();
  427. events.insert(events.size, Event.error(
  428. account,host,i_ctx.fetch(1), i_cause.fetch(1),
  429. permissions, mail_name, mail_body, commands));
  430. events_lock.unlock();
  431. return false;
  432. }
  433. // return true if successful, false to stop due to an error
  434. private bool eval_smd_loop_message(string s){
  435. try {
  436. GLib.MatchInfo info = null;
  437. var r_tags = new GLib.Regex(
  438. "^([^:]+): smd-(client|server|loop|push|pull|pushpull)@([^:]+): TAGS:(.*)$");
  439. var r_skip = new GLib.Regex(
  440. "^([^:]+): smd-(client|server)@([^:]+): ERROR");
  441. if (r_skip.match(s,0,null)) { return true; }
  442. if (!r_tags.match(s,0,out info)) {
  443. debug("unhandled smd-loop message: %s".printf(s));
  444. return true;
  445. }
  446. var account = info.fetch(1);
  447. var host = info.fetch(3);
  448. var tags = info.fetch(4);
  449. GLib.MatchInfo i_args = null;
  450. var r_stats = new GLib.Regex(" stats::(.*)$");
  451. var r_error = new GLib.Regex(" error::(.*)$");
  452. if (r_stats.match(tags,0,out i_args)) {
  453. var r_neW = new GLib.Regex("new-mails\\(([0-9]+)\\)");
  454. var r_del = new GLib.Regex("del-mails\\(([0-9]+)\\)");
  455. GLib.MatchInfo i_new = null, i_del = null;
  456. var args = i_args.fetch(1);
  457. // check if matches
  458. var has_new = r_neW.match(args,0,out i_new);
  459. var has_del = r_del.match(args,0,out i_del);
  460. int new_mails = 0;
  461. if (has_new) { new_mails = int.parse(i_new.fetch(1)); }
  462. int del_mails = 0;
  463. if (has_del) { del_mails = int.parse(i_del.fetch(1)); }
  464. if (host == "localhost" && (new_mails > 0 || del_mails > 0)) {
  465. events_lock.lock();
  466. events.insert(events.size,
  467. Event.stats(account,host,new_mails, del_mails));
  468. events_lock.unlock();
  469. } else {
  470. // we skip non-error messages from the remote host
  471. }
  472. return true;
  473. } else if (r_error.match(tags,0,out i_args)) {
  474. var args = i_args.fetch(1);
  475. return eval_smd_loop_error_message(args,account,host);
  476. } else {
  477. stderr.printf("unhandled smd-loop message: %s\n",s);
  478. return true;
  479. }
  480. } catch (GLib.RegexError e) { stderr.printf("%s\n",e.message); }
  481. return true;
  482. }
  483. // runs smd loop once, returns true if it stopped
  484. // with a recoverable error and thus should be restarted
  485. private bool run_smd_loop() throws Exit {
  486. string[] cmd = { smd_loop_cmd, "-v" };
  487. int child_in;
  488. int child_out;
  489. int child_err;
  490. char[] buff = new char[10240];
  491. GLib.SpawnFlags flags = 0;
  492. bool rc;
  493. debug("spawning %s\n".printf(smd_loop_cmd));
  494. try {
  495. rc = GLib.Process.spawn_async_with_pipes(
  496. null,cmd,null,flags,() => {
  497. // create a new session
  498. Posix.setpgid(0,0);
  499. },
  500. out pid, out child_in, out child_out, out child_err);
  501. } catch (GLib.Error e) {
  502. stderr.printf("Unable to execute "+
  503. smd_loop_cmd+": "+e.message+"\n");
  504. throw new Exit.ABORT("Unable to run smd-loop");
  505. }
  506. if (rc) {
  507. var input = GLib.FileStream.fdopen(child_out,"r");
  508. string s = null;
  509. bool goon = true;
  510. while ( goon && (s = input.gets(buff)) != null && !thread_die) {
  511. debug("smd-loop outputs: %s".printf(s));
  512. goon = eval_smd_loop_message(s);
  513. debug("eval_smd_loop_message returned %d".printf((int)goon));
  514. }
  515. if ( s != null ) {
  516. // smd-loop prints the error tag as its last action
  517. if ( (s = input.gets(buff)) != null ){
  518. stderr.printf("smd-loop gave error tag but not died\n");
  519. stderr.printf("smd-loop has pid %d and prints %s\n",
  520. (int)pid, s);
  521. }
  522. }
  523. GLib.Process.close_pid(pid);
  524. Posix.kill((Posix.pid_t) (-(int)pid),Posix.SIGTERM);
  525. return goon; // maybe true, if s == null
  526. } else {
  527. stderr.printf("Unable to execute "+smd_loop_cmd+"\n");
  528. throw new Exit.ABORT("Unable to run smd-loop");
  529. }
  530. }
  531. // process an event in the events queue by notifying the user
  532. // with its message
  533. bool eat_event() {
  534. Event e = null;
  535. // in error mode no events are processed
  536. if ( error_mode ) return true;
  537. // fetch the event
  538. events_lock.lock();
  539. if ( events.size > 0) {
  540. e = events.first();
  541. events.remove_at(0);
  542. }
  543. events_lock.unlock();
  544. // notification
  545. if ( e != null && e.message != null) {
  546. bool notify_on_newail = false;
  547. notify_on_newail = settings.get_boolean(key_newmail);
  548. if (e.enter_network_error_mode && network_error_mode) {
  549. // we avoid notifying the network problem more than once
  550. } else if ((!e.is_error_event() && notify_on_newail) ||
  551. (e.is_error_event() && e.enter_network_error_mode)) {
  552. notification = new Notify.Notification(
  553. "Syncmaildir",e.message,e.message_icon);
  554. notification.set_hint("transient",new Variant.boolean(true));
  555. try { notification.show(); }
  556. catch (GLib.Error e) { stderr.printf("%s\n",e.message); }
  557. } else if (e.is_error_event()) {
  558. notification = new Notify.Notification(
  559. "Syncmaildir",e.message,e.message_icon);
  560. notification.set_timeout(0);
  561. notification.add_action("clicked","Handle error",
  562. (not, action) => { err_win.reshow_with_initial_size(); });
  563. try { notification.show(); }
  564. catch (GLib.Error e) { stderr.printf("%s\n",e.message); }
  565. }
  566. }
  567. // behavioural changes, like entering error mode
  568. if ( e != null && e.enter_error_mode ) {
  569. // {{{ error notification and widget setup
  570. si.set_from_icon_name("error");
  571. si.set_tooltip_text("smd-applet encountered an error");
  572. error_mode = true;
  573. if (!notification_server_has_persistence) si.set_visible(true);
  574. var l_ctx = builder.get_object("lContext") as Gtk.Label;
  575. var l_cause = builder.get_object("lCause") as Gtk.Label;
  576. l_ctx.set_text(e.context);
  577. l_cause.set_text(e.cause);
  578. command_hash.remove_all();
  579. var vb = builder.get_object("vbRun") as Gtk.Grid;
  580. foreach(Gtk.Widget w in vb.get_children()){ vb.remove(w); }
  581. if (e.permissions != null) {
  582. var l = builder.get_object("lPermissions") as Gtk.Label;
  583. l.set_text(e.permissions);
  584. }
  585. if (e.mail_name != null) {
  586. var fn = builder.get_object("eMailName") as Gtk.Entry;
  587. fn.set_text(e.mail_name);
  588. var l = builder.get_object("tvMail") as Gtk.TextView;
  589. Gtk.TextBuffer b = l.get_buffer();
  590. b.set_text(e.mail_body,-1);
  591. Gtk.TextIter it,subj;
  592. b.get_start_iter(out it);
  593. if (it.forward_search("Subject:",
  594. Gtk.TextSearchFlags.TEXT_ONLY, out subj,null,null)){
  595. var insert = b.get_insert();
  596. b.select_range(subj,subj);
  597. l.scroll_to_mark(insert,0.0,true,0.0,0.0);
  598. }
  599. }
  600. if (e.commands != null) {
  601. foreach (string command in e.commands) {
  602. var hb = new Gtk.Grid();
  603. hb.set_column_homogeneous(false);
  604. hb.set_column_spacing(10);
  605. string nice_command;
  606. try {
  607. GLib.MatchInfo i_mailto;
  608. var mailto_rex = new GLib.Regex("^gnome-open..mailto:");
  609. if ( mailto_rex.match(command,0,out i_mailto)) {
  610. nice_command =
  611. GLib.Uri.unescape_string(command).
  612. substring(12,70) + "...";
  613. } else {
  614. nice_command = command;
  615. }
  616. } catch (GLib.RegexError e) {
  617. nice_command = command;
  618. }
  619. var lbl = new Gtk.Label(nice_command);
  620. lbl.set_alignment(0.0f,0.5f);
  621. var but = new Gtk.Button.from_stock("gtk-execute");
  622. command_hash.insert(but,command);
  623. but.clicked.connect((b) => {
  624. int cmd_status;
  625. string output;
  626. string error;
  627. debug("executing: %s\n".printf(command_hash.lookup(b)));
  628. //XXX take host into account
  629. try{
  630. GLib.Process.spawn_command_line_sync(
  631. command_hash.lookup(b),
  632. out output,out error,out cmd_status);
  633. if (GLib.Process.if_exited(cmd_status) &&
  634. 0==GLib.Process.exit_status(cmd_status)){
  635. // OK!
  636. b.set_sensitive(false);
  637. } else {
  638. var w = new Gtk.MessageDialog(err_win,
  639. Gtk.DialogFlags.MODAL, Gtk.MessageType.ERROR,
  640. Gtk.ButtonsType.CLOSE,
  641. "An error occurred:\n%s\n%s",output,error);
  642. w.run();
  643. w.destroy();
  644. }
  645. } catch (GLib.SpawnError e) {
  646. stderr.printf("Spawning: %s\n",e.message);
  647. }
  648. });
  649. hb.attach(lbl,1,0,1,1);
  650. hb.attach(but,0,0,1,1);
  651. vb.add(hb);
  652. hb.show_all();
  653. }
  654. }
  655. var x = builder.get_object("fDisplayPermissions") as Gtk.Widget;
  656. x.visible = (e.permissions != null);
  657. x = builder.get_object("fDisplayMail") as Gtk.Widget;
  658. x.visible = (e.mail_name != null);
  659. x = builder.get_object("fRun") as Gtk.Widget;
  660. x.visible = (e.commands.size > 0);
  661. // }}}
  662. } else if (e != null && e.enter_network_error_mode ) {
  663. // network error warning
  664. network_error_mode = true;
  665. si.set_from_icon_name("dialog-warning");
  666. si.set_tooltip_text("Network error");
  667. } else if (e != null) {
  668. // no error
  669. network_error_mode = false;
  670. si.set_from_icon_name("mail-send-receive");
  671. si.set_tooltip_text("smd-applet is running");
  672. }
  673. return true; // re-schedule me please
  674. }
  675. // ===================== named signal handlers =======================
  676. // these are just wrappers for close_err
  677. private void close_err_action(Gtk.Button b){ reset_to_regular_run(); }
  678. private bool close_err_event(Gdk.EventAny e){
  679. reset_to_regular_run();
  680. return true;
  681. }
  682. private void reset_to_regular_run(bool force = false) {
  683. err_win.hide();
  684. error_mode = false;
  685. si.set_tooltip_text("smd-applet is running");
  686. si.set_from_icon_name("mail-send-receive");
  687. debug("joining smdThread");
  688. if (thread != null) thread.join();
  689. thread_die = false;
  690. debug("starting smdThread");
  691. start_smdThread(force);
  692. }
  693. // these are just wrappers for close_prefs
  694. private bool close_prefs_event(Gdk.EventAny e){
  695. close_prefs();
  696. return true;
  697. }
  698. // close the prefs button, eventually start the theread if exiting
  699. // config_wait_mode
  700. private void close_prefs(){
  701. win.hide();
  702. if (is_smd_stack_configured() && config_wait_mode) {
  703. config_wait_mode = false;
  704. // restore the default icon
  705. si.set_from_icon_name("mail-send-receive");
  706. // start the thread (if connected)
  707. debug("starting smdThread since smd stack is configured");
  708. start_smdThread();
  709. }
  710. }
  711. // pause/unpause the program
  712. private void pause() {
  713. debug("enter pause mode");
  714. if ((int)pid != 0) {
  715. debug("sending SIGTERM to %d".printf(-(int)pid));
  716. Posix.kill((Posix.pid_t)(-(int)pid),Posix.SIGTERM);
  717. }
  718. thread_die = true;
  719. si.set_from_stock("gtk-media-pause");
  720. si.set_tooltip_text("smd-applet is paused");
  721. }
  722. private void unpause() {
  723. debug("exit pause mode");
  724. reset_to_regular_run(true);
  725. }
  726. // ======================== config check ===========================
  727. private bool is_smd_loop_configured() {
  728. bool rc = GLib.FileUtils.test(SMD_LOOP_CFG,GLib.FileTest.EXISTS);
  729. var l = builder.get_object("bErrLoop") as Gtk.Box;
  730. if (!rc) l.show();
  731. else l.hide();
  732. return rc;
  733. }
  734. private bool is_smd_pushpull_configured() {
  735. bool rc = GLib.FileUtils.test(SMD_PP_DEF_CFG,GLib.FileTest.EXISTS);
  736. var l = builder.get_object("bErrPushPull") as Gtk.Box;
  737. if (!rc) l.show();
  738. else l.hide();
  739. return rc;
  740. }
  741. private bool is_smd_stack_configured() {
  742. var a = is_smd_loop_configured();
  743. var b = is_smd_pushpull_configured();
  744. return a && b;
  745. }
  746. // ======================== log window ================================
  747. private void update_loglist(){
  748. var tv = builder.get_object("tvLog") as Gtk.TextView;
  749. var b = tv.get_buffer();
  750. try {
  751. Dir d = GLib.Dir.open(SMD_LOGS_DIR);
  752. string file;
  753. var new_lognames = new Gee.ArrayList<string>(str_equal);
  754. while ( (file = d.read_name()) != null ){
  755. new_lognames.add(file);
  756. }
  757. if (!new_lognames.contains_all(lognames) ||
  758. !lognames.contains_all(new_lognames)) {
  759. ((Gtk.ListStore)cblogs.get_model()).clear();
  760. lognames.clear();
  761. foreach (string f in new_lognames) {
  762. lognames.add(f);
  763. cblogs.append_text(f);
  764. }
  765. if (lognames.size == 0) {
  766. b.set_text("No logs in %s".printf(SMD_LOGS_DIR),-1);
  767. } else {
  768. cblogs.set_title("Choose log file");
  769. cblogs.set_active(0);
  770. }
  771. }
  772. } catch (GLib.FileError e) {
  773. b.set_text("Unable to list directory %s".printf(SMD_LOGS_DIR),-1);
  774. }
  775. }
  776. void update_logcontents() {
  777. int selected = cblogs.get_active();
  778. if (selected >= 0) {
  779. string file = lognames.get(selected);
  780. string content;
  781. try {
  782. if (GLib.FileUtils.get_contents(
  783. SMD_LOGS_DIR+file,out content)){
  784. var tv = builder.get_object("tvLog") as Gtk.TextView;
  785. var b = tv.get_buffer();
  786. Gtk.TextIter end_iter, start_iter;
  787. b.get_end_iter(out end_iter);
  788. b.get_start_iter(out start_iter);
  789. if (content != b.get_text(start_iter,end_iter,false)) {
  790. b.set_text(content,-1);
  791. b.get_end_iter(out end_iter);
  792. tv.scroll_to_iter(end_iter, 0.0, true, 0.0, 0.0);
  793. }
  794. } else {
  795. stderr.printf("Unable to read %s\n",SMD_LOGS_DIR+file);
  796. }
  797. } catch (GLib.FileError e) {
  798. stderr.printf("Unable to read %s: %s\n",
  799. SMD_LOGS_DIR+file, e.message);
  800. }
  801. }
  802. }
  803. // ====================== public methods ==============================
  804. // starts the thread and the timeout handler
  805. public void start() throws Exit {
  806. // the timout function that will eventually notify the user
  807. GLib.Timeout.add(1000, eat_event);
  808. // before running, we need the whole smd stack
  809. // to be configured
  810. if (is_smd_stack_configured()) {
  811. start_smdThread();
  812. } else {
  813. config_wait_mode = true;
  814. }
  815. // windows will last for the whole execution,
  816. // so the (x) button should just hide them
  817. win.delete_event.connect(close_prefs_event);
  818. err_win.delete_event.connect(close_err_event);
  819. // we show the icon if we have to.
  820. // this is performed here and not in the constructor
  821. // since if we passed --configure the icon has not
  822. // to be shown
  823. if ( config_wait_mode ) {
  824. while ( Gtk.events_pending() ) Gtk.main_iteration();
  825. si.set_visible(false);
  826. // we wait a bit, hopefully the gnome bar will be drawn in the
  827. // meanwhile
  828. Posix.sleep(5);
  829. // we draw the icon
  830. si.set_visible(true);
  831. si.set_from_icon_name("error");
  832. // we process events to have the icon before the notification baloon
  833. while ( Gtk.events_pending() ) Gtk.main_iteration();
  834. // we do the notification
  835. notification = new Notify.Notification(
  836. "Syncmaildir","Syncmaildir is not configured properly.",
  837. "dialog-error");
  838. notification.set_hint("transient",new Variant.boolean(true));
  839. notification.add_action("configure","Configure now",((n,a) =>
  840. { si.activate(); }));
  841. try { notification.show(); }
  842. catch (GLib.Error e) { stderr.printf("%s\n",e.message); }
  843. }
  844. base.run();
  845. if (thread != null) thread.join();
  846. }
  847. } // class end
  848. // =================== main =====================================
  849. static int main(string[] args){
  850. string PREFIX = SMDConf.PREFIX;
  851. // handle prefix
  852. if (! GLib.FileUtils.test(PREFIX + SMD_APPLET_UI,GLib.FileTest.EXISTS)) {
  853. stderr.printf("error: file not found: %s + %s\n",
  854. PREFIX, SMD_APPLET_UI);
  855. smdApplet.smd_loop_cmd = "./smd-loop";
  856. stderr.printf("smd-applet not installed, " +
  857. "assuming smd-loop is: %s\n", smdApplet.smd_loop_cmd);
  858. smdApplet.smd_applet_ui = "./smd-applet.ui";
  859. stderr.printf("smd-applet not installed, " +
  860. "assuming smd-applet.ui is: %s\n", smdApplet.smd_applet_ui);
  861. smdApplet.smd_push_cmd = "./smd-push";
  862. stderr.printf("smd-applet not installed, " +
  863. "assuming smd-push is: %s\n", smdApplet.smd_push_cmd);
  864. smdApplet.smd_applet_desktop = "./smd-applet.desktop";
  865. stderr.printf("smd-applet not installed, " +
  866. "assuming smd-applet.desktop is: %s\n",
  867. smdApplet.smd_applet_desktop);
  868. } else {
  869. smdApplet.smd_loop_cmd = PREFIX + SMD_LOOP;
  870. smdApplet.smd_push_cmd = PREFIX + SMD_PUSH;
  871. smdApplet.smd_applet_ui = PREFIX + SMD_APPLET_UI;
  872. smdApplet.smd_applet_desktop = PREFIX + SMD_APPLET_DESKTOP;
  873. }
  874. var homedir = GLib.Environment.get_home_dir();
  875. SMD_LOGS_DIR = homedir+"/.smd/log/";
  876. SMD_LOOP_CFG = homedir+"/.smd/loop";
  877. SMD_PP_DEF_CFG = homedir+"/.smd/config.default";
  878. var conf_home = GLib.Environment.get_variable("XDG_CONFIG_HOME");
  879. if (conf_home != null)
  880. XDG_AUTORUN_FILE = conf_home+"/autostart/smd-applet.desktop";
  881. else
  882. XDG_AUTORUN_FILE = homedir + "/.config/autostart/smd-applet.desktop";
  883. GLib.DirUtils.create_with_parents(
  884. GLib.Path.get_dirname(XDG_AUTORUN_FILE),0700);
  885. // we init gtk+ and notify
  886. Gtk.init (ref args);
  887. Notify.init("smd-applet");
  888. GLib.OptionEntry[] oe = {
  889. GLib.OptionEntry () {
  890. long_name = "verbose", short_name = 'v',
  891. flags = 0, arg = GLib.OptionArg.NONE,
  892. arg_data = &verbose,
  893. description = "verbose output, for debugging only",
  894. arg_description = null },
  895. GLib.OptionEntry () {
  896. long_name = "smd-loop", short_name = 'l',
  897. flags = 0, arg = GLib.OptionArg.STRING,
  898. arg_data = &smdApplet.smd_loop_cmd,
  899. description = "override smd-loop command name, debugging only",
  900. arg_description = "program" },
  901. GLib.OptionEntry () { long_name = null }
  902. };
  903. var oc = new GLib.OptionContext(" - syncmaildir applet");
  904. oc.add_main_entries(oe,null);
  905. try { oc.parse(ref args); }
  906. catch (GLib.OptionError e) { stderr.printf("%s\n",e.message); return 1; }
  907. // go!
  908. try {
  909. var smd_applet = new smdApplet();
  910. smd_applet.start();
  911. } catch (Exit e) {
  912. stderr.printf("abort: %s\n",e.message);
  913. return 1;
  914. }
  915. return 0;
  916. }
  917. // vim:set ts=4 foldmethod=marker: