/* * Copyright 2008-2013 Various Authors * Copyright 2008 Timo Hirvonen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "utils.h" #include "job.h" #include "worker.h" #include "cache.h" #include "xmalloc.h" #include "debug.h" #include "load_dir.h" #include "path.h" #include "editable.h" #include "pl.h" #include "play_queue.h" #include "lib.h" #include "utils.h" #include "file.h" #include "cache.h" #include "player.h" #include "discid.h" #include "xstrjoin.h" #include "ui_curses.h" #include "cue_utils.h" #include "pl_env.h" #include "misc.h" #include #include #include #include enum job_result_var { JOB_RES_ADD, JOB_RES_UPDATE, JOB_RES_UPDATE_CACHE, JOB_RES_PL_DELETE, }; enum update_kind { UPDATE_NONE = 0, UPDATE_REMOVE = 1, UPDATE_MTIME_CHANGED = 2, }; struct job_result { struct list_head node; enum job_result_var var; union { struct { add_ti_cb add_cb; size_t add_num; struct track_info **add_ti; void *add_opaque; }; struct { size_t update_num; struct track_info **update_ti; enum update_kind *update_kind; }; struct { size_t update_cache_num; struct track_info **update_cache_ti; }; struct { void (*pl_delete_cb)(struct playlist *); struct playlist *pl_delete_pl; }; }; }; int job_fd; static int job_fd_priv; static LIST_HEAD(job_result_head); static pthread_mutex_t job_mutex = CMUS_MUTEX_INITIALIZER; #define TI_CAP 32 static struct track_info **ti_buffer; static size_t ti_buffer_fill; static struct add_data *jd; #define job_lock() cmus_mutex_lock(&job_mutex) #define job_unlock() cmus_mutex_unlock(&job_mutex) void job_init(void) { init_pipes(&job_fd, &job_fd_priv); worker_init(); } void job_exit(void) { worker_remove_jobs_by_type(JOB_TYPE_ANY); worker_exit(); close(job_fd); close(job_fd_priv); } static void job_push_result(struct job_result *res) { job_lock(); list_add_tail(&res->node, &job_result_head); job_unlock(); notify_via_pipe(job_fd_priv); } static struct job_result *job_pop_result(void) { struct job_result *res = NULL; job_lock(); if (!list_empty(&job_result_head)) { struct list_head *item = job_result_head.next; list_del(item); res = container_of(item, struct job_result, node); } job_unlock(); return res; } static void flush_ti_buffer(void) { struct job_result *res = xnew(struct job_result, 1); res->var = JOB_RES_ADD; res->add_cb = jd->add; res->add_num = ti_buffer_fill; res->add_ti = ti_buffer; res->add_opaque = jd->opaque; job_push_result(res); ti_buffer_fill = 0; ti_buffer = NULL; } static void add_ti(struct track_info *ti) { if (ti_buffer_fill == TI_CAP) flush_ti_buffer(); if (!ti_buffer) ti_buffer = xnew(struct track_info *, TI_CAP); ti_buffer[ti_buffer_fill++] = ti; } static int add_file_cue(const char *filename); static void add_file(const char *filename, int force) { struct track_info *ti; if (!is_cue_url(filename)) { if (force || lookup_cache_entry(filename, hash_str(filename)) == NULL) { int done = add_file_cue(filename); if (done) return; } } cache_lock(); ti = cache_get_ti(filename, force); cache_unlock(); if (ti) add_ti(ti); } static int add_file_cue(const char *filename) { if (!is_cue(filename)) return 0; int *track_nums; int n = cue_get_track_nums(filename, &track_nums); if (n == -1) return 0; for (int i = 0; i < n; i++) { char *url = construct_cue_url(filename, track_nums[i]); add_file(url, 0); free(url); } free(track_nums); return 1; } static void add_url(const char *url) { add_file(url, 0); } static void add_cdda(const char *url) { char *disc_id = NULL; int start_track = 1, end_track = -1; parse_cdda_url(url, &disc_id, &start_track, &end_track); if (end_track != -1) { int i; for (i = start_track; i <= end_track; i++) { char *new_url = gen_cdda_url(disc_id, i, -1); add_file(new_url, 0); free(new_url); } } else add_file(url, 0); free(disc_id); } struct dir_list_entry { struct list_head node; char *path; }; static void dir_list_truncate(struct list_head *head, struct list_head *new_tail) { if (head->prev == new_tail) return; struct list_head to_remove; to_remove.next = new_tail->next; new_tail->next->prev = &to_remove; to_remove.prev = head->prev; head->prev->next = &to_remove; head->prev = new_tail; new_tail->next = head; struct dir_list_entry *d, *next; list_for_each_entry_safe(d, next, &to_remove, node) { free(d->path); free(d); } } static void dir_list(const char *dirname, const char *root, struct ptr_array *files, struct list_head *dirs) { struct directory dir; const char *name; struct list_head *dirs_backup = list_prev(dirs); int files_backup = files->count; if (dir_open(&dir, dirname)) { d_print("error: opening %s: %s\n", dirname, strerror(errno)); return; } while ((name = dir_read(&dir))) { if (strcmp(name, ".nomusic") == 0 || strcmp(name, ".nomedia") == 0) { ptr_array_truncate(files, files_backup); dir_list_truncate(dirs, dirs_backup); break; } if (name[0] == '.') continue; if (S_ISDIR(dir.st.st_mode)) { if (!dir.is_link) { struct dir_list_entry *ent; ent = xnew(struct dir_list_entry, 1); ent->path = path_absolute_cwd(name, dirname); list_add_tail(&ent->node, dirs); } continue; } ptr_array_add(files, path_absolute_cwd(name, dirname)); } dir_close(&dir); } static void dir_list_recursive(const char *dirname, const char *root, struct ptr_array *files) { struct list_head dirs; list_init(&dirs); struct dir_list_entry *ent; ent = xnew(struct dir_list_entry, 1); ent->path = xstrdup(dirname); list_add_tail(&ent->node, &dirs); while (!list_empty(&dirs)) { struct list_head *tail = list_prev(&dirs); ent = list_entry(tail, struct dir_list_entry, node); dir_list(ent->path, root, files, &dirs); list_del(tail); free(ent->path); free(ent); } } static void handle_cue_files(struct ptr_array *files) { int i, j, k; char **ents = files->ptrs; bool *to_remove = xnew0(bool, files->count); for (i = 0; i < files->count; i++) { if (!ents[i] || !is_cue(ents[i])) continue; char **files_in_cue; int n = cue_get_files(ents[i], &files_in_cue); if (n == -1) continue; char *cue_dir = path_dirname(ents[i]); for (j = 0; j < n; j++) { char *path = path_absolute_cwd(files_in_cue[j], cue_dir); k = ptr_array_bsearch(&path, files, strptrcmp); if (k >= 0) to_remove[k] = true; free(path); free(files_in_cue[j]); } free(cue_dir); free(files_in_cue); } for (i = 0; i < files->count; i++) { if (to_remove[i]) { free(ents[i]); ents[i] = NULL; } } free(to_remove); } static void add_dir(const char *dirname, const char *root) { PTR_ARRAY(files); dir_list_recursive(dirname, root, &files); ptr_array_sort(&files, strptrcmp); ptr_array_unique(&files, strptrcmp); handle_cue_files(&files); int i; char **ents = files.ptrs; if (jd->add == play_queue_prepend) { for (i = files.count - 1; i >= 0 && !worker_cancelling(); i--) if (ents[i]) add_file(ents[i], 0); } else { for (i = 0; i < files.count && !worker_cancelling(); i++) if (ents[i]) add_file(ents[i], 0); } ptr_array_clear(&files); } static int handle_line(void *data, const char *line) { if (worker_cancelling()) return 1; if (is_http_url(line) || is_cue_url(line)) { add_url(line); } else { char *absolute = pl_env_var(line, NULL) ? pl_env_expand(line) : path_absolute_cwd(line, data); add_file(absolute, 0); free(absolute); } return 0; } static void add_pl(const char *filename) { char *buf; ssize_t size; int reverse; buf = mmap_file(filename, &size); if (size == -1) return; if (buf) { char *cwd = xstrjoin(filename, "/.."); /* beautiful hack */ reverse = jd->add == play_queue_prepend; cmus_playlist_for_each(buf, size, reverse, handle_line, cwd); free(cwd); munmap(buf, size); add_ti(NULL); // marks end of load } } static void do_add_job(void *data) { jd = data; switch (jd->type) { case FILE_TYPE_URL: add_url(jd->name); break; case FILE_TYPE_CDDA: add_cdda(jd->name); break; case FILE_TYPE_PL: add_pl(jd->name); break; case FILE_TYPE_DIR: add_dir(jd->name, jd->name); break; case FILE_TYPE_FILE: add_file(jd->name, jd->force); break; case FILE_TYPE_INVALID: break; } if (ti_buffer) flush_ti_buffer(); jd = NULL; } static void free_add_job(void *data) { struct add_data *d = data; free(d->name); free(d); } static void job_handle_add_result(struct job_result *res) { for (size_t i = 0; i < res->add_num; i++) { res->add_cb(res->add_ti[i], res->add_opaque); if (res->add_ti[i] != NULL) track_info_unref(res->add_ti[i]); } free(res->add_ti); } void job_schedule_add(int type, struct add_data *data) { worker_add_job(type | JOB_TYPE_ADD, do_add_job, free_add_job, data); } static void do_update_job(void *data) { struct update_data *d = data; int i; enum update_kind *kind = xnew(enum update_kind, d->used); struct job_result *res; for (i = 0; i < d->used; i++) { struct track_info *ti = d->ti[i]; struct stat s; int rc; rc = stat(ti->filename, &s); if (rc || d->force || ti->mtime != s.st_mtime || ti->duration == 0) { kind[i] = UPDATE_NONE; if (!is_cue_url(ti->filename) && !is_http_url(ti->filename) && rc) kind[i] |= UPDATE_REMOVE; else if (ti->mtime != s.st_mtime) kind[i] |= UPDATE_MTIME_CHANGED; } else { track_info_unref(ti); d->ti[i] = NULL; } } res = xnew(struct job_result, 1); res->var = JOB_RES_UPDATE; res->update_num = d->used; res->update_ti = d->ti; res->update_kind = kind; job_push_result(res); d->ti = NULL; } static void free_update_job(void *data) { struct update_data *d = data; if (d->ti) { for (size_t i = 0; i < d->used; i++) track_info_unref(d->ti[i]); free(d->ti); } free(d); } static void job_handle_update_result(struct job_result *res) { for (size_t i = 0; i < res->update_num; i++) { struct track_info *ti = res->update_ti[i]; int force; if (!ti) continue; lib_remove(ti); cache_lock(); cache_remove_ti(ti); cache_unlock(); if (res->update_kind[i] & UPDATE_REMOVE) { d_print("removing dead file %s\n", ti->filename); } else { if (res->update_kind[i] & UPDATE_MTIME_CHANGED) d_print("mtime changed: %s\n", ti->filename); force = ti->duration == 0; cmus_add(lib_add_track, ti->filename, FILE_TYPE_FILE, JOB_TYPE_LIB, force, NULL); } track_info_unref(ti); } free(res->update_kind); free(res->update_ti); } void job_schedule_update(struct update_data *data) { worker_add_job(JOB_TYPE_LIB | JOB_TYPE_UPDATE, do_update_job, free_update_job, data); } static void do_update_cache_job(void *data) { struct update_cache_data *d = data; int count; struct track_info **tis; struct job_result *res; cache_lock(); tis = cache_refresh(&count, d->force); cache_unlock(); res = xnew(struct job_result, 1); res->var = JOB_RES_UPDATE_CACHE; res->update_cache_ti = tis; res->update_cache_num = count; job_push_result(res); } static void free_update_cache_job(void *data) { free(data); } static void job_handle_update_cache_result(struct job_result *res) { for (size_t i = 0; i < res->update_cache_num; i++) { struct track_info *new, *old = res->update_cache_ti[i]; if (!old) continue; new = old->next; if (lib_remove(old) && new) lib_add_track(new, NULL); pl_update_track(old, new); editable_update_track(&pq_editable, old, new); if (player_info.ti == old && new) { track_info_ref(new); player_file_changed(new); } track_info_unref(old); if (new) track_info_unref(new); } free(res->update_cache_ti); } void job_schedule_update_cache(int type, struct update_cache_data *data) { worker_add_job(type | JOB_TYPE_UPDATE_CACHE, do_update_cache_job, free_update_cache_job, data); } static void do_pl_delete_job(void *data) { /* * If PL jobs are canceled this function won't run. Hence we push the * result in the free function. */ } static void free_pl_delete_job(void *data) { struct pl_delete_data *pdd = data; struct job_result *res; res = xnew(struct job_result, 1); res->var = JOB_RES_PL_DELETE; res->pl_delete_cb = pdd->cb; res->pl_delete_pl = pdd->pl; job_push_result(res); free(pdd); } static void job_handle_pl_delete_result(struct job_result *res) { res->pl_delete_cb(res->pl_delete_pl); } void job_schedule_pl_delete(struct pl_delete_data *data) { worker_add_job(JOB_TYPE_PL | JOB_TYPE_DELETE, do_pl_delete_job, free_pl_delete_job, data); } static void job_handle_result(struct job_result *res) { switch (res->var) { case JOB_RES_ADD: job_handle_add_result(res); break; case JOB_RES_UPDATE: job_handle_update_result(res); break; case JOB_RES_UPDATE_CACHE: job_handle_update_cache_result(res); break; case JOB_RES_PL_DELETE: job_handle_pl_delete_result(res); break; } free(res); } void job_handle(void) { clear_pipe(job_fd, -1); struct job_result *res; while ((res = job_pop_result())) job_handle_result(res); }