1126 lines
28 KiB
C++
1126 lines
28 KiB
C++
/*
|
|
* File: downloads.cc
|
|
*
|
|
* Copyright (C) 2005-2007 Jorge Arellano Cid <jcid@dillo.org>
|
|
* Copyright (C) 2024 Rodrigo Arias Mallo <rodarima@gmail.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*/
|
|
|
|
/*
|
|
* A FLTK-based GUI for the downloads dpi (dillo plugin).
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <ctype.h>
|
|
#include <math.h>
|
|
#include <time.h>
|
|
#include <signal.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/un.h>
|
|
#include <sys/wait.h>
|
|
|
|
#include <vector>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
#include <FL/Fl.H>
|
|
#include <FL/fl_ask.H>
|
|
#include <FL/fl_draw.H>
|
|
#include <FL/Fl_File_Chooser.H>
|
|
#include <FL/Fl_Window.H>
|
|
#include <FL/Fl_Widget.H>
|
|
#include <FL/Fl_Group.H>
|
|
#include <FL/Fl_Scroll.H>
|
|
#include <FL/Fl_Pack.H>
|
|
#include <FL/Fl_Box.H>
|
|
#include <FL/Fl_Button.H>
|
|
|
|
#include "config.h"
|
|
#include "dpiutil.hh"
|
|
#include "../dpip/dpip.hh"
|
|
|
|
/*
|
|
* Debugging macros
|
|
*/
|
|
#define _MSG(...)
|
|
#define MSG(...) printf("[downloads dpi]: " __VA_ARGS__)
|
|
|
|
/*
|
|
* Class declarations
|
|
*/
|
|
|
|
// ProgressBar widget --------------------------------------------------------
|
|
|
|
class ProgressBar : public Fl_Box {
|
|
protected:
|
|
double mMin;
|
|
double mMax;
|
|
double mPresent;
|
|
double mStep;
|
|
bool mShowPct, mShowMsg;
|
|
char mMsg[64];
|
|
Fl_Color mTextColor;
|
|
void draw();
|
|
public:
|
|
ProgressBar(int x, int y, int w, int h, const char *lbl = 0);
|
|
void range(double min, double max, double step = 1) {
|
|
mMin = min; mMax = max; mStep = step;
|
|
};
|
|
void step(double step) { mPresent += step; redraw(); };
|
|
void move(double step);
|
|
double minimum() { return mMin; }
|
|
double maximum() { return mMax; }
|
|
void minimum(double nm) { mMin = nm; };
|
|
void maximum(double nm) { mMax = nm; };
|
|
double position () { return mPresent; }
|
|
double step() { return mStep; }
|
|
void position(double pos) { mPresent = pos; redraw(); }
|
|
void showtext(bool st) { mShowPct = st; }
|
|
void message(char *msg) { mShowMsg = true; strncpy(mMsg,msg,63); redraw(); }
|
|
bool showtext() { return mShowPct; }
|
|
void text_color(Fl_Color col) { mTextColor = col; }
|
|
Fl_Color text_color() { return mTextColor; }
|
|
};
|
|
|
|
// Download-item class -------------------------------------------------------
|
|
|
|
class DLItem {
|
|
enum {
|
|
ST_newline, ST_number, ST_discard, ST_copy
|
|
};
|
|
|
|
pid_t mPid;
|
|
int LogPipe[2];
|
|
char *shortname, *fullname;
|
|
char *esc_url;
|
|
char *cookies_path;
|
|
std::string target_dir;
|
|
size_t log_len, log_max;
|
|
int log_state;
|
|
char *log_text;
|
|
time_t init_time;
|
|
std::vector< char * > dl_argv;
|
|
time_t twosec_time, onesec_time;
|
|
int twosec_bytesize, onesec_bytesize;
|
|
int init_bytesize, curr_bytesize, total_bytesize;
|
|
int DataDone, LogDone, ForkDone, UpdatesDone, WidgetDone;
|
|
int WgetStatus;
|
|
|
|
int gw, gh;
|
|
std::unique_ptr< Fl_Group > group;
|
|
ProgressBar *prBar;
|
|
Fl_Button *prButton;
|
|
Fl_Widget *prTitle, *prGot, *prSize, *prRate, *pr_Rate, *prETA, *prETAt;
|
|
|
|
public:
|
|
DLItem(const char *full_filename, const char *url, const char *user_agent);
|
|
~DLItem();
|
|
void child_init();
|
|
void father_init();
|
|
void update_size(int new_sz);
|
|
void log_text_add(const char *buf, ssize_t st);
|
|
void log_text_show();
|
|
void abort_dl();
|
|
void prButton_cb();
|
|
pid_t pid() { return mPid; }
|
|
void pid(pid_t p) { mPid = p; }
|
|
void child_finished(int status);
|
|
void status_msg(const char *msg) { prBar->message((char*)msg); }
|
|
Fl_Widget *get_widget() { return group.get(); }
|
|
int widget_done() { return WidgetDone; }
|
|
void widget_done(int val) { WidgetDone = val; }
|
|
int updates_done() { return UpdatesDone; }
|
|
void updates_done(int val) { UpdatesDone = val; }
|
|
int fork_done() { return ForkDone; }
|
|
void fork_done(int val) { ForkDone = val; }
|
|
int log_done() { return LogDone; }
|
|
void log_done(int val) { LogDone = val; }
|
|
int wget_status() { return WgetStatus; }
|
|
void wget_status(int val) { WgetStatus = val; }
|
|
void update_prSize(int newsize);
|
|
void update();
|
|
};
|
|
|
|
// DLItem List ---------------------------------------------------------------
|
|
|
|
/// BUG: make dynamic
|
|
class DLItemList {
|
|
DLItem *mList[32];
|
|
int mNum, mMax;
|
|
|
|
public:
|
|
DLItemList() { mNum = 0; mMax = 32; }
|
|
~DLItemList() { }
|
|
int num() { return mNum; }
|
|
void add(DLItem *i) { if (mNum < mMax) mList[mNum++] = i; }
|
|
DLItem *get(int n) { return (n >= 0 && n < mNum) ? mList[n] : NULL; }
|
|
void del(int n) { if (n >= 0 && n < mNum) mList[n] = mList[--mNum]; }
|
|
};
|
|
|
|
// DLWin ---------------------------------------------------------------------
|
|
|
|
class DLWin {
|
|
DLItemList *mDList;
|
|
Fl_Window *mWin;
|
|
Fl_Scroll *mScroll;
|
|
Fl_Pack *mPG;
|
|
|
|
public:
|
|
DLWin(int ww, int wh);
|
|
void add(const char *full_filename, const char *url, const char *user_agent);
|
|
void del(int n_item);
|
|
int num();
|
|
int num_running();
|
|
void listen(int req_fd);
|
|
void show() { mWin->show(); }
|
|
void hide() { mWin->hide(); }
|
|
void abort_all();
|
|
};
|
|
|
|
/*
|
|
* FLTK cannot be dissuaded from interpreting '@' in a tooltip
|
|
* as indicating a symbol unless we escape it.
|
|
*/
|
|
static std::string escape_tooltip(const char *buf, ssize_t len)
|
|
{
|
|
if (len < 0)
|
|
len = 0;
|
|
|
|
//char *ret = (char *) malloc(2 * len + 1);
|
|
std::string ret;
|
|
|
|
while (len-- > 0) {
|
|
if (*buf == '@') ret.push_back( *buf );
|
|
ret.push_back( *buf++ );
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
* Global variables
|
|
*/
|
|
|
|
// SIGCHLD mask
|
|
sigset_t mask_sigchld;
|
|
|
|
// SIGCHLD flag
|
|
volatile sig_atomic_t caught_sigchld = 0;
|
|
|
|
// The download window object
|
|
static class DLWin *dl_win = NULL;
|
|
|
|
|
|
|
|
// ProgressBar widget --------------------------------------------------------
|
|
|
|
void ProgressBar::move(double step)
|
|
{
|
|
mPresent += step;
|
|
if (mPresent > mMax)
|
|
mPresent = mMin;
|
|
redraw();
|
|
}
|
|
|
|
ProgressBar::ProgressBar(int x, int y, int w, int h, const char *lbl)
|
|
: Fl_Box(x, y, w, h, lbl)
|
|
{
|
|
mMin = mPresent = 0;
|
|
mMax = 100;
|
|
mShowPct = true;
|
|
mShowMsg = false;
|
|
box(FL_THIN_UP_BOX);
|
|
color(FL_WHITE);
|
|
}
|
|
|
|
void ProgressBar::draw()
|
|
{
|
|
struct Rectangle {
|
|
int x, y, w, h;
|
|
};
|
|
|
|
//drawstyle(style(), flags());
|
|
draw_box();
|
|
Rectangle r = {x(), y(), w(), h()};
|
|
if (mPresent > mMax)
|
|
mPresent = mMax;
|
|
if (mPresent < mMin)
|
|
mPresent = mMin;
|
|
double pct = (mPresent - mMin) / mMax;
|
|
|
|
r.w = r.w * pct + .5;
|
|
fl_rectf(r.x, r.y, r.w, r.h, FL_BLUE);
|
|
|
|
if (mShowMsg) {
|
|
fl_color(FL_RED);
|
|
fl_font(this->labelfont(), this->labelsize());
|
|
fl_draw(mMsg, x(), y(), w(), h(), FL_ALIGN_CENTER);
|
|
} else if (mShowPct) {
|
|
char buffer[30];
|
|
sprintf(buffer, "%d%%", int (pct * 100 + .5));
|
|
fl_color(FL_RED);
|
|
fl_font(this->labelfont(), this->labelsize());
|
|
fl_draw(buffer, x(), y(), w(), h(), FL_ALIGN_CENTER);
|
|
}
|
|
}
|
|
|
|
|
|
// Download-item class -------------------------------------------------------
|
|
|
|
static void prButton_scb(Fl_Widget *, void *cb_data)
|
|
{
|
|
DLItem *i = (DLItem *)cb_data;
|
|
|
|
i->prButton_cb();
|
|
}
|
|
|
|
DLItem::DLItem(const char *full_filename, const char *url, const char *user_agent)
|
|
{
|
|
struct stat ss;
|
|
const char *p;
|
|
|
|
if (pipe(LogPipe) < 0) {
|
|
MSG("pipe, %s\n", dStrerror(errno));
|
|
return;
|
|
}
|
|
/* Set FD to background */
|
|
fcntl(LogPipe[0], F_SETFL,
|
|
O_NONBLOCK | fcntl(LogPipe[0], F_GETFL));
|
|
|
|
fullname = dStrdup(full_filename);
|
|
p = strrchr(fullname, '/');
|
|
shortname = (p) ? dStrdup(p + 1) : dStrdup("??");
|
|
p = strrchr(full_filename, '/');
|
|
target_dir= p ? std::string_view{ full_filename, p - full_filename + 1 } : "??";
|
|
|
|
log_len = 0;
|
|
log_max = 0;
|
|
log_state = ST_newline;
|
|
log_text = NULL;
|
|
onesec_bytesize = twosec_bytesize = curr_bytesize = init_bytesize = 0;
|
|
total_bytesize = -1;
|
|
|
|
// Init value. Reset later, upon the first data bytes arrival
|
|
init_time = time(NULL);
|
|
|
|
twosec_time = onesec_time = init_time;
|
|
|
|
// BUG:? test a URL with ' inside.
|
|
/* escape "'" character for the shell. Is it necessary? */
|
|
esc_url = dStrdup( Escape_uri_str(url, "'").c_str() );
|
|
/* avoid malicious SMTP relaying with FTP urls */
|
|
if (dStrnAsciiCasecmp(esc_url, "ftp:/", 5) == 0)
|
|
Filter_smtp_hack(esc_url);
|
|
cookies_path = dStrconcat(dGethomedir(), "/.flenser/cookies.txt", NULL);
|
|
dl_argv.clear();
|
|
dl_argv.push_back( (char*)"wget" );
|
|
if (stat(fullname, &ss) == 0)
|
|
init_bytesize = (int)ss.st_size;
|
|
dl_argv.push_back( (char*)"-U" );
|
|
dl_argv.push_back( (char*) user_agent );
|
|
dl_argv.push_back( (char*)"-c" );
|
|
dl_argv.push_back( (char*)"--load-cookies" );
|
|
dl_argv.push_back( cookies_path );
|
|
dl_argv.push_back( (char*)"-O" );
|
|
dl_argv.push_back( fullname );
|
|
dl_argv.push_back( esc_url );
|
|
dl_argv.push_back( nullptr );
|
|
|
|
DataDone = 0;
|
|
LogDone = 0;
|
|
UpdatesDone = 0;
|
|
ForkDone = 0;
|
|
WidgetDone = 0;
|
|
WgetStatus = -1;
|
|
|
|
gw = 400, gh = 70;
|
|
group = std::make_unique< Fl_Group >(0,0,gw,gh);
|
|
group->begin();
|
|
prTitle = new Fl_Box(24, 7, 290, 23);
|
|
prTitle->box(FL_RSHADOW_BOX);
|
|
prTitle->color(FL_WHITE);
|
|
prTitle->align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE|FL_ALIGN_CLIP);
|
|
prTitle->copy_label(shortname);
|
|
// Attach this 'log_text' to the tooltip
|
|
log_text_add("Target File: ", 13);
|
|
log_text_add(fullname, strlen(fullname));
|
|
log_text_add("\n\n", 2);
|
|
|
|
prBar = new ProgressBar(24, 40, 92, 20);
|
|
prBar->box(FL_THIN_UP_BOX);
|
|
prBar->tooltip("Progress Status");
|
|
|
|
int ix = 122, iy = 37, iw = 50, ih = 14;
|
|
Fl_Widget *o = new Fl_Box(ix,iy,iw,ih, "Got");
|
|
o->box(FL_RFLAT_BOX);
|
|
o->color(FL_DARK2);
|
|
o->labelsize(12);
|
|
o->tooltip("Downloaded Size");
|
|
prGot = new Fl_Box(ix,iy+14,iw,ih, "0KB");
|
|
prGot->align(FL_ALIGN_CENTER|FL_ALIGN_INSIDE);
|
|
prGot->labelcolor(FL_BLUE);
|
|
prGot->labelsize(12);
|
|
prGot->box(FL_NO_BOX);
|
|
|
|
ix += iw;
|
|
o = new Fl_Box(ix,iy,iw,ih, "Size");
|
|
o->box(FL_RFLAT_BOX);
|
|
o->color(FL_DARK2);
|
|
o->labelsize(12);
|
|
o->tooltip("Total Size");
|
|
prSize = new Fl_Box(ix,iy+14,iw,ih, "??");
|
|
prSize->align(FL_ALIGN_CENTER|FL_ALIGN_INSIDE);
|
|
prSize->labelsize(12);
|
|
prSize->box(FL_NO_BOX);
|
|
|
|
ix += iw;
|
|
o = new Fl_Box(ix,iy,iw,ih, "Rate");
|
|
o->box(FL_RFLAT_BOX);
|
|
o->color(FL_DARK2);
|
|
o->labelsize(12);
|
|
o->tooltip("Current transfer Rate (KBytes/sec)");
|
|
prRate = new Fl_Box(ix,iy+14,iw,ih, "??");
|
|
prRate->align(FL_ALIGN_CENTER|FL_ALIGN_INSIDE);
|
|
prRate->labelsize(12);
|
|
prRate->box(FL_NO_BOX);
|
|
|
|
ix += iw;
|
|
o = new Fl_Box(ix,iy,iw,ih, "~Rate");
|
|
o->box(FL_RFLAT_BOX);
|
|
o->color(FL_DARK2);
|
|
o->labelsize(12);
|
|
o->tooltip("Average transfer Rate (KBytes/sec)");
|
|
pr_Rate = new Fl_Box(ix,iy+14,iw,ih, "??");
|
|
pr_Rate->align(FL_ALIGN_CENTER|FL_ALIGN_INSIDE);
|
|
pr_Rate->labelsize(12);
|
|
pr_Rate->box(FL_NO_BOX);
|
|
|
|
ix += iw;
|
|
prETAt = o = new Fl_Box(ix,iy,iw,ih, "ETA");
|
|
o->box(FL_RFLAT_BOX);
|
|
o->color(FL_DARK2);
|
|
o->labelsize(12);
|
|
o->tooltip("Estimated Time of Arrival");
|
|
prETA = new Fl_Box(ix,iy+14,iw,ih, "??");
|
|
prETA->align(FL_ALIGN_CENTER|FL_ALIGN_INSIDE);
|
|
prETA->labelsize(12);
|
|
prETA->box(FL_NO_BOX);
|
|
|
|
prButton = new Fl_Button(326, 9, 44, 19, "Stop");
|
|
prButton->tooltip("Stop this transfer");
|
|
prButton->box(FL_UP_BOX);
|
|
prButton->clear_visible_focus();
|
|
prButton->callback(prButton_scb, this);
|
|
|
|
group->box(FL_ROUNDED_BOX);
|
|
group->end();
|
|
}
|
|
|
|
DLItem::~DLItem()
|
|
{
|
|
free(shortname);
|
|
dFree(fullname);
|
|
free(log_text);
|
|
dFree(esc_url);
|
|
dFree(cookies_path);
|
|
}
|
|
|
|
/*
|
|
* Abort a running download
|
|
*/
|
|
void DLItem::abort_dl()
|
|
{
|
|
if (!log_done()) {
|
|
dClose(LogPipe[0]);
|
|
Fl::remove_fd(LogPipe[0]);
|
|
log_done(1);
|
|
// Stop wget
|
|
if (!fork_done())
|
|
kill(pid(), SIGTERM);
|
|
}
|
|
widget_done(1);
|
|
}
|
|
|
|
void DLItem::prButton_cb()
|
|
{
|
|
prButton->deactivate();
|
|
abort_dl();
|
|
}
|
|
|
|
void DLItem::child_init()
|
|
{
|
|
dClose(0); // stdin
|
|
dClose(1); // stdout
|
|
dClose(LogPipe[0]);
|
|
dup2(LogPipe[1], 2); // stderr
|
|
// set the locale to C for log parsing
|
|
setenv("LC_ALL", "C", 1);
|
|
// start wget
|
|
execvp(dl_argv[0], dl_argv.data());
|
|
}
|
|
|
|
/*
|
|
* Update displayed size
|
|
*/
|
|
void DLItem::update_prSize(int newsize)
|
|
{
|
|
char num[64];
|
|
|
|
if (newsize > 1024 * 1024)
|
|
snprintf(num, 64, "%.1fMB", (float)newsize / (1024*1024));
|
|
else
|
|
snprintf(num, 64, "%.0fKB", (float)newsize / 1024);
|
|
prSize->copy_label(num);
|
|
}
|
|
|
|
void DLItem::log_text_add(const char *buf, ssize_t st)
|
|
{
|
|
const char *p;
|
|
char *q, *d, num[64];
|
|
size_t esc_len;
|
|
|
|
// WORKAROUND: We have to escape '@' in FLTK tooltips.
|
|
auto esc_str = escape_tooltip(buf, st);
|
|
esc_len = esc_str.size();
|
|
|
|
// Make room...
|
|
if (log_len + esc_len >= log_max) {
|
|
log_max = log_len + esc_len + 1024;
|
|
log_text = (char *) dRealloc (log_text, log_max);
|
|
log_text[log_len] = 0;
|
|
prTitle->tooltip(log_text);
|
|
}
|
|
|
|
// FSM to remove wget's "dot-progress" (i.e. "^ " || "^[0-9]+K")
|
|
q = log_text + log_len;
|
|
for (p = esc_str.data(); (size_t)(p - esc_str.data()) < esc_len; ++p) {
|
|
switch (log_state) {
|
|
case ST_newline:
|
|
if (*p == ' ') {
|
|
log_state = ST_discard;
|
|
} else if (isdigit(*p)) {
|
|
*q++ = *p; log_state = ST_number;
|
|
} else if (*p == '\n') {
|
|
*q++ = *p;
|
|
} else {
|
|
*q++ = *p; log_state = ST_copy;
|
|
}
|
|
break;
|
|
case ST_number:
|
|
if (isdigit(*q++ = *p)) {
|
|
// keep here
|
|
} else if (*p == 'K') {
|
|
for (--q; isdigit(q[-1]); --q) ;
|
|
log_state = ST_discard;
|
|
} else {
|
|
log_state = ST_copy;
|
|
}
|
|
break;
|
|
case ST_discard:
|
|
if (*p == '\n')
|
|
log_state = ST_newline;
|
|
break;
|
|
case ST_copy:
|
|
if ((*q++ = *p) == '\n')
|
|
log_state = ST_newline;
|
|
break;
|
|
}
|
|
}
|
|
*q = 0;
|
|
log_len = strlen(log_text);
|
|
|
|
// Now scan for the length of the file
|
|
if (total_bytesize == -1) {
|
|
p = strstr(log_text, "\nLength: ");
|
|
if (p && isdigit(p[9]) && strchr(p + 9, ' ')) {
|
|
for (p += 9, d = &num[0]; *p != ' '; ++p)
|
|
if (isdigit(*p))
|
|
*d++ = *p;
|
|
*d = 0;
|
|
total_bytesize = strtol (num, NULL, 10);
|
|
// Update displayed size
|
|
update_prSize(total_bytesize);
|
|
|
|
// WORKAROUND: For unknown reasons a redraw is needed here for some
|
|
// machines --jcid
|
|
group->redraw();
|
|
}
|
|
}
|
|
|
|
// Show we're connecting...
|
|
if (curr_bytesize == 0) {
|
|
prTitle->copy_label("Connecting...");
|
|
}
|
|
}
|
|
|
|
///
|
|
void DLItem::log_text_show()
|
|
{
|
|
MSG("\nStored Log:\n%s", log_text);
|
|
}
|
|
|
|
void DLItem::update_size(int new_sz)
|
|
{
|
|
char buf[64];
|
|
|
|
if (curr_bytesize == 0 && new_sz) {
|
|
// Start the timer with the first bytes got
|
|
init_time = time(NULL);
|
|
// Update the title
|
|
prTitle->copy_label(shortname);
|
|
// WORKAROUND: For unknown reasons a redraw is needed here for some
|
|
// machines --jcid
|
|
group->redraw();
|
|
}
|
|
|
|
curr_bytesize = new_sz;
|
|
if (curr_bytesize > 1024 * 1024)
|
|
snprintf(buf, 64, "%.1fMB", (float)curr_bytesize / (1024*1024));
|
|
else
|
|
snprintf(buf, 64, "%.0fKB", (float)curr_bytesize / 1024);
|
|
prGot->copy_label(buf);
|
|
if (total_bytesize == -1) {
|
|
prBar->showtext(false);
|
|
prBar->move(1);
|
|
} else {
|
|
prBar->showtext(true);
|
|
double pos = 100.0;
|
|
if (total_bytesize > 0)
|
|
pos *= (double)curr_bytesize / total_bytesize;
|
|
prBar->position(pos);
|
|
}
|
|
}
|
|
|
|
static void read_log_cb(int fd_in, void *data)
|
|
{
|
|
DLItem *dl_item = (DLItem *)data;
|
|
const int BufLen = 4096;
|
|
char Buf[BufLen];
|
|
ssize_t st;
|
|
|
|
do {
|
|
st = read(fd_in, Buf, BufLen);
|
|
if (st < 0) {
|
|
if (errno == EAGAIN) {
|
|
break;
|
|
}
|
|
perror("read, ");
|
|
break;
|
|
} else if (st == 0) {
|
|
dClose(fd_in);
|
|
Fl::remove_fd(fd_in, 1);
|
|
dl_item->log_done(1);
|
|
break;
|
|
} else {
|
|
dl_item->log_text_add(Buf, st);
|
|
}
|
|
} while (1);
|
|
}
|
|
|
|
void DLItem::father_init()
|
|
{
|
|
dClose(LogPipe[1]);
|
|
Fl::add_fd(LogPipe[0], 1, read_log_cb, this); // Read
|
|
|
|
// Start the timer after the child is running.
|
|
// (this makes a big difference with wget)
|
|
//init_time = time(NULL);
|
|
}
|
|
|
|
/*
|
|
* Our wget exited, let's check its status and update the panel.
|
|
*/
|
|
void DLItem::child_finished(int status)
|
|
{
|
|
wget_status(status);
|
|
|
|
if (status == 0) {
|
|
prButton->label("Done");
|
|
prButton->tooltip("Close this information panel");
|
|
} else {
|
|
prButton->label("Close");
|
|
prButton->tooltip("Close this information panel");
|
|
status_msg("ABORTED");
|
|
if (curr_bytesize == 0) {
|
|
// Update the title
|
|
prTitle->copy_label(shortname);
|
|
}
|
|
}
|
|
prButton->activate();
|
|
prButton->redraw();
|
|
MSG("wget status %d\n", status);
|
|
}
|
|
|
|
/*
|
|
* Convert seconds into human readable [hour]:[min]:[sec] string.
|
|
*/
|
|
static void secs2timestr(int et, char *str)
|
|
{
|
|
int eh, em, es;
|
|
|
|
eh = et / 3600; em = (et % 3600) / 60; es = et % 60;
|
|
if (eh == 0) {
|
|
if (em == 0)
|
|
snprintf(str, 16, "%ds", es);
|
|
else
|
|
snprintf(str, 16, "%dm%ds", em, es);
|
|
} else {
|
|
snprintf(str, 16, "%dh%dm", eh, em);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Update Got, Rate, ~Rate and ETA
|
|
*/
|
|
void DLItem::update()
|
|
{
|
|
struct stat ss;
|
|
time_t curr_time;
|
|
float csec, tsec, rate, _rate = 0;
|
|
char str[64];
|
|
int et;
|
|
|
|
if (updates_done())
|
|
return;
|
|
|
|
/* Update curr_size */
|
|
if (stat(fullname, &ss) == -1) {
|
|
MSG("stat, %s\n", dStrerror(errno));
|
|
return;
|
|
}
|
|
update_size((int)ss.st_size);
|
|
|
|
/* Get current time */
|
|
time(&curr_time);
|
|
csec = (float) (curr_time - init_time);
|
|
|
|
/* Rate */
|
|
if (csec >= 2) {
|
|
tsec = (float) (curr_time - twosec_time);
|
|
rate = ((float)(curr_bytesize-twosec_bytesize) / 1024) / tsec;
|
|
snprintf(str, 64, (rate < 100) ? "%.1fK/s" : "%.0fK/s", rate);
|
|
prRate->copy_label(str);
|
|
}
|
|
/* ~Rate */
|
|
if (csec >= 1) {
|
|
_rate = ((float)(curr_bytesize-init_bytesize) / 1024) / csec;
|
|
snprintf(str, 64, (_rate < 100) ? "%.1fK/s" : "%.0fK/s", _rate);
|
|
pr_Rate->copy_label(str);
|
|
}
|
|
|
|
/* ETA */
|
|
if (fork_done()) {
|
|
updates_done(1); // Last update
|
|
prETAt->label("Time");
|
|
prETAt->tooltip("Download Time");
|
|
prETAt->redraw();
|
|
secs2timestr((int)csec, str);
|
|
prETA->copy_label(str);
|
|
if (total_bytesize == -1) {
|
|
update_prSize(curr_bytesize);
|
|
if (wget_status() == 0)
|
|
status_msg("Done");
|
|
}
|
|
} else {
|
|
if (_rate > 0 && total_bytesize > 0 && curr_bytesize > 0) {
|
|
et = (int)((total_bytesize-curr_bytesize) / (_rate * 1024));
|
|
secs2timestr(et, str);
|
|
prETA->copy_label(str);
|
|
}
|
|
}
|
|
|
|
/* Update one and two secs ago times and bytesizes */
|
|
twosec_time = onesec_time;
|
|
onesec_time = curr_time;
|
|
twosec_bytesize = onesec_bytesize;
|
|
onesec_bytesize = curr_bytesize;
|
|
}
|
|
|
|
// SIGCHLD -------------------------------------------------------------------
|
|
|
|
/*! SIGCHLD handler
|
|
*/
|
|
static void raw_sigchld(int)
|
|
{
|
|
caught_sigchld = 1;
|
|
}
|
|
|
|
/*! Establish SIGCHLD handler */
|
|
static void est_sigchld(void)
|
|
{
|
|
struct sigaction sigact;
|
|
sigset_t set;
|
|
|
|
(void) sigemptyset(&set);
|
|
sigact.sa_handler = raw_sigchld;
|
|
sigact.sa_mask = set;
|
|
sigact.sa_flags = SA_NOCLDSTOP;
|
|
if (sigaction(SIGCHLD, &sigact, NULL) == -1) {
|
|
perror("sigaction");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Timeout function to check wget's exit status.
|
|
*/
|
|
static void cleanup_cb(void *data)
|
|
{
|
|
DLItemList *list = (DLItemList *)data;
|
|
|
|
sigprocmask(SIG_BLOCK, &mask_sigchld, NULL);
|
|
if (caught_sigchld) {
|
|
/* Handle SIGCHLD */
|
|
int i, status;
|
|
for (i = 0; i < list->num(); ++i) {
|
|
if (!list->get(i)->fork_done() &&
|
|
waitpid(list->get(i)->pid(), &status, WNOHANG) > 0) {
|
|
list->get(i)->child_finished(status);
|
|
list->get(i)->fork_done(1);
|
|
}
|
|
}
|
|
caught_sigchld = 0;
|
|
}
|
|
sigprocmask(SIG_UNBLOCK, &mask_sigchld, NULL);
|
|
|
|
Fl::repeat_timeout(1.0,cleanup_cb,data);
|
|
}
|
|
|
|
/*
|
|
* Timeout function to update the widget indicators,
|
|
* also remove widgets marked "done".
|
|
*/
|
|
static void update_cb(void *data)
|
|
{
|
|
static int cb_used = 0;
|
|
|
|
DLItemList *list = (DLItemList *)data;
|
|
|
|
/* Update the widgets and remove the ones marked as done */
|
|
for (int i = 0; i < list->num(); ++i) {
|
|
if (!list->get(i)->widget_done()) {
|
|
list->get(i)->update();
|
|
} else if (list->get(i)->fork_done()) {
|
|
// widget_done and fork_done avoid a race condition.
|
|
dl_win->del(i); --i;
|
|
}
|
|
cb_used = 1;
|
|
}
|
|
|
|
if (cb_used && list->num() == 0)
|
|
exit(0);
|
|
|
|
Fl::repeat_timeout(1.0,update_cb,data);
|
|
}
|
|
|
|
|
|
// DLWin ---------------------------------------------------------------------
|
|
|
|
/*
|
|
* Callback function for the request socket.
|
|
* Read the request, parse and start a new download.
|
|
*/
|
|
static void read_req_cb(int req_fd, void *)
|
|
{
|
|
struct sockaddr_un clnt_addr;
|
|
int sock_fd;
|
|
socklen_t csz;
|
|
Dsh *sh = NULL;
|
|
char *dpip_tag = NULL, *cmd_ptr = NULL, *url = NULL, *dl_dest = NULL, *ua = NULL;
|
|
std::string cmd;
|
|
|
|
/* Initialize the value-result parameter */
|
|
csz = sizeof(struct sockaddr_un);
|
|
/* accept the request */
|
|
do {
|
|
sock_fd = accept(req_fd, (struct sockaddr *) &clnt_addr, &csz);
|
|
} while (sock_fd == -1 && errno == EINTR);
|
|
if (sock_fd == -1) {
|
|
MSG("accept, %s fd=%d\n", dStrerror(errno), req_fd);
|
|
return;
|
|
}
|
|
|
|
/* create a sock handler */
|
|
sh = a_Dpip_dsh_new(sock_fd, sock_fd, 8*1024);
|
|
|
|
/* Authenticate our client... */
|
|
if (!(dpip_tag = a_Dpip_dsh_read_token(sh, 1)) ||
|
|
a_Dpip_check_auth(dpip_tag) < 0) {
|
|
MSG("can't authenticate request: %s fd=%d\n", dStrerror(errno), sock_fd);
|
|
a_Dpip_dsh_close(sh);
|
|
goto end;
|
|
}
|
|
dFree(dpip_tag);
|
|
|
|
/* Read request */
|
|
if (!(dpip_tag = a_Dpip_dsh_read_token(sh, 1))) {
|
|
MSG("can't read request: %s fd=%d\n", dStrerror(errno), sock_fd);
|
|
a_Dpip_dsh_close(sh);
|
|
goto end;
|
|
}
|
|
a_Dpip_dsh_close(sh);
|
|
_MSG("Received tag={%s}\n", dpip_tag);
|
|
|
|
if ((cmd_ptr = a_Dpip_get_attr(dpip_tag, "cmd")) == NULL) {
|
|
MSG("Failed to parse 'cmd' in {%s}\n", dpip_tag);
|
|
goto end;
|
|
}
|
|
cmd= cmd_ptr;
|
|
if (strcmp(cmd.c_str(), "DpiBye") == 0) {
|
|
MSG("got DpiBye, ignoring...\n");
|
|
goto end;
|
|
}
|
|
if (strcmp(cmd.c_str(), "download") != 0) {
|
|
MSG("unknown command: '%s'. Aborting.\n", cmd.c_str());
|
|
goto end;
|
|
}
|
|
if (!(url = a_Dpip_get_attr(dpip_tag, "url"))){
|
|
MSG("Failed to parse 'url' in {%s}\n", dpip_tag);
|
|
goto end;
|
|
}
|
|
if (!(dl_dest = a_Dpip_get_attr(dpip_tag, "destination"))){
|
|
MSG("Failed to parse 'destination' in {%s}\n", dpip_tag);
|
|
goto end;
|
|
}
|
|
if (!(ua = a_Dpip_get_attr(dpip_tag, "user-agent"))){
|
|
MSG("Failed to parse 'user-agent' in {%s}\n", dpip_tag);
|
|
goto end;
|
|
}
|
|
dl_win->add(dl_dest, url, ua);
|
|
|
|
end:
|
|
dFree(url);
|
|
dFree(dl_dest);
|
|
dFree(dpip_tag);
|
|
a_Dpip_dsh_free(sh);
|
|
}
|
|
|
|
/*
|
|
* Callback for close window request (WM or EscapeKey press)
|
|
*/
|
|
static void dlwin_esc_cb(Fl_Widget *, void *)
|
|
{
|
|
const char *msg = "There are running downloads.\n"
|
|
"ABORT them and EXIT anyway?";
|
|
|
|
if (dl_win && dl_win->num_running() > 0) {
|
|
fl_message_title("Flenser Downloads: Abort downloads?");
|
|
int ch = fl_choice("%s", "Cancel", "*No", "Yes", msg);
|
|
if (ch == 0 || ch == 1)
|
|
return;
|
|
}
|
|
|
|
/* abort each download properly */
|
|
dl_win->abort_all();
|
|
}
|
|
|
|
/*
|
|
* Add a new download request to the main window and
|
|
* fork a child to do the job.
|
|
*/
|
|
void DLWin::add(const char *full_filename, const char *url, const char *user_agent)
|
|
{
|
|
DLItem *dl_item = new DLItem(full_filename, url, user_agent);
|
|
mDList->add(dl_item);
|
|
mPG->insert(*dl_item->get_widget(), 0);
|
|
|
|
_MSG("Child index = %d\n", mPG->find(dl_item->get_widget()));
|
|
|
|
// Start the child process
|
|
pid_t f_pid = fork();
|
|
if (f_pid == 0) {
|
|
/* child */
|
|
dl_item->child_init();
|
|
_exit(EXIT_FAILURE);
|
|
} else if (f_pid < 0) {
|
|
perror("fork, ");
|
|
exit(1);
|
|
} else {
|
|
/* father */
|
|
dl_item->get_widget()->show();
|
|
dl_win->show();
|
|
dl_item->pid(f_pid);
|
|
dl_item->father_init();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Delete a download request from the main window.
|
|
*/
|
|
void DLWin::del(int n_item)
|
|
{
|
|
DLItem *dl_item = mDList->get(n_item);
|
|
|
|
// Remove the widget from the packed group
|
|
mPG->remove(dl_item->get_widget());
|
|
mScroll->redraw();
|
|
mDList->del(n_item);
|
|
delete(dl_item);
|
|
}
|
|
|
|
/*
|
|
* Return number of entries
|
|
*/
|
|
int DLWin::num()
|
|
{
|
|
return mDList->num();
|
|
}
|
|
|
|
/*
|
|
* Return number of running downloads
|
|
*/
|
|
int DLWin::num_running()
|
|
{
|
|
int i, nr;
|
|
|
|
for (i = nr = 0; i < mDList->num(); ++i)
|
|
if (!mDList->get(i)->fork_done())
|
|
++nr;
|
|
return nr;
|
|
}
|
|
|
|
/*
|
|
* Set a callback function for the request socket
|
|
*/
|
|
void DLWin::listen(int req_fd)
|
|
{
|
|
Fl::add_fd(req_fd, 1, read_req_cb, NULL); // Read
|
|
}
|
|
|
|
/*
|
|
* Abort each download properly, and let the main cycle exit
|
|
*/
|
|
void DLWin::abort_all()
|
|
{
|
|
for (int i = 0; i < mDList->num(); ++i)
|
|
mDList->get(i)->abort_dl();
|
|
}
|
|
|
|
/*
|
|
* A Scroll class that resizes its resizable widget to its width.
|
|
* see http://seriss.com/people/erco/fltk/#ScrollableWidgetBrowser
|
|
*/
|
|
class DlScroll : public Fl_Scroll
|
|
{
|
|
public:
|
|
void resize(int x_, int y_, int w_, int h_)
|
|
{
|
|
Fl_Scroll::resize(x_, y_, w_, h_);
|
|
Fl_Widget *resizable_ = resizable();
|
|
int sb_size =
|
|
resizable_->h() <= h() ? 0 :
|
|
scrollbar_size() ? scrollbar_size() :
|
|
Fl::scrollbar_size();
|
|
if (resizable_)
|
|
resizable_->resize(resizable_->x(),
|
|
resizable_->y(),
|
|
w() - sb_size,
|
|
resizable_->h());
|
|
}
|
|
DlScroll(int x, int y, int w, int h, const char *l = 0)
|
|
: Fl_Scroll(x, y, w, h, l)
|
|
{
|
|
}
|
|
};
|
|
|
|
/*
|
|
* Create the main window and an empty list of requests.
|
|
*/
|
|
DLWin::DLWin(int ww, int wh) {
|
|
|
|
// Init an empty list for the download requests
|
|
mDList = new DLItemList();
|
|
|
|
// Create the empty main window
|
|
mWin = new Fl_Window(ww, wh, "Flenser Downloads");
|
|
mWin->begin();
|
|
mScroll = new DlScroll(0,0,ww,wh);
|
|
mScroll->begin();
|
|
mPG = new Fl_Pack(0,0,ww-18,wh);
|
|
mPG->end();
|
|
mScroll->end();
|
|
mScroll->type(Fl_Scroll::VERTICAL);
|
|
mScroll->resizable(mPG);
|
|
mWin->end();
|
|
mWin->resizable(mScroll);
|
|
mWin->callback(dlwin_esc_cb, NULL);
|
|
mWin->show();
|
|
|
|
// Set SIGCHLD handlers
|
|
sigemptyset(&mask_sigchld);
|
|
sigaddset(&mask_sigchld, SIGCHLD);
|
|
est_sigchld();
|
|
|
|
fl_message_title_default("Flenser Downloads: Message");
|
|
|
|
// Set the cleanup timeout
|
|
Fl::add_timeout(1.0, cleanup_cb, mDList);
|
|
// Set the update timeout
|
|
Fl::add_timeout(1.0, update_cb, mDList);
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/*
|
|
* Set FL_NORMAL_LABEL to interpret neither symbols (@) nor shortcuts (&)
|
|
*/
|
|
static void custLabelDraw(const Fl_Label* o, int X, int Y, int W, int H,
|
|
Fl_Align align)
|
|
{
|
|
const int interpret_symbols = 0;
|
|
|
|
fl_draw_shortcut = 0;
|
|
fl_font(o->font, o->size);
|
|
fl_color((Fl_Color)o->color);
|
|
fl_draw(o->value, X, Y, W, H, align, o->image, interpret_symbols);
|
|
}
|
|
|
|
static void custLabelMeasure(const Fl_Label* o, int& W, int& H)
|
|
{
|
|
const int interpret_symbols = 0;
|
|
|
|
fl_draw_shortcut = 0;
|
|
fl_font(o->font, o->size);
|
|
fl_measure(o->value, W, H, interpret_symbols);
|
|
}
|
|
|
|
|
|
|
|
//int main(int argc, char **argv)
|
|
int main()
|
|
{
|
|
int ww = 420, wh = 85;
|
|
|
|
Fl::lock();
|
|
|
|
// Disable '@' and '&' interpretation in normal labels.
|
|
Fl::set_labeltype(FL_NORMAL_LABEL, custLabelDraw, custLabelMeasure);
|
|
|
|
Fl::scheme(NULL);
|
|
|
|
// Create the download window
|
|
dl_win = new DLWin(ww, wh);
|
|
|
|
// Start listening to the request socket
|
|
dl_win->listen(STDIN_FILENO);
|
|
|
|
MSG("started...\n");
|
|
|
|
return Fl::run();
|
|
}
|
|
|