svn update: 60286 (latest:60286)
[profile/ivi/ecore.git] / src / lib / ecore_file / ecore_file_download.c
1 #ifdef HAVE_CONFIG_H
2 # include <config.h>
3 #endif
4
5 #include <stdio.h>
6 #include <string.h>
7
8 #ifdef BUILD_ECORE_CON
9 # include "Ecore_Con.h"
10 #endif
11
12 #include "ecore_file_private.h"
13
14 #ifdef BUILD_ECORE_CON
15
16 #define ECORE_MAGIC_FILE_DOWNLOAD_JOB 0xf7427cb8
17
18 struct _Ecore_File_Download_Job
19 {
20    ECORE_MAGIC;
21
22    Ecore_Con_Url        *url_con;
23    FILE                 *file;
24
25    char                 *dst;
26
27    Ecore_File_Download_Completion_Cb completion_cb;
28    Ecore_File_Download_Progress_Cb progress_cb;
29 };
30
31 #ifdef HAVE_CURL
32 Ecore_File_Download_Job *_ecore_file_download_curl(const char *url, const char *dst,
33                                                    Ecore_File_Download_Completion_Cb completion_cb,
34                                                    Ecore_File_Download_Progress_Cb progress_cb,
35                                                    void *data,
36                                                    Eina_Hash *headers);
37
38 static Eina_Bool _ecore_file_download_url_complete_cb(void *data, int type, void *event);
39 static Eina_Bool _ecore_file_download_url_progress_cb(void *data, int type, void *event);
40 #endif
41
42 static Ecore_Event_Handler *_url_complete_handler = NULL;
43 static Ecore_Event_Handler *_url_progress_download = NULL;
44 static Eina_List           *_job_list;
45
46 #endif /* BUILD_ECORE_CON */
47
48 int
49 ecore_file_download_init(void)
50 {
51 #ifdef BUILD_ECORE_CON
52   if (!ecore_con_url_init())
53     return 0;
54
55 # ifdef HAVE_CURL
56   _url_complete_handler = ecore_event_handler_add(ECORE_CON_EVENT_URL_COMPLETE, _ecore_file_download_url_complete_cb, NULL);
57   _url_progress_download = ecore_event_handler_add(ECORE_CON_EVENT_URL_PROGRESS, _ecore_file_download_url_progress_cb, NULL);
58 # endif
59
60 #endif /* BUILD_ECORE_CON */
61
62    return 1;
63 }
64
65 void
66 ecore_file_download_shutdown(void)
67 {
68 #ifdef BUILD_ECORE_CON
69   if (_url_complete_handler)
70     ecore_event_handler_del(_url_complete_handler);
71   if (_url_progress_download)
72     ecore_event_handler_del(_url_progress_download);
73   _url_complete_handler = NULL;
74   _url_progress_download = NULL;
75   ecore_file_download_abort_all();
76
77   ecore_con_url_shutdown();
78 #endif /* BUILD_ECORE_CON */
79 }
80
81 #ifdef BUILD_ECORE_CON
82 # ifdef HAVE_CURL
83 static Eina_Bool
84 _ecore_file_download_headers_foreach_cb(const Eina_Hash *hash __UNUSED__, const void *key, void *data, void *fdata)
85 {
86    Ecore_File_Download_Job *job = fdata;
87    ecore_con_url_additional_header_add(job->url_con, key, data);
88
89    return EINA_TRUE;
90 }
91 # endif
92 #endif
93
94 static Eina_Bool
95 _ecore_file_download(const char *url,
96                      const char *dst,
97                      Ecore_File_Download_Completion_Cb completion_cb,
98                      Ecore_File_Download_Progress_Cb progress_cb,
99                      void *data,
100                      Ecore_File_Download_Job **job_ret,
101                      Eina_Hash *headers)
102 {
103 #ifdef BUILD_ECORE_CON
104    char *dir = ecore_file_dir_get(dst);
105
106    if (!ecore_file_is_dir(dir))
107      {
108         EINA_LOG_ERR("%s is not a directory", dir);
109         free(dir);
110         return EINA_FALSE;
111      }
112    free(dir);
113    if (ecore_file_exists(dst))
114      {
115         EINA_LOG_ERR("%s already exists", dst);
116         return EINA_FALSE;
117      }
118
119    if (!strncmp(url, "file://", 7))
120      {
121         /* FIXME: Maybe fork? Might take a while to copy.
122          * Check filesize? */
123         /* Just copy it */
124
125         url += 7;
126         /* skip hostname */
127         url = strchr(url, '/');
128         return ecore_file_cp(url, dst);
129      }
130 # ifdef HAVE_CURL
131    else if ((!strncmp(url, "http://", 7)) || (!strncmp(url, "https://", 8)) ||
132             (!strncmp(url, "ftp://", 6)))
133      {
134         /* download */
135         Ecore_File_Download_Job *job;
136
137         job = _ecore_file_download_curl(url, dst, completion_cb, progress_cb, data, headers);
138         if(job_ret) *job_ret = job;
139         if(job)
140           return EINA_TRUE;
141         else
142           {
143              EINA_LOG_ERR("no job returned\n");
144              return EINA_FALSE;
145           }
146         return job ? EINA_TRUE : EINA_FALSE;
147      }
148 # else
149    else if ((!strncmp(url, "http://", 7)) || (!strncmp(url, "https://", 8)) ||
150             (!strncmp(url, "ftp://", 6)))
151      {
152         (void)completion_cb;
153         (void)progress_cb;
154         (void)data;
155         (void)job_ret;
156         return EINA_FALSE;
157      }
158 # endif
159    else
160      {
161         return EINA_FALSE;
162      }
163 #else
164    completion_cb = NULL;
165    progress_cb = NULL;
166    data = NULL;
167    return EINA_FALSE;
168 #endif /* BUILD_ECORE_CON */
169 }
170
171 /**
172  * @addtogroup Ecore_File_Group Ecore_File - Files and directories convenience functions
173  *
174  * @{
175  */
176
177 /**
178  * @brief Download the given url to the given destination.
179  *
180  * @param  url The complete url to download.
181  * @param  dst The local file to save the downloaded to.
182  * @param  completion_cb A callback called on download complete.
183  * @param  progress_cb A callback called during the download operation.
184  * @param  data User data passed to both callbacks.
185  * @param  job_ret Job used to abort the download.
186  * @return EINA_TRUE if the download start or EINA_FALSE on failure
187  *
188  * This function starts the download of the URL @p url and saves it to
189  * @p dst. @p url must provide the protocol, including 'http://',
190  * 'ftp://' or 'file://'. Ecore_File must be compiled with CURL to
191  * download using http and ftp protocols. If @p dst is ill-formed, or
192  * if it already exists, the function returns EINA_FALSE. When the
193  * download is complete, the callback @p completion_cb is called and
194  * @p data is passed to it. The @p status parameter of @p completion_cb
195  * will be filled with the status of the download (200, 404,...). The
196  * @p progress_cb is called during the download operation, each time a
197  * packet is received or when CURL wants. It can be used to display the
198  * percentage of the downloaded file. Return 0 from this callback, if provided,
199  * to continue the operation or anything else to abort the download. The only
200  * operations that can be aborted are those with protocol 'http' or 'ftp'. In
201  * that case @p job_ret can be filled. It can be used with
202  * ecore_file_download_abort() or ecore_file_download_abort_all() to
203  * respectively abort one or all download operations. This function returns
204  * EINA_TRUE if the download starts, EINA_FALSE otherwise.
205  */
206 EAPI Eina_Bool
207 ecore_file_download(const char *url,
208                     const char *dst,
209                     Ecore_File_Download_Completion_Cb completion_cb,
210                     Ecore_File_Download_Progress_Cb progress_cb,
211                     void *data,
212                     Ecore_File_Download_Job **job_ret)
213 {
214    return _ecore_file_download(url, dst, completion_cb, progress_cb, data, job_ret, NULL);
215 }
216
217 /**
218  * @brief Download the given url to the given destination with additional headers.
219  *
220  * @param  url The complete url to download.
221  * @param  dst The local file to save the downloaded to.
222  * @param  completion_cb A callback called on download complete.
223  * @param  progress_cb A callback called during the download operation.
224  * @param  data User data passed to both callbacks.
225  * @param  job_ret Job used to abort the download.
226  * @param  headers pointer of header lists.
227  * @return EINA_TRUE if the download start or EINA_FALSE on failure
228  */
229 EAPI Eina_Bool
230 ecore_file_download_full(const char *url,
231                          const char *dst,
232                          Ecore_File_Download_Completion_Cb completion_cb,
233                          Ecore_File_Download_Progress_Cb progress_cb,
234                          void *data,
235                          Ecore_File_Download_Job **job_ret,
236                          Eina_Hash *headers)
237 {
238    return _ecore_file_download(url, dst, completion_cb, progress_cb, data, job_ret, headers);
239 }
240
241 /**
242  * @brief Check if the given protocol is available.
243  *
244  * @param  protocol The protocol to check.
245  * @return EINA_TRUE if protocol is handled, EINA_FALSE otherwise.
246  *
247  * This function returns EINA_TRUE if @p protocol is supported,
248  * EINA_FALSE otherwise. @p protocol can be 'http://', 'ftp://' or
249  * 'file://'. Ecore_FILE must be compiled with CURL to handle http and
250  * ftp protocols.
251  */
252 EAPI Eina_Bool
253 ecore_file_download_protocol_available(const char *protocol)
254 {
255 #ifdef BUILD_ECORE_CON
256    if (!strncmp(protocol, "file://", 7)) return EINA_TRUE;
257 # ifdef HAVE_CURL
258    else if (!strncmp(protocol, "http://", 7)) return EINA_TRUE;
259    else if (!strncmp(protocol, "ftp://", 6)) return EINA_TRUE;
260 # endif
261 #endif /* BUILD_ECORE_CON */
262
263    return EINA_FALSE;
264 }
265
266 #ifdef BUILD_ECORE_CON
267
268 # ifdef HAVE_CURL
269 static int
270 _ecore_file_download_url_compare_job(const void *data1, const void *data2)
271 {
272    const Ecore_File_Download_Job *job = data1;
273    const Ecore_Con_Url           *url = data2;
274
275    if (job->url_con == url) return 0;
276    return -1;
277 }
278
279 static Eina_Bool
280 _ecore_file_download_url_complete_cb(void *data __UNUSED__, int type __UNUSED__, void *event)
281 {
282    Ecore_Con_Event_Url_Complete *ev = event;
283    Ecore_File_Download_Job      *job;
284
285    job = eina_list_search_unsorted(_job_list, _ecore_file_download_url_compare_job, ev->url_con);
286    if (!ECORE_MAGIC_CHECK(job, ECORE_MAGIC_FILE_DOWNLOAD_JOB)) return ECORE_CALLBACK_PASS_ON;
287
288    if (job->completion_cb)
289      job->completion_cb(ecore_con_url_data_get(job->url_con), job->dst, !ev->status);
290
291    _job_list = eina_list_remove(_job_list, job);
292    fclose(job->file);
293    free(job->dst);
294    ecore_con_url_free(job->url_con);
295    free(job);
296
297    return ECORE_CALLBACK_DONE;
298 }
299
300 static Eina_Bool
301 _ecore_file_download_url_progress_cb(void *data __UNUSED__, int type __UNUSED__, void *event)
302 {
303 /* this reports the downloads progress. if we return 0, then download
304  * continues, if we return anything else, then the download stops */
305    Ecore_Con_Event_Url_Progress *ev = event;
306    Ecore_File_Download_Job      *job;
307
308    job = eina_list_search_unsorted(_job_list, _ecore_file_download_url_compare_job, ev->url_con);
309    if (!ECORE_MAGIC_CHECK(job, ECORE_MAGIC_FILE_DOWNLOAD_JOB)) return ECORE_CALLBACK_PASS_ON;
310
311    if (job->progress_cb)
312      if (job->progress_cb(ecore_con_url_data_get(job->url_con), job->dst,
313                           (long int) ev->down.total, (long int) ev->down.now,
314                           (long int) ev->up.total, (long int) ev->up.now) != 0)
315        {
316           _job_list = eina_list_remove(_job_list, job);
317           fclose(job->file);
318           free(job->dst);
319           free(job);
320
321           return ECORE_CALLBACK_PASS_ON;
322        }
323
324    return ECORE_CALLBACK_DONE;
325 }
326
327 Ecore_File_Download_Job *
328 _ecore_file_download_curl(const char *url, const char *dst,
329                           Ecore_File_Download_Completion_Cb completion_cb,
330                           Ecore_File_Download_Progress_Cb progress_cb,
331                           void *data,
332                           Eina_Hash *headers)
333 {
334    Ecore_File_Download_Job *job;
335
336    job = calloc(1, sizeof(Ecore_File_Download_Job));
337    if (!job) return NULL;
338
339    ECORE_MAGIC_SET(job, ECORE_MAGIC_FILE_DOWNLOAD_JOB);
340
341    job->file = fopen(dst, "wb");
342    if (!job->file)
343      {
344         free(job);
345         return NULL;
346      }
347    job->url_con = ecore_con_url_new(url);
348    if (!job->url_con)
349      {
350         fclose(job->file);
351         free(job);
352         return NULL;
353      }
354
355    if (headers) eina_hash_foreach(headers, _ecore_file_download_headers_foreach_cb, job);
356    ecore_con_url_fd_set(job->url_con, fileno(job->file));
357    ecore_con_url_data_set(job->url_con, data);
358
359    job->dst = strdup(dst);
360
361    job->completion_cb = completion_cb;
362    job->progress_cb = progress_cb;
363    _job_list = eina_list_append(_job_list, job);
364
365    ecore_con_url_get(job->url_con);
366
367    return job;
368 }
369 # endif
370 #endif
371
372 /**
373  * @brief Abort the given download job and call the completion_cb
374  * callbck with a status of 1 (error).
375  *
376  * @param job The download job to abort.
377  *
378  * This function aborts a download operation started by
379  * ecore_file_download(). @p job is the #Ecore_File_Download_Job
380  * structure filled by ecore_file_download(). If it is @c NULL, this
381  * function does nothing. To abort all the currently downloading
382  * operations, call ecore_file_download_abort_all().
383  */
384 EAPI void
385 ecore_file_download_abort(Ecore_File_Download_Job *job)
386 {
387    if (!job)
388      return;
389
390 #ifdef BUILD_ECORE_CON
391    if (job->completion_cb)
392      job->completion_cb(ecore_con_url_data_get(job->url_con), job->dst, 1);
393 # ifdef HAVE_CURL
394    ecore_con_url_free(job->url_con);
395 # endif
396    _job_list = eina_list_remove(_job_list, job);
397    fclose(job->file);
398    free(job->dst);
399    free(job);
400 #endif /* BUILD_ECORE_CON */
401 }
402
403 /**
404  * @brief Abort all downloads.
405  *
406  * This function aborts all the downloads that have been started by
407  * ecore_file_download(). It loops over the started downloads and call
408  * ecore_file_download_abort() for each of them. To abort only one
409  * specific download operation, call ecore_file_download_abort().
410  */
411 EAPI void
412 ecore_file_download_abort_all(void)
413 {
414 #ifdef BUILD_ECORE_CON
415    Ecore_File_Download_Job *job;
416
417    EINA_LIST_FREE(_job_list, job)
418              ecore_file_download_abort(job);
419 #endif /* BUILD_ECORE_CON */
420 }
421
422 /**
423  * @}
424  */