From 6928581bf93210e71dd9a0d5053850851a5bec43 Mon Sep 17 00:00:00 2001 From: William Wilgus Date: Wed, 25 Mar 2026 10:56:14 -0400 Subject: [PATCH] [bugfix] open_plugin_import fails to import full path WIP settings_load_config() only reads up to 128 characters while openplugin entries could be upwards of 600 instead use the open_plugins plugin to restore entries add import from .cfg file add save to .cfg file better dupe checking Change-Id: Iec2506aad14a3eb89dcc558b0fbc1f014aad98b5 --- apps/open_plugin.c | 64 ++-- apps/open_plugin.h | 3 +- apps/plugin.c | 3 +- apps/plugins/open_plugins.c | 616 +++++++++++++++++++++++++++++++++--- apps/settings.c | 18 +- 5 files changed, 614 insertions(+), 90 deletions(-) diff --git a/apps/open_plugin.c b/apps/open_plugin.c index c892a94233..7950d96101 100644 --- a/apps/open_plugin.c +++ b/apps/open_plugin.c @@ -35,7 +35,7 @@ static const uint32_t open_plugin_csum = OPEN_PLUGIN_CHECKSUM; -static const int op_entry_sz = sizeof(struct open_plugin_entry_t); +#define OP_ENTRY_SZ (sizeof(struct open_plugin_entry_t)) static const char* strip_rockbox_root(const char *path) { @@ -49,7 +49,7 @@ static inline void op_clear_entry(struct open_plugin_entry_t *entry) { if (entry == NULL) return; - memset(entry, 0, op_entry_sz); + memset(entry, 0, OP_ENTRY_SZ); entry->lang_id = OPEN_PLUGIN_LANG_INVALID; } @@ -78,7 +78,7 @@ static int op_find_entry(int fd, struct open_plugin_entry_t *entry, { logf("OP find_entry *Searching* hash: %x lang_id: %d", hash, lang_id); - while (read(fd, entry, op_entry_sz) == op_entry_sz) + while (read(fd, entry, OP_ENTRY_SZ) == OP_ENTRY_SZ) { if (entry->lang_id == lang_id || entry->hash == hash || (lang_id == OPEN_PLUGIN_LANG_IGNOREALL))/* return first entry found */ @@ -167,9 +167,9 @@ static int op_update_dat(struct open_plugin_entry_t *entry, bool clear) lseek(fd, 0-hlc_sz, SEEK_CUR);/* back to the start of record */ break; } - lseek(fd, op_entry_sz - hlc_sz, SEEK_CUR); /* finish record */ + lseek(fd, OP_ENTRY_SZ - hlc_sz, SEEK_CUR); /* finish record */ } - write(fd, entry, op_entry_sz); + write(fd, entry, OP_ENTRY_SZ); close(fd); #else /* Everyone else make a temp file */ logf("OP update *Copying entries* %s", OPEN_PLUGIN_DAT ".tmp"); @@ -177,18 +177,18 @@ static int op_update_dat(struct open_plugin_entry_t *entry, bool clear) if (fd < 0) return OPEN_PLUGIN_NOT_FOUND; - write(fd, entry, op_entry_sz); + write(fd, entry, OP_ENTRY_SZ); int fd1 = open(OPEN_PLUGIN_DAT, O_RDONLY); if (fd1 >= 0) { /* copy non-duplicate entries back from original */ - while (read(fd1, entry, op_entry_sz) == op_entry_sz) + while (read(fd1, entry, OP_ENTRY_SZ) == OP_ENTRY_SZ) { if (entry->hash != hash && entry->lang_id != lang_id && op_entry_checksum(entry) > 0) { - write(fd, entry, op_entry_sz); + write(fd, entry, OP_ENTRY_SZ); } } close(fd1); @@ -529,41 +529,13 @@ static bool op_entry_read(int fd, int selected_item, off_t data_sz, struct open_ { op_clear_entry(op_entry); return ((selected_item >= 0) && (fd >= 0) && - (lseek(fd, selected_item * op_entry_sz, SEEK_SET) >= 0) && + (lseek(fd, selected_item * OP_ENTRY_SZ, SEEK_SET) >= 0) && (read(fd, op_entry, data_sz) == data_sz) && op_entry_checksum(op_entry) > 0); } -void open_plugin_import(char *strdat) +void open_plugin_import(const char *cfg_file) { - /* Note: Destroys strdat */ - char *vect[5]; - /* expected openplugin strdat: "englishid", "name", "path", "param" */ - - if (split_string(strdat, ',', vect, 5) == 4) - { - /* Space for 5 values so we will fail if excess arguments or too few */ - for(int i = 0; i < 4; i++) - { - vect[i] = skip_whitespace(vect[i]); - int eos = ((int)strlen(vect[i]))-2; - /* Failure if string is not quoted */ - if (vect[i][0] != '"' || eos < 0 || vect[i][eos + 1] != '"') - { - logf("%s[%d] error: quoted string expected\n", __func__, i); - return; - } - vect[i]++; /* skip first " */ - vect[i][eos] = '\0'; /* skip other " */ - } - char *key = vect[1]; - int lang_id = lang_english_to_id(vect[0]); - if (lang_id >= 0) - key = ID2P(lang_id); - /* if key exists it will be overwritten */ - open_plugin_add_path(key, vect[2], vect[3]); - return; - } - logf("%s error: importing entries", __func__); + plugin_load(VIEWERS_DIR"/open_plugins.rock", cfg_file); } void open_plugin_export(int cfg_fd) @@ -584,7 +556,7 @@ void open_plugin_export(int cfg_fd) logf("%s error: opening %s", __func__, OPEN_PLUGIN_DAT); return; /* OPEN_PLUGIN_NOT_FOUND */ } - while (op_entry_read(fd, index++, op_entry_sz, op_entry)) + while (op_entry_read(fd, index++, OP_ENTRY_SZ, op_entry)) { /* don't save the LANG_OPEN_PLUGIN entry -- it is for internal use */ if (op_entry->lang_id == LANG_OPEN_PLUGIN) @@ -596,8 +568,16 @@ void open_plugin_export(int cfg_fd) else lang_name = "[USER]"; /* needs to be an invalid lang string */ - fdprintf(cfg_fd,"%s: \"%s\", \"%s\", \"%s\", \"%s\"\n", - "openplugin", lang_name, op_entry->name, op_entry->path, op_entry->param); + bool dblquote = strchr(op_entry->name, '"') != NULL || + strchr(op_entry->param, '"') != NULL; + const char* fmtstr = "%s: \"%s\", \"%s\", \"%s\", \"%s\"\n"; + + if (dblquote) /* if using double quotes export with single quotes */ + { + fmtstr = "%s: '%s', '%s', '%s', '%s'\n"; + } + fdprintf(cfg_fd, fmtstr, OPEN_PLUGIN_CFGNAME, + lang_name, op_entry->name, op_entry->path, op_entry->param); logf("openplugin[%d]: \"%s\", \"%s\", \"%s\", \"%s\"\n lang id: %d hash: %x, csum: %x\n", index, lang_name, op_entry->name, op_entry->path, op_entry->param, diff --git a/apps/open_plugin.h b/apps/open_plugin.h index 7038f419f3..3e00827baf 100644 --- a/apps/open_plugin.h +++ b/apps/open_plugin.h @@ -35,6 +35,7 @@ #define OPEN_RBPLUGIN_DAT PLUGIN_DIR "/rb_plugins.dat" #define OPEN_PLUGIN_BUFSZ MAX_PATH #define OPEN_PLUGIN_NAMESZ 32 +#define OPEN_PLUGIN_CFGNAME "openplugin" enum { OPEN_PLUGIN_LANG_INVALID = (-1), @@ -84,7 +85,7 @@ int open_plugin_load_entry(const char *key); void open_plugin_browse(const char *key); int open_plugin_run(const char *key); void open_plugin_cache_flush(void); /* flush to disk */ -void open_plugin_import(char *strdat); +void open_plugin_import(const char *cfg_file); void open_plugin_export(int cfg_fd); #endif diff --git a/apps/plugin.c b/apps/plugin.c index c2ab8ad322..d787149de2 100644 --- a/apps/plugin.c +++ b/apps/plugin.c @@ -892,7 +892,8 @@ int plugin_load(const char* plugin, const void* parameter) !strcmp("playing_time.rock", sepch + 1) || !strcmp("main_menu_config.rock", sepch + 1) || !strcmp("text_viewer.rock", sepch + 1) || - !strcmp("disktidy.rock", sepch + 1)); + !strcmp("disktidy.rock", sepch + 1) || + !strcmp("open_plugins.rock", sepch + 1)); if (current_plugin_handle) { diff --git a/apps/plugins/open_plugins.c b/apps/plugins/open_plugins.c index 9da5c1f3a2..a5f8b07dcd 100644 --- a/apps/plugins/open_plugins.c +++ b/apps/plugins/open_plugins.c @@ -31,6 +31,14 @@ * with the parameters previously supplied */ +#if defined(DEBUG) || defined(SIMULATOR) + #define logf(...) rb->debugf(__VA_ARGS__); rb->debugf("\n") +#elif defined(ROCKBOX_HAS_LOGF) + #define logf rb->logf +#else + #define logf(...) do { } while(0) +#endif + #include "plugin.h" #include "lang_enum.h" #include "../open_plugin.h" @@ -46,16 +54,77 @@ #define MENU_ID_MAIN "0" #define MENU_ID_EDIT "1" -static int fd_dat; +#define MAX_ENTRIES 128 + +static int fd_dat = -1; static struct gui_synclist lists; -struct open_plugin_entry_t op_entry; +static struct open_plugin_entry_t op_entry; static const uint32_t open_plugin_csum = OPEN_PLUGIN_CHECKSUM; static const off_t op_entry_sz = sizeof(struct open_plugin_entry_t); /* we only need the names for the first menu so don't bother reading paths yet */ const off_t op_name_sz = OPEN_PLUGIN_NAMESZ + (op_entry.name - (char*)&op_entry); +/*static char op_names[MAX_ENTRIES][OPEN_PLUGIN_NAMESZ + 1] = {0};*/ + +/* Forward declarations*/ static uint32_t op_entry_add_path(const char *key, const char *plugin, const char *parameter, bool use_key); +static int import_from_config(const char* cfgfile); +static int export_to_config(const char* cfgfile); + +static bool hash_exists(uint32_t hash, bool remove) +{ + /* keeps track of hash entries + * supply hash = 0, remove = true to erase all entries + * supply hash = 0, remove = false; returns true if empty entries are available + * supply a hash, remove = false; returns false if the hash does not exist & adds hash to the db + * supply a hash, remove = true; returns true and removes the hash from the db + * returns false if hash is unique + */ + static uint32_t seen_hashes[MAX_ENTRIES] = {0}; + int i; + if (hash == 0) + { + if (remove) + { + logf("removing all hashes"); + for(i = 0; i < MAX_ENTRIES; i++) + seen_hashes[i] = 0; + } + return seen_hashes[MAX_ENTRIES - 1] == 0; /* true if empty entries exist */ + } + for(i = 0; i < MAX_ENTRIES; i++) + { + uint32_t current_hash = seen_hashes[i]; + if (current_hash == 0 && !remove) + { + logf("(%d) added new hash: %x", i, hash); + seen_hashes[i] = hash; + break; + } + if (current_hash == hash) + { + if (remove) + { + logf("(%d) remove hash: %x", i, hash); + for (int j = i + 1; j < MAX_ENTRIES; j++, i++) + { + /* move everything above down 1*/ + uint32_t next_hash = seen_hashes[j]; + seen_hashes[i] = next_hash; + if (next_hash == 0) + break; + } + if (i + 1 == MAX_ENTRIES) /* handle last entry */ + { + seen_hashes[i] = 0; + } + } + return true; + } + } + return false; /* hash is unique */ +} static bool _yesno_pop(const char* text) { @@ -87,6 +156,7 @@ static size_t pathbasename(const char *name, const char **nameptr) *nameptr = q; return r - q; } + static int op_entry_checksum(void) { if (op_entry.checksum != open_plugin_csum + @@ -134,7 +204,36 @@ static int op_entry_read_opx(const char *path) return ret; } -static void op_entry_export(int selection) +static void op_entry_config_export(void) +{ + int i = 0; + int len; + char buf[MAX_PATH + 1]; + + do + { + i++; + rb->snprintf(buf, sizeof(buf), "%s/%s%d%s", ROCKBOX_DIR, "open_plugins", i, ".cfg"); + } while (i < 100 && rb->file_exists(buf)); + + if( rb->kbd_input(buf, MAX_PATH, NULL ) == 0 ) + { + len = rb->strlen(buf); + if(len > 4 && buf[len] != PATH_SEPCH && + rb->strcasecmp(&((buf)[len-4]), ".cfg") != 0) + { + rb->strcat(buf, ".cfg"); + } + + if (export_to_config(buf) > 0) + return; + } + + rb->splashf( 2*HZ, "Save Failed (%s)", buf ); + +} + +static void op_entry_export_opx(int selection) { int len; int fd = -1; @@ -257,19 +356,43 @@ static void op_entry_set_param(void) rb->strlcpy(op_entry.param, tmp_buf, OPEN_PLUGIN_BUFSZ); } +static int op_et_exclude_invalid(struct open_plugin_entry_t *op_entry, int item, void *data) +{ + (void)item; + (void)data; + if (op_entry->hash == 0 || op_entry->name[0] == '\0') + { + logf("Exclude entry %d - Bad hash", item); + return 0; + } + if (op_entry->checksum != open_plugin_csum + + (op_entry->lang_id <= OPEN_PLUGIN_LANG_INVALID ? 0 : LANG_LAST_INDEX_IN_ARRAY)) + { + logf("Exclude entry %d - Bad csum", item); + return 0; + } + if (!rb->file_exists(op_entry->path)) + { + logf("Exclude entry %d - Bad path '%s'", item, op_entry->path); + return 0; + } + logf("%s Include entry %d %s - hash %x", __func__, item, + (op_entry->lang_id >= 0 ? "[Builtin]" : "[User]"), op_entry->hash); + return 1; +} + static int op_et_exclude_hash(struct open_plugin_entry_t *op_entry, int item, void *data) { (void)item; - if (op_entry->hash == 0 || op_entry->name[0] == '\0') - return 0; - if (data) { uint32_t *hash = data; + if (op_entry->hash != *hash) - return 1; + return op_et_exclude_invalid(op_entry, item, data); } + logf("Exclude entry %d - hash %x", item, op_entry->hash); return 0; } @@ -279,11 +402,12 @@ static int op_et_exclude_builtin(struct open_plugin_entry_t *op_entry, int item, (void)data; if (op_entry->lang_id >= 0) + { + logf("Exclude entry %d - Builtin", item); return 0; - else if(op_entry->hash == 0 || op_entry->name[0] == '\0') - return 0; + } - return 1; + return op_et_exclude_invalid(op_entry, item, data); } static int op_et_exclude_user(struct open_plugin_entry_t *op_entry, int item, void *data) @@ -292,11 +416,11 @@ static int op_et_exclude_user(struct open_plugin_entry_t *op_entry, int item, vo (void)data; if (op_entry->lang_id < 0) + { + logf("Exclude entry %d - User", item); return 0; - else if (op_entry->hash == 0 || op_entry->name[0] == '\0') - return 0; - - return 1; + } + return op_et_exclude_invalid(op_entry, item, data); } static int op_entry_transfer(int fd, int fd_tmp, @@ -304,21 +428,82 @@ static int op_entry_transfer(int fd, int fd_tmp, void *data) { int entries = -1; + int skipped = 0; if (fd_tmp >= 0 && fd >= 0 && rb->lseek(fd, 0, SEEK_SET) == 0) { entries = 0; while (rb->read(fd, &op_entry, op_entry_sz) == op_entry_sz) { - if (compfn && compfn(&op_entry, entries, data) > 0 && op_entry_checksum() > 0) + if (compfn && compfn(&op_entry, entries + skipped, data) > 0 && op_entry_checksum() > 0) { rb->write(fd_tmp, &op_entry, op_entry_sz); entries++; } + else + skipped++; } + logf("%s %d entries %d skipped", __func__, entries, skipped); + } + else + { + logf("%s Error: fd %d, fd_tmp %d, seekfd: %d", __func__, + fd, fd_tmp, (int) rb->lseek(fd, 0, SEEK_SET)); } return entries + 1; } +#if 0 +static int op_entry_exists(int fd) +{ + logf("%s? %s - %s", __func__, op_entry.name, op_entry.path); + /* returns index of the entry that already exists if not found returns -1 */ + static struct open_plugin_entry_t op_entry_rd; + int entries = 0; + if (fd >= 0 && rb->lseek(fd, 0, SEEK_SET) == 0) + { + while (rb->read(fd, &op_entry_rd, op_entry_sz) == op_entry_sz) + { + logf("rd: %s - %s", op_entry_rd.name, op_entry_rd.path); + if (rb->memcmp(&op_entry_rd, &op_entry, op_entry_sz) == 0) + { + logf("%s entry exists @ %d", op_entry_rd.name, entries); + return entries; + } + entries++; + } + } + logf("%s is unique in %d entries", op_entry.name, entries); + return -1; +} +#endif + +static bool browse_configs(void) +{ + int entries = 0; + char tmp_buf[MAX_PATH+1]; + + struct browse_context browse = { + .dirfilter = SHOW_CFG, + .flags = BROWSE_SELECTONLY | BROWSE_DIRFILTER, + .title = rb->str(LANG_CUSTOM_CFG), + .icon = Icon_Plugin, + .root = ROCKBOX_DIR, + .buf = tmp_buf, + .bufsize = sizeof(tmp_buf), + }; + + if (rb->rockbox_browse(&browse) == GO_TO_PREVIOUS) + { + logf("import from %s\n", tmp_buf); + if(rb->filetype_get_attr(tmp_buf) == FILE_ATTR_CFG) + { + entries = import_from_config(tmp_buf); + rb->splashf(HZ *2, "Imported %d entries", entries); + } + } + return (entries > 0); +} + static uint32_t op_entry_add_path(const char *key, const char *plugin, const char *parameter, bool use_key) { char buf[MAX_PATH]; @@ -358,8 +543,6 @@ static uint32_t op_entry_add_path(const char *key, const char *plugin, const cha if (op_entry.name[0] == '\0' || op_entry.lang_id >= 0) rb->strlcpy(op_entry.name, pos, OPEN_PLUGIN_NAMESZ); - - if ((!parameter || parameter[0] == '\0') && fattr != FILE_ATTR_ROCK && fattr != FILE_ATTR_OPX) { rb->strlcpy(op_entry.param, plugin, OPEN_PLUGIN_BUFSZ); @@ -396,16 +579,28 @@ static uint32_t op_entry_add_path(const char *key, const char *plugin, const cha } else if (parameter != op_entry.param) rb->strlcpy(op_entry.param, parameter, OPEN_PLUGIN_BUFSZ); + } - /* hash on the parameter path if it is a file */ - if (op_entry.lang_id <0 && (key == op_entry.path || key == NULL) && - rb->file_exists(op_entry.param)) + op_entry_set_checksum(); + + if (!use_key) + { + if (op_entry.lang_id <0) { - open_plugin_get_hash(op_entry.param, &newhash); - op_entry.hash = newhash; + open_plugin_get_hash(op_entry.name, &hash); + op_entry.hash = hash; + } + if (hash_exists(op_entry.hash, false) && + _yesno_pop(ID2P(LANG_REALLY_OVERWRITE)) == false) + { + logf("%s error: duplicate key exists: '%s' lang id: %d hash: %x", + __func__, key, op_entry.lang_id, op_entry.hash); + rb->close(fd_tmp); + rb->remove(OPEN_PLUGIN_DAT ".tmp"); + return op_entry.hash; } } - op_entry_set_checksum(); + rb->write(fd_tmp, &op_entry, op_entry_sz); /* add new entry first */ } else if(op_entry_read_opx(plugin) == op_entry_sz) @@ -414,13 +609,23 @@ static uint32_t op_entry_add_path(const char *key, const char *plugin, const cha if (fd_tmp < 0) return 0; - if (op_entry.lang_id <0 && rb->file_exists(op_entry.param)) - open_plugin_get_hash(op_entry.param, &hash); - else - open_plugin_get_hash(op_entry.path, &hash); + if (op_entry.lang_id <0) + { + open_plugin_get_hash(op_entry.name, &hash); + } op_entry.hash = hash; op_entry_set_checksum(); + + if (hash_exists(op_entry.hash, false)) + { + logf("%s error: duplicate key exists: '%s' lang id: %d hash: %x", + __func__, key, op_entry.lang_id, op_entry.hash); + rb->close(fd_tmp); + rb->remove(OPEN_PLUGIN_DAT ".tmp"); + return op_entry.hash; + } + rb->write(fd_tmp, &op_entry, op_entry_sz); /* add new entry first */ } else @@ -431,6 +636,7 @@ static uint32_t op_entry_add_path(const char *key, const char *plugin, const cha } } + /*logf("OP add key: '%s' lang id: %d hash: %x", op_entry.name, op_entry.lang_id, op_entry.hash);*/ if (op_entry_transfer(fd_dat, fd_tmp, op_et_exclude_hash, &hash) > 0) { rb->close(fd_tmp); @@ -451,6 +657,7 @@ static uint32_t op_entry_add_path(const char *key, const char *plugin, const cha void op_entry_browse_add(int selection) { + logf("%s", __func__); char* key; op_entry_read(fd_dat, selection, op_entry_sz); if (op_entry_set_path() > 0) @@ -466,7 +673,6 @@ void op_entry_browse_add(int selection) static void op_entry_remove(int selection) { - int entries = rb->lseek(fd_dat, 0, SEEK_END) / op_entry_sz; int32_t hash = 0; int lang_id = -1; @@ -486,6 +692,7 @@ static void op_entry_remove(int selection) rb->global_settings->start_in_screen = GO_TO_ROOT + 2; /* default */ } } + hash_exists(op_entry.hash, true); rb->memset(&op_entry, 0, op_entry_sz); op_entry.lang_id = lang_id; op_entry.hash = hash; @@ -685,9 +892,9 @@ static int context_menu_cb(int action, /*Run, Edit, Remove, Export, Blank, Import, Add, Back*/ switch(selection) { - case 0:case 1:case 2:case 3:case 5: + case 0:case 1:case 2:case 3:case 4:case 5:case 7: return ACTION_STD_OK; - case 4: /*blank*/ + case 6: /*blank*/ break; default: return ACTION_STD_CANCEL; @@ -709,6 +916,9 @@ static void edit_menu(int selection) if (!op_entry_read(fd_dat, selection, op_entry_sz)) return; + if (op_entry.hash != 0) /* remove old hash */ + hash_exists(op_entry.hash, true); + uint32_t crc = rb->crc_32(&op_entry, op_entry_sz, 0xffffffff); synclist_set(MENU_ID_EDIT, 2, 8, 2); @@ -762,6 +972,8 @@ static void edit_menu(int selection) op_entry_add_path(NULL, op_entry.path, param, false); fd_dat = rb->open(OPEN_PLUGIN_DAT, O_RDWR, 0666); } + else + hash_exists(op_entry.hash, false); } static int context_menu(int selection) @@ -771,9 +983,11 @@ static int context_menu(int selection) { MENUITEM_STRINGLIST(menu, op_entry.name, context_menu_cb, ID2P(LANG_RUN), ID2P(LANG_EDIT), ID2P(LANG_REMOVE), ID2P(LANG_EXPORT), - ID2P(VOICE_BLANK), ID2P(LANG_ADD), ID2P(LANG_BACK)); + ID2P(LANG_SAVE_SETTINGS), ID2P(LANG_CUSTOM_CFG), ID2P(VOICE_BLANK), + ID2P(LANG_ADD), ID2P(LANG_BACK)); selected_item = rb->do_menu(&menu, 0, NULL, false); + logf("%s %d", __func__, selected_item); switch (selected_item) { case 0: /*run*/ @@ -785,11 +999,20 @@ static int context_menu(int selection) op_entry_remove(selection); break; case 3: /*export*/ - op_entry_export(selection); + op_entry_export_opx(selection); break; - case 4: /*blank*/ + case 4: /* export to cfg */ + op_entry_config_export(); + case 5: /*import from cfg*/ + if (browse_configs()) + { + rb->plugin_open(rb->plugin_get_current_filename(), "\0"); + return OP_PLUGIN_RESTART; + } break; - case 5: /*add*/ + case 6: /*blank*/ + break; + case 7: /*add*/ op_entry_browse_add(-1); rb->plugin_open(rb->plugin_get_current_filename(), "\0"); return OP_PLUGIN_RESTART; @@ -802,6 +1025,296 @@ static int context_menu(int selection) return PLUGIN_ERROR; } +/* Read up to buffer_size chars from a quoted string + * within fd into buffer and return number of bytes read. + * A string starts with a quote character (single or double quote) + * and ends with a matching closing quote. Neither opening or closing quotes + * are stored in buffer. Too small buf or no opening and closing quote is an error. + * If an error occurs, -1 is returned (and buffer is cleared). + * If buffer too small file will still be advanced to the closing quote/LF/EOF + */ +static int read_quoted_string(int fd, char* buffer, int buffer_size) +{ + int pos = 0; + char ch; + char quote = '\0'; + /*logf("%s fd: %d bufsz: %d", __func__, fd, buffer_size);*/ + while (rb->read(fd, &ch, 1) == 1) + { + if (ch == '\n') /*LF marks end of line*/ + { + rb->lseek(fd, -1, SEEK_CUR);/*back up cursor to LF so calling fn sees it*/ + break; /* fail */ + } + if (quote == '\0' && + (ch == '\'' || ch == '"')) /*handle single or double quotes*/ + { + quote = ch; + } + else if (quote != '\0') + { + if (ch == quote) + { + if (pos < buffer_size) + { + buffer[pos] = '\0'; /*end quote*/ + return pos + 1; + } + break; /*fail*/ + } + if (pos < buffer_size) + { + buffer[pos] = ch; /*inside quote*/ + pos++; + } + } + } + + /*fail*/ + /*logf("Error %s", __func__);*/ + buffer[0] = '\0'; + return -1; +} + +static int lang_english_to_id(const char *english) +{ + int i; + unsigned char *ptr; + size_t ptrlen, len = rb->strlen(english); + for (i = 0; i < LANG_LAST_INDEX_IN_ARRAY; i++) { + ptr = rb->language_strings[i]; + ptrlen = rb->strlen((char *)ptr); + if ((ptrlen == len) && rb->memcmp(ptr, english, ptrlen) == 0) + return i; + } + return -1; +} + +static bool op_entry_config_import(int cfg_fd, int fd_tmp) +{ + /* NOTE: assumes cfg_fd is valid */ + /*"key", "name", "path", "param"*/ + /*"Start Screen", "logo.rock", "/.rockbox/rocks/demos/logo.rock", ""*/ + /*"[USER]", "text_viewer.rock", "/.rockbox/rocks/viewers/text_viewer.rock", "/text.txt"*/ + rb->memset(&op_entry, 0, op_entry_sz); + + static char errmsg[MAX_PATH]; + static char keybuf[MAX_PATH]; + int32_t lang_id; + uint32_t hash; + + if (read_quoted_string(cfg_fd, keybuf, sizeof(keybuf)) < 0) + { + logf("%s error: importing key entry @ %d", __func__, 0); + rb->snprintf(errmsg, sizeof(errmsg), "importing key entry @ %d", 1); + rb->splashf(HZ*2, ID2P(LANG_ERROR_FORMATSTR), errmsg); + return false; /* fail */ + } + + lang_id = lang_english_to_id(keybuf); + if (lang_id < 0) + { + int rd = read_quoted_string(cfg_fd, keybuf, sizeof(keybuf)); /* grab name field */ + if(rd < 0) + { + logf("%s error: importing key entry @ %d", __func__, 1); + rb->snprintf(errmsg, sizeof(errmsg), "importing key entry @ %d", 1); + rb->splashf(HZ*2, ID2P(LANG_ERROR_FORMATSTR), errmsg); + return false; /* fail */ + } + rb->lseek(cfg_fd, -(rd+2), SEEK_CUR); /* restore position to read name again */ + } + + int i, bufsz; + char *field[3] = {op_entry.name, op_entry.path, op_entry.param}; + for (i = 0, bufsz = OPEN_PLUGIN_NAMESZ; i < (int)ARRAYLEN(field); i++) + { + if (read_quoted_string(cfg_fd, field[i], bufsz) < 0) + { + logf("%s error: importing entry @ %d", __func__, i); + logf("OP import key: '%s' name: '%s' '%s' '%s'", keybuf, + op_entry.name, op_entry.path, op_entry.param); + rb->memset(&op_entry, 0, op_entry_sz); + rb->snprintf(errmsg, sizeof(errmsg), "importing entry %s @ %d", keybuf, i); + rb->splashf(HZ*2, ID2P(LANG_ERROR_FORMATSTR), errmsg); + return false; /* fail */ + } + bufsz = OPEN_PLUGIN_BUFSZ; + } + + if (!rb->file_exists(op_entry.path)) + { + logf("%s error: '%s' '%s' does not exist", __func__, keybuf, op_entry.path); + rb->splashf(HZ*2, ID2P(LANG_PLUGIN_CANT_OPEN), op_entry.path); + return false; /* fail */ + } + + if (lang_id <0) + { + open_plugin_get_hash(op_entry.name, &hash); + + } + else + { + open_plugin_get_hash(keybuf, &hash); + } + + op_entry.hash = hash; + op_entry.lang_id = lang_id; + + /*logf("OP import key: '%s' name: '%s' '%s' '%s'", keybuf, + op_entry.name, op_entry.path, op_entry.param);*/ + + op_entry_set_checksum(); + + if (fd_tmp >= 0 && fd_dat >= 0) + { + if (hash_exists(op_entry.hash, false)) + { + logf("%s error: duplicate key exists: '%s' lang id: %d hash: %x", + __func__, keybuf, lang_id, hash); + return false; + } + + logf("writing to tmp: %s - %s", op_entry.name, op_entry.path); + + if (rb->write(fd_tmp, &op_entry, op_entry_sz) != op_entry_sz)/* add new entry */ + return false; + } + else + { + logf("%s error: bad fd dat: %d tmp: %d", __func__, fd_dat, fd_tmp); + return false; + } + + return true; +} + +static int import_from_config(const char* cfgfile) +{ + logf("%s() %s\r\n", __func__, cfgfile); + int fd; + char ch; + int entries = 0; + static char line[sizeof("openplugin:")]; + + fd = rb->open_utf8(cfgfile, O_RDONLY); + if (fd < 0) + return -1; + + int fd_tmp = rb->open(OPEN_PLUGIN_DAT ".tmp", O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd_tmp < 0) + { + logf("%s error: can not open '%s'", __func__, OPEN_PLUGIN_DAT ".tmp"); + return -1; + } + + while ((rb->read(fd, line, sizeof line) - 1) == sizeof(line) - 1) + { + if (rb->strncasecmp(line, "openplugin:", sizeof("openplugin:") -1) == 0) + { + if (op_entry_config_import(fd, fd_tmp)) + entries++; + } + + while (rb->read(fd, &ch, 1) == 1) /* continue reading till EOL */ + { + if (ch == '\n') + break; + } + } /* while(...) */ + + rb->close(fd); +#if 1 + if (entries > 0 && op_entry_transfer(fd_dat, fd_tmp, &op_et_exclude_user, NULL) > 0 && + op_entry_transfer(fd_dat, fd_tmp, &op_et_exclude_builtin, NULL) > 0) +#else + if (entries > 0 && op_entry_transfer(fd_dat, fd_tmp, op_et_exclude_invalid, 0) >= 0) +#endif + { + logf("%s imported %d entries", __func__, entries); + rb->close(fd_tmp); + rb->close(fd_dat); + fd_dat = -1; + rb->remove(OPEN_PLUGIN_DAT); + rb->rename(OPEN_PLUGIN_DAT ".tmp", OPEN_PLUGIN_DAT); + } + else + { + logf("%s error: can not transfer entries", __func__); + if (entries > 0) + { + logf("%s error: can not transfer entries to '%s'", __func__, OPEN_PLUGIN_DAT ".tmp"); + entries = -1; + } + rb->close(fd_tmp); + rb->remove(OPEN_PLUGIN_DAT ".tmp"); + } + + return entries; +} + +static int export_to_config(const char* cfgfile) +{ + logf("%s() %s\r\n", __func__, cfgfile); + int cfg_fd; + char *lang_name; + int fd; + int index = 0; + + cfg_fd = rb->open_utf8(cfgfile, O_WRONLY | O_CREAT | O_TRUNC); + if (cfg_fd < 0) + return index; + + fd = rb->open_utf8(OPEN_PLUGIN_DAT, O_RDONLY); + + if (fd < 0) + { + logf("%s error: opening %s", __func__, OPEN_PLUGIN_DAT); + rb->close(cfg_fd); + rb->remove(cfgfile); + return index; /* OPEN_PLUGIN_NOT_FOUND */ + } + while (op_entry_read(fd, index, op_entry_sz)) + { + index++; + /* don't save the LANG_OPEN_PLUGIN entry -- it is for internal use */ + if (op_entry.lang_id == LANG_OPEN_PLUGIN) + { + continue; + } + if (op_entry.lang_id >=0) + lang_name = rb->str(op_entry.lang_id); + else + lang_name = "[USER]"; /* needs to be an invalid lang string */ + bool dblquote = rb->strchr(op_entry.name, '"') != NULL || + rb->strchr(op_entry.param, '"') != NULL; + + const char* fmtstr = "%s: \"%s\", \"%s\", \"%s\", \"%s\"\n"; + + if (dblquote) /* if using double quotes export with single quotes */ + { + fmtstr = "%s: '%s', '%s', '%s', '%s'\n"; + } + rb->fdprintf(cfg_fd,fmtstr, OPEN_PLUGIN_CFGNAME, + lang_name, op_entry.name, op_entry.path, op_entry.param); + + logf("openplugin[%d]: \"%s\", \"%s\", \"%s\", \"%s\"\n lang id: %d hash: %x, csum: %x\n", + index, lang_name, op_entry.name, op_entry.path, op_entry.param, + op_entry.lang_id, op_entry.hash, op_entry.checksum); + } + rb->close(fd); + rb->close(cfg_fd); + logf("%s exported %d entries", __func__, index); + if (index == 0) + { + /* Nothing to export */ + rb->remove(cfgfile); + } + + return index; +} + enum plugin_status plugin_start(const void* parameter) { int ret = PLUGIN_OK; @@ -817,11 +1330,20 @@ enum plugin_status plugin_start(const void* parameter) const int creat_flags = O_RDWR | O_CREAT; reopen_datfile: + fd_dat = rb->open(OPEN_PLUGIN_DAT, creat_flags, 0666); + + hash_exists(0, true); + while (rb->read(fd_dat, &op_entry, op_entry_sz) == op_entry_sz) + { + hash_exists(op_entry.hash, false); + } + if (!fd_dat) exit = true; items = rb->lseek(fd_dat, 0, SEEK_END) / op_entry_sz; + if (parameter) { path = (char*)parameter; @@ -833,13 +1355,18 @@ reopen_datfile: parameter = NULL; op_entry_browse_add(-1); rb->close(fd_dat); + fd_dat = -1; goto reopen_datfile; } - } + if(rb->filetype_get_attr(path) == FILE_ATTR_CFG) + { + int ret = import_from_config(path); + rb->close(fd_dat); + if (ret >= 0) + return PLUGIN_OK; + return PLUGIN_ERROR; + } - if (parameter) - { - path = (char*)parameter; res = op_entry_read_opx(path); if (res >= 0) { @@ -910,6 +1437,7 @@ reopen_datfile: if (op_entry_add_path(rb->str(LANG_ADD), cur_filename, "-add", true)) { rb->close(fd_dat); + fd_dat = -1; parameter = NULL; goto reopen_datfile; } @@ -917,8 +1445,6 @@ reopen_datfile: return PLUGIN_ERROR; } - - if (!exit) { synclist_set(MENU_ID_MAIN, selection, items, 1); @@ -947,7 +1473,7 @@ reopen_datfile: rb->gui_synclist_draw(&lists); break; } - /* Inentional fallthrough */ + /* fallthrough */ case ACTION_STD_OK: if (op_entry_read(fd_dat, selection, op_entry_sz)) { @@ -962,6 +1488,14 @@ reopen_datfile: exit = true; break; } + default: + if (rb->default_event_handler(action) == SYS_USB_CONNECTED) + { + op_entry_remove_empty(); + rb->close(fd_dat); + return PLUGIN_USB_CONNECTED; + } + break; } } op_entry_remove_empty(); diff --git a/apps/settings.c b/apps/settings.c index 7ed4257084..a87844ac24 100644 --- a/apps/settings.c +++ b/apps/settings.c @@ -405,7 +405,7 @@ bool settings_load_config(const char* file, bool apply) int fd; char line[128]; bool theme_changed = false; - + bool import_open_plugins = false; fd = open_utf8(file, O_RDONLY); if (fd < 0) return false; @@ -418,17 +418,25 @@ bool settings_load_config(const char* file, bool apply) if (!string_to_cfg(name, value, &theme_changed)) { -#ifndef __PCTOOL__ /* if we are here then name was not a valid setting */ - if (!strcmp(name, "openplugin")) + if (strcmp(name, OPEN_PLUGIN_CFGNAME) == 0) { - open_plugin_import(value); + import_open_plugins = true; + char ch; + while (read(fd, &ch, 1) == 1 && ch != '\n'){}; } -#endif } } /* while(...) */ close(fd); + +#ifndef __PCTOOL__ + if (import_open_plugins) + open_plugin_import(file); +#else + (void) import_open_plugins; +#endif + if (apply) { settings_save();