#include "config.h" /* Copyright (C) 2002 Brad Jorsch 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 #include #include #include #include #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; }