| /*************************************************************************** |
| * __________ __ ___. |
| * 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" |
| 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"); |
| } |