1 /*
2 * Copyright 2008-2013 Various Authors
3 * Copyright 2004 Timo Hirvonen
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "cmus.h"
20 #include "job.h"
21 #include "lib.h"
22 #include "pl.h"
23 #include "player.h"
24 #include "input.h"
25 #include "play_queue.h"
26 #include "cache.h"
27 #include "misc.h"
28 #include "file.h"
29 #include "utils.h"
30 #include "path.h"
31 #include "options.h"
32 #include "xmalloc.h"
33 #include "debug.h"
34 #include "load_dir.h"
35 #include "ui_curses.h"
36 #include "cache.h"
37 #include "gbuf.h"
38 #include "discid.h"
39 #include "locking.h"
40
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 #include <fcntl.h>
44 #include <unistd.h>
45 #include <dirent.h>
46 #include <dlfcn.h>
47 #include <stdlib.h>
48 #include <ctype.h>
49 #include <strings.h>
50
51 /* save_playlist_cb, save_ext_playlist_cb */
52 typedef int (*save_tracks_cb)(void *data, struct track_info *ti);
53
54 static char **playable_exts;
55 static const char * const playlist_exts[] = { "m3u", "pl", "pls", NULL };
56
57 int cmus_next_track_request_fd;
58 static bool play_queue_active = false;
59 static int cmus_next_track_request_fd_priv;
60 static pthread_mutex_t cmus_next_file_mutex = CMUS_MUTEX_INITIALIZER;
61 static pthread_cond_t cmus_next_file_cond = CMUS_COND_INITIALIZER;
62 static int cmus_next_file_provided;
63 static struct track_info *cmus_next_file;
64
65 static int x11_init_done = 0;
66 static void *(*x11_open)(void *) = NULL;
67 static int (*x11_raise)(void *, int) = NULL;
68 static int (*x11_close)(void *) = NULL;
69
cmus_init(void)70 int cmus_init(void)
71 {
72 playable_exts = ip_get_supported_extensions();
73 cache_init();
74 job_init();
75 play_queue_init();
76 return 0;
77 }
78
cmus_exit(void)79 void cmus_exit(void)
80 {
81 job_exit();
82 if (cache_close())
83 d_print("error: %s\n", strerror(errno));
84 }
85
cmus_next(void)86 void cmus_next(void)
87 {
88 struct track_info *info = cmus_get_next_track();
89 if (info)
90 player_set_file(info);
91 }
92
cmus_prev(void)93 void cmus_prev(void)
94 {
95 struct track_info *info;
96
97 if (play_library) {
98 info = lib_goto_prev();
99 } else {
100 info = pl_goto_prev();
101 }
102
103 if (info)
104 player_set_file(info);
105 }
106
cmus_play_file(const char * filename)107 void cmus_play_file(const char *filename)
108 {
109 struct track_info *ti;
110
111 cache_lock();
112 ti = cache_get_ti(filename, 0);
113 cache_unlock();
114 if (!ti) {
115 error_msg("Couldn't get file information for %s\n", filename);
116 return;
117 }
118
119 player_play_file(ti);
120 }
121
cmus_detect_ft(const char * name,char ** ret)122 enum file_type cmus_detect_ft(const char *name, char **ret)
123 {
124 char *absolute;
125 struct stat st;
126
127 if (is_http_url(name) || is_cue_url(name)) {
128 *ret = xstrdup(name);
129 return FILE_TYPE_URL;
130 }
131
132 if (is_cdda_url(name)) {
133 *ret = complete_cdda_url(cdda_device, name);
134 return FILE_TYPE_CDDA;
135 }
136
137 *ret = NULL;
138 absolute = path_absolute(name);
139 if (absolute == NULL)
140 return FILE_TYPE_INVALID;
141
142 /* stat follows symlinks, lstat does not */
143 if (stat(absolute, &st) == -1) {
144 free(absolute);
145 return FILE_TYPE_INVALID;
146 }
147
148 if (S_ISDIR(st.st_mode)) {
149 *ret = absolute;
150 return FILE_TYPE_DIR;
151 }
152 if (!S_ISREG(st.st_mode)) {
153 free(absolute);
154 errno = EINVAL;
155 return FILE_TYPE_INVALID;
156 }
157
158 *ret = absolute;
159 if (cmus_is_playlist(absolute))
160 return FILE_TYPE_PL;
161
162 /* NOTE: it could be FILE_TYPE_PL too! */
163 return FILE_TYPE_FILE;
164 }
165
cmus_add(add_ti_cb add,const char * name,enum file_type ft,int jt,int force,void * opaque)166 void cmus_add(add_ti_cb add, const char *name, enum file_type ft, int jt, int force,
167 void *opaque)
168 {
169 struct add_data *data = xnew(struct add_data, 1);
170
171 data->add = add;
172 data->name = xstrdup(name);
173 data->type = ft;
174 data->force = force;
175 data->opaque = opaque;
176
177 job_schedule_add(jt, data);
178 }
179
save_ext_playlist_cb(void * data,struct track_info * ti)180 static int save_ext_playlist_cb(void *data, struct track_info *ti)
181 {
182 GBUF(buf);
183 int fd = *(int *)data;
184 int i, rc;
185
186 gbuf_addf(&buf, "file %s\n", escape(ti->filename));
187 gbuf_addf(&buf, "duration %d\n", ti->duration);
188 gbuf_addf(&buf, "codec %s\n", ti->codec);
189 gbuf_addf(&buf, "bitrate %ld\n", ti->bitrate);
190 for (i = 0; ti->comments[i].key; i++)
191 gbuf_addf(&buf, "tag %s %s\n",
192 ti->comments[i].key,
193 escape(ti->comments[i].val));
194
195 rc = write_all(fd, buf.buffer, buf.len);
196 gbuf_free(&buf);
197
198 if (rc == -1)
199 return -1;
200 return 0;
201 }
202
save_playlist_cb(void * data,struct track_info * ti)203 static int save_playlist_cb(void *data, struct track_info *ti)
204 {
205 int fd = *(int *)data;
206 const char nl = '\n';
207 int rc;
208
209 rc = write_all(fd, ti->filename, strlen(ti->filename));
210 if (rc == -1)
211 return -1;
212 rc = write_all(fd, &nl, 1);
213 if (rc == -1)
214 return -1;
215 return 0;
216 }
217
do_cmus_save(for_each_ti_cb for_each_ti,const char * filename,save_tracks_cb save_tracks,void * opaque)218 static int do_cmus_save(for_each_ti_cb for_each_ti, const char *filename,
219 save_tracks_cb save_tracks, void *opaque)
220 {
221 int fd, rc;
222
223 if (strcmp(filename, "-") == 0) {
224 if (get_client_fd() == -1) {
225 error_msg("saving to stdout works only remotely");
226 return 0;
227 }
228 fd = dup(get_client_fd());
229 } else
230 fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
231 if (fd == -1)
232 return -1;
233 rc = for_each_ti(save_tracks, &fd, opaque);
234 close(fd);
235 return rc;
236 }
237
cmus_save(for_each_ti_cb for_each_ti,const char * filename,void * opaque)238 int cmus_save(for_each_ti_cb for_each_ti, const char *filename, void *opaque)
239 {
240 return do_cmus_save(for_each_ti, filename, save_playlist_cb, opaque);
241 }
242
cmus_save_ext(for_each_ti_cb for_each_ti,const char * filename,void * opaque)243 int cmus_save_ext(for_each_ti_cb for_each_ti, const char *filename,
244 void *opaque)
245 {
246 return do_cmus_save(for_each_ti, filename, save_ext_playlist_cb,
247 opaque);
248 }
249
update_cb(void * data,struct track_info * ti)250 static int update_cb(void *data, struct track_info *ti)
251 {
252 struct update_data *d = data;
253
254 if (d->size == d->used) {
255 if (d->size == 0)
256 d->size = 16;
257 d->size *= 2;
258 d->ti = xrenew(struct track_info *, d->ti, d->size);
259 }
260 track_info_ref(ti);
261 d->ti[d->used++] = ti;
262 return 0;
263 }
264
cmus_update_cache(int force)265 void cmus_update_cache(int force)
266 {
267 struct update_cache_data *data;
268
269 data = xnew(struct update_cache_data, 1);
270 data->force = force;
271
272 job_schedule_update_cache(JOB_TYPE_LIB, data);
273 }
274
cmus_update_lib(void)275 void cmus_update_lib(void)
276 {
277 struct update_data *data;
278
279 data = xnew0(struct update_data, 1);
280
281 lib_for_each(update_cb, data, NULL);
282
283 job_schedule_update(data);
284 }
285
cmus_update_tis(struct track_info ** tis,int nr,int force)286 void cmus_update_tis(struct track_info **tis, int nr, int force)
287 {
288 struct update_data *data;
289
290 data = xnew(struct update_data, 1);
291 data->size = nr;
292 data->used = nr;
293 data->ti = tis;
294 data->force = force;
295
296 job_schedule_update(data);
297 }
298
get_ext(const char * filename)299 static const char *get_ext(const char *filename)
300 {
301 const char *ext = strrchr(filename, '.');
302
303 if (ext)
304 ext++;
305 return ext;
306 }
307
str_in_array(const char * str,const char * const * array)308 static int str_in_array(const char *str, const char * const *array)
309 {
310 int i;
311
312 for (i = 0; array[i]; i++) {
313 if (strcasecmp(str, array[i]) == 0)
314 return 1;
315 }
316 return 0;
317 }
318
cmus_is_playlist(const char * filename)319 int cmus_is_playlist(const char *filename)
320 {
321 const char *ext = get_ext(filename);
322
323 return ext && str_in_array(ext, playlist_exts);
324 }
325
cmus_is_playable(const char * filename)326 int cmus_is_playable(const char *filename)
327 {
328 const char *ext = get_ext(filename);
329
330 return ext && str_in_array(ext, (const char * const *)playable_exts);
331 }
332
cmus_is_supported(const char * filename)333 int cmus_is_supported(const char *filename)
334 {
335 const char *ext = get_ext(filename);
336
337 return ext && (str_in_array(ext, (const char * const *)playable_exts) ||
338 str_in_array(ext, playlist_exts));
339 }
340
341 struct pl_data {
342 int (*cb)(void *data, const char *line);
343 void *data;
344 };
345
pl_handle_line(void * data,const char * line)346 static int pl_handle_line(void *data, const char *line)
347 {
348 struct pl_data *d = data;
349 int i = 0;
350
351 while (isspace((unsigned char)line[i]))
352 i++;
353 if (line[i] == 0)
354 return 0;
355
356 if (line[i] == '#')
357 return 0;
358
359 return d->cb(d->data, line);
360 }
361
pls_handle_line(void * data,const char * line)362 static int pls_handle_line(void *data, const char *line)
363 {
364 struct pl_data *d = data;
365
366 if (strncasecmp(line, "file", 4))
367 return 0;
368 line = strchr(line, '=');
369 if (line == NULL)
370 return 0;
371 return d->cb(d->data, line + 1);
372 }
373
cmus_playlist_for_each(const char * buf,int size,int reverse,int (* cb)(void * data,const char * line),void * data)374 int cmus_playlist_for_each(const char *buf, int size, int reverse,
375 int (*cb)(void *data, const char *line),
376 void *data)
377 {
378 struct pl_data d = { cb, data };
379 int (*handler)(void *, const char *);
380
381 handler = pl_handle_line;
382 if (size >= 10 && strncasecmp(buf, "[playlist]", 10) == 0)
383 handler = pls_handle_line;
384
385 if (reverse) {
386 buffer_for_each_line_reverse(buf, size, handler, &d);
387 } else {
388 buffer_for_each_line(buf, size, handler, &d);
389 }
390 return 0;
391 }
392
393 /* multi-threaded next track requests */
394
395 #define cmus_next_file_lock() cmus_mutex_lock(&cmus_next_file_mutex)
396 #define cmus_next_file_unlock() cmus_mutex_unlock(&cmus_next_file_mutex)
397
cmus_get_next_from_main_thread(void)398 static struct track_info *cmus_get_next_from_main_thread(void)
399 {
400 struct track_info *ti = play_queue_remove();
401 if (ti) {
402 play_queue_active = true;
403 } else {
404 if (!play_queue_active || !stop_after_queue)
405 ti = play_library ? lib_goto_next() : pl_goto_next();
406 play_queue_active = false;
407 }
408 return ti;
409 }
410
cmus_get_next_from_other_thread(void)411 static struct track_info *cmus_get_next_from_other_thread(void)
412 {
413 static pthread_mutex_t mutex = CMUS_MUTEX_INITIALIZER;
414 cmus_mutex_lock(&mutex);
415
416 /* only one thread may request a track at a time */
417
418 notify_via_pipe(cmus_next_track_request_fd_priv);
419
420 cmus_next_file_lock();
421 while (!cmus_next_file_provided)
422 pthread_cond_wait(&cmus_next_file_cond, &cmus_next_file_mutex);
423 struct track_info *ti = cmus_next_file;
424 cmus_next_file_provided = 0;
425 cmus_next_file_unlock();
426
427 cmus_mutex_unlock(&mutex);
428
429 return ti;
430 }
431
cmus_get_next_track(void)432 struct track_info *cmus_get_next_track(void)
433 {
434 pthread_t this_thread = pthread_self();
435 if (pthread_equal(this_thread, main_thread))
436 return cmus_get_next_from_main_thread();
437 return cmus_get_next_from_other_thread();
438 }
439
cmus_provide_next_track(void)440 void cmus_provide_next_track(void)
441 {
442 clear_pipe(cmus_next_track_request_fd, 1);
443
444 cmus_next_file_lock();
445 cmus_next_file = cmus_get_next_from_main_thread();
446 cmus_next_file_provided = 1;
447 cmus_next_file_unlock();
448
449 pthread_cond_broadcast(&cmus_next_file_cond);
450 }
451
cmus_track_request_init(void)452 void cmus_track_request_init(void)
453 {
454 init_pipes(&cmus_next_track_request_fd, &cmus_next_track_request_fd_priv);
455 }
456
cmus_can_raise_vte_x11(void)457 static int cmus_can_raise_vte_x11(void)
458 {
459 return getenv("DISPLAY") && getenv("WINDOWID");
460 }
461
cmus_can_raise_vte(void)462 int cmus_can_raise_vte(void)
463 {
464 return cmus_can_raise_vte_x11();
465 }
466
cmus_raise_vte_x11_error(void)467 static int cmus_raise_vte_x11_error(void)
468 {
469 return 0;
470 }
471
cmus_raise_vte(void)472 void cmus_raise_vte(void)
473 {
474 if (cmus_can_raise_vte_x11()) {
475 if (!x11_init_done) {
476 void *x11;
477
478 x11_init_done = 1;
479 x11 = dlopen("libX11.so", RTLD_LAZY);
480
481 if (x11) {
482 int (*x11_error)(void *);
483
484 x11_error = dlsym(x11, "XSetErrorHandler");
485 x11_open = dlsym(x11, "XOpenDisplay");
486 x11_raise = dlsym(x11, "XRaiseWindow");
487 x11_close = dlsym(x11, "XCloseDisplay");
488
489 if (x11_error) {
490 x11_error(cmus_raise_vte_x11_error);
491 }
492 }
493 }
494
495 if (x11_open && x11_raise && x11_close) {
496 char *xid_str;
497 long int xid = 0;
498
499 xid_str = getenv("WINDOWID");
500 if (!str_to_int(xid_str, &xid) && xid != 0) {
501 void *display;
502
503 display = x11_open(NULL);
504 if (display) {
505 x11_raise(display, (int) xid);
506 x11_close(display);
507 }
508 }
509 }
510 }
511 }
512