/*************************************************************************** * __________ __ ___. * 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; }