1 /*
2  * File: downloads.cc
3  *
4  * Copyright (C) 2005-2007 Jorge Arellano Cid <jcid@dillo.org>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  */
11 
12 /*
13  * A FLTK-based GUI for the downloads dpi (dillo plugin).
14  */
15 
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <unistd.h>
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <ctype.h>
23 #include <math.h>
24 #include <time.h>
25 #include <signal.h>
26 #include <sys/types.h>
27 #include <sys/socket.h>
28 #include <sys/stat.h>
29 #include <sys/un.h>
30 #include <sys/wait.h>
31 
32 #include <FL/Fl.H>
33 #include <FL/fl_ask.H>
34 #include <FL/fl_draw.H>
35 #include <FL/Fl_File_Chooser.H>
36 #include <FL/Fl_Window.H>
37 #include <FL/Fl_Widget.H>
38 #include <FL/Fl_Group.H>
39 #include <FL/Fl_Scroll.H>
40 #include <FL/Fl_Pack.H>
41 #include <FL/Fl_Box.H>
42 #include <FL/Fl_Button.H>
43 
44 #include "dpiutil.h"
45 #include "../dpip/dpip.h"
46 
47 /*
48  * Debugging macros
49  */
50 #define _MSG(...)
51 #define MSG(...)  printf("[downloads dpi]: " __VA_ARGS__)
52 
53 /*
54  * Class declarations
55  */
56 
57 // ProgressBar widget --------------------------------------------------------
58 
59 class ProgressBar : public Fl_Box {
60 protected:
61    double mMin;
62    double mMax;
63    double mPresent;
64    double mStep;
65    bool mShowPct, mShowMsg;
66    char mMsg[64];
67    Fl_Color mTextColor;
68    void draw();
69 public:
70    ProgressBar(int x, int y, int w, int h, const char *lbl = 0);
range(double min,double max,double step=1)71    void range(double min, double max, double step = 1)  {
72       mMin = min; mMax = max; mStep = step;
73    };
step(double step)74    void step(double step)        { mPresent += step; redraw(); };
75    void move(double step);
minimum()76    double minimum()        { return mMin; }
maximum()77    double maximum()        { return mMax; }
minimum(double nm)78    void minimum(double nm) { mMin = nm; };
maximum(double nm)79    void maximum(double nm) { mMax = nm; };
position()80    double position  ()     { return mPresent; }
step()81    double step()           { return mStep; }
position(double pos)82    void position(double pos)     { mPresent = pos; redraw(); }
showtext(bool st)83    void showtext(bool st)        { mShowPct = st; }
message(char * msg)84    void message(char *msg) { mShowMsg = true; strncpy(mMsg,msg,63); redraw(); }
showtext()85    bool showtext()               { return mShowPct; }
text_color(Fl_Color col)86    void text_color(Fl_Color col) { mTextColor = col; }
text_color()87    Fl_Color text_color()   { return mTextColor; }
88 };
89 
90 // Download-item class -------------------------------------------------------
91 
92 class DLItem {
93    enum {
94       ST_newline, ST_number, ST_discard, ST_copy
95    };
96 
97    pid_t mPid;
98    int LogPipe[2];
99    char *shortname, *fullname;
100    char *target_dir;
101    size_t log_len, log_max;
102    int log_state;
103    char *log_text;
104    time_t init_time;
105    char **dl_argv;
106    time_t twosec_time, onesec_time;
107    int twosec_bytesize, onesec_bytesize;
108    int init_bytesize, curr_bytesize, total_bytesize;
109    int DataDone, LogDone, ForkDone, UpdatesDone, WidgetDone;
110    int WgetStatus;
111 
112    int gw, gh;
113    Fl_Group *group;
114    ProgressBar *prBar;
115    Fl_Button *prButton;
116    Fl_Widget *prTitle, *prGot, *prSize, *prRate, *pr_Rate, *prETA, *prETAt;
117 
118 public:
119    DLItem(const char *full_filename, const char *url);
120    ~DLItem();
121    void child_init();
122    void father_init();
123    void update_size(int new_sz);
124    void log_text_add(const char *buf, ssize_t st);
125    void log_text_show();
126    void abort_dl();
127    void prButton_cb();
pid()128    pid_t pid() { return mPid; }
pid(pid_t p)129    void pid(pid_t p) { mPid = p; }
130    void child_finished(int status);
status_msg(const char * msg)131    void status_msg(const char *msg) { prBar->message((char*)msg); }
get_widget()132    Fl_Widget *get_widget() { return group; }
widget_done()133    int widget_done() { return WidgetDone; }
widget_done(int val)134    void widget_done(int val) { WidgetDone = val; }
updates_done()135    int updates_done() { return UpdatesDone; }
updates_done(int val)136    void updates_done(int val) { UpdatesDone = val; }
fork_done()137    int fork_done() { return ForkDone; }
fork_done(int val)138    void fork_done(int val) { ForkDone = val; }
log_done()139    int log_done() { return LogDone; }
log_done(int val)140    void log_done(int val) { LogDone = val; }
wget_status()141    int wget_status() { return WgetStatus; }
wget_status(int val)142    void wget_status(int val) { WgetStatus = val; }
143    void update_prSize(int newsize);
144    void update();
145 };
146 
147 // DLItem List ---------------------------------------------------------------
148 
149 /// BUG: make dynamic
150 class DLItemList {
151    DLItem *mList[32];
152    int mNum, mMax;
153 
154 public:
DLItemList()155    DLItemList() { mNum = 0; mMax = 32; }
~DLItemList()156    ~DLItemList() { }
num()157    int num() { return mNum; }
add(DLItem * i)158    void add(DLItem *i) { if (mNum < mMax) mList[mNum++] = i; }
get(int n)159    DLItem *get(int n) { return (n >= 0 && n < mNum) ? mList[n] : NULL; }
del(int n)160    void del(int n) { if (n >= 0 && n < mNum) mList[n] = mList[--mNum]; }
161 };
162 
163 // DLWin ---------------------------------------------------------------------
164 
165 class DLWin {
166    DLItemList *mDList;
167    Fl_Window *mWin;
168    Fl_Scroll *mScroll;
169    Fl_Pack *mPG;
170 
171 public:
172    DLWin(int ww, int wh);
173    void add(const char *full_filename, const char *url);
174    void del(int n_item);
175    int num();
176    int num_running();
177    void listen(int req_fd);
show()178    void show() { mWin->show(); }
hide()179    void hide() { mWin->hide(); }
180    void abort_all();
181 };
182 
183 /*
184  * FLTK cannot be dissuaded from interpreting '@' in a tooltip
185  * as indicating a symbol unless we escape it.
186  */
escape_tooltip(const char * buf,ssize_t len)187 static char *escape_tooltip(const char *buf, ssize_t len)
188 {
189    if (len < 0)
190       len = 0;
191 
192    char *ret = (char *) malloc(2 * len + 1);
193    char *dest = ret;
194 
195    while (len-- > 0) {
196       if (*buf == '@')
197          *dest++ = *buf;
198       *dest++ = *buf++;
199    }
200    *dest = '\0';
201 
202    return ret;
203 }
204 
205 
206 /*
207  * Global variables
208  */
209 
210 // SIGCHLD mask
211 sigset_t mask_sigchld;
212 
213 // SIGCHLD flag
214 volatile sig_atomic_t caught_sigchld = 0;
215 
216 // The download window object
217 static class DLWin *dl_win = NULL;
218 
219 
220 
221 // ProgressBar widget --------------------------------------------------------
222 
move(double step)223 void ProgressBar::move(double step)
224 {
225    mPresent += step;
226    if (mPresent > mMax)
227       mPresent = mMin;
228    redraw();
229 }
230 
ProgressBar(int x,int y,int w,int h,const char * lbl)231 ProgressBar::ProgressBar(int x, int y, int w, int h, const char *lbl)
232 :  Fl_Box(x, y, w, h, lbl)
233 {
234    mMin = mPresent = 0;
235    mMax = 100;
236    mShowPct = true;
237    mShowMsg = false;
238    box(FL_THIN_UP_BOX);
239    color(FL_WHITE);
240 }
241 
draw()242 void ProgressBar::draw()
243 {
244    struct Rectangle {
245       int x, y, w, h;
246    };
247 
248    //drawstyle(style(), flags());
249    draw_box();
250    Rectangle r = {x(), y(), w(), h()};
251    if (mPresent > mMax)
252       mPresent = mMax;
253    if (mPresent < mMin)
254       mPresent = mMin;
255    double pct = (mPresent - mMin) / mMax;
256 
257    r.w = r.w * pct + .5;
258    fl_rectf(r.x, r.y, r.w, r.h, FL_BLUE);
259 
260    if (mShowMsg) {
261       fl_color(FL_RED);
262       fl_font(this->labelfont(), this->labelsize());
263       fl_draw(mMsg, x(), y(), w(), h(), FL_ALIGN_CENTER);
264    } else if (mShowPct) {
265       char buffer[30];
266       sprintf(buffer, "%d%%", int (pct * 100 + .5));
267       fl_color(FL_RED);
268       fl_font(this->labelfont(), this->labelsize());
269       fl_draw(buffer, x(), y(), w(), h(), FL_ALIGN_CENTER);
270    }
271 }
272 
273 
274 // Download-item class -------------------------------------------------------
275 
prButton_scb(Fl_Widget *,void * cb_data)276 static void prButton_scb(Fl_Widget *, void *cb_data)
277 {
278    DLItem *i = (DLItem *)cb_data;
279 
280    i->prButton_cb();
281 }
282 
DLItem(const char * full_filename,const char * url)283 DLItem::DLItem(const char *full_filename, const char *url)
284 {
285    struct stat ss;
286    const char *p;
287    char *esc_url;
288 
289    if (pipe(LogPipe) < 0) {
290       MSG("pipe, %s\n", dStrerror(errno));
291       return;
292    }
293    /* Set FD to background */
294    fcntl(LogPipe[0], F_SETFL,
295          O_NONBLOCK | fcntl(LogPipe[0], F_GETFL));
296 
297    fullname = dStrdup(full_filename);
298    p = strrchr(fullname, '/');
299    shortname = (p) ? dStrdup(p + 1) : dStrdup("??");
300    p = strrchr(full_filename, '/');
301    target_dir= p ? dStrndup(full_filename,p-full_filename+1) : dStrdup("??");
302 
303    log_len = 0;
304    log_max = 0;
305    log_state = ST_newline;
306    log_text = NULL;
307    onesec_bytesize = twosec_bytesize = curr_bytesize = init_bytesize = 0;
308    total_bytesize = -1;
309 
310    // Init value. Reset later, upon the first data bytes arrival
311    init_time = time(NULL);
312 
313    twosec_time = onesec_time = init_time;
314 
315    // BUG:? test a URL with ' inside.
316    /* escape "'" character for the shell. Is it necessary? */
317    esc_url = Escape_uri_str(url, "'");
318    /* avoid malicious SMTP relaying with FTP urls */
319    if (dStrnAsciiCasecmp(esc_url, "ftp:/", 5) == 0)
320       Filter_smtp_hack(esc_url);
321    dl_argv = new char*[8];
322    int i = 0;
323    dl_argv[i++] = (char*)"wget";
324    if (stat(fullname, &ss) == 0)
325       init_bytesize = (int)ss.st_size;
326    dl_argv[i++] = (char*)"-c";
327    dl_argv[i++] = (char*)"--load-cookies";
328    dl_argv[i++] = dStrconcat(dGethomedir(), "/.dillo/cookies.txt", NULL);
329    dl_argv[i++] = (char*)"-O";
330    dl_argv[i++] = fullname;
331    dl_argv[i++] = esc_url;
332    dl_argv[i++] = NULL;
333 
334    DataDone = 0;
335    LogDone = 0;
336    UpdatesDone = 0;
337    ForkDone = 0;
338    WidgetDone = 0;
339    WgetStatus = -1;
340 
341    gw = 400, gh = 70;
342    group = new Fl_Group(0,0,gw,gh);
343    group->begin();
344    prTitle = new Fl_Box(24, 7, 290, 23);
345    prTitle->box(FL_RSHADOW_BOX);
346    prTitle->color(FL_WHITE);
347    prTitle->align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE|FL_ALIGN_CLIP);
348    prTitle->copy_label(shortname);
349    // Attach this 'log_text' to the tooltip
350    log_text_add("Target File: ", 13);
351    log_text_add(fullname, strlen(fullname));
352    log_text_add("\n\n", 2);
353 
354    prBar = new ProgressBar(24, 40, 92, 20);
355    prBar->box(FL_THIN_UP_BOX);
356    prBar->tooltip("Progress Status");
357 
358    int ix = 122, iy = 37, iw = 50, ih = 14;
359    Fl_Widget *o = new Fl_Box(ix,iy,iw,ih, "Got");
360    o->box(FL_RFLAT_BOX);
361    o->color(FL_DARK2);
362    o->labelsize(12);
363    o->tooltip("Downloaded Size");
364    prGot = new Fl_Box(ix,iy+14,iw,ih, "0KB");
365    prGot->align(FL_ALIGN_CENTER|FL_ALIGN_INSIDE);
366    prGot->labelcolor(FL_BLUE);
367    prGot->labelsize(12);
368    prGot->box(FL_NO_BOX);
369 
370    ix += iw;
371    o = new Fl_Box(ix,iy,iw,ih, "Size");
372    o->box(FL_RFLAT_BOX);
373    o->color(FL_DARK2);
374    o->labelsize(12);
375    o->tooltip("Total Size");
376    prSize = new Fl_Box(ix,iy+14,iw,ih, "??");
377    prSize->align(FL_ALIGN_CENTER|FL_ALIGN_INSIDE);
378    prSize->labelsize(12);
379    prSize->box(FL_NO_BOX);
380 
381    ix += iw;
382    o = new Fl_Box(ix,iy,iw,ih, "Rate");
383    o->box(FL_RFLAT_BOX);
384    o->color(FL_DARK2);
385    o->labelsize(12);
386    o->tooltip("Current transfer Rate (KBytes/sec)");
387    prRate = new Fl_Box(ix,iy+14,iw,ih, "??");
388    prRate->align(FL_ALIGN_CENTER|FL_ALIGN_INSIDE);
389    prRate->labelsize(12);
390    prRate->box(FL_NO_BOX);
391 
392    ix += iw;
393    o = new Fl_Box(ix,iy,iw,ih, "~Rate");
394    o->box(FL_RFLAT_BOX);
395    o->color(FL_DARK2);
396    o->labelsize(12);
397    o->tooltip("Average transfer Rate (KBytes/sec)");
398    pr_Rate = new Fl_Box(ix,iy+14,iw,ih, "??");
399    pr_Rate->align(FL_ALIGN_CENTER|FL_ALIGN_INSIDE);
400    pr_Rate->labelsize(12);
401    pr_Rate->box(FL_NO_BOX);
402 
403    ix += iw;
404    prETAt = o = new Fl_Box(ix,iy,iw,ih, "ETA");
405    o->box(FL_RFLAT_BOX);
406    o->color(FL_DARK2);
407    o->labelsize(12);
408    o->tooltip("Estimated Time of Arrival");
409    prETA = new Fl_Box(ix,iy+14,iw,ih, "??");
410    prETA->align(FL_ALIGN_CENTER|FL_ALIGN_INSIDE);
411    prETA->labelsize(12);
412    prETA->box(FL_NO_BOX);
413 
414    prButton = new Fl_Button(326, 9, 44, 19, "Stop");
415    prButton->tooltip("Stop this transfer");
416    prButton->box(FL_UP_BOX);
417    prButton->clear_visible_focus();
418    prButton->callback(prButton_scb, this);
419 
420    group->box(FL_ROUNDED_BOX);
421    group->end();
422 }
423 
~DLItem()424 DLItem::~DLItem()
425 {
426    free(shortname);
427    dFree(fullname);
428    dFree(target_dir);
429    free(log_text);
430    int idx = (strcmp(dl_argv[1], "-c")) ? 2 : 3;
431    dFree(dl_argv[idx]);
432    dFree(dl_argv[idx+3]);
433    delete [] dl_argv;
434 
435    delete(group);
436 }
437 
438 /*
439  * Abort a running download
440  */
abort_dl()441 void DLItem::abort_dl()
442 {
443    if (!log_done()) {
444       dClose(LogPipe[0]);
445       Fl::remove_fd(LogPipe[0]);
446       log_done(1);
447       // Stop wget
448       if (!fork_done())
449          kill(pid(), SIGTERM);
450    }
451    widget_done(1);
452 }
453 
prButton_cb()454 void DLItem::prButton_cb()
455 {
456    prButton->deactivate();
457    abort_dl();
458 }
459 
child_init()460 void DLItem::child_init()
461 {
462    dClose(0); // stdin
463    dClose(1); // stdout
464    dClose(LogPipe[0]);
465    dup2(LogPipe[1], 2); // stderr
466    // set the locale to C for log parsing
467    setenv("LC_ALL", "C", 1);
468    // start wget
469    execvp(dl_argv[0], dl_argv);
470 }
471 
472 /*
473  * Update displayed size
474  */
update_prSize(int newsize)475 void DLItem::update_prSize(int newsize)
476 {
477    char num[64];
478 
479    if (newsize > 1024 * 1024)
480       snprintf(num, 64, "%.1fMB", (float)newsize / (1024*1024));
481    else
482       snprintf(num, 64, "%.0fKB", (float)newsize / 1024);
483    prSize->copy_label(num);
484 }
485 
log_text_add(const char * buf,ssize_t st)486 void DLItem::log_text_add(const char *buf, ssize_t st)
487 {
488    const char *p;
489    char *esc_str, *q, *d, num[64];
490    size_t esc_len;
491 
492    // WORKAROUND: We have to escape '@' in FLTK tooltips.
493    esc_str = escape_tooltip(buf, st);
494    esc_len = strlen(esc_str);
495 
496    // Make room...
497    if (log_len + esc_len >= log_max) {
498       log_max = log_len + esc_len + 1024;
499       log_text = (char *) dRealloc (log_text, log_max);
500       log_text[log_len] = 0;
501       prTitle->tooltip(log_text);
502    }
503 
504    // FSM to remove wget's "dot-progress" (i.e. "^ " || "^[0-9]+K")
505    q = log_text + log_len;
506    for (p = esc_str; (size_t)(p - esc_str) < esc_len; ++p) {
507       switch (log_state) {
508       case ST_newline:
509          if (*p == ' ') {
510             log_state = ST_discard;
511          } else if (isdigit(*p)) {
512             *q++ = *p; log_state = ST_number;
513          } else if (*p == '\n') {
514             *q++ = *p;
515          } else {
516             *q++ = *p; log_state = ST_copy;
517          }
518          break;
519       case ST_number:
520          if (isdigit(*q++ = *p)) {
521             // keep here
522          } else if (*p == 'K') {
523             for (--q; isdigit(q[-1]); --q) ; log_state = ST_discard;
524          } else {
525             log_state = ST_copy;
526          }
527          break;
528       case ST_discard:
529          if (*p == '\n')
530             log_state = ST_newline;
531          break;
532       case ST_copy:
533          if ((*q++ = *p) == '\n')
534             log_state = ST_newline;
535          break;
536       }
537    }
538    *q = 0;
539    log_len = strlen(log_text);
540 
541    free(esc_str);
542 
543    // Now scan for the length of the file
544    if (total_bytesize == -1) {
545       p = strstr(log_text, "\nLength: ");
546       if (p && isdigit(p[9]) && strchr(p + 9, ' ')) {
547          for (p += 9, d = &num[0]; *p != ' '; ++p)
548             if (isdigit(*p))
549                *d++ = *p;
550          *d = 0;
551          total_bytesize = strtol (num, NULL, 10);
552          // Update displayed size
553          update_prSize(total_bytesize);
554 
555          // WORKAROUND: For unknown reasons a redraw is needed here for some
556          //             machines --jcid
557          group->redraw();
558       }
559    }
560 
561    // Show we're connecting...
562    if (curr_bytesize == 0) {
563       prTitle->copy_label("Connecting...");
564    }
565 }
566 
567 ///
log_text_show()568 void DLItem::log_text_show()
569 {
570    MSG("\nStored Log:\n%s", log_text);
571 }
572 
update_size(int new_sz)573 void DLItem::update_size(int new_sz)
574 {
575    char buf[64];
576 
577    if (curr_bytesize == 0 && new_sz) {
578       // Start the timer with the first bytes got
579       init_time = time(NULL);
580       // Update the title
581       prTitle->copy_label(shortname);
582       // WORKAROUND: For unknown reasons a redraw is needed here for some
583       //             machines --jcid
584       group->redraw();
585    }
586 
587    curr_bytesize = new_sz;
588    if (curr_bytesize > 1024 * 1024)
589       snprintf(buf, 64, "%.1fMB", (float)curr_bytesize / (1024*1024));
590    else
591       snprintf(buf, 64, "%.0fKB", (float)curr_bytesize / 1024);
592    prGot->copy_label(buf);
593    if (total_bytesize == -1) {
594       prBar->showtext(false);
595       prBar->move(1);
596    } else {
597       prBar->showtext(true);
598       double pos = 100.0;
599       if (total_bytesize > 0)
600          pos *= (double)curr_bytesize / total_bytesize;
601       prBar->position(pos);
602    }
603 }
604 
read_log_cb(int fd_in,void * data)605 static void read_log_cb(int fd_in, void *data)
606 {
607    DLItem *dl_item = (DLItem *)data;
608    const int BufLen = 4096;
609    char Buf[BufLen];
610    ssize_t st;
611 
612    do {
613       st = read(fd_in, Buf, BufLen);
614       if (st < 0) {
615          if (errno == EAGAIN) {
616             break;
617          }
618          perror("read, ");
619          break;
620       } else if (st == 0) {
621          dClose(fd_in);
622          Fl::remove_fd(fd_in, 1);
623          dl_item->log_done(1);
624          break;
625       } else {
626          dl_item->log_text_add(Buf, st);
627       }
628    } while (1);
629 }
630 
father_init()631 void DLItem::father_init()
632 {
633    dClose(LogPipe[1]);
634    Fl::add_fd(LogPipe[0], 1, read_log_cb, this); // Read
635 
636    // Start the timer after the child is running.
637    // (this makes a big difference with wget)
638    //init_time = time(NULL);
639 }
640 
641 /*
642  * Our wget exited, let's check its status and update the panel.
643  */
child_finished(int status)644 void DLItem::child_finished(int status)
645 {
646    wget_status(status);
647 
648    if (status == 0) {
649       prButton->label("Done");
650       prButton->tooltip("Close this information panel");
651    } else {
652       prButton->label("Close");
653       prButton->tooltip("Close this information panel");
654       status_msg("ABORTED");
655       if (curr_bytesize == 0) {
656          // Update the title
657          prTitle->copy_label(shortname);
658       }
659    }
660    prButton->activate();
661    prButton->redraw();
662    MSG("wget status %d\n", status);
663 }
664 
665 /*
666  * Convert seconds into human readable [hour]:[min]:[sec] string.
667  */
secs2timestr(int et,char * str)668 static void secs2timestr(int et, char *str)
669 {
670    int eh, em, es;
671 
672    eh = et / 3600; em = (et % 3600) / 60; es = et % 60;
673    if (eh == 0) {
674       if (em == 0)
675          snprintf(str, 8, "%ds", es);
676       else
677          snprintf(str, 8, "%dm%ds", em, es);
678    } else {
679       snprintf(str, 8, "%dh%dm", eh, em);
680    }
681 }
682 
683 /*
684  * Update Got, Rate, ~Rate and ETA
685  */
update()686 void DLItem::update()
687 {
688    struct stat ss;
689    time_t curr_time;
690    float csec, tsec, rate, _rate = 0;
691    char str[64];
692    int et;
693 
694    if (updates_done())
695       return;
696 
697    /* Update curr_size */
698    if (stat(fullname, &ss) == -1) {
699       MSG("stat, %s\n", dStrerror(errno));
700       return;
701    }
702    update_size((int)ss.st_size);
703 
704    /* Get current time */
705    time(&curr_time);
706    csec = (float) (curr_time - init_time);
707 
708    /* Rate */
709    if (csec >= 2) {
710       tsec = (float) (curr_time - twosec_time);
711       rate = ((float)(curr_bytesize-twosec_bytesize) / 1024) / tsec;
712       snprintf(str, 64, (rate < 100) ? "%.1fK/s" : "%.0fK/s", rate);
713       prRate->copy_label(str);
714    }
715    /* ~Rate */
716    if (csec >= 1) {
717       _rate = ((float)(curr_bytesize-init_bytesize) / 1024) / csec;
718       snprintf(str, 64, (_rate < 100) ? "%.1fK/s" : "%.0fK/s", _rate);
719       pr_Rate->copy_label(str);
720    }
721 
722    /* ETA */
723    if (fork_done()) {
724       updates_done(1); // Last update
725       prETAt->label("Time");
726       prETAt->tooltip("Download Time");
727       prETAt->redraw();
728       secs2timestr((int)csec, str);
729       prETA->copy_label(str);
730       if (total_bytesize == -1) {
731          update_prSize(curr_bytesize);
732          if (wget_status() == 0)
733             status_msg("Done");
734       }
735    } else {
736       if (_rate > 0 && total_bytesize > 0 && curr_bytesize > 0) {
737          et = (int)((total_bytesize-curr_bytesize) / (_rate * 1024));
738          secs2timestr(et, str);
739          prETA->copy_label(str);
740       }
741    }
742 
743    /* Update one and two secs ago times and bytesizes */
744    twosec_time = onesec_time;
745    onesec_time = curr_time;
746    twosec_bytesize = onesec_bytesize;
747    onesec_bytesize = curr_bytesize;
748 }
749 
750 // SIGCHLD -------------------------------------------------------------------
751 
752 /*! SIGCHLD handler
753  */
raw_sigchld(int)754 static void raw_sigchld(int)
755 {
756    caught_sigchld = 1;
757 }
758 
759 /*! Establish SIGCHLD handler */
est_sigchld(void)760 static void est_sigchld(void)
761 {
762    struct sigaction sigact;
763    sigset_t set;
764 
765    (void) sigemptyset(&set);
766    sigact.sa_handler = raw_sigchld;
767    sigact.sa_mask = set;
768    sigact.sa_flags = SA_NOCLDSTOP;
769    if (sigaction(SIGCHLD, &sigact, NULL) == -1) {
770       perror("sigaction");
771       exit(1);
772    }
773 }
774 
775 /*
776  * Timeout function to check wget's exit status.
777  */
cleanup_cb(void * data)778 static void cleanup_cb(void *data)
779 {
780    DLItemList *list = (DLItemList *)data;
781 
782    sigprocmask(SIG_BLOCK, &mask_sigchld, NULL);
783    if (caught_sigchld) {
784       /* Handle SIGCHLD */
785       int i, status;
786       for (i = 0; i < list->num(); ++i) {
787          if (!list->get(i)->fork_done() &&
788              waitpid(list->get(i)->pid(), &status, WNOHANG) > 0) {
789             list->get(i)->child_finished(status);
790             list->get(i)->fork_done(1);
791          }
792       }
793       caught_sigchld = 0;
794    }
795    sigprocmask(SIG_UNBLOCK, &mask_sigchld, NULL);
796 
797    Fl::repeat_timeout(1.0,cleanup_cb,data);
798 }
799 
800 /*
801  * Timeout function to update the widget indicators,
802  * also remove widgets marked "done".
803  */
update_cb(void * data)804 static void update_cb(void *data)
805 {
806    static int cb_used = 0;
807 
808    DLItemList *list = (DLItemList *)data;
809 
810    /* Update the widgets and remove the ones marked as done */
811    for (int i = 0; i < list->num(); ++i) {
812       if (!list->get(i)->widget_done()) {
813          list->get(i)->update();
814       } else if (list->get(i)->fork_done()) {
815          // widget_done and fork_done avoid a race condition.
816          dl_win->del(i); --i;
817       }
818       cb_used = 1;
819    }
820 
821    if (cb_used && list->num() == 0)
822       exit(0);
823 
824    Fl::repeat_timeout(1.0,update_cb,data);
825 }
826 
827 
828 // DLWin ---------------------------------------------------------------------
829 
830 /*
831  * Callback function for the request socket.
832  * Read the request, parse and start a new download.
833  */
read_req_cb(int req_fd,void *)834 static void read_req_cb(int req_fd, void *)
835 {
836    struct sockaddr_un clnt_addr;
837    int sock_fd;
838    socklen_t csz;
839    Dsh *sh = NULL;
840    char *dpip_tag = NULL, *cmd = NULL, *url = NULL, *dl_dest = NULL;
841 
842    /* Initialize the value-result parameter */
843    csz = sizeof(struct sockaddr_un);
844    /* accept the request */
845    do {
846       sock_fd = accept(req_fd, (struct sockaddr *) &clnt_addr, &csz);
847    } while (sock_fd == -1 && errno == EINTR);
848    if (sock_fd == -1) {
849       MSG("accept, %s fd=%d\n", dStrerror(errno), req_fd);
850       return;
851    }
852 
853    /* create a sock handler */
854    sh = a_Dpip_dsh_new(sock_fd, sock_fd, 8*1024);
855 
856    /* Authenticate our client... */
857    if (!(dpip_tag = a_Dpip_dsh_read_token(sh, 1)) ||
858        a_Dpip_check_auth(dpip_tag) < 0) {
859       MSG("can't authenticate request: %s fd=%d\n", dStrerror(errno), sock_fd);
860       a_Dpip_dsh_close(sh);
861       goto end;
862    }
863    dFree(dpip_tag);
864 
865    /* Read request */
866    if (!(dpip_tag = a_Dpip_dsh_read_token(sh, 1))) {
867       MSG("can't read request: %s fd=%d\n", dStrerror(errno), sock_fd);
868       a_Dpip_dsh_close(sh);
869       goto end;
870    }
871    a_Dpip_dsh_close(sh);
872    _MSG("Received tag={%s}\n", dpip_tag);
873 
874    if ((cmd = a_Dpip_get_attr(dpip_tag, "cmd")) == NULL) {
875       MSG("Failed to parse 'cmd' in {%s}\n", dpip_tag);
876       goto end;
877    }
878    if (strcmp(cmd, "DpiBye") == 0) {
879       MSG("got DpiBye, ignoring...\n");
880       goto end;
881    }
882    if (strcmp(cmd, "download") != 0) {
883       MSG("unknown command: '%s'. Aborting.\n", cmd);
884       goto end;
885    }
886    if (!(url = a_Dpip_get_attr(dpip_tag, "url"))){
887       MSG("Failed to parse 'url' in {%s}\n", dpip_tag);
888       goto end;
889    }
890    if (!(dl_dest = a_Dpip_get_attr(dpip_tag, "destination"))){
891       MSG("Failed to parse 'destination' in {%s}\n", dpip_tag);
892       goto end;
893    }
894    dl_win->add(dl_dest, url);
895 
896 end:
897    dFree(cmd);
898    dFree(url);
899    dFree(dl_dest);
900    dFree(dpip_tag);
901    a_Dpip_dsh_free(sh);
902 }
903 
904 /*
905  * Callback for close window request (WM or EscapeKey press)
906  */
dlwin_esc_cb(Fl_Widget *,void *)907 static void dlwin_esc_cb(Fl_Widget *, void *)
908 {
909    const char *msg = "There are running downloads.\n"
910                      "ABORT them and EXIT anyway?";
911 
912    if (dl_win && dl_win->num_running() > 0) {
913       fl_message_title("Dillo Downloads: Abort downloads?");
914       int ch = fl_choice("%s", "Cancel", "*No", "Yes", msg);
915       if (ch == 0 || ch == 1)
916          return;
917    }
918 
919    /* abort each download properly */
920    dl_win->abort_all();
921 }
922 
923 /*
924  * Add a new download request to the main window and
925  * fork a child to do the job.
926  */
add(const char * full_filename,const char * url)927 void DLWin::add(const char *full_filename, const char *url)
928 {
929    DLItem *dl_item = new DLItem(full_filename, url);
930    mDList->add(dl_item);
931    mPG->insert(*dl_item->get_widget(), 0);
932 
933    _MSG("Child index = %d\n", mPG->find(dl_item->get_widget()));
934 
935    // Start the child process
936    pid_t f_pid = fork();
937    if (f_pid == 0) {
938       /* child */
939       dl_item->child_init();
940       _exit(EXIT_FAILURE);
941    } else if (f_pid < 0) {
942       perror("fork, ");
943       exit(1);
944    } else {
945       /* father */
946       dl_item->get_widget()->show();
947       dl_win->show();
948       dl_item->pid(f_pid);
949       dl_item->father_init();
950    }
951 }
952 
953 /*
954  * Delete a download request from the main window.
955  */
del(int n_item)956 void DLWin::del(int n_item)
957 {
958    DLItem *dl_item = mDList->get(n_item);
959 
960    // Remove the widget from the packed group
961    mPG->remove(dl_item->get_widget());
962    mScroll->redraw();
963    mDList->del(n_item);
964    delete(dl_item);
965 }
966 
967 /*
968  * Return number of entries
969  */
num()970 int DLWin::num()
971 {
972    return mDList->num();
973 }
974 
975 /*
976  * Return number of running downloads
977  */
num_running()978 int DLWin::num_running()
979 {
980    int i, nr;
981 
982    for (i = nr = 0; i < mDList->num(); ++i)
983       if (!mDList->get(i)->fork_done())
984          ++nr;
985    return nr;
986 }
987 
988 /*
989  * Set a callback function for the request socket
990  */
listen(int req_fd)991 void DLWin::listen(int req_fd)
992 {
993    Fl::add_fd(req_fd, 1, read_req_cb, NULL); // Read
994 }
995 
996 /*
997  * Abort each download properly, and let the main cycle exit
998  */
abort_all()999 void DLWin::abort_all()
1000 {
1001    for (int i = 0; i < mDList->num(); ++i)
1002       mDList->get(i)->abort_dl();
1003 }
1004 
1005 /*
1006  * A Scroll class that resizes its resizable widget to its width.
1007  * see http://seriss.com/people/erco/fltk/#ScrollableWidgetBrowser
1008  */
1009 class DlScroll : public Fl_Scroll
1010 {
1011 public:
resize(int x_,int y_,int w_,int h_)1012   void resize(int x_, int y_, int w_, int h_)
1013   {
1014     Fl_Scroll::resize(x_, y_, w_, h_);
1015     Fl_Widget *resizable_ = resizable();
1016     int sb_size =
1017        resizable_->h() <= h() ? 0 :
1018        scrollbar_size() ? scrollbar_size() :
1019        Fl::scrollbar_size();
1020     if (resizable_)
1021       resizable_->resize(resizable_->x(),
1022                          resizable_->y(),
1023                          w() - sb_size,
1024                          resizable_->h());
1025   }
DlScroll(int x,int y,int w,int h,const char * l=0)1026   DlScroll(int x, int y, int w, int h, const char *l = 0)
1027     : Fl_Scroll(x, y, w, h, l)
1028   {
1029   }
1030 };
1031 
1032 /*
1033  * Create the main window and an empty list of requests.
1034  */
DLWin(int ww,int wh)1035 DLWin::DLWin(int ww, int wh) {
1036 
1037    // Init an empty list for the download requests
1038    mDList = new DLItemList();
1039 
1040    // Create the empty main window
1041    mWin = new Fl_Window(ww, wh, "Dillo Downloads");
1042    mWin->begin();
1043    mScroll = new DlScroll(0,0,ww,wh);
1044    mScroll->begin();
1045    mPG = new Fl_Pack(0,0,ww-18,wh);
1046    mPG->end();
1047    mScroll->end();
1048    mScroll->type(Fl_Scroll::VERTICAL);
1049    mScroll->resizable(mPG);
1050    mWin->end();
1051    mWin->resizable(mScroll);
1052    mWin->callback(dlwin_esc_cb, NULL);
1053    mWin->show();
1054 
1055    // Set SIGCHLD handlers
1056    sigemptyset(&mask_sigchld);
1057    sigaddset(&mask_sigchld, SIGCHLD);
1058    est_sigchld();
1059 
1060    fl_message_title_default("Dillo Downloads: Message");
1061 
1062    // Set the cleanup timeout
1063    Fl::add_timeout(1.0, cleanup_cb, mDList);
1064    // Set the update timeout
1065    Fl::add_timeout(1.0, update_cb, mDList);
1066 }
1067 
1068 
1069 // ---------------------------------------------------------------------------
1070 
1071 /*
1072  * Set FL_NORMAL_LABEL to interpret neither symbols (@) nor shortcuts (&)
1073  */
custLabelDraw(const Fl_Label * o,int X,int Y,int W,int H,Fl_Align align)1074 static void custLabelDraw(const Fl_Label* o, int X, int Y, int W, int H,
1075                           Fl_Align align)
1076 {
1077    const int interpret_symbols = 0;
1078 
1079    fl_draw_shortcut = 0;
1080    fl_font(o->font, o->size);
1081    fl_color((Fl_Color)o->color);
1082    fl_draw(o->value, X, Y, W, H, align, o->image, interpret_symbols);
1083 }
1084 
custLabelMeasure(const Fl_Label * o,int & W,int & H)1085 static void custLabelMeasure(const Fl_Label* o, int& W, int& H)
1086 {
1087    const int interpret_symbols = 0;
1088 
1089    fl_draw_shortcut = 0;
1090    fl_font(o->font, o->size);
1091    fl_measure(o->value, W, H, interpret_symbols);
1092 }
1093 
1094 
1095 
1096 //int main(int argc, char **argv)
main()1097 int main()
1098 {
1099    int ww = 420, wh = 85;
1100 
1101    Fl::lock();
1102 
1103    // Disable '@' and '&' interpretation in normal labels.
1104    Fl::set_labeltype(FL_NORMAL_LABEL, custLabelDraw, custLabelMeasure);
1105 
1106    Fl::scheme(NULL);
1107 
1108    // Create the download window
1109    dl_win = new DLWin(ww, wh);
1110 
1111    // Start listening to the request socket
1112    dl_win->listen(STDIN_FILENO);
1113 
1114    MSG("started...\n");
1115 
1116    return Fl::run();
1117 }
1118 
1119