Make doldacond-shell concurrency safe.
[doldaconnect.git] / clients / gui-shell / dsh.c
CommitLineData
34d45a15
FT
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
47struct trinfo {
48 int ostate;
0d12617c 49 int opos, spos, speed;
fa9a8ac1 50 time_t lastprog;
05ac1f10 51 int warned;
0d12617c 52 double sprog;
34d45a15
FT
53};
54
55void updatewrite(void);
56
71aeadfc 57int remote = 0;
a0ca560e 58char *server;
34d45a15
FT
59GtkStatusIcon *tray;
60pid_t dpid = 0, dcpid = 0;
61int connected = 0;
62int dstat;
63int derrfd, derrtag;
64char *derrbuf = NULL;
65size_t derrbufsize = 0, derrbufdata = 0;
66int dcfd, dcfdrtag, dcfdwtag = -1;
67GdkPixbuf *dcicon;
68#ifdef HAVE_NOTIFY
69NotifyNotification *trnote = NULL;
70#endif
71
72#include "dsh-start.gtkh"
73#include "dsh-menu.gtkh"
74
75int 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
92void 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
109int 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
126void destroytr(struct dc_transfer *tr)
127{
128 struct trinfo *tri;
129
130 tri = tr->udata;
131 free(tri);
132}
133
134void 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;
0d12617c
FT
141 tri->spos = tri->opos = tr->curpos;
142 tri->speed = -1;
fa9a8ac1 143 tri->lastprog = time(NULL);
0d12617c 144 tri->sprog = ntime();
34d45a15
FT
145}
146
147#ifdef HAVE_NOTIFY
148void 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
fa9a8ac1
FT
166/* XXX: Achtung! Too DC-specific! */
167wchar_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
0d12617c
FT
177char *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
201void updatetooltip(void)
202{
203 struct dc_transfer *tr;
204 struct trinfo *tri;
0c095756 205 int t, i, a, st, bc, bt;
0d12617c
FT
206 char *buf;
207 size_t bufsize, bufdata;
208
209 t = i = a = 0;
0c095756 210 st = bc = bt = -1;
0d12617c
FT
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++;
0c095756
FT
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 }
0d12617c
FT
230 }
231 }
232 buf = NULL;
233 bufsize = bufdata = 0;
f782eb97 234 bprintf(buf, "%s: %i", _("Transfers"), t);
0d12617c
FT
235 if(t > 0)
236 bprintf(buf, " (%i/%i)", i, a);
0c095756 237 if(bt > 0)
2c16e7a5 238 bprintf(buf, ", %.1f%%", ((double)bc / (double)bt) * 100.0);
0c095756 239 if(st != -1)
0d12617c 240 bprintf(buf, ", %s/s", bytes2si(st));
0d12617c
FT
241 addtobuf(buf, 0);
242 gtk_status_icon_set_tooltip(tray, buf);
243 free(buf);
244}
245
34d45a15
FT
246void trstatechange(struct dc_transfer *tr, int ostate)
247{
0d12617c
FT
248 struct trinfo *tri;
249
250 tri = tr->udata;
fa9a8ac1 251 if((ostate == DC_TRNS_MAIN) && (tr->dir == DC_TRNSD_DOWN)) {
34d45a15
FT
252 if(tr->state == DC_TRNS_DONE) {
253#ifdef HAVE_NOTIFY
43edea91
FT
254 if(dcpid == 0)
255 notify(&trnote, "transfer.complete", _("Transfer complete"), _("Finished downloading %ls from %ls"), getfilename(tr->path), tr->peernick);
34d45a15
FT
256#endif
257 } else {
258#ifdef HAVE_NOTIFY
43edea91
FT
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);
34d45a15
FT
261#endif
262 }
263 }
0d12617c
FT
264 if(tr->state == DC_TRNS_MAIN) {
265 tri->speed = -1;
266 tri->spos = tr->curpos;
267 tri->sprog = ntime();
268 }
34d45a15
FT
269}
270
271void updatetrinfo(void)
272{
273 struct dc_transfer *tr;
274 struct trinfo *tri;
fa9a8ac1 275 time_t now;
0d12617c 276 double dnow;
34d45a15 277
fa9a8ac1 278 now = time(NULL);
0d12617c 279 dnow = ntime();
34d45a15
FT
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 }
fa9a8ac1
FT
289 if(tri->opos != tr->curpos) {
290 tri->opos = tr->curpos;
291 tri->lastprog = now;
05ac1f10 292 tri->warned = 0;
fa9a8ac1 293 }
05ac1f10
FT
294#ifdef HAVE_NOTIFY
295 if((tr->state = DC_TRNS_MAIN) && (now - tri->lastprog > 600) && !tri->warned) {
296 if(dcpid == 0) {
43edea91 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);
05ac1f10
FT
298 tri->warned = 1;
299 }
fa9a8ac1
FT
300 }
301#endif
0d12617c
FT
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 }
34d45a15
FT
307 }
308 }
0d12617c 309 updatetooltip();
34d45a15
FT
310}
311
312void trlscb(int resp, void *data)
313{
314 updatetrinfo();
315}
316
0d12617c
FT
317gint trupdatecb(gpointer data)
318{
319 updatetrinfo();
320 return(TRUE);
321}
322
34d45a15
FT
323void 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 }
34d45a15
FT
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
335void 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 }
d20e3861 364 dc_freeresp(resp);
34d45a15
FT
365 }
366 updatewrite();
367}
368
369void 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
384void connectdc(void)
385{
a0ca560e 386 if((dcfd = dc_connect(server)) < 0) {
34d45a15
FT
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
395void startdaemon(void)
396{
397 char pf[1024];
398 int pfd[2], i;
a4159022 399 sigset_t ss;
34d45a15
FT
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);
a4159022
FT
407 sigemptyset(&ss);
408 sigaddset(&ss, SIGCHLD);
409 sigprocmask(SIG_BLOCK, &ss, NULL);
34d45a15 410 if((dpid = fork()) == 0) {
a4159022 411 sigprocmask(SIG_UNBLOCK, &ss, NULL);
34d45a15
FT
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..."));
a4159022 427 sigprocmask(SIG_UNBLOCK, &ss, NULL);
34d45a15
FT
428 } else {
429 connectdc();
430 }
431}
432
433gboolean 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
451void 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
468void dolcon(void)
469{
470 int i;
471
472 if((dcpid = fork()) == 0) {
473 for(i = 3; i < FD_SETSIZE; i++)
474 close(i);
71aeadfc
FT
475 if(remote)
476 execlp("dolcon", "dolcon", NULL);
477 else
478 execlp("dolcon", "dolcon", "-l", NULL);
34d45a15
FT
479 perror("dolcon");
480 exit(127);
481 }
482}
483
31a01fdd
FT
484void 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
34d45a15
FT
497void cb_shm_dolcon_activate(GtkWidget *uu1, gpointer uu2)
498{
499 dolcon();
500}
501
502void cb_shm_quit_activate(GtkWidget *uu1, gpointer uu2)
503{
504 dc_queuecmd(NULL, NULL, L"shutdown", NULL);
505 updatewrite();
506}
507
508void 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
517void 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
522void cb_start_hide_clicked(GtkWidget *uu1, gpointer uu2)
523{
524 gtk_widget_hide(start_wnd);
525}
526
527void cb_start_abort_clicked(GtkWidget *uu1, gpointer uu2)
528{
529 kill(dpid, SIGINT);
530 exit(0);
531}
532
533void cb_start_errok_clicked(GtkWidget *uu1, gpointer uu2)
534{
535 gtk_main_quit();
536}
537
538#include "../dolda-icon.xpm"
539
540void 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
548int main(int argc, char **argv)
549{
71aeadfc
FT
550 int c;
551
34d45a15
FT
552 setlocale(LC_ALL, "");
553 bindtextdomain(PACKAGE, LOCALEDIR);
554 textdomain(PACKAGE);
555 signal(SIGCHLD, sighandler);
556 dc_init();
a0ca560e 557 server = dc_srv_local;
34d45a15
FT
558 gtk_init(&argc, &argv);
559#ifdef HAVE_NOTIFY
560 notify_init("Dolda Connect");
561#endif
a0ca560e 562 while((c = getopt(argc, argv, "rhs:")) != -1) {
71aeadfc
FT
563 switch(c) {
564 case 'r':
565 remote = 1;
a0ca560e
FT
566 server = NULL;
567 break;
568 case 's':
569 remote = 1;
570 server = optarg;
71aeadfc
FT
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 }
34d45a15
FT
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();
71aeadfc
FT
587 if(remote)
588 connectdc();
589 else
590 startdaemon();
34d45a15 591
0d12617c 592 g_timeout_add(10000, trupdatecb, NULL);
34d45a15
FT
593 gtk_main();
594
595 return(0);
596}
597
598#include "dsh-start.gtk"
599#include "dsh-menu.gtk"