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