From 6efd8b9b4a0e1d0202d60d9c96a158505147699b Mon Sep 17 00:00:00 2001 From: Doug Torrance Date: Sun, 7 Feb 2016 00:53:15 -0500 Subject: [PATCH] wmget: Add version 0.6.0 to repository. Obtained from [1]. [1] http://amtrickey.net/download/wmget-0.6.0-src.tar.gz --- wmget/Makefile | 179 +++++++ wmget/NEWS | 89 ++++ wmget/README | 70 +++ wmget/TODO | 14 + wmget/cancel.c | 93 ++++ wmget/config.def | 62 +++ wmget/configure.c | 419 +++++++++++++++ wmget/dockapp/Makefile | 35 ++ wmget/dockapp/da_mouse.c | 125 +++++ wmget/dockapp/da_mouse.h | 47 ++ wmget/dockapp/da_run.c | 266 +++++++++ wmget/dockapp/da_x.c | 481 +++++++++++++++++ wmget/dockapp/dockapp.h | 158 ++++++ wmget/iq.c | 233 ++++++++ wmget/list.c | 89 ++++ wmget/messages.c | 106 ++++ wmget/request.c | 216 ++++++++ wmget/retrieve.c | 225 ++++++++ wmget/server.c | 1096 ++++++++++++++++++++++++++++++++++++++ wmget/usage.c | 62 +++ wmget/wmget-test.pl | 45 ++ wmget/wmget.1 | 217 ++++++++ wmget/wmget.c | 127 +++++ wmget/wmget.h | 251 +++++++++ wmget/wmget.refentry.xml | 492 +++++++++++++++++ wmget/wmget.xpm | 129 +++++ 26 files changed, 5326 insertions(+) create mode 100644 wmget/Makefile create mode 100644 wmget/NEWS create mode 100644 wmget/README create mode 100644 wmget/TODO create mode 100644 wmget/cancel.c create mode 100644 wmget/config.def create mode 100644 wmget/configure.c create mode 100644 wmget/dockapp/Makefile create mode 100644 wmget/dockapp/da_mouse.c create mode 100644 wmget/dockapp/da_mouse.h create mode 100644 wmget/dockapp/da_run.c create mode 100644 wmget/dockapp/da_x.c create mode 100644 wmget/dockapp/dockapp.h create mode 100644 wmget/iq.c create mode 100644 wmget/list.c create mode 100644 wmget/messages.c create mode 100644 wmget/request.c create mode 100644 wmget/retrieve.c create mode 100644 wmget/server.c create mode 100644 wmget/usage.c create mode 100755 wmget/wmget-test.pl create mode 100644 wmget/wmget.1 create mode 100644 wmget/wmget.c create mode 100644 wmget/wmget.h create mode 100644 wmget/wmget.refentry.xml create mode 100644 wmget/wmget.xpm diff --git a/wmget/Makefile b/wmget/Makefile new file mode 100644 index 0000000..483cb55 --- /dev/null +++ b/wmget/Makefile @@ -0,0 +1,179 @@ +# Copyright (c) 2001-2003 Aaron Trickey +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# Makefile for the ``wmget'' (formerly ``wmcurl'') project. +# This Makefile requires GNU make and probably other GNU stuff... + + +######################################################################## +# Build Targets: +# +# all [default]: Builds the wmget application and documentation +# install: Installs the application +# uninstall: Attempts to uninstall, if this makefile installed it +# dockapplib: Recurses into the dockapp dir and builds the library +# doc: Builds all documentation (okay, it's only a manpage) +# clean: Cleans, except for generated HTML/man docs +# docclean: Cleans generated docs +# slackpkg: Builds a Slackware package in packages/slackware +# sourceball: Builds a source+docs tarball in packages/source +######################################################################## + +all: wmget doc + +.PHONY: all install uninstall dockapplib doc clean \ + docclean slackpkg sourceball + + +##### BUILD SETTINGS AND VARIABLES ##################################### + +# To specify a different prefix, you can override this on the command line +# make PREFIX=/opt/dockapps install +PREFIX= /usr/local + +INSTALLDIR= install -d -m 755 +INSTALLBIN= install -m 555 +INSTALLMAN= install -m 444 +CC= gcc +CFLAGS= -Wall -W -I/usr/X11R6/include -O +# The following line is what I use during development +#CFLAGS:= $(CFLAGS) -Werror -g +LDFLAGS= -L/usr/X11R6/lib -lXpm -lXext -lX11 -lm -lcurl +DOCS= wmget.1 + +VERSION:= $(shell grep '\#define WMGET_VERSION ' wmget.h \ + | sed -e 's/.*"\(.*\)".*/\1/' ) + +OBJS= server.o \ + request.o \ + cancel.o \ + list.o \ + retrieve.o \ + iq.o \ + wmget.o \ + configure.o \ + messages.o \ + usage.o + +DALIBDIR= dockapp +DALIB= $(DALIBDIR)/dockapp.a + +ALL_SRCS= $(subst .o,.c,$(OBJS)) + + +##### PROGRAM ########################################################## + +install: all + echo $(PREFIX) > install.prefix ; \ + $(INSTALLDIR) $(PREFIX)/bin ; \ + $(INSTALLBIN) wmget $(PREFIX)/bin/wmget ; \ + $(INSTALLDIR) $(PREFIX)/man/man1 ; \ + $(INSTALLMAN) wmget.1 $(PREFIX)/man/man1/wmget.1 ; \ + +uninstall: + cd `cat install.prefix` && rm -f bin/wmget man/man1/wmget.1 + rm -f install.prefix + +wmget: dockapplib $(OBJS) + $(CC) $(CFLAGS) $(LDFLAGS) $(OBJS) $(DALIB) -o $@ + + +##### LIBRARY ########################################################## + +dockapplib: + make -C $(DALIBDIR) + + + +##### DOCUMENTATION #################################################### + +doc: $(DOCS) + + +# NOTE: The wmget refentry page uses an PI, I use an XML +# catalog file to map the given URL to a local path + +wmget.1: wmget.refentry.xml + xsltproc --nonet $< + + +##### CLEANUP ########################################################## + +clean: + rm -f *.o wmget core pod2html-* install.prefix \ + wmget.html + make -C dockapp clean + +docclean: + rm -f $(DOCS) + + +##### SLACKWARE PACKAGE ################################################ + +SLACK_PFX= slackroot/$(PREFIX) +SLACK_PACKAGE= packages/slackware/wmget-$(VERSION).tgz + +slackpkg: wmget doc + -mkdir -p packages/slackware + rm -rf slackroot + $(INSTALLDIR) $(SLACK_PFX) + $(INSTALLDIR) $(SLACK_PFX)/bin + $(INSTALLDIR) $(SLACK_PFX)/man + $(INSTALLDIR) $(SLACK_PFX)/man/man1 + $(INSTALLBIN) wmget $(SLACK_PFX)/bin/wmget + $(INSTALLMAN) wmget.1 $(SLACK_PFX)/man/man1/wmget.1 + cd slackroot && \ + tar czv -f ../$(SLACK_PACKAGE) \ + --owner=root \ + --group=root \ + * + rm -rf slackroot + + +##### SOURCE PACKAGE ################################################### + +SOURCEBALL= packages/source/wmget-$(VERSION)-src.tar.gz + +sourceball: doc clean + -mkdir -p packages/source + cd .. && \ + tar czv --exclude RCS \ + --exclude .\* \ + --exclude packages \ + --exclude tags \ + --exclude \*~ \ + --exclude working \ + --exclude www \ + -f wmget/$(SOURCEBALL) \ + wmget + + +##### WEB SITE ######################################################### + +WWW_SRC= $(HOME)/amtrickey.net/src +WWW_DOWNLOAD= $(WWW_SRC)/download +WWW_WMGET= $(WWW_SRC)/wmget + +www: slackpkg sourceball + cp $(SLACK_PACKAGE) $(WWW_DOWNLOAD) + cp $(SOURCEBALL) $(WWW_DOWNLOAD) + cp NEWS wmget.refentry.xml $(WWW_WMGET) + + + diff --git a/wmget/NEWS b/wmget/NEWS new file mode 100644 index 0000000..26497f9 --- /dev/null +++ b/wmget/NEWS @@ -0,0 +1,89 @@ + +Development History for wmget + +wmget 0.6.0 - More options, nominal error handling, fixes + + * New options: auth, proxy_auth, ascii, interface, referer, headers + * Failed downloads now generate a .ERROR file containing + error text, instead of making you guess + * Minor fixes & cleanups + * Rewrote much of the manpage, and moved it from POD to DocBook-XML + +wmget 0.5.0 - RC file, more options, bugfixes + + * wmget may now be configured via ~/.wmgetrc + * Cleaned up and more comprehensive command-line options + * More download options available: proxy support, redirect support, + and yes, FINALLY... output directory support! + * Some old source cleanups, and some new source sloppiness. + * Fix to a bug that created garbage download names + +wmget 0.4.4 - Fix to work with libcurl > 7.9.5 + + * Thanks again to Rafal Zawadzki for a report: wmget + newer libcurl + did not show download progress. After updating my libcurl it + turns out that the progress callback function signature has + changed, so a little #iffing and all is well again. + +wmget 0.4.3 - Minor bugfixes, cleanups + + * Fixed the makefile sourceball target to unpack into a subdirectory + * Cleaned up a couple of bits of code + * Removed -Werror and -g from normal builds + * Thanks to Rafal Zawadzki for building a .deb and submitting it to + Debian Sid, and to Kurt Hindenburg for reporting build and tarball + issues. + +wmget 0.4.2 - Fixed AfterStep support + + * The new dockapp code no longer worked right under AfterStep's + Wharf. Fixed. Thanks to Andy Jalics for the email bugreport. + * Updated the docs to reflect the fact that Window Maker is not the + only WM out there. :) + +wmget 0.4.1 - Bugfix + + * Off-by-one error in a memmove() caused garbage characters to + appear at the end of downloaded filenames on the local system in + certain cases. + +wmget 0.4.0 - New ``dockapp'' library, cleanups, features + + * Middle clicking on the dockapp now starts a download of the URI in + the primary X selection (right-click on a link in Mozilla, select + ``Copy Link Address'', and middle-click on wmget... presto.) + * Wrote from scratch a new little library to do dockapp programming. + Hides all the X stuff, and provides a main loop with a few new + features. Replaces the old wmgeneral.c. + * Fixed default save-name of URL's with no trailing filename + component---now selects the final path component, whatever it is + (e.g. http://example.com/ => "example.com"); also fixed the + display name to always show the basename of the save filename if + no display name was provided + * Added a Slackware package target. + * Corrected/added command line help (LIST was missing...) + * Cleanups: Makefile, source, docs. + +wmget 0.3.0 - A couple of new features, cleanups, fixes + + * Implemented CANCEL and LIST commands + * Minor doc cleanup + * Fixed it to work with more recent libcurl's (thanks to Paul Tweedy + for emailing me about that; I hadn't upgraded my cURL in a while) + +wmget 0.2.0 - Rewrite of the client/server ipc. Renaming. + + Very little user-visible change. + + Rewrote client/server ipc to use text requests/responses over a unix + domain socket rather than binary structures over a FIFO. + + Also, with this version, I renamed it from ``wmcurl'' to ``wmget''. + The old name was sorta a poor choice: while I still rely on, and want + to give full credit to, the cURL guys, the name ``wmcurl'' is + not very intuitive. Plus I didn't want people thinking it was part + of the cURL project proper. Plus this saves me a keystroke when + typing it :) + +wmcurl 0.1.0 - Initial release. + diff --git a/wmget/README b/wmget/README new file mode 100644 index 0000000..9e105e0 --- /dev/null +++ b/wmget/README @@ -0,0 +1,70 @@ + +wmget - Background download manager in a Window Maker dock app +Copyright (C) 2001-2003 Aaron Trickey +Free software, licensed under ``MIT-style'' terms (see below) +ABSOLUTELY NO WARRANTY -- SEE BELOW + +[This file is only rarely updated. If you're upgrading from a prior + release, please see the file NEWS.] + +wmget is a dock app for the GNU Window Maker window manager (or one of +the many other WM's which support dockapps) which makes it more +convenient to perform long downloads in the background. It uses the +excellent libcurl library, part of the cURL automated-download program, +to perform file retrieval. + +The latest information about and version of wmget will hopefully always +be available at: + + +Wmget requires libcurl (various versions from 7.5.1 on were used during +development); this is part of the main cURL distribution. Grab it from: + + +configure and install it if you don't have it. And for routine download +tasks, check out their ``curl'' command-line tool. + +wmget is written entirely in C. It (as of 0.4) uses a from-scratch +dockapp library I cooked up for fun. This replaced a common dockapp +utility library named wmgeneral.c, which helped me get off the ground; +thanks to the authors (Martijn Pieterse & Dave Clark). + +The wmget manpage is written in Perl's POD documentation language. If you +don't have Perl, that's fine; just don't delete and try to remake the +manpage I provided. + +I'm releasing my code under the same licensing terms as the X Window +System. These terms are commonly known as ``MIT'' licensing terms, +since that's where they originated. I've attached these licensing +terms, and the important non-warranty clauses, below. + +Enjoy! + +Aaron Trickey +aaron at amtrickey dot net + + +---------------------------------------------------------------------------- + +Licensing and Non-Warranty Terms and Disclaimers. + +Copyright (c) 2001-2003 Aaron Trickey + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + diff --git a/wmget/TODO b/wmget/TODO new file mode 100644 index 0000000..ab4eda7 --- /dev/null +++ b/wmget/TODO @@ -0,0 +1,14 @@ + + +Missing things: +- graphical indication of errors + +Improvable things: +- more packages +- XDND support + +- bigger clickable region +- allow overwrite of zero-length files and/or clean up files after + failure + + diff --git a/wmget/cancel.c b/wmget/cancel.c new file mode 100644 index 0000000..3c77662 --- /dev/null +++ b/wmget/cancel.c @@ -0,0 +1,93 @@ +/* + wmget - A background download manager as a Window Maker dock app + Copyright (c) 2001-2003 Aaron Trickey + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + ******************************************************************** + cancel.c - Client code for canceling download requests + + When invoked with the ``cancel'' argument, main() calls cancel(), + defined below. This code expects a job ID and forms and submits + a CANCEL request to the server. +*/ + +#include +#include +#include + +#include "wmget.h" + +int cancel (int argc, char **argv) +{ + char line[MAXCMDLEN + 1]; + char *word_break; + FILE *fp; + job_id_t job_id; + + /* The argument vector we receive is guaranteed to start with + * { , cancel }. Currently, we don't support any + * options or anything; we just expect a single nonzero job ID. + */ + if (argc < 3) { + error ("What do you want me to cancel? (Missing job number)"); + return 1; + } else if (argc > 3) { + error ("Extra arguments: cancel takes only a job number"); + return 1; + } else if (!(job_id = strtoul (argv[2], &word_break, 0)) + || *word_break) { + error ("Invalid job number (must be an integer > 0)"); + return 1; + } + + if (!(fp = iq_client_connect ())) + return 1; + + if (fprintf (fp, "CANCEL JOBID(%lu)\r\n", job_id) == EOF) { + error_sys ("Could not submit command to server"); + fclose (fp); + return 1; + } + + if (!fgets (line, sizeof line - 1, fp)) { + error ("Server did not respond to command!"); + fclose (fp); + return 1; + } + + /* Extract the first word and compare. */ + word_break = line + strcspn (line, " \t\r\n"); + + if (*word_break) + *word_break++ = 0; + + if (strcasecmp (line, RESPONSE_JOB_CANCELED)) { + error ("Server responded with error: %s", word_break); + fclose (fp); + return 1; + } + + fclose (fp); + + return 0; +} + + diff --git a/wmget/config.def b/wmget/config.def new file mode 100644 index 0000000..21a6f57 --- /dev/null +++ b/wmget/config.def @@ -0,0 +1,62 @@ +/* + wmget - A background download manager as a Window Maker dock app + Copyright (c) 2001-2003 Aaron Trickey + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + ******************************************************************** + config.def - Definition of the command line and RC file settings + + This file is #included in multiple places in the source code, each + of which interprets it by providing a different #define for the + macros used here. Each macro takes the following parameters: + + O( + '', <-- short opt character, \0 for none + name, <-- option name (*) + yes|no, <-- has argument? + "documentation" <-- provided in help + ) + + (*): These are composed to produce RC file keywords, long + command-line options, and internal C identifiers. Write them as C + identifiers (they'll be prefixed, don't worry about collision with + keywords or other symbols), not as quoted strings. +*/ + +O( 's', silent, no, "Suppress any non-error messages" ) +O( 'V', verbose, no, "Produce verbose output (debugging only)" ) +O( 'o', output, yes,"Where to save the downloaded file" ) +O( 'd', display, yes,"The text to display in the progress bar" ) +O( 'O', overwrite, no, "Allow overwriting an existing file?" ) +O( 'C', continue, no, "Continue a previously-aborted download" ) +O( 'a', auth, yes,"USERNAME:PASSWORD for server" ) +O( 'p', proxy, yes,"Specify an HTTP proxy server" ) +O( 'P', proxy_auth, yes,"USERNAME:PASSWORD for HTTP proxy" ) +O( 'f', follow, yes,"How many HTTP redirects to follow (0 = off)" ) +O( 'U', user_agent, yes,"User-Agent to provide to Web servers" ) +O( 'B', ascii, no, "Force FTP downloads to be ASCII-mode" ) +O( 'e', referer, yes,"Set the referer URL passed to the HTTP server") +O( 'n', interface, yes,"Use this network interface (e.g. eth0)" ) +O( 'h', headers, no, "Include the HTTP response header in the file" ) + + + +/* vim: set filetype=c: */ diff --git a/wmget/configure.c b/wmget/configure.c new file mode 100644 index 0000000..71678ed --- /dev/null +++ b/wmget/configure.c @@ -0,0 +1,419 @@ +/* + wmget - A background download manager as a Window Maker dock app + Copyright (c) 2001-2003 Aaron Trickey + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + ******************************************************************** + config.c - Implementation of command-line and RC-file configuration + + The code in this file parses RC files and command lines and provides + defaults for what you don't specify. Used to configure both + requests and servers. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "wmget.h" + + + + + +/* Option characters. + */ +#define O(s,l,a,t) optchar_##l = s, +enum { +#include "config.def" +}; +#undef O + + + +/*********************************************************************** + * clear_request(): Initialize an empty Request object. + */ +void clear_request (Request *req) +{ + req->source_url = 0; + req->display = 0; + req->save_to = 0; + req->overwrite = -1; + req->continue_from = -1; + req->proxy = 0; + req->follow = -1; + req->user_agent = 0; + + req->use_ascii = -1; + req->referer = 0; + req->include = -1; + req->interface = 0; + req->proxy_auth = 0; + req->auth = 0; +} + + +static void set_silent () +{ + set_output_level (OL_SILENT); +} + + +static void set_verbose () +{ + set_output_level (OL_DEBUG); +} + + +static void set_output (Request *r, ServerConfig *c, const char *value) +{ + if (r) r->save_to = value; + if (c) { + struct stat st; + + /* For server configuration, we must set the default to an + * absolute path. + */ + if (value[0] == '/') { + if (strlen (value) + 1 > sizeof c->job_defaults.save_to) { + error ( +"Download directory name too long! Defaulting to home directory"); + strcpy (c->job_defaults.save_to, home_directory ()); + } else { + strcpy (c->job_defaults.save_to, value); + } + } else { + if (strlen (value) + strlen (home_directory ()) + 2 + > sizeof c->job_defaults.save_to) { + error ( +"Download directory name too long! Defaulting to home directory"); + strcpy (c->job_defaults.save_to, home_directory ()); + } else { + strcpy (c->job_defaults.save_to, home_directory ()); + strcat (c->job_defaults.save_to, "/"); + strcat (c->job_defaults.save_to, value); + } + } + + /* And we need to make sure it's really a directory. + */ + if ( stat (c->job_defaults.save_to, &st) + || !S_ISDIR (st.st_mode)) { + error ( +"``output'' option is not a valid directory! Defaulting to home"); + strcpy (c->job_defaults.save_to, home_directory ()); + } + } +} + + +static void set_display (Request *r, ServerConfig *c, const char *value) +{ + if (r) r->display = value; + if (c) info ("Cannot set ``display'' option on dockapp"); +} + + +static void set_overwrite (Request *r, ServerConfig *c) +{ + if (r) r->overwrite = 1; + if (c) c->job_defaults.overwrite = 1; +} + + +static void set_continue (Request *r, ServerConfig *c) +{ + if (r) r->continue_from = 1; + if (c) c->job_defaults.continue_from = 1; +} + + +static void set_proxy (Request *r, ServerConfig *c, const char *value) +{ + if (r) r->proxy = value; + if (c) STRCPY_TO_ARRAY (c->job_defaults.proxy, value); +} + + +static void set_follow (Request *r, ServerConfig *c, const char *value) +{ + if (r) r->follow = atoi (value); + if (c) c->job_defaults.follow = atoi (value); +} + + +static void set_user_agent (Request *r, ServerConfig *c, const char *v) +{ + if (r) r->user_agent = v; + if (c) STRCPY_TO_ARRAY (c->job_defaults.user_agent, v); +} + + +static void set_ascii (Request *r, ServerConfig *c) +{ + if (r) r->use_ascii = 1; + if (c) c->job_defaults.use_ascii = 1; +} + + +static void set_referer (Request *r, ServerConfig *c, const char *v) +{ + if (r) r->referer = v; + if (c) STRCPY_TO_ARRAY (c->job_defaults.referer, v); +} + + +static void set_headers (Request *r, ServerConfig *c) +{ + if (r) r->include = 1; + if (c) c->job_defaults.include = 1; +} + + +static void set_interface (Request *r, ServerConfig *c, const char *v) +{ + if (r) r->interface = v; + if (c) STRCPY_TO_ARRAY (c->job_defaults.interface, v); +} + + +static void set_proxy_auth (Request *r, ServerConfig *c, const char *v) +{ + if (r) r->proxy_auth = v; + if (c) STRCPY_TO_ARRAY (c->job_defaults.proxy_auth, v); +} + + +static void set_auth (Request *r, ServerConfig *c, const char *v) +{ + if (r) r->auth = v; + if (c) STRCPY_TO_ARRAY (c->job_defaults.auth, v); +} + + +/*********************************************************************** + * load_cmdline(): Parse options from the command line. + */ +static int load_cmdline (int argc, char **argv, + Request *req, ServerConfig *cfg) +{ + int o; + static const char shortopts[] = { +#define no +#define yes , ':' +#define O(s,l,a,t) s a, +#include "config.def" +#undef O +#undef no +#undef yes + 0 + }; + + static const struct option longopts[] = { +#define no no_argument +#define yes required_argument +#define O(s,l,a,t) { #l, a, 0, s }, +#include "config.def" +#undef O +#undef yes +#undef no + { 0, 0, 0, 0 } + }; + + while ((o = getopt_long (argc, argv, shortopts, longopts, 0)) + != EOF) { + switch (o) { + default: + return 1; + +#define yes , optarg +#define no +#define O(s,l,a,t) \ + case optchar_##l: \ + set_##l (req, cfg a); \ + break; +#include "config.def" +#undef O +#undef no +#undef yes + } + } + + if (optind < argc) { + if (req) { + if (strlen (argv[optind]) > MAXURL) { + error ("URL too long!"); + } else { + req->source_url = argv[optind]; + } + ++optind; + } + + if (cfg) { + if (strcasecmp (argv[optind], "dock") == 0) { + /* That's part of the syntax... ignore. */ + ++optind; + } + } + } + + if (optind < argc) { + error ("Extra argument: '%s'", argv[optind]); + } + + return 0; +} + + +static void read_rcfile (FILE *rcfp, ServerConfig *cfg) +{ + char line[MAXRCLINELEN]; + + while (fgets (line, sizeof line, rcfp)) { + char *name = 0; + char *value = 0; + char *value_end = 0; + char *tictactoe = strchr (line, '#'); + + if (tictactoe) { + *tictactoe = '\0'; + } + + name = line; /* locate name: */ + while (*name && isspace (*name)) /* skip leading ws */ + ++name; + if (!*name) { /* no name? skip line */ + continue; + } + value = name; /* locate value: */ + while (*value && !isspace (*value)) /* skip name */ + ++value; + if (*value) { /* not eol: look for val */ + *value++ = '\0'; /* terminate name */ + while (*value && isspace (*value)) /* skip dividing ws */ + ++value; + value_end = value + strlen (value); /* right-trim */ + --value_end; + while (value_end > value && isspace (*value_end)) { + *value_end-- = 0; + } + } + + +# define ARG_yes(NAM) \ + if (!*value) { \ + error ("Keyword '" #NAM "' in config file is missing " \ + "its required argument"); \ + } else { \ + debug ("set " #NAM " (%s)", value); \ + set_##NAM (0, cfg, value); \ + } + +# define ARG_no(NAM) \ + if (*value) { \ + error ("Keyword '" #NAM "' in config file has an "\ + "extra argument: '%s'", value); \ + } else { \ + debug ("set " #NAM " "); \ + set_##NAM (0, cfg); \ + } + +# define O(s,l,a,t) \ + if (strcasecmp (name, #l) == 0) { \ + ARG_##a (l) \ + } else + +# include "config.def" + +# undef O +# undef ARG_yes +# undef ARG_no + + error ("Unknown keyword in config file: %s", name); + } +} + + +static void load_rcfile (ServerConfig *cfg) +{ + char rcfile[MAXPATHLEN + 1]; + static const char *rcfile_base = "/.wmgetrc"; + FILE *rcfp = 0; + + strcpy (rcfile, home_directory ()); + if (strlen (rcfile) + strlen (rcfile_base) >= sizeof rcfile) { + error ("Your home directory name is too long!"); + return; + } + + strcat (rcfile, rcfile_base); + + if ((rcfp = fopen (rcfile, "rt"))) { + read_rcfile (rcfp, cfg); + } else { + /* rcfiles are fully optional... */ + debug_sys ("Could not open rcfile '%s'", rcfile); + } +} + + +void config_server (int argc, char **argv, ServerConfig *cfg) +{ + /* Default job options: These take effect unless overridden by + * server configuration or per-job configuration. + */ + cfg->job_defaults.display[0] = 0; + STRCPY_TO_ARRAY (cfg->job_defaults.save_to, home_directory ()); + cfg->job_defaults.overwrite = 0; + cfg->job_defaults.continue_from = 0; + cfg->job_defaults.proxy[0] = 0; + cfg->job_defaults.follow = 5; + STRCPY_TO_ARRAY (cfg->job_defaults.user_agent, DEFAULT_USER_AGENT); + + cfg->job_defaults.use_ascii = 0; + cfg->job_defaults.referer[0] = 0; + cfg->job_defaults.include = 0; + cfg->job_defaults.interface[0] = 0; + cfg->job_defaults.proxy_auth[0] = 0; + cfg->job_defaults.auth[0] = 0; + + load_rcfile (cfg); + load_cmdline (argc, argv, 0, cfg); +} + + +void config_request (int argc, char **argv, Request *req) +{ + clear_request (req); + + load_cmdline (argc, argv, req, 0); +} + + + + + + diff --git a/wmget/dockapp/Makefile b/wmget/dockapp/Makefile new file mode 100644 index 0000000..35af235 --- /dev/null +++ b/wmget/dockapp/Makefile @@ -0,0 +1,35 @@ +# Copyright (c) 2001-2003 Aaron Trickey +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# Makefile for the dockapp library packaged in the ``wmget'' application. + + +DAOBJS= da_run.o da_x.o da_mouse.o + +CFLAGS= -ansi -Wall -W -Werror -g +LDFLAGS= -L/usr/X11R6/lib -lXpm -lXext -lX11 + +dockapp.a: $(DAOBJS) + ar rcsv dockapp.a $(DAOBJS) + +unittest: unittest.o $(DAOBJS) + +clean: + rm -f unittest.o unittest dockapp.a $(DAOBJS) + diff --git a/wmget/dockapp/da_mouse.c b/wmget/dockapp/da_mouse.c new file mode 100644 index 0000000..9c56b18 --- /dev/null +++ b/wmget/dockapp/da_mouse.c @@ -0,0 +1,125 @@ +/* + wmget - A background download manager as a Window Maker dock app + Copyright (c) 2001-2003 Aaron Trickey + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + ******************************************************************** + dockapp/da_mouse.c - Mouse/clickregion handling + + This code keeps track of app-registered ``clickregions'', which + represent clickable rectangles and button/modifier masks associated + with app callbacks. +*/ + +#include +#include +#include "dockapp.h" + +#define DOCKAPP_EXPOSE_INTERNALS +#include "da_mouse.h" + + + +typedef struct { + int x, y, w, h; + int buttonmask; + + dockapp_rv_t (*cb) (void *, int x, int y); + void *cbdata; +} da_clickregion; + + +/* for now we use a simple array of clickregions + */ +#define DA_MAX_CLICKREGIONS 50 +static da_clickregion da_clickregions[DA_MAX_CLICKREGIONS]; +static size_t da_num_clickregions = 0; + + +/* a mouse-down simply stores the mouse state here for later processing + * by a mouse-up + */ +static int da_mouse_down_x; +static int da_mouse_down_y; +static int da_mouse_down_buttons; + + +void da_mouse_button_down (int x, int y, int buttons) +{ + /* fprintf (stderr, "hello from da_mouse_button_down\n"); */ + da_mouse_down_x = x; + da_mouse_down_y = y; + da_mouse_down_buttons = buttons; +} + + +dockapp_rv_t da_mouse_button_up (int x, int y, int buttons) +{ + da_clickregion *c, *end; + + /* + fprintf (stderr, "hello from da_mouse_button_up, btns=0x%02x\n", + buttons); + */ + + end = da_clickregions + da_num_clickregions; + + for (c = da_clickregions; c < end; ++c) { + if (((buttons & c->buttonmask) == c->buttonmask) + && x >= c->x && x < c->x + c->w + && y >= c->y && y < c->y + c->h + && da_mouse_down_x >= c->x && da_mouse_down_x < c->x + c->w + && da_mouse_down_y >= c->y && da_mouse_down_y < c->y + c->h) { + if (c->cb (c->cbdata, x, y) == dockapp_exit) + return dockapp_exit; + } + } + + return dockapp_ok; +} + + +dockapp_rv_t dockapp_add_clickregion ( + int x, int y, int w, int h, + int buttonmask, + dockapp_rv_t (*cb) (void *, int x, int y), + void *cbdata) +{ + da_clickregion *c; + + if (da_num_clickregions >= DA_MAX_CLICKREGIONS) + return dockapp_rv_too_many_clickregions; + + c = da_clickregions + da_num_clickregions++; + + c->x = x; + c->y = y; + c->w = w; + c->h = h; + c->buttonmask = buttonmask; + c->cb = cb; + c->cbdata = cbdata; + + return dockapp_ok; +} + + + diff --git a/wmget/dockapp/da_mouse.h b/wmget/dockapp/da_mouse.h new file mode 100644 index 0000000..6f439c5 --- /dev/null +++ b/wmget/dockapp/da_mouse.h @@ -0,0 +1,47 @@ +#ifndef I_DOCKAPP_DA_MOUSE_H +#define I_DOCKAPP_DA_MOUSE_H +/* + wmget - A background download manager as a Window Maker dock app + Copyright (c) 2001-2003 Aaron Trickey + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + ******************************************************************** + dockapp/da_mouse.h - Mouse/clickregion handling defs + + Private header file. +*/ + +#ifndef DOCKAPP_EXPOSE_INTERNALS +# error da_mouse.h #included... this is internal to the dockapp lib... +#endif + +void da_mouse_button_down ( + int xpos, + int ypos, + int state); + +dockapp_rv_t da_mouse_button_up ( + int xpos, + int ypos, + int state); + + +#endif /* I_DOCKAPP_DA_MOUSE_H */ diff --git a/wmget/dockapp/da_run.c b/wmget/dockapp/da_run.c new file mode 100644 index 0000000..3c032ac --- /dev/null +++ b/wmget/dockapp/da_run.c @@ -0,0 +1,266 @@ +/* + wmget - A background download manager as a Window Maker dock app + Copyright (c) 2001-2003 Aaron Trickey + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + ******************************************************************** + dockapp/da_run.c - Main dockapp loop; fd polling; periodic callback + + This file implements dockapp_run(), which provides the main loop of + the dockapp application. In addition, it implements + dockapp_add_pollfd(), which adds polling of arbitrary file + descriptors to the main loop, and dockapp_set_periodic_callback(), + which sets up a periodic app callback. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "dockapp.h" + + +/* we keep two parallel structures... one, the list of pollfd's for + * poll(2)'s benefit, and the other, the collection of callback + * information + */ +typedef struct { + dockapp_rv_t (*cb) (void *cbdata, short revents); + void *cbdata; + short revents; +} da_pollcb; + + +enum { + da_max_pollfds = 50 +}; + +static struct pollfd da_pollfds[da_max_pollfds]; +static da_pollcb da_pollcbs[da_max_pollfds]; +static int da_num_pollfds = 0; + + +dockapp_rv_t dockapp_add_pollfd ( + int fd, + short pollevents, + dockapp_rv_t (*cb) (void *, short pollstatus), + void *cbdata) +{ + struct pollfd *pfd; + da_pollcb *pcb; + + assert (da_num_pollfds < da_max_pollfds); + + /* + fprintf (stderr, "adding pollfd #%d, fd = %d, events = %hu\n", + da_num_pollfds, fd, pollevents); + */ + + pfd = da_pollfds + da_num_pollfds; + pcb = da_pollcbs + da_num_pollfds; + + pfd->fd = fd; + pfd->events = pollevents; + pcb->cb = cb; + pcb->cbdata = cbdata; + + ++da_num_pollfds; + + return dockapp_ok; +} + + +dockapp_rv_t dockapp_remove_pollfd ( + int fd) +{ + int i = da_num_pollfds; + struct pollfd *pfd = da_pollfds; + da_pollcb *pcb = da_pollcbs; + + for ( ; i; --i, ++pfd, ++pcb) { + if (pfd->fd == fd) { + memmove (pfd, pfd + 1, (i - 1) * sizeof (struct pollfd)); + memmove (pcb, pcb + 1, (i - 1) * sizeof (da_pollcb)); + return dockapp_ok; + } + } + + /* not found... */ + return dockapp_invalid_arg; +} + + +/* periodic callback info... if da_timer_cb is null, then no callback is + * set. + */ +static dockapp_rv_t (*da_timer_cb) (void *) = 0; +static void *da_timer_cbdata; +static struct timeval da_timer_next_timeout; +static long da_timer_interval_msec; + + +static void da_reset_timer (void) +{ + int rv; + + rv = gettimeofday (&da_timer_next_timeout, 0); + assert (rv == 0); + + /* + fprintf (stderr, + "da_reset_timer: RIGHT NOW is %ld.%ld, msec = %ld\n", + da_timer_next_timeout.tv_sec, + da_timer_next_timeout.tv_usec, + da_timer_interval_msec); + */ + + da_timer_next_timeout.tv_usec + += (da_timer_interval_msec % 1000L) * 1000L; + + da_timer_next_timeout.tv_sec + += da_timer_interval_msec / 1000L + + da_timer_next_timeout.tv_usec / 1000000L; + + da_timer_next_timeout.tv_usec %= 1000000L; + + /* + fprintf (stderr, + "da_reset_timer: NEXT TIMEOUT is %ld.%ld\n", + da_timer_next_timeout.tv_sec, + da_timer_next_timeout.tv_usec); + */ +} + + +static long da_timer_msec_remaining (void) +{ + struct timeval right_now; + int rv; + + rv = gettimeofday (&right_now, 0); + + return + (da_timer_next_timeout.tv_sec - right_now.tv_sec) * 1000L + + (da_timer_next_timeout.tv_usec - right_now.tv_usec) / 1000L; +} + + +void dockapp_set_periodic_callback ( + long msec, + dockapp_rv_t (*cb) (void *), + void *cbdata) +{ + da_timer_cb = cb; + da_timer_cbdata = cbdata; + + da_timer_interval_msec = msec; + + da_reset_timer (); +} + + +void dockapp_remove_periodic_callback (void) +{ + da_timer_cb = 0; + da_timer_cbdata = 0; + da_timer_interval_msec = 0; +} + + +/* this is the main loop for the dockapp... + */ +dockapp_rv_t dockapp_run (void) +{ + for ( ; ; ) { + int rv; + int poll_timeout = -1; /* infinite unless periodic callback */ + + if (da_timer_cb) { + if (da_timer_msec_remaining () <= 0) { + rv = da_timer_cb (da_timer_cbdata); + da_reset_timer (); + } + + poll_timeout = da_timer_msec_remaining (); + } + + /* + fprintf (stderr, "about to poll(%d fd, %d timeout)\n", + da_num_pollfds, poll_timeout); + */ + + rv = poll (da_pollfds, da_num_pollfds, poll_timeout); + + /* + fprintf (stderr, "poll returned %d\n", rv); + */ + + if (rv == 0) { + /* poll timed out; let's loop back up and let the logic + * prior to the poll() invoke the user-defined periodic + * callback + */ + continue; + } else if (rv < 0) { + /* Disregard EINTR; just means that a signal was caught. + * We'll retry later. + */ + if (errno != EINTR) + perror ("poll()"); + + } else { + /* poll returned with some nonzero number of events... + * collect the callback structures first, then invoke the + * callbacks, since the callback functions are allowed to + * create and destroy callbacks as well + */ + int i; + da_pollcb callbacks[da_max_pollfds]; + da_pollcb *c; + int ncallbacks = 0; + + for (i = 0; i < da_num_pollfds && ncallbacks < rv; ++i) { + if (da_pollfds[i].revents) { + callbacks[ncallbacks] = da_pollcbs[i]; + callbacks[ncallbacks].revents + = da_pollfds[i].revents; + ++ncallbacks; + } + } + + for (c = callbacks; ncallbacks; --ncallbacks, ++c) { + if (c->cb (c->cbdata, c->revents) == dockapp_exit) { + fprintf (stderr, "exiting dockapp_run()\n"); + return dockapp_ok; + } + } + } + } + +} + + + + diff --git a/wmget/dockapp/da_x.c b/wmget/dockapp/da_x.c new file mode 100644 index 0000000..7102e89 --- /dev/null +++ b/wmget/dockapp/da_x.c @@ -0,0 +1,481 @@ +/* + wmget - A background download manager as a Window Maker dock app + Copyright (c) 2001-2003 Aaron Trickey + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + ******************************************************************** + dockapp/da_x.c - General X11 code + + This file encapsulates all the X11 interface code, such as drawing, + initialization, and selection handling. +*/ + +#include +#include +#include +#include +#include +#include + +#include "dockapp.h" + +#define DOCKAPP_EXPOSE_INTERNALS +#include "da_mouse.h" + +#define SELECTION_MAX 1024 + +static Display *display; + +/* We need to update both the ``icon'' and ``main'' windows + * simultaneously... icon for WindowMaker and main for AfterStep... + */ +static Window icon_win; +static Window main_win; + +static Pixmap icon_pixmap; + +static GC copy_gc; +static GC xor_gc; + + +/* request the X selection; when the server responds, we will get it in + * da_xfd_callback. + */ +static void da_request_selection () +{ + Atom prop; + + prop = XInternAtom (display, "XSEL_DATA", False); + XConvertSelection ( + display, + XA_PRIMARY, /* only use the PRIMARY selection */ + XA_STRING, /* we only accept strings (the 90% case) */ + prop, + icon_win, + CurrentTime); +} + +static dockapp_rv_t (*da_selection_cb) (void *, const char *); +static void *da_selection_cbdata; + +static dockapp_rv_t da_receive_selection (XEvent *event) +{ + Atom actual_type; + int actual_format; + unsigned long actual_length; + unsigned long remaining; + unsigned char *data; + dockapp_rv_t rv; + + if (event->xselection.selection != XA_PRIMARY) + return dockapp_ok; + + if (event->xselection.property == None) { + fprintf (stderr, "! selection can't become string !\n"); + return dockapp_ok; + } + + XGetWindowProperty ( + event->xselection.display, + event->xselection.requestor, + event->xselection.property, + 0, /* from byte zero... */ + SELECTION_MAX, /* to this many bytes */ + False, /* do not delete after retrieval */ + AnyPropertyType, + &actual_type, /* actual type returned */ + &actual_format, /* actual format returned */ + &actual_length, /* in 8, 16, or 32 bit units */ + &remaining, /* unread content (always in 8 bit units!) */ + &data); /* property content */ + + /* We do not support non-string data or incremental transfer. */ + if (actual_type != XA_STRING || actual_format != 8) { + fprintf (stderr, "! selection unavailable or not of known type !\n"); + + if (actual_length) { + XFree (data); + } + + return dockapp_ok; + } + + + rv = da_selection_cb (da_selection_cbdata, (const char *)data); + + XFree (data); + + return rv; +} + + +dockapp_rv_t dockapp_request_selection_string ( + dockapp_rv_t (*cb) (void *, const char *), + void *cbdata) +{ + da_selection_cb = cb; + da_selection_cbdata = cbdata; + da_request_selection (); + + return dockapp_ok; +} + + +static void da_redraw_icon (void) +{ + XEvent event; + + /* slurp up any queued Expose events */ + while (XCheckTypedWindowEvent ( + display, + event.xany.window, + Expose, + &event)) + ; + + XCopyArea ( + display, + icon_pixmap, + icon_win, + copy_gc, + 0, 0, 64, 64, /* source coords */ + 0, 0); /* dest coords */ + + XCopyArea ( + display, + icon_pixmap, + main_win, + copy_gc, + 0, 0, 64, 64, /* source coords */ + 0, 0); /* dest coords */ +} + + +/* this is the callback bound to the X server socket descriptor + */ +static dockapp_rv_t da_xfd_callback ( + void *unused_cbdata, + short unused_revents) +{ + (void)unused_cbdata; + (void)unused_revents; + + XSync (display, False); + + while (XPending (display)) { + + XEvent event; + + XNextEvent (display, &event); + + switch (event.type) { + case ButtonPress: + da_mouse_button_down ( + event.xbutton.x, + event.xbutton.y, + event.xbutton.state); + break; + + case ButtonRelease: + /* da_mouse_button_up() returns the callback rv of the + * callback which is invoked, if any + */ + return da_mouse_button_up ( + event.xbutton.x, + event.xbutton.y, + event.xbutton.state); + break; + + case Expose: + da_redraw_icon (); + break; + + case DestroyNotify: + XCloseDisplay (display); + return dockapp_exit; + + case SelectionNotify: + return da_receive_selection (&event); + } + } + + return dockapp_ok; +} + + +int da_x_error_handler (Display *display, XErrorEvent *xerr) +{ + char msgbuf[1024]; + + XGetErrorText (display, xerr->error_code, msgbuf, sizeof msgbuf); + + fprintf (stderr, "X11 error: %s\n", msgbuf); + + return 0; +} + + +/* the following code is stolen largely from wmgeneral.c... + */ +static Pixmap da_create_shaping_bitmap (char **xpm) +{ + int i,j,k; + int width, height, numcol, depth; + unsigned long zero=0; + unsigned char bwrite; + int bcount; + unsigned long curpixel; + + char xbm_data[64*64]; + char *xbm = xbm_data; + + sscanf(*xpm, "%d %d %d %d", &width, &height, &numcol, &depth); + + for (k=0; k!=depth; k++) + { + zero <<=8; + zero |= xpm[1][k]; + } + + for (i=numcol+1; i < numcol+64+1; i++) { + bcount = 0; + bwrite = 0; + for (j=0; j<64*depth; j+=depth) { + bwrite >>= 1; + + curpixel=0; + for (k=0; k!=depth; k++) + { + curpixel <<=8; + curpixel |= xpm[i][j+k]; + } + + if ( curpixel != zero ) { + bwrite += 128; + } + bcount++; + if (bcount == 8) { + *xbm = bwrite; + xbm++; + bcount = 0; + bwrite = 0; + } + } + } + + return XCreateBitmapFromData ( + display, + icon_win, + xbm_data, + 64, 64); +} + + +dockapp_rv_t dockapp_init_gui ( + char *appname, + char *argv[], + char **xpmdata) +{ + int screen; + Window root; + XpmAttributes xpm_att; + Pixmap xpm_mask; + unsigned long black, white; + + (void)argv; /* not currently parsed */ + + /* initialize x error handler */ + XSetErrorHandler (da_x_error_handler); + + /* get some stuff out */ + display = XOpenDisplay (0); + + screen = DefaultScreen (display); + + root = RootWindow (display, screen); + + black = BlackPixel (display, screen); + white = WhitePixel (display, screen); + + /* construct the pixmap */ + xpm_att.valuemask = XpmReturnPixels | XpmReturnExtensions; + + XpmCreatePixmapFromData ( + display, + root, + xpmdata, + &icon_pixmap, + &xpm_mask, + &xpm_att); + + /* construct the windows */ + main_win = XCreateSimpleWindow ( + display, + root, /* parent */ + 0, 0, 64, 64, /* pos & size */ + 1, /* border width */ + black, /* foreground */ + white); /* background */ + + icon_win = XCreateSimpleWindow ( + display, + main_win, /* parent */ + 0, 0, 64, 64, /* pos & size */ + 1, /* border width */ + black, /* foreground */ + white); /* background */ + + /* request all interesting X events */ + XSelectInput (display, main_win, + ButtonPressMask | ExposureMask | ButtonReleaseMask | + PointerMotionMask | StructureNotifyMask); + + XSelectInput (display, icon_win, + ButtonPressMask | ExposureMask | ButtonReleaseMask | + PointerMotionMask | StructureNotifyMask); + + /* apply the shaping bitmap */ + XShapeCombineMask ( + display, + icon_win, + ShapeBounding, + 0, 0, + da_create_shaping_bitmap (xpmdata), + ShapeSet); + + XShapeCombineMask ( + display, + main_win, + ShapeBounding, + 0, 0, + da_create_shaping_bitmap (xpmdata), + ShapeSet); + + + /* set wm hints and title */ + { + XClassHint classhint; + XWMHints wmhints; + + classhint.res_name = appname; + classhint.res_class = appname; + XSetClassHint (display, main_win, &classhint); + + wmhints.initial_state = WithdrawnState; + wmhints.icon_window = icon_win; + wmhints.window_group = main_win; + wmhints.flags = StateHint | IconWindowHint | WindowGroupHint; + XSetWMHints (display, main_win, &wmhints); + + XStoreName (display, main_win, appname); + } + + /* now we need two gc's to do drawing & copying... one which paints + * by overwriting pixels, and one which paints by xor'ing pixels + */ + { + XGCValues gcv; + + gcv.graphics_exposures = 0; + gcv.function = GXcopy; + + copy_gc = XCreateGC ( + display, + root, + GCGraphicsExposures | GCFunction, + &gcv); + + gcv.function = GXxor; + + xor_gc = XCreateGC ( + display, + root, + GCGraphicsExposures | GCFunction, + &gcv); + } + + /* finally, show the window */ + XMapWindow (display, main_win); + + /* invoke da_xfd_callback once manually to process all events which + * were generated by the above work... after this, it will be called + * by the framework whenever there is activity on the server socket + */ + da_xfd_callback (0, 0); + + /* now that we're up, add the X file descriptor to the main poll + * loop and bind it to our event handler + */ + return dockapp_add_pollfd ( + XConnectionNumber (display), + POLLIN | POLLPRI, + da_xfd_callback, + 0); +} + + +void dockapp_copy_pixmap ( + int source_x, int source_y, + int target_x, int target_y, + int w, int h) +{ + XCopyArea ( + display, + icon_pixmap, + icon_pixmap, + copy_gc, + source_x, source_y, + w, h, + target_x, target_y); + + da_redraw_icon (); +} + + +void dockapp_overlay_pixmap ( + int source_x, int source_y, + int target_x, int target_y, + int w, int h) +{ + XCopyArea ( + display, + icon_pixmap, + icon_pixmap, + xor_gc, + source_x, source_y, + w, h, + target_x, target_y); + + da_redraw_icon (); +} + + + + + + + + + + + + + diff --git a/wmget/dockapp/dockapp.h b/wmget/dockapp/dockapp.h new file mode 100644 index 0000000..deec5cd --- /dev/null +++ b/wmget/dockapp/dockapp.h @@ -0,0 +1,158 @@ +#ifndef I_DOCKAPP_H +#define I_DOCKAPP_H +/* + wmget - A background download manager as a Window Maker dock app + Copyright (c) 2001-2003 Aaron Trickey + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + ******************************************************************** + dockapp/dockapp.h - Public interface to the dockapp library + + The dockapp library, distributed as part of the wmget program, + provides a fairly simple way to write dockapps. It abstracts away + the actual drawing, input, and main-loop logic. +*/ + +#include /* to get Button?Mask, etc. */ +#include /* to get POLLIN, POLLOUT, etc. */ + + +/********************************************************************** + * BASIC TYPES + */ + +/* RV type. When writing a callback, you should generally return ok or + * exit, to ask the dockapp to proceed or quit. + */ +typedef enum { + dockapp_ok = 0, + dockapp_exit, + dockapp_invalid_arg, + dockapp_rv_too_many_clickregions, +} dockapp_rv_t; + + +/********************************************************************** + * STARTUP/RUNTIME + */ + +/* initializes the dockapp's X display + */ +dockapp_rv_t dockapp_init_gui ( + char *appname, /* desired dockapp name */ + char *argv[], /* X/dockapp args */ + char **xpmdata); /* XPM data */ + + +/* the main loop... when this returns, your program should exit + */ +dockapp_rv_t dockapp_run (void); + + +/* this asks dockapp_run() to periodically call an arbitrary function + * (you can only have one of these installed at a time). All sorts of + * other things can pre-empt a periodic callback, so consider the 'msec' + * parameter to be an interval floor. + */ +void dockapp_set_periodic_callback ( + long msec, /* approx. interval */ + dockapp_rv_t (*cb) (void *), + void *cbdata); + +void dockapp_remove_periodic_callback (void); + + +/* you can ask dockapp_run() to add an fd to an internal efficient + * poll-list... see poll(2) for full documentation; the semantics here + * are that you give fd and pollevents and we create a struct pollfd; + * when we invoke the callback, the pollstatus arg will contain the + * revents field from the struct pollfd + */ +dockapp_rv_t dockapp_add_pollfd ( + int fd, /* the fd to poll */ + short pollevents, /* see poll(2), e.g. POLLIN */ + dockapp_rv_t (*cb) (void *, short pollstatus), + void *cbdata); + +dockapp_rv_t dockapp_remove_pollfd ( + int fd); /* the fd to remove */ + + + +/********************************************************************** + * CLICKREGIONS + */ + +/* a clickregion is simply an area where the user can click to trigger a + * callback + * (Helpful list of buttonmask components: ShiftMask, LockMask, + * ControlMask, Mod1Mask .. Mod5Mask, Button1Mask .. Button5Mask) + */ +dockapp_rv_t dockapp_add_clickregion ( + int x, int y, int w, int h, + int buttonmask, + dockapp_rv_t (*cb) (void *, int x, int y), + void *cbdata); + + + + +/********************************************************************** + * PIXMAP COPY/OVERLAY OPERATIONS + * Most dockapp drawing will consist of simple pixel moving from one + * part of the pixmap to another. + */ + +/* copy pixels, overwriting the target + */ +void dockapp_copy_pixmap ( + int source_x, int source_y, /* copy from where */ + int target_x, int target_y, /* copy to where */ + int w, int h); /* copy how much */ + +/* use an XOR raster-op to overlay the source on the target; call this + * again with the same arguments to undo the first overlay + */ +void dockapp_overlay_pixmap ( + int source_x, int source_y, /* copy from where */ + int target_x, int target_y, /* copy to where */ + int w, int h); /* copy how much */ + + + + +/********************************************************************** + * X SELECTION + */ + +/* request the current X selection... only non-incremental strings + * supported... if there is no selection, callback gets called with + * (const char *)0; otherwise, it gets called with the string (which + * will still be owned by the X server, so you need to copy it if you + * want to keep it) + */ +dockapp_rv_t dockapp_request_selection_string ( + dockapp_rv_t (*cb) (void *, const char *), + void *cbdata); + + +#endif /* I_DOCKAPP_H */ + diff --git a/wmget/iq.c b/wmget/iq.c new file mode 100644 index 0000000..985640e --- /dev/null +++ b/wmget/iq.c @@ -0,0 +1,233 @@ +/* + wmget - A background download manager as a Window Maker dock app + Copyright (c) 2001-2003 Aaron Trickey + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + ******************************************************************** + iq.c - Core functions implementing the server's Unix-domain socket + + To submit jobs to the server's input queue, you connect to a Unix- + domain socket it opens. These functions are responsible for + setting up, connecting to, and accepting connections from that + socket, and are used by server.c and request.c. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wmget.h" + + +/* iqsun == the Unix-domain socket address, aka the pathname, of our + * socket. iqsun_len == the number of bytes in iqsun incl. the header + * and the pathname string, excluding the unused bytes after the + * string's terminator. + */ +static struct sockaddr_un iqsun; +static int iqsun_len = 0; /* 0 => not yet initialized */ +const char *iqname = ".wmget.iq"; + +int init_paths (void) +{ + return 0; +} + +static int iq_init_address (void) +{ + const char *homedir = home_directory (); + + if (iqsun_len) /* already initialized */ + return 0; + + /* Our pathname length is constrained by sizeof(sockaddr_un), and + * must be able to fit the homedir, the /, the iqname, and the \0. + */ + if (strlen (homedir) > sizeof iqsun.sun_path - 2 - sizeof iqname) { + error ("Home directory path is too long, can't construct " + "socket name"); + return 1; + } + + sprintf (iqsun.sun_path, "%s/%s", homedir, iqname); + + debug ("IQ = '%s'", iqsun.sun_path); + + iqsun.sun_family = AF_UNIX; + + iqsun_len = sizeof iqsun - sizeof iqsun.sun_path + + strlen (iqsun.sun_path) + 1; + + return 0; +} + + +FILE *iq_client_connect (void) +{ + int fd; + FILE *fp; + + if (iq_init_address ()) + return 0; + + if ((fd = socket (AF_UNIX, SOCK_STREAM, 0)) < 0) { + error_sys ("Could not create Unix-domain socket to talk to server"); + return 0; + } + + if (connect (fd, (struct sockaddr *)&iqsun, iqsun_len) < 0) { + error_sys ("Could not connect to the server"); + close (fd); + return 0; + } + + if (!(fp = fdopen (fd, "r+"))) { + /* this should never fail for any reparable reason */ + error_sys ("fdopen"); + close (fd); + return 0; + } + + return fp; +} + +/* Server listener socket */ +static int iq_listen_fd; + + +/** was going to use this in atexit(), but the problem is that we fork, + * and those children inherit the atexits, and well ... +static void iq_server_cleanup (void) +{ + close (iq_listen_fd); + unlink (iqsun.sun_path); +} +*/ + + +int iq_server_init (void) +{ + int fd, test_fd; + + if (iq_init_address ()) + return 1; + + + if ((fd = socket (AF_UNIX, SOCK_STREAM, 0)) < 0) { + error_sys ("Could not create Unix-domain socket to receive requests"); + return 1; + } + + /* We're going to listen on this socket and don't want accept() to + * block + */ + if (fcntl (fd, F_SETFL, (long)O_NONBLOCK) < 0) { + error_sys ("fcntl"); + close (fd); + return 1; + } + + /* Before proceeding, try to *connect* to the iq. If this succeeds, + * then uh-oh, there's another wmget running out there. + */ + test_fd = connect (fd, (struct sockaddr *)&iqsun, iqsun_len); + if (test_fd >= 0) { + close (test_fd); + error ( + "There's another wmget dock running. You can only run " + "one at a time."); + return 1; + } + + /* Good. Just in case the pathname exists, unlink it. */ + unlink (iqsun.sun_path); + + if (bind (fd, (struct sockaddr *)&iqsun, iqsun_len) < 0) { + error_sys ("bind"); + close (fd); + return 1; + } + + /* Tighten up the new filename's permissions. */ + if (chmod (iqsun.sun_path, S_IRWXU) < 0) { + error_sys ("chmod"); + close (fd); + return 1; + } + + if (listen (fd, 5) < 0) { + error_sys ("listen"); + close (fd); + return 1; + } + + iq_listen_fd = fd; + + return 0; +} + + +FILE *iq_server_accept (void) +{ + int fd; + FILE *fp; + struct sockaddr_un sun; + int sun_len = sizeof sun; + + if ((fd = accept (iq_listen_fd, (struct sockaddr *)&sun, + &sun_len)) < 0) { + if (errno == EAGAIN) { + /* Simply no connections waiting. */ + return 0; + } + + error_sys ("accept"); + return 0; + } + + debug ("got a connection..."); + + if (!(fp = fdopen (fd, "r+"))) { + error_sys ("fdopen"); + close (fd); + return 0; + } + + return fp; +} + + +int iq_get_listen_fd (void) +{ + return iq_listen_fd; +} + + + + diff --git a/wmget/list.c b/wmget/list.c new file mode 100644 index 0000000..bfa4dce --- /dev/null +++ b/wmget/list.c @@ -0,0 +1,89 @@ +/* + wmget - A background download manager as a Window Maker dock app + Copyright (c) 2001-2003 Aaron Trickey + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + ******************************************************************** + list.c - Client code for retrieving the current job list + + When invoked with the ``list'' argument, main() calls list(), + defined below. This code sends a LIST command to the server and + dumps the returned list to stdout. +*/ + +#include +#include +#include + +#include "wmget.h" + +int list (int argc, char **argv) +{ + char line[MAXCMDLEN + 1]; + char *word_break; + FILE *fp; + + /* No additional options or arguments. + */ + if (argc > 2) { + error ("Extra arguments: list takes none"); + return 1; + } + + (void)argv; + + if (!(fp = iq_client_connect ())) + return 1; + + if (fprintf (fp, "LIST\r\n") == EOF) { + error_sys ("Could not submit command to server"); + fclose (fp); + return 1; + } + + if (!fgets (line, sizeof line - 1, fp)) { + error ("Server did not respond to command!"); + fclose (fp); + return 1; + } + + /* Extract the first word and compare. */ + word_break = line + strcspn (line, " \t\r\n"); + + if (*word_break) + *word_break++ = 0; + + if (strcasecmp (line, RESPONSE_LIST_COMING)) { + error ("Server responded with error: %s", word_break); + fclose (fp); + return 1; + } + + while (fgets (line, sizeof line - 1, fp)) { + fputs (line, stdout); + } + + fclose (fp); + + return 0; +} + + diff --git a/wmget/messages.c b/wmget/messages.c new file mode 100644 index 0000000..1bfbfd1 --- /dev/null +++ b/wmget/messages.c @@ -0,0 +1,106 @@ +/* + wmget - A background download manager as a Window Maker dock app + Copyright (c) 2001-2003 Aaron Trickey + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + ******************************************************************** + messages.c - functions for writing error, status, & debug messages +*/ + +#include +#include +#include + +#include "wmget.h" + +static OutputLevel output_level_; + + +void set_output_level (OutputLevel lev) +{ + output_level_ = lev; +} + + +OutputLevel output_level (void) +{ + return output_level_; +} + + +void error (const char *fmt, ...) +{ + va_list ap; + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); + fputs ("\n", stderr); +} + +void error_sys (const char *fmt, ...) +{ + va_list ap; + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); + perror (" "); +} + +void info (const char *fmt, ...) +{ + va_list ap; + + if (output_level_ < OL_NORMAL) + return; + + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); + fputs ("\n", stderr); +} + +void debug (const char *fmt, ...) +{ + va_list ap; + + if (output_level_ < OL_DEBUG) + return; + + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); + fputs ("\n", stderr); +} + +void debug_sys (const char *fmt, ...) +{ + va_list ap; + + if (output_level_ < OL_DEBUG) + return; + + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); + perror (" "); +} + + diff --git a/wmget/request.c b/wmget/request.c new file mode 100644 index 0000000..dc79332 --- /dev/null +++ b/wmget/request.c @@ -0,0 +1,216 @@ +/* + wmget - A background download manager as a Window Maker dock app + Copyright (c) 2001-2003 Aaron Trickey + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + ******************************************************************** + request.c - Client code for submitting download requests + + Invoked whenever wmget is run in "request" mode; submits a job to + the server dockapp. +*/ + +#include +#include +#include +#include +#include + +#include "wmget.h" + + +static char *enquote_strdup (const char *string) +{ + int len = strlen (string); + char *newstr; + const char *src; + char *dest; + + for (src = string; *src; src += strcspn (src, ")\\")) { + ++len; + } + + dest = newstr = malloc (len + 1); + + for (src = string; *src; ++src) { + switch (*src) { + case ')': + case '\\': + *dest++ = '\\'; + } + + *dest++ = *src; + } + + *dest = '\0'; + + return newstr; +} + + +int add_arg_s (char *line, const char *argname, const char *argval) +{ + char *qargval; + + if (strlen (line) + strlen (argname) + strlen (argval) + 3 + > MAXCMDLEN) { + error ("Too much data to send to server!"); + return 1; + } + + qargval = enquote_strdup (argval); + + line += strlen (line); + sprintf (line, " %s(%s)", argname, qargval); + + free (qargval); + + return 0; +} + + +int add_arg_i (char *line, const char *argname, int argval) +{ + if (strlen (line) + strlen (argname) + 10 + > MAXCMDLEN) { + error ("Too much data to send to server!"); + return 1; + } + + line += strlen (line); + sprintf (line, " %s(%d)", argname, argval); + + return 0; +} + + +int request (int argc, char **argv) +{ + char line[MAXCMDLEN + 1]; + char *word_break; + FILE *fp; + Request req; + + config_request (argc, argv, &req); + + if (!req.source_url) { + error ("Missing source URL!"); + usage (); + return 1; + } + + strcpy (line, CMD_GET); + if (add_arg_s (line, ARG_GET_SOURCE_URL, req.source_url)) + return 1; + + if (req.display) + if (add_arg_s (line, ARG_GET_DISPLAY, req.display)) + return 1; + + if (req.save_to) + if (add_arg_s (line, ARG_GET_SAVE_TO, req.save_to)) + return 1; + + if (req.overwrite != -1) + if (add_arg_i (line, ARG_GET_OVERWRITE, req.overwrite)) + return 1; + + if (req.continue_from != -1) + if (add_arg_i (line, ARG_GET_CONTINUE_FROM, req.continue_from)) + return 1; + + if (req.proxy) + if (add_arg_s (line, ARG_GET_PROXY, req.proxy)) + return 1; + + if (req.follow != -1) + if (add_arg_i (line, ARG_GET_FOLLOW, req.follow)) + return 1; + + if (req.user_agent) + if (add_arg_s (line, ARG_GET_UA, req.user_agent)) + return 1; + + if (req.use_ascii != -1) + if (add_arg_i (line, ARG_GET_USE_ASCII, req.use_ascii)) + return 1; + + if (req.referer) + if (add_arg_s (line, ARG_GET_REFERER, req.referer)) + return 1; + + if (req.include != -1) + if (add_arg_i (line, ARG_GET_INCLUDE, req.include)) + return 1; + + if (req.interface) + if (add_arg_s (line, ARG_GET_INTERFACE, req.interface)) + return 1; + + if (req.proxy_auth) + if (add_arg_s (line, ARG_GET_PROXY_AUTH, req.proxy_auth)) + return 1; + + if (req.auth) + if (add_arg_s (line, ARG_GET_AUTH, req.auth)) + return 1; + + debug ("Command line is '%s'", line); + + strcat (line, "\n"); + + if (!(fp = iq_client_connect ())) + return 1; + + if (fputs (line, fp) == EOF) { + error_sys ("Failed to send command to server (fputs)"); + fclose (fp); + return 1; + } + + if (!fgets (line, sizeof line - 1, fp)) { + error ("Server did not respond to command!"); + fclose (fp); + return 1; + } + + /* Extract the first word and compare. */ + word_break = line + strcspn (line, " \t\r\n"); + + while (*word_break && isspace (*word_break)) + *word_break++ = 0; + + if (strcasecmp (line, RESPONSE_JOB_ACCEPTED)) { + error ("%s", word_break); + fclose (fp); + return 1; + } + + fclose (fp); + + /* Since we got a RESPONSE_JOB_ACCEPTED, present the user with the + * job ID for future cancellation. + */ + printf ("Job ID is %s\n", word_break); + + return 0; +} + diff --git a/wmget/retrieve.c b/wmget/retrieve.c new file mode 100644 index 0000000..4ba2908 --- /dev/null +++ b/wmget/retrieve.c @@ -0,0 +1,225 @@ +/* + wmget - A background download manager as a Window Maker dock app + Copyright (c) 2001-2003 Aaron Trickey + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + ******************************************************************** + retrieve.c - Child process code for performing downloads + + When the server [server.c] accepts a download job, it fork()s a + new process which in turn invokes this file's retrieve(). This + code takes care of setting up and invoking libcurl to grab the + data. The shared memory segment (shmem, wmget.h) is inherited from + the server and contains the Job structure through which the child + process communicates with the server. +*/ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "wmget.h" + +#if LIBCURL_VERSION_NUM >= 0x070905 +# define PROGRESS double +#else +# define PROGRESS size_t +#endif + +static int progress_callback ( + void *data, + PROGRESS total, + PROGRESS progress, + PROGRESS unused1, + PROGRESS unused2) +{ + Job *job = data; + + (void)unused1; + (void)unused2; + + if (job->stop_request) { + /* Abort transfer. */ + job->status = J_STOPPING; + return 1; + } + + if (!total) { + debug ("Total bytes unknown!"); + total = progress * 2 + 1; /* just to make the bar halfway */ + } + job->progress = (unsigned long)progress; + job->prog_max = (unsigned long)total; + + debug ("progress_callback (%d/%d)", job->progress, job->prog_max); + + return 0; +} + + +void write_error_file (Job *job, const char *msg) +{ + char error_file_name[MAXPATHLEN + 1]; + FILE *error_file; + + strcpy (error_file_name, job->options.save_to); + strncat (error_file_name, ".ERROR", + MAXPATHLEN - strlen (error_file_name)); + + if (!(error_file = fopen (error_file_name, "w"))) + return; + + fprintf (error_file, "Download failed:\n"); + fprintf (error_file, " From URL: %s\n", job->source_url); + fprintf (error_file, " To file: %s\n", job->options.save_to); + fprintf (error_file, " Error: %s\n", msg); + fprintf (error_file, " (" WMGET_VERSION_BANNER ")\n"); +} + + +int retrieve (Job *job) +{ + CURL *curl; + CURLcode rc; + FILE *outfp; + JobOptions *opts; + + debug ("Retrieval process %d running job:", getpid()); + debug_dump_job (job); + + if (job->options.continue_from) { + outfp = fopen (job->options.save_to, "a"); + } else { + outfp = fopen (job->options.save_to, "w"); + } + + if (!outfp) { + error_sys ("could not open `%s' for output", + job->options.save_to); + return 1; + } + + curl = curl_easy_init (); + if (!curl) { + error ("could not initialize libcurl"); + write_error_file (job, "could not initialize libcurl"); + fclose (outfp); + return 1; + } + + curl_easy_setopt (curl, CURLOPT_FILE, outfp); + curl_easy_setopt (curl, CURLOPT_URL, job->source_url); + curl_easy_setopt ( + curl, CURLOPT_PROGRESSFUNCTION, progress_callback); + curl_easy_setopt (curl, CURLOPT_PROGRESSDATA, (void *)job); + curl_easy_setopt (curl, CURLOPT_ERRORBUFFER, job->error); + curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 0); + + if (job->options.continue_from) { + curl_easy_setopt (curl, CURLOPT_RESUME_FROM, + (long)job->options.continue_from); + } + + /* Now load the job's user-configurable parameters: + */ + opts = &job->options; + + if (opts->proxy[0]) { + curl_easy_setopt (curl, CURLOPT_PROXY, opts->proxy); + } + + if (opts->follow) { + curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt (curl, CURLOPT_MAXREDIRS, opts->follow); + } else { + curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, 0); + } + + if (opts->user_agent[0]) { + curl_easy_setopt (curl, CURLOPT_USERAGENT, opts->user_agent); + } + + if (opts->use_ascii) { + curl_easy_setopt (curl, CURLOPT_TRANSFERTEXT, 1); + } + + if (opts->referer[0]) { + curl_easy_setopt (curl, CURLOPT_REFERER, opts->referer); + } + + if (opts->include) { + curl_easy_setopt (curl, CURLOPT_HEADER, 1); + } + + if (opts->interface[0]) { + curl_easy_setopt (curl, CURLOPT_INTERFACE, opts->interface); + } + + if (opts->proxy_auth[0]) { + curl_easy_setopt (curl, CURLOPT_PROXYUSERPWD, opts->proxy_auth); + } + + if (opts->auth[0]) { + curl_easy_setopt (curl, CURLOPT_USERPWD, opts->auth); + } + + + /* If wmget is verbose, set libcurl to verbose too... + */ + if (output_level () > OL_NORMAL) + curl_easy_setopt (curl, CURLOPT_VERBOSE, 1); + + + /* Finally, perform the download: + */ + job->status = J_RUNNING; + rc = curl_easy_perform (curl); + + if (rc) { + if (job->status == J_STOPPING) { + info ("aborted by user"); + job->status = J_COMPLETE; + + } else { + error (job->error); + write_error_file (job, job->error); + job->status = J_COMPLETE; + } + } else { + job->status = J_COMPLETE; + } + + curl_easy_cleanup (curl); + fclose (outfp); + + if (rc) + return 1; + + return 0; +} + + diff --git a/wmget/server.c b/wmget/server.c new file mode 100644 index 0000000..58caf05 --- /dev/null +++ b/wmget/server.c @@ -0,0 +1,1096 @@ +/* + wmget - A background download manager as a Window Maker dock app + Copyright (c) 2001-2003 Aaron Trickey + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + ******************************************************************** + server.c - Manage the dock app display, accept & spawn jobs + + When ``wmget --dock'' is invoked, main() calls server(), defined + below. This initializes the dock app window, the shared memory + segment, and the job input queue, and then enters the main loop + whereby it accepts X events (such as redraws or clicks), accepts + job requests (from request(), in request.c), and monitors shared + memory, updating the display as necessary. It forks off children to + handle accepted jobs; see retrieve.c. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include +#include +#include + +#include "wmget.h" +#include "wmget.xpm" +#include "dockapp/dockapp.h" + + +static ServerConfig config; + +/*********************************************************************** + * Text Drawing + * The various CHAR_* consts locate and dimension the chars on the xpm. + * Call init_font() to set up the CHAR_X and CHAR_Y tables, then + * draw_string() to put text on the xpm. + */ +static const int CHAR_WIDTH = 6; +static const int CHAR_HEIGHT = 7; + +static const int CHAR_UCALPHA_X = 1; +static const int CHAR_UCALPHA_Y = 85; +static const int CHAR_LCALPHA_X = 1; +static const int CHAR_LCALPHA_Y = 95; +static const int CHAR_SYMNUM_X = 1; +static const int CHAR_SYMNUM_Y = 105; + +static int CHAR_X[128]; +static int CHAR_Y[128]; + +static void init_font (void) +{ + int i; + int *cx, *cy; + + for (i = 0, cx = CHAR_X, cy = CHAR_Y; i < 128; ++i, ++cx, ++cy) { + if (i > 'z') { + *cx = CHAR_SYMNUM_X; /* 1st SYMNUM is the space */ + *cy = CHAR_SYMNUM_Y; + } else if (i >= 'a') { + *cx = CHAR_LCALPHA_X + CHAR_WIDTH * (i - 'a'); + *cy = CHAR_LCALPHA_Y; + } else if (i > 'Z') { + *cx = CHAR_SYMNUM_X; + *cy = CHAR_SYMNUM_Y; + } else if (i >= 'A') { + *cx = CHAR_UCALPHA_X + CHAR_WIDTH * (i - 'A'); + *cy = CHAR_UCALPHA_Y; + } else if (i > '9') { + *cx = CHAR_SYMNUM_X; + *cy = CHAR_SYMNUM_Y; + } else if (i >= ' ') { + *cx = CHAR_SYMNUM_X + CHAR_WIDTH * (i - ' '); + *cy = CHAR_SYMNUM_Y; + } else { + *cx = CHAR_SYMNUM_X; + *cy = CHAR_SYMNUM_Y; + } + } +} + +static void draw_string (const char *str, int x, int y) +{ + for ( ; *str; ++str) { + dockapp_overlay_pixmap ( + CHAR_X[(int)*str], CHAR_Y[(int)*str], + x, y, + CHAR_WIDTH, CHAR_HEIGHT); + x += CHAR_WIDTH; + } +} + + +/*********************************************************************** + * Button Widgets + */ + +static const int BTN_PAUSE_X = 128; +static const int BTN_STOP_X = 147; +static const int BTN_Y = 37; +static const int BTN_WIDTH = 19; +static const int BTN_HEIGHT = 9; + + +/*********************************************************************** + * Progress Bars + */ + +/* Coords and dimensions refer to the pbars, excluding the borders + * which make up the ``ditches'' + */ +static const int PBAR_Y[4] = { + 5, + 20, + 35, + 50, +}; + +static const int PBAR_X = 3; + +/* These are the graphics for the bars themselves. + */ +static const int PBAR_FULL_X = 67; +static const int PBAR_FULL_Y = 37; + +static const int PBAR_EMPTY_X = 67; +static const int PBAR_EMPTY_Y = 47; + +static const int PBAR_LENGTH = 58; +static const int PBAR_HEIGHT = 9; + +static int bar_selected = -1; + +static void draw_pbar (int trough_x, int trough_y, int value, int max) +{ + int width = ((unsigned long) PBAR_LENGTH * value) / max; + + dockapp_copy_pixmap ( + PBAR_FULL_X, PBAR_FULL_Y, + trough_x, trough_y, + width, PBAR_HEIGHT); + + dockapp_copy_pixmap ( + PBAR_EMPTY_X, PBAR_EMPTY_Y, + trough_x + width, trough_y, + PBAR_LENGTH - width, PBAR_HEIGHT); +} + +static const char *const DEFAULT_TEXT[] = { + " wmget", + "", + "", + "", +}; + +static void draw_pbars (void) +{ + int i; + + for (i = 0; i < 4; ++i) { + Job *j = &shmem->jobs[i]; + + if (j->status == J_EMPTY) { + draw_pbar (PBAR_X, PBAR_Y[i], 0, 1); + draw_string (DEFAULT_TEXT[i], PBAR_X + 1, PBAR_Y[i] + 1); + continue; + } + + if (i == bar_selected) { + /* percentage (or error) + stop button */ + + draw_pbar (PBAR_X, PBAR_Y[i], 0, 1); + + if (j->status != J_FAILED) { + char pct[4]; + sprintf (pct, "%02lu%%", + 100L * j->progress / j->prog_max); + + draw_string (pct, PBAR_X + 1, PBAR_Y[i] + 1); + } else { + char err[9]; + strncpy (err, j->error, 8); + err[8] = '\0'; + draw_string (err, PBAR_X + 1, PBAR_Y[i] + 1); + } + + dockapp_copy_pixmap ( + BTN_STOP_X, BTN_Y, + PBAR_X + PBAR_LENGTH - BTN_WIDTH, PBAR_Y[i], + BTN_WIDTH, BTN_HEIGHT); + } else { + /* name + scrollbar, or error */ + draw_pbar (PBAR_X, PBAR_Y[i], j->progress, j->prog_max); + draw_string (j->options.display, PBAR_X + 1, PBAR_Y[i] + 1); + } + } +} + +/*********************************************************************** + * Shared memory segment: global pointer, constructor + */ +Shmem *shmem = 0; + +static int init_shmem (void) +{ + int shmid; + int i; + + if ((shmid = shmget (IPC_PRIVATE, sizeof *shmem, SHM_R | SHM_W)) < 0) { + error_sys ("could not allocate shared memory segment [shmget()]"); + return 1; + } + + if ((shmem = shmat (shmid, 0, 0)) == (void *) -1) { + error_sys ("could not attach shared memory segment [shmat()]"); + return 1; + } + + for (i = 0; i < 4; ++i) { + shmem->jobs[i].status = J_EMPTY; + shmem->jobs[i].options.display[0] = 0; + shmem->jobs[i].progress = 0; + shmem->jobs[i].prog_max = 0; + } + + return 0; +} + +/*********************************************************************** + * start_job(): Spawn a new process and call retrieve() in there. + * Note that `j' must be in shared memory. + */ +static int start_job (Job *j) +{ + int f; + + j->prog_max = 1; + j->progress = 0; + j->status = J_INIT; + j->stop_request = 0; + + f = fork (); + if (f < 0) { + error_sys ("could not create child process [fork()]"); + return 1; + } else if (f == 0) { /* child */ + retrieve (j); + + if (j->status == J_FAILED) { + /* Sleep until user acks the error. + */ + while (!j->stop_request) { + struct timespec sleeptime = { 0, 100000000L }; + nanosleep (&sleeptime, NULL); + } + } + + j->status = J_EMPTY; + exit (0); + } + + return 0; +} + +/*********************************************************************** + * The Job Queue. Okay, this is a little cheesy right now. + */ +static Job *job_queue[MAX_QUEUED_JOBS] = { 0 }; +static size_t job_queue_depth = 0; +static job_id_t next_job_id = 1; /* Job id 0 is never valid */ + + +/*********************************************************************** + * process_queue(): If a job has finished, pull it from its slot. + * If a slot is open, pull the next job from the queue. + */ +static int process_queue (void) +{ + size_t i; + + for (i = 0; i < MAX_ACTIVE_JOBS; ++i) { + switch (shmem->jobs[i].status) { + default: /* job occupying slot */ + continue; + + case J_EMPTY: + /* aha. see if there is anything queued up */ + if (!job_queue_depth) + continue; + + shmem->jobs[i] = *job_queue[--job_queue_depth]; + + free (job_queue[job_queue_depth]); + + debug ("Pulled new active job %lu off queue", + shmem->jobs[i].job_id); + + if (start_job (&shmem->jobs[i])) + return 1; + + continue; + } + } + + return 0; +} + +/*********************************************************************** + * cancel_job(): Cancel a job. If it's running, stop it; if it's + * in the queue, dequeue it; if it's nowhere, do nothing. + */ +static int cancel_job (job_id_t job_id) +{ + Job *j; + Job **jp; + + /* First search the active jobs. */ + for (j = shmem->jobs; j < shmem->jobs + MAX_ACTIVE_JOBS; ++j) { + if (j->job_id == job_id) { + switch (j->status) { + case J_EMPTY: + case J_STOPPING: + case J_COMPLETE: + /* Job has already completed. */ + return 0; + case J_FAILED: + /* Job has already failed; this simply clears it + * out. */ + j->status = J_COMPLETE; + return 0; + default: + /* just to keep the compiler warnings at bay */ + break; + } + + ++j->stop_request; /* Request job termination. */ + + return 0; + } + } + + /* Okay, now search the pending queue. */ + for (jp = job_queue; jp < job_queue + job_queue_depth; ++jp) { + if (*jp && (*jp)->job_id == job_id) { + /* Simply delete it from the queue. */ + --job_queue_depth; + memmove (jp, jp + 1, + ((job_queue + job_queue_depth) - jp) * sizeof (Job *)); + + return 0; + } + } + + /* Job not found. This is not an error, as we assume that the + * job has already died off. + */ + return 0; +} + +/*********************************************************************** + * client_*(): Issue a response back to the client. + */ +static void client_error (FILE *fp, const char *fmt, ...) +{ + va_list ap; + va_start (ap, fmt); + + fprintf (fp, RESPONSE_ERROR " "); + vfprintf (fp, fmt, ap); + va_end (ap); +} + +static void client_job_accepted (FILE *fp, job_id_t job_id) +{ + fprintf (fp, RESPONSE_JOB_ACCEPTED " %lu\n", job_id); +} + +static void client_job_canceled (FILE *fp, job_id_t job_id) +{ + fprintf (fp, RESPONSE_JOB_CANCELED " %lu\n", job_id); +} + +static void client_list_header (FILE *fp) +{ + debug ("client_list_header()"); + + fprintf (fp, RESPONSE_LIST_COMING "\n"); +} + +static void client_list_job (FILE *fp, Job *j) +{ + const char *status; + + switch (j->status) { + default: + status = "UNKNOWN: Internal Error!"; + break; + + case J_INIT: + status = "INIT: Waiting to start"; + break; + + case J_RUNNING: + status = "RUNNING: Currently retrieving"; + break; + + case J_PAUSED: + status = "PAUSED: Download suspended"; + break; + + case J_STOPPING: + status = "STOPPING: Got stop request"; + break; + + case J_COMPLETE: + status = "COMPLETE: Download complete"; + break; + + case J_FAILED: + status = j->error; + break; + } + + fprintf (fp, "Job %lu [%9s]: %lu/%lu %s\n%s => %s\n\n", + j->job_id, j->options.display, j->progress, j->prog_max, + status, j->source_url, j->options.save_to); +} + + +static int insert_job (Job *j) +{ + if (job_queue_depth >= MAX_QUEUED_JOBS) { + error ("Job queue full"); + free (j); + return 1; + } + + j->job_id = next_job_id++; + + debug ("Accepted job..."); + debug_dump_job (j); + + job_queue[job_queue_depth++] = j; + + return 0; +} + + +static Job *init_job (Request *req, FILE *errfp) +{ + struct stat st; + const char *base_first; + const char *base_last; + size_t base_sz; + + Job *j = malloc (sizeof (Job)); + if (!j) { + client_error (errfp, "Dockapp out of memory!"); + return 0; + } + + STRCPY_TO_ARRAY (j->source_url, req->source_url); + + j->status = J_INIT; + j->progress = j->prog_max = 0; + j->stop_request = 0; + j->options = config.job_defaults; + + /* Copy over any applicable options---except save_to and display, + * which merit special consideration below. + */ + if (req->overwrite != -1) + j->options.overwrite = req->overwrite; + + if (req->continue_from != -1) + j->options.continue_from = req->continue_from; + + if (req->proxy) + STRCPY_TO_ARRAY (j->options.proxy, req->proxy); + + if (req->follow != -1) + j->options.follow = req->follow; + + if (req->user_agent) + STRCPY_TO_ARRAY (j->options.user_agent, req->user_agent); + + if (req->use_ascii != -1) + j->options.use_ascii = req->use_ascii; + + if (req->referer) + STRCPY_TO_ARRAY (j->options.referer, req->referer); + + if (req->include != -1) + j->options.include = req->include; + + if (req->interface) + STRCPY_TO_ARRAY (j->options.interface, req->interface); + + if (req->proxy_auth) + STRCPY_TO_ARRAY (j->options.proxy_auth, req->proxy_auth); + + if (req->auth) + STRCPY_TO_ARRAY (j->options.auth, req->auth); + + /* Extract the "base name" (last slash-delimited component) of the + * source URL for future use. + */ + base_last = j->source_url + strlen (j->source_url) - 1; + while (*base_last == '/' && base_last > j->source_url) + --base_last; + base_first = base_last; + while (*base_first != '/' && base_first > j->source_url) + --base_first; + base_sz = base_last - base_first; + ++base_first; /* get it past that initial slash */ + + if (base_sz == 0) { + /* Uh, oh... invalid source_url anyway... give up. */ + client_error (errfp, "Invalid URL '%s'", j->source_url); + goto RETURN_NULL; + } + + debug ("baselen %d", base_sz); + + /* If no display-name was provided, use the basename. + */ + if (req->display) { + STRCPY_TO_ARRAY (j->options.display, req->display); + } else { + size_t n = base_sz; + if (n > sizeof j->options.display - 1) + n = sizeof j->options.display - 1; + strncpy (j->options.display, base_first, n); + j->options.display[n] = '\0'; + debug ("display was empty... set it to %s", j->options.display); + } + + + /* If there was a save-to location provided, copy it into the job. + * If it's a relative path, make it relative to the download + * directory. If it wasn't given, just copy the download dir. + */ + if (req->save_to) { + if (req->save_to[0] == '/') { + debug ("Reqest contained absolute dir."); + } else { + STRCPY_TO_ARRAY (j->options.save_to, + config.job_defaults.save_to); + if (strlen (j->options.save_to) + strlen (req->save_to) + 2 + > MAXPATHLEN) { + client_error (errfp, + "Download output pathname too long"); + goto RETURN_NULL; + } + strcat (j->options.save_to, "/"); + strcat (j->options.save_to, req->save_to); + + debug ("Resolved output to '%s'", j->options.save_to); + } + } else { + STRCPY_TO_ARRAY (j->options.save_to, + config.job_defaults.save_to); + debug ("Defaulted output to '%s'", j->options.save_to); + } + + + /* Now we've got something... let's see what it is... + */ + if (stat (j->options.save_to, &st)) { + if (errno == ENOENT) { + /* Name of a file which doesn't exist... ready to save. */ + debug ("Target does not exist."); + return j; + } + error_sys ("could not stat(`%s')", j->options.save_to); + client_error (errfp, "Failed when checking pathname '%s'", + j->options.save_to); + goto RETURN_NULL; + } + + /* If it's a directory name, append the basename from above and + * re-stat. + */ + if (S_ISDIR (st.st_mode)) { + int offset = strlen (j->options.save_to); + debug ("Is a directory."); + if (offset + base_sz + 2 > sizeof j->options.save_to) { + client_error (errfp, "Save-to path too long!"); + goto RETURN_NULL; + } + + j->options.save_to[offset] = '/'; + strncpy (j->options.save_to + offset + 1, base_first, base_sz); + j->options.save_to[offset + 1 + base_sz] = '\0'; + + debug ("Extended to %s", j->options.save_to); + + if (stat (j->options.save_to, &st)) { + if (errno == ENOENT) { + return j; + } + error_sys ("could not stat(`%s')", j->options.save_to); + client_error (errfp, "Failed when checking pathname '%s'", + j->options.save_to); + goto RETURN_NULL; + } + } + + /* If we're here, it's not a directory but it exists. */ + debug ("%s Exists!", j->options.save_to); + if (!j->options.overwrite && !j->options.continue_from) { + client_error (errfp, + "File '%s' exists and --overwrite not specified", + j->options.save_to); + goto RETURN_NULL; + } + + /* For continuations, get the file length. If the file does not + * exist, just disable continuation; this is not an error. + * (Continuation may now be permanently enabled in the RC file.) + */ + if (j->options.continue_from) { + if (S_ISREG (st.st_mode)) { + j->options.continue_from = st.st_size; + } else { + j->options.continue_from = 0; + } + } + + /* Finally, check permissions */ + if ((st.st_mode & S_IWOTH) + || ((st.st_mode & S_IWGRP) && st.st_gid == getegid ()) + || ((st.st_mode & S_IWUSR) && st.st_uid == geteuid ())) + return j; + + client_error (errfp, "File '%s' exists and is not writable.\n", + j->options.save_to); + +RETURN_NULL: + free (j); + + return 0; +} + + +/*********************************************************************** + * process_*(): Implementations of each server command. + */ +static void process_get ( + FILE *fp, char *argnames[], char *argvalues[]) +{ + char **an, **av; + Request req; + Job *job; + + debug ("process_get()"); + + /* Don't waste the user's time if we're full already... */ + if (job_queue_depth >= MAX_QUEUED_JOBS) { + client_error (fp, "Job queue full"); + return; + } + + /* Empty out the request object... */ + clear_request (&req); + + /* And parse the args... */ + for (an = argnames, av = argvalues; *an && *av; ++an, ++av) { + if (strcasecmp (*an, ARG_GET_SOURCE_URL) == 0) { + if (strlen (*av) > MAXURL) { + client_error (fp, "Source URL too long"); + return; + } + req.source_url = *av; + + } else if (strcasecmp (*an, ARG_GET_DISPLAY) == 0) { + req.display = *av; + + } else if (strcasecmp (*an, ARG_GET_SAVE_TO) == 0) { + req.save_to = *av; + + } else if (strcasecmp (*an, ARG_GET_CONTINUE_FROM) == 0) { + char *end; + req.continue_from = strtoul (*av, &end, 0); + if (*end) { + client_error (fp, + ARG_GET_CONTINUE_FROM ": must be an integer"); + return; + } + + } else if (strcasecmp (*an, ARG_GET_OVERWRITE) == 0) { + req.overwrite = 1; + + } else if (strcasecmp (*an, ARG_GET_PROXY) == 0) { + req.proxy = *av; + + } else if (strcasecmp (*an, ARG_GET_FOLLOW) == 0) { + req.follow = atoi (*av); + + } else if (strcasecmp (*an, ARG_GET_UA) == 0) { + req.user_agent = *av; + + } else if (strcasecmp (*an, ARG_GET_USE_ASCII) == 0) { + req.use_ascii = 1; + + } else if (strcasecmp (*an, ARG_GET_REFERER) == 0) { + req.referer = *av; + + } else if (strcasecmp (*an, ARG_GET_INCLUDE) == 0) { + req.include = 1; + + } else if (strcasecmp (*an, ARG_GET_INTERFACE) == 0) { + req.interface = *av; + + } else if (strcasecmp (*an, ARG_GET_PROXY_AUTH) == 0) { + req.proxy_auth = *av; + + } else if (strcasecmp (*an, ARG_GET_AUTH) == 0) { + req.auth = *av; + + } else { + client_error (fp, "Unknown parameter '%s'", *an); + return; + } + } + + job = init_job (&req, fp); + if (!job) + return; + + if (insert_job (job)) { + client_error (fp, "Invalid job parameters"); + free (job); + } else { + client_job_accepted (fp, job->job_id); + } +} + + +static void process_cancel ( + FILE *fp, char *argnames[], char *argvalues[]) +{ + char **an, **av; + job_id_t job_id = 0; /* job id 0 is never valid */ + + debug ("process_cancel()"); + + for (an = argnames, av = argvalues; *an && *av; ++an, ++av) { + if (strcasecmp (*an, ARG_CANCEL_JOBID) == 0) { + job_id = strtoul (*av, 0, 0); + } else { + client_error (fp, "Unknown parameter '%s'", *an); + return; + } + } + + if (job_id == 0) { + client_error (fp, + CMD_CANCEL " requires the argument " ARG_CANCEL_JOBID); + return; + } + + if (cancel_job (job_id)) + client_error (fp, "Cancel failed"); + else + client_job_canceled (fp, job_id); +} + + +void process_list (FILE *fp, char *argnames[], char *argvalues[]) +{ + Job *j, **jp; + + (void)argnames; + (void)argvalues; + + debug ("process_list()"); + + client_list_header (fp); + + /* First list the active jobs. */ + for (j = shmem->jobs; j < shmem->jobs + MAX_ACTIVE_JOBS; ++j) + if (j->status != J_EMPTY) + client_list_job (fp, j); + + /* Then the waiting jobs. */ + for (jp = job_queue; jp < job_queue + job_queue_depth; ++jp) + client_list_job (fp, *jp); +} + +/*********************************************************************** + * process_request(): Accept a command and parameters, process it, + * and reply. + */ +static void process_request (FILE *fp) +{ + char command[MAXCMDLEN]; + char *arg; + int nargs; + + char *argnames[MAXCMDARGS + 1]; + char *argvalues[MAXCMDARGS + 1]; + + /* A command line always comes first. */ + if (!fgets (command, sizeof command - 1, fp)) { + debug ("No command on pipe!"); + return; + } + + /* Arguments come after whitespace. Note that not all commands have + * args. Each argument looks like this: PARAMETERNAME(VALUE). + * Hey, don't tell me *none* of you have ever done AS/400 CL.... + */ + nargs = 0; + arg = command + strcspn (command, " \t\r\n"); + *arg++ = 0; + arg += strspn (arg, " \t"); + + while (*arg && *arg != '\n' && *arg != '\r') { + if (nargs > MAXCMDARGS - 1) { + client_error (fp, "Too many arguments!"); + return; + } + + argnames[nargs] = arg; + + if (!(arg = strchr (arg, '('))) { + client_error (fp, "Argument missing value"); + return; + } + + *arg++ = 0; + + argvalues[nargs] = arg; + + /* Arguments are terminated by a ), of course, but they may + * also contain characters (such as )) quoted by \. + */ + while (*arg && *arg != ')') { + if (*arg == '\\') { + if (!arg[1]) { + client_error (fp, "Argument missing closing ), " + "ended with \\ by itself"); + return; + } + + /* strlen(arg+1)+1 = strlen(arg)-1+1 = strlen(arg) */ + memmove (arg, arg + 1, strlen (arg)); + } + ++arg; + } + + if (!arg) { + client_error (fp, "Argument missing closing )"); + return; + } + + *arg++ = 0; + + arg += strspn (arg, " \t"); + + ++nargs; + } + + argnames[nargs] = 0; + argvalues[nargs] = 0; + + /* Got a valid command/argument set. Process it. */ + if (strcasecmp (command, CMD_GET) == 0) + process_get (fp, argnames, argvalues); + else if (strcasecmp (command, CMD_CANCEL) == 0) + process_cancel (fp, argnames, argvalues); + else if (strcasecmp (command, CMD_LIST) == 0) + process_list (fp, argnames, argvalues); + else + client_error (fp, "Unknown command"); +} + + +/*********************************************************************** + * on_iq_ready(): invoked by the dockapp lib when there are connections + * pending on the iq + */ +static dockapp_rv_t on_iq_ready (void *unused0, short unused1) +{ + FILE *fp; + + (void)unused0; + (void)unused1; + + debug ("on_iq_ready"); + + if ((fp = iq_server_accept ())) { + process_request (fp); + + fclose (fp); + } + + return dockapp_ok; +} + +static int init_grim_reaper (void) +{ + struct sigaction sa; + + sa.sa_handler = SIG_IGN; + sigemptyset (&sa.sa_mask); + sa.sa_flags = SA_NOCLDSTOP | SA_RESTART; + /* Obsolete - sa.sa_restorer = 0; */ + + if (sigaction (SIGCHLD, &sa, 0)) { + error_sys ("sigaction(SIGCHLD) failed"); + return 1; + } + + return 0; +} + + +static dockapp_rv_t on_click_pbar ( + void *cbdata, int x_unused, int y_unused) +{ + int which = (int)cbdata; + (void)x_unused; + (void)y_unused; + + debug ("got a click on pbar %d", which); + + if (bar_selected == which) { + /* Selected bar gets deselected. */ + bar_selected = -1; + + } else { + bar_selected = which; + } + + return dockapp_ok; +} + +static dockapp_rv_t on_click_stop ( + void *cbdata, int x_unused, int y_unused) +{ + int which = (int)cbdata; + (void)x_unused; + (void)y_unused; + + debug ("got a click on stop %d", which); + + if (bar_selected == which) { + /* got a stop request (only works on selected pbar) */ + ++shmem->jobs[which].stop_request; + } + + return dockapp_ok; +} + +static dockapp_rv_t on_periodic_callback (void *cbdata) +{ + (void)cbdata; + + if (process_queue ()) + return dockapp_exit; + + draw_pbars (); + + return dockapp_ok; +} + +static dockapp_rv_t on_got_selection (void *cbdata, const char *str) +{ + Request req; + Job *j; + + (void)cbdata; + + debug ("on_got_selection >> %s", str); + + if (strlen (str) > MAXURL) { + error ("rejecting job submission: URL too long!"); + return dockapp_ok; + } + + clear_request (&req); + + req.source_url = str; + + j = init_job (&req, stderr); + if (!j) { + return dockapp_ok; + } + + debug ("submitting job for [%s]...", j->source_url); + + if (insert_job (j)) { + free (j); + debug ("insert_job rejected it!"); + } + + return dockapp_ok; +} + +static dockapp_rv_t on_middle_click (void *cbdata, int x, int y) +{ + (void)cbdata; + (void)x; + (void)y; + + debug ("on_middle_click"); + + dockapp_request_selection_string (on_got_selection, 0); + + return dockapp_ok; +} + + +/* This is the main routine for the dock app (the first instance + * started) + */ +int server (int argc, char **argv) +{ + int i; + + config_server (argc, argv, &config); + + if (init_grim_reaper ()) + return 1; + + if (init_shmem ()) + return 1; + + if (iq_server_init ()) + return 1; + + init_font (); + + dockapp_init_gui ("wmget", argv, wmget_xpm); + + for (i = 0; i < 4; ++i) { + dockapp_add_clickregion ( + PBAR_X + PBAR_LENGTH - BTN_WIDTH, PBAR_Y[i], + PBAR_LENGTH, PBAR_HEIGHT, + Button1Mask, + on_click_stop, (void *)i); + + dockapp_add_clickregion ( + PBAR_X, PBAR_Y[i], + PBAR_LENGTH, PBAR_HEIGHT, + Button1Mask, + on_click_pbar, (void *)i); + } + + dockapp_add_clickregion ( + 0, 0, 64, 64, Button2Mask, on_middle_click, 0); + + dockapp_add_pollfd (iq_get_listen_fd (), POLLIN, on_iq_ready, 0); + + dockapp_set_periodic_callback (400, on_periodic_callback, 0); + + /* Perform one initial refresh. + */ + on_periodic_callback (0); + + dockapp_run (); + + return 0; +} + diff --git a/wmget/usage.c b/wmget/usage.c new file mode 100644 index 0000000..59b0d91 --- /dev/null +++ b/wmget/usage.c @@ -0,0 +1,62 @@ +/* + wmget - A background download manager as a Window Maker dock app + Copyright (c) 2001-2003 Aaron Trickey + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + ******************************************************************** + usage.c - Displays usage information + + Provides usage(), which gets invoked whenever the user asks for + help, or provides bad options. +*/ + +#include + +#include "wmget.h" + + +void usage (void) +{ + printf ( +WMGET_VERSION_BANNER "\n" +WMGET_COPYRIGHT "\n" +"Usage:\n" +" wmget dock [options] # To start up the dockapp\n" +" wmget [options] # To request a download\n" +" wmget cancel # To cancel a download\n" +" wmget list # To show current and pending jobs\n" +" wmget --version # (or -v) To print the version\n" +" wmget --help # (or -h) To print this text\n" +"Options:\n"); + +#define yes " ..." +#define no " " +#define O(s,l,a,t) \ + printf (" -%c|--%-15s " t "\n", s, #l a); +#include "config.def" +#undef O +#undef no +#undef optional +#undef required +} + + + diff --git a/wmget/wmget-test.pl b/wmget/wmget-test.pl new file mode 100755 index 0000000..b1ab469 --- /dev/null +++ b/wmget/wmget-test.pl @@ -0,0 +1,45 @@ +#!/usr/bin/perl -w +use strict; + +use IO::Handle; +use IO::Socket; + +&run_server (); + +sub run_server { + local $SIG{PIPE} = 'IGNORE'; + + my $servsock = new IO::Socket ( + Domain => AF_INET, + Type => SOCK_STREAM, + Proto => "tcp", + Reuse => 1, + Listen => 1, + LocalPort => 8000, + ) or die "new IO::Socket: $!"; + + print "to test, enter wmget http://localhost:8000/...\n"; + + while (my $client = $servsock->accept) { + $client->autoflush (1); + + while (<$client>) { + print STDERR "> $_"; + last if not /\S/; + } + + print STDERR "headers done. sending data...\n"; + + print $client "HTTP/1.0 200 Ok, here you go...\r\n"; + print $client "Content-Type: text/plain\r\n"; + print $client "Content-Length: 1000\r\n\r\n"; + + # generate bogus data, and do it slowly.... + for (1..10) { + print $client "x" x 99, "\n"; + sleep 1; + } + } +} + + diff --git a/wmget/wmget.1 b/wmget/wmget.1 new file mode 100644 index 0000000..0c20a02 --- /dev/null +++ b/wmget/wmget.1 @@ -0,0 +1,217 @@ +.\"Generated by db2man.xsl. Don't modify this, modify the source. +.de Sh \" Subsection +.br +.if t .Sp +.ne 5 +.PP +\fB\\$1\fR +.PP +.. +.de Sp \" Vertical space (when we can't use .PP) +.if t .sp .5v +.if n .sp +.. +.de Ip \" List item +.br +.ie \\n(.$>=3 .ne \\$3 +.el .ne 3 +.IP "\\$1" \\$2 +.. +.TH "WMGET" 1 "" "" "" +.SH NAME +wmget \- Background download manager in a dockapp +.SH "SYNOPSIS" +.ad l +.hy 0 +.HP 6 +\fBwmget\fR dock [\fIoptions\fR] +.ad +.hy +.ad l +.hy 0 +.HP 6 +\fBwmget\fR [\fIoptions\fR] {\fIURL\fR} +.ad +.hy +.ad l +.hy 0 +.HP 6 +\fBwmget\fR cancel {\fIjob\-id\fR} +.ad +.hy +.ad l +.hy 0 +.HP 6 +\fBwmget\fR list +.ad +.hy + +.SH "DESCRIPTION" + +.PP +wmget is a ``dockapp'' which makes it more convenient to retrieve files in the background\&. Dockapps are applications which run in small windows intended to be ``docked'' in window manager\-provided locations\&. wmget was developed primarily under GNU Window Maker, the author's preferred WM, but is known to work under AfterStep as well, and should work with other dockapp\-aware window managers and docks\&. + +.PP +It uses the excellent libcurl library, part of the Curl automated\-download program, to perform file retrieval from Web servers, FTP servers, and other sources\&. + +.PP +wmget allows you to perform multiple downloads without keeping a terminal open (for FTP or curl or something) or another window on your desktop (e\&.g\&. for Mozilla download progress); download progress is visible any time the Dock is visible\&. + +.PP +You start downloads either by ``pasting'' URLs from Web browsers or other applications, or by invoking wmget from the command line (or another script or program) with a source URL\&. The dockapp has a handful of configurable download options, such as target directory, HTTP proxy server, etc\&. + +.SH "STARTING UP" + +.PP +To start the dockapp, just run \fBwmget dock &\fR\&. If you are running Window Maker, you can then just drag the new appicon onto your Dock, right\-click on an area outside the four progress bars, select Settings, and select Start when Window Maker is started\&. + +.PP +If you are running AfterStep, you can add it to your Wharf by adding the following line to your \fI~/GNUstep/Library/AfterStep/wharf\fR file: + +.nf + + *Wharf wmget \- Swallow "wmget" wmget dock & + +.fi + +.PP +Other window managers support dockapps in different ways\&. Even in window managers without any special dockapp support, you can run wmget as noted above; it will simply show up as a small window or "icon"\&. + +.SH "USING WMGET" + +.PP +wmget's user interface is simple: four stacked progress bars, initially empty, representing four possible simultaneous downloads\&. The top bar will say ``wmget'' when there isn't a download running there, but any download will cover that up\&. + +.PP +Each running download normally shows up to nine characters of its filename, overlaid with a progress bar\&. You can click on any progress bar to reveal a percentage display and a stop button; clicking on the percentage display switches back, while clicking on the stop button stops the download\&. There is currently no confirmation; it just stops\&. + +.PP +You can ``request'' downloads at any time\&. If all four places show running downloads, additional requests will queue up, waiting for one to complete; wmget will never be downloading more than four files at a time\&. + +.PP +By default, wmget figures out a reasonable filename for any requested downloads, writes them to your home directory, and won't overwrite an existing file by the same name\&. All of these, along with a few other options, are configurable\&. See below\&. + +.SS "Requesting Downloads with the Mouse" + +.PP +The easiest way to request a download is by copying and pasting a link\&. wmget lets you paste a URL by middle\-clicking anywhere on any of its status bars\&. Simply copy a link from some other source (for example, by right\-clicking on a link in Mozilla or Netscape and picking Copy Link Location), and middle\-click on one of the progress meter boxes in wmget\&. + +.SS "Requesting Downloads from the Command Line" + +.PP +The \fBwmget\fR command also lets you directly request downloads from the command line, or from within a script or another program\&. The syntax is \fBwmget \fIURL\fR\fR, plus any of the options documented below\&. + +.PP +Once you run this command, you'll either get an error message or a ``job ID''\&. The job ID is only useful in conjunction with the \fBwmget cancel\fR command\&. + +.SS "Download Failures" + +.PP +Downloads can fail for a variety of reasons, from running out of disk space to modem hangups\&. Since wmget is designed not to interrupt your workflow or exceed its little square window, it responds to any download error by aborting the download and writing an error file to your download directory\&. This error file has the name \fIfile\&.ERROR\fR, where \fIfile\fR is the name of the actual download target\&. This error file is a plain text file containing information on what you were downloading and what went wrong\&. + +.SS "Viewing and Canceling Downloads" + +.PP +As noted above, you can see the currently\-running downloads in the four progress boxes on the dockapp\&. Clicking on a bar reveals a stop button, and clicking on that stop button cancels the download (but leaves the partially\-downloaded file on your computer)\&. + +.PP +At any time, you can also run the \fBwmget list\fR command, which displays all the running downloads as well as any queued\-up requests\&. The listing contains entries like this: + +.IP +.nf +Job 10 [linux\-2\&.6]: 1658544/33073407 RUNNING +ftp://ftp\&.kernel\&.org/pub/linux/kernel/v2\&.6/linux\-2\&.6\&.0\-test6\&.tar\&.bz2 +=> /home/aaron/DOWNLOAD/linux\-2\&.6\&.0\-test6\&.tar\&.bz2 + +.fi + +.PP +What you see in that (admittedly dense) listing are the job ID, the name of the download as displayed on the dockapp (surrounded in brackets), the progress in bytes, the total bytes to download, the current status, the source URL, and the target file on your computer\&. Whew\&. + +.PP +You can cancel any requested or running download from the command line by specifying \fBwmget cancel \fIjob\-id\fR\fR\&. + +.SH "COMMAND-LINE OPTIONS AND THE CONFIGURATION FILE" + +.PP +wmget supports a handful of configuration options\&. You can specify defaults for all downloads by putting them in a configuration file or adding command\-line options to the \fBwmget dock\fR command at startup, or you can specify options for one specific download by adding options to the \fBwmget \fIURL\fR\fR command when you request them\&. There isn't any way to specify options on URLs you paste with the mouse\&. Dockapp command\-line arguments override config\-file settings, and per\-URL settings override dockapp settings\&. + +.PP +The configuration file is an optional file named \fI\&.wmgetrc\fR in your home directory\&. If it's there, it's parsed by the dockapp at startup\&. The syntax is simple: one option per line, all options consisting of a name and possibly a value\&. Blank lines are okay, and lines starting with # are ignored (so you can disable options easily)\&. Option names are just the same as the command\-line option names given below, except you don't put the dashes (``\-\-'') and you can't use the one\-letter abbreviations\&. + +.TP +\-\-version, \-v +Regardless of any other options, this prints out version and copyright information and exits\&. + +.TP +\-\-help, \-h +Regardless of any other options, this prints out a help message and exits\&. + +.TP +\-\-silent, \-s +Suppress any output text other than error messages\&. + +.TP +\-\-verbose, \-V +Write extra debugging information; not very useful unless you're debugging or extending the software\&. + +.TP +\-\-output \fIpathname\fR, \-o \fIpathname\fR +Specifies where to write downloaded files\&. In the config file or on the dockapp command line, this can only be used to specify your default download directory; it must be an existing directory, and if it's not absolute then it is assumed to be relative to your home directory\&. On a specific download request, this can provide an alternate save directory or even an alternate filename; in that case, a non\-absolute path is relative to the default download directory\&. + +.TP +\-\-display \fIname\fR, \-d \fIname\fR +Display the first nine characters of \fIname\fR in the progress display for this file\&. (Only valid on specific download requests, not on the dockapp or in the config file\&.) + +.TP +\-\-overwrite, \-O +Allow wmget to overwrite an existing file when downloading\&. Normally, it will refuse to do so\&. + +.TP +\-\-continue, \-C +When fetching a file that already exists locally, assume the local copy was an aborted download and try to download just the remainder\&. + +.TP +\-\-auth \fIusername\fR:\fIpassword\fR, \-a \fIusername\fR:\fIpassword\fR +Provides login information for the server from which you're downloading\&. + +.TP +\-\-proxy \fIserver\fR:\fIport\fR, \-p \fIserver\fR:\fIport\fR, \-\-proxy_auth \fIuser\fR:\fIpassword\fR, \-P \fIuser\fR:\fIpassword\fR +Specifies a proxy server and optionally a proxy\-server username/password pair for getting past firewalls\&. + +.TP +\-\-follow \fIN\fR, \-f \fIN\fR +Specifies how many HTTP redirects to follow when resolving a page; by default, wmget is configured to follow up to 5\&. Set this to 0 to disable redirection\&. (In any real\-world situation, if you're getting redirected more than 5 times, there's a problem\&.\&.\&.) + +.TP +\-\-user\-agent \fIstring\fR, \-U \fIstring\fR +Specifies which User\-Agent string to provide to servers when performing HTTP downloads\&. The default User\-Agent names both the wmget and libcurl versions in use\&. + +.TP +\-\-ascii, \-B +Force FTP downloads to use ASCII mode; normally, they use binary mode\&. If you're downloading text documents, ASCII mode will take care of any necessary conversions between the text formats of the server and your computer\&. + +.TP +\-\-referer \fIstring\fR, \-e \fIstring\fR +Provides a ``referer'' string to the Web server\&. + +.TP +\-\-interface \fIinterface\fR, \-\-n \fIinterface\fR +Names a specific network interface to use (e\&.g\&., eth0 for the first Ethernet interface on a Linux system)\&. Rarely needed\&. + +.TP +\-\-headers, \-h +When performing an HTTP retrieval, include the HTTP message header in the saved file\&. This is only really useful for testing\&. + +.SH "FILES" + +.TP +\fI~/\&.wmgetrc\fR +The (optional) configuration file for the wmget dockapp\&. Settings in this file are used to specify defaults for the dockapp when it starts; see the section on configuration and command\-line options for more details\&. + +.TP +\fI~/\&.wmget\&.iq\fR +A Unix\-domain socket created by the wmget dockapp to accept requests from wmget commands\&. Created at startup automatically\&. + +.SH AUTHOR +Aaron Trickey. diff --git a/wmget/wmget.c b/wmget/wmget.c new file mode 100644 index 0000000..16d3fc3 --- /dev/null +++ b/wmget/wmget.c @@ -0,0 +1,127 @@ +/* + wmget - A background download manager as a Window Maker dock app + Copyright (c) 2001-2003 Aaron Trickey + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + ******************************************************************** + wmget.c - main() and a few common functions + + This file contains the main() for wmget; it simply checks for the + presence of ``dock'' as the first argument and invokes server(), + request(), cancel(), or list() as appropriate. Also found here are + a few global utilities. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wmget.h" + +void debug_dump_job (Job *job) +{ + if (output_level () < OL_DEBUG) + return; + + debug ("id = %lu\n" + "status = %d\n" + "progress=%d/%d\n" + "stop_request=%d\n" + "display=[%s]\n" + "source_url=[%s]\n" + "save_to=[%s]\n" + "continue_from=%d\n\n", + job->job_id, + job->status, job->progress, job->stop_request, + job->prog_max, job->options.display, job->source_url, + job->options.save_to, job->options.continue_from); +} + + +const char *home_directory (void) +{ + static char *home_directory = 0; + struct passwd *pwent; + + if (home_directory) + return home_directory; + + home_directory = getenv ("HOME"); + + if (!home_directory) { + pwent = getpwuid (geteuid ()); + home_directory = strdup (pwent->pw_dir); + } + + return home_directory; +} + + +int main (int argc, char **argv) +{ + char **arg; + int (*function) (int, char **) = &request; + + if (argc < 2) { + usage (); + return 0; + } + + /* Cheat, because we don't invoke getopt until we know what mode + * we're in... + */ + for (arg = argv; *arg; ++arg) { + if (strcmp (*arg, "dock") == 0) { + function = &server; + + } else if (strcmp (*arg, "cancel") == 0) { + function = &cancel; + + } else if (strcmp (*arg, "list") == 0) { + function = &list; + + } else if (strcmp (*arg, "-v") == 0 + || strcmp (*arg, "--version") == 0) { + puts (WMGET_VERSION_BANNER); + puts (WMGET_COPYRIGHT); + return 0; + + } else if (strcmp (*arg, "-h") == 0 + || strcmp (*arg, "--help") == 0) { + usage (); + return 0; + } + } + + return function (argc, argv); +} + + + + + + diff --git a/wmget/wmget.h b/wmget/wmget.h new file mode 100644 index 0000000..1dab25a --- /dev/null +++ b/wmget/wmget.h @@ -0,0 +1,251 @@ +#ifndef I_WMGET_H +#define I_WMGET_H +/* + wmget - A background download manager as a Window Maker dock app + Copyright (c) 2001-2003 Aaron Trickey + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + ******************************************************************** + wmget.h - Common definitions for all wmget modules + + This file defines the public entry points in each *.c file in the + main wmget program, as well as some common constants, global + variables, and the structure of the shared memory segment. +*/ + + +#include +#include + +/* Important: the Makefile greps the source to extract WMGET_VERSION. + * So don't change its format or anything without checking there first. + */ +#define WMGET_VERSION "0.6.0" +#define WMGET_VERSION_BANNER "wmget " WMGET_VERSION \ + ", compiled with libcurl " \ + LIBCURL_VERSION +#define WMGET_COPYRIGHT "Copyright (c) 2001-2003 " \ + "Aaron Trickey " \ + "; ABSOLUTELY NO WARRANTY" + + +#define DEFAULT_USER_AGENT \ + "wmget/" WMGET_VERSION " (libcurl/" LIBCURL_VERSION ")" + +#define MAXRCLINELEN 1024 +#define MAXURL 1024 +#define MAXUA 255 +#define MAXAUTH 100 +#define MAXIF 255 +#define MAXCMDLEN 2048 +#define MAXCMDARGS 20 +#define MAX_DISPLAY 9 + +#define MAX_ACTIVE_JOBS 4 /* a number constrained by the UI */ +#define MAX_QUEUED_JOBS 20 + +/* Command language */ + +/* The GET command and its arguments: */ +#define CMD_GET "GET" +#define ARG_GET_SOURCE_URL "FROM" +#define ARG_GET_DISPLAY "DISP" +#define ARG_GET_SAVE_TO "TO" +#define ARG_GET_OVERWRITE "OVER" +#define ARG_GET_CONTINUE_FROM "CONT" +#define ARG_GET_PROXY "PROXY" +#define ARG_GET_FOLLOW "FOLLOW" +#define ARG_GET_UA "UA" +#define ARG_GET_USE_ASCII "ASCII" +#define ARG_GET_REFERER "REF" +#define ARG_GET_INCLUDE "INCL" +#define ARG_GET_INTERFACE "IF" +#define ARG_GET_PROXY_AUTH "PROXY-AUTH" +#define ARG_GET_AUTH "AUTH" + +#define CMD_CANCEL "CANCEL" +#define ARG_CANCEL_JOBID "JOBID" + +#define CMD_LIST "LIST" + +#define RESPONSE_JOB_ACCEPTED "ACCEPTED" +#define RESPONSE_JOB_CANCELED "CANCELED" +#define RESPONSE_LIST_COMING "JOBLIST" +#define RESPONSE_ERROR "ERROR" + + +/* Debug trace text output levels */ +typedef enum { + OL_SILENT, + OL_NORMAL, + OL_DEBUG, +} OutputLevel; + + +/* The various states an individual job may be in. + */ +typedef enum { + J_EMPTY, /* this job slot is empty */ + J_INIT, /* slot has been taken, job not yet started */ + J_RUNNING, /* this job is running */ + J_PAUSED, /* this job is paused */ + J_STOPPING, /* a stop request has come from the user */ + J_COMPLETE, /* job complete, cleaning up */ + J_FAILED, /* job failed! */ +} JobStatus; + + +/* The type of a job ID; these are allocated by the dockapp and are + * never reused within its lifetime. + */ +typedef unsigned long job_id_t; + + +/* User-configurable job options. + */ +typedef struct { + char display[MAX_DISPLAY + 1]; /* Text to display */ + char save_to[MAXPATHLEN + 1]; /* Full pathname to save to */ + /* (For srvr, this MUST be a dir) */ + int overwrite; /* Allow overwrite of save_to? */ + int continue_from; /* Byte# to resume from */ + char proxy[MAXURL + 1]; /* Proxy to use (or empty string) */ + int follow; /* How many redirects to follow */ + char user_agent[MAXUA + 1]; /* User-agent string to provide */ + + int use_ascii; /* Force FTP to ASCII */ + char referer[MAXURL + 1]; /* Specify referer */ + int include; /* Include HTTP headers in output */ + char interface[MAXIF + 1]; /* Limit to given interface */ + char proxy_auth[MAXAUTH + 1]; /* Proxy authentication */ + char auth[MAXAUTH + 1]; /* Site authentication */ + +} JobOptions; + + +/* A command-line download request. Strings are NULL if defaulted; + * integers are -1. + */ +typedef struct { + const char *source_url; /* MANDATORY. Duh. */ + const char *display; + const char *save_to; + int overwrite; + int continue_from; + const char *proxy; + int follow; + const char *user_agent; + + int use_ascii; + const char *referer; + int include; + const char *interface; + const char *proxy_auth; + const char *auth; + +} Request; + + +/* The totality of a running or queued job: + */ +typedef struct { + job_id_t job_id; + JobStatus status; + char error[CURL_ERROR_SIZE + 1]; + unsigned long progress; + unsigned long prog_max; + int stop_request; + char source_url[MAXURL + 1]; /* URL to fetch */ + + JobOptions options; +} Job; + + +/* The shared-memory structure containing the active job list. (Pending + * jobs are queued in the dockapp's private data segment.) + */ +typedef struct { + Job jobs[MAX_ACTIVE_JOBS]; +} Shmem; + + +/* Specifies the server configuration. This is used only by server.c, + * and gets populated by config_server() in config.c. + */ +typedef struct { + JobOptions job_defaults; +} ServerConfig; + + +/* Convenience macro + */ +#define STRCPY_TO_ARRAY(to,from) \ + do { \ + strncpy (to, from, sizeof to); \ + to[sizeof to - 1] = '\0'; \ + } while (0) + + +/* configure.c */ +extern void config_server (int argc, char **argv, ServerConfig *cfg); +extern void clear_request (Request *req); +extern void config_request (int argc, char **argv, Request *req); + +/* usage.c */ +extern void usage (void); + +/* iq.c */ +extern int iq_server_init (void); /* called once, by server */ +extern FILE *iq_server_accept (void); /* returns new cxn or NULL */ +extern FILE *iq_client_connect (void); /* called by each client */ +extern int iq_get_listen_fd (void); /* so you can select/poll */ + +/* server.c */ +extern Shmem *shmem; +extern int server (int argc, char **argv); + +/* request.c */ +extern int request (int argc, char **argv); + +/* cancel.c */ +extern int cancel (int argc, char **argv); + +/* list.c */ +extern int list (int argc, char **argv); + +/* retrieve.c */ +extern int retrieve (Job *job); + +/* wmget.c */ +extern const char *home_directory (void); +extern void debug_dump_job (Job *job); + +/* messages.c */ +extern void set_output_level (OutputLevel lev); +extern OutputLevel output_level (void); +extern void error (const char *fmt, ...); +extern void error_sys (const char *fmt, ...); +extern void info (const char *fmt, ...); +extern void debug (const char *fmt, ...); +extern void debug_sys (const char *fmt, ...); + + +#endif /* I_WMGET_H */ diff --git a/wmget/wmget.refentry.xml b/wmget/wmget.refentry.xml new file mode 100644 index 0000000..0b65011 --- /dev/null +++ b/wmget/wmget.refentry.xml @@ -0,0 +1,492 @@ + + + + + + + + + + AaronTrickey + aaron@amtrickey.net + + + 2001 + 2002 + 2003 + Aaron Trickey + + + + + wmget + 1 + + + + wmget + Background download manager in a dockapp + + + + + wmget + dock + options + + + wmget + options + URL + + + wmget + cancel + job-id + + + wmget + list + + + + + Description + + wmget is a ``dockapp'' which makes it more convenient to + retrieve files in the background. Dockapps are applications + which run in small windows intended to be ``docked'' in window + manager-provided locations. wmget was developed primarily under + GNU Window Maker, the author's preferred WM, but is known to + work under AfterStep as well, and should work with other + dockapp-aware window managers and docks. + + + It uses the excellent libcurl library, part of the Curl + automated-download program, to perform file retrieval from Web + servers, FTP servers, and other sources. + + + wmget allows you to perform multiple downloads without keeping a + terminal open (for FTP or curl or something) or another window + on your desktop (e.g. for Mozilla download progress); download + progress is visible any time the Dock is visible. + + + You start downloads either by ``pasting'' URLs from Web browsers + or other applications, or by invoking wmget from the command + line (or another script or program) with a source URL. The + dockapp has a handful of configurable download options, such as + target directory, HTTP proxy server, etc. + + + + + Starting Up + + To start the dockapp, just run wmget dock + &. If you are running Window Maker, you can then + just drag the new appicon onto your Dock, right-click on an area + outside the four progress bars, select + Settings, and select Start + when Window Maker is started. + + + + If you are running AfterStep, you can add it to your Wharf by + adding the following line to your + ~/GNUstep/Library/AfterStep/wharf file: + + + + + *Wharf wmget - Swallow "wmget" wmget dock & + + + + + Other window managers support dockapps in different ways. Even in + window managers without any special dockapp support, you can run + wmget as noted above; it will simply show up as a small window or + "icon". + + + + + Using wmget + + wmget's user interface is simple: four stacked progress bars, + initially empty, representing four possible simultaneous + downloads. The top bar will say ``wmget'' when there isn't a + download running there, but any download will cover that up. + + + Each running download normally shows up to nine characters of its + filename, overlaid with a progress bar. You can click on any + progress bar to reveal a percentage display and a stop button; + clicking on the percentage display switches back, while clicking + on the stop button stops the download. There is currently no + confirmation; it just stops. + + + You can ``request'' downloads at any time. If all four places + show running downloads, additional requests will queue up, waiting + for one to complete; wmget will never be downloading more than + four files at a time. + + + By default, wmget figures out a reasonable filename for any + requested downloads, writes them to your home directory, and won't + overwrite an existing file by the same name. All of these, along + with a few other options, are configurable. See below. + + + + Requesting Downloads with the Mouse + + The easiest way to request a download is by copying and pasting + a link. wmget lets you paste a URL by middle-clicking anywhere + on any of its status bars. Simply copy a link from some other + source (for example, by right-clicking on a link in Mozilla or + Netscape and picking Copy Link + Location), and middle-click on one of the progress + meter boxes in wmget. + + + + + Requesting Downloads from the Command Line + + The wmget command also lets you directly + request downloads from the command line, or from within a script + or another program. The syntax is wmget + URL, plus any of the + options documented below. + + + Once you run this command, you'll either get an error message or + a ``job ID''. The job ID is only useful in conjunction with the + wmget cancel command. + + + + + Download Failures + + Downloads can fail for a variety of reasons, from running out of + disk space to modem hangups. Since wmget is designed not to + interrupt your workflow or exceed its little square window, it + responds to any download error by aborting the download and + writing an error file to your download directory. This error + file has the name file.ERROR, where + file is the name of the actual download + target. This error file is a plain text file containing + information on what you were downloading and what went wrong. + + + + + Viewing and Canceling Downloads + + As noted above, you can see the currently-running downloads in + the four progress boxes on the dockapp. Clicking on a bar + reveals a stop button, and clicking on that stop button cancels + the download (but leaves the partially-downloaded file on your + computer). + + + At any time, you can also run the wmget list + command, which displays all the running downloads as well as any + queued-up requests. The listing contains entries like this: + +Job 10 [linux-2.6]: 1658544/33073407 RUNNING +ftp://ftp.kernel.org/pub/linux/kernel/v2.6/linux-2.6.0-test6.tar.bz2 +=> /home/aaron/DOWNLOAD/linux-2.6.0-test6.tar.bz2 + + + What you see in that (admittedly dense) listing are the job ID, + the name of the download as displayed on the dockapp (surrounded + in brackets), the progress in bytes, the total bytes to + download, the current status, the source URL, and the target + file on your computer. Whew. + + + You can cancel any requested or running download from the + command line by specifying wmget cancel + job-id. + + + + + + Command-Line Options and the Configuration File + + wmget supports a handful of configuration options. You can + specify defaults for all downloads by putting them in a + configuration file or adding command-line options to the + wmget dock command at startup, or you can + specify options for one specific download by adding options to the + wmget URL command + when you request them. There isn't any way to specify options on + URLs you paste with the mouse. Dockapp command-line arguments + override config-file settings, and per-URL settings override + dockapp settings. + + + The configuration file is an optional file named + .wmgetrc in your home directory. If it's + there, it's parsed by the dockapp at startup. The syntax is + simple: one option per line, all options consisting of a name and + possibly a value. Blank lines are okay, and lines starting with # + are ignored (so you can disable options easily). Option names are + just the same as the command-line option names given below, except + you don't put the dashes (``--'') and you can't use the one-letter + abbreviations. + + + + + --version + -v + + + Regardless of any other options, this prints out version and + copyright information and exits. + + + + + --help + -h + + + Regardless of any other options, this prints out a help + message and exits. + + + + + --silent + -s + + + Suppress any output text other than error messages. + + + + + --verbose + -V + + + Write extra debugging information; not very useful unless + you're debugging or extending the software. + + + + + --output pathname + -o pathname + + + Specifies where to write downloaded files. In the config + file or on the dockapp command line, this can only be used + to specify your default download directory; it must be an + existing directory, and if it's not absolute then it is + assumed to be relative to your home directory. On a + specific download request, this can provide an alternate + save directory or even an alternate filename; in that case, + a non-absolute path is relative to the default download + directory. + + + + + --display name + -d name + + + Display the first nine characters of + name in the progress display for + this file. + (Only valid on specific download requests, not on the + dockapp or in the config file.) + + + + + --overwrite + -O + + + Allow wmget to overwrite an existing file when downloading. + Normally, it will refuse to do so. + + + + + --continue + -C + + + When fetching a file that already exists locally, assume the + local copy was an aborted download and try to download just + the remainder. + + + + + --auth username:password + -a username:password + + + Provides login information for the server from which you're + downloading. + + + + + --proxy + server:port + + -p + server:port + + --proxy_auth + user:password + + -P + user:password + + + + Specifies a proxy server and optionally a proxy-server + username/password pair for getting past firewalls. + + + + + --follow N + -f N + + + Specifies how many HTTP redirects to follow when resolving a + page; by default, wmget is configured to follow up to 5. + Set this to 0 to disable redirection. (In any real-world + situation, if you're getting redirected more than 5 times, + there's a problem...) + + + + + --user-agent string + -U string + + + Specifies which User-Agent string to provide to servers when + performing HTTP downloads. The default User-Agent names + both the wmget and libcurl versions in use. + + + + + --ascii + -B + + + Force FTP downloads to use ASCII mode; normally, they use + binary mode. If you're downloading text documents, ASCII + mode will take care of any necessary conversions between + the text formats of the server and your computer. + + + + + --referer string + -e string + + + Provides a ``referer'' string to the Web server. + + + + + --interface interface + --n interface + + + Names a specific network interface to use (e.g., eth0 for + the first Ethernet interface on a Linux system). Rarely + needed. + + + + + --headers + -h + + + When performing an HTTP retrieval, include the HTTP message + header in the saved file. This is only really useful for + testing. + + + + + + + + Files + + + ~/.wmgetrc + + + The (optional) configuration file for the wmget dockapp. + Settings in this file are used to specify defaults for the + dockapp when it starts; see the section on configuration and + command-line options for more details. + + + + + + ~/.wmget.iq + + + A Unix-domain socket created by the wmget dockapp to accept + requests from wmget commands. Created at startup + automatically. + + + + + + + + + + + + + + + + + + + + diff --git a/wmget/wmget.xpm b/wmget/wmget.xpm new file mode 100644 index 0000000..78358f3 --- /dev/null +++ b/wmget/wmget.xpm @@ -0,0 +1,129 @@ +/* XPM */ +static char * wmget_xpm[] = { +"240 120 6 1", +" c None", +". c #202020", +"+ c #000000", +"@ c #C7C3C7", +"# c #20B2AE", +"$ c #666666", +" . ", +" . ", +" . ", +" . ", +" ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ . ", +" +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ . ", +" +@########################################################+@ . ", +" +@########################################################+@ . ", +" +@########################################################+@ . ", +" +@########################################################+@ . ", +" +@########################################################+@ . ", +" +@########################################################+@ . ", +" +@########################################################+@ . ", +" +@+++++++++++++++++++++++++++++++++++++++++++++++++++++++++@ . ", +" +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ . ", +" . ", +" . ", +" . ", +" . ", +" ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ . ", +" +..........................................................@ . ", +" +..........................................................@ . ", +" +..........................................................@ . ", +" +..........................................................@ . ", +" +..........................................................@ . ", +" +..........................................................@ . ", +" +..........................................................@ . ", +" +..........................................................@ . ", +" +..........................................................@ . ", +" +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ . ", +" . ", +" . ", +" . ", +" . ", +" ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ . ", +" +..........................................................@ . ", +" +..........................................................@ . ", +" +..........................................................@ . @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@ ", +" +..........................................................@ . @########################################################+ @$$$$$$$$$$$$$$$+ @$$$$$$$$$$$$$$$+ ", +" +..........................................................@ . @########################################################+ @$$$$$$++$++$$$$$$+@$$$$$$+++++$$$$$$+ ", +" +..........................................................@ . @########################################################+ @$$$$$$+@$+@$$$$$$+@$$$$$$+$$$@$$$$$$+ ", +" +..........................................................@ . @########################################################+ @$$$$$$+@$+@$$$$$$+@$$$$$$+$$$@$$$$$$+ ", +" +..........................................................@ . @########################################################+ @$$$$$$+@$+@$$$$$$+@$$$$$$+$$$@$$$$$$+ ", +" +..........................................................@ . @########################################################+ @$$$$$$@@$@@$$$$$$+@$$$$$$@@@@@$$$$$$+ ", +" +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ . @########################################################+ +$$$$$$$$$$$$$$$+ +$$$$$$$$$$$$$$$+ ", +" . @+++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++ +++++++++++++++ ", +" . ", +" . .......................................................... ", +" . .......................................................... ", +" ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ . .......................................................... ", +" +..........................................................@ . .......................................................... ", +" +..........................................................@ . .......................................................... ", +" +..........................................................@ . .......................................................... ", +" +..........................................................@ . .......................................................... ", +" +..........................................................@ . .......................................................... ", +" +..........................................................@ . .......................................................... ", +" +..........................................................@ . ", +" +..........................................................@ . ", +" +..........................................................@ . ", +" +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ . ", +" . ", +" . ", +" . ", +" . ", +"................................................................. ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +"+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ", +"+++#+++####+++###++####++#####+#####++###++#+++#++###++++###+#+++#+#+++++#+++#+#+++#++###++####+++###++####+++###++#####+#+++#+#+++#+#+++#+#+++#+#+++#+#####+ ", +"++#+#+++#++#+#+++#++#++#+#+++++#+++++#+++#+#+++#+++#+++++++#+#++#++#+++++#+++#+#+++#+#+++#+#+++#+#+++#+#+++#+#+++#+++#+++#+++#+#+++#+#+++#+#+++#+#+++#+++++#+ ", +"+#+++#++#++#+#++++++#++#+#+++++#+++++#+++++#+++#+++#+++++++#+#+#+++#+++++##+##+##++#+#+++#+#+++#+#+++#+#+++#+#+++++++#+++#+++#+#+++#+#+++#++#+#+++#+#+++++#++ ", +"+#+++#++###++#++++++#++#+####++####++#+++++#####+++#+++++++#+##++++#+++++#+#+#+#+#+#+#+++#+####++#+++#+####+++###++++#+++#+++#++#+#++#+#+#+++#+++++#+++++#+++ ", +"+#####++#++#+#++++++#++#+#+++++#+++++#++##+#+++#+++#+++++++#+#+#+++#+++++#+++#+#++##+#+++#+#+++++#+#+#+#+#+++++++#+++#+++#+++#++#+#++#+#+#++#+#++++#++++#++++ ", +"+#+++#++#++#+#+++#++#++#+#+++++#+++++#+++#+#+++#+++#+++#+++#+#++#++#+++++#+++#+#+++#+#+++#+#+++++#++#++#++#++#+++#+++#+++#+++#++#+#++##+##+#+++#+++#+++#+++++ ", +"+#+++#+####+++###++####++#####+#++++++###++#+++#++###+++###++#+++#+#####+#+++#+#+++#++###++#++++++##+#+#+++#++###++++#++++###++++#+++#+++#+#+++#+++#+++#####+ ", +"+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ", +" ", +"+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ", +"+++++++#+++++++++++++++#+++++++++##++++++++#+++++++#++++++#++#++++++##++++++++++++++++++++++++++++++++++++++++++++++#++++++++++++++++++++++++++++++++++++++++ ", +"+++++++#+++++++++++++++#++++++++#++#+++++++#+++++++++++++++++#+++++++#++++++++++++++++++++++++++++++++++++++++++++++#++++++++++++++++++++++++++++++++++++++++ ", +"++###++#+##+++###+++##+#++###+++#+++++##+#+#+##+++##+++++##++#+++#+++#+++##+#++#+##+++###++#+##+++##+#+#+##+++###++####++#+++#+#+++#+#+++#+#+++#+#+++#+#####+ ", +"+++++#+##++#+#+++#+#++##+#+++#+####++#++#++##++#+++#++++++#++#++#++++#+++#+#+#+##++#+#+++#+##++#+#++##+##++#+#++++++#++++#+++#+#+++#+#+++#++#+#++#+++#++++#++ ", +"++####+#+++#+#+++++#+++#+#####++#+++++##+++#+++#+++#+++#++#++###+++++#+++#+#+#+#+++#+#+++#+##++#+#++##+#++++++###+++#++++#+++#++#+#++#+#+#+++#++++####+++#+++ ", +"+#+++#+##++#+#+++#+#++##+#++++++#+++++++#++#+++#+++#+++#++#++#++#++++#+++#+#+#+#+++#+#+++#+#+##+++##+#+#+++++++++#++#++#+#++##++#+#++#+#+#++#+#++++++#++#++++ ", +"++####+#+##+++###+++##+#++###+++#++++###+++#+++#++###+++##+++#+++#++###++#+++#+#+++#++###++#+++++++++#+#+++++####++++##+++##+#+++#++++#+#++#+++#++###++#####+ ", +"+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ", +" ", +"+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ", +"+++++++++#++++#+#+++#+#++++#++++#++#++#++++++##+++++#+++#++++++++++++++++++++++++++++++++++++++#+++#+++++#++++###++#####++++#++#####+++##++#####++###+++###++ ", +"+++++++++#++++#+#+++#+#+++###++#+#+#+#+#+++++#+++++#+++++#+++#+++#+++#+++++++++++++++++++++++++#++#+#+++##+++#+++#+++++#+++##++#++++++#++++++++#+#+++#+#+++#+ ", +"+++++++++#++++#+#++#####+#+#++++#+#++#+#++++#+++++#+++++++#+++#+#++++#++++++++++++++++++++++++#++#+++#+#+#+++++++#++++#+++#+#++#+##++#++++++++#++#+++#+#++##+ ", +"+++++++++#++++++++++#+#+++###++++#++++#+++++++++++#+++++++#++#####+#####+++++++#####+++++++++#+++#+++#+++#+++++##++++##++#++#++##++#+#+##+++++#+++###+++##+#+ ", +"+++++++++#+++++++++#####+++#+#++#+#++#+#+#++++++++#+++++++#+++#+#++++#+++++##++++++++++#++++#++++#+++#+++#++++#++++++++#+#####+++++#+##++#+++#+++#+++#+++++#+ ", +"++++++++++++++++++++#+#+++###++#+#+#+#++#++++++++++#+++++#+++#+++#+++#+++++#++++++++++###++#++++++#+#++++#+++#+++++#+++#++++#++#+++#+#+++#++#++++#+++#++++#++ ", +"+++++++++#++++++++++#+#++++#+++#++#+++##+#++++++++++#+++#+++++++++++++++++#++++++++++++#+++#+++++++#+++#####+#####++###+++++#+++###+++###+++#+++++###+++##+++ ", +"+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ", +" ", +" ", +" ", +" ", +" ", +" ", +" "};