diff options
-rw-r--r-- | apps/SOURCES | 1 | ||||
-rw-r--r-- | apps/lang/english.lang | 85 | ||||
-rw-r--r-- | apps/onplay.c | 52 | ||||
-rw-r--r-- | apps/playlist.c | 256 | ||||
-rw-r--r-- | apps/playlist.h | 3 | ||||
-rw-r--r-- | apps/playlist_catalog.c | 504 | ||||
-rw-r--r-- | apps/playlist_catalog.h | 39 | ||||
-rw-r--r-- | apps/playlist_menu.c | 12 |
8 files changed, 832 insertions, 120 deletions
diff --git a/apps/SOURCES b/apps/SOURCES index 4c479b7a60..7580c5e3f6 100644 --- a/apps/SOURCES +++ b/apps/SOURCES | |||
@@ -13,6 +13,7 @@ menu.c | |||
13 | misc.c | 13 | misc.c |
14 | onplay.c | 14 | onplay.c |
15 | playlist.c | 15 | playlist.c |
16 | playlist_catalog.c | ||
16 | playlist_menu.c | 17 | playlist_menu.c |
17 | playlist_viewer.c | 18 | playlist_viewer.c |
18 | plugin.c | 19 | plugin.c |
diff --git a/apps/lang/english.lang b/apps/lang/english.lang index 0bf10055e6..78e4ab6147 100644 --- a/apps/lang/english.lang +++ b/apps/lang/english.lang | |||
@@ -8571,3 +8571,88 @@ | |||
8571 | *: "Export modifications" | 8571 | *: "Export modifications" |
8572 | </voice> | 8572 | </voice> |
8573 | </phrase> | 8573 | </phrase> |
8574 | <phrase> | ||
8575 | id: LANG_CATALOG | ||
8576 | desc: in onplay menu | ||
8577 | user: | ||
8578 | <source> | ||
8579 | *: "Playlist catalog" | ||
8580 | </source> | ||
8581 | <dest> | ||
8582 | *: "Playlist catalog" | ||
8583 | </dest> | ||
8584 | <voice> | ||
8585 | *: "Playlist catalog" | ||
8586 | </voice> | ||
8587 | </phrase> | ||
8588 | <phrase> | ||
8589 | id: LANG_CATALOG_ADD_TO | ||
8590 | desc: in onplay playlist catalog submenu | ||
8591 | user: | ||
8592 | <source> | ||
8593 | *: "Add to playlist" | ||
8594 | </source> | ||
8595 | <dest> | ||
8596 | *: "Add to playlist" | ||
8597 | </dest> | ||
8598 | <voice> | ||
8599 | *: "Add to playlist" | ||
8600 | </voice> | ||
8601 | </phrase> | ||
8602 | <phrase> | ||
8603 | id: LANG_CATALOG_ADD_TO_NEW | ||
8604 | desc: in onplay playlist catalog submenu | ||
8605 | user: | ||
8606 | <source> | ||
8607 | *: "Add to new playlist" | ||
8608 | </source> | ||
8609 | <dest> | ||
8610 | *: "Add to new playlist" | ||
8611 | </dest> | ||
8612 | <voice> | ||
8613 | *: "Add to new playlist" | ||
8614 | </voice> | ||
8615 | </phrase> | ||
8616 | <phrase> | ||
8617 | id: LANG_CATALOG_VIEW | ||
8618 | desc: in onplay playlist catalog submenu | ||
8619 | user: | ||
8620 | <source> | ||
8621 | *: "View catalog" | ||
8622 | </source> | ||
8623 | <dest> | ||
8624 | *: "View catalog" | ||
8625 | </dest> | ||
8626 | <voice> | ||
8627 | *: "View catalog" | ||
8628 | </voice> | ||
8629 | </phrase> | ||
8630 | <phrase> | ||
8631 | id: LANG_CATALOG_NO_DIRECTORY | ||
8632 | desc: error message when playlist catalog directory doesn't exist | ||
8633 | user: | ||
8634 | <source> | ||
8635 | *: "%s doesn't exist" | ||
8636 | </source> | ||
8637 | <dest> | ||
8638 | *: "%s doesn't exist" | ||
8639 | </dest> | ||
8640 | <voice> | ||
8641 | *: "" | ||
8642 | </voice> | ||
8643 | </phrase> | ||
8644 | <phrase> | ||
8645 | id: LANG_CATALOG_NO_PLAYLISTS | ||
8646 | desc: error message when no playlists for playlist catalog | ||
8647 | user: | ||
8648 | <source> | ||
8649 | *: "No playlists" | ||
8650 | </source> | ||
8651 | <dest> | ||
8652 | *: "No playlists" | ||
8653 | </dest> | ||
8654 | <voice> | ||
8655 | *: "" | ||
8656 | </voice> | ||
8657 | </phrase> | ||
8658 | |||
diff --git a/apps/onplay.c b/apps/onplay.c index bc486d1a38..d552e41826 100644 --- a/apps/onplay.c +++ b/apps/onplay.c | |||
@@ -63,6 +63,7 @@ | |||
63 | #include "eq_menu.h" | 63 | #include "eq_menu.h" |
64 | #endif | 64 | #endif |
65 | #include "playlist_menu.h" | 65 | #include "playlist_menu.h" |
66 | #include "playlist_catalog.h" | ||
66 | 67 | ||
67 | static int context; | 68 | static int context; |
68 | static char* selected_file = NULL; | 69 | static char* selected_file = NULL; |
@@ -222,6 +223,50 @@ static bool view_playlist(void) | |||
222 | return result; | 223 | return result; |
223 | } | 224 | } |
224 | 225 | ||
226 | bool cat_add_to_a_playlist(void) | ||
227 | { | ||
228 | return catalog_add_to_a_playlist(selected_file, selected_file_attr, | ||
229 | false); | ||
230 | } | ||
231 | |||
232 | bool cat_add_to_a_new_playlist(void) | ||
233 | { | ||
234 | return catalog_add_to_a_playlist(selected_file, selected_file_attr, true); | ||
235 | } | ||
236 | |||
237 | static bool cat_playlist_options(void) | ||
238 | { | ||
239 | struct menu_item items[3]; | ||
240 | int m, i=0, result; | ||
241 | bool ret = false; | ||
242 | |||
243 | if ((audio_status() & AUDIO_STATUS_PLAY && context == CONTEXT_WPS) || | ||
244 | context == CONTEXT_TREE) | ||
245 | { | ||
246 | if (context == CONTEXT_WPS) | ||
247 | { | ||
248 | items[i].desc = ID2P(LANG_CATALOG_VIEW); | ||
249 | items[i].function = catalog_view_playlists; | ||
250 | i++; | ||
251 | } | ||
252 | |||
253 | items[i].desc = ID2P(LANG_CATALOG_ADD_TO); | ||
254 | items[i].function = cat_add_to_a_playlist; | ||
255 | i++; | ||
256 | items[i].desc = ID2P(LANG_CATALOG_ADD_TO_NEW); | ||
257 | items[i].function = cat_add_to_a_new_playlist; | ||
258 | i++; | ||
259 | } | ||
260 | |||
261 | m = menu_init( items, i, NULL, NULL, NULL, NULL ); | ||
262 | result = menu_show(m); | ||
263 | if(result >= 0) | ||
264 | ret = items[result].function(); | ||
265 | menu_exit(m); | ||
266 | |||
267 | return ret; | ||
268 | } | ||
269 | |||
225 | /* Sub-menu for playlist options */ | 270 | /* Sub-menu for playlist options */ |
226 | static bool playlist_options(void) | 271 | static bool playlist_options(void) |
227 | { | 272 | { |
@@ -773,9 +818,9 @@ static int onplay_callback(int key, int menu) | |||
773 | int onplay(char* file, int attr, int from) | 818 | int onplay(char* file, int attr, int from) |
774 | { | 819 | { |
775 | #if CONFIG_CODEC == SWCODEC | 820 | #if CONFIG_CODEC == SWCODEC |
776 | struct menu_item items[13]; /* increase this if you add entries! */ | 821 | struct menu_item items[14]; /* increase this if you add entries! */ |
777 | #else | 822 | #else |
778 | struct menu_item items[11]; | 823 | struct menu_item items[12]; |
779 | #endif | 824 | #endif |
780 | int m, i=0, result; | 825 | int m, i=0, result; |
781 | #ifdef HAVE_LCD_COLOR | 826 | #ifdef HAVE_LCD_COLOR |
@@ -803,6 +848,9 @@ int onplay(char* file, int attr, int from) | |||
803 | items[i].desc = ID2P(LANG_PLAYLIST); | 848 | items[i].desc = ID2P(LANG_PLAYLIST); |
804 | items[i].function = playlist_options; | 849 | items[i].function = playlist_options; |
805 | i++; | 850 | i++; |
851 | items[i].desc = ID2P(LANG_CATALOG); | ||
852 | items[i].function = cat_playlist_options; | ||
853 | i++; | ||
806 | } | 854 | } |
807 | 855 | ||
808 | if (context == CONTEXT_WPS) | 856 | if (context == CONTEXT_WPS) |
diff --git a/apps/playlist.c b/apps/playlist.c index 93293d75ed..a08ecdfe34 100644 --- a/apps/playlist.c +++ b/apps/playlist.c | |||
@@ -133,6 +133,13 @@ | |||
133 | 133 | ||
134 | #define PLAYLIST_DISPLAY_COUNT 10 | 134 | #define PLAYLIST_DISPLAY_COUNT 10 |
135 | 135 | ||
136 | struct directory_search_context { | ||
137 | struct playlist_info* playlist; | ||
138 | int position; | ||
139 | bool queue; | ||
140 | int count; | ||
141 | }; | ||
142 | |||
136 | static bool changing_dir = false; | 143 | static bool changing_dir = false; |
137 | 144 | ||
138 | static struct playlist_info current_playlist; | 145 | static struct playlist_info current_playlist; |
@@ -151,9 +158,7 @@ static int add_indices_to_playlist(struct playlist_info* playlist, | |||
151 | static int add_track_to_playlist(struct playlist_info* playlist, | 158 | static int add_track_to_playlist(struct playlist_info* playlist, |
152 | const char *filename, int position, | 159 | const char *filename, int position, |
153 | bool queue, int seek_pos); | 160 | bool queue, int seek_pos); |
154 | static int add_directory_to_playlist(struct playlist_info* playlist, | 161 | static int directory_search_callback(char* filename, void* context); |
155 | const char *dirname, int *position, | ||
156 | bool queue, int *count, bool recurse); | ||
157 | static int remove_track_from_playlist(struct playlist_info* playlist, | 162 | static int remove_track_from_playlist(struct playlist_info* playlist, |
158 | int position, bool write); | 163 | int position, bool write); |
159 | static int randomise_playlist(struct playlist_info* playlist, | 164 | static int randomise_playlist(struct playlist_info* playlist, |
@@ -681,121 +686,46 @@ static int add_track_to_playlist(struct playlist_info* playlist, | |||
681 | } | 686 | } |
682 | 687 | ||
683 | /* | 688 | /* |
684 | * Insert directory into playlist. May be called recursively. | 689 | * Callback for playlist_directory_tracksearch to insert track into |
690 | * playlist. | ||
685 | */ | 691 | */ |
686 | static int add_directory_to_playlist(struct playlist_info* playlist, | 692 | static int directory_search_callback(char* filename, void* context) |
687 | const char *dirname, int *position, | ||
688 | bool queue, int *count, bool recurse) | ||
689 | { | 693 | { |
690 | char buf[MAX_PATH+1]; | 694 | struct directory_search_context* c = |
691 | unsigned char *count_str; | 695 | (struct directory_search_context*) context; |
692 | int result = 0; | 696 | int insert_pos; |
693 | int num_files = 0; | ||
694 | int i; | ||
695 | struct entry *files; | ||
696 | struct tree_context* tc = tree_get_context(); | ||
697 | int dirfilter = *(tc->dirfilter); | ||
698 | 697 | ||
699 | /* use the tree browser dircache to load files */ | 698 | insert_pos = add_track_to_playlist(c->playlist, filename, c->position, |
700 | *(tc->dirfilter) = SHOW_ALL; | 699 | c->queue, -1); |
701 | 700 | ||
702 | if (ft_load(tc, dirname) < 0) | 701 | if (insert_pos < 0) |
703 | { | ||
704 | gui_syncsplash(HZ*2, true, str(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR)); | ||
705 | *(tc->dirfilter) = dirfilter; | ||
706 | return -1; | 702 | return -1; |
707 | } | 703 | |
708 | 704 | (c->count)++; | |
709 | files = (struct entry*) tc->dircache; | 705 | |
710 | num_files = tc->filesindir; | 706 | /* Make sure tracks are inserted in correct order if user requests |
711 | 707 | INSERT_FIRST */ | |
712 | /* we've overwritten the dircache so tree browser will need to be | 708 | if (c->position == PLAYLIST_INSERT_FIRST || c->position >= 0) |
713 | reloaded */ | 709 | c->position = insert_pos + 1; |
714 | reload_directory(); | 710 | |
715 | 711 | if (((c->count)%PLAYLIST_DISPLAY_COUNT) == 0) | |
716 | if (queue) | ||
717 | count_str = str(LANG_PLAYLIST_QUEUE_COUNT); | ||
718 | else | ||
719 | count_str = str(LANG_PLAYLIST_INSERT_COUNT); | ||
720 | |||
721 | for (i=0; i<num_files; i++) | ||
722 | { | 712 | { |
723 | /* user abort */ | 713 | unsigned char* count_str; |
724 | if (button_get(false) == SETTINGS_CANCEL) | ||
725 | { | ||
726 | result = -1; | ||
727 | break; | ||
728 | } | ||
729 | 714 | ||
730 | if (files[i].attr & ATTR_DIRECTORY) | 715 | if (c->queue) |
731 | { | 716 | count_str = str(LANG_PLAYLIST_QUEUE_COUNT); |
732 | if (recurse) | 717 | else |
733 | { | 718 | count_str = str(LANG_PLAYLIST_INSERT_COUNT); |
734 | /* recursively add directories */ | ||
735 | snprintf(buf, sizeof(buf), "%s/%s", dirname, files[i].name); | ||
736 | result = add_directory_to_playlist(playlist, buf, position, | ||
737 | queue, count, recurse); | ||
738 | if (result < 0) | ||
739 | break; | ||
740 | |||
741 | /* we now need to reload our current directory */ | ||
742 | if(ft_load(tc, dirname) < 0) | ||
743 | { | ||
744 | result = -1; | ||
745 | break; | ||
746 | } | ||
747 | |||
748 | files = (struct entry*) tc->dircache; | ||
749 | num_files = tc->filesindir; | ||
750 | if (!num_files) | ||
751 | { | ||
752 | result = -1; | ||
753 | break; | ||
754 | } | ||
755 | } | ||
756 | else | ||
757 | continue; | ||
758 | } | ||
759 | else if ((files[i].attr & TREE_ATTR_MASK) == TREE_ATTR_MPA) | ||
760 | { | ||
761 | int insert_pos; | ||
762 | |||
763 | snprintf(buf, sizeof(buf), "%s/%s", dirname, files[i].name); | ||
764 | |||
765 | insert_pos = add_track_to_playlist(playlist, buf, *position, | ||
766 | queue, -1); | ||
767 | if (insert_pos < 0) | ||
768 | { | ||
769 | result = -1; | ||
770 | break; | ||
771 | } | ||
772 | |||
773 | (*count)++; | ||
774 | |||
775 | /* Make sure tracks are inserted in correct order if user requests | ||
776 | INSERT_FIRST */ | ||
777 | if (*position == PLAYLIST_INSERT_FIRST || *position >= 0) | ||
778 | *position = insert_pos + 1; | ||
779 | |||
780 | if ((*count%PLAYLIST_DISPLAY_COUNT) == 0) | ||
781 | { | ||
782 | display_playlist_count(*count, count_str); | ||
783 | 719 | ||
784 | if (*count == PLAYLIST_DISPLAY_COUNT && | 720 | display_playlist_count(c->count, count_str); |
785 | (audio_status() & AUDIO_STATUS_PLAY) && | 721 | |
786 | playlist->started) | 722 | if ((c->count) == PLAYLIST_DISPLAY_COUNT && |
787 | audio_flush_and_reload_tracks(); | 723 | (audio_status() & AUDIO_STATUS_PLAY) && |
788 | } | 724 | c->playlist->started) |
789 | 725 | audio_flush_and_reload_tracks(); | |
790 | /* let the other threads work */ | ||
791 | yield(); | ||
792 | } | ||
793 | } | 726 | } |
794 | 727 | ||
795 | /* restore dirfilter */ | 728 | return 0; |
796 | *(tc->dirfilter) = dirfilter; | ||
797 | |||
798 | return result; | ||
799 | } | 729 | } |
800 | 730 | ||
801 | /* | 731 | /* |
@@ -2811,9 +2741,9 @@ int playlist_insert_directory(struct playlist_info* playlist, | |||
2811 | const char *dirname, int position, bool queue, | 2741 | const char *dirname, int position, bool queue, |
2812 | bool recurse) | 2742 | bool recurse) |
2813 | { | 2743 | { |
2814 | int count = 0; | ||
2815 | int result; | 2744 | int result; |
2816 | unsigned char *count_str; | 2745 | unsigned char *count_str; |
2746 | struct directory_search_context context; | ||
2817 | 2747 | ||
2818 | if (!playlist) | 2748 | if (!playlist) |
2819 | playlist = ¤t_playlist; | 2749 | playlist = ¤t_playlist; |
@@ -2829,18 +2759,23 @@ int playlist_insert_directory(struct playlist_info* playlist, | |||
2829 | else | 2759 | else |
2830 | count_str = str(LANG_PLAYLIST_INSERT_COUNT); | 2760 | count_str = str(LANG_PLAYLIST_INSERT_COUNT); |
2831 | 2761 | ||
2832 | display_playlist_count(count, count_str); | 2762 | display_playlist_count(0, count_str); |
2763 | |||
2764 | context.playlist = playlist; | ||
2765 | context.position = position; | ||
2766 | context.queue = queue; | ||
2767 | context.count = 0; | ||
2833 | 2768 | ||
2834 | cpu_boost(true); | 2769 | cpu_boost(true); |
2835 | 2770 | ||
2836 | result = add_directory_to_playlist(playlist, dirname, &position, queue, | 2771 | result = playlist_directory_tracksearch(dirname, recurse, |
2837 | &count, recurse); | 2772 | directory_search_callback, &context); |
2838 | 2773 | ||
2839 | sync_control(playlist, false); | 2774 | sync_control(playlist, false); |
2840 | 2775 | ||
2841 | cpu_boost(false); | 2776 | cpu_boost(false); |
2842 | 2777 | ||
2843 | display_playlist_count(count, count_str); | 2778 | display_playlist_count(context.count, count_str); |
2844 | 2779 | ||
2845 | if ((audio_status() & AUDIO_STATUS_PLAY) && playlist->started) | 2780 | if ((audio_status() & AUDIO_STATUS_PLAY) && playlist->started) |
2846 | audio_flush_and_reload_tracks(); | 2781 | audio_flush_and_reload_tracks(); |
@@ -3403,3 +3338,98 @@ int playlist_save(struct playlist_info* playlist, char *filename) | |||
3403 | 3338 | ||
3404 | return result; | 3339 | return result; |
3405 | } | 3340 | } |
3341 | |||
3342 | /* | ||
3343 | * Search specified directory for tracks and notify via callback. May be | ||
3344 | * called recursively. | ||
3345 | */ | ||
3346 | int playlist_directory_tracksearch(const char* dirname, bool recurse, | ||
3347 | int (*callback)(char*, void*), | ||
3348 | void* context) | ||
3349 | { | ||
3350 | char buf[MAX_PATH+1]; | ||
3351 | int result = 0; | ||
3352 | int num_files = 0; | ||
3353 | int i; | ||
3354 | struct entry *files; | ||
3355 | struct tree_context* tc = tree_get_context(); | ||
3356 | int old_dirfilter = *(tc->dirfilter); | ||
3357 | |||
3358 | if (!callback) | ||
3359 | return -1; | ||
3360 | |||
3361 | /* use the tree browser dircache to load files */ | ||
3362 | *(tc->dirfilter) = SHOW_ALL; | ||
3363 | |||
3364 | if (ft_load(tc, dirname) < 0) | ||
3365 | { | ||
3366 | gui_syncsplash(HZ*2, true, str(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR)); | ||
3367 | *(tc->dirfilter) = old_dirfilter; | ||
3368 | return -1; | ||
3369 | } | ||
3370 | |||
3371 | files = (struct entry*) tc->dircache; | ||
3372 | num_files = tc->filesindir; | ||
3373 | |||
3374 | /* we've overwritten the dircache so tree browser will need to be | ||
3375 | reloaded */ | ||
3376 | reload_directory(); | ||
3377 | |||
3378 | for (i=0; i<num_files; i++) | ||
3379 | { | ||
3380 | /* user abort */ | ||
3381 | if (button_get(false) == SETTINGS_CANCEL) | ||
3382 | { | ||
3383 | result = -1; | ||
3384 | break; | ||
3385 | } | ||
3386 | |||
3387 | if (files[i].attr & ATTR_DIRECTORY) | ||
3388 | { | ||
3389 | if (recurse) | ||
3390 | { | ||
3391 | /* recursively add directories */ | ||
3392 | snprintf(buf, sizeof(buf), "%s/%s", dirname, files[i].name); | ||
3393 | result = playlist_directory_tracksearch(buf, recurse, | ||
3394 | callback, context); | ||
3395 | if (result < 0) | ||
3396 | break; | ||
3397 | |||
3398 | /* we now need to reload our current directory */ | ||
3399 | if(ft_load(tc, dirname) < 0) | ||
3400 | { | ||
3401 | result = -1; | ||
3402 | break; | ||
3403 | } | ||
3404 | |||
3405 | files = (struct entry*) tc->dircache; | ||
3406 | num_files = tc->filesindir; | ||
3407 | if (!num_files) | ||
3408 | { | ||
3409 | result = -1; | ||
3410 | break; | ||
3411 | } | ||
3412 | } | ||
3413 | else | ||
3414 | continue; | ||
3415 | } | ||
3416 | else if ((files[i].attr & TREE_ATTR_MASK) == TREE_ATTR_MPA) | ||
3417 | { | ||
3418 | snprintf(buf, sizeof(buf), "%s/%s", dirname, files[i].name); | ||
3419 | |||
3420 | if (callback(buf, context) != 0) | ||
3421 | { | ||
3422 | result = -1; | ||
3423 | break; | ||
3424 | } | ||
3425 | |||
3426 | /* let the other threads work */ | ||
3427 | yield(); | ||
3428 | } | ||
3429 | } | ||
3430 | |||
3431 | /* restore dirfilter */ | ||
3432 | *(tc->dirfilter) = old_dirfilter; | ||
3433 | |||
3434 | return result; | ||
3435 | } | ||
diff --git a/apps/playlist.h b/apps/playlist.h index 1a5bb3d33c..c5449a219e 100644 --- a/apps/playlist.h +++ b/apps/playlist.h | |||
@@ -158,5 +158,8 @@ char *playlist_get_name(const struct playlist_info* playlist, char *buf, | |||
158 | int playlist_get_track_info(struct playlist_info* playlist, int index, | 158 | int playlist_get_track_info(struct playlist_info* playlist, int index, |
159 | struct playlist_track_info* info); | 159 | struct playlist_track_info* info); |
160 | int playlist_save(struct playlist_info* playlist, char *filename); | 160 | int playlist_save(struct playlist_info* playlist, char *filename); |
161 | int playlist_directory_tracksearch(const char* dirname, bool recurse, | ||
162 | int (*callback)(char*, void*), | ||
163 | void* context); | ||
161 | 164 | ||
162 | #endif /* __PLAYLIST_H__ */ | 165 | #endif /* __PLAYLIST_H__ */ |
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 | } | ||
diff --git a/apps/playlist_catalog.h b/apps/playlist_catalog.h new file mode 100644 index 0000000000..591906e64f --- /dev/null +++ b/apps/playlist_catalog.h | |||
@@ -0,0 +1,39 @@ | |||
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 | #ifndef _PLAYLIST_CATALOG_H_ | ||
20 | #define _PLAYLIST_CATALOG_H_ | ||
21 | |||
22 | /* | ||
23 | * View list of playlists in catalog. | ||
24 | * ret : true if no error | ||
25 | */ | ||
26 | bool catalog_view_playlists(void); | ||
27 | |||
28 | /* | ||
29 | * Add something to a playlist (new or select from list of playlists in | ||
30 | * catalog). | ||
31 | * sel : the path of the music file, playlist or directory to add | ||
32 | * sel_attr : the attributes that tell what type of file we're adding | ||
33 | * new_playlist : whether we want to create a new playlist or add to an | ||
34 | * existing one. | ||
35 | * ret : true if the file was successfully added | ||
36 | */ | ||
37 | bool catalog_add_to_a_playlist(char* sel, int sel_attr, bool new_playlist); | ||
38 | |||
39 | #endif | ||
diff --git a/apps/playlist_menu.c b/apps/playlist_menu.c index ba4a4fe0d2..e23722160f 100644 --- a/apps/playlist_menu.c +++ b/apps/playlist_menu.c | |||
@@ -28,6 +28,7 @@ | |||
28 | #include "playlist_viewer.h" | 28 | #include "playlist_viewer.h" |
29 | #include "talk.h" | 29 | #include "talk.h" |
30 | #include "lang.h" | 30 | #include "lang.h" |
31 | #include "playlist_catalog.h" | ||
31 | #include "playlist_menu.h" | 32 | #include "playlist_menu.h" |
32 | 33 | ||
33 | static bool save_playlist(void) | 34 | static bool save_playlist(void) |
@@ -61,11 +62,12 @@ bool playlist_menu(void) | |||
61 | bool result; | 62 | bool result; |
62 | 63 | ||
63 | static const struct menu_item items[] = { | 64 | static const struct menu_item items[] = { |
64 | { ID2P(LANG_CREATE_PLAYLIST), create_playlist }, | 65 | { ID2P(LANG_CREATE_PLAYLIST), create_playlist }, |
65 | { ID2P(LANG_VIEW_DYNAMIC_PLAYLIST), playlist_viewer }, | 66 | { ID2P(LANG_VIEW_DYNAMIC_PLAYLIST), playlist_viewer }, |
66 | { ID2P(LANG_SAVE_DYNAMIC_PLAYLIST), save_playlist }, | 67 | { ID2P(LANG_SAVE_DYNAMIC_PLAYLIST), save_playlist }, |
67 | { ID2P(LANG_RECURSE_DIRECTORY), recurse_directory }, | 68 | { ID2P(LANG_CATALOG), catalog_view_playlists }, |
68 | { ID2P(LANG_WARN_ERASEDYNPLAYLIST_MENU), warnon_option}, | 69 | { ID2P(LANG_RECURSE_DIRECTORY), recurse_directory }, |
70 | { ID2P(LANG_WARN_ERASEDYNPLAYLIST_MENU), warnon_option }, | ||
69 | }; | 71 | }; |
70 | 72 | ||
71 | m = menu_init( items, sizeof items / sizeof(struct menu_item), NULL, | 73 | m = menu_init( items, sizeof items / sizeof(struct menu_item), NULL, |