/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2006 Sebastian Henriksen, Hardeep Sidhu * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include #include #include #include "string-extra.h" #include "action.h" #include "dir.h" #include "file.h" #include "filetree.h" #include "kernel.h" #include "keyboard.h" #include "lang.h" #include "list.h" #include "misc.h" #include "pathfuncs.h" #include "onplay.h" #include "playlist.h" #include "settings.h" #include "rbpaths.h" #include "splash.h" #include "tree.h" #include "yesno.h" #include "filetypes.h" #include "debug.h" #include "playlist_catalog.h" #include "talk.h" #include "playlist_viewer.h" #include "bookmark.h" #include "root_menu.h" /* Use for recursive directory search */ struct add_track_context { int fd; int count; }; enum catbrowse_status_flags{ CATBROWSE_NOTHING = 0, CATBROWSE_CATVIEW, CATBROWSE_PLAYLIST }; /* keep track of most recently used playlist */ static char most_recent_playlist[MAX_PATH]; /* we need playlist_dir_length for easy removal of playlist dir prefix */ static size_t playlist_dir_length; /* keep track of what browser(s) are current to prevent reentry */ static int browser_status = CATBROWSE_NOTHING; static size_t get_directory(char* dirbuf, size_t dirbuf_sz) { char *pl_dir = PLAYLIST_CATALOG_DEFAULT_DIR; /* directory config is of the format: "dir: /path/to/dir" */ if (global_settings.playlist_catalog_dir[0] != '\0') { pl_dir = global_settings.playlist_catalog_dir; } /* remove duplicate leading '/' */ if (pl_dir[0] == '/' && pl_dir[1] == '/') { pl_dir++; } return strlcpy(dirbuf, pl_dir, dirbuf_sz); } /* Retrieve playlist directory from config file and verify it exists * attempts to create directory * catalog dir is returned in dirbuf */ static int initialize_catalog_buf(char* dirbuf, size_t dirbuf_sz) { playlist_dir_length = get_directory(dirbuf, dirbuf_sz); if (playlist_dir_length >= dirbuf_sz) { return -2; } if (!dir_exists(dirbuf)) { if (mkdir(dirbuf) < 0) { splashf(HZ*2, ID2P(LANG_CATALOG_NO_DIRECTORY), dirbuf); return -1; } else { memset(most_recent_playlist, 0, sizeof(most_recent_playlist)); } } return 0; } /* Retrieve playlist directory from config file and verify it exists * attempts to create directory * Don't inline as we want the stack to be freed ASAP */ static NO_INLINE int initialize_catalog(void) { char playlist_dir[MAX_PATH]; return initialize_catalog_buf(playlist_dir, sizeof(playlist_dir)); } void catalog_set_directory(const char* directory) { if (directory == NULL) { global_settings.playlist_catalog_dir[0] = '\0'; } else { strmemccpy(global_settings.playlist_catalog_dir, directory, sizeof(global_settings.playlist_catalog_dir)); } initialize_catalog(); } void catalog_get_directory(char* dirbuf, size_t dirbuf_sz) { if (initialize_catalog_buf(dirbuf, dirbuf_sz) < 0) { dirbuf[0] = '\0'; return; } } /* Display all playlists in catalog. Selected "playlist" is returned. * If status is CATBROWSE_CATVIEW then we're not adding anything into playlist */ static int display_playlists(char* playlist, enum catbrowse_status_flags status) { static bool reopen_last_playlist = false; static int most_recent_selection = 0; int result = -1; char selected_playlist[MAX_PATH]; selected_playlist[0] = '\0'; browser_status |= status; bool view = (status == CATBROWSE_CATVIEW); struct browse_context browse = { .dirfilter = SHOW_M3U, .flags = BROWSE_SELECTONLY | (view ? 0 : BROWSE_NO_CONTEXT_MENU), .title = str(LANG_CATALOG), .icon = Icon_NOICON, .root = selected_playlist, .selected = &most_recent_playlist[playlist_dir_length + 1], .buf = selected_playlist, .bufsize = sizeof(selected_playlist), }; restart: /* set / restore the root directory for the browser */ catalog_get_directory(selected_playlist, sizeof(selected_playlist)); browse.flags &= ~BROWSE_SELECTED; if (view && reopen_last_playlist) { switch (playlist_viewer_ex(most_recent_playlist, &most_recent_selection)) { case PLAYLIST_VIEWER_OK: { result = 0; break; } case PLAYLIST_VIEWER_CANCEL: { reopen_last_playlist = false; goto restart; } case PLAYLIST_VIEWER_USB: case PLAYLIST_VIEWER_MAINMENU: /* Fall through */ default: break; } } else /* browse playlist dir */ { int browse_ret = rockbox_browse(&browse); if (browse_ret == GO_TO_WPS || (view && browse_ret == GO_TO_PREVIOUS_MUSIC)) result = 0; } if (browse.flags & BROWSE_SELECTED) /* User picked a playlist */ { if (strcmp(most_recent_playlist, selected_playlist)) /* isn't most recent one */ { strmemccpy(most_recent_playlist, selected_playlist, sizeof(most_recent_playlist)); most_recent_selection = 0; reopen_last_playlist = false; } if (view) /* display playlist contents or resume bookmark */ { int res = bookmark_autoload(selected_playlist); if (res == BOOKMARK_DO_RESUME) result = 0; else if (res == BOOKMARK_CANCEL) goto restart; else { switch (playlist_viewer_ex(selected_playlist, &most_recent_selection)) { case PLAYLIST_VIEWER_OK: { reopen_last_playlist = true; result = 0; break; } case PLAYLIST_VIEWER_CANCEL: { goto restart; } case PLAYLIST_VIEWER_USB: case PLAYLIST_VIEWER_MAINMENU: /* Fall through */ default: reopen_last_playlist = true; break; } } } else /* we're just adding something to a playlist */ { result = 0; strmemccpy(playlist, selected_playlist, MAX_PATH); } } browser_status &= ~status; return result; } /* display number of tracks inserted into playlists. Used for directory insert */ static void display_insert_count(int count) { static long talked_tick = 0; if(global_settings.talk_menu && count && (talked_tick == 0 || TIME_AFTER(current_tick, talked_tick+5*HZ))) { talked_tick = current_tick; talk_number(count, false); talk_id(LANG_PLAYLIST_INSERT_COUNT, true); } splashf(0, str(LANG_PLAYLIST_INSERT_COUNT), count, str(LANG_OFF_ABORT)); } /* Add specified track into playlist. Callback from directory insert */ static int add_track_to_playlist(char* filename, void* context) { struct add_track_context* c = (struct add_track_context*) context; if (fdprintf(c->fd, "%s\n", filename) <= 0) return -1; (c->count)++; if (((c->count)%PLAYLIST_DISPLAY_COUNT) == 0) display_insert_count(c->count); return 0; } /* Add "sel" file into specified "playlist". How to insert depends on type of file */ static int add_to_playlist(const char* playlist, bool new_playlist, const char* sel, int sel_attr) { int fd; int result = -1; if (new_playlist) fd = open_utf8(playlist, O_CREAT|O_WRONLY|O_TRUNC); else fd = open(playlist, O_CREAT|O_WRONLY|O_APPEND, 0666); if(fd < 0) { splash(HZ*2, ID2P(LANG_FAILED)); return result; } /* In case we're in the playlist directory */ reload_directory(); if ((sel_attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO) { /* append the selected file */ if (fdprintf(fd, "%s\n", sel) > 0) result = 0; } else if ((sel_attr & FILE_ATTR_MASK) == FILE_ATTR_M3U) { /* append playlist */ int f, fs, i; char buf[1024]; if(strcasecmp(playlist, sel) == 0) goto exit; f = open_utf8(sel, O_RDONLY); if (f < 0) goto exit; i = lseek(f, 0, SEEK_CUR); fs = filesize(f); while (i < fs) { int n; n = read(f, buf, sizeof(buf)); if (n < 0) break; if (write(fd, buf, n) < 0) break; i += n; } if (i >= fs) result = 0; close(f); } else if (sel_attr & ATTR_DIRECTORY) { /* search directory for tracks and append to playlist */ bool recurse = false; const char *lines[] = { ID2P(LANG_RECURSE_DIRECTORY_QUESTION), sel}; const struct text_message message={lines, 2}; struct add_track_context context; if (global_settings.recursive_dir_insert != RECURSE_ASK) recurse = (bool)global_settings.recursive_dir_insert; else { /* Ask if user wants to recurse directory */ recurse = (gui_syncyesno_run(&message, NULL, NULL)==YESNO_YES); } context.fd = fd; context.count = 0; display_insert_count(0); result = playlist_directory_tracksearch(sel, recurse, add_track_to_playlist, &context); display_insert_count(context.count); } exit: close(fd); return result; } bool catalog_view_playlists(void) { if ((browser_status & CATBROWSE_CATVIEW) == CATBROWSE_CATVIEW) return false; if (initialize_catalog() < 0) return false; return (display_playlists(NULL, CATBROWSE_CATVIEW) >= 0); } static int (*ctx_add_to_playlist)(const char* playlist, bool new_playlist); bool catalog_add_to_a_playlist(const char* sel, int sel_attr, bool new_playlist, char *m3u8name, void (*add_to_pl_cb)) { int result; char playlist[MAX_PATH + 7]; /* room for /.m3u8\0*/ if ((browser_status & CATBROWSE_PLAYLIST) == CATBROWSE_PLAYLIST) return false; if (initialize_catalog_buf(playlist, sizeof(playlist)) < 0) return false; if (new_playlist) { size_t len; if (m3u8name == NULL) { const char *name; /* If sel is empty, root, or playlist directory we use 'all' */ if (!sel || !strcmp(sel, "/") || !strcmp(sel, playlist)) { sel = "/"; name = "/all"; } else /*If sel is a folder, we prefill the text field with its name*/ name = strrchr(sel, '/'); if (name == NULL || ((sel_attr & ATTR_DIRECTORY) != ATTR_DIRECTORY)) name = "/"; strlcat(playlist, name, sizeof(playlist)); } else strmemccpy(playlist, m3u8name, MAX_PATH); if (kbd_input(playlist, MAX_PATH, NULL)) return false; len = strlen(playlist); if(len > 4 && !strcasecmp(&playlist[len-4], ".m3u")) strlcat(playlist, "8", sizeof(playlist)); else if(len <= 5 || strcasecmp(&playlist[len-5], ".m3u8")) strlcat(playlist, ".m3u8", sizeof(playlist)); } else { if (display_playlists(playlist, CATBROWSE_PLAYLIST) < 0) return false; } if (add_to_pl_cb != NULL) { ctx_add_to_playlist = add_to_pl_cb; result = ctx_add_to_playlist(playlist, new_playlist); } else result = add_to_playlist(playlist, new_playlist, sel, sel_attr); return (result == 0); }