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