5 #include "assman_impl.h"
11 #include <curl/curl.h>
27 /* fopen-thread waits until the state becomes known (request starts transmitting or fails) */
29 pthread_cond_t state_cond;
30 pthread_mutex_t state_mutex;
33 static void *fop_open(const char *fname, void *udata);
34 static void fop_close(void *fp, void *udata);
35 static long fop_seek(void *fp, long offs, int whence, void *udata);
36 static long fop_read(void *fp, void *buf, long size, void *udata);
38 static void exit_cleanup(void);
39 static void download(void *data);
40 static size_t recv_callback(char *ptr, size_t size, size_t nmemb, void *udata);
41 static const char *get_temp_dir(void);
42 static int mkdir_path(const char *path);
44 static char *tmpdir, *cachedir;
45 static struct thread_pool *tpool;
48 struct ass_fileops *ass_alloc_url(const char *url)
51 struct ass_fileops *fop;
56 curl_global_init(CURL_GLOBAL_ALL);
59 if(!*ass_mod_url_cachedir) {
60 strcpy(ass_mod_url_cachedir, "assman_cache");
62 tmpdir = (char*)get_temp_dir();
63 if(!(cachedir = malloc(strlen(ass_mod_url_cachedir) + strlen(tmpdir) + 2))) {
64 fprintf(stderr, "assman mod_url: failed to allocate cachedir path buffer\n");
67 sprintf(cachedir, "%s/%s", tmpdir, ass_mod_url_cachedir);
69 if(mkdir_path(cachedir) == -1) {
70 fprintf(stderr, "assman mod_url: failed to create cache directory: %s\n", cachedir);
74 if(ass_mod_url_max_threads <= 0) {
75 ass_mod_url_max_threads = 8;
78 if(!(curl = calloc(ass_mod_url_max_threads, sizeof *curl))) {
79 perror("assman: failed to allocate curl context table");
82 for(i=0; i<ass_mod_url_max_threads; i++) {
83 if(!(curl[i] = curl_easy_init())) {
86 curl_easy_setopt(curl[i], CURLOPT_WRITEFUNCTION, recv_callback);
89 if(!(tpool = ass_tpool_create(ass_mod_url_max_threads))) {
90 fprintf(stderr, "assman: failed to create thread pool\n");
97 if(!(fop = malloc(sizeof *fop))) {
101 if(!(fop->udata = malloc(len + 1))) {
105 memcpy(fop->udata, url, len + 1);
107 ptr = (char*)fop->udata + len - 1;
108 while(*ptr == '/') *ptr-- = 0;
111 fop->open = fop_open;
112 fop->close = fop_close;
113 fop->seek = fop_seek;
114 fop->read = fop_read;
120 for(i=0; i<ass_mod_url_max_threads; i++) {
122 curl_easy_cleanup(curl[i]);
130 static void exit_cleanup(void)
135 ass_tpool_destroy(tpool);
138 for(i=0; i<ass_mod_url_max_threads; i++) {
140 curl_easy_cleanup(curl[i]);
145 curl_global_cleanup();
149 void ass_free_url(struct ass_fileops *fop)
153 static char *cache_filename(const char *fname, const char *url)
156 unsigned char sum[16];
160 int url_len = strlen(url);
161 int fname_len = strlen(fname);
164 MD4Update(&md4ctx, (unsigned char*)url, url_len);
165 MD4Final((unsigned char*)sum, &md4ctx);
167 for(i=0; i<16; i++) {
168 sprintf(sumstr + i * 2, "%x", (unsigned int)sum[i]);
172 prefix_len = strlen(cachedir);
173 if(!(resfname = malloc(prefix_len + fname_len + 64))) {
176 sprintf(resfname, "%s/%s-%s", cachedir, fname, sumstr);
180 static void *fop_open(const char *fname, void *udata)
182 struct file_info *file;
184 char *prefix = udata;
186 if(!fname || !*fname) {
191 if(!(file = malloc(sizeof *file))) {
196 if(!(file->url = malloc(strlen(prefix) + strlen(fname) + 2))) {
197 perror("assman: mod_url: failed to allocate url buffer");
202 if(prefix && *prefix) {
203 sprintf(file->url, "%s/%s", prefix, fname);
205 strcpy(file->url, fname);
208 if(!(file->cache_fname = cache_filename(fname, file->url))) {
214 printf("assman: mod_url cache file: %s\n", file->cache_fname);
215 if(!(file->cache_file = fopen(file->cache_fname, "wb"))) {
216 fprintf(stderr, "assman: mod_url: failed to open cache file (%s) for writing: %s\n",
217 file->cache_fname, strerror(errno));
220 free(file->cache_fname);
225 file->state = DL_UNKNOWN;
226 pthread_mutex_init(&file->state_mutex, 0);
227 pthread_cond_init(&file->state_cond, 0);
229 ass_tpool_enqueue(tpool, file, download, 0);
231 /* wait until the file changes state */
232 pthread_mutex_lock(&file->state_mutex);
233 while(file->state == DL_UNKNOWN) {
234 pthread_cond_wait(&file->state_cond, &file->state_mutex);
237 pthread_mutex_unlock(&file->state_mutex);
239 if(state == DL_ERROR) {
240 /* the worker stopped, so we can safely cleanup and return error */
241 fclose(file->cache_file);
242 remove(file->cache_fname);
243 free(file->cache_fname);
245 pthread_cond_destroy(&file->state_cond);
246 pthread_mutex_destroy(&file->state_mutex);
248 ass_errno = ENOENT; /* TODO: differentiate between 403 and 404 */
254 static void wait_done(struct file_info *file)
256 pthread_mutex_lock(&file->state_mutex);
257 while(file->state != DL_DONE && file->state != DL_ERROR) {
258 pthread_cond_wait(&file->state_cond, &file->state_mutex);
260 pthread_mutex_unlock(&file->state_mutex);
263 static void fop_close(void *fp, void *udata)
265 struct file_info *file = fp;
267 wait_done(file); /* TODO: stop download instead of waiting to finish */
269 fclose(file->cache_file);
270 if(file->state == DL_ERROR) remove(file->cache_fname);
271 free(file->cache_fname);
273 pthread_cond_destroy(&file->state_cond);
274 pthread_mutex_destroy(&file->state_mutex);
278 static long fop_seek(void *fp, long offs, int whence, void *udata)
280 struct file_info *file = fp;
283 fseek(file->cache_file, offs, whence);
284 return ftell(file->cache_file);
287 static long fop_read(void *fp, void *buf, long size, void *udata)
289 struct file_info *file = fp;
292 return fread(buf, 1, size, file->cache_file);
295 /* this is the function called by the worker threads to perform the download
296 * signal state changes, and prepare the cache file for reading
298 static void download(void *data)
301 struct file_info *file = data;
303 tid = ass_tpool_thread_id(tpool);
305 curl_easy_setopt(curl[tid], CURLOPT_URL, file->url);
306 curl_easy_setopt(curl[tid], CURLOPT_WRITEDATA, file);
307 res = curl_easy_perform(curl[tid]);
309 pthread_mutex_lock(&file->state_mutex);
310 if(res == CURLE_OK) {
311 file->state = DL_DONE;
312 fclose(file->cache_file);
313 if(!(file->cache_file = fopen(file->cache_fname, "rb"))) {
314 fprintf(stderr, "assman: failed to reopen cache file (%s) for reading: %s\n",
315 file->cache_fname, strerror(errno));
316 file->state = DL_ERROR;
319 file->state = DL_ERROR;
321 pthread_cond_broadcast(&file->state_cond);
322 pthread_mutex_unlock(&file->state_mutex);
325 /* this function is called by curl to pass along downloaded data chunks */
326 static size_t recv_callback(char *ptr, size_t size, size_t count, void *udata)
328 struct file_info *file = udata;
330 pthread_mutex_lock(&file->state_mutex);
331 if(file->state == DL_UNKNOWN) {
332 file->state = DL_STARTED;
333 pthread_cond_broadcast(&file->state_cond);
335 pthread_mutex_unlock(&file->state_mutex);
337 return fwrite(ptr, size, count, file->cache_file);
343 static const char *get_temp_dir(void)
345 static char buf[MAX_PATH + 1];
346 GetTempPathA(MAX_PATH + 1, buf);
350 static const char *get_temp_dir(void)
352 char *env = getenv("TMPDIR");
353 return env ? env : "/tmp";
358 static int mkdir_path(const char *path)
360 char *pathbuf, *dptr;
363 if(!path || !*path) return -1;
365 pathbuf = dptr = alloca(strlen(path) + 1);
372 if(c == '/' || c == '\\') break;
376 if(stat(pathbuf, &st) == -1) {
377 /* path component does not exist, create it */
378 if(mkdir(pathbuf, 0777) == -1) {
387 #else /* don't build mod_url */
388 struct ass_fileops *ass_alloc_url(const char *url)
390 fprintf(stderr, "assman: compiled without URL asset source support\n");
394 void ass_free_url(struct ass_fileops *fop)