configure: Remove EXTRA_CFLAGS
[cmus.git] / cmus.c
blob6893dcb5f6ddeb99aea1c463bc0c92110b4b68a5
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>
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #include <fcntl.h>
21 #include <unistd.h>
22 #include <dirent.h>
23 #include <stdlib.h>
24 #include <ctype.h>
26 static pthread_mutex_t track_db_mutex = CMUS_MUTEX_INITIALIZER;
27 static struct track_db *track_db;
28 static char **playable_exts;
29 static const char * const playlist_exts[] = { "m3u", "pl", "pls", NULL };
31 #define track_db_lock() cmus_mutex_lock(&track_db_mutex)
32 #define track_db_unlock() cmus_mutex_unlock(&track_db_mutex)
34 /* add (worker job) {{{ */
36 struct add_data {
37 enum file_type type;
38 char *name;
39 add_ti_cb add;
42 static struct track_info *track_info_url_new(const char *url)
44 struct track_info *ti = track_info_new(url);
45 ti->comments = xnew0(struct keyval, 1);
46 ti->duration = -1;
47 ti->mtime = -1;
48 return ti;
51 static void add_url(add_ti_cb add, const char *filename)
53 struct track_info *ti;
55 ti = track_info_url_new(filename);
56 editable_lock();
57 add(ti);
58 editable_unlock();
59 track_info_unref(ti);
62 /* add file to the playlist
64 * @filename: absolute filename with extraneous slashes stripped
66 static void add_file(add_ti_cb add, const char *filename)
68 struct track_info *ti;
70 track_db_lock();
71 ti = track_db_get_track(track_db, filename);
72 track_db_unlock();
74 if (ti == NULL)
75 return;
77 editable_lock();
78 add(ti);
79 editable_unlock();
80 track_info_unref(ti);
83 static char *fullname(const char *path, const char *name)
85 int l1, l2;
86 char *full;
88 l1 = strlen(path);
89 l2 = strlen(name);
90 if (path[l1 - 1] == '/')
91 l1--;
92 full = xnew(char, l1 + 1 + l2 + 1);
93 memcpy(full, path, l1);
94 full[l1] = '/';
95 memcpy(full + l1 + 1, name, l2 + 1);
96 return full;
99 static int dir_filter(const struct dirent *d)
101 if (d->d_name[0] == '.')
102 return 0;
103 return 1;
106 static void add_dir(add_ti_cb add, const char *dirname);
108 static void handle_dentry(add_ti_cb add, const char *dirname, const char *name)
110 struct stat s;
111 char *full;
113 full = fullname(dirname, name);
114 /* stat follows symlinks, lstat does not */
115 if (stat(full, &s) == 0) {
116 if (S_ISDIR(s.st_mode)) {
117 add_dir(add, full);
118 } else {
119 add_file(add, full);
122 free(full);
125 static void add_dir(add_ti_cb add, const char *dirname)
127 struct dirent **dentries;
128 int num, i;
130 num = scandir(dirname, &dentries, dir_filter, alphasort);
131 if (num == -1) {
132 d_print("error: scandir: %s\n", strerror(errno));
133 return;
135 if (add == play_queue_prepend) {
136 for (i = num - 1; i >= 0; i--) {
137 if (!worker_cancelling())
138 handle_dentry(add, dirname, dentries[i]->d_name);
139 free(dentries[i]);
141 } else {
142 for (i = 0; i < num; i++) {
143 if (!worker_cancelling())
144 handle_dentry(add, dirname, dentries[i]->d_name);
145 free(dentries[i]);
148 free(dentries);
151 static int handle_line(void *data, const char *line)
153 add_ti_cb add = data;
155 if (worker_cancelling())
156 return 1;
158 if (is_url(line)) {
159 add_url(add, line);
160 } else {
161 add_file(add, line);
163 return 0;
166 static void add_pl(add_ti_cb add, const char *filename)
168 char *buf;
169 int size, reverse;
171 buf = mmap_file(filename, &size);
172 if (size == -1)
173 return;
175 if (buf) {
176 /* beautiful hack */
177 reverse = add == play_queue_prepend;
179 cmus_playlist_for_each(buf, size, reverse, handle_line, add);
180 munmap(buf, size);
184 static void do_add_job(void *data)
186 struct add_data *jd = data;
188 switch (jd->type) {
189 case FILE_TYPE_URL:
190 add_url(jd->add, jd->name);
191 break;
192 case FILE_TYPE_PL:
193 add_pl(jd->add, jd->name);
194 break;
195 case FILE_TYPE_DIR:
196 add_dir(jd->add, jd->name);
197 break;
198 case FILE_TYPE_FILE:
199 add_file(jd->add, jd->name);
200 break;
201 case FILE_TYPE_INVALID:
202 break;
206 static void free_add_job(void *data)
208 struct add_data *jd = data;
210 free(jd->name);
211 free(jd);
214 /* }}} */
216 /* update (worker job) {{{ */
218 struct update_data {
219 size_t size;
220 size_t used;
221 struct track_info **ti;
224 static void do_update_job(void *data)
226 struct update_data *d = data;
227 int i;
229 for (i = 0; i < d->used; i++) {
230 struct track_info *ti = d->ti[i];
231 struct stat s;
233 /* stat follows symlinks, lstat does not */
234 if (stat(ti->filename, &s) == -1) {
235 d_print("removing dead file %s\n", ti->filename);
236 editable_lock();
237 lib_remove(ti);
238 editable_unlock();
239 } else if (ti->mtime != s.st_mtime) {
240 d_print("mtime changed: %s\n", ti->filename);
241 editable_lock();
242 lib_remove(ti);
243 editable_unlock();
245 cmus_add(lib_add_track, ti->filename, FILE_TYPE_FILE, JOB_TYPE_LIB);
247 track_info_unref(ti);
251 static void free_update_job(void *data)
253 struct update_data *d = data;
255 free(d->ti);
256 free(d);
259 /* }}} */
261 int cmus_init(void)
263 char *db_filename_base;
265 playable_exts = ip_get_supported_extensions();
267 db_filename_base = xstrjoin(cmus_config_dir, "/trackdb");
268 track_db = track_db_new(db_filename_base);
269 free(db_filename_base);
271 worker_init();
273 play_queue_init();
274 return 0;
277 void cmus_exit(void)
279 worker_remove_jobs(JOB_TYPE_ANY);
280 worker_exit();
281 if (track_db_close(track_db))
282 d_print("error: %s\n", strerror(errno));
285 void cmus_next(void)
287 struct track_info *info;
289 editable_lock();
290 info = play_queue_remove();
291 if (info == NULL) {
292 if (play_library) {
293 info = lib_set_next();
294 } else {
295 info = pl_set_next();
298 editable_unlock();
300 if (info) {
301 player_set_file(info->filename);
302 track_info_unref(info);
306 void cmus_prev(void)
308 struct track_info *info;
310 editable_lock();
311 if (play_library) {
312 info = lib_set_prev();
313 } else {
314 info = pl_set_prev();
316 editable_unlock();
318 if (info) {
319 player_set_file(info->filename);
320 track_info_unref(info);
324 void cmus_play_file(const char *filename)
326 player_play_file(filename);
329 enum file_type cmus_detect_ft(const char *name, char **ret)
331 char *absolute;
332 struct stat st;
334 if (is_url(name)) {
335 *ret = xstrdup(name);
336 return FILE_TYPE_URL;
339 *ret = NULL;
340 absolute = path_absolute(name);
341 if (absolute == NULL)
342 return FILE_TYPE_INVALID;
344 /* stat follows symlinks, lstat does not */
345 if (stat(absolute, &st) == -1) {
346 free(absolute);
347 return FILE_TYPE_INVALID;
350 if (S_ISDIR(st.st_mode)) {
351 *ret = absolute;
352 return FILE_TYPE_DIR;
354 if (!S_ISREG(st.st_mode)) {
355 free(absolute);
356 errno = EINVAL;
357 return FILE_TYPE_INVALID;
360 *ret = absolute;
361 if (cmus_is_playlist(absolute))
362 return FILE_TYPE_PL;
364 /* NOTE: it could be FILE_TYPE_PL too! */
365 return FILE_TYPE_FILE;
368 void cmus_add(add_ti_cb add, const char *name, enum file_type ft, int jt)
370 struct add_data *data = xnew(struct add_data, 1);
372 data->add = add;
373 data->name = xstrdup(name);
374 data->type = ft;
375 worker_add_job(jt, do_add_job, free_add_job, data);
378 static int save_playlist_cb(void *data, struct track_info *ti)
380 int fd = *(int *)data;
381 const char nl = '\n';
382 int rc;
384 rc = write_all(fd, ti->filename, strlen(ti->filename));
385 if (rc == -1)
386 return -1;
387 rc = write_all(fd, &nl, 1);
388 if (rc == -1)
389 return -1;
390 return 0;
393 int cmus_save(for_each_ti_cb for_each_ti, const char *filename)
395 int fd, rc;
397 fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
398 if (fd == -1)
399 return -1;
400 rc = for_each_ti(save_playlist_cb, &fd);
401 close(fd);
402 return rc;
405 static int update_cb(void *data, struct track_info *ti)
407 struct update_data *d = data;
409 if (is_url(ti->filename))
410 return 0;
412 if (d->size == d->used) {
413 if (d->size == 0)
414 d->size = 16;
415 d->size *= 2;
416 d->ti = xrealloc(d->ti, d->size * sizeof(struct track_info *));
418 track_info_ref(ti);
419 d->ti[d->used++] = ti;
420 return 0;
423 void cmus_update_lib(void)
425 struct update_data *data;
427 data = xnew(struct update_data, 1);
428 data->size = 0;
429 data->used = 0;
430 data->ti = NULL;
432 editable_lock();
433 lib_for_each(update_cb, data);
434 editable_unlock();
436 worker_add_job(JOB_TYPE_LIB, do_update_job, free_update_job, data);
439 void cmus_update_tis(struct track_info **tis, int nr)
441 struct update_data *data;
443 data = xnew(struct update_data, 1);
444 data->size = nr;
445 data->used = nr;
446 data->ti = tis;
447 worker_add_job(JOB_TYPE_LIB, do_update_job, free_update_job, data);
450 struct track_info *cmus_get_track_info(const char *name)
452 struct track_info *ti;
454 if (is_url(name))
455 return track_info_url_new(name);
457 track_db_lock();
458 ti = track_db_get_track(track_db, name);
459 track_db_unlock();
460 return ti;
463 static const char *get_ext(const char *filename)
465 const char *ext = strrchr(filename, '.');
467 if (ext)
468 ext++;
469 return ext;
472 static int str_in_array(const char *str, const char * const * array)
474 int i;
476 for (i = 0; array[i]; i++) {
477 if (strcasecmp(str, array[i]) == 0)
478 return 1;
480 return 0;
483 int cmus_is_playlist(const char *filename)
485 const char *ext = get_ext(filename);
487 return ext && str_in_array(ext, playlist_exts);
490 int cmus_is_playable(const char *filename)
492 const char *ext = get_ext(filename);
494 return ext && str_in_array(ext, (const char * const *)playable_exts);
497 int cmus_is_supported(const char *filename)
499 const char *ext = get_ext(filename);
501 return ext && (str_in_array(ext, (const char * const *)playable_exts) ||
502 str_in_array(ext, playlist_exts));
505 struct pl_data {
506 int (*cb)(void *data, const char *line);
507 void *data;
510 static int pl_handle_line(void *data, const char *line)
512 struct pl_data *d = data;
513 int i = 0;
515 while (isspace(line[i]))
516 i++;
517 if (line[i] == 0)
518 return 0;
520 if (line[i] == '#')
521 return 0;
523 return d->cb(d->data, line);
526 static int pls_handle_line(void *data, const char *line)
528 struct pl_data *d = data;
530 if (strncasecmp(line, "file", 4))
531 return 0;
532 line = strchr(line, '=');
533 if (line == NULL)
534 return 0;
535 return d->cb(d->data, line + 1);
538 int cmus_playlist_for_each(const char *buf, int size, int reverse,
539 int (*cb)(void *data, const char *line),
540 void *data)
542 struct pl_data d = { cb, data };
543 int (*handler)(void *, const char *);
545 handler = pl_handle_line;
546 if (size >= 10 && strncasecmp(buf, "[playlist]", 10) == 0)
547 handler = pls_handle_line;
549 if (reverse) {
550 buffer_for_each_line_reverse(buf, size, handler, &d);
551 } else {
552 buffer_for_each_line(buf, size, handler, &d);
554 return 0;