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