rockbox/apps/open_plugin.c
William Wilgus 0c29d1788e [Bugfix] open_plugin_browse() not showing plugins
rockbox_browse() overrides the desired dirfilter with
global_settings.dirfilter if you aren't using one of the
SHOW_ modes > NUM_FILTER_MODES (which come with their own sideeffects)

add flag BROWSE_DIRFILTER to
override global_settings.dirfilter with browse_context.dirfilter

add ability to set tc->browse to NULL to exit dirbrowse immediately

Change-Id: I2f40d394f9dc0864b2041293eda219f7436a7bf0
2023-03-18 04:23:43 -04:00

485 lines
16 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2020 by William Wilgus
*
* 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.
*
****************************************************************************/
#ifndef __PCTOOL__
#include "plugin.h"
#include "open_plugin.h"
#include "pathfuncs.h"
#include "splash.h"
#include "lang.h"
/* Define LOGF_ENABLE to enable logf output in this file */
/*#define LOGF_ENABLE*/
#include "logf.h"
#define ROCK_EXT "rock"
#define ROCK_LEN sizeof(ROCK_EXT)
#define OP_EXT "opx"
#define OP_LEN sizeof(OP_EXT)
static const uint32_t open_plugin_csum = OPEN_PLUGIN_CHECKSUM;
static const int op_entry_sz = sizeof(struct open_plugin_entry_t);
static const char* strip_rockbox_root(const char *path)
{
int dlen = ROCKBOX_DIR_LEN;
if (strncmp(path, ROCKBOX_DIR, dlen) == 0)
path+= dlen;
return path;
}
static inline void op_clear_entry(struct open_plugin_entry_t *entry)
{
if (entry == NULL)
return;
memset(entry, 0, op_entry_sz);
entry->lang_id = OPEN_PLUGIN_LANG_INVALID;
}
static int op_entry_checksum(struct open_plugin_entry_t *entry)
{
/*Note: since we use langids as checksums everytime someone moves the lang file
* around it could mess with our indexing so invalidate entries when this occurs
*/
if (entry == NULL || entry->checksum != open_plugin_csum +
(entry->lang_id <= OPEN_PLUGIN_LANG_INVALID ? 0 : LANG_LAST_INDEX_IN_ARRAY))
{
logf("OP entry bad checksum");
return 0;
}
return 1;
}
static int op_find_entry(int fd, struct open_plugin_entry_t *entry,
uint32_t hash, int32_t lang_id)
{
int ret = OPEN_PLUGIN_NOT_FOUND;
int record = 0;
if (hash == 0)
hash = OPEN_PLUGIN_SEED;
if (fd >= 0)
{
logf("OP find_entry *Searching* hash: %x lang_id: %d", hash, lang_id);
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 */
{
#if (CONFIG_STORAGE & STORAGE_ATA)
/* may have invalid entries but we append the file so continue looking*/
if (op_entry_checksum(entry) <= 0)
{
ret = OPEN_PLUGIN_INVALID_ENTRY;
continue;
}
#endif
ret = record;
/* NULL terminate fields NOTE -- all are actually +1 larger */
entry->name[OPEN_PLUGIN_NAMESZ] = '\0';
/*entry->key[OPEN_PLUGIN_BUFSZ] = '\0';*/
entry->path[OPEN_PLUGIN_BUFSZ] = '\0';
entry->param[OPEN_PLUGIN_BUFSZ] = '\0';
logf("OP find_entry *Found* hash: %x lang_id: %d",
entry->hash, entry->lang_id);
logf("OP find_entry rec: %d name: %s %s %s", record,
entry->name, entry->path, entry->param);
break;
}
record++;
}
}
/* sanity check */
#if (CONFIG_STORAGE & STORAGE_ATA)
if (ret == OPEN_PLUGIN_INVALID_ENTRY ||
#else
if(
#endif
(ret > OPEN_PLUGIN_NOT_FOUND && op_entry_checksum(entry) <= 0))
{
splashf(HZ * 2, "%s Invalid entry", str(LANG_OPEN_PLUGIN));
ret = OPEN_PLUGIN_NOT_FOUND;
}
if (ret == OPEN_PLUGIN_NOT_FOUND)
op_clear_entry(entry);
return ret;
}
static int op_update_dat(struct open_plugin_entry_t *entry, bool clear)
{
int fd;
uint32_t hash;
int32_t lang_id;
if (entry == NULL|| entry->hash == 0)
{
logf("OP update *No entry*");
return OPEN_PLUGIN_NOT_FOUND;
}
hash = entry->hash;
lang_id = entry->lang_id;
if (lang_id <= OPEN_PLUGIN_LANG_INVALID)
lang_id = OPEN_PLUGIN_LANG_IGNORE;
logf("OP update hash: %x lang_id: %d", hash, lang_id);
logf("OP update name: %s clear: %d", entry->name, (int) clear);
logf("OP update %s %s %s", entry->name, entry->path, entry->param);
#if (CONFIG_STORAGE & STORAGE_ATA) /* Harddrive -- update existing */
logf("OP update *Updating entries* %s", OPEN_PLUGIN_DAT);
fd = open(OPEN_PLUGIN_DAT, O_RDWR | O_CREAT, 0666);
if (fd < 0)
return OPEN_PLUGIN_NOT_FOUND;
/* Only read the hash lang id and checksum */
uint32_t hash_langid_csum[3] = {0};
const off_t hlc_sz = sizeof(hash_langid_csum);
uint32_t csum = open_plugin_csum +
(lang_id <= OPEN_PLUGIN_LANG_INVALID ? 0 : LANG_LAST_INDEX_IN_ARRAY);
while (read(fd, &hash_langid_csum, hlc_sz) == hlc_sz)
{
if ((hash_langid_csum[0] == hash || (int32_t)hash_langid_csum[1] == lang_id) &&
hash_langid_csum[2] == csum)
{
logf("OP update *Entry Exists* hash: %x langid: %d",
hash_langid_csum[0], (int32_t)hash_langid_csum[1]);
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 */
}
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");
fd = open(OPEN_PLUGIN_DAT ".tmp", O_RDWR | O_CREAT | O_TRUNC, 0666);
if (fd < 0)
return OPEN_PLUGIN_NOT_FOUND;
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)
{
if (entry->hash != hash && entry->lang_id != lang_id &&
op_entry_checksum(entry) > 0)
{
write(fd, entry, op_entry_sz);
}
}
close(fd1);
remove(OPEN_PLUGIN_DAT);
}
if (!clear) /* retrieve original entry */
{
logf("OP update *Loading original entry*");
lseek(fd, 0, SEEK_SET);
op_find_entry(fd, entry, hash, lang_id);
}
close(fd);
rename(OPEN_PLUGIN_DAT ".tmp", OPEN_PLUGIN_DAT);
#endif
if (clear)
{
logf("OP update *Clearing entry*");
op_clear_entry(entry);
}
return 0;
}
static int op_load_entry(uint32_t hash, int32_t lang_id,
struct open_plugin_entry_t *entry, const char *dat_file)
{
int opret = OPEN_PLUGIN_NOT_FOUND;
if (entry != NULL)
{
/* Is the entry we want already loaded? */
if(hash != 0 && entry->hash == hash)
return OPEN_PLUGIN_NEEDS_FLUSHED;
if(lang_id <= OPEN_PLUGIN_LANG_INVALID)
{
lang_id = OPEN_PLUGIN_LANG_IGNORE;
if (hash == 0)/* no hash or langid -- returns first entry found */
lang_id = OPEN_PLUGIN_LANG_IGNOREALL;
}
else if(entry->lang_id == lang_id)
{
return OPEN_PLUGIN_NEEDS_FLUSHED;
}
/* if another entry is loaded; flush it to disk before we destroy it */
op_update_dat(open_plugin_get_entry(), true);
logf("OP get_entry hash: %x lang id: %d db: %s", hash, lang_id, dat_file);
int fd = open(dat_file, O_RDONLY);
if(fd < 0)
return OPEN_PLUGIN_NOT_FOUND;
opret = op_find_entry(fd, entry, hash, lang_id);
close(fd);
}
return opret;
}
/******************************************************************************/
/******************************************************************************/
/* ************************************************************************** */
/* * PUBLIC INTERFACE FUNCTIONS * *********************************************/
/* ************************************************************************** */
/******************************************************************************/
/******************************************************************************/
/* open_plugin_get_entry()
* returns the internal open_plugin_entry
*/
struct open_plugin_entry_t * open_plugin_get_entry(void)
{
/* holds entry data to load/run/store */
static struct open_plugin_entry_t open_plugin_entry = {0};
return &open_plugin_entry;
}
/* open_plugin_add_path()
* adds a plugin path and calling parameters to open_plugin_entry
* hash of the key is created for later recall of the plugin path and parameters
* returns hash of the key or 0 on error
*/
uint32_t open_plugin_add_path(const char *key, const char *plugin, const char *parameter)
{
size_t len;
uint32_t hash;
int32_t lang_id;
char *pos = "\0";
struct open_plugin_entry_t *op_entry = open_plugin_get_entry();
if(key == NULL)
{
logf("OP add_path No Key, *Clearing entry*");
op_clear_entry(op_entry);
return 0;
}
lang_id = P2ID((unsigned char*)key);
const char *skey = P2STR((unsigned char *)key);
logf("OP add_path key: %s lang id: %d", skey, lang_id);
open_plugin_get_hash(strip_rockbox_root(skey), &hash);
if(op_entry->hash != hash)
{
logf("OP add_path *Flush entry*");
/* the entry in ram needs saved */
op_update_dat(op_entry, true);
}
if (plugin)
{
op_entry->hash = hash;
op_entry->lang_id = lang_id;
op_entry->checksum = open_plugin_csum +
(lang_id <= OPEN_PLUGIN_LANG_INVALID ? 0 : LANG_LAST_INDEX_IN_ARRAY);
/* name */
if (path_basename(plugin, (const char **)&pos) == 0)
pos = "\0";
len = strlcpy(op_entry->name, pos, OPEN_PLUGIN_NAMESZ);
if (len > ROCK_LEN && strcasecmp(&(pos[len-ROCK_LEN]), "." ROCK_EXT) == 0)
{
/* path */
strmemccpy(op_entry->path, plugin, OPEN_PLUGIN_BUFSZ);
if(!parameter)
parameter = "";
strmemccpy(op_entry->param, parameter, OPEN_PLUGIN_BUFSZ);
goto retnhash;
}
else if (len > OP_LEN && strcasecmp(&(pos[len-OP_LEN]), "." OP_EXT) == 0)
{
op_load_entry(0, OPEN_PLUGIN_LANG_IGNORE, op_entry, plugin);
goto retnhash;
}
}
logf("OP add_path Invalid, *Clearing entry*");
if (lang_id != LANG_SHORTCUTS) /* from shortcuts menu */
splashf(HZ * 2, str(LANG_OPEN_PLUGIN_NOT_A_PLUGIN), pos);
op_clear_entry(op_entry);
hash = 0;
retnhash:
logf("OP add_path name: %s %s %s",
op_entry->name, op_entry->path, op_entry->param);
return hash;
}
/* only displays directories and .rock files */
static bool callback_show_item(char *name, int attr, struct tree_context *tc)
{
(void)name;
if(attr & ATTR_DIRECTORY)
{
if (strstr(tc->currdir, PLUGIN_DIR) != NULL)
return true;
tc->browse = NULL; /* exit immediately */
}
else if(attr & FILE_ATTR_ROCK)
{
return true;
}
return false;
}
/* open_plugin_browse()
* allows the user to browse for a plugin to set to a supplied key
* if key is a lang_id that is used otherwise a hash of the key is created
* for later recall of the plugin path
*/
void open_plugin_browse(const char *key)
{
logf("%s", __func__);
char tmp_buf[OPEN_PLUGIN_BUFSZ+1];
open_plugin_load_entry(key);
struct open_plugin_entry_t *op_entry = open_plugin_get_entry();
logf("OP browse key: %s name: %s",
(key ? P2STR((unsigned char *)key):"No Key"), open_plugin_entry.name);
logf("OP browse %s %s", op_entry->path, op_entry->param);
if (op_entry->path[0] == '\0' || !file_exists(op_entry->path))
strcpy(op_entry->path, PLUGIN_DIR"/");
struct browse_context browse = {
.dirfilter = SHOW_ALL,
.flags = BROWSE_SELECTONLY | BROWSE_NO_CONTEXT_MENU | BROWSE_DIRFILTER,
.title = str(LANG_OPEN_PLUGIN),
.icon = Icon_Plugin,
.root = op_entry->path,
.buf = tmp_buf,
.bufsize = sizeof(tmp_buf),
.callback_show_item = callback_show_item,
};
if (rockbox_browse(&browse) == GO_TO_PREVIOUS)
open_plugin_add_path(key, tmp_buf, NULL);
}
/* open_plugin_load_entry()
* recall of the plugin path and parameters based on supplied key
* returns the index in OPEN_PLUGIN_DAT where the entry was found (>= 0)
* if the entry was found but has not been saved returns OPEN_PLUGIN_NEEDS_FLUSHED
* otherwise returns OPEN_PLUGIN_NOT_FOUND (< 0) if key was not found
*/
int open_plugin_load_entry(const char *key)
{
if (key == NULL)
key = ID2P(LANG_OPEN_PLUGIN_NOT_A_PLUGIN); /* won't be found */
struct open_plugin_entry_t *op_entry = open_plugin_get_entry();
int opret;
uint32_t hash = 0;
int32_t lang_id = P2ID((unsigned char *)key);
const char* skey = P2STR((unsigned char *)key); /* string|LANGPTR => string */
/*Note: P2ID() returns -1 if key isnt a valid lang_id */
if (lang_id <= OPEN_PLUGIN_LANG_INVALID)
open_plugin_get_hash(strip_rockbox_root(skey), &hash); /* in open_plugin.h */
opret = op_load_entry(hash, lang_id, op_entry, OPEN_PLUGIN_DAT);
logf("OP entry hash: %x lang id: %d ret: %d key: %s", hash, lang_id, opret, skey);
if (opret == OPEN_PLUGIN_NOT_FOUND && lang_id > OPEN_PLUGIN_LANG_INVALID)
{ /* try rb defaults */
opret = op_load_entry(hash, lang_id, op_entry, OPEN_RBPLUGIN_DAT);
logf("OP rb_entry hash: %x lang id: %d ret: %d key: %s", hash, lang_id, opret, skey);
/* add to the user plugin.dat file if found */
op_update_dat(op_entry, false);
}
logf("OP entry ret: %s", (opret == OPEN_PLUGIN_NOT_FOUND ? "Not Found":"Found"));
return opret;
}
/* open_plugin_run()
* recall of the plugin path and parameters based on supplied key
* runs the plugin using plugin_load see plugin_load for return values
*/
int open_plugin_run(const char *key)
{
int ret = 0;
int opret = open_plugin_load_entry(key);
struct open_plugin_entry_t *op_entry = open_plugin_get_entry();
if (opret == OPEN_PLUGIN_NEEDS_FLUSHED)
op_update_dat(op_entry, false);
const char *path = op_entry->path;
const char *param = op_entry->param;
logf("OP run key: %s ret: %d name: %s",
(key ? P2STR((unsigned char *)key):"No Key"), opret, op_entry->name);
logf("OP run: %s %s %s", op_entry->name, path, param);
if (param[0] == '\0')
param = NULL;
if (path[0] == '\0' && key)
path = P2STR((unsigned char *)key);
ret = plugin_load(path, param);
if (ret != GO_TO_PLUGIN)
op_clear_entry(op_entry);
return ret;
}
/* open_plugin_cache_flush()
* saves the current open_plugin_entry to disk
*/
void open_plugin_cache_flush(void)
{
logf("%s", __func__);
struct open_plugin_entry_t *op_entry = open_plugin_get_entry();
/* start_in_screen == 0 is 'Previous Screen' it is actually
* defined as (GO_TO_PREVIOUS = -2) + 2 for *Legacy?* reasons AFAICT */
if (global_settings.start_in_screen == 0 &&
global_status.last_screen == GO_TO_PLUGIN &&
op_entry->lang_id > OPEN_PLUGIN_LANG_INVALID)
{
/* flush the last item as LANG_PREVIOUS_SCREEN if the user wants to resume */
op_entry->lang_id = LANG_PREVIOUS_SCREEN;
}
op_update_dat(op_entry, true);
}
#endif /* ndef __PCTOOL__ */