100e9ac137
plugins need rb-> for things like strlen Change-Id: Ia3b52423d3f7f91c2996aa9b9b194f39af44468f
1033 lines
31 KiB
C
1033 lines
31 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// __ \_/ ___\| |/ /| __ \ / __ \ \/ /
|
|
* Jukebox | | ( (__) ) \___| ( | \_\ ( (__) ) (
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2023 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.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#include "plugin.h"
|
|
#include "lang_enum.h"
|
|
|
|
#include "lib/printcell_helper.h"
|
|
|
|
#include "lib/configfile.h"
|
|
#define CFG_FILE "/lastfm_scrobbler_viewer.cfg"
|
|
#define TMP_FILE ""PLUGIN_DATA_DIR "/lscrobbler_viewer_%d.tmp"
|
|
#define CFG_VER 1
|
|
|
|
#ifdef ROCKBOX_HAS_LOGF
|
|
#define logf rb->logf
|
|
#else
|
|
#define logf(...) do { } while(0)
|
|
#endif
|
|
|
|
/*#ARTIST #ALBUM #TITLE #TRACKNUM #LENGTH #RATING #TIMESTAMP #MUSICBRAINZ_TRACKID*/
|
|
|
|
#define SCROBBLE_HDR_FMT "$*%d$%s$*%d$%s$*%d$%s$Track#$Length$Rating$TimeStamp$TrackId"
|
|
//#define SCROBBLE_HDR "$*128$Artist$*128$Album$*128$Title$Track#$Length$Rating$TimeStamp$TrackId"
|
|
|
|
#define SCROBBLER_MIN_COLUMNS (6) /* a valid scrobbler file should have at least this many columns */
|
|
|
|
#define GOTO_ACTION_DEFAULT_HANDLER (PLUGIN_OK + 1)
|
|
#define CANCEL_CONTEXT_MENU (PLUGIN_OK + 2)
|
|
#define QUIT_CONTEXT_MENU (PLUGIN_OK + 3)
|
|
/* global lists, for everything */
|
|
static struct gui_synclist lists;
|
|
|
|
/* printcell data for the current file */
|
|
struct printcell_data_t {
|
|
int view_columns;
|
|
int view_lastcol;
|
|
|
|
int items_buffered;
|
|
int items_total;
|
|
int fd_cur;
|
|
char *filename;
|
|
|
|
char *buf;
|
|
size_t buf_size;
|
|
off_t buf_used;
|
|
char header[PRINTCELL_MAXLINELEN];
|
|
|
|
};
|
|
|
|
enum e_find_type {
|
|
FIND_ALL = 0,
|
|
FIND_EXCLUDE,
|
|
FIND_EXCLUDE_CASE,
|
|
FIND_EXCLUDE_ANY,
|
|
FIND_INCLUDE,
|
|
FIND_INCLUDE_CASE,
|
|
FIND_INCLUDE_ANY,
|
|
FIND_CUSTOM,
|
|
};
|
|
|
|
static void synclist_set(int selected_item, int items, int sel_size, struct printcell_data_t *pc_data);
|
|
static void pc_data_set_header(struct printcell_data_t *pc_data);
|
|
|
|
static void browse_file(char *buf, size_t bufsz)
|
|
{
|
|
struct browse_context browse = {
|
|
.dirfilter = SHOW_ALL,
|
|
.flags = BROWSE_SELECTONLY,
|
|
.title = "Select a scrobbler log file",
|
|
.icon = Icon_Playlist,
|
|
.buf = buf,
|
|
.bufsize = bufsz,
|
|
.root = "/",
|
|
};
|
|
|
|
if (rb->rockbox_browse(&browse) != GO_TO_PREVIOUS)
|
|
{
|
|
buf[0] = '\0';
|
|
}
|
|
}
|
|
|
|
static struct plugin_config
|
|
{
|
|
bool separator;
|
|
bool talk;
|
|
int col_width;
|
|
int hidecol_flags;
|
|
} gConfig;
|
|
|
|
static struct configdata config[] =
|
|
{
|
|
{TYPE_BOOL, 0, 1, { .bool_p = &gConfig.separator }, "Cell Separator", NULL},
|
|
{TYPE_BOOL, 0, 1, { .bool_p = &gConfig.talk }, "Voice", NULL},
|
|
{TYPE_INT, 32, LCD_WIDTH, { .int_p = &gConfig.col_width }, "Cell Width", NULL},
|
|
{TYPE_INT, INT_MIN, INT_MAX, { .int_p = &gConfig.hidecol_flags }, "Hidden Columns", NULL},
|
|
};
|
|
const int gCfg_sz = sizeof(config)/sizeof(*config);
|
|
/****************** config functions *****************/
|
|
static void config_set_defaults(void)
|
|
{
|
|
gConfig.col_width = MIN(LCD_WIDTH, 128);
|
|
gConfig.hidecol_flags = 0;
|
|
gConfig.separator = true;
|
|
gConfig.talk = rb->global_settings->talk_menu;
|
|
}
|
|
|
|
static void config_save(void)
|
|
{
|
|
configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER);
|
|
}
|
|
|
|
static int config_settings_menu(struct printcell_data_t *pc_data)
|
|
{
|
|
int selection = 0;
|
|
|
|
struct viewport parentvp[NB_SCREENS];
|
|
FOR_NB_SCREENS(l)
|
|
{
|
|
rb->viewport_set_defaults(&parentvp[l], l);
|
|
rb->viewport_set_fullscreen(&parentvp[l], l);
|
|
}
|
|
|
|
MENUITEM_STRINGLIST(settings_menu, ID2P(LANG_SETTINGS), NULL,
|
|
ID2P(LANG_LIST_SEPARATOR),
|
|
"Cell Width",
|
|
ID2P(LANG_VOICE),
|
|
ID2P(VOICE_BLANK),
|
|
ID2P(VOICE_BLANK),
|
|
ID2P(LANG_CANCEL_0),
|
|
ID2P(LANG_SAVE_EXIT));
|
|
|
|
do {
|
|
selection=rb->do_menu(&settings_menu,&selection, parentvp, true);
|
|
switch(selection) {
|
|
|
|
case 0:
|
|
rb->set_bool(rb->str(LANG_LIST_SEPARATOR), &gConfig.separator);
|
|
break;
|
|
case 1:
|
|
rb->set_int("Cell Width", "", UNIT_INT,
|
|
&gConfig.col_width, NULL, 1, 32, LCD_WIDTH, NULL );
|
|
break;
|
|
case 2:
|
|
rb->set_bool(rb->str(LANG_VOICE), &gConfig.talk);
|
|
break;
|
|
case 3:
|
|
continue;
|
|
case 4: /*sep*/
|
|
continue;
|
|
case 5:
|
|
return -1;
|
|
break;
|
|
case 6:
|
|
{
|
|
pc_data_set_header(pc_data);
|
|
synclist_set(0, pc_data->items_total, 1, pc_data);
|
|
int res = configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER);
|
|
if (res >= 0)
|
|
{
|
|
logf("Cfg saved %s %d bytes", CFG_FILE, gCfg_sz);
|
|
return PLUGIN_OK;
|
|
}
|
|
logf("Cfg FAILED (%d) %s", res, CFG_FILE);
|
|
return PLUGIN_ERROR;
|
|
}
|
|
case MENU_ATTACHED_USB:
|
|
return PLUGIN_USB_CONNECTED;
|
|
default:
|
|
return PLUGIN_OK;
|
|
}
|
|
} while ( selection >= 0 );
|
|
return 0;
|
|
}
|
|
|
|
/* open file pass back file descriptor and return file size */
|
|
static size_t file_open(const char *filename, int *fd)
|
|
{
|
|
size_t fsize = 0;
|
|
|
|
if (filename && fd)
|
|
{
|
|
*fd = rb->open(filename, O_RDONLY);
|
|
if (*fd >= 0)
|
|
{
|
|
fsize = rb->filesize(*fd);
|
|
}
|
|
}
|
|
return fsize;
|
|
}
|
|
|
|
static const char* list_get_name_cb(int selected_item, void* data,
|
|
char* buf, size_t buf_len)
|
|
{
|
|
const int slush_pos = 32; /* entries around the current entry to keep buffered */
|
|
/* keep track of the last position in the buffer */
|
|
static int buf_line_num = 0;
|
|
static off_t buf_last_pos = 0;
|
|
/* keep track of the last position in the file */
|
|
static int file_line_num = 0;
|
|
static off_t file_last_seek = 0;
|
|
|
|
if (selected_item < 0)
|
|
{
|
|
logf("[%s] Reset positions", __func__);
|
|
buf_line_num = 0;
|
|
buf_last_pos = 0;
|
|
file_line_num = 0;
|
|
file_last_seek = 0;
|
|
return "";
|
|
}
|
|
|
|
int line_num;
|
|
struct printcell_data_t *pc_data = (struct printcell_data_t*) data;
|
|
bool found = false;
|
|
|
|
if (pc_data->buf && selected_item < pc_data->items_buffered)
|
|
{
|
|
int buf_pos;
|
|
|
|
if (buf_line_num > selected_item || buf_last_pos >= pc_data->buf_used
|
|
|| buf_line_num < 0)
|
|
{
|
|
logf("[%s] Set pos buffer 0", __func__);
|
|
buf_line_num = 0;
|
|
buf_last_pos = 0;
|
|
}
|
|
buf_pos = buf_last_pos;
|
|
line_num = buf_line_num;
|
|
|
|
while (buf_pos < pc_data->buf_used
|
|
&& line_num < pc_data->items_buffered)
|
|
{
|
|
size_t len = rb->strlen(&pc_data->buf[buf_pos]);
|
|
if(line_num == selected_item)
|
|
{
|
|
rb->strlcpy(buf, &pc_data->buf[buf_pos], MIN(buf_len, len));
|
|
logf("(%d) in buffer: %s", line_num, buf);
|
|
found = true;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
buf_pos += len + 1; /* need to go past the NULL terminator */
|
|
line_num++;
|
|
|
|
if (buf_line_num + slush_pos < selected_item)
|
|
{
|
|
logf("[%s] Set pos buffer %d", __func__, line_num);
|
|
buf_line_num = line_num;
|
|
buf_last_pos = buf_pos;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* didn't find the item try the file */
|
|
if(!found && pc_data->fd_cur >= 0)
|
|
{
|
|
int fd = pc_data->fd_cur;
|
|
|
|
if (file_line_num < 0 || file_line_num > selected_item)
|
|
{
|
|
logf("[%s] Set seek file 0", __func__);
|
|
file_line_num = 0;
|
|
file_last_seek = 0;
|
|
}
|
|
|
|
rb->lseek(fd, file_last_seek, SEEK_SET);
|
|
line_num = file_line_num;
|
|
|
|
while ((rb->read_line(fd, buf, buf_len)) > 0)
|
|
{
|
|
if(buf[0] == '#')
|
|
continue;
|
|
if(line_num == selected_item)
|
|
{
|
|
logf("(%d) in file: %s", line_num, buf);
|
|
found = true;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
line_num++;
|
|
|
|
if (file_line_num + slush_pos < selected_item)
|
|
{
|
|
logf("[%s] Set seek file %d", __func__, line_num);
|
|
file_line_num = line_num;
|
|
file_last_seek = rb->lseek(fd, 0, SEEK_CUR);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!found)
|
|
{
|
|
logf("(%d) Not Found!", selected_item);
|
|
buf_line_num = -1;
|
|
file_line_num = -1;
|
|
buf[0] = '\0';
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
static int list_voice_cb(int list_index, void* data)
|
|
{
|
|
(void) list_index;
|
|
struct printcell_data_t *pc_data = (struct printcell_data_t*) data;
|
|
if (!gConfig.talk)
|
|
return -1;
|
|
|
|
int selcol = printcell_get_column_selected();
|
|
char buf[MAX_PATH];
|
|
char* name;
|
|
long id;
|
|
|
|
if (pc_data->view_lastcol != selcol)
|
|
{
|
|
name = printcell_get_title_text(selcol, buf, sizeof(buf));
|
|
|
|
id = P2ID((const unsigned char *)name);
|
|
|
|
if(id>=0)
|
|
rb->talk_id(id, true);
|
|
else if (selcol >= 0)
|
|
{
|
|
switch (selcol)
|
|
{
|
|
case 0:
|
|
rb->talk_id(LANG_ID3_ARTIST, true);
|
|
break;
|
|
case 1:
|
|
rb->talk_id(LANG_ID3_ALBUM, true);
|
|
break;
|
|
case 2:
|
|
rb->talk_id(LANG_ID3_TITLE, true);
|
|
break;
|
|
case 3:
|
|
rb->talk_id(LANG_ID3_TRACKNUM, true);
|
|
break;
|
|
case 4:
|
|
rb->talk_id(LANG_ID3_LENGTH, true);
|
|
break;
|
|
|
|
default:
|
|
rb->talk_spell(name, true);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
rb->talk_id(LANG_ALL, true);
|
|
|
|
rb->talk_id(VOICE_PAUSE, true);
|
|
}
|
|
|
|
name = printcell_get_column_text(selcol, buf, sizeof(buf));
|
|
|
|
id = P2ID((const unsigned char *)name);
|
|
|
|
if(id>=0)
|
|
rb->talk_id(id, true);
|
|
else if (selcol >= 0)
|
|
rb->talk_spell(name, true);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static enum themable_icons list_icon_cb(int selected_item, void *data)
|
|
{
|
|
struct printcell_data_t *pc_data = (struct printcell_data_t*) data;
|
|
if (lists.selected_item == selected_item)
|
|
{
|
|
if (pc_data->view_lastcol < 0)
|
|
return Icon_Config;
|
|
return Icon_Audio;
|
|
}
|
|
return Icon_NOICON;
|
|
}
|
|
|
|
/* load file entries into pc_data buffer, file should already be opened
|
|
* and will be closed if the whole file was buffered */
|
|
static int file_load_entries(struct printcell_data_t *pc_data)
|
|
{
|
|
int items = 0;
|
|
int count = 0;
|
|
int buffered = 0;
|
|
unsigned int pos = 0;
|
|
bool comment = false;
|
|
char ch;
|
|
int fd = pc_data->fd_cur;
|
|
if (fd < 0)
|
|
return 0;
|
|
|
|
rb->lseek(fd, 0, SEEK_SET);
|
|
|
|
while (rb->read(fd, &ch, 1) > 0)
|
|
{
|
|
if (count++ == 0 && ch == '#') /* skip comments */
|
|
comment = true;
|
|
else if (!comment && ch != '\r' && pc_data->buf_size > pos)
|
|
pc_data->buf[pos++] = ch;
|
|
|
|
if (items == 0 && pos > PRINTCELL_MAXLINELEN * 2)
|
|
break;
|
|
|
|
if (ch == '\n')
|
|
{
|
|
if (!comment)
|
|
{
|
|
pc_data->buf[pos] = '\0';
|
|
if (pc_data->buf_size > pos)
|
|
{
|
|
pos++;
|
|
buffered++;
|
|
}
|
|
items++;
|
|
}
|
|
comment = false;
|
|
count = 0;
|
|
rb->yield();
|
|
}
|
|
}
|
|
|
|
logf("[%s] items: %d buffered: %d", __func__, items, buffered);
|
|
|
|
pc_data->items_total = items;
|
|
pc_data->items_buffered = buffered;
|
|
pc_data->buf[pos] = '\0';
|
|
pc_data->buf_used = pos;
|
|
|
|
if (items == buffered) /* whole file fit into buffer; close file */
|
|
{
|
|
rb->close(pc_data->fd_cur);
|
|
pc_data->fd_cur = -1;
|
|
}
|
|
|
|
list_get_name_cb(-1, NULL, NULL, 0); /* prime name cb */
|
|
return items;
|
|
}
|
|
|
|
static int filter_items(struct printcell_data_t *pc_data,
|
|
enum e_find_type find_type, int col)
|
|
{
|
|
/* saves filtered items to a temp file and loads it */
|
|
int fd;
|
|
bool reload = false;
|
|
char buf[PRINTCELL_MAXLINELEN];
|
|
char find_exclude_buf[PRINTCELL_MAXLINELEN];
|
|
int selcol = printcell_get_column_selected();
|
|
char *find_exclude = printcell_get_column_text(selcol, find_exclude_buf,
|
|
sizeof(find_exclude_buf));
|
|
const char colsep = '\t';
|
|
int find_len = rb->strlen(find_exclude);
|
|
|
|
if (find_type == FIND_CUSTOM || find_len == 0)
|
|
{
|
|
int option = 0;
|
|
struct opt_items find_types[] = {
|
|
{"Exclude", -1}, {"Exclude Case Sensitive", -1},
|
|
{"Exclude Any", -1}, {"Include", -1},
|
|
{"Include Case Sensitive", -1}, {"Include Any", -1}
|
|
};
|
|
if (rb->set_option("Find Type", &option, INT,
|
|
find_types, 6, NULL))
|
|
{
|
|
return 0;
|
|
}
|
|
switch (option)
|
|
{
|
|
case 0:
|
|
find_type = FIND_EXCLUDE;
|
|
break;
|
|
case 1:
|
|
find_type = FIND_EXCLUDE_CASE;
|
|
break;
|
|
case 2:
|
|
find_type = FIND_EXCLUDE_ANY;
|
|
break;
|
|
case 3:
|
|
find_type = FIND_INCLUDE;
|
|
break;
|
|
case 4:
|
|
find_type = FIND_INCLUDE_CASE;
|
|
break;
|
|
case 5:
|
|
find_type = FIND_INCLUDE_ANY;
|
|
break;
|
|
default:
|
|
find_type = FIND_ALL;
|
|
break;
|
|
}
|
|
|
|
/* copy data to beginning of buf */
|
|
rb->memmove(find_exclude_buf, find_exclude, find_len);
|
|
find_exclude_buf[find_len] = '\0';
|
|
|
|
if (rb->kbd_input(find_exclude_buf, sizeof(find_exclude_buf), NULL) < 0)
|
|
return -1;
|
|
find_exclude = find_exclude_buf;
|
|
find_len = rb->strlen(find_exclude);
|
|
}
|
|
|
|
char tmp_filename[MAX_PATH];
|
|
static int tmp_num = 0;
|
|
rb->snprintf(tmp_filename, sizeof(tmp_filename), TMP_FILE, tmp_num);
|
|
tmp_num++;
|
|
|
|
fd = rb->open(tmp_filename, O_RDWR | O_CREAT | O_TRUNC, 0666);
|
|
if(fd >= 0)
|
|
{
|
|
rb->splash_progress_set_delay(HZ * 5);
|
|
for (int i = 0; i < pc_data->items_total; i++)
|
|
{
|
|
rb->splash_progress(i, pc_data->items_total, "Filtering...");
|
|
const char * data = list_get_name_cb(i, pc_data, buf, sizeof(buf));
|
|
|
|
if (find_type != FIND_ALL)
|
|
{
|
|
int index = col;
|
|
|
|
if (index < 0)
|
|
index = 0;
|
|
if (find_len > 0)
|
|
{
|
|
char *bcol = buf;
|
|
while (*bcol != '\0' && index > 0)
|
|
{
|
|
if (*bcol == colsep)
|
|
index--;
|
|
bcol++;
|
|
}
|
|
if (index > 0)
|
|
continue;
|
|
|
|
if (find_type == FIND_EXCLUDE)
|
|
{
|
|
if (rb->strncasecmp(bcol, find_exclude, find_len) == 0)
|
|
{
|
|
logf("[%s] exclude [%s]", find_exclude, bcol);
|
|
continue;
|
|
}
|
|
}
|
|
else if (find_type == FIND_INCLUDE)
|
|
{
|
|
if (rb->strncasecmp(bcol, find_exclude, find_len) != 0)
|
|
{
|
|
logf("%s include %s", find_exclude, bcol);
|
|
continue;
|
|
}
|
|
}
|
|
else if (find_type == FIND_EXCLUDE_CASE)
|
|
{
|
|
if (rb->strncmp(bcol, find_exclude, find_len) == 0)
|
|
{
|
|
logf("[%s] exclude case [%s]", find_exclude, bcol);
|
|
continue;
|
|
}
|
|
}
|
|
else if (find_type == FIND_INCLUDE_CASE)
|
|
{
|
|
if (rb->strncmp(bcol, find_exclude, find_len) != 0)
|
|
{
|
|
logf("%s include case %s", find_exclude, bcol);
|
|
continue;
|
|
}
|
|
}
|
|
else if (find_type == FIND_EXCLUDE_ANY)
|
|
{
|
|
bool found = false;
|
|
while (*bcol != '\0' && *bcol != colsep)
|
|
{
|
|
if (rb->strncasecmp(bcol, find_exclude, find_len) == 0)
|
|
{
|
|
logf("[%s] exclude any [%s]", find_exclude, bcol);
|
|
found = true;
|
|
break;
|
|
}
|
|
bcol++;
|
|
}
|
|
if (found)
|
|
continue;
|
|
}
|
|
else if (find_type == FIND_INCLUDE_ANY)
|
|
{
|
|
bool found = false;
|
|
while (*bcol != '\0' && *bcol != colsep)
|
|
{
|
|
if (rb->strncasecmp(bcol, find_exclude, find_len) == 0)
|
|
{
|
|
found = true;
|
|
logf("[%s] include any [%s]", find_exclude, bcol);
|
|
break;
|
|
}
|
|
bcol++;
|
|
}
|
|
if (!found)
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
int len = rb->strlen(data);
|
|
if (len > 0)
|
|
{
|
|
logf("writing [%d bytes][%s]", len + 1, data);
|
|
rb->write(fd, data, len);
|
|
rb->write(fd, "\n", 1);
|
|
}
|
|
}
|
|
reload = true;
|
|
}
|
|
|
|
if (reload)
|
|
{
|
|
if (pc_data->fd_cur >= 0)
|
|
rb->close(pc_data->fd_cur);
|
|
|
|
pc_data->fd_cur = fd;
|
|
int items = file_load_entries(pc_data);
|
|
if (items >= 0)
|
|
{
|
|
lists.selected_item = 0;
|
|
rb->gui_synclist_set_nb_items(&lists, items);
|
|
}
|
|
}
|
|
logf("Col text '%s'", find_exclude);
|
|
logf("text '%s'", find_exclude_buf);
|
|
|
|
return pc_data->items_total;
|
|
}
|
|
|
|
static int scrobbler_context_menu(struct printcell_data_t *pc_data)
|
|
{
|
|
struct viewport parentvp[NB_SCREENS];
|
|
FOR_NB_SCREENS(l)
|
|
{
|
|
rb->viewport_set_defaults(&parentvp[l], l);
|
|
rb->viewport_set_fullscreen(&parentvp[l], l);
|
|
}
|
|
|
|
int col = printcell_get_column_selected();
|
|
int selection = 0;
|
|
int items = 0;
|
|
uint32_t visible = printcell_get_column_visibility(-1);
|
|
bool hide_col = PRINTCELL_COLUMN_IS_VISIBLE(visible, col);
|
|
|
|
char namebuf[PRINTCELL_MAXLINELEN];
|
|
enum e_find_type find_type = FIND_ALL;
|
|
|
|
char *colname = pc_data->filename;
|
|
if (col >= 0)
|
|
colname = printcell_get_title_text(col, namebuf, sizeof(namebuf));
|
|
|
|
#define MENUITEM_STRINGLIST_CUSTOM(name, str, callback, ... ) \
|
|
const char *name##_[] = {__VA_ARGS__}; \
|
|
const struct menu_callback_with_desc name##__ = \
|
|
{callback,str, Icon_NOICON}; \
|
|
const struct menu_item_ex name = \
|
|
{MT_RETURN_ID|MENU_HAS_DESC| \
|
|
MENU_ITEM_COUNT(sizeof( name##_)/sizeof(*name##_)), \
|
|
{ .strings = name##_},{.callback_and_desc = & name##__}};
|
|
|
|
const char *menu_item[4];
|
|
|
|
menu_item[0]= hide_col ? "Hide":"Show";
|
|
menu_item[1]= "Exclude";
|
|
menu_item[2]= "Include";
|
|
menu_item[3]= "Custom Filter";
|
|
|
|
if (col == -1)
|
|
{
|
|
menu_item[0]= hide_col ? "Hide All":"Show All";
|
|
menu_item[1]= "Open";
|
|
menu_item[2]= "Reload";
|
|
menu_item[3]= ID2P(LANG_SETTINGS);
|
|
}
|
|
|
|
MENUITEM_STRINGLIST_CUSTOM(context_menu, colname, NULL,
|
|
menu_item[0],
|
|
menu_item[1],
|
|
menu_item[2],
|
|
menu_item[3],
|
|
ID2P(VOICE_BLANK),
|
|
ID2P(LANG_CANCEL_0),
|
|
ID2P(LANG_MENU_QUIT));
|
|
|
|
#undef MENUITEM_STRINGLIST_CUSTOM
|
|
do {
|
|
selection=rb->do_menu(&context_menu,&selection, parentvp, true);
|
|
switch(selection) {
|
|
|
|
case 0:
|
|
{
|
|
printcell_set_column_visible(col, !hide_col);
|
|
if (hide_col)
|
|
{
|
|
do
|
|
{
|
|
col = printcell_increment_column(1, true);
|
|
} while (col >= 0 && printcell_get_column_visibility(col) == 1);
|
|
pc_data->view_lastcol = col;
|
|
}
|
|
gConfig.hidecol_flags = printcell_get_column_visibility(-1);
|
|
break;
|
|
}
|
|
case 1: /* Exclude / Open */
|
|
{
|
|
if (col == -1)/*Open*/
|
|
{
|
|
char buf[MAX_PATH];
|
|
browse_file(buf, sizeof(buf));
|
|
if (rb->file_exists(buf))
|
|
{
|
|
rb->strlcpy(pc_data->filename, buf, MAX_PATH);
|
|
if (pc_data->fd_cur >= 0)
|
|
rb->close(pc_data->fd_cur);
|
|
if (file_open(pc_data->filename, &pc_data->fd_cur) > 0)
|
|
items = file_load_entries(pc_data);
|
|
if (items >= 0)
|
|
synclist_set(0, items, 1, pc_data);
|
|
}
|
|
else
|
|
rb->splash(HZ *2, "Error Opening");
|
|
|
|
return CANCEL_CONTEXT_MENU;
|
|
}
|
|
|
|
find_type = FIND_EXCLUDE;
|
|
}
|
|
/* fall-through */
|
|
case 2: /* Include / Reload */
|
|
{
|
|
if (col == -1) /*Reload*/
|
|
{
|
|
if (pc_data->fd_cur >= 0)
|
|
rb->close(pc_data->fd_cur);
|
|
if (file_open(pc_data->filename, &pc_data->fd_cur) > 0)
|
|
items = file_load_entries(pc_data);
|
|
if (items >= 0)
|
|
rb->gui_synclist_set_nb_items(&lists, items);
|
|
return CANCEL_CONTEXT_MENU;
|
|
}
|
|
|
|
if (find_type == FIND_ALL)
|
|
find_type = FIND_INCLUDE;
|
|
/* fall-through */
|
|
}
|
|
case 3: /*Custom Filter / Settings */
|
|
{
|
|
if (col == -1)/*Settings*/
|
|
return config_settings_menu(pc_data);
|
|
|
|
if (find_type == FIND_ALL)
|
|
find_type = FIND_CUSTOM;
|
|
items = filter_items(pc_data, find_type, col);
|
|
|
|
if (items >= 0)
|
|
rb->gui_synclist_set_nb_items(&lists, items);
|
|
break;
|
|
}
|
|
case 4: /*sep*/
|
|
continue;
|
|
case 5:
|
|
return CANCEL_CONTEXT_MENU;
|
|
break;
|
|
case 6: /* Quit */
|
|
return QUIT_CONTEXT_MENU;
|
|
break;
|
|
case MENU_ATTACHED_USB:
|
|
return PLUGIN_USB_CONNECTED;
|
|
default:
|
|
return PLUGIN_OK;
|
|
}
|
|
} while ( selection < 0 );
|
|
return 0;
|
|
}
|
|
|
|
static void cleanup(void *parameter)
|
|
{
|
|
struct printcell_data_t *pc_data = (struct printcell_data_t*) parameter;
|
|
if (pc_data->fd_cur >= 0)
|
|
rb->close(pc_data->fd_cur);
|
|
pc_data->fd_cur = -1;
|
|
}
|
|
|
|
static void menu_action_printcell(int *action, int selected_item, bool* exit, struct gui_synclist *lists)
|
|
{
|
|
(void) exit;
|
|
struct printcell_data_t *pc_data = (struct printcell_data_t*) lists->data;
|
|
if (*action == ACTION_STD_OK)
|
|
{
|
|
if (selected_item < lists->nb_items)
|
|
{
|
|
pc_data->view_lastcol = printcell_increment_column(1, true);
|
|
*action = ACTION_NONE;
|
|
}
|
|
}
|
|
else if (*action == ACTION_STD_CANCEL)
|
|
{
|
|
pc_data->view_lastcol = printcell_increment_column(-1, true);
|
|
if (pc_data->view_lastcol != pc_data->view_columns - 1)
|
|
{
|
|
*action = ACTION_NONE;
|
|
}
|
|
}
|
|
else if (*action == ACTION_STD_CONTEXT)
|
|
{
|
|
int ctxret = scrobbler_context_menu(pc_data);
|
|
if (ctxret == QUIT_CONTEXT_MENU)
|
|
*exit = true;
|
|
}
|
|
}
|
|
|
|
int menu_action_cb(int *action, int selected_item, bool* exit, struct gui_synclist *lists)
|
|
{
|
|
|
|
menu_action_printcell(action, selected_item, exit, lists);
|
|
|
|
if (rb->default_event_handler_ex(*action, cleanup, lists->data) == SYS_USB_CONNECTED)
|
|
{
|
|
*exit = true;
|
|
return PLUGIN_USB_CONNECTED;
|
|
}
|
|
return PLUGIN_OK;
|
|
}
|
|
|
|
static int count_max_columns(int items, char delimeter,
|
|
int expected_cols, struct printcell_data_t *pc_data)
|
|
{
|
|
int max_cols = 0;
|
|
int cols = 0;
|
|
char buf[PRINTCELL_MAXLINELEN];
|
|
for (int i = 0; i < items; i++)
|
|
{
|
|
const char *txt = list_get_name_cb(i, pc_data, buf, sizeof(buf));
|
|
while (*txt != '\0')
|
|
{
|
|
if (*txt == delimeter)
|
|
{
|
|
cols++;
|
|
if (cols == expected_cols)
|
|
{
|
|
max_cols = cols;
|
|
break;
|
|
}
|
|
}
|
|
txt++;
|
|
}
|
|
|
|
if(max_cols < expected_cols && i > 32)
|
|
break;
|
|
|
|
if (cols > max_cols)
|
|
max_cols = cols;
|
|
cols = 0;
|
|
}
|
|
return max_cols;
|
|
}
|
|
|
|
static void synclist_set(int selected_item, int items, int sel_size, struct printcell_data_t *pc_data)
|
|
{
|
|
if (items <= 0)
|
|
return;
|
|
if (selected_item < 0)
|
|
selected_item = 0;
|
|
|
|
rb->gui_synclist_init(&lists,list_get_name_cb,
|
|
pc_data, false, sel_size, NULL);
|
|
|
|
rb->gui_synclist_set_icon_callback(&lists, list_icon_cb);
|
|
rb->gui_synclist_set_voice_callback(&lists, list_voice_cb);
|
|
rb->gui_synclist_set_nb_items(&lists,items);
|
|
rb->gui_synclist_select_item(&lists, selected_item);
|
|
|
|
struct printcell_settings pcs = {.cell_separator = gConfig.separator,
|
|
.title_delimeter = '$',
|
|
.text_delimeter = '\t',
|
|
.hidecol_flags = gConfig.hidecol_flags};
|
|
|
|
pc_data->view_columns = printcell_set_columns(&lists, &pcs,
|
|
pc_data->header, Icon_Rockbox);
|
|
printcell_enable(true);
|
|
|
|
|
|
int max_cols = count_max_columns(items, pcs.text_delimeter,
|
|
SCROBBLER_MIN_COLUMNS, pc_data);
|
|
if (max_cols < SCROBBLER_MIN_COLUMNS) /* not a scrobbler file? */
|
|
{
|
|
rb->gui_synclist_set_voice_callback(&lists, NULL);
|
|
pc_data->view_columns = printcell_set_columns(&lists, NULL,
|
|
"$*512$", Icon_Questionmark);
|
|
}
|
|
|
|
int curcol = printcell_get_column_selected();
|
|
while (curcol >= 0)
|
|
curcol = printcell_increment_column(-1, false);
|
|
|
|
if (pc_data->view_lastcol >= pc_data->view_columns)
|
|
pc_data->view_lastcol = -1;
|
|
|
|
/* restore column position */
|
|
while (pc_data->view_lastcol > -1 && curcol != pc_data->view_lastcol)
|
|
{
|
|
curcol = printcell_increment_column(1, true);
|
|
}
|
|
pc_data->view_lastcol = curcol;
|
|
list_voice_cb(0, pc_data);
|
|
}
|
|
|
|
static void pc_data_set_header(struct printcell_data_t *pc_data)
|
|
{
|
|
int col_w = gConfig.col_width;
|
|
rb->snprintf(pc_data->header, PRINTCELL_MAXLINELEN,
|
|
SCROBBLE_HDR_FMT, col_w, rb->str(LANG_ID3_ARTIST),
|
|
col_w, rb->str(LANG_ID3_ALBUM),
|
|
col_w, rb->str(LANG_ID3_TITLE));
|
|
}
|
|
|
|
enum plugin_status plugin_start(const void* parameter)
|
|
{
|
|
int ret = PLUGIN_OK;
|
|
int selected_item = -1;
|
|
int action;
|
|
int items = 0;
|
|
#if CONFIG_RTC
|
|
static char filename[MAX_PATH] = HOME_DIR "/.scrobbler.log";
|
|
#else /* !CONFIG_RTC */
|
|
static char filename[MAX_PATH] = HOME_DIR "/.scrobbler-timeless.log";
|
|
#endif /* CONFIG_RTC */
|
|
bool redraw = true;
|
|
bool exit = false;
|
|
|
|
static struct printcell_data_t printcell_data;
|
|
|
|
if (parameter)
|
|
{
|
|
rb->strlcpy(filename, (const char*)parameter, MAX_PATH);
|
|
filename[MAX_PATH - 1] = '\0';
|
|
}
|
|
|
|
if (!rb->file_exists(filename))
|
|
{
|
|
browse_file(filename, sizeof(filename));
|
|
if (!rb->file_exists(filename))
|
|
{
|
|
rb->splash(HZ, "No Scrobbler file Goodbye.");
|
|
return PLUGIN_ERROR;
|
|
}
|
|
|
|
}
|
|
|
|
config_set_defaults();
|
|
if (configfile_load(CFG_FILE, config, gCfg_sz, CFG_VER) < 0)
|
|
{
|
|
/* If the loading failed, save a new config file */
|
|
config_set_defaults();
|
|
configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER);
|
|
|
|
rb->splash(HZ, ID2P(LANG_REVERT_TO_DEFAULT_SETTINGS));
|
|
}
|
|
|
|
rb->memset(&printcell_data, 0, sizeof (struct printcell_data_t));
|
|
printcell_data.fd_cur = -1;
|
|
printcell_data.view_lastcol = -1;
|
|
|
|
if (rb->file_exists(filename))
|
|
{
|
|
if (file_open(filename, &printcell_data.fd_cur) == 0)
|
|
printcell_data.fd_cur = -1;
|
|
else
|
|
{
|
|
size_t buf_size;
|
|
printcell_data.buf = rb->plugin_get_buffer(&buf_size);
|
|
printcell_data.buf_size = buf_size;
|
|
printcell_data.buf_used = 0;
|
|
printcell_data.filename = filename;
|
|
items = file_load_entries(&printcell_data);
|
|
}
|
|
}
|
|
int col_w = gConfig.col_width;
|
|
rb->snprintf(printcell_data.header, PRINTCELL_MAXLINELEN,
|
|
SCROBBLE_HDR_FMT, col_w, rb->str(LANG_ID3_ARTIST),
|
|
col_w, rb->str(LANG_ID3_ALBUM),
|
|
col_w, rb->str(LANG_ID3_TITLE));
|
|
|
|
if (!exit && items > 0)
|
|
{
|
|
synclist_set(0, items, 1, &printcell_data);
|
|
rb->gui_synclist_draw(&lists);
|
|
|
|
while (!exit)
|
|
{
|
|
action = rb->get_action(CONTEXT_LIST, HZ / 10);
|
|
|
|
if (action == ACTION_NONE)
|
|
{
|
|
if (redraw)
|
|
{
|
|
action = ACTION_REDRAW;
|
|
redraw = false;
|
|
}
|
|
}
|
|
else
|
|
redraw = true;
|
|
|
|
ret = menu_action_cb(&action, selected_item, &exit, &lists);
|
|
if (rb->gui_synclist_do_button(&lists, &action))
|
|
continue;
|
|
selected_item = rb->gui_synclist_get_sel_pos(&lists);
|
|
|
|
}
|
|
}
|
|
config_save();
|
|
cleanup(&printcell_data);
|
|
return ret;
|
|
}
|