push
This commit is contained in:
672
job.c
Normal file
672
job.c
Normal file
@@ -0,0 +1,672 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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 <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user