Pass struct track_info to player functions instead of filename
[cmus.git] / cmus.c
blob368f403c890196a6957fa98522176a161ac835e5
1 #include "cmus.h"
2 #include "lib.h"
3 #include "pl.h"
4 #include "player.h"
5 #include "input.h"
6 #include "play_queue.h"
7 #include "worker.h"
8 #include "track_db.h"
9 #include "misc.h"
10 #include "file.h"
11 #include "utils.h"
12 #include "path.h"
13 #include "options.h"
14 #include "xmalloc.h"
15 #include "xstrjoin.h"
16 #include "debug.h"
17 #include "load_dir.h"
18 #include "ui_curses.h"
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <fcntl.h>
23 #include <unistd.h>
24 #include <dirent.h>
25 #include <stdlib.h>
26 #include <ctype.h>
28 static pthread_mutex_t track_db_mutex = CMUS_MUTEX_INITIALIZER;
29 static struct track_db *track_db;
30 static char **playable_exts;
31 static const char * const playlist_exts[] = { "m3u", "pl", "pls", NULL };
33 #define track_db_lock() cmus_mutex_lock(&track_db_mutex)
34 #define track_db_unlock() cmus_mutex_unlock(&track_db_mutex)
36 /* add (worker job) {{{ */
38 struct add_data {
39 enum file_type type;
40 char *name;
41 add_ti_cb add;
44 static struct track_info *track_info_url_new(const char *url)
46 struct track_info *ti = track_info_new(url);
47 ti->comments = xnew0(struct keyval, 1);
48 ti->duration = -1;
49 ti->mtime = -1;
50 return ti;
53 static void add_url(add_ti_cb add, const char *filename)
55 struct track_info *ti;
57 ti = track_info_url_new(filename);
58 editable_lock();
59 add(ti);
60 editable_unlock();
61 track_info_unref(ti);
64 /* add file to the playlist
66 * @filename: absolute filename with extraneous slashes stripped
68 static void add_file(add_ti_cb add, const char *filename)
70 struct track_info *ti;
72 track_db_lock();
73 ti = track_db_get_track(track_db, filename);
74 track_db_unlock();
76 if (ti == NULL)
77 return;
79 editable_lock();
80 add(ti);
81 editable_unlock();
82 track_info_unref(ti);
85 static int dir_entry_cmp(const void *ap, const void *bp)
87 struct dir_entry *a = *(struct dir_entry **)ap;
88 struct dir_entry *b = *(struct dir_entry **)bp;
90 return strcmp(a->name, b->name);
93 static int dir_entry_cmp_reverse(const void *ap, const void *bp)
95 struct dir_entry *a = *(struct dir_entry **)ap;
96 struct dir_entry *b = *(struct dir_entry **)bp;
98 return strcmp(b->name, a->name);
101 static int points_within(const char *target, const char *root)
103 int tlen = strlen(target);
104 int rlen = strlen(root);
106 if (rlen > tlen)
107 return 0;
108 if (strncmp(target, root, rlen))
109 return 0;
110 return target[rlen] == '/' || !target[rlen];
113 static void do_add_dir(add_ti_cb add, const char *dirname, const char *root)
115 struct directory dir;
116 struct dir_entry **ents;
117 const char *name;
118 PTR_ARRAY(array);
119 int i;
121 if (dir_open(&dir, dirname)) {
122 d_print("error: opening %s: %s\n", dirname, strerror(errno));
123 return;
125 while ((name = dir_read(&dir))) {
126 struct dir_entry *ent;
127 int size;
129 if (name[0] == '.')
130 continue;
132 if (dir.is_link) {
133 char buf[1024];
134 char *target;
135 int rc = readlink(dir.path, buf, sizeof(buf));
137 if (rc < 0 || rc == sizeof(buf))
138 continue;
139 buf[rc] = 0;
140 target = path_absolute_cwd(buf, dirname);
141 if (points_within(target, root)) {
142 /* symlink points withing the root */
143 d_print("%s -> %s points within %s. ignoring\n",
144 dir.path, target, root);
145 free(target);
146 continue;
148 free(target);
151 size = strlen(name) + 1;
152 ent = xmalloc(sizeof(struct dir_entry) + size);
153 ent->mode = dir.st.st_mode;
154 memcpy(ent->name, name, size);
155 ptr_array_add(&array, ent);
157 dir_close(&dir);
159 if (add == play_queue_prepend) {
160 ptr_array_sort(&array, dir_entry_cmp_reverse);
161 } else {
162 ptr_array_sort(&array, dir_entry_cmp);
164 ents = array.ptrs;
165 for (i = 0; i < array.count; i++) {
166 if (!worker_cancelling()) {
167 /* abuse dir.path because
168 * - it already contains dirname + '/'
169 * - it is guaranteed to be large enough
171 int len = strlen(ents[i]->name);
173 memcpy(dir.path + dir.len, ents[i]->name, len + 1);
174 if (S_ISDIR(ents[i]->mode)) {
175 do_add_dir(add, dir.path, root);
176 } else {
177 add_file(add, dir.path);
180 free(ents[i]);
182 free(ents);
185 static void add_dir(add_ti_cb add, const char *dirname)
187 do_add_dir(add, dirname, dirname);
190 static int handle_line(void *data, const char *line)
192 add_ti_cb add = data;
194 if (worker_cancelling())
195 return 1;
197 if (is_url(line)) {
198 add_url(add, line);
199 } else {
200 add_file(add, line);
202 return 0;
205 static void add_pl(add_ti_cb add, const char *filename)
207 char *buf;
208 int size, reverse;
210 buf = mmap_file(filename, &size);
211 if (size == -1)
212 return;
214 if (buf) {
215 /* beautiful hack */
216 reverse = add == play_queue_prepend;
218 cmus_playlist_for_each(buf, size, reverse, handle_line, add);
219 munmap(buf, size);
223 static void do_add_job(void *data)
225 struct add_data *jd = data;
227 switch (jd->type) {
228 case FILE_TYPE_URL:
229 add_url(jd->add, jd->name);
230 break;
231 case FILE_TYPE_PL:
232 add_pl(jd->add, jd->name);
233 break;
234 case FILE_TYPE_DIR:
235 add_dir(jd->add, jd->name);
236 break;
237 case FILE_TYPE_FILE:
238 add_file(jd->add, jd->name);
239 break;
240 case FILE_TYPE_INVALID:
241 break;
245 static void free_add_job(void *data)
247 struct add_data *jd = data;
249 free(jd->name);
250 free(jd);
253 /* }}} */
255 /* update (worker job) {{{ */
257 struct update_data {
258 size_t size;
259 size_t used;
260 struct track_info **ti;
263 static void do_update_job(void *data)
265 struct update_data *d = data;
266 int i;
268 for (i = 0; i < d->used; i++) {
269 struct track_info *ti = d->ti[i];
270 struct stat s;
272 /* stat follows symlinks, lstat does not */
273 if (stat(ti->filename, &s) == -1) {
274 d_print("removing dead file %s\n", ti->filename);
275 editable_lock();
276 lib_remove(ti);
277 editable_unlock();
278 } else if (ti->mtime != s.st_mtime) {
279 d_print("mtime changed: %s\n", ti->filename);
280 editable_lock();
281 lib_remove(ti);
282 editable_unlock();
284 cmus_add(lib_add_track, ti->filename, FILE_TYPE_FILE, JOB_TYPE_LIB);
286 track_info_unref(ti);
290 static void free_update_job(void *data)
292 struct update_data *d = data;
294 free(d->ti);
295 free(d);
298 /* }}} */
300 int cmus_init(void)
302 char *db_filename_base;
304 playable_exts = ip_get_supported_extensions();
306 db_filename_base = xstrjoin(cmus_config_dir, "/trackdb");
307 track_db = track_db_new(db_filename_base);
308 free(db_filename_base);
310 worker_init();
312 play_queue_init();
313 return 0;
316 void cmus_exit(void)
318 worker_remove_jobs(JOB_TYPE_ANY);
319 worker_exit();
320 if (track_db_close(track_db))
321 d_print("error: %s\n", strerror(errno));
324 void cmus_next(void)
326 struct track_info *info;
328 editable_lock();
329 info = play_queue_remove();
330 if (info == NULL) {
331 if (play_library) {
332 info = lib_set_next();
333 } else {
334 info = pl_set_next();
337 editable_unlock();
339 if (info)
340 player_set_file(info);
343 void cmus_prev(void)
345 struct track_info *info;
347 editable_lock();
348 if (play_library) {
349 info = lib_set_prev();
350 } else {
351 info = pl_set_prev();
353 editable_unlock();
355 if (info)
356 player_set_file(info);
359 void cmus_play_file(const char *filename)
361 struct track_info *ti;
363 track_db_lock();
364 ti = track_db_get_track(track_db, filename);
365 track_db_unlock();
367 if (!ti) {
368 error_msg("Couldn't get file information for %s\n", filename);
369 return;
371 player_play_file(ti);
374 enum file_type cmus_detect_ft(const char *name, char **ret)
376 char *absolute;
377 struct stat st;
379 if (is_url(name)) {
380 *ret = xstrdup(name);
381 return FILE_TYPE_URL;
384 *ret = NULL;
385 absolute = path_absolute(name);
386 if (absolute == NULL)
387 return FILE_TYPE_INVALID;
389 /* stat follows symlinks, lstat does not */
390 if (stat(absolute, &st) == -1) {
391 free(absolute);
392 return FILE_TYPE_INVALID;
395 if (S_ISDIR(st.st_mode)) {
396 *ret = absolute;
397 return FILE_TYPE_DIR;
399 if (!S_ISREG(st.st_mode)) {
400 free(absolute);
401 errno = EINVAL;
402 return FILE_TYPE_INVALID;
405 *ret = absolute;
406 if (cmus_is_playlist(absolute))
407 return FILE_TYPE_PL;
409 /* NOTE: it could be FILE_TYPE_PL too! */
410 return FILE_TYPE_FILE;
413 void cmus_add(add_ti_cb add, const char *name, enum file_type ft, int jt)
415 struct add_data *data = xnew(struct add_data, 1);
417 data->add = add;
418 data->name = xstrdup(name);
419 data->type = ft;
420 worker_add_job(jt, do_add_job, free_add_job, data);
423 static int save_playlist_cb(void *data, struct track_info *ti)
425 int fd = *(int *)data;
426 const char nl = '\n';
427 int rc;
429 rc = write_all(fd, ti->filename, strlen(ti->filename));
430 if (rc == -1)
431 return -1;
432 rc = write_all(fd, &nl, 1);
433 if (rc == -1)
434 return -1;
435 return 0;
438 int cmus_save(for_each_ti_cb for_each_ti, const char *filename)
440 int fd, rc;
442 fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
443 if (fd == -1)
444 return -1;
445 rc = for_each_ti(save_playlist_cb, &fd);
446 close(fd);
447 return rc;
450 static int update_cb(void *data, struct track_info *ti)
452 struct update_data *d = data;
454 if (is_url(ti->filename))
455 return 0;
457 if (d->size == d->used) {
458 if (d->size == 0)
459 d->size = 16;
460 d->size *= 2;
461 d->ti = xrealloc(d->ti, d->size * sizeof(struct track_info *));
463 track_info_ref(ti);
464 d->ti[d->used++] = ti;
465 return 0;
468 void cmus_update_lib(void)
470 struct update_data *data;
472 data = xnew(struct update_data, 1);
473 data->size = 0;
474 data->used = 0;
475 data->ti = NULL;
477 editable_lock();
478 lib_for_each(update_cb, data);
479 editable_unlock();
481 worker_add_job(JOB_TYPE_LIB, do_update_job, free_update_job, data);
484 void cmus_update_tis(struct track_info **tis, int nr)
486 struct update_data *data;
488 data = xnew(struct update_data, 1);
489 data->size = nr;
490 data->used = nr;
491 data->ti = tis;
492 worker_add_job(JOB_TYPE_LIB, do_update_job, free_update_job, data);
495 struct track_info *cmus_get_track_info(const char *name)
497 struct track_info *ti;
499 if (is_url(name))
500 return track_info_url_new(name);
502 track_db_lock();
503 ti = track_db_get_track(track_db, name);
504 track_db_unlock();
505 return ti;
508 static const char *get_ext(const char *filename)
510 const char *ext = strrchr(filename, '.');
512 if (ext)
513 ext++;
514 return ext;
517 static int str_in_array(const char *str, const char * const * array)
519 int i;
521 for (i = 0; array[i]; i++) {
522 if (strcasecmp(str, array[i]) == 0)
523 return 1;
525 return 0;
528 int cmus_is_playlist(const char *filename)
530 const char *ext = get_ext(filename);
532 return ext && str_in_array(ext, playlist_exts);
535 int cmus_is_playable(const char *filename)
537 const char *ext = get_ext(filename);
539 return ext && str_in_array(ext, (const char * const *)playable_exts);
542 int cmus_is_supported(const char *filename)
544 const char *ext = get_ext(filename);
546 return ext && (str_in_array(ext, (const char * const *)playable_exts) ||
547 str_in_array(ext, playlist_exts));
550 struct pl_data {
551 int (*cb)(void *data, const char *line);
552 void *data;
555 static int pl_handle_line(void *data, const char *line)
557 struct pl_data *d = data;
558 int i = 0;
560 while (isspace(line[i]))
561 i++;
562 if (line[i] == 0)
563 return 0;
565 if (line[i] == '#')
566 return 0;
568 return d->cb(d->data, line);
571 static int pls_handle_line(void *data, const char *line)
573 struct pl_data *d = data;
575 if (strncasecmp(line, "file", 4))
576 return 0;
577 line = strchr(line, '=');
578 if (line == NULL)
579 return 0;
580 return d->cb(d->data, line + 1);
583 int cmus_playlist_for_each(const char *buf, int size, int reverse,
584 int (*cb)(void *data, const char *line),
585 void *data)
587 struct pl_data d = { cb, data };
588 int (*handler)(void *, const char *);
590 handler = pl_handle_line;
591 if (size >= 10 && strncasecmp(buf, "[playlist]", 10) == 0)
592 handler = pls_handle_line;
594 if (reverse) {
595 buffer_for_each_line_reverse(buf, size, handler, &d);
596 } else {
597 buffer_for_each_line(buf, size, handler, &d);
599 return 0;