1 #ifdef HAVE_CONFIG_H
2 # include <config.h>
3 #endif
4 
5 #include <stdlib.h>
6 #include <stdio.h>
7 #include <string.h>
8 #include <fcntl.h>
9 
10 # include "Ecore_Con.h"
11 
12 #include "ecore_file_private.h"
13 
14 #define ECORE_MAGIC_FILE_DOWNLOAD_JOB 0xf7427cb8
15 #define ECORE_FILE_DOWNLOAD_TIMEOUT 30
16 
17 struct _Ecore_File_Download_Job
18 {
19    ECORE_MAGIC;
20 
21    Eo                   *input;
22    Eo                   *output;
23    Eo                   *copier;
24 
25    Ecore_File_Download_Completion_Cb completion_cb;
26    Ecore_File_Download_Progress_Cb progress_cb;
27    const void *data;
28 };
29 
30 static Eina_List           *_job_list;
31 static int download_init = 0;
32 
33 int
ecore_file_download_init(void)34 ecore_file_download_init(void)
35 {
36    download_init++;
37    if (download_init > 1) return 1;
38    if (!ecore_con_init())
39      {
40         download_init--;
41         return 0;
42      }
43    if (!ecore_con_url_init())
44      {
45         ecore_con_shutdown();
46         download_init--;
47         return 0;
48      }
49    return download_init;
50 }
51 
52 void
ecore_file_download_shutdown(void)53 ecore_file_download_shutdown(void)
54 {
55    download_init--;
56    if (download_init > 0) return;
57    ecore_file_download_abort_all();
58    ecore_con_url_shutdown();
59    ecore_con_shutdown();
60 }
61 
62 static void
_ecore_file_download_copier_done(void * data,const Efl_Event * event EINA_UNUSED)63 _ecore_file_download_copier_done(void *data, const Efl_Event *event EINA_UNUSED)
64 {
65    Ecore_File_Download_Job *job = data;
66    Efl_Net_Http_Status status = efl_net_dialer_http_response_status_get(job->input);
67    const char *file;
68 
69    file = efl_file_get(job->output);
70 
71    DBG("Finished downloading %s (status=%d) -> %s",
72        efl_net_dialer_address_dial_get(job->input),
73        status,
74        file);
75 
76    if (job->completion_cb)
77      {
78         Ecore_File_Download_Completion_Cb cb = job->completion_cb;
79         job->completion_cb = NULL;
80         ECORE_MAGIC_SET(job, ECORE_MAGIC_NONE);
81         cb((void *)job->data, file, status);
82      }
83 
84    efl_del(job->copier);
85 }
86 
87 static void
_ecore_file_download_copier_error(void * data,const Efl_Event * event)88 _ecore_file_download_copier_error(void *data, const Efl_Event *event)
89 {
90    Ecore_File_Download_Job *job = data;
91    Efl_Net_Http_Status status = efl_net_dialer_http_response_status_get(job->input);
92    Eina_Error *perr = event->info;
93    const char *file;
94 
95    file = efl_file_get(job->output);
96 
97    WRN("Failed downloading %s (status=%d) -> %s: %s",
98        efl_net_dialer_address_dial_get(job->input),
99        efl_net_dialer_http_response_status_get(job->input),
100        file,
101        eina_error_msg_get(*perr));
102 
103    if (job->completion_cb)
104      {
105         Ecore_File_Download_Completion_Cb cb = job->completion_cb;
106         job->completion_cb = NULL;
107 
108         if ((status < 500) || ((int)status > 599))
109           {
110              /* not an HTTP error, likely copier or output, use 500 */
111              status = 500;
112           }
113 
114         cb((void *)job->data, file, status);
115      }
116 
117    efl_del(job->copier);
118 }
119 
120 static void
_ecore_file_download_copier_progress(void * data,const Efl_Event * event EINA_UNUSED)121 _ecore_file_download_copier_progress(void *data, const Efl_Event *event EINA_UNUSED)
122 {
123    Ecore_File_Download_Job *job = data;
124    Ecore_File_Progress_Return ret;
125    uint64_t dn, dt, un, ut;
126    const char *file;
127 
128    if (!job->progress_cb) return;
129 
130    file = efl_file_get(job->output);
131    efl_net_dialer_http_progress_download_get(job->input, &dn, &dt);
132    efl_net_dialer_http_progress_upload_get(job->input, &un, &ut);
133    ret = job->progress_cb((void *)job->data, file, dt, dn, ut, un);
134 
135    if (ret == ECORE_FILE_PROGRESS_CONTINUE) return;
136 
137    ecore_file_download_abort(job);
138 }
139 
140 static void
_ecore_file_download_copier_del(void * data,const Efl_Event * event EINA_UNUSED)141 _ecore_file_download_copier_del(void *data, const Efl_Event *event EINA_UNUSED)
142 {
143    Ecore_File_Download_Job *job = data;
144 
145    ECORE_MAGIC_SET(job, ECORE_MAGIC_NONE);
146 
147    efl_del(job->input);
148    job->input = NULL;
149 
150    efl_del(job->output);
151    job->output = NULL;
152 
153    job->completion_cb = NULL;
154    job->progress_cb = NULL;
155    job->data = NULL;
156 
157    _job_list = eina_list_remove(_job_list, job);
158    free(job);
159 }
160 
161 EFL_CALLBACKS_ARRAY_DEFINE(ecore_file_download_copier_cbs,
162                            { EFL_IO_COPIER_EVENT_DONE, _ecore_file_download_copier_done },
163                            { EFL_IO_COPIER_EVENT_ERROR, _ecore_file_download_copier_error },
164                            { EFL_IO_COPIER_EVENT_PROGRESS, _ecore_file_download_copier_progress },
165                            { EFL_EVENT_DEL, _ecore_file_download_copier_del });
166 
167 static Eina_Bool
_ecore_file_download_headers_foreach_cb(const Eina_Hash * hash EINA_UNUSED,const void * key,void * data,void * fdata)168 _ecore_file_download_headers_foreach_cb(const Eina_Hash *hash EINA_UNUSED, const void *key, void *data, void *fdata)
169 {
170    Ecore_File_Download_Job *job = fdata;
171 
172    efl_net_dialer_http_request_header_add(job->input, key, data);
173 
174    return EINA_TRUE;
175 }
176 
177 EAPI Eina_Bool
ecore_file_download_full(const char * url,const char * dst,Ecore_File_Download_Completion_Cb completion_cb,Ecore_File_Download_Progress_Cb progress_cb,void * data,Ecore_File_Download_Job ** job_ret,Eina_Hash * headers)178 ecore_file_download_full(const char *url,
179                          const char *dst,
180                          Ecore_File_Download_Completion_Cb completion_cb,
181                          Ecore_File_Download_Progress_Cb progress_cb,
182                          void *data,
183                          Ecore_File_Download_Job **job_ret,
184                          Eina_Hash *headers)
185 {
186    Ecore_File_Download_Job *job;
187    Eina_Error err;
188    Eo *loop;
189    char *dir;
190 
191    if (job_ret) *job_ret = NULL;
192    if (!url)
193      {
194         CRI("Download URL is null");
195         return EINA_FALSE;
196      }
197 
198    if (!strstr(url, "://"))
199      {
200         ERR("'%s' is not an URL, missing protocol://", url);
201         return EINA_FALSE;
202      }
203 
204    dir = ecore_file_dir_get(dst);
205    if (!ecore_file_is_dir(dir))
206      {
207         ERR("%s is not a directory", dir);
208         free(dir);
209         return EINA_FALSE;
210      }
211    free(dir);
212    if (ecore_file_exists(dst))
213      {
214         ERR("%s already exists", dst);
215         return EINA_FALSE;
216      }
217 
218    loop = efl_main_loop_get();
219    EINA_SAFETY_ON_NULL_RETURN_VAL(loop, EINA_FALSE);
220 
221    job = calloc(1, sizeof(Ecore_File_Download_Job));
222    EINA_SAFETY_ON_NULL_RETURN_VAL(job, EINA_FALSE);
223    ECORE_MAGIC_SET(job, ECORE_MAGIC_FILE_DOWNLOAD_JOB);
224 
225    job->input = efl_add(EFL_NET_DIALER_HTTP_CLASS, loop,
226                         efl_net_dialer_http_allow_redirects_set(efl_added, EINA_TRUE));
227    EINA_SAFETY_ON_NULL_GOTO(job->input, error_input);
228 
229    job->output = efl_add(EFL_IO_FILE_CLASS, loop,
230                          efl_file_set(efl_added, dst),
231                          efl_io_file_flags_set(efl_added, O_WRONLY | O_CREAT),
232                          efl_io_closer_close_on_exec_set(efl_added, EINA_TRUE),
233                          efl_io_closer_close_on_invalidate_set(efl_added, EINA_TRUE),
234                          efl_io_file_mode_set(efl_added, 0644));
235    EINA_SAFETY_ON_NULL_GOTO(job->output, error_output);
236 
237    job->copier = efl_add(EFL_IO_COPIER_CLASS, loop,
238                          efl_io_copier_source_set(efl_added, job->input),
239                          efl_io_copier_destination_set(efl_added, job->output),
240                          efl_io_closer_close_on_invalidate_set(efl_added, EINA_TRUE),
241                          efl_event_callback_array_add(efl_added, ecore_file_download_copier_cbs(), job));
242    EINA_SAFETY_ON_NULL_GOTO(job->copier, error_copier);
243 
244    _job_list = eina_list_append(_job_list, job);
245 
246    if (headers)
247      eina_hash_foreach(headers, _ecore_file_download_headers_foreach_cb, job);
248 
249    job->completion_cb = completion_cb;
250    job->progress_cb = progress_cb;
251    job->data = data;
252 
253    err = efl_net_dialer_dial(job->input, url);
254    if (err)
255      {
256         ERR("Could not download %s: %s", url, eina_error_msg_get(err));
257         goto error_dial;
258      }
259 
260    if (job_ret) *job_ret = job;
261    return EINA_TRUE;
262 
263  error_dial:
264    efl_del(job->copier);
265    return EINA_FALSE; /* copier's "del" event will delete everything else */
266 
267  error_copier:
268    efl_del(job->output);
269  error_output:
270    efl_del(job->input);
271  error_input:
272    ECORE_MAGIC_SET(job, ECORE_MAGIC_NONE);
273    free(job);
274    return EINA_FALSE;
275 }
276 
277 EAPI Eina_Bool
ecore_file_download(const char * url,const char * dst,Ecore_File_Download_Completion_Cb completion_cb,Ecore_File_Download_Progress_Cb progress_cb,void * data,Ecore_File_Download_Job ** job_ret)278 ecore_file_download(const char *url,
279                     const char *dst,
280                     Ecore_File_Download_Completion_Cb completion_cb,
281                     Ecore_File_Download_Progress_Cb progress_cb,
282                     void *data,
283                     Ecore_File_Download_Job **job_ret)
284 {
285    return ecore_file_download_full(url, dst, completion_cb, progress_cb, data, job_ret, NULL);
286 }
287 
288 EAPI Eina_Bool
ecore_file_download_protocol_available(const char * protocol)289 ecore_file_download_protocol_available(const char *protocol)
290 {
291    if (!strncmp(protocol, "file://", 7)) return EINA_TRUE;
292    else if (!strncmp(protocol, "http://", 7)) return EINA_TRUE;
293    else if (!strncmp(protocol, "https://", 8)) return EINA_TRUE;
294    else if (!strncmp(protocol, "ftp://", 6)) return EINA_TRUE;
295 
296    return EINA_FALSE;
297 }
298 
299 EAPI void
ecore_file_download_abort(Ecore_File_Download_Job * job)300 ecore_file_download_abort(Ecore_File_Download_Job *job)
301 {
302    const char *file;
303 
304    if (!job)
305      return;
306    if (!ECORE_MAGIC_CHECK(job, ECORE_MAGIC_FILE_DOWNLOAD_JOB))
307      {
308         ECORE_MAGIC_FAIL(job, ECORE_MAGIC_FILE_DOWNLOAD_JOB, __func__);
309         return;
310      }
311 
312    file = efl_file_get(job->output);
313    DBG("Aborting download %s -> %s",
314        efl_net_dialer_address_dial_get(job->input),
315        file);
316 
317    /* abort should have status = 1 */
318    if (job->completion_cb)
319      {
320         Ecore_File_Download_Completion_Cb cb = job->completion_cb;
321         job->completion_cb = NULL;
322         ECORE_MAGIC_SET(job, ECORE_MAGIC_NONE);
323         cb((void *)job->data, file, 1);
324      }
325 
326    /* efl_io_closer_close()
327     *  -> _ecore_file_download_copier_done()
328     *       -> efl_del()
329     *           -> _ecore_file_download_copier_del()
330     */
331    efl_io_closer_close(job->copier);
332 }
333 
334 EAPI void
ecore_file_download_abort_all(void)335 ecore_file_download_abort_all(void)
336 {
337    Ecore_File_Download_Job *job;
338 
339    EINA_LIST_FREE(_job_list, job)
340              ecore_file_download_abort(job);
341 }
342