6 #include "play_queue.h"
19 #include <sys/types.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) {{{ */
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);
52 static void add_url(add_ti_cb add
, const char *filename
)
54 struct track_info
*ti
;
56 ti
= track_info_url_new(filename
);
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
;
72 ti
= track_db_get_track(track_db
, filename
);
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
);
107 if (strncmp(target
, root
, rlen
))
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
;
120 if (dir_open(&dir
, dirname
)) {
121 d_print("error: opening %s: %s\n", dirname
, strerror(errno
));
124 while ((name
= dir_read(&dir
))) {
125 struct dir_entry
*ent
;
134 int rc
= readlink(dir
.path
, buf
, sizeof(buf
));
136 if (rc
< 0 || rc
== sizeof(buf
))
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
);
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
);
158 if (add
== play_queue_prepend
) {
159 ptr_array_sort(&array
, dir_entry_cmp_reverse
);
161 ptr_array_sort(&array
, dir_entry_cmp
);
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
);
176 add_file(add
, dir
.path
);
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())
204 static void add_pl(add_ti_cb add
, const char *filename
)
209 buf
= mmap_file(filename
, &size
);
215 reverse
= add
== play_queue_prepend
;
217 cmus_playlist_for_each(buf
, size
, reverse
, handle_line
, add
);
222 static void do_add_job(void *data
)
224 struct add_data
*jd
= data
;
228 add_url(jd
->add
, jd
->name
);
231 add_pl(jd
->add
, jd
->name
);
234 add_dir(jd
->add
, jd
->name
);
237 add_file(jd
->add
, jd
->name
);
239 case FILE_TYPE_INVALID
:
244 static void free_add_job(void *data
)
246 struct add_data
*jd
= data
;
254 /* update (worker job) {{{ */
259 struct track_info
**ti
;
262 static void do_update_job(void *data
)
264 struct update_data
*d
= data
;
267 for (i
= 0; i
< d
->used
; i
++) {
268 struct track_info
*ti
= d
->ti
[i
];
271 /* stat follows symlinks, lstat does not */
272 if (stat(ti
->filename
, &s
) == -1) {
273 d_print("removing dead file %s\n", ti
->filename
);
277 } else if (ti
->mtime
!= s
.st_mtime
) {
278 d_print("mtime changed: %s\n", ti
->filename
);
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
;
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
);
317 worker_remove_jobs(JOB_TYPE_ANY
);
319 if (track_db_close(track_db
))
320 d_print("error: %s\n", strerror(errno
));
325 struct track_info
*info
;
328 info
= play_queue_remove();
331 info
= lib_set_next();
333 info
= pl_set_next();
339 player_set_file(info
->filename
);
340 track_info_unref(info
);
346 struct track_info
*info
;
350 info
= lib_set_prev();
352 info
= pl_set_prev();
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
)
373 *ret
= xstrdup(name
);
374 return FILE_TYPE_URL
;
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) {
385 return FILE_TYPE_INVALID
;
388 if (S_ISDIR(st
.st_mode
)) {
390 return FILE_TYPE_DIR
;
392 if (!S_ISREG(st
.st_mode
)) {
395 return FILE_TYPE_INVALID
;
399 if (cmus_is_playlist(absolute
))
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);
411 data
->name
= xstrdup(name
);
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';
422 rc
= write_all(fd
, ti
->filename
, strlen(ti
->filename
));
425 rc
= write_all(fd
, &nl
, 1);
431 int cmus_save(for_each_ti_cb for_each_ti
, const char *filename
)
435 fd
= open(filename
, O_CREAT
| O_WRONLY
| O_TRUNC
, 0666);
438 rc
= for_each_ti(save_playlist_cb
, &fd
);
443 static int update_cb(void *data
, struct track_info
*ti
)
445 struct update_data
*d
= data
;
447 if (is_url(ti
->filename
))
450 if (d
->size
== d
->used
) {
454 d
->ti
= xrealloc(d
->ti
, d
->size
* sizeof(struct track_info
*));
457 d
->ti
[d
->used
++] = ti
;
461 void cmus_update_lib(void)
463 struct update_data
*data
;
465 data
= xnew(struct update_data
, 1);
471 lib_for_each(update_cb
, data
);
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);
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
;
493 return track_info_url_new(name
);
496 ti
= track_db_get_track(track_db
, name
);
501 static const char *get_ext(const char *filename
)
503 const char *ext
= strrchr(filename
, '.');
510 static int str_in_array(const char *str
, const char * const * array
)
514 for (i
= 0; array
[i
]; i
++) {
515 if (strcasecmp(str
, array
[i
]) == 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
));
544 int (*cb
)(void *data
, const char *line
);
548 static int pl_handle_line(void *data
, const char *line
)
550 struct pl_data
*d
= data
;
553 while (isspace(line
[i
]))
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))
570 line
= strchr(line
, '=');
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
),
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
;
588 buffer_for_each_line_reverse(buf
, size
, handler
, &d
);
590 buffer_for_each_line(buf
, size
, handler
, &d
);