5 #include "assman_impl.h"
11 #include <curl/curl.h>
26 /* fopen-thread waits until the state becomes known (request starts transmitting or fails) */
28 pthread_cond_t state_cond;
29 pthread_mutex_t state_mutex;
32 static void *fop_open(const char *fname, void *udata);
33 static void fop_close(void *fp, void *udata);
34 static long fop_seek(void *fp, long offs, int whence, void *udata);
35 static long fop_read(void *fp, void *buf, long size, void *udata);
37 static void exit_cleanup(void);
38 static void download(void *data);
39 static size_t recv_callback(char *ptr, size_t size, size_t nmemb, void *udata);
40 static const char *get_temp_dir(void);
42 static char *tmpdir, *cachedir;
43 static struct thread_pool *tpool;
46 struct ass_fileops *ass_alloc_url(const char *url)
49 struct ass_fileops *fop;
54 curl_global_init(CURL_GLOBAL_ALL);
57 if(!*ass_mod_url_cachedir) {
58 strcpy(ass_mod_url_cachedir, "assman_cache");
60 tmpdir = (char*)get_temp_dir();
61 if(!(cachedir = malloc(strlen(ass_mod_url_cachedir) + strlen(tmpdir) + 2))) {
62 fprintf(stderr, "assman: failed to allocate cachedir path buffer\n");
65 sprintf(cachedir, "%s/%s", tmpdir, ass_mod_url_cachedir);
67 if(ass_mod_url_max_threads <= 0) {
68 ass_mod_url_max_threads = 8;
71 if(!(curl = calloc(ass_mod_url_max_threads, sizeof *curl))) {
72 perror("assman: failed to allocate curl context table");
75 for(i=0; i<ass_mod_url_max_threads; i++) {
76 if(!(curl[i] = curl_easy_init())) {
79 curl_easy_setopt(curl[i], CURLOPT_WRITEFUNCTION, recv_callback);
82 if(!(tpool = ass_tpool_create(ass_mod_url_max_threads))) {
83 fprintf(stderr, "assman: failed to create thread pool\n");
90 if(!(fop = malloc(sizeof *fop))) {
94 if(!(fop->udata = malloc(len + 1))) {
98 memcpy(fop->udata, url, len + 1);
100 ptr = (char*)fop->udata + len - 1;
101 while(*ptr == '/') *ptr-- = 0;
104 fop->open = fop_open;
105 fop->close = fop_close;
106 fop->seek = fop_seek;
107 fop->read = fop_read;
113 for(i=0; i<ass_mod_url_max_threads; i++) {
115 curl_easy_cleanup(curl[i]);
123 static void exit_cleanup(void)
128 ass_tpool_destroy(tpool);
131 for(i=0; i<ass_mod_url_max_threads; i++) {
133 curl_easy_cleanup(curl[i]);
138 curl_global_cleanup();
142 void ass_free_url(struct ass_fileops *fop)
146 static char *cache_filename(const char *fname, const char *url_prefix)
149 unsigned char sum[16];
153 int fname_len = strlen(fname);
154 int prefix_len = strlen(url_prefix);
155 int url_len = fname_len + prefix_len + 1;
157 char *url = alloca(url_len + 1);
158 sprintf(url, "%s/%s", url_prefix, fname);
161 MD4Update(&md4ctx, (unsigned char*)url, url_len);
162 MD4Final((unsigned char*)sum, &md4ctx);
164 for(i=0; i<16; i++) {
165 sprintf(sumstr + i * 2, "%x", (unsigned int)sum[i]);
169 prefix_len = strlen(cachedir);
170 if(!(resfname = malloc(prefix_len + fname_len + 64))) {
173 sprintf(resfname, "%s/%s-%s", cachedir, fname, sumstr);
177 static void *fop_open(const char *fname, void *udata)
179 struct file_info *file;
181 char *prefix = udata;
183 if(!fname || !*fname) {
188 if(!(file = malloc(sizeof *file))) {
192 if(!(file->cache_fname = cache_filename(fname, udata))) {
197 printf("assman: mod_url cache file: %s\n", file->cache_fname);
198 if(!(file->cache_file = fopen(file->cache_fname, "wb"))) {
199 fprintf(stderr, "assman: mod_url: failed to open cache file (%s) for writing: %s\n",
200 file->cache_fname, strerror(errno));
202 free(file->cache_fname);
207 if(!(file->url = malloc(strlen(prefix) + strlen(fname) + 2))) {
208 perror("assman: mod_url: failed to allocate url buffer");
210 fclose(file->cache_file);
211 remove(file->cache_fname);
212 free(file->cache_fname);
216 if(prefix && *prefix) {
217 sprintf(file->url, "%s/%s", prefix, fname);
219 strcpy(file->url, fname);
222 file->state = DL_UNKNOWN;
223 pthread_mutex_init(&file->state_mutex, 0);
224 pthread_cond_init(&file->state_cond, 0);
226 ass_tpool_enqueue(tpool, file, download, 0);
228 /* wait until the file changes state */
229 pthread_mutex_lock(&file->state_mutex);
230 while(file->state == DL_UNKNOWN) {
231 pthread_cond_wait(&file->state_cond, &file->state_mutex);
234 pthread_mutex_unlock(&file->state_mutex);
236 if(state == DL_ERROR) {
237 /* the worker stopped, so we can safely cleanup and return error */
238 fclose(file->cache_file);
239 remove(file->cache_fname);
240 free(file->cache_fname);
241 pthread_cond_destroy(&file->state_cond);
242 pthread_mutex_destroy(&file->state_mutex);
244 ass_errno = ENOENT; /* TODO: differentiate between 403 and 404 */
250 static void wait_done(struct file_info *file)
252 pthread_mutex_lock(&file->state_mutex);
253 while(file->state != DL_DONE && file->state != DL_ERROR) {
254 pthread_cond_wait(&file->state_cond, &file->state_mutex);
256 pthread_mutex_unlock(&file->state_mutex);
259 static void fop_close(void *fp, void *udata)
261 struct file_info *file = fp;
263 wait_done(file); /* TODO: stop download instead of waiting to finish */
265 fclose(file->cache_file);
266 if(file->state == DL_ERROR) remove(file->cache_fname);
267 free(file->cache_fname);
268 pthread_cond_destroy(&file->state_cond);
269 pthread_mutex_destroy(&file->state_mutex);
273 static long fop_seek(void *fp, long offs, int whence, void *udata)
275 struct file_info *file = fp;
278 fseek(file->cache_file, offs, whence);
279 return ftell(file->cache_file);
282 static long fop_read(void *fp, void *buf, long size, void *udata)
284 struct file_info *file = fp;
287 return fread(buf, 1, size, file->cache_file);
290 /* this is the function called by the worker threads to perform the download
291 * signal state changes, and prepare the cache file for reading
293 static void download(void *data)
296 struct file_info *file = data;
298 tid = ass_tpool_thread_id(tpool);
300 curl_easy_setopt(curl[tid], CURLOPT_URL, file->url);
301 curl_easy_setopt(curl[tid], CURLOPT_WRITEDATA, file);
302 res = curl_easy_perform(curl[tid]);
304 pthread_mutex_lock(&file->state_mutex);
305 if(res == CURLE_OK) {
306 file->state = DL_DONE;
307 fclose(file->cache_file);
308 if(!(file->cache_file = fopen(file->cache_fname, "rb"))) {
309 fprintf(stderr, "assman: failed to reopen cache file (%s) for reading: %s\n",
310 file->cache_fname, strerror(errno));
311 file->state = DL_ERROR;
314 file->state = DL_ERROR;
316 pthread_cond_broadcast(&file->state_cond);
317 pthread_mutex_unlock(&file->state_mutex);
320 /* this function is called by curl to pass along downloaded data chunks */
321 static size_t recv_callback(char *ptr, size_t size, size_t count, void *udata)
323 struct file_info *file = udata;
325 pthread_mutex_lock(&file->state_mutex);
326 if(file->state == DL_UNKNOWN) {
327 file->state = DL_STARTED;
328 pthread_cond_broadcast(&file->state_cond);
330 pthread_mutex_unlock(&file->state_mutex);
332 return fwrite(ptr, size, count, file->cache_file);
338 static const char *get_temp_dir(void)
340 static char buf[MAX_PATH + 1];
341 GetTempPathA(MAX_PATH + 1, buf);
345 static const char *get_temp_dir(void)
347 char *env = getenv("TMPDIR");
348 return env ? env : "/tmp";
354 #else /* don't build mod_url */
355 struct ass_fileops *ass_alloc_url(const char *url)
357 fprintf(stderr, "assman: compiled without URL asset source support\n");
361 void ass_free_url(struct ass_fileops *fop)