forked from len0rd/rockbox
Accept FS #7667 by Alexander Levin with minor fixes by me. Splits the shortcuts plugin into two, one for adding and one for viewing. Removes hard-coded file extension and allows to link from one shortcut file to another.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@14599 a1c6a512-1295-4272-9138-f99709370657
This commit is contained in:
parent
9b3be37f84
commit
946a815cd4
11 changed files with 927 additions and 554 deletions
394
apps/plugins/shortcuts/shortcuts_common.c
Normal file
394
apps/plugins/shortcuts/shortcuts_common.c
Normal file
|
|
@ -0,0 +1,394 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2007 Bryan Childs
|
||||
* Copyright (c) 2007 Alexander Levin
|
||||
*
|
||||
* All files in this archive are subject to the GNU General Public License.
|
||||
* See the file COPYING in the source tree root for full license agreement.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include "shortcuts.h"
|
||||
MEM_FUNCTION_WRAPPERS(rb);
|
||||
|
||||
#define SHORTCUTS_FILENAME "/shortcuts.link"
|
||||
|
||||
#define PATH_DISP_SEPARATOR "\t"
|
||||
#define PATH_DISP_SEPARATOR_LEN 1 /* strlen(PATH_DISP_SEPARATOR) */
|
||||
#define CONTROL_PREFIX "#"
|
||||
#define CONTROL_PREFIX_LEN 1 /* strlen(CONTROL_PREFIX) */
|
||||
#define NAME_VALUE_SEPARATOR "="
|
||||
#define NAME_VALUE_SEPARATOR_LEN 1 /* strlen(NAME_VALUE_SEPARATOR) */
|
||||
|
||||
#define INSTR_DISPLAY_LAST_SEGMENTS "Display last path segments"
|
||||
|
||||
/* Memory (will be used for entries) */
|
||||
void *memory_buf;
|
||||
long memory_bufsize; /* Size of memory_buf in bytes */
|
||||
|
||||
|
||||
/* The file we're processing */
|
||||
sc_file_t sc_file;
|
||||
|
||||
bool parse_entry_content(char *line, sc_entry_t *entry, int last_segm);
|
||||
char *last_segments(char *path, int nsegm);
|
||||
bool is_control(char *line, sc_file_t *file);
|
||||
bool starts_with(char *string, char *prefix);
|
||||
bool parse_name_value(char *line, char *name, int namesize,
|
||||
char *value, int valuesize);
|
||||
void write_entry_to_file(int fd, sc_entry_t *entry);
|
||||
void write_int_instruction_to_file(int fd, char *instr, int value);
|
||||
|
||||
|
||||
void allocate_memory(void **buf, size_t *bufsize)
|
||||
{
|
||||
*buf = rb->plugin_get_buffer(bufsize);
|
||||
DEBUGF("Got %ld bytes of memory\n", *bufsize);
|
||||
}
|
||||
|
||||
|
||||
void init_sc_file(sc_file_t *file, void *buf, size_t bufsize)
|
||||
{
|
||||
file->entries = (sc_entry_t*)buf;
|
||||
file->max_entries = bufsize / sizeof(sc_entry_t);
|
||||
DEBUGF("Buffer capacity: %d entries\n", file->max_entries);
|
||||
file->entry_cnt = 0;
|
||||
file->show_last_segments = -1;
|
||||
}
|
||||
|
||||
|
||||
bool load_sc_file(sc_file_t *file, char *filename, bool must_exist,
|
||||
void *entry_buf, size_t entry_bufsize)
|
||||
{
|
||||
int fd = -1;
|
||||
bool ret_val = false; /* Assume bad case */
|
||||
int amountread = 0;
|
||||
char sc_content[2*MAX_PATH];
|
||||
sc_entry_t entry;
|
||||
|
||||
/* We start to load a new file -> prepare it */
|
||||
init_sc_file(&sc_file, entry_buf, entry_bufsize);
|
||||
|
||||
fd = rb->open(filename, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
/* The file didn't exist on disk */
|
||||
if (!must_exist) {
|
||||
DEBUGF("Trying to create link file '%s'...\n", filename);
|
||||
fd = rb->creat(filename);
|
||||
if (fd < 0){
|
||||
/* For some reason we couldn't create the file,
|
||||
* so return an error message and exit */
|
||||
rb->splash(HZ*2, "Couldn't create the shortcuts file %s",
|
||||
filename);
|
||||
goto end_of_proc;
|
||||
}
|
||||
/* File created, but there's nothing in it, so just exit */
|
||||
ret_val = true;
|
||||
goto end_of_proc;
|
||||
} else {
|
||||
rb->splash(HZ, "Couldn't open %s", filename);
|
||||
goto end_of_proc;
|
||||
}
|
||||
}
|
||||
|
||||
while ((amountread=rb->read_line(fd,sc_content, sizeof(sc_content)))) {
|
||||
if (is_control(sc_content, file)) {
|
||||
continue;
|
||||
}
|
||||
if (file->entry_cnt >= file->max_entries) {
|
||||
rb->splash(HZ*2, "Too many entries in the file, max allowed: %d",
|
||||
file->max_entries);
|
||||
goto end_of_proc;
|
||||
}
|
||||
if (!parse_entry_content(sc_content, &entry,file->show_last_segments)) {
|
||||
/* Could not parse the entry (too long path?) -> ignore */
|
||||
DEBUGF("Could not parse '%s' -> ignored\n", sc_content);
|
||||
continue;
|
||||
}
|
||||
DEBUGF("Parsed entry: path=%s, disp=%s\n", entry.path, entry.disp);
|
||||
append_entry(file, &entry);
|
||||
}
|
||||
|
||||
#ifdef SC_DEBUG
|
||||
print_file(file);
|
||||
#endif
|
||||
|
||||
ret_val = true; /* Everything went ok */
|
||||
|
||||
end_of_proc:
|
||||
if (fd >= 0) {
|
||||
rb->close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
#ifdef SC_DEBUG
|
||||
void print_file(sc_file_t *file)
|
||||
{
|
||||
DEBUGF("Number of entries : %d\n", file->entry_cnt);
|
||||
DEBUGF("Show Last Segments: %d\n", file->show_last_segments);
|
||||
int i;
|
||||
sc_entry_t *entry;
|
||||
for (i=0, entry=file->entries; i<file->entry_cnt; i++,entry++) {
|
||||
if (entry->explicit_disp) {
|
||||
DEBUGF("%2d. '%s', show as '%s'\n", i+1, entry->path, entry->disp);
|
||||
} else {
|
||||
DEBUGF("%2d. '%s' (%s)\n", i+1, entry->path, entry->disp);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
bool append_entry(sc_file_t *file, sc_entry_t *entry)
|
||||
{
|
||||
if (file->entry_cnt >= file->max_entries) {
|
||||
return false;
|
||||
}
|
||||
rb->memcpy(file->entries+file->entry_cnt, entry, sizeof(*entry));
|
||||
file->entry_cnt++;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool remove_entry(sc_file_t *file, int entry_idx)
|
||||
{
|
||||
if ((entry_idx<0) || (entry_idx>=file->entry_cnt)) {
|
||||
return false;
|
||||
}
|
||||
sc_entry_t *start = file->entries + entry_idx;
|
||||
rb->memmove(start, start + 1,
|
||||
(file->entry_cnt-entry_idx-1) * sizeof(sc_entry_t));
|
||||
file->entry_cnt--;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool is_valid_index(sc_file_t *file, int entry_idx)
|
||||
{
|
||||
return (entry_idx >= 0) && (entry_idx < file->entry_cnt);
|
||||
}
|
||||
|
||||
|
||||
bool parse_entry_content(char *line, sc_entry_t *entry, int last_segm)
|
||||
{
|
||||
char *sep;
|
||||
char *path, *disp;
|
||||
unsigned int path_len, disp_len;
|
||||
bool expl;
|
||||
|
||||
sep = rb->strcasestr(line, PATH_DISP_SEPARATOR);
|
||||
expl = (sep != NULL);
|
||||
if (expl) {
|
||||
/* Explicit name for the entry is specified -> use it */
|
||||
path = line;
|
||||
path_len = sep - line;
|
||||
disp = sep + PATH_DISP_SEPARATOR_LEN;
|
||||
disp_len = rb->strlen(disp);
|
||||
} else {
|
||||
/* No special name to display */
|
||||
path = line;
|
||||
path_len = rb->strlen(line);
|
||||
if (last_segm <= 0) {
|
||||
disp = path;
|
||||
} else {
|
||||
disp = last_segments(line, last_segm);
|
||||
}
|
||||
disp_len = rb->strlen(disp);
|
||||
}
|
||||
|
||||
if (path_len >= sizeof(entry->path) || disp_len >= sizeof(entry->disp)) {
|
||||
DEBUGF("Bad entry: pathlen=%d, displen=%d\n", path_len, disp_len);
|
||||
return false;
|
||||
}
|
||||
rb->strncpy(entry->path, path, path_len);
|
||||
entry->path[path_len] = '\0';
|
||||
rb->strcpy(entry->disp, disp); /* Safe since we've checked the length */
|
||||
entry->explicit_disp = expl;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
char *last_segments(char *path, int nsegm)
|
||||
{
|
||||
char *p = rb->strrchr(path, PATH_SEPARATOR[0]); /* Hack */
|
||||
int seg_cnt;
|
||||
if (p == NULL)
|
||||
return path; /* No separator??? */
|
||||
seg_cnt = 0;
|
||||
while ((p > path) && (seg_cnt < nsegm)) {
|
||||
p--;
|
||||
if (!starts_with(p, PATH_SEPARATOR)) {
|
||||
continue;
|
||||
}
|
||||
seg_cnt++;
|
||||
if (seg_cnt == nsegm && p > path) {
|
||||
p++; /* Eat the '/' to show that something has been truncated */
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
bool is_control(char *line, sc_file_t *file)
|
||||
{
|
||||
char name[MAX_PATH], value[MAX_PATH];
|
||||
if (!starts_with(line, CONTROL_PREFIX)) {
|
||||
return false;
|
||||
}
|
||||
line += CONTROL_PREFIX_LEN;
|
||||
|
||||
if (!parse_name_value(line, name, sizeof(name),
|
||||
value, sizeof(value))) {
|
||||
DEBUGF("Bad processing instruction: '%s'\n", line);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Process control instruction */
|
||||
if (rb->strcasestr(name, INSTR_DISPLAY_LAST_SEGMENTS)) {
|
||||
file->show_last_segments = rb->atoi(value);
|
||||
DEBUGF("Set show last segms to %d\n", file->show_last_segments);
|
||||
} else {
|
||||
/* Unknown instruction -> ignore */
|
||||
DEBUGF("Unknown processing instruction: '%s'\n", name);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool starts_with(char *string, char *prefix)
|
||||
{
|
||||
unsigned int pfx_len = rb->strlen(prefix);
|
||||
if (rb->strlen(string) < pfx_len)
|
||||
return false;
|
||||
return (rb->strncmp(string, prefix, pfx_len) == 0);
|
||||
}
|
||||
|
||||
|
||||
bool parse_name_value(char *line, char *name, int namesize,
|
||||
char *value, int valuesize)
|
||||
{
|
||||
char *sep;
|
||||
int name_len, val_len;
|
||||
name[0] = value[0] = '\0';
|
||||
|
||||
sep = rb->strcasestr(line, NAME_VALUE_SEPARATOR);
|
||||
if (sep == NULL) {
|
||||
/* No separator char -> weird instruction */
|
||||
return false;
|
||||
}
|
||||
name_len = sep - line;
|
||||
if (name_len >= namesize) {
|
||||
/* Too long name */
|
||||
return false;
|
||||
}
|
||||
rb->strncpy(name, line, name_len);
|
||||
name[name_len] = '\0';
|
||||
|
||||
val_len = rb->strlen(line) - name_len - NAME_VALUE_SEPARATOR_LEN;
|
||||
if (val_len >= valuesize) {
|
||||
/* Too long value */
|
||||
return false;
|
||||
}
|
||||
rb->strncpy(value, sep+NAME_VALUE_SEPARATOR_LEN, val_len+1);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool dump_sc_file(sc_file_t *file, char *filename)
|
||||
{
|
||||
DEBUGF("Dumping shortcuts to the file '%s'\n", filename);
|
||||
int fd;
|
||||
|
||||
/* ideally, we should just write a new
|
||||
* entry to the file, but I'm going to
|
||||
* be lazy, and just re-write the whole
|
||||
* thing. */
|
||||
fd = rb->open(filename, O_WRONLY|O_TRUNC);
|
||||
if (fd < 0) {
|
||||
rb->splash(HZ*2, "Could not open shortcuts file %s for writing",
|
||||
filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write instructions
|
||||
*/
|
||||
/* Always dump the 'display last segms' settings (even it it's
|
||||
* not active) so that it can be easily changed without having
|
||||
* to remember the setting name */
|
||||
write_int_instruction_to_file(fd,
|
||||
INSTR_DISPLAY_LAST_SEGMENTS, file->show_last_segments);
|
||||
|
||||
int i;
|
||||
sc_entry_t *entry;
|
||||
for (i=0, entry=file->entries; i<file->entry_cnt; i++,entry++) {
|
||||
write_entry_to_file(fd, entry);
|
||||
}
|
||||
|
||||
rb->close(fd);
|
||||
DEBUGF("Dumped %d entries to the file '%s'\n", file->entry_cnt, filename);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void write_int_instruction_to_file(int fd, char *instr, int value)
|
||||
{
|
||||
rb->fdprintf(fd, "%s%s%s%d\n", CONTROL_PREFIX, instr,
|
||||
NAME_VALUE_SEPARATOR, value);
|
||||
}
|
||||
|
||||
|
||||
void write_entry_to_file(int fd, sc_entry_t *entry)
|
||||
{
|
||||
rb->fdprintf(fd, "%s", entry->path);
|
||||
if (entry->explicit_disp) {
|
||||
rb->fdprintf(fd, "%s%s", PATH_DISP_SEPARATOR, entry->disp);
|
||||
}
|
||||
rb->fdprintf(fd, "\n");
|
||||
}
|
||||
|
||||
|
||||
bool file_exists(char *filename)
|
||||
{
|
||||
int fd = rb->open(filename, O_RDONLY);
|
||||
bool retval;
|
||||
if (fd >= 0) {
|
||||
rb->close(fd);
|
||||
retval = true;
|
||||
} else {
|
||||
retval = false;
|
||||
}
|
||||
DEBUGF("Checked existence of the file '%s': %s\n",
|
||||
filename, (retval ? "found" : "NOT FOUND"));
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
bool dir_exists(char *path)
|
||||
{
|
||||
DIR* d = rb->opendir(path);
|
||||
bool retval;
|
||||
if (d != NULL) {
|
||||
rb->closedir(d);
|
||||
retval = true;
|
||||
} else {
|
||||
retval = false;
|
||||
}
|
||||
DEBUGF("Checked existence of the dir '%s': %s\n",
|
||||
path, (retval ? "found" : "NOT FOUND"));
|
||||
return retval;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue