diff options
Diffstat (limited to 'apps/playlist_catalog.c')
-rw-r--r-- | apps/playlist_catalog.c | 504 |
1 files changed, 504 insertions, 0 deletions
diff --git a/apps/playlist_catalog.c b/apps/playlist_catalog.c new file mode 100644 index 0000000000..a1a9bd8ea1 --- /dev/null +++ b/apps/playlist_catalog.c | |||
@@ -0,0 +1,504 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2006 Sebastian Henriksen, Hardeep Sidhu | ||
11 | * | ||
12 | * All files in this archive are subject to the GNU General Public License. | ||
13 | * See the file COPYING in the source tree root for full license agreement. | ||
14 | * | ||
15 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
16 | * KIND, either express or implied. | ||
17 | * | ||
18 | ****************************************************************************/ | ||
19 | |||
20 | #include <stdbool.h> | ||
21 | #include <stdio.h> | ||
22 | #include <stdlib.h> | ||
23 | #include <string.h> | ||
24 | #include "action.h" | ||
25 | #include "dir.h" | ||
26 | #include "file.h" | ||
27 | #include "filetree.h" | ||
28 | #include "kernel.h" | ||
29 | #include "keyboard.h" | ||
30 | #include "lang.h" | ||
31 | #include "list.h" | ||
32 | #include "misc.h" | ||
33 | #include "onplay.h" | ||
34 | #include "playlist.h" | ||
35 | #include "settings.h" | ||
36 | #include "splash.h" | ||
37 | #include "sprintf.h" | ||
38 | #include "tree.h" | ||
39 | #include "yesno.h" | ||
40 | |||
41 | #define PLAYLIST_CATALOG_CFG ROCKBOX_DIR "/playlist_catalog.config" | ||
42 | #define PLAYLIST_CATALOG_DEFAULT_DIR "/Playlists" | ||
43 | #define MAX_PLAYLISTS 400 | ||
44 | #define PLAYLIST_DISPLAY_COUNT 10 | ||
45 | |||
46 | /* Use for recursive directory search */ | ||
47 | struct add_track_context { | ||
48 | int fd; | ||
49 | int count; | ||
50 | }; | ||
51 | |||
52 | /* keep track of most recently used playlist */ | ||
53 | static char most_recent_playlist[MAX_PATH]; | ||
54 | |||
55 | /* directory where our playlists our stored (configured in | ||
56 | PLAYLIST_CATALOG_CFG) */ | ||
57 | static char playlist_dir[MAX_PATH]; | ||
58 | static int playlist_dir_length; | ||
59 | static bool playlist_dir_exists = false; | ||
60 | |||
61 | /* Retrieve playlist directory from config file and verify it exists */ | ||
62 | static int initialize_catalog(void) | ||
63 | { | ||
64 | static bool initialized = false; | ||
65 | |||
66 | if (!initialized) | ||
67 | { | ||
68 | int f; | ||
69 | DIR* dir; | ||
70 | bool default_dir = true; | ||
71 | |||
72 | f = open(PLAYLIST_CATALOG_CFG, O_RDONLY); | ||
73 | if (f >= 0) | ||
74 | { | ||
75 | char buf[MAX_PATH+5]; | ||
76 | |||
77 | while (read_line(f, buf, sizeof(buf))) | ||
78 | { | ||
79 | char* name; | ||
80 | char* value; | ||
81 | |||
82 | /* directory config is of the format: "dir: /path/to/dir" */ | ||
83 | if (settings_parseline(buf, &name, &value) && | ||
84 | !strncasecmp(name, "dir:", strlen(name)) && | ||
85 | strlen(value) > 0) | ||
86 | { | ||
87 | strncpy(playlist_dir, value, strlen(value)); | ||
88 | default_dir = false; | ||
89 | } | ||
90 | } | ||
91 | |||
92 | close(f); | ||
93 | } | ||
94 | |||
95 | /* fall back to default directory if no or invalid config */ | ||
96 | if (default_dir) | ||
97 | strncpy(playlist_dir, PLAYLIST_CATALOG_DEFAULT_DIR, | ||
98 | sizeof(playlist_dir)); | ||
99 | |||
100 | playlist_dir_length = strlen(playlist_dir); | ||
101 | |||
102 | dir = opendir(playlist_dir); | ||
103 | if (dir) | ||
104 | { | ||
105 | playlist_dir_exists = true; | ||
106 | closedir(dir); | ||
107 | memset(most_recent_playlist, 0, sizeof(most_recent_playlist)); | ||
108 | initialized = true; | ||
109 | } | ||
110 | } | ||
111 | |||
112 | if (!playlist_dir_exists) | ||
113 | { | ||
114 | gui_syncsplash(HZ*2, true, str(LANG_CATALOG_NO_DIRECTORY), | ||
115 | playlist_dir); | ||
116 | return -1; | ||
117 | } | ||
118 | |||
119 | return 0; | ||
120 | } | ||
121 | |||
122 | /* Use the filetree functions to retrieve the list of playlists in the | ||
123 | directory */ | ||
124 | static int create_playlist_list(char** playlists, int num_items, | ||
125 | int* num_playlists) | ||
126 | { | ||
127 | int result = -1; | ||
128 | int num_files = 0; | ||
129 | int index = 0; | ||
130 | int i; | ||
131 | bool most_recent = false; | ||
132 | struct entry *files; | ||
133 | struct tree_context* tc = tree_get_context(); | ||
134 | int dirfilter = *(tc->dirfilter); | ||
135 | |||
136 | *num_playlists = 0; | ||
137 | |||
138 | /* use the tree browser dircache to load only playlists */ | ||
139 | *(tc->dirfilter) = SHOW_PLAYLIST; | ||
140 | |||
141 | if (ft_load(tc, playlist_dir) < 0) | ||
142 | { | ||
143 | gui_syncsplash(HZ*2, true, str(LANG_CATALOG_NO_DIRECTORY), | ||
144 | playlist_dir); | ||
145 | goto exit; | ||
146 | } | ||
147 | |||
148 | files = (struct entry*) tc->dircache; | ||
149 | num_files = tc->filesindir; | ||
150 | |||
151 | /* we've overwritten the dircache so tree browser will need to be | ||
152 | reloaded */ | ||
153 | reload_directory(); | ||
154 | |||
155 | /* if it exists, most recent playlist will always be index 0 */ | ||
156 | if (most_recent_playlist[0] != '\0') | ||
157 | { | ||
158 | index = 1; | ||
159 | most_recent = true; | ||
160 | } | ||
161 | |||
162 | for (i=0; i<num_files && index<num_items; i++) | ||
163 | { | ||
164 | if (files[i].attr & TREE_ATTR_M3U) | ||
165 | { | ||
166 | if (most_recent && !strncmp(files[i].name, most_recent_playlist, | ||
167 | sizeof(most_recent_playlist))) | ||
168 | { | ||
169 | playlists[0] = files[i].name; | ||
170 | most_recent = false; | ||
171 | } | ||
172 | else | ||
173 | { | ||
174 | playlists[index] = files[i].name; | ||
175 | index++; | ||
176 | } | ||
177 | } | ||
178 | } | ||
179 | |||
180 | *num_playlists = index; | ||
181 | |||
182 | /* we couldn't find the most recent playlist, shift all playlists up */ | ||
183 | if (most_recent) | ||
184 | { | ||
185 | for (i=0; i<index-1; i++) | ||
186 | playlists[i] = playlists[i+1]; | ||
187 | |||
188 | (*num_playlists)--; | ||
189 | |||
190 | most_recent_playlist[0] = '\0'; | ||
191 | } | ||
192 | |||
193 | result = 0; | ||
194 | |||
195 | exit: | ||
196 | *(tc->dirfilter) = dirfilter; | ||
197 | return result; | ||
198 | } | ||
199 | |||
200 | /* Callback for gui_synclist */ | ||
201 | static char* playlist_callback_name(int selected_item, void* data, | ||
202 | char* buffer) | ||
203 | { | ||
204 | char** playlists = (char**) data; | ||
205 | |||
206 | strncpy(buffer, playlists[selected_item], MAX_PATH); | ||
207 | |||
208 | return buffer; | ||
209 | } | ||
210 | |||
211 | /* Display all playlists in catalog. Selected "playlist" is returned. | ||
212 | If "view" mode is set then we're not adding anything into playlist. */ | ||
213 | static int display_playlists(char* playlist, bool view) | ||
214 | { | ||
215 | int result = -1; | ||
216 | int num_playlists = 0; | ||
217 | int lastbutton = BUTTON_NONE; | ||
218 | bool exit = false; | ||
219 | char temp_buf[MAX_PATH]; | ||
220 | char* playlists[MAX_PLAYLISTS]; | ||
221 | struct gui_synclist playlist_lists; | ||
222 | |||
223 | if (create_playlist_list(playlists, sizeof(playlists), | ||
224 | &num_playlists) != 0) | ||
225 | return -1; | ||
226 | |||
227 | if (num_playlists <= 0) | ||
228 | { | ||
229 | gui_syncsplash(HZ*2, true, str(LANG_CATALOG_NO_PLAYLISTS)); | ||
230 | return -1; | ||
231 | } | ||
232 | |||
233 | if (!playlist) | ||
234 | playlist = temp_buf; | ||
235 | |||
236 | gui_synclist_init(&playlist_lists, playlist_callback_name, playlists, | ||
237 | false, 1); | ||
238 | gui_synclist_set_nb_items(&playlist_lists, num_playlists); | ||
239 | gui_synclist_draw(&playlist_lists); | ||
240 | |||
241 | while (!exit) | ||
242 | { | ||
243 | int button = button_get_w_tmo(HZ/2); | ||
244 | char* sel_file; | ||
245 | |||
246 | gui_synclist_do_button(&playlist_lists, button); | ||
247 | |||
248 | sel_file = playlists[gui_synclist_get_sel_pos(&playlist_lists)]; | ||
249 | |||
250 | switch (button) | ||
251 | { | ||
252 | case TREE_EXIT: | ||
253 | #ifdef TREE_RC_EXIT | ||
254 | case TREE_RC_EXIT: | ||
255 | #endif | ||
256 | #ifdef TREE_OFF | ||
257 | case TREE_OFF: | ||
258 | #endif | ||
259 | exit = true; | ||
260 | break; | ||
261 | |||
262 | #ifdef TREE_ENTER | ||
263 | case TREE_ENTER: | ||
264 | case TREE_ENTER | BUTTON_REPEAT: | ||
265 | #endif | ||
266 | #ifdef TREE_RC_RUN | ||
267 | case TREE_RC_RUN: | ||
268 | #endif | ||
269 | case TREE_RUN: | ||
270 | #ifdef TREE_RUN_PRE | ||
271 | if (((button == TREE_RUN) | ||
272 | #ifdef TREE_RC_RUN_PRE | ||
273 | || (button == TREE_RC_RUN)) | ||
274 | && ((lastbutton != TREE_RC_RUN_PRE) | ||
275 | #endif | ||
276 | && (lastbutton != TREE_RUN_PRE))) | ||
277 | break; | ||
278 | #endif | ||
279 | |||
280 | if (view) | ||
281 | { | ||
282 | /* In view mode, selecting a playlist starts playback */ | ||
283 | if (playlist_create(playlist_dir, sel_file) != -1) | ||
284 | { | ||
285 | if (global_settings.playlist_shuffle) | ||
286 | playlist_shuffle(current_tick, -1); | ||
287 | playlist_start(0, 0); | ||
288 | } | ||
289 | } | ||
290 | else | ||
291 | { | ||
292 | /* we found the playlist we want to add to */ | ||
293 | snprintf(playlist, MAX_PATH, "%s/%s", playlist_dir, | ||
294 | sel_file); | ||
295 | } | ||
296 | |||
297 | result = 0; | ||
298 | exit = true; | ||
299 | break; | ||
300 | |||
301 | case TREE_CONTEXT: | ||
302 | #ifdef TREE_CONTEXT2 | ||
303 | case TREE_CONTEXT2: | ||
304 | #endif | ||
305 | #ifdef TREE_RC_CONTEXT | ||
306 | case TREE_RC_CONTEXT: | ||
307 | #endif | ||
308 | /* context menu only available in view mode */ | ||
309 | if (view) | ||
310 | { | ||
311 | snprintf(playlist, MAX_PATH, "%s/%s", playlist_dir, | ||
312 | sel_file); | ||
313 | |||
314 | if (onplay(playlist, TREE_ATTR_M3U, | ||
315 | CONTEXT_TREE) != ONPLAY_OK) | ||
316 | { | ||
317 | result = 0; | ||
318 | exit = true; | ||
319 | } | ||
320 | else | ||
321 | gui_synclist_draw(&playlist_lists); | ||
322 | } | ||
323 | break; | ||
324 | |||
325 | case BUTTON_NONE: | ||
326 | gui_syncstatusbar_draw(&statusbars, false); | ||
327 | break; | ||
328 | |||
329 | default: | ||
330 | if(default_event_handler(button) == SYS_USB_CONNECTED) | ||
331 | { | ||
332 | result = -1; | ||
333 | exit = true; | ||
334 | } | ||
335 | break; | ||
336 | } | ||
337 | |||
338 | lastbutton = button; | ||
339 | } | ||
340 | |||
341 | return result; | ||
342 | } | ||
343 | |||
344 | /* display number of tracks inserted into playlists. Used for directory | ||
345 | insert */ | ||
346 | static void display_insert_count(int count) | ||
347 | { | ||
348 | gui_syncsplash(0, true, str(LANG_PLAYLIST_INSERT_COUNT), count, | ||
349 | #if CONFIG_KEYPAD == PLAYER_PAD | ||
350 | str(LANG_STOP_ABORT) | ||
351 | #else | ||
352 | str(LANG_OFF_ABORT) | ||
353 | #endif | ||
354 | ); | ||
355 | } | ||
356 | |||
357 | /* Add specified track into playlist. Callback from directory insert */ | ||
358 | static int add_track_to_playlist(char* filename, void* context) | ||
359 | { | ||
360 | struct add_track_context* c = (struct add_track_context*) context; | ||
361 | |||
362 | if (fdprintf(c->fd, "%s\n", filename) <= 0) | ||
363 | return -1; | ||
364 | |||
365 | (c->count)++; | ||
366 | |||
367 | if (((c->count)%PLAYLIST_DISPLAY_COUNT) == 0) | ||
368 | display_insert_count(c->count); | ||
369 | |||
370 | return 0; | ||
371 | } | ||
372 | |||
373 | /* Add "sel" file into specified "playlist". How to insert depends on type | ||
374 | of file */ | ||
375 | static int add_to_playlist(const char* playlist, char* sel, int sel_attr) | ||
376 | { | ||
377 | int fd; | ||
378 | int result = -1; | ||
379 | |||
380 | fd = open(playlist, O_CREAT|O_WRONLY|O_APPEND); | ||
381 | if(fd < 0) | ||
382 | return result; | ||
383 | |||
384 | /* In case we're in the playlist directory */ | ||
385 | reload_directory(); | ||
386 | |||
387 | if ((sel_attr & TREE_ATTR_MASK) == TREE_ATTR_MPA) | ||
388 | { | ||
389 | /* append the selected file */ | ||
390 | if (fdprintf(fd, "%s\n", sel) > 0) | ||
391 | result = 0; | ||
392 | } | ||
393 | else if ((sel_attr & TREE_ATTR_MASK) == TREE_ATTR_M3U) | ||
394 | { | ||
395 | /* append playlist */ | ||
396 | int f, fs, i; | ||
397 | char buf[1024]; | ||
398 | |||
399 | if(strcasecmp(playlist, sel) == 0) | ||
400 | goto exit; | ||
401 | |||
402 | f = open(sel, O_RDONLY); | ||
403 | if (f < 0) | ||
404 | goto exit; | ||
405 | |||
406 | fs = filesize(f); | ||
407 | |||
408 | for (i=0; i<fs;) | ||
409 | { | ||
410 | int n; | ||
411 | |||
412 | n = read(f, buf, sizeof(buf)); | ||
413 | if (n < 0) | ||
414 | break; | ||
415 | |||
416 | if (write(fd, buf, n) < 0) | ||
417 | break; | ||
418 | |||
419 | i += n; | ||
420 | } | ||
421 | |||
422 | if (i >= fs) | ||
423 | result = 0; | ||
424 | |||
425 | close(f); | ||
426 | } | ||
427 | else if (sel_attr & ATTR_DIRECTORY) | ||
428 | { | ||
429 | /* search directory for tracks and append to playlist */ | ||
430 | bool recurse = false; | ||
431 | char *lines[] = { | ||
432 | (char *)str(LANG_RECURSE_DIRECTORY_QUESTION), | ||
433 | sel | ||
434 | }; | ||
435 | struct text_message message={lines, 2}; | ||
436 | struct add_track_context context; | ||
437 | |||
438 | if (global_settings.recursive_dir_insert != RECURSE_ASK) | ||
439 | recurse = (bool)global_settings.recursive_dir_insert; | ||
440 | else | ||
441 | { | ||
442 | /* Ask if user wants to recurse directory */ | ||
443 | recurse = (gui_syncyesno_run(&message, NULL, NULL)==YESNO_YES); | ||
444 | } | ||
445 | |||
446 | context.fd = fd; | ||
447 | context.count = 0; | ||
448 | |||
449 | display_insert_count(0); | ||
450 | |||
451 | result = playlist_directory_tracksearch(sel, recurse, | ||
452 | add_track_to_playlist, &context); | ||
453 | |||
454 | display_insert_count(context.count); | ||
455 | } | ||
456 | |||
457 | exit: | ||
458 | close(fd); | ||
459 | return result; | ||
460 | } | ||
461 | |||
462 | bool catalog_view_playlists(void) | ||
463 | { | ||
464 | if (initialize_catalog() == -1) | ||
465 | return false; | ||
466 | |||
467 | if (display_playlists(NULL, true) == -1) | ||
468 | return false; | ||
469 | |||
470 | return true; | ||
471 | } | ||
472 | |||
473 | bool catalog_add_to_a_playlist(char* sel, int sel_attr, bool new_playlist) | ||
474 | { | ||
475 | char playlist[MAX_PATH]; | ||
476 | |||
477 | if (initialize_catalog() == -1) | ||
478 | return false; | ||
479 | |||
480 | if (new_playlist) | ||
481 | { | ||
482 | snprintf(playlist, MAX_PATH, "%s/", playlist_dir); | ||
483 | if (kbd_input(playlist, MAX_PATH)) | ||
484 | return false; | ||
485 | |||
486 | if(strlen(playlist) <= 4 || | ||
487 | strcasecmp(&playlist[strlen(playlist)-4], ".m3u")) | ||
488 | strcat(playlist, ".m3u"); | ||
489 | } | ||
490 | else | ||
491 | { | ||
492 | if (display_playlists(playlist, false) == -1) | ||
493 | return false; | ||
494 | } | ||
495 | |||
496 | if (add_to_playlist(playlist, sel, sel_attr) == 0) | ||
497 | { | ||
498 | strncpy(most_recent_playlist, playlist+playlist_dir_length+1, | ||
499 | sizeof(most_recent_playlist)); | ||
500 | return true; | ||
501 | } | ||
502 | else | ||
503 | return false; | ||
504 | } | ||