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