1 /*
2 * Copyright 2008-2013 Various Authors
3 * Copyright 2008 Timo Hirvonen
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "utils.h"
20 #include "job.h"
21 #include "worker.h"
22 #include "cache.h"
23 #include "xmalloc.h"
24 #include "debug.h"
25 #include "load_dir.h"
26 #include "path.h"
27 #include "editable.h"
28 #include "pl.h"
29 #include "play_queue.h"
30 #include "lib.h"
31 #include "utils.h"
32 #include "file.h"
33 #include "cache.h"
34 #include "player.h"
35 #include "discid.h"
36 #include "xstrjoin.h"
37 #include "ui_curses.h"
38 #include "cue_utils.h"
39
40 #include <string.h>
41 #include <unistd.h>
42 #include <errno.h>
43 #include <sys/mman.h>
44
45 enum job_result_var {
46 JOB_RES_ADD,
47 JOB_RES_UPDATE,
48 JOB_RES_UPDATE_CACHE,
49 JOB_RES_PL_DELETE,
50 };
51
52 enum update_kind {
53 UPDATE_NONE = 0,
54 UPDATE_REMOVE = 1,
55 UPDATE_MTIME_CHANGED = 2,
56 };
57
58 struct job_result {
59 struct list_head node;
60
61 enum job_result_var var;
62 union {
63 struct {
64 add_ti_cb add_cb;
65 size_t add_num;
66 struct track_info **add_ti;
67 void *add_opaque;
68 };
69 struct {
70 size_t update_num;
71 struct track_info **update_ti;
72 enum update_kind *update_kind;
73 };
74 struct {
75 size_t update_cache_num;
76 struct track_info **update_cache_ti;
77 };
78 struct {
79 void (*pl_delete_cb)(struct playlist *);
80 struct playlist *pl_delete_pl;
81 };
82 };
83 };
84
85 int job_fd;
86 static int job_fd_priv;
87
88 static LIST_HEAD(job_result_head);
89 static pthread_mutex_t job_mutex = CMUS_MUTEX_INITIALIZER;
90
91 #define TI_CAP 32
92 static struct track_info **ti_buffer;
93 static size_t ti_buffer_fill;
94 static struct add_data *jd;
95
96 #define job_lock() cmus_mutex_lock(&job_mutex)
97 #define job_unlock() cmus_mutex_unlock(&job_mutex)
98
job_init(void)99 void job_init(void)
100 {
101 init_pipes(&job_fd, &job_fd_priv);
102
103 worker_init();
104 }
105
job_exit(void)106 void job_exit(void)
107 {
108 worker_remove_jobs_by_type(JOB_TYPE_ANY);
109 worker_exit();
110
111 close(job_fd);
112 close(job_fd_priv);
113 }
114
job_push_result(struct job_result * res)115 static void job_push_result(struct job_result *res)
116 {
117 job_lock();
118 list_add_tail(&res->node, &job_result_head);
119 job_unlock();
120
121 notify_via_pipe(job_fd_priv);
122 }
123
job_pop_result(void)124 static struct job_result *job_pop_result(void)
125 {
126 struct job_result *res = NULL;
127
128 job_lock();
129 if (!list_empty(&job_result_head)) {
130 struct list_head *item = job_result_head.next;
131 list_del(item);
132 res = container_of(item, struct job_result, node);
133 }
134 job_unlock();
135
136 return res;
137 }
138
flush_ti_buffer(void)139 static void flush_ti_buffer(void)
140 {
141 struct job_result *res = xnew(struct job_result, 1);
142
143 res->var = JOB_RES_ADD;
144 res->add_cb = jd->add;
145 res->add_num = ti_buffer_fill;
146 res->add_ti = ti_buffer;
147 res->add_opaque = jd->opaque;
148
149 job_push_result(res);
150
151 ti_buffer_fill = 0;
152 ti_buffer = NULL;
153 }
154
add_ti(struct track_info * ti)155 static void add_ti(struct track_info *ti)
156 {
157 if (ti_buffer_fill == TI_CAP)
158 flush_ti_buffer();
159 if (!ti_buffer)
160 ti_buffer = xnew(struct track_info *, TI_CAP);
161 ti_buffer[ti_buffer_fill++] = ti;
162 }
163
164 static int add_file_cue(const char *filename);
165
add_file(const char * filename,int force)166 static void add_file(const char *filename, int force)
167 {
168 struct track_info *ti;
169
170 if (!is_cue_url(filename)) {
171 if (force || lookup_cache_entry(filename, hash_str(filename)) == NULL) {
172 int done = add_file_cue(filename);
173 if (done)
174 return;
175 }
176 }
177
178 cache_lock();
179 ti = cache_get_ti(filename, force);
180 cache_unlock();
181
182 if (ti)
183 add_ti(ti);
184 }
185
add_file_cue(const char * filename)186 static int add_file_cue(const char *filename)
187 {
188 int n_tracks;
189 char *url;
190 char *cue_filename;
191
192 cue_filename = associated_cue(filename);
193 if (cue_filename == NULL)
194 return 0;
195
196 n_tracks = cue_get_ntracks(cue_filename);
197 if (n_tracks <= 0) {
198 free(cue_filename);
199 return 0;
200 }
201
202 for (int i = 1; i <= n_tracks; ++i) {
203 url = construct_cue_url(cue_filename, i);
204 add_file(url, 0);
205 free(url);
206 }
207
208 free(cue_filename);
209 return 1;
210 }
211
add_url(const char * url)212 static void add_url(const char *url)
213 {
214 add_file(url, 0);
215 }
216
add_cdda(const char * url)217 static void add_cdda(const char *url)
218 {
219 char *disc_id = NULL;
220 int start_track = 1, end_track = -1;
221
222 parse_cdda_url(url, &disc_id, &start_track, &end_track);
223
224 if (end_track != -1) {
225 int i;
226 for (i = start_track; i <= end_track; i++) {
227 char *new_url = gen_cdda_url(disc_id, i, -1);
228 add_file(new_url, 0);
229 free(new_url);
230 }
231 } else
232 add_file(url, 0);
233 free(disc_id);
234 }
235
dir_entry_cmp(const void * ap,const void * bp)236 static int dir_entry_cmp(const void *ap, const void *bp)
237 {
238 struct dir_entry *a = *(struct dir_entry **)ap;
239 struct dir_entry *b = *(struct dir_entry **)bp;
240
241 return strcmp(a->name, b->name);
242 }
243
dir_entry_cmp_reverse(const void * ap,const void * bp)244 static int dir_entry_cmp_reverse(const void *ap, const void *bp)
245 {
246 struct dir_entry *a = *(struct dir_entry **)ap;
247 struct dir_entry *b = *(struct dir_entry **)bp;
248
249 return strcmp(b->name, a->name);
250 }
251
points_within_and_visible(const char * target,const char * root)252 static int points_within_and_visible(const char *target, const char *root)
253 {
254 int tlen = strlen(target);
255 int rlen = strlen(root);
256
257 if (rlen > tlen)
258 return 0;
259 if (strncmp(target, root, rlen))
260 return 0;
261 if (target[rlen] != '/' && target[rlen] != '\0')
262 return 0;
263 /* assume the path is normalized */
264 if (strstr(target + rlen, "/."))
265 return 0;
266
267 return 1;
268 }
269
add_dir(const char * dirname,const char * root)270 static void add_dir(const char *dirname, const char *root)
271 {
272 struct directory dir;
273 struct dir_entry **ents;
274 const char *name;
275 PTR_ARRAY(array);
276 int i;
277
278 if (dir_open(&dir, dirname)) {
279 d_print("error: opening %s: %s\n", dirname, strerror(errno));
280 return;
281 }
282 while ((name = dir_read(&dir))) {
283 struct dir_entry *ent;
284 int size;
285
286 if (name[0] == '.')
287 continue;
288
289 if (dir.is_link) {
290 char buf[1024];
291 char *target;
292 int rc = readlink(dir.path, buf, sizeof(buf));
293
294 if (rc < 0 || rc == sizeof(buf))
295 continue;
296 buf[rc] = 0;
297 target = path_absolute_cwd(buf, dirname);
298 if (points_within_and_visible(target, root)) {
299 d_print("%s -> %s points within %s. ignoring\n",
300 dir.path, target, root);
301 free(target);
302 continue;
303 }
304 free(target);
305 }
306
307 size = strlen(name) + 1;
308 ent = xmalloc(sizeof(struct dir_entry) + size);
309 ent->mode = dir.st.st_mode;
310 memcpy(ent->name, name, size);
311 ptr_array_add(&array, ent);
312 }
313 dir_close(&dir);
314
315 if (jd->add == play_queue_prepend) {
316 ptr_array_sort(&array, dir_entry_cmp_reverse);
317 } else {
318 ptr_array_sort(&array, dir_entry_cmp);
319 }
320 ents = array.ptrs;
321 for (i = 0; i < array.count; i++) {
322 if (!worker_cancelling()) {
323 /* abuse dir.path because
324 * - it already contains dirname + '/'
325 * - it is guaranteed to be large enough
326 */
327 int len = strlen(ents[i]->name);
328
329 memcpy(dir.path + dir.len, ents[i]->name, len + 1);
330 if (S_ISDIR(ents[i]->mode)) {
331 add_dir(dir.path, root);
332 } else {
333 add_file(dir.path, 0);
334 }
335 }
336 free(ents[i]);
337 }
338 free(ents);
339 }
340
handle_line(void * data,const char * line)341 static int handle_line(void *data, const char *line)
342 {
343 if (worker_cancelling())
344 return 1;
345
346 if (is_http_url(line) || is_cue_url(line)) {
347 add_url(line);
348 } else {
349 char *absolute = path_absolute_cwd(line, data);
350 add_file(absolute, 0);
351 free(absolute);
352 }
353
354 return 0;
355 }
356
add_pl(const char * filename)357 static void add_pl(const char *filename)
358 {
359 char *buf;
360 ssize_t size;
361 int reverse;
362
363 buf = mmap_file(filename, &size);
364 if (size == -1)
365 return;
366
367 if (buf) {
368 char *cwd = xstrjoin(filename, "/..");
369 /* beautiful hack */
370 reverse = jd->add == play_queue_prepend;
371
372 cmus_playlist_for_each(buf, size, reverse, handle_line, cwd);
373 free(cwd);
374 munmap(buf, size);
375 }
376 }
377
do_add_job(void * data)378 static void do_add_job(void *data)
379 {
380 jd = data;
381 switch (jd->type) {
382 case FILE_TYPE_URL:
383 add_url(jd->name);
384 break;
385 case FILE_TYPE_CDDA:
386 add_cdda(jd->name);
387 break;
388 case FILE_TYPE_PL:
389 add_pl(jd->name);
390 break;
391 case FILE_TYPE_DIR:
392 add_dir(jd->name, jd->name);
393 break;
394 case FILE_TYPE_FILE:
395 add_file(jd->name, jd->force);
396 break;
397 case FILE_TYPE_INVALID:
398 break;
399 }
400 if (ti_buffer)
401 flush_ti_buffer();
402 jd = NULL;
403 }
404
free_add_job(void * data)405 static void free_add_job(void *data)
406 {
407 struct add_data *d = data;
408 free(d->name);
409 free(d);
410 }
411
job_handle_add_result(struct job_result * res)412 static void job_handle_add_result(struct job_result *res)
413 {
414 for (size_t i = 0; i < res->add_num; i++) {
415 res->add_cb(res->add_ti[i], res->add_opaque);
416 track_info_unref(res->add_ti[i]);
417 }
418
419 free(res->add_ti);
420 }
421
job_schedule_add(int type,struct add_data * data)422 void job_schedule_add(int type, struct add_data *data)
423 {
424 worker_add_job(type | JOB_TYPE_ADD, do_add_job, free_add_job, data);
425 }
426
do_update_job(void * data)427 static void do_update_job(void *data)
428 {
429 struct update_data *d = data;
430 int i;
431 enum update_kind *kind = xnew(enum update_kind, d->used);
432 struct job_result *res;
433
434 for (i = 0; i < d->used; i++) {
435 struct track_info *ti = d->ti[i];
436 struct stat s;
437 int rc;
438
439 rc = stat(ti->filename, &s);
440 if (rc || d->force || ti->mtime != s.st_mtime || ti->duration == 0) {
441 kind[i] = UPDATE_NONE;
442 if (!is_cue_url(ti->filename) && !is_http_url(ti->filename) && rc)
443 kind[i] |= UPDATE_REMOVE;
444 else if (ti->mtime != s.st_mtime)
445 kind[i] |= UPDATE_MTIME_CHANGED;
446 } else {
447 track_info_unref(ti);
448 d->ti[i] = NULL;
449 }
450 }
451
452 res = xnew(struct job_result, 1);
453
454 res->var = JOB_RES_UPDATE;
455 res->update_num = d->used;
456 res->update_ti = d->ti;
457 res->update_kind = kind;
458
459 job_push_result(res);
460
461 d->ti = NULL;
462 }
463
free_update_job(void * data)464 static void free_update_job(void *data)
465 {
466 struct update_data *d = data;
467
468 if (d->ti) {
469 for (size_t i = 0; i < d->used; i++)
470 track_info_unref(d->ti[i]);
471 free(d->ti);
472 }
473 free(d);
474 }
475
job_handle_update_result(struct job_result * res)476 static void job_handle_update_result(struct job_result *res)
477 {
478 for (size_t i = 0; i < res->update_num; i++) {
479 struct track_info *ti = res->update_ti[i];
480 int force;
481
482 if (!ti)
483 continue;
484
485 lib_remove(ti);
486
487 cache_lock();
488 cache_remove_ti(ti);
489 cache_unlock();
490
491 if (res->update_kind[i] & UPDATE_REMOVE) {
492 d_print("removing dead file %s\n", ti->filename);
493 } else {
494 if (res->update_kind[i] & UPDATE_MTIME_CHANGED)
495 d_print("mtime changed: %s\n", ti->filename);
496 force = ti->duration == 0;
497 cmus_add(lib_add_track, ti->filename, FILE_TYPE_FILE,
498 JOB_TYPE_LIB, force, NULL);
499 }
500
501 track_info_unref(ti);
502 }
503
504 free(res->update_kind);
505 free(res->update_ti);
506 }
507
job_schedule_update(struct update_data * data)508 void job_schedule_update(struct update_data *data)
509 {
510 worker_add_job(JOB_TYPE_LIB | JOB_TYPE_UPDATE, do_update_job,
511 free_update_job, data);
512 }
513
do_update_cache_job(void * data)514 static void do_update_cache_job(void *data)
515 {
516 struct update_cache_data *d = data;
517 int count;
518 struct track_info **tis;
519 struct job_result *res;
520
521 cache_lock();
522 tis = cache_refresh(&count, d->force);
523 cache_unlock();
524
525 res = xnew(struct job_result, 1);
526 res->var = JOB_RES_UPDATE_CACHE;
527 res->update_cache_ti = tis;
528 res->update_cache_num = count;
529 job_push_result(res);
530 }
531
free_update_cache_job(void * data)532 static void free_update_cache_job(void *data)
533 {
534 free(data);
535 }
536
job_handle_update_cache_result(struct job_result * res)537 static void job_handle_update_cache_result(struct job_result *res)
538 {
539 for (size_t i = 0; i < res->update_cache_num; i++) {
540 struct track_info *new, *old = res->update_cache_ti[i];
541
542 if (!old)
543 continue;
544
545 new = old->next;
546 if (lib_remove(old) && new)
547 lib_add_track(new, NULL);
548 pl_update_track(old, new);
549 editable_update_track(&pq_editable, old, new);
550 if (player_info.ti == old && new) {
551 track_info_ref(new);
552 player_file_changed(new);
553 }
554
555 track_info_unref(old);
556 if (new)
557 track_info_unref(new);
558 }
559 free(res->update_cache_ti);
560 }
561
job_schedule_update_cache(int type,struct update_cache_data * data)562 void job_schedule_update_cache(int type, struct update_cache_data *data)
563 {
564 worker_add_job(type | JOB_TYPE_UPDATE_CACHE, do_update_cache_job,
565 free_update_cache_job, data);
566 }
567
do_pl_delete_job(void * data)568 static void do_pl_delete_job(void *data)
569 {
570 /*
571 * If PL jobs are canceled this function won't run. Hence we push the
572 * result in the free function.
573 */
574 }
575
free_pl_delete_job(void * data)576 static void free_pl_delete_job(void *data)
577 {
578 struct pl_delete_data *pdd = data;
579 struct job_result *res;
580
581 res = xnew(struct job_result, 1);
582 res->var = JOB_RES_PL_DELETE;
583 res->pl_delete_cb = pdd->cb;
584 res->pl_delete_pl = pdd->pl;
585 job_push_result(res);
586
587 free(pdd);
588 }
589
job_handle_pl_delete_result(struct job_result * res)590 static void job_handle_pl_delete_result(struct job_result *res)
591 {
592 res->pl_delete_cb(res->pl_delete_pl);
593 }
594
job_schedule_pl_delete(struct pl_delete_data * data)595 void job_schedule_pl_delete(struct pl_delete_data *data)
596 {
597 worker_add_job(JOB_TYPE_PL | JOB_TYPE_DELETE, do_pl_delete_job,
598 free_pl_delete_job, data);
599 }
600
job_handle_result(struct job_result * res)601 static void job_handle_result(struct job_result *res)
602 {
603 switch (res->var) {
604 case JOB_RES_ADD:
605 job_handle_add_result(res);
606 break;
607 case JOB_RES_UPDATE:
608 job_handle_update_result(res);
609 break;
610 case JOB_RES_UPDATE_CACHE:
611 job_handle_update_cache_result(res);
612 break;
613 case JOB_RES_PL_DELETE:
614 job_handle_pl_delete_result(res);
615 break;
616 }
617 free(res);
618 }
619
job_handle(void)620 void job_handle(void)
621 {
622 clear_pipe(job_fd, -1);
623
624 struct job_result *res;
625 while ((res = job_pop_result()))
626 job_handle_result(res);
627 }
628