Don't allow saving if tracks are being added
[cmus.git] / cmus.c
bloba2d9dac11a894925e9e5c34e17208a3b741eb0f0
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 if (is_url(filename)) {
364 ti = track_info_url_new(filename);
365 } else {
366 track_db_lock();
367 ti = track_db_get_track(track_db, filename);
368 track_db_unlock();
369 if (!ti) {
370 error_msg("Couldn't get file information for %s\n", filename);
371 return;
374 player_play_file(ti);
377 enum file_type cmus_detect_ft(const char *name, char **ret)
379 char *absolute;
380 struct stat st;
382 if (is_url(name)) {
383 *ret = xstrdup(name);
384 return FILE_TYPE_URL;
387 *ret = NULL;
388 absolute = path_absolute(name);
389 if (absolute == NULL)
390 return FILE_TYPE_INVALID;
392 /* stat follows symlinks, lstat does not */
393 if (stat(absolute, &st) == -1) {
394 free(absolute);
395 return FILE_TYPE_INVALID;
398 if (S_ISDIR(st.st_mode)) {
399 *ret = absolute;
400 return FILE_TYPE_DIR;
402 if (!S_ISREG(st.st_mode)) {
403 free(absolute);
404 errno = EINVAL;
405 return FILE_TYPE_INVALID;
408 *ret = absolute;
409 if (cmus_is_playlist(absolute))
410 return FILE_TYPE_PL;
412 /* NOTE: it could be FILE_TYPE_PL too! */
413 return FILE_TYPE_FILE;
416 void cmus_add(add_ti_cb add, const char *name, enum file_type ft, int jt)
418 struct add_data *data = xnew(struct add_data, 1);
420 data->add = add;
421 data->name = xstrdup(name);
422 data->type = ft;
423 worker_add_job(jt, do_add_job, free_add_job, data);
426 static int save_playlist_cb(void *data, struct track_info *ti)
428 int fd = *(int *)data;
429 const char nl = '\n';
430 int rc;
432 rc = write_all(fd, ti->filename, strlen(ti->filename));
433 if (rc == -1)
434 return -1;
435 rc = write_all(fd, &nl, 1);
436 if (rc == -1)
437 return -1;
438 return 0;
441 int cmus_save(for_each_ti_cb for_each_ti, const char *filename)
443 int fd, rc;
445 fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
446 if (fd == -1)
447 return -1;
448 rc = for_each_ti(save_playlist_cb, &fd);
449 close(fd);
450 return rc;
453 static int update_cb(void *data, struct track_info *ti)
455 struct update_data *d = data;
457 if (is_url(ti->filename))
458 return 0;
460 if (d->size == d->used) {
461 if (d->size == 0)
462 d->size = 16;
463 d->size *= 2;
464 d->ti = xrealloc(d->ti, d->size * sizeof(struct track_info *));
466 track_info_ref(ti);
467 d->ti[d->used++] = ti;
468 return 0;
471 void cmus_update_lib(void)
473 struct update_data *data;
475 data = xnew(struct update_data, 1);
476 data->size = 0;
477 data->used = 0;
478 data->ti = NULL;
480 editable_lock();
481 lib_for_each(update_cb, data);
482 editable_unlock();
484 worker_add_job(JOB_TYPE_LIB, do_update_job, free_update_job, data);
487 void cmus_update_tis(struct track_info **tis, int nr)
489 struct update_data *data;
491 data = xnew(struct update_data, 1);
492 data->size = nr;
493 data->used = nr;
494 data->ti = tis;
495 worker_add_job(JOB_TYPE_LIB, do_update_job, free_update_job, data);
498 struct track_info *cmus_get_track_info(const char *name)
500 struct track_info *ti;
502 if (is_url(name))
503 return track_info_url_new(name);
505 track_db_lock();
506 ti = track_db_get_track(track_db, name);
507 track_db_unlock();
508 return ti;
511 static const char *get_ext(const char *filename)
513 const char *ext = strrchr(filename, '.');
515 if (ext)
516 ext++;
517 return ext;
520 static int str_in_array(const char *str, const char * const * array)
522 int i;
524 for (i = 0; array[i]; i++) {
525 if (strcasecmp(str, array[i]) == 0)
526 return 1;
528 return 0;
531 int cmus_is_playlist(const char *filename)
533 const char *ext = get_ext(filename);
535 return ext && str_in_array(ext, playlist_exts);
538 int cmus_is_playable(const char *filename)
540 const char *ext = get_ext(filename);
542 return ext && str_in_array(ext, (const char * const *)playable_exts);
545 int cmus_is_supported(const char *filename)
547 const char *ext = get_ext(filename);
549 return ext && (str_in_array(ext, (const char * const *)playable_exts) ||
550 str_in_array(ext, playlist_exts));
553 struct pl_data {
554 int (*cb)(void *data, const char *line);
555 void *data;
558 static int pl_handle_line(void *data, const char *line)
560 struct pl_data *d = data;
561 int i = 0;
563 while (isspace(line[i]))
564 i++;
565 if (line[i] == 0)
566 return 0;
568 if (line[i] == '#')
569 return 0;
571 return d->cb(d->data, line);
574 static int pls_handle_line(void *data, const char *line)
576 struct pl_data *d = data;
578 if (strncasecmp(line, "file", 4))
579 return 0;
580 line = strchr(line, '=');
581 if (line == NULL)
582 return 0;
583 return d->cb(d->data, line + 1);
586 int cmus_playlist_for_each(const char *buf, int size, int reverse,
587 int (*cb)(void *data, const char *line),
588 void *data)
590 struct pl_data d = { cb, data };
591 int (*handler)(void *, const char *);
593 handler = pl_handle_line;
594 if (size >= 10 && strncasecmp(buf, "[playlist]", 10) == 0)
595 handler = pls_handle_line;
597 if (reverse) {
598 buffer_for_each_line_reverse(buf, size, handler, &d);
599 } else {
600 buffer_for_each_line(buf, size, handler, &d);
602 return 0;