From dfe12252bba8cbfe427752ad9ed101002a76fe67 Mon Sep 17 00:00:00 2001 From: William Wilgus Date: Sat, 1 Apr 2023 12:32:47 -0400 Subject: [Feature, Plugin] lastfm_scrobbler_viewer a plugin to view lastfm scrobbler logs uses print cell to give a spreadsheet view of scrobbler logs buffers the whole file if possible otherwise it reads entries from disk rudimentary text searching for columns include / exclude; all/any and case sensitive Change-Id: Id9616e5796658952fba4ea747f596cb77d6f34c0 --- apps/plugins/lastfm_scrobbler_viewer.c | 1033 ++++++++++++++++++++++++++++++++ 1 file changed, 1033 insertions(+) create mode 100644 apps/plugins/lastfm_scrobbler_viewer.c (limited to 'apps/plugins/lastfm_scrobbler_viewer.c') diff --git a/apps/plugins/lastfm_scrobbler_viewer.c b/apps/plugins/lastfm_scrobbler_viewer.c new file mode 100644 index 0000000000..8f533ef5f7 --- /dev/null +++ b/apps/plugins/lastfm_scrobbler_viewer.c @@ -0,0 +1,1033 @@ +/*************************************************************************** + * __________ __ ___. + * 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; + uint32_t 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 = 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 = 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 = 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; +} -- cgit v1.2.3