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;
53 curl_global_init(CURL_GLOBAL_ALL);
56 if(!*ass_mod_url_cachedir) {
57 strcpy(ass_mod_url_cachedir, "assman_cache");
59 tmpdir = (char*)get_temp_dir();
60 if(!(cachedir = malloc(strlen(ass_mod_url_cachedir) + strlen(tmpdir) + 2))) {
61 fprintf(stderr, "assman: failed to allocate cachedir path buffer\n");
64 sprintf(cachedir, "%s/%s", tmpdir, ass_mod_url_cachedir);
66 if(ass_mod_url_max_threads <= 0) {
67 ass_mod_url_max_threads = 8;
70 if(!(curl = calloc(ass_mod_url_max_threads, sizeof *curl))) {
71 perror("assman: failed to allocate curl context table");
74 for(i=0; i<ass_mod_url_max_threads; i++) {
75 if(!(curl[i] = curl_easy_init())) {
78 curl_easy_setopt(curl[i], CURLOPT_WRITEFUNCTION, recv_callback);
81 if(!(tpool = ass_tpool_create(ass_mod_url_max_threads))) {
82 fprintf(stderr, "assman: failed to create thread pool\n");
89 if(!(fop = malloc(sizeof *fop))) {
92 if(!(fop->udata = malloc(strlen(url) + 1))) {
96 strcpy(fop->udata, url);
99 fop->close = fop_close;
100 fop->seek = fop_seek;
101 fop->read = fop_read;
107 for(i=0; i<ass_mod_url_max_threads; i++) {
109 curl_easy_cleanup(curl[i]);
117 static void exit_cleanup(void)
122 ass_tpool_destroy(tpool);
125 for(i=0; i<ass_mod_url_max_threads; i++) {
127 curl_easy_cleanup(curl[i]);
132 curl_global_cleanup();
136 void ass_free_url(struct ass_fileops *fop)
140 static char *cache_filename(const char *fname, const char *url_prefix)
143 unsigned char sum[16];
147 int fname_len = strlen(fname);
148 int prefix_len = strlen(url_prefix);
149 int url_len = fname_len + prefix_len + 1;
151 char *url = alloca(url_len + 1);
152 sprintf(url, "%s/%s", url_prefix, fname);
155 MD4Update(&md4ctx, (unsigned char*)url, url_len);
156 MD4Final((unsigned char*)sum, &md4ctx);
158 for(i=0; i<16; i++) {
159 sprintf(sumstr + i * 2, "%x", (unsigned int)sum[i]);
163 prefix_len = strlen(cachedir);
164 if(!(resfname = malloc(prefix_len + fname_len + 20))) {
167 sprintf(resfname, "%s/%s-%s", cachedir, fname, sumstr);
171 static void *fop_open(const char *fname, void *udata)
173 struct file_info *file;
176 if(!(file = malloc(sizeof *file))) {
180 if(!(file->cache_fname = cache_filename(fname, udata))) {
185 if(!(file->cache_file = fopen(file->cache_fname, "wb"))) {
186 fprintf(stderr, "assman: mod_url: failed to open cache file (%s) for writing: %s\n",
187 file->cache_fname, strerror(errno));
189 free(file->cache_fname);
194 file->state = DL_UNKNOWN;
195 pthread_mutex_init(&file->state_mutex, 0);
196 pthread_cond_init(&file->state_cond, 0);
198 ass_tpool_enqueue(tpool, file, download, 0);
200 /* wait until the file changes state */
201 pthread_mutex_lock(&file->state_mutex);
202 while(file->state == DL_UNKNOWN) {
203 pthread_cond_wait(&file->state_cond, &file->state_mutex);
206 pthread_mutex_unlock(&file->state_mutex);
208 if(state == DL_ERROR) {
209 /* the worker stopped, so we can safely cleanup and return error */
210 fclose(file->cache_file);
211 remove(file->cache_fname);
212 free(file->cache_fname);
213 pthread_cond_destroy(&file->state_cond);
214 pthread_mutex_destroy(&file->state_mutex);
216 ass_errno = ENOENT; /* TODO: differentiate between 403 and 404 */
222 static void wait_done(struct file_info *file)
224 pthread_mutex_lock(&file->state_mutex);
225 while(file->state != DL_DONE && file->state != DL_ERROR) {
226 pthread_cond_wait(&file->state_cond, &file->state_mutex);
228 pthread_mutex_unlock(&file->state_mutex);
231 static void fop_close(void *fp, void *udata)
233 struct file_info *file = fp;
235 wait_done(file); /* TODO: stop download instead of waiting to finish */
237 fclose(file->cache_file);
238 if(file->state == DL_ERROR) remove(file->cache_fname);
239 free(file->cache_fname);
240 pthread_cond_destroy(&file->state_cond);
241 pthread_mutex_destroy(&file->state_mutex);
245 static long fop_seek(void *fp, long offs, int whence, void *udata)
247 struct file_info *file = fp;
250 fseek(file->cache_file, offs, whence);
251 return ftell(file->cache_file);
254 static long fop_read(void *fp, void *buf, long size, void *udata)
256 struct file_info *file = fp;
259 return fread(buf, 1, size, file->cache_file);
262 /* this is the function called by the worker threads to perform the download
263 * signal state changes, and prepare the cache file for reading
265 static void download(void *data)
268 struct file_info *file = data;
270 tid = ass_tpool_thread_id(tpool);
272 curl_easy_setopt(curl[tid], CURLOPT_URL, file->url);
273 curl_easy_setopt(curl[tid], CURLOPT_WRITEDATA, file);
274 res = curl_easy_perform(curl[tid]);
276 pthread_mutex_lock(&file->state_mutex);
277 if(res == CURLE_OK) {
278 file->state = DL_DONE;
279 fclose(file->cache_file);
280 if(!(file->cache_file = fopen(file->cache_fname, "rb"))) {
281 fprintf(stderr, "assman: failed to reopen cache file (%s) for reading: %s\n",
282 file->cache_fname, strerror(errno));
283 file->state = DL_ERROR;
286 file->state = DL_ERROR;
288 pthread_cond_broadcast(&file->state_cond);
289 pthread_mutex_unlock(&file->state_mutex);
292 /* this function is called by curl to pass along downloaded data chunks */
293 static size_t recv_callback(char *ptr, size_t size, size_t count, void *udata)
295 struct file_info *file = udata;
297 pthread_mutex_lock(&file->state_mutex);
298 if(file->state == DL_UNKNOWN) {
299 file->state = DL_STARTED;
300 pthread_cond_broadcast(&file->state_cond);
302 pthread_mutex_unlock(&file->state_mutex);
304 return fwrite(ptr, size, count, file->cache_file);
310 static const char *get_temp_dir(void)
312 static char buf[MAX_PATH + 1];
313 GetTempPathA(MAX_PATH + 1, buf);
317 static const char *get_temp_dir(void)
319 char *env = getenv("TMPDIR");
320 return env ? env : "/tmp";
326 #else /* don't build mod_url */
327 struct ass_fileops *ass_alloc_url(const char *url)
329 fprintf(stderr, "assman: compiled without URL asset source support\n");
333 void ass_free_url(struct ass_fileops *fop)