wav: Don't read incomplete frame at end of a truncated file
[cmus.git] / cmus.c
blob50aafaf638dc89f3b349a0dd1819159833512d8b
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"
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <fcntl.h>
22 #include <unistd.h>
23 #include <dirent.h>
24 #include <stdlib.h>
25 #include <ctype.h>
27 static pthread_mutex_t track_db_mutex = CMUS_MUTEX_INITIALIZER;
28 static struct track_db *track_db;
29 static char **playable_exts;
30 static const char * const playlist_exts[] = { "m3u", "pl", "pls", NULL };
32 #define track_db_lock() cmus_mutex_lock(&track_db_mutex)
33 #define track_db_unlock() cmus_mutex_unlock(&track_db_mutex)
35 /* add (worker job) {{{ */
37 struct add_data {
38 enum file_type type;
39 char *name;
40 add_ti_cb add;
43 static struct track_info *track_info_url_new(const char *url)
45 struct track_info *ti = track_info_new(url);
46 ti->comments = xnew0(struct keyval, 1);
47 ti->duration = -1;
48 ti->mtime = -1;
49 return ti;
52 static void add_url(add_ti_cb add, const char *filename)
54 struct track_info *ti;
56 ti = track_info_url_new(filename);
57 editable_lock();
58 add(ti);
59 editable_unlock();
60 track_info_unref(ti);
63 /* add file to the playlist
65 * @filename: absolute filename with extraneous slashes stripped
67 static void add_file(add_ti_cb add, const char *filename)
69 struct track_info *ti;
71 track_db_lock();
72 ti = track_db_get_track(track_db, filename);
73 track_db_unlock();
75 if (ti == NULL)
76 return;
78 editable_lock();
79 add(ti);
80 editable_unlock();
81 track_info_unref(ti);
84 static int dir_entry_cmp(const void *ap, const void *bp)
86 struct dir_entry *a = *(struct dir_entry **)ap;
87 struct dir_entry *b = *(struct dir_entry **)bp;
89 return strcmp(a->name, b->name);
92 static int dir_entry_cmp_reverse(const void *ap, const void *bp)
94 struct dir_entry *a = *(struct dir_entry **)ap;
95 struct dir_entry *b = *(struct dir_entry **)bp;
97 return strcmp(b->name, a->name);
100 static int points_within(const char *target, const char *root)
102 int tlen = strlen(target);
103 int rlen = strlen(root);
105 if (rlen > tlen)
106 return 0;
107 if (strncmp(target, root, rlen))
108 return 0;
109 return target[rlen] == '/' || !target[rlen];
112 static void do_add_dir(add_ti_cb add, const char *dirname, const char *root)
114 struct directory dir;
115 struct dir_entry **ents;
116 const char *name;
117 PTR_ARRAY(array);
118 int i;
120 if (dir_open(&dir, dirname)) {
121 d_print("error: opening %s: %s\n", dirname, strerror(errno));
122 return;
124 while ((name = dir_read(&dir))) {
125 struct dir_entry *ent;
126 int size;
128 if (name[0] == '.')
129 continue;
131 if (dir.is_link) {
132 char buf[1024];
133 char *target;
134 int rc = readlink(dir.path, buf, sizeof(buf));
136 if (rc < 0 || rc == sizeof(buf))
137 continue;
138 buf[rc] = 0;
139 target = path_absolute_cwd(buf, dirname);
140 if (points_within(target, root)) {
141 /* symlink points withing the root */
142 d_print("%s -> %s points within %s. ignoring\n",
143 dir.path, target, root);
144 free(target);
145 continue;
147 free(target);
150 size = strlen(name) + 1;
151 ent = xmalloc(sizeof(struct dir_entry) + size);
152 ent->mode = dir.st.st_mode;
153 memcpy(ent->name, name, size);
154 ptr_array_add(&array, ent);
156 dir_close(&dir);
158 if (add == play_queue_prepend) {
159 ptr_array_sort(&array, dir_entry_cmp_reverse);
160 } else {
161 ptr_array_sort(&array, dir_entry_cmp);
163 ents = array.ptrs;
164 for (i = 0; i < array.count; i++) {
165 if (!worker_cancelling()) {
166 /* abuse dir.path because
167 * - it already contains dirname + '/'
168 * - it is guaranteed to be large enough
170 int len = strlen(ents[i]->name);
172 memcpy(dir.path + dir.len, ents[i]->name, len + 1);
173 if (S_ISDIR(ents[i]->mode)) {
174 do_add_dir(add, dir.path, root);
175 } else {
176 add_file(add, dir.path);
179 free(ents[i]);
181 free(ents);
184 static void add_dir(add_ti_cb add, const char *dirname)
186 do_add_dir(add, dirname, dirname);
189 static int handle_line(void *data, const char *line)
191 add_ti_cb add = data;
193 if (worker_cancelling())
194 return 1;
196 if (is_url(line)) {
197 add_url(add, line);
198 } else {
199 add_file(add, line);
201 return 0;
204 static void add_pl(add_ti_cb add, const char *filename)
206 char *buf;
207 int size, reverse;
209 buf = mmap_file(filename, &size);
210 if (size == -1)
211 return;
213 if (buf) {
214 /* beautiful hack */
215 reverse = add == play_queue_prepend;
217 cmus_playlist_for_each(buf, size, reverse, handle_line, add);
218 munmap(buf, size);
222 static void do_add_job(void *data)
224 struct add_data *jd = data;
226 switch (jd->type) {
227 case FILE_TYPE_URL:
228 add_url(jd->add, jd->name);
229 break;
230 case FILE_TYPE_PL:
231 add_pl(jd->add, jd->name);
232 break;
233 case FILE_TYPE_DIR:
234 add_dir(jd->add, jd->name);
235 break;
236 case FILE_TYPE_FILE:
237 add_file(jd->add, jd->name);
238 break;
239 case FILE_TYPE_INVALID:
240 break;
244 static void free_add_job(void *data)
246 struct add_data *jd = data;
248 free(jd->name);
249 free(jd);
252 /* }}} */
254 /* update (worker job) {{{ */
256 struct update_data {
257 size_t size;
258 size_t used;
259 struct track_info **ti;
262 static void do_update_job(void *data)
264 struct update_data *d = data;
265 int i;
267 for (i = 0; i < d->used; i++) {
268 struct track_info *ti = d->ti[i];
269 struct stat s;
271 /* stat follows symlinks, lstat does not */
272 if (stat(ti->filename, &s) == -1) {
273 d_print("removing dead file %s\n", ti->filename);
274 editable_lock();
275 lib_remove(ti);
276 editable_unlock();
277 } else if (ti->mtime != s.st_mtime) {
278 d_print("mtime changed: %s\n", ti->filename);
279 editable_lock();
280 lib_remove(ti);
281 editable_unlock();
283 cmus_add(lib_add_track, ti->filename, FILE_TYPE_FILE, JOB_TYPE_LIB);
285 track_info_unref(ti);
289 static void free_update_job(void *data)
291 struct update_data *d = data;
293 free(d->ti);
294 free(d);
297 /* }}} */
299 int cmus_init(void)
301 char *db_filename_base;
303 playable_exts = ip_get_supported_extensions();
305 db_filename_base = xstrjoin(cmus_config_dir, "/trackdb");
306 track_db = track_db_new(db_filename_base);
307 free(db_filename_base);
309 worker_init();
311 play_queue_init();
312 return 0;
315 void cmus_exit(void)
317 worker_remove_jobs(JOB_TYPE_ANY);
318 worker_exit();
319 if (track_db_close(track_db))
320 d_print("error: %s\n", strerror(errno));
323 void cmus_next(void)
325 struct track_info *info;
327 editable_lock();
328 info = play_queue_remove();
329 if (info == NULL) {
330 if (play_library) {
331 info = lib_set_next();
332 } else {
333 info = pl_set_next();
336 editable_unlock();
338 if (info) {
339 player_set_file(info->filename);
340 track_info_unref(info);
344 void cmus_prev(void)
346 struct track_info *info;
348 editable_lock();
349 if (play_library) {
350 info = lib_set_prev();
351 } else {
352 info = pl_set_prev();
354 editable_unlock();
356 if (info) {
357 player_set_file(info->filename);
358 track_info_unref(info);
362 void cmus_play_file(const char *filename)
364 player_play_file(filename);
367 enum file_type cmus_detect_ft(const char *name, char **ret)
369 char *absolute;
370 struct stat st;
372 if (is_url(name)) {
373 *ret = xstrdup(name);
374 return FILE_TYPE_URL;
377 *ret = NULL;
378 absolute = path_absolute(name);
379 if (absolute == NULL)
380 return FILE_TYPE_INVALID;
382 /* stat follows symlinks, lstat does not */
383 if (stat(absolute, &st) == -1) {
384 free(absolute);
385 return FILE_TYPE_INVALID;
388 if (S_ISDIR(st.st_mode)) {
389 *ret = absolute;
390 return FILE_TYPE_DIR;
392 if (!S_ISREG(st.st_mode)) {
393 free(absolute);
394 errno = EINVAL;
395 return FILE_TYPE_INVALID;
398 *ret = absolute;
399 if (cmus_is_playlist(absolute))
400 return FILE_TYPE_PL;
402 /* NOTE: it could be FILE_TYPE_PL too! */
403 return FILE_TYPE_FILE;
406 void cmus_add(add_ti_cb add, const char *name, enum file_type ft, int jt)
408 struct add_data *data = xnew(struct add_data, 1);
410 data->add = add;
411 data->name = xstrdup(name);
412 data->type = ft;
413 worker_add_job(jt, do_add_job, free_add_job, data);
416 static int save_playlist_cb(void *data, struct track_info *ti)
418 int fd = *(int *)data;
419 const char nl = '\n';
420 int rc;
422 rc = write_all(fd, ti->filename, strlen(ti->filename));
423 if (rc == -1)
424 return -1;
425 rc = write_all(fd, &nl, 1);
426 if (rc == -1)
427 return -1;
428 return 0;
431 int cmus_save(for_each_ti_cb for_each_ti, const char *filename)
433 int fd, rc;
435 fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
436 if (fd == -1)
437 return -1;
438 rc = for_each_ti(save_playlist_cb, &fd);
439 close(fd);
440 return rc;
443 static int update_cb(void *data, struct track_info *ti)
445 struct update_data *d = data;
447 if (is_url(ti->filename))
448 return 0;
450 if (d->size == d->used) {
451 if (d->size == 0)
452 d->size = 16;
453 d->size *= 2;
454 d->ti = xrealloc(d->ti, d->size * sizeof(struct track_info *));
456 track_info_ref(ti);
457 d->ti[d->used++] = ti;
458 return 0;
461 void cmus_update_lib(void)
463 struct update_data *data;
465 data = xnew(struct update_data, 1);
466 data->size = 0;
467 data->used = 0;
468 data->ti = NULL;
470 editable_lock();
471 lib_for_each(update_cb, data);
472 editable_unlock();
474 worker_add_job(JOB_TYPE_LIB, do_update_job, free_update_job, data);
477 void cmus_update_tis(struct track_info **tis, int nr)
479 struct update_data *data;
481 data = xnew(struct update_data, 1);
482 data->size = nr;
483 data->used = nr;
484 data->ti = tis;
485 worker_add_job(JOB_TYPE_LIB, do_update_job, free_update_job, data);
488 struct track_info *cmus_get_track_info(const char *name)
490 struct track_info *ti;
492 if (is_url(name))
493 return track_info_url_new(name);
495 track_db_lock();
496 ti = track_db_get_track(track_db, name);
497 track_db_unlock();
498 return ti;
501 static const char *get_ext(const char *filename)
503 const char *ext = strrchr(filename, '.');
505 if (ext)
506 ext++;
507 return ext;
510 static int str_in_array(const char *str, const char * const * array)
512 int i;
514 for (i = 0; array[i]; i++) {
515 if (strcasecmp(str, array[i]) == 0)
516 return 1;
518 return 0;
521 int cmus_is_playlist(const char *filename)
523 const char *ext = get_ext(filename);
525 return ext && str_in_array(ext, playlist_exts);
528 int cmus_is_playable(const char *filename)
530 const char *ext = get_ext(filename);
532 return ext && str_in_array(ext, (const char * const *)playable_exts);
535 int cmus_is_supported(const char *filename)
537 const char *ext = get_ext(filename);
539 return ext && (str_in_array(ext, (const char * const *)playable_exts) ||
540 str_in_array(ext, playlist_exts));
543 struct pl_data {
544 int (*cb)(void *data, const char *line);
545 void *data;
548 static int pl_handle_line(void *data, const char *line)
550 struct pl_data *d = data;
551 int i = 0;
553 while (isspace(line[i]))
554 i++;
555 if (line[i] == 0)
556 return 0;
558 if (line[i] == '#')
559 return 0;
561 return d->cb(d->data, line);
564 static int pls_handle_line(void *data, const char *line)
566 struct pl_data *d = data;
568 if (strncasecmp(line, "file", 4))
569 return 0;
570 line = strchr(line, '=');
571 if (line == NULL)
572 return 0;
573 return d->cb(d->data, line + 1);
576 int cmus_playlist_for_each(const char *buf, int size, int reverse,
577 int (*cb)(void *data, const char *line),
578 void *data)
580 struct pl_data d = { cb, data };
581 int (*handler)(void *, const char *);
583 handler = pl_handle_line;
584 if (size >= 10 && strncasecmp(buf, "[playlist]", 10) == 0)
585 handler = pls_handle_line;
587 if (reverse) {
588 buffer_for_each_line_reverse(buf, size, handler, &d);
589 } else {
590 buffer_for_each_line(buf, size, handler, &d);
592 return 0;