/* * vim:ts=8:sw=3:sts=8:noexpandtab:cino=>5n-3f0^-2{2 */ #include "ecore_file_private.h" static int init = 0; #ifdef HAVE_CURL #include typedef struct _Ecore_File_Download_Job Ecore_File_Download_Job; struct _Ecore_File_Download_Job { Ecore_Fd_Handler *fd_handler; CURL *curl; void (*completion_cb)(void *data, const char *file, int status); int (*progress_cb)(void *data, const char *file, long int dltotal, long int dlnow, long int ultotal, long int ulnow); void *data; FILE *file; char *dst; }; Ecore_File_Download_Job *_ecore_file_download_curl(const char *url, const char *dst, void (*completion_cb)(void *data, const char *file, int status), int (*progress_cb)(void *data, const char *file, long int dltotal, long int dlnow, long int ultotal, long int ulnow), void *data); static int _ecore_file_download_curl_fd_handler(void *data, Ecore_Fd_Handler *fd_handler); static CURLM *curlm; static Ecore_List *_job_list; static fd_set _current_fd_set; #endif int ecore_file_download_init(void) { if (++init != 1) return init; #ifdef HAVE_CURL FD_ZERO(&_current_fd_set); _job_list = ecore_list_new(); if (!_job_list) return --init; if (curl_global_init(CURL_GLOBAL_NOTHING)) return 0; curlm = curl_multi_init(); if (!curlm) { ecore_list_destroy(_job_list); _job_list = NULL; return --init; } #endif return init; } int ecore_file_download_shutdown(void) { if (--init != 0) return init; #ifdef HAVE_CURL Ecore_File_Download_Job *job; if (!ecore_list_empty_is(_job_list)) { ecore_list_first_goto(_job_list); while ((job = ecore_list_next(_job_list))) { ecore_main_fd_handler_del(job->fd_handler); curl_multi_remove_handle(curlm, job->curl); curl_easy_cleanup(job->curl); fclose(job->file); free(job->dst); free(job); } } ecore_list_destroy(_job_list); curl_multi_cleanup(curlm); curl_global_cleanup(); #endif return init; } void ecore_file_download_abort_all(void) { #ifdef HAVE_CURL Ecore_File_Download_Job *job; if (!_job_list) return; ecore_list_first_goto(_job_list); while ((job = ecore_list_next(_job_list))) { ecore_main_fd_handler_del(job->fd_handler); curl_multi_remove_handle(curlm, job->curl); curl_easy_cleanup(job->curl); fclose(job->file); free(job->dst); free(job); } ecore_list_clear(_job_list); #endif } EAPI int ecore_file_download(const char *url, const char *dst, void (*completion_cb)(void *data, const char *file, int status), int (*progress_cb)(void *data, const char *file, long int dltotal, long int dlnow, long int ultotal, long int ulnow), void *data) { if (!ecore_file_is_dir(ecore_file_dir_get((char *)dst))) return 0; if (ecore_file_exists(dst)) return 0; /* FIXME: Add handlers for http and ftp! */ if (!strncmp(url, "file://", 7)) { /* FIXME: Maybe fork? Might take a while to copy. * Check filesize? */ /* Just copy it */ url += 7; /* skip hostname */ url = strchr(url, '/'); return ecore_file_cp(url, dst); } #ifdef HAVE_CURL else if ((!strncmp(url, "http://", 7)) || (!strncmp(url, "ftp://", 6))) { /* download */ Ecore_File_Download_Job *job; job = _ecore_file_download_curl(url, dst, completion_cb, progress_cb, data); if (job) return 1; else return 0; } #endif else { return 0; } #ifndef HAVE_CURL completion_cb = NULL; progress_cb = NULL; data = NULL; #endif } EAPI int ecore_file_download_protocol_available(const char *protocol) { if (!strncmp(protocol, "file://", 7)) return 1; #ifdef HAVE_CURL else if (!strncmp(protocol, "http://", 7)) return 1; else if (!strncmp(protocol, "ftp://", 6)) return 1; #endif return 0; } #ifdef HAVE_CURL /* this reports the downloads progress. if we return 0, then download * continues, if we return anything else, then the download stops */ static int _ecore_file_download_curl_progress_func(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow) { Ecore_File_Download_Job *job; job = clientp; if(job->progress_cb) return job->progress_cb(job->data, job->dst, (long int)dltotal, (long int)dlnow, (long int)ultotal, (long int)ulnow); return 0; } Ecore_File_Download_Job * _ecore_file_download_curl(const char *url, const char *dst, void (*completion_cb)(void *data, const char *file, int status), int (*progress_cb)(void *data, const char *file, long int dltotal, long int dlnow, long int ultotal, long int ulnow), void *data) { CURLMsg *curlmsg; fd_set read_set, write_set, exc_set; int fd_max; int fd; int flags; int n_remaining, still_running; Ecore_File_Download_Job *job; double start = 0.0; job = calloc(1, sizeof(Ecore_File_Download_Job)); if (!job) return NULL; job->file = fopen(dst, "wb"); if (!job->file) { free(job); return NULL; } job->curl = curl_easy_init(); if (!job->curl) { fclose(job->file); free(job); return NULL; } curl_easy_setopt(job->curl, CURLOPT_URL, url); curl_easy_setopt(job->curl, CURLOPT_WRITEDATA, job->file); if (progress_cb) { curl_easy_setopt(job->curl, CURLOPT_NOPROGRESS, FALSE); curl_easy_setopt(job->curl, CURLOPT_PROGRESSDATA, job); curl_easy_setopt(job->curl, CURLOPT_PROGRESSFUNCTION, _ecore_file_download_curl_progress_func); } job->data = data; job->completion_cb = completion_cb; job->progress_cb = progress_cb; job->dst = strdup(dst); ecore_list_append(_job_list, job); curl_multi_add_handle(curlm, job->curl); start = ecore_time_get(); while (curl_multi_perform(curlm, &still_running) == CURLM_CALL_MULTI_PERFORM) { /* make this 1/100th of a second to keep interactivity high. really * though this needs to somehow get the fd from curl and use an fd handler * and thus select */ if ((ecore_time_get() - start) > 0.01) break; } /* check for completed jobs */ while ((curlmsg = curl_multi_info_read(curlm, &n_remaining)) != NULL) { Ecore_File_Download_Job *current; if (curlmsg->msg != CURLMSG_DONE) continue; /* find the job which is done */ ecore_list_first_goto(_job_list); while ((current = ecore_list_current(_job_list))) { if (curlmsg->easy_handle == current->curl) { /* We have a match -- delete the job */ if (current == job) job = NULL; if (current->fd_handler) { FD_CLR(ecore_main_fd_handler_fd_get(current->fd_handler), &_current_fd_set); ecore_main_fd_handler_del(current->fd_handler); } ecore_list_remove(_job_list); curl_multi_remove_handle(curlm, current->curl); curl_easy_cleanup(current->curl); fclose(current->file); if (current->completion_cb) current->completion_cb(current->data, current->dst, curlmsg->data.result); free(current->dst); free(current); break; } ecore_list_next(_job_list); } } if (job) { FD_ZERO(&read_set); FD_ZERO(&write_set); FD_ZERO(&exc_set); /* Stupid curl, why can't I get the fd to the current added job? */ curl_multi_fdset(curlm, &read_set, &write_set, &exc_set, &fd_max); for (fd = 0; fd <= fd_max; fd++) { if (!FD_ISSET(fd, &_current_fd_set)) { flags = 0; if (FD_ISSET(fd, &read_set)) flags |= ECORE_FD_READ; if (FD_ISSET(fd, &write_set)) flags |= ECORE_FD_WRITE; if (FD_ISSET(fd, &exc_set)) flags |= ECORE_FD_ERROR; if (flags) { FD_SET(fd, &_current_fd_set); job->fd_handler = ecore_main_fd_handler_add(fd, flags, _ecore_file_download_curl_fd_handler, NULL, NULL, NULL); } } } if (!job->fd_handler) { curl_easy_cleanup(job->curl); fclose(job->file); free(job); job = NULL; } } return job; } static int _ecore_file_download_curl_fd_handler(void *data __UNUSED__, Ecore_Fd_Handler *fd_handler __UNUSED__) { Ecore_File_Download_Job *job; CURLMsg *curlmsg; int n_remaining, still_running; double start = 0.0; start = ecore_time_get(); while (curl_multi_perform(curlm, &still_running) == CURLM_CALL_MULTI_PERFORM) { if ((ecore_time_get() - start) > 0.2) break; } /* Loop jobs and check if any are done */ while ((curlmsg = curl_multi_info_read(curlm, &n_remaining)) != NULL) { if (curlmsg->msg != CURLMSG_DONE) continue; /* find the job which is done */ ecore_list_first_goto(_job_list); while ((job = ecore_list_current(_job_list))) { if (curlmsg->easy_handle == job->curl) { /* We have a match -- delete the job */ FD_CLR(ecore_main_fd_handler_fd_get(job->fd_handler), &_current_fd_set); ecore_list_remove(_job_list); ecore_main_fd_handler_del(job->fd_handler); curl_multi_remove_handle(curlm, job->curl); curl_easy_cleanup(job->curl); fclose(job->file); if (job->completion_cb) job->completion_cb(job->data, job->dst, !curlmsg->data.result); free(job->dst); free(job); break; } ecore_list_next(_job_list); } } return 1; } #endif