rockbox/apps/plugins/lastfm_scrobbler_viewer.c
William Wilgus c3fd32bdf2 [Feature] playback logging from core
people don't like having to remember to run the TSR plugin so
lets meet them halfway

all tracks are added with timestamp, elapsed, length, trackname

added buffering for ATA devices

still needed:
-Done -- a plugin that parses for duplicates and reads the track info
to create the actual scrobbler log
(log can be truncated once dumped)
this should allow you to run the plugin at leisure

later I'd like to expand this logging to allow
track culling based on skipped songs..

remove the TSR plugin as hard disk no longer need to use it

Change-Id: Ib0b74b4c868fecb3e4941a8f4b9de7bd8728fe3e
2025-01-13 00:45:09 -05:00

1119 lines
34 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 int scrobbler_read_line(int fd, char* buf, size_t buf_size);
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 ((scrobbler_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;
}
static int scrobbler_read_line(int fd, char* buf, size_t buf_size)
{
int count = 0;
unsigned int pos = 0;
char sep = '\t';
char ch, last_ch = sep;
bool comment = false;
while (rb->read(fd, &ch, 1) > 0)
{
if (ch == sep && last_ch == sep && buf_size > pos)
buf[pos++] = ' ';
if (count++ == 0 && ch == '#') /* skip comments */
comment = true;
else if (!comment && ch != '\r' && buf_size > pos)
buf[pos++] = ch;
last_ch = ch;
if (pos > PRINTCELL_MAXLINELEN * 2)
break;
if (ch == '\n')
{
if (!comment)
{
buf[pos] = '\0';
if (buf_size > pos)
return count;
}
last_ch = sep;
comment = false;
count = 0;
}
}
return count;
}
/* 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 sep = '\t';
char ch, last_ch = sep;
int fd = pc_data->fd_cur;
if (fd < 0)
return 0;
size_t buf_size = pc_data->buf_size - 1;
rb->lseek(fd, 0, SEEK_SET);
while (rb->read(fd, &ch, 1) > 0)
{
if (ch == sep && last_ch == sep && buf_size > pos)
pc_data->buf[pos++] = ' ';
if (count++ == 0 && ch == '#') /* skip comments */
comment = true;
else if (!comment && ch != '\r' && 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 (buf_size > pos)
{
pos++;
buffered++;
}
items++;
}
last_ch = sep;
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, RB_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? */
{
/*check for a playlist_control file or a playback log*/
max_cols = count_max_columns(items, ':', 3, pc_data);
if (max_cols >= 3)
{
char headerbuf[32];
int w = gConfig.col_width;
rb->snprintf(headerbuf, sizeof(headerbuf),
"$*%d$$*%d$$*%d$$*%d$", w, w, w, w);
struct printcell_settings pcs = {.cell_separator = gConfig.separator,
.title_delimeter = '$',
.text_delimeter = ':',
.hidecol_flags = gConfig.hidecol_flags};
rb->gui_synclist_set_voice_callback(&lists, NULL);
pc_data->view_columns = printcell_set_columns(&lists, &pcs,
headerbuf, Icon_Rockbox);
}
else
{
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, sizeof(filename));
filename[MAX_PATH - 1] = '\0';
parameter = NULL;
}
if (!rb->file_exists(filename))
{
if (rb->strcmp(filename, "-scrobbler_view_pbl") == 0)
{
parameter = PLUGIN_APPS_DIR "/lastfm_scrobbler.rock";
rb->strlcpy(filename, ROCKBOX_DIR "/playback.log", MAX_PATH);
if (!rb->file_exists(filename))
{
rb->splashf(HZ * 2, "Viewer: NO log! %s", filename);
return rb->plugin_open(parameter, "-resume");
}
}
else
{
browse_file(filename, sizeof(filename));
if (!rb->file_exists(filename))
{
rb->splash(HZ, "No 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);
if (parameter)
{
return rb->plugin_open((const char*)parameter, "-resume");
}
return ret;
}