mirror of
https://github.com/Rockbox/rockbox.git
synced 2025-10-13 10:07:38 -04:00
Basically, this just replaces str(STRID) with ID2P(STRID). The voiced version of these strings cannot not have any format specifiers (enforced by the language tooling) and are instead more generic. As many of these are error conditions, it is doubly important for them to be voiced in some way. There are some places in the code that perform their own voicing for splash messages (eg the shutdown code); those are left alone. Change-Id: I7d51af351e8fa5c4beee42fbfc02719f1d6591b8
520 lines
15 KiB
C
520 lines
15 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* 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 <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#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"
|
|
#include "general.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)
|
|
{
|
|
const 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;
|
|
}
|
|
|
|
return path_append(dirbuf, pl_dir, PA_SEP_SOFT, 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) {
|
|
if (global_settings.talk_menu) {
|
|
talk_id(LANG_CATALOG_NO_DIRECTORY, true);
|
|
talk_dir_or_spell(dirbuf, NULL, true);
|
|
talk_force_enqueue_next();
|
|
}
|
|
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
|
|
{
|
|
path_append(global_settings.playlist_catalog_dir, directory,
|
|
PA_SEP_SOFT, 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_PLAYLISTS),
|
|
.icon = Icon_Playlist,
|
|
.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 */
|
|
{
|
|
path_append(most_recent_playlist, selected_playlist,
|
|
PA_SEP_SOFT, 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, ID2P(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 */
|
|
int catalog_insert_into(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;
|
|
const char *lines[] = {
|
|
ID2P(LANG_RECURSE_DIRECTORY_QUESTION), sel};
|
|
const struct text_message message={lines, 2};
|
|
struct add_track_context context;
|
|
|
|
|
|
if (sel[1] == '\0' && sel[0] == PATH_ROOTCHR)
|
|
recurse = true;
|
|
else 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 void apply_playlist_extension(char* buf, size_t buf_size)
|
|
{
|
|
size_t len = strlen(buf);
|
|
if(len > 4 && !strcasecmp(&buf[len-4], ".m3u"))
|
|
strlcat(buf, "8", buf_size);
|
|
else if(len <= 5 || strcasecmp(&buf[len-5], ".m3u8"))
|
|
strlcat(buf, ".m3u8", buf_size);
|
|
}
|
|
|
|
static int remove_extension(char* path)
|
|
{
|
|
char *dot = strrchr(path, '.');
|
|
if (dot)
|
|
*dot = '\0';
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
bool catalog_pick_new_playlist_name(char *pl_name, size_t buf_size,
|
|
const char* curr_pl_name)
|
|
{
|
|
char bmark_file[MAX_PATH + 7], *p;
|
|
bool do_save = false;
|
|
while (!do_save && !remove_extension(pl_name) &&
|
|
!kbd_input(pl_name, buf_size - 7, NULL))
|
|
{
|
|
if (*pl_name == PATH_SEPCH) /* Require absolute filenames */
|
|
{
|
|
p = strrchr(pl_name, PATH_SEPCH);
|
|
do_save = *(p + 1); /* Disallow empty filename */
|
|
|
|
/* prevent illegal characters */
|
|
fix_path_part(p, 1, strlen(p + 1));
|
|
|
|
/* Create dir if necessary */
|
|
if (do_save && p != (char *) pl_name)
|
|
{
|
|
*p = '\0';
|
|
do_save = dir_exists(pl_name) || mkdir(pl_name) == 0;
|
|
if (!do_save)
|
|
splashf(HZ*2, ID2P(LANG_CATALOG_NO_DIRECTORY), pl_name);
|
|
*p = PATH_SEPCH;
|
|
}
|
|
}
|
|
apply_playlist_extension(pl_name, buf_size);
|
|
|
|
/* warn before overwriting existing (different) playlist */
|
|
if (do_save && (!curr_pl_name || strcmp(curr_pl_name, pl_name)))
|
|
{
|
|
if (file_exists(pl_name))
|
|
do_save = yesno_pop(ID2P(LANG_REALLY_OVERWRITE));
|
|
|
|
if (do_save) /* delete bookmark file unrelated to new playlist */
|
|
{
|
|
snprintf(bmark_file, sizeof(bmark_file), "%s.bmark", pl_name);
|
|
if (file_exists(bmark_file))
|
|
remove(bmark_file);
|
|
}
|
|
}
|
|
}
|
|
return do_save;
|
|
}
|
|
|
|
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*/
|
|
size_t basename_start;
|
|
if ((browser_status & CATBROWSE_PLAYLIST) == CATBROWSE_PLAYLIST)
|
|
return false;
|
|
|
|
if (initialize_catalog_buf(playlist, sizeof(playlist)) < 0)
|
|
return false;
|
|
|
|
if (new_playlist)
|
|
{
|
|
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))
|
|
create_numbered_filename(playlist, playlist, PLAYLIST_UNTITLED_PREFIX,
|
|
".m3u8", 1 IF_CNFN_NUM_(, NULL));
|
|
else
|
|
{
|
|
basename_start = strlen(playlist) + 1;
|
|
strlcat(playlist, name, sizeof(playlist));
|
|
fix_path_part(playlist, basename_start,
|
|
sizeof(playlist) - 1 - basename_start) ;
|
|
apply_playlist_extension(playlist, sizeof(playlist));
|
|
}
|
|
}
|
|
else
|
|
strmemccpy(playlist, m3u8name, sizeof(playlist));
|
|
|
|
if (!catalog_pick_new_playlist_name(playlist, sizeof(playlist), NULL))
|
|
return false;
|
|
}
|
|
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 = catalog_insert_into(playlist, new_playlist, sel, sel_attr);
|
|
|
|
return (result == 0);
|
|
}
|