283 lines
8.5 KiB
C
283 lines
8.5 KiB
C
#include "config.h"
|
|
|
|
/* Copyright (C) 2002 Brad Jorsch <anomie@users.sourceforge.net>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <curl/curl.h>
|
|
|
|
#include "die.h"
|
|
#include "download.h"
|
|
|
|
static const char *user_agent = "wmweather+/" VERSION;
|
|
|
|
static CURLM *multi_handle=NULL;
|
|
struct download_info {
|
|
CURL *handle;
|
|
FILE *fp;
|
|
void (*callback)(char *filename, void *data);
|
|
char *filename;
|
|
void *data;
|
|
int flags;
|
|
struct download_info *next;
|
|
struct download_info *prev;
|
|
};
|
|
static struct download_info *active_list=NULL;
|
|
|
|
static void add_active(struct download_info *d){
|
|
d->next=active_list;
|
|
d->prev=NULL;
|
|
if(active_list!=NULL) active_list->prev=d;
|
|
active_list=d;
|
|
}
|
|
|
|
static void remove_active(struct download_info *d){
|
|
if(active_list==d) active_list=d->next;
|
|
if(d->prev!=NULL) d->prev->next=d->next;
|
|
if(d->next!=NULL) d->next->prev=d->prev;
|
|
d->next=NULL;
|
|
d->prev=NULL;
|
|
}
|
|
|
|
static struct download_info *find_active_file(char *f){
|
|
struct download_info *d;
|
|
for(d=active_list; d!=NULL; d=d->next){
|
|
if(!strcmp(d->filename,f)) return d;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void handle_done(CURLMsg *msg){
|
|
struct download_info *info;
|
|
long status;
|
|
|
|
if(curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &info)!=CURLE_OK || info==NULL){
|
|
warn("Could not retrieve info handle from CURL handle. WTF?");
|
|
for(info=active_list; info && info->handle!=msg->easy_handle; info=info->next);
|
|
if(info==NULL){
|
|
warn("Could not find it in the active list either. WTF?");
|
|
curl_multi_remove_handle(multi_handle, msg->easy_handle);
|
|
curl_easy_cleanup(msg->easy_handle);
|
|
return;
|
|
}
|
|
}
|
|
|
|
remove_active(info);
|
|
curl_multi_remove_handle(multi_handle, info->handle);
|
|
fclose(info->fp);
|
|
|
|
if(msg->data.result!=CURLE_OK){
|
|
if(msg->data.result==CURLE_HTTP_RETURNED_ERROR){
|
|
if(curl_easy_getinfo(info->handle, CURLINFO_RESPONSE_CODE, &status)!=CURLE_OK) status=600;
|
|
if(status!=404 || !(info->flags&DOWNLOAD_NO_404))
|
|
warn("HTTP download of %s returned %ld", info->filename, status);
|
|
} else {
|
|
warn("Download of %s failed: %s", info->filename, curl_easy_strerror(msg->data.result));
|
|
}
|
|
unlink(info->filename);
|
|
} else {
|
|
(*info->callback)(info->filename, info->data);
|
|
}
|
|
|
|
curl_easy_cleanup(info->handle);
|
|
free(info->filename);
|
|
free(info);
|
|
}
|
|
|
|
|
|
void download_init(char *email){
|
|
if(multi_handle==NULL){
|
|
if(curl_global_init(CURL_GLOBAL_ALL))
|
|
die("Could not initialize CURL");
|
|
multi_handle=curl_multi_init();
|
|
if(multi_handle==NULL) die("Could not create a CURL multihandle");
|
|
}
|
|
}
|
|
|
|
void download_process(unsigned long sleeptime){
|
|
fd_set rd, wr, er;
|
|
struct timeval tv;
|
|
int maxfd, n, x;
|
|
CURLMsg *msg;
|
|
CURLMcode status;
|
|
|
|
if(sleeptime>0) do {
|
|
FD_ZERO(&rd);
|
|
FD_ZERO(&wr);
|
|
FD_ZERO(&er);
|
|
errno = 0;
|
|
status = curl_multi_fdset(multi_handle, &rd, &wr, &er, &maxfd);
|
|
if(status!=CURLM_OK){
|
|
warn("Could not fetch curl fdset: %s", curl_multi_strerror(status));
|
|
usleep(sleeptime);
|
|
return;
|
|
}
|
|
if(maxfd<0){
|
|
/* It's not something we can select on, sleep and then blindly call
|
|
* curl_multi_perform.
|
|
*/
|
|
usleep(sleeptime);
|
|
break;
|
|
}
|
|
|
|
tv.tv_sec=0;
|
|
tv.tv_usec=sleeptime;
|
|
if(sleeptime>=1000000){
|
|
tv.tv_sec=sleeptime/1000000;
|
|
tv.tv_usec=sleeptime%1000000;
|
|
}
|
|
|
|
n=select(maxfd+1, &rd, &wr, &er, &tv);
|
|
if(n==0) return;
|
|
if(n<0){
|
|
switch(errno){
|
|
case EINTR:
|
|
case ENOMEM:
|
|
/* transient errors, hope it's good next time */
|
|
break;
|
|
|
|
default:
|
|
warn("WTF? select errno=%d", errno);
|
|
break;
|
|
}
|
|
usleep(sleeptime);
|
|
return;
|
|
}
|
|
} while(0);
|
|
|
|
while(curl_multi_perform(multi_handle, &x)==CURLM_CALL_MULTI_PERFORM);
|
|
while((msg=curl_multi_info_read(multi_handle, &x))){
|
|
switch(msg->msg){
|
|
case CURLMSG_DONE:
|
|
handle_done(msg);
|
|
break;
|
|
default:
|
|
warn("Unknown CURL message type %d", msg->msg);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
int download_kill(char *filename){
|
|
struct download_info *info;
|
|
info=find_active_file(filename);
|
|
if(info==NULL) return ENOENT;
|
|
remove_active(info);
|
|
curl_multi_remove_handle(multi_handle, info->handle);
|
|
warn("Download of %s interrupted", info->filename);
|
|
unlink(info->filename);
|
|
curl_easy_cleanup(info->handle);
|
|
free(info->filename);
|
|
fclose(info->fp);
|
|
free(info);
|
|
return 0;
|
|
}
|
|
|
|
int download_file(char *filename, char *from_addr, char *postdata, int flags, void (*callback)(char *filename, void *data), void *data){
|
|
struct download_info *info=NULL;
|
|
|
|
if(callback==NULL || filename==NULL || from_addr==NULL) return 1;
|
|
|
|
if(flags&DOWNLOAD_KILL_OTHER_REQUESTS){
|
|
download_kill(filename);
|
|
} else {
|
|
info=find_active_file(filename);
|
|
if(info!=NULL){
|
|
errno=0;
|
|
warn("Cannot download %s: download already in progress", filename);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if((info=malloc(sizeof(*info)))==NULL){
|
|
warn("Malloc error in download_file");
|
|
goto fail;
|
|
}
|
|
info->handle=NULL;
|
|
info->fp=NULL;
|
|
info->callback=callback;
|
|
info->filename=NULL;
|
|
info->data=data;
|
|
info->flags=flags;
|
|
info->next=NULL;
|
|
info->prev=NULL;
|
|
|
|
if((info->filename=strdup(filename))==NULL) goto fail;
|
|
if((info->fp=fopen(info->filename, "wb"))==NULL){
|
|
warn("Error opening %s for output", info->filename);
|
|
goto fail;
|
|
}
|
|
|
|
info->handle=curl_easy_init();
|
|
if(info->handle==NULL){
|
|
warn("Error creating a CURL handle");
|
|
goto fail;
|
|
}
|
|
if(curl_easy_setopt(info->handle, CURLOPT_URL, from_addr)!=CURLE_OK ||
|
|
curl_easy_setopt(info->handle, CURLOPT_NOPROGRESS, 1)!=CURLE_OK ||
|
|
curl_easy_setopt(info->handle, CURLOPT_NOSIGNAL, 1)!=CURLE_OK ||
|
|
curl_easy_setopt(info->handle, CURLOPT_WRITEDATA, info->fp)!=CURLE_OK ||
|
|
curl_easy_setopt(info->handle, CURLOPT_FAILONERROR, 1)!=CURLE_OK ||
|
|
curl_easy_setopt(info->handle, CURLOPT_AUTOREFERER, 1)!=CURLE_OK ||
|
|
curl_easy_setopt(info->handle, CURLOPT_FOLLOWLOCATION, 1)!=CURLE_OK ||
|
|
curl_easy_setopt(info->handle, CURLOPT_USERAGENT, user_agent)!=CURLE_OK ||
|
|
curl_easy_setopt(info->handle, CURLOPT_TIMEOUT, 10*60)!=CURLE_OK ||
|
|
curl_easy_setopt(info->handle, CURLOPT_PRIVATE, info)!=CURLE_OK
|
|
){
|
|
warn("Error setting CURL options");
|
|
goto fail;
|
|
}
|
|
if(postdata!=NULL){
|
|
if(curl_easy_setopt(info->handle, CURLOPT_COPYPOSTFIELDS, postdata)!=CURLE_OK){
|
|
warn("Error setting CURL post options");
|
|
goto fail;
|
|
}
|
|
}
|
|
if(curl_multi_add_handle(multi_handle, info->handle)!=CURLM_OK){
|
|
warn("Could not add handle for %s to multihandle", info->filename);
|
|
goto fail;
|
|
}
|
|
add_active(info);
|
|
|
|
/* Call download_process with 0 to force at least one call to
|
|
* curl_multi_process, because curl won't actually create a socket until
|
|
* that function is called and download_process won't otherwise call
|
|
* curl_multi_process until the socket is created...
|
|
*/
|
|
download_process(0);
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
if(info){
|
|
if(info->handle) curl_easy_cleanup(info->handle);
|
|
info->handle=NULL;
|
|
if(info->fp) fclose(info->fp);
|
|
info->fp=NULL;
|
|
if(info->filename){
|
|
unlink(info->filename);
|
|
free(info->filename);
|
|
}
|
|
info->filename=NULL;
|
|
free(info);
|
|
}
|
|
return 1;
|
|
}
|