Make doldacond-shell concurrency safe.
[doldaconnect.git] / clients / gui-shell / dsh.c
1 /*
2  *  Dolda Connect - Modular multiuser Direct Connect-style client
3  *  Copyright (C) 2007 Fredrik Tolf <fredrik@dolda2000.com>
4  *  
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *  
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *  
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 */
19
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <unistd.h>
23 #include <string.h>
24 #include <errno.h>
25 #include <pwd.h>
26 #include <locale.h>
27 #include <libintl.h>
28 #include <signal.h>
29 #include <sys/wait.h>
30 #include <gtk/gtk.h>
31 #include <gdk/gdkkeysyms.h>
32 #include <stdarg.h>
33 #include <doldaconnect/uilib.h>
34 #include <doldaconnect/uimisc.h>
35 #include <doldaconnect/utils.h>
36
37 #ifdef HAVE_CONFIG_H
38 #include <config.h>
39 #endif
40
41 #ifdef HAVE_NOTIFY
42 #include <libnotify/notify.h>
43 #endif
44
45 #define _(text) gettext(text)
46
47 struct trinfo {
48     int ostate;
49     int opos, spos, speed;
50     time_t lastprog;
51     int warned;
52     double sprog;
53 };
54
55 void updatewrite(void);
56
57 int remote = 0;
58 char *server;
59 GtkStatusIcon *tray;
60 pid_t dpid = 0, dcpid = 0;
61 int connected = 0;
62 int dstat;
63 int derrfd, derrtag;
64 char *derrbuf = NULL;
65 size_t derrbufsize = 0, derrbufdata = 0;
66 int dcfd, dcfdrtag, dcfdwtag = -1;
67 GdkPixbuf *dcicon;
68 #ifdef HAVE_NOTIFY
69 NotifyNotification *trnote = NULL;
70 #endif
71
72 #include "dsh-start.gtkh"
73 #include "dsh-menu.gtkh"
74
75 int running(char *pf)
76 {
77     FILE *pfs;
78     char buf[1024];
79     int pid;
80     
81     if((pfs = fopen(pf, "r")) == NULL) {
82         perror(pf);
83         return(0);
84     }
85     fgets(buf, sizeof(buf), pfs);
86     fclose(pfs);
87     if((pid = atoi(buf)) == 0)
88         return(0);
89     return(!kill(pid, 0));
90 }
91
92 void derrcb(gpointer data, gint source, GdkInputCondition cond)
93 {
94     int ret = 0;
95     
96     sizebuf2(derrbuf, derrbufdata + 1024, 1);
97     ret = read(derrfd, derrbuf + derrbufdata, derrbufsize - derrbufdata);
98     if(ret <= 0) {
99         if(ret < 0)
100             bprintf(derrbuf, "\ncould not read from daemon: %s\n", strerror(errno));
101         gdk_input_remove(derrtag);
102         close(derrfd);
103         derrfd = -1;
104     } else {
105         derrbufdata += ret;
106     }
107 }
108
109 int msgbox(int type, int buttons, char *format, ...)
110 {
111     GtkWidget *swnd;
112     va_list args;
113     char *buf;
114     int resp;
115     
116     va_start(args, format);
117     buf = vsprintf2(format, args);
118     va_end(args);
119     swnd = gtk_message_dialog_new(NULL, 0, type, buttons, "%s", buf);
120     resp = gtk_dialog_run(GTK_DIALOG(swnd));
121     gtk_widget_destroy(swnd);
122     free(buf);
123     return(resp);
124 }
125
126 void destroytr(struct dc_transfer *tr)
127 {
128     struct trinfo *tri;
129     
130     tri = tr->udata;
131     free(tri);
132 }
133
134 void inittr(struct dc_transfer *tr)
135 {
136     struct trinfo *tri;
137     
138     tr->udata = tri = memset(smalloc(sizeof(*tri)), 0, sizeof(*tri));
139     tr->destroycb = destroytr;
140     tri->ostate = tr->state;
141     tri->spos = tri->opos = tr->curpos;
142     tri->speed = -1;
143     tri->lastprog = time(NULL);
144     tri->sprog = ntime();
145 }
146
147 #ifdef HAVE_NOTIFY
148 void notify(NotifyNotification **n, char *cat, char *title, char *body, ...)
149 {
150     va_list args;
151     char *bbuf;
152     
153     va_start(args, body);
154     bbuf = vsprintf2(body, args);
155     va_end(args);
156     if(*n == NULL) {
157         *n = notify_notification_new_with_status_icon(title, bbuf, NULL, tray);
158         notify_notification_set_icon_from_pixbuf(*n, dcicon);
159     } else {
160         notify_notification_update(*n, title, bbuf, NULL);
161     }
162     notify_notification_show(*n, NULL);
163 }
164 #endif
165
166 /* XXX: Achtung! Too DC-specific! */
167 wchar_t *getfilename(wchar_t *path)
168 {
169     wchar_t *p;
170     
171     if((p = wcsrchr(path, L'\\')) == NULL)
172         return(path);
173     else
174         return(p + 1);
175 }
176
177 char *bytes2si(long long bytes)
178 {
179     int i;
180     double b;
181     char *sd;
182     static char ret[64];
183     
184     b = bytes;
185     for(i = 0; (b >= 1024) && (i < 4); i++)
186         b /= 1024;
187     if(i == 0)
188         sd = "B";
189     else if(i == 1)
190         sd = "kiB";
191     else if(i == 2)
192         sd = "MiB";
193     else if(i == 3)
194         sd = "GiB";
195     else
196         sd = "TiB";
197     snprintf(ret, 64, "%.1f %s", b, sd);
198     return(ret);
199 }
200
201 void updatetooltip(void)
202 {
203     struct dc_transfer *tr;
204     struct trinfo *tri;
205     int t, i, a, st, bc, bt;
206     char *buf;
207     size_t bufsize, bufdata;
208     
209     t = i = a = 0;
210     st = bc = bt = -1;
211     for(tr = dc_transfers; tr != NULL; tr = tr->next) {
212         if(tr->dir != DC_TRNSD_DOWN)
213             continue;
214         tri = tr->udata;
215         t++;
216         if(tr->state == DC_TRNS_WAITING)
217             i++;
218         else if((tr->state == DC_TRNS_HS) || (tr->state == DC_TRNS_MAIN))
219             a++;
220         if((tr->state == DC_TRNS_MAIN)) {
221             if(bt == -1)
222                 bc = bt = 0;
223             bc += tr->curpos;
224             bt += tr->size;
225             if(tri->speed != -1) {
226                 if(st == -1)
227                     st = 0;
228                 st += tri->speed;
229             }
230         }
231     }
232     buf = NULL;
233     bufsize = bufdata = 0;
234     bprintf(buf, "%s: %i", _("Transfers"), t);
235     if(t > 0)
236         bprintf(buf, " (%i/%i)", i, a);
237     if(bt > 0)
238         bprintf(buf, ", %.1f%%", ((double)bc / (double)bt) * 100.0);
239     if(st != -1)
240         bprintf(buf, ", %s/s", bytes2si(st));
241     addtobuf(buf, 0);
242     gtk_status_icon_set_tooltip(tray, buf);
243     free(buf);
244 }
245
246 void trstatechange(struct dc_transfer *tr, int ostate)
247 {
248     struct trinfo *tri;
249     
250     tri = tr->udata;
251     if((ostate == DC_TRNS_MAIN) && (tr->dir == DC_TRNSD_DOWN)) {
252         if(tr->state == DC_TRNS_DONE) {
253 #ifdef HAVE_NOTIFY
254             if(dcpid == 0)
255                 notify(&trnote, "transfer.complete", _("Transfer complete"), _("Finished downloading %ls from %ls"), getfilename(tr->path), tr->peernick);
256 #endif
257         } else {
258 #ifdef HAVE_NOTIFY
259             if(dcpid == 0)
260                 notify(&trnote, "transfer.error", _("Transfer interrupted"), _("The transfer of %ls from %ls was interrupted from the other side"), getfilename(tr->path), tr->peernick);
261 #endif
262         }
263     }
264     if(tr->state == DC_TRNS_MAIN) {
265         tri->speed = -1;
266         tri->spos = tr->curpos;
267         tri->sprog = ntime();
268     }
269 }
270
271 void updatetrinfo(void)
272 {
273     struct dc_transfer *tr;
274     struct trinfo *tri;
275     time_t now;
276     double dnow;
277     
278     now = time(NULL);
279     dnow = ntime();
280     for(tr = dc_transfers; tr != NULL; tr = tr->next) {
281         if(tr->udata == NULL) {
282             inittr(tr);
283         } else {
284             tri = tr->udata;
285             if(tri->ostate != tr->state) {
286                 trstatechange(tr, tri->ostate);
287                 tri->ostate = tr->state;
288             }
289             if(tri->opos != tr->curpos) {
290                 tri->opos = tr->curpos;
291                 tri->lastprog = now;
292                 tri->warned = 0;
293             }
294 #ifdef HAVE_NOTIFY
295             if((tr->state = DC_TRNS_MAIN) && (now - tri->lastprog > 600) && !tri->warned) {
296                 if(dcpid == 0) {
297                     notify(&trnote, "transfer.error", _("Transfer stalled"), _("The transfer of %ls from %ls has not made progress for 10 minutes"), getfilename(tr->path), tr->peernick);
298                     tri->warned = 1;
299                 }
300             }
301 #endif
302             if((tr->state == DC_TRNS_MAIN) && (dnow - tri->sprog > 10)) {
303                 tri->speed = ((double)(tr->curpos - tri->spos) / (dnow - tri->sprog));
304                 tri->spos = tr->curpos;
305                 tri->sprog = dnow;
306             }
307         }
308     }
309     updatetooltip();
310 }
311
312 void trlscb(int resp, void *data)
313 {
314     updatetrinfo();
315 }
316
317 gint trupdatecb(gpointer data)
318 {
319     updatetrinfo();
320     return(TRUE);
321 }
322
323 void logincb(int err, wchar_t *reason, void *data)
324 {
325     if(err != DC_LOGIN_ERR_SUCCESS) {
326         msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not connect to server"));
327         exit(1);
328     }
329     dc_queuecmd(NULL, NULL, L"notify", L"trans:act", L"on", L"trans:prog", L"on", NULL);
330     dc_gettrlistasync(trlscb, NULL);
331     connected = 1;
332     updatewrite();
333 }
334
335 void dcfdcb(gpointer data, gint source, GdkInputCondition cond)
336 {
337     struct dc_response *resp;
338     
339     if(((cond & GDK_INPUT_READ) && dc_handleread()) || ((cond & GDK_INPUT_WRITE) && dc_handlewrite())) {
340         if(errno == 0) {
341             gtk_main_quit();
342         } else {
343             msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not connect to server: %s"), strerror(errno));
344             exit(1);
345         }
346         return;
347     }
348     while((resp = dc_getresp()) != NULL) {
349         if(!wcscmp(resp->cmdname, L".connect")) {
350             if(resp->code != 201) {
351                 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("The server refused the connection"));
352                 exit(1);
353             } else if(dc_checkprotocol(resp, DC_LATEST)) {
354                 msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Server protocol revision mismatch"));
355                 exit(1);
356             } else {
357                 gtk_status_icon_set_tooltip(tray, _("Authenticating..."));
358                 dc_loginasync(NULL, 1, dc_convnone, logincb, NULL);
359             }
360         } else if(!wcscmp(resp->cmdname, L".notify")) {
361             dc_uimisc_handlenotify(resp);
362             updatetrinfo();
363         }
364         dc_freeresp(resp);
365     }
366     updatewrite();
367 }
368
369 void updatewrite(void)
370 {
371     if(dcfd < 0)
372         return;
373     if(dc_wantwrite()) {
374         if(dcfdwtag == -1)
375             dcfdwtag = gdk_input_add(dcfd, GDK_INPUT_WRITE, dcfdcb, NULL);
376     } else {
377         if(dcfdwtag != -1) {
378             gdk_input_remove(dcfdwtag);
379             dcfdwtag = -1;
380         }
381     }
382 }
383
384 void connectdc(void)
385 {
386     if((dcfd = dc_connect(server)) < 0) {
387         msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not connect to server: %s"), strerror(errno));
388         exit(1);
389     }
390     dcfdrtag = gdk_input_add(dcfd, GDK_INPUT_READ, dcfdcb, NULL);
391     updatewrite();
392     gtk_status_icon_set_tooltip(tray, _("Connecting to server..."));
393 }
394
395 void startdaemon(void)
396 {
397     char pf[1024];
398     int pfd[2], i;
399     sigset_t ss;
400     
401     if(getenv("HOME") != NULL)
402         snprintf(pf, sizeof(pf), "%s/.doldacond.pid", getenv("HOME"));
403     else
404         snprintf(pf, sizeof(pf), "%s/.doldacond.pid", getpwuid(getuid())->pw_dir);
405     if(access(pf, F_OK) || !running(pf)) {
406         pipe(pfd);
407         sigemptyset(&ss);
408         sigaddset(&ss, SIGCHLD);
409         sigprocmask(SIG_BLOCK, &ss, NULL);
410         if((dpid = fork()) == 0) {
411             sigprocmask(SIG_UNBLOCK, &ss, NULL);
412             dup2(pfd[1], 2);
413             for(i = 3; i < FD_SETSIZE; i++)
414                 close(i);
415             execlp("doldacond", "doldacond", "-p", pf, NULL);
416             perror("doldacond");
417             exit(127);
418         }
419         if(dpid == -1)
420             abort();
421         close(pfd[1]);
422         derrfd = pfd[0];
423         derrtag = gdk_input_add(derrfd, GDK_INPUT_READ, derrcb, NULL);
424         create_start_wnd();
425         gtk_widget_show(start_wnd);
426         gtk_status_icon_set_tooltip(tray, _("Starting..."));
427         sigprocmask(SIG_UNBLOCK, &ss, NULL);
428     } else {
429         connectdc();
430     }
431 }
432
433 gboolean daemonized(gpointer uu)
434 {
435     gtk_widget_hide(start_wnd);
436     dpid = 0;
437     if(derrfd != -1) {
438         gdk_input_remove(derrtag);
439         close(derrfd);
440     }
441     if(dstat != 0) {
442         gtk_status_icon_set_visible(tray, FALSE);
443         gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(start_log)), derrbuf, derrbufdata);
444         gtk_widget_show(start_errwnd);
445     } else {
446         connectdc();
447     }
448     return(FALSE);
449 }
450
451 void sighandler(int sig)
452 {
453     pid_t p;
454     int status;
455     
456     if(sig == SIGCHLD) {
457         while((p = waitpid(-1, &status, WNOHANG)) > 0) {
458             if(p == dpid) {
459                 dstat = status;
460                 gtk_timeout_add(1, daemonized, NULL);
461             } else if(p == dcpid) {
462                 dcpid = 0;
463             }
464         }
465     }
466 }
467
468 void dolcon(void)
469 {
470     int i;
471     
472     if((dcpid = fork()) == 0) {
473         for(i = 3; i < FD_SETSIZE; i++)
474             close(i);
475         if(remote)
476             execlp("dolcon", "dolcon", NULL);
477         else
478             execlp("dolcon", "dolcon", "-l", NULL);
479         perror("dolcon");
480         exit(127);
481     }
482 }
483
484 void cb_shm_dolconf_activate(GtkWidget *uu1, gpointer uu2)
485 {
486     int i;
487     
488     if((dcpid = fork()) == 0) {
489         for(i = 3; i < FD_SETSIZE; i++)
490             close(i);
491         execlp("dolconf", "dolconf", NULL);
492         perror("dolconf");
493         exit(127);
494     }
495 }
496
497 void cb_shm_dolcon_activate(GtkWidget *uu1, gpointer uu2)
498 {
499     dolcon();
500 }
501
502 void cb_shm_quit_activate(GtkWidget *uu1, gpointer uu2)
503 {
504     dc_queuecmd(NULL, NULL, L"shutdown", NULL);
505     updatewrite();
506 }
507
508 void tray_activate(GtkStatusIcon *uu1, gpointer uu2)
509 {
510     if(dpid != 0) {
511         gtk_widget_show(start_wnd);
512     } else if(connected) {
513         dolcon();
514     }
515 }
516
517 void tray_popup(GtkStatusIcon *uu1, guint button, guint time, gpointer uu2)
518 {
519     gtk_menu_popup(GTK_MENU(shm_menu), NULL, NULL, NULL, NULL, button, time);
520 }
521
522 void cb_start_hide_clicked(GtkWidget *uu1, gpointer uu2)
523 {
524     gtk_widget_hide(start_wnd);
525 }
526
527 void cb_start_abort_clicked(GtkWidget *uu1, gpointer uu2)
528 {
529     kill(dpid, SIGINT);
530     exit(0);
531 }
532
533 void cb_start_errok_clicked(GtkWidget *uu1, gpointer uu2)
534 {
535     gtk_main_quit();
536 }
537
538 #include "../dolda-icon.xpm"
539
540 void inittray(void)
541 {
542     tray = gtk_status_icon_new_from_pixbuf(gdk_pixbuf_scale_simple(dcicon, 24, 24, GDK_INTERP_BILINEAR));
543     gtk_status_icon_set_tooltip(tray, "");
544     g_signal_connect(G_OBJECT(tray), "activate", G_CALLBACK(tray_activate), (gpointer)NULL);
545     g_signal_connect(G_OBJECT(tray), "popup-menu", G_CALLBACK(tray_popup), (gpointer)NULL);
546 }
547
548 int main(int argc, char **argv)
549 {
550     int c;
551     
552     setlocale(LC_ALL, "");
553     bindtextdomain(PACKAGE, LOCALEDIR);
554     textdomain(PACKAGE);
555     signal(SIGCHLD, sighandler);
556     dc_init();
557     server = dc_srv_local;
558     gtk_init(&argc, &argv);
559 #ifdef HAVE_NOTIFY
560     notify_init("Dolda Connect");
561 #endif
562     while((c = getopt(argc, argv, "rhs:")) != -1) {
563         switch(c) {
564         case 'r':
565             remote = 1;
566             server = NULL;
567             break;
568         case 's':
569             remote = 1;
570             server = optarg;
571             break;
572         case 'h':
573             printf("usage: doldacond-shell [-hr]\n");
574             printf("\t-h\tDisplay this help message\n");
575             printf("\t-r\tConnect to a remote host\n");
576             exit(0);
577         default:
578             fprintf(stderr, "usage: doldacond-shell [-hr]\n");
579             exit(1);
580         }
581     }
582
583     create_shm_wnd();
584     dcicon = gdk_pixbuf_new_from_xpm_data((const char **)dolda_icon_xpm);
585     gtk_window_set_default_icon(dcicon);
586     inittray();
587     if(remote)
588         connectdc();
589     else
590         startdaemon();
591     
592     g_timeout_add(10000, trupdatecb, NULL);
593     gtk_main();
594
595     return(0);
596 }
597
598 #include "dsh-start.gtk"
599 #include "dsh-menu.gtk"