1 /*
2  * Copyright (c) 2014 Tim van der Molen <tim@kariliq.nl>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16 
17 /* For FreeBSD. */
18 #define _WITH_GETLINE
19 
20 #include <pthread.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 
25 #include "siren.h"
26 
27 static int		 playlist_search_entry(const void *, const char *);
28 
29 static pthread_mutex_t	 playlist_menu_mtx = PTHREAD_MUTEX_INITIALIZER;
30 static struct format	*playlist_altformat;
31 static struct format	*playlist_format;
32 static struct menu	*playlist_menu;
33 static unsigned int	 playlist_duration;
34 static char		*playlist_file;
35 
36 void
37 playlist_activate_entry(void)
38 {
39 	struct menu_entry	*e;
40 	struct track		*t;
41 
42 	XPTHREAD_MUTEX_LOCK(&playlist_menu_mtx);
43 	if ((e = menu_get_selected_entry(playlist_menu)) == NULL)
44 		t = NULL;
45 	else {
46 		menu_activate_entry(playlist_menu, e);
47 		t = menu_get_entry_data(e);
48 	}
49 	XPTHREAD_MUTEX_UNLOCK(&playlist_menu_mtx);
50 
51 	if (t != NULL) {
52 		player_set_source(PLAYER_SOURCE_PLAYLIST);
53 		player_play_track(t);
54 		playlist_print();
55 	}
56 }
57 
58 void
59 playlist_copy_entry(enum view_id view)
60 {
61 	struct track *t;
62 
63 	XPTHREAD_MUTEX_LOCK(&playlist_menu_mtx);
64 	if ((t = menu_get_selected_entry_data(playlist_menu)) != NULL)
65 		view_add_track(view, t);
66 	XPTHREAD_MUTEX_UNLOCK(&playlist_menu_mtx);
67 }
68 
69 void
70 playlist_end(void)
71 {
72 	menu_free(playlist_menu);
73 	free(playlist_file);
74 }
75 
76 static void
77 playlist_get_entry_text(const void *e, char *buf, size_t bufsize)
78 {
79 	const struct track *t;
80 
81 	t = e;
82 	format_track_snprintf(buf, bufsize, playlist_format,
83 	    playlist_altformat, t);
84 }
85 
86 struct track *
87 playlist_get_next_track(void)
88 {
89 	struct menu_entry	*e;
90 	struct track		*t;
91 
92 	XPTHREAD_MUTEX_LOCK(&playlist_menu_mtx);
93 	if ((e = menu_get_active_entry(playlist_menu)) == NULL)
94 		t = NULL;
95 	else {
96 		if ((e = menu_get_next_entry(e)) == NULL &&
97 		    option_get_boolean("repeat-all"))
98 			e = menu_get_first_entry(playlist_menu);
99 
100 		if (e == NULL)
101 			t = NULL;
102 		else {
103 			menu_activate_entry(playlist_menu, e);
104 			t = menu_get_entry_data(e);
105 		}
106 	}
107 	XPTHREAD_MUTEX_UNLOCK(&playlist_menu_mtx);
108 
109 	playlist_print();
110 	return t;
111 }
112 
113 struct track *
114 playlist_get_prev_track(void)
115 {
116 	struct menu_entry	*e;
117 	struct track		*t;
118 
119 	XPTHREAD_MUTEX_LOCK(&playlist_menu_mtx);
120 	if ((e = menu_get_active_entry(playlist_menu)) == NULL)
121 		t = NULL;
122 	else {
123 		if ((e = menu_get_prev_entry(e)) == NULL &&
124 		    option_get_boolean("repeat-all"))
125 			e = menu_get_last_entry(playlist_menu);
126 
127 		if (e == NULL)
128 			t = NULL;
129 		else {
130 			menu_activate_entry(playlist_menu, e);
131 			t = menu_get_entry_data(e);
132 		}
133 	}
134 	XPTHREAD_MUTEX_UNLOCK(&playlist_menu_mtx);
135 
136 	playlist_print();
137 	return t;
138 }
139 
140 void
141 playlist_init(void)
142 {
143 	playlist_menu = menu_init(NULL, playlist_get_entry_text,
144 	    playlist_search_entry);
145 }
146 
147 void
148 playlist_load(const char *file)
149 {
150 	struct track		*t;
151 	FILE			*fp;
152 	size_t			 size;
153 	ssize_t			 len;
154 	char			*dir, *line, *path, *tmp;
155 
156 	if ((fp = fopen(file, "r")) == NULL) {
157 		LOG_ERR("fopen: %s", file);
158 		msg_err("Cannot open playlist: %s", file);
159 		return;
160 	}
161 
162 	XPTHREAD_MUTEX_LOCK(&playlist_menu_mtx);
163 
164 	menu_remove_all_entries(playlist_menu);
165 	free(playlist_file);
166 	playlist_duration = 0;
167 
168 	playlist_file = path_normalise(file);
169 	dir = path_get_dirname(playlist_file);
170 
171 	line = NULL;
172 	size = 0;
173 	while ((len = getline(&line, &size, fp)) != -1) {
174 		/* Strip both \n and \r\n EOLs. */
175 		if (len > 0 && line[len - 1] == '\n') {
176 			if (len > 1 && line[len - 2] == '\r')
177 				line[len - 2] = '\0';
178 			else
179 				line[len - 1] = '\0';
180 		}
181 
182 		if (line[0] == '#' || line[0] == '\0')
183 			continue;
184 
185 		if (line[0] == '/')
186 			path = path_normalise(line);
187 		else {
188 			xasprintf(&tmp, "%s/%s", dir, line);
189 			path = path_normalise(tmp);
190 			free(tmp);
191 		}
192 
193 		if ((t = track_require(path)) != NULL) {
194 			menu_insert_tail(playlist_menu, t);
195 			playlist_duration += t->duration;
196 		}
197 
198 		free(path);
199 	}
200 
201 	if (ferror(fp)) {
202 		LOG_ERR("getline: %s", file);
203 		msg_err("Cannot read playlist: %s", file);
204 	}
205 
206 	free(line);
207 	free(dir);
208 
209 	XPTHREAD_MUTEX_UNLOCK(&playlist_menu_mtx);
210 
211 	fclose(fp);
212 
213 	playlist_print();
214 }
215 
216 void
217 playlist_print(void)
218 {
219 	if (view_get_id() != VIEW_ID_PLAYLIST)
220 		return;
221 
222 	XPTHREAD_MUTEX_LOCK(&playlist_menu_mtx);
223 	screen_view_title_printf("Playlist: %s (%u track%s, %u:%02u:%02u)",
224 	    playlist_file != NULL ? playlist_file : "None",
225 	    menu_get_nentries(playlist_menu),
226 	    menu_get_nentries(playlist_menu) == 1 ? "" : "s",
227 	    HOURS(playlist_duration),
228 	    HMINS(playlist_duration),
229 	    MSECS(playlist_duration));
230 	option_lock();
231 	playlist_format = option_get_format("playlist-format");
232 	playlist_altformat = option_get_format("playlist-format-alt");
233 	menu_print(playlist_menu);
234 	option_unlock();
235 	XPTHREAD_MUTEX_UNLOCK(&playlist_menu_mtx);
236 }
237 
238 void
239 playlist_scroll_down(enum menu_scroll scroll)
240 {
241 	XPTHREAD_MUTEX_LOCK(&playlist_menu_mtx);
242 	menu_scroll_down(playlist_menu, scroll);
243 	XPTHREAD_MUTEX_UNLOCK(&playlist_menu_mtx);
244 	playlist_print();
245 }
246 
247 void
248 playlist_scroll_up(enum menu_scroll scroll)
249 {
250 	XPTHREAD_MUTEX_LOCK(&playlist_menu_mtx);
251 	menu_scroll_up(playlist_menu, scroll);
252 	XPTHREAD_MUTEX_UNLOCK(&playlist_menu_mtx);
253 	playlist_print();
254 }
255 
256 static int
257 playlist_search_entry(const void *e, const char *search)
258 {
259 	const struct track *t;
260 
261 	t = e;
262 	return track_search(t, search);
263 }
264 
265 void
266 playlist_search_next(const char *search)
267 {
268 	XPTHREAD_MUTEX_LOCK(&playlist_menu_mtx);
269 	menu_search_next(playlist_menu, search);
270 	XPTHREAD_MUTEX_UNLOCK(&playlist_menu_mtx);
271 	playlist_print();
272 }
273 
274 void
275 playlist_search_prev(const char *search)
276 {
277 	XPTHREAD_MUTEX_LOCK(&playlist_menu_mtx);
278 	menu_search_prev(playlist_menu, search);
279 	XPTHREAD_MUTEX_UNLOCK(&playlist_menu_mtx);
280 	playlist_print();
281 }
282 
283 void
284 playlist_select_active_entry(void)
285 {
286 	XPTHREAD_MUTEX_LOCK(&playlist_menu_mtx);
287 	menu_select_active_entry(playlist_menu);
288 	XPTHREAD_MUTEX_UNLOCK(&playlist_menu_mtx);
289 	playlist_print();
290 }
291 
292 void
293 playlist_select_first_entry(void)
294 {
295 	XPTHREAD_MUTEX_LOCK(&playlist_menu_mtx);
296 	menu_select_first_entry(playlist_menu);
297 	XPTHREAD_MUTEX_UNLOCK(&playlist_menu_mtx);
298 	playlist_print();
299 }
300 
301 void
302 playlist_select_last_entry(void)
303 {
304 	XPTHREAD_MUTEX_LOCK(&playlist_menu_mtx);
305 	menu_select_last_entry(playlist_menu);
306 	XPTHREAD_MUTEX_UNLOCK(&playlist_menu_mtx);
307 	playlist_print();
308 }
309 
310 void
311 playlist_select_next_entry(void)
312 {
313 	XPTHREAD_MUTEX_LOCK(&playlist_menu_mtx);
314 	menu_select_next_entry(playlist_menu);
315 	XPTHREAD_MUTEX_UNLOCK(&playlist_menu_mtx);
316 	playlist_print();
317 }
318 
319 void
320 playlist_select_prev_entry(void)
321 {
322 	XPTHREAD_MUTEX_LOCK(&playlist_menu_mtx);
323 	menu_select_prev_entry(playlist_menu);
324 	XPTHREAD_MUTEX_UNLOCK(&playlist_menu_mtx);
325 	playlist_print();
326 }
327 
328 /* Recalculate the duration. */
329 void
330 playlist_update(void)
331 {
332 	struct menu_entry	*e;
333 	struct track		*t;
334 
335 	XPTHREAD_MUTEX_LOCK(&playlist_menu_mtx);
336 	playlist_duration = 0;
337 	MENU_FOR_EACH_ENTRY(playlist_menu, e) {
338 		t = menu_get_entry_data(e);
339 		playlist_duration += t->duration;
340 	}
341 	XPTHREAD_MUTEX_UNLOCK(&playlist_menu_mtx);
342 }
343