rockbox/apps/plugins/shortcuts/shortcuts_common.c

362 lines
10 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2007 Bryan Childs
* Copyright (c) 2007 Alexander Levin
*
* 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 "shortcuts.h"
#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;
size_t 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, 0666);
if (fd < 0){
/* For some reason we couldn't create the file,
* so return an error message and exit */
rb->splashf(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->splashf(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->splashf(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->strlcpy(entry->path, path, path_len + 1);
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)
{
/* don't count one trailing separator */
char *p = path+rb->strlen(path)-PATH_SEPARATOR_LEN;
int seg_cnt = 0;
if(p <= path)
return path;
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->strlcpy(name, line, name_len + 1);
val_len = rb->strlen(line) - name_len - NAME_VALUE_SEPARATOR_LEN;
if (val_len >= valuesize) {
/* Too long value */
return false;
}
rb->strlcpy(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->splashf(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");
}