summaryrefslogtreecommitdiff
path: root/apps/plugins/lastfm_scrobbler_viewer.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/lastfm_scrobbler_viewer.c')
-rw-r--r--apps/plugins/lastfm_scrobbler_viewer.c1033
1 files changed, 1033 insertions, 0 deletions
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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// __ \_/ ___\| |/ /| __ \ / __ \ \/ /
5 * Jukebox | | ( (__) ) \___| ( | \_\ ( (__) ) (
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2023 William Wilgus
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include "plugin.h"
23#include "lang_enum.h"
24
25#include "lib/printcell_helper.h"
26
27#include "lib/configfile.h"
28#define CFG_FILE "/lastfm_scrobbler_viewer.cfg"
29#define TMP_FILE ""PLUGIN_DATA_DIR "/lscrobbler_viewer_%d.tmp"
30#define CFG_VER 1
31
32#ifdef ROCKBOX_HAS_LOGF
33#define logf rb->logf
34#else
35#define logf(...) do { } while(0)
36#endif
37
38/*#ARTIST #ALBUM #TITLE #TRACKNUM #LENGTH #RATING #TIMESTAMP #MUSICBRAINZ_TRACKID*/
39
40#define SCROBBLE_HDR_FMT "$*%d$%s$*%d$%s$*%d$%s$Track#$Length$Rating$TimeStamp$TrackId"
41//#define SCROBBLE_HDR "$*128$Artist$*128$Album$*128$Title$Track#$Length$Rating$TimeStamp$TrackId"
42
43#define SCROBBLER_MIN_COLUMNS (6) /* a valid scrobbler file should have at least this many columns */
44
45#define GOTO_ACTION_DEFAULT_HANDLER (PLUGIN_OK + 1)
46#define CANCEL_CONTEXT_MENU (PLUGIN_OK + 2)
47#define QUIT_CONTEXT_MENU (PLUGIN_OK + 3)
48/* global lists, for everything */
49static struct gui_synclist lists;
50
51/* printcell data for the current file */
52struct printcell_data_t {
53 int view_columns;
54 int view_lastcol;
55
56 int items_buffered;
57 int items_total;
58 int fd_cur;
59 char *filename;
60
61 char *buf;
62 size_t buf_size;
63 off_t buf_used;
64 char header[PRINTCELL_MAXLINELEN];
65
66};
67
68enum e_find_type {
69 FIND_ALL = 0,
70 FIND_EXCLUDE,
71 FIND_EXCLUDE_CASE,
72 FIND_EXCLUDE_ANY,
73 FIND_INCLUDE,
74 FIND_INCLUDE_CASE,
75 FIND_INCLUDE_ANY,
76 FIND_CUSTOM,
77};
78
79static void synclist_set(int selected_item, int items, int sel_size, struct printcell_data_t *pc_data);
80static void pc_data_set_header(struct printcell_data_t *pc_data);
81
82static void browse_file(char *buf, size_t bufsz)
83{
84 struct browse_context browse = {
85 .dirfilter = SHOW_ALL,
86 .flags = BROWSE_SELECTONLY,
87 .title = "Select a scrobbler log file",
88 .icon = Icon_Playlist,
89 .buf = buf,
90 .bufsize = bufsz,
91 .root = "/",
92 };
93
94 if (rb->rockbox_browse(&browse) != GO_TO_PREVIOUS)
95 {
96 buf[0] = '\0';
97 }
98}
99
100static struct plugin_config
101{
102 bool separator;
103 bool talk;
104 int col_width;
105 uint32_t hidecol_flags;
106} gConfig;
107
108static struct configdata config[] =
109{
110 {TYPE_BOOL, 0, 1, { .bool_p = &gConfig.separator }, "Cell Separator", NULL},
111 {TYPE_BOOL, 0, 1, { .bool_p = &gConfig.talk }, "Voice", NULL},
112 {TYPE_INT, 32, LCD_WIDTH, { .int_p = &gConfig.col_width }, "Cell Width", NULL},
113 {TYPE_INT, INT_MIN, INT_MAX, { .int_p = &gConfig.hidecol_flags }, "Hidden Columns", NULL},
114};
115const int gCfg_sz = sizeof(config)/sizeof(*config);
116/****************** config functions *****************/
117static void config_set_defaults(void)
118{
119 gConfig.col_width = MIN(LCD_WIDTH, 128);
120 gConfig.hidecol_flags = 0;
121 gConfig.separator = true;
122 gConfig.talk = rb->global_settings->talk_menu;
123}
124
125static void config_save(void)
126{
127 configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER);
128}
129
130static int config_settings_menu(struct printcell_data_t *pc_data)
131{
132 int selection = 0;
133
134 struct viewport parentvp[NB_SCREENS];
135 FOR_NB_SCREENS(l)
136 {
137 rb->viewport_set_defaults(&parentvp[l], l);
138 rb->viewport_set_fullscreen(&parentvp[l], l);
139 }
140
141 MENUITEM_STRINGLIST(settings_menu, ID2P(LANG_SETTINGS), NULL,
142 ID2P(LANG_LIST_SEPARATOR),
143 "Cell Width",
144 ID2P(LANG_VOICE),
145 ID2P(VOICE_BLANK),
146 ID2P(VOICE_BLANK),
147 ID2P(LANG_CANCEL_0),
148 ID2P(LANG_SAVE_EXIT));
149
150 do {
151 selection=rb->do_menu(&settings_menu,&selection, parentvp, true);
152 switch(selection) {
153
154 case 0:
155 rb->set_bool(rb->str(LANG_LIST_SEPARATOR), &gConfig.separator);
156 break;
157 case 1:
158 rb->set_int("Cell Width", "", UNIT_INT,
159 &gConfig.col_width, NULL, 1, 32, LCD_WIDTH, NULL );
160 break;
161 case 2:
162 rb->set_bool(rb->str(LANG_VOICE), &gConfig.talk);
163 break;
164 case 3:
165 continue;
166 case 4: /*sep*/
167 continue;
168 case 5:
169 return -1;
170 break;
171 case 6:
172 {
173 pc_data_set_header(pc_data);
174 synclist_set(0, pc_data->items_total, 1, pc_data);
175 int res = configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER);
176 if (res >= 0)
177 {
178 logf("Cfg saved %s %d bytes", CFG_FILE, gCfg_sz);
179 return PLUGIN_OK;
180 }
181 logf("Cfg FAILED (%d) %s", res, CFG_FILE);
182 return PLUGIN_ERROR;
183 }
184 case MENU_ATTACHED_USB:
185 return PLUGIN_USB_CONNECTED;
186 default:
187 return PLUGIN_OK;
188 }
189 } while ( selection >= 0 );
190 return 0;
191}
192
193/* open file pass back file descriptor and return file size */
194static size_t file_open(const char *filename, int *fd)
195{
196 size_t fsize = 0;
197
198 if (filename && fd)
199 {
200 *fd = rb->open(filename, O_RDONLY);
201 if (*fd >= 0)
202 {
203 fsize = rb->filesize(*fd);
204 }
205 }
206 return fsize;
207}
208
209static const char* list_get_name_cb(int selected_item, void* data,
210 char* buf, size_t buf_len)
211{
212 const int slush_pos = 32; /* entries around the current entry to keep buffered */
213 /* keep track of the last position in the buffer */
214 static int buf_line_num = 0;
215 static off_t buf_last_pos = 0;
216 /* keep track of the last position in the file */
217 static int file_line_num = 0;
218 static off_t file_last_seek = 0;
219
220 if (selected_item < 0)
221 {
222 logf("[%s] Reset positions", __func__);
223 buf_line_num = 0;
224 buf_last_pos = 0;
225 file_line_num = 0;
226 file_last_seek = 0;
227 return "";
228 }
229
230 int line_num;
231 struct printcell_data_t *pc_data = (struct printcell_data_t*) data;
232 bool found = false;
233
234 if (pc_data->buf && selected_item < pc_data->items_buffered)
235 {
236 int buf_pos;
237
238 if (buf_line_num > selected_item || buf_last_pos >= pc_data->buf_used
239 || buf_line_num < 0)
240 {
241 logf("[%s] Set pos buffer 0", __func__);
242 buf_line_num = 0;
243 buf_last_pos = 0;
244 }
245 buf_pos = buf_last_pos;
246 line_num = buf_line_num;
247
248 while (buf_pos < pc_data->buf_used
249 && line_num < pc_data->items_buffered)
250 {
251 size_t len = rb->strlen(&pc_data->buf[buf_pos]);
252 if(line_num == selected_item)
253 {
254 rb->strlcpy(buf, &pc_data->buf[buf_pos], MIN(buf_len, len));
255 logf("(%d) in buffer: %s", line_num, buf);
256 found = true;
257 break;
258 }
259 else
260 {
261 buf_pos += len + 1; /* need to go past the NULL terminator */
262 line_num++;
263
264 if (buf_line_num + slush_pos < selected_item)
265 {
266 logf("[%s] Set pos buffer %d", __func__, line_num);
267 buf_line_num = line_num;
268 buf_last_pos = buf_pos;
269 }
270 }
271 }
272 }
273
274 /* didn't find the item try the file */
275 if(!found && pc_data->fd_cur >= 0)
276 {
277 int fd = pc_data->fd_cur;
278
279 if (file_line_num < 0 || file_line_num > selected_item)
280 {
281 logf("[%s] Set seek file 0", __func__);
282 file_line_num = 0;
283 file_last_seek = 0;
284 }
285
286 rb->lseek(fd, file_last_seek, SEEK_SET);
287 line_num = file_line_num;
288
289 while ((rb->read_line(fd, buf, buf_len)) > 0)
290 {
291 if(buf[0] == '#')
292 continue;
293 if(line_num == selected_item)
294 {
295 logf("(%d) in file: %s", line_num, buf);
296 found = true;
297 break;
298 }
299 else
300 {
301 line_num++;
302
303 if (file_line_num + slush_pos < selected_item)
304 {
305 logf("[%s] Set seek file %d", __func__, line_num);
306 file_line_num = line_num;
307 file_last_seek = rb->lseek(fd, 0, SEEK_CUR);
308 }
309 }
310 }
311 }
312
313 if(!found)
314 {
315 logf("(%d) Not Found!", selected_item);
316 buf_line_num = -1;
317 file_line_num = -1;
318 buf[0] = '\0';
319 }
320 return buf;
321}
322
323static int list_voice_cb(int list_index, void* data)
324{
325 (void) list_index;
326 struct printcell_data_t *pc_data = (struct printcell_data_t*) data;
327 if (!gConfig.talk)
328 return -1;
329
330 int selcol = printcell_get_column_selected();
331 char buf[MAX_PATH];
332 char* name;
333 long id;
334
335 if (pc_data->view_lastcol != selcol)
336 {
337 name = printcell_get_title_text(selcol, buf, sizeof(buf));
338
339 id = P2ID((const unsigned char *)name);
340
341 if(id>=0)
342 rb->talk_id(id, true);
343 else if (selcol >= 0)
344 {
345 switch (selcol)
346 {
347 case 0:
348 rb->talk_id(LANG_ID3_ARTIST, true);
349 break;
350 case 1:
351 rb->talk_id(LANG_ID3_ALBUM, true);
352 break;
353 case 2:
354 rb->talk_id(LANG_ID3_TITLE, true);
355 break;
356 case 3:
357 rb->talk_id(LANG_ID3_TRACKNUM, true);
358 break;
359 case 4:
360 rb->talk_id(LANG_ID3_LENGTH, true);
361 break;
362
363 default:
364 rb->talk_spell(name, true);
365 break;
366 }
367 }
368 else
369 rb->talk_id(LANG_ALL, true);
370
371 rb->talk_id(VOICE_PAUSE, true);
372 }
373
374 name = printcell_get_column_text(selcol, buf, sizeof(buf));
375
376 id = P2ID((const unsigned char *)name);
377
378 if(id>=0)
379 rb->talk_id(id, true);
380 else if (selcol >= 0)
381 rb->talk_spell(name, true);
382
383 return 0;
384}
385
386static enum themable_icons list_icon_cb(int selected_item, void *data)
387{
388 struct printcell_data_t *pc_data = (struct printcell_data_t*) data;
389 if (lists.selected_item == selected_item)
390 {
391 if (pc_data->view_lastcol < 0)
392 return Icon_Config;
393 return Icon_Audio;
394 }
395 return Icon_NOICON;
396}
397
398/* load file entries into pc_data buffer, file should already be opened
399 * and will be closed if the whole file was buffered */
400static int file_load_entries(struct printcell_data_t *pc_data)
401{
402 int items = 0;
403 int count = 0;
404 int buffered = 0;
405 unsigned int pos = 0;
406 bool comment = false;
407 char ch;
408 int fd = pc_data->fd_cur;
409 if (fd < 0)
410 return 0;
411
412 rb->lseek(fd, 0, SEEK_SET);
413
414 while (rb->read(fd, &ch, 1) > 0)
415 {
416 if (count++ == 0 && ch == '#') /* skip comments */
417 comment = true;
418 else if (!comment && ch != '\r' && pc_data->buf_size > pos)
419 pc_data->buf[pos++] = ch;
420
421 if (items == 0 && pos > PRINTCELL_MAXLINELEN * 2)
422 break;
423
424 if (ch == '\n')
425 {
426 if (!comment)
427 {
428 pc_data->buf[pos] = '\0';
429 if (pc_data->buf_size > pos)
430 {
431 pos++;
432 buffered++;
433 }
434 items++;
435 }
436 comment = false;
437 count = 0;
438 rb->yield();
439 }
440 }
441
442 logf("[%s] items: %d buffered: %d", __func__, items, buffered);
443
444 pc_data->items_total = items;
445 pc_data->items_buffered = buffered;
446 pc_data->buf[pos] = '\0';
447 pc_data->buf_used = pos;
448
449 if (items == buffered) /* whole file fit into buffer; close file */
450 {
451 rb->close(pc_data->fd_cur);
452 pc_data->fd_cur = -1;
453 }
454
455 list_get_name_cb(-1, NULL, NULL, 0); /* prime name cb */
456 return items;
457}
458
459static int filter_items(struct printcell_data_t *pc_data,
460 enum e_find_type find_type, int col)
461{
462 /* saves filtered items to a temp file and loads it */
463 int fd;
464 bool reload = false;
465 char buf[PRINTCELL_MAXLINELEN];
466 char find_exclude_buf[PRINTCELL_MAXLINELEN];
467 int selcol = printcell_get_column_selected();
468 char *find_exclude = printcell_get_column_text(selcol, find_exclude_buf,
469 sizeof(find_exclude_buf));
470 const char colsep = '\t';
471 int find_len = strlen(find_exclude);
472
473 if (find_type == FIND_CUSTOM || find_len == 0)
474 {
475 int option = 0;
476 struct opt_items find_types[] = {
477 {"Exclude", -1}, {"Exclude Case Sensitive", -1},
478 {"Exclude Any", -1}, {"Include", -1},
479 {"Include Case Sensitive", -1}, {"Include Any", -1}
480 };
481 if (rb->set_option("Find Type", &option, INT,
482 find_types, 6, NULL))
483 {
484 return 0;
485 }
486 switch (option)
487 {
488 case 0:
489 find_type = FIND_EXCLUDE;
490 break;
491 case 1:
492 find_type = FIND_EXCLUDE_CASE;
493 break;
494 case 2:
495 find_type = FIND_EXCLUDE_ANY;
496 break;
497 case 3:
498 find_type = FIND_INCLUDE;
499 break;
500 case 4:
501 find_type = FIND_INCLUDE_CASE;
502 break;
503 case 5:
504 find_type = FIND_INCLUDE_ANY;
505 break;
506 default:
507 find_type = FIND_ALL;
508 break;
509 }
510
511 /* copy data to beginning of buf */
512 rb->memmove(find_exclude_buf, find_exclude, find_len);
513 find_exclude_buf[find_len] = '\0';
514
515 if (rb->kbd_input(find_exclude_buf, sizeof(find_exclude_buf), NULL) < 0)
516 return -1;
517 find_exclude = find_exclude_buf;
518 find_len = strlen(find_exclude);
519 }
520
521 char tmp_filename[MAX_PATH];
522 static int tmp_num = 0;
523 rb->snprintf(tmp_filename, sizeof(tmp_filename), TMP_FILE, tmp_num);
524 tmp_num++;
525
526 fd = rb->open(tmp_filename, O_RDWR | O_CREAT | O_TRUNC, 0666);
527 if(fd >= 0)
528 {
529 rb->splash_progress_set_delay(HZ * 5);
530 for (int i = 0; i < pc_data->items_total; i++)
531 {
532 rb->splash_progress(i, pc_data->items_total, "Filtering...");
533 const char * data = list_get_name_cb(i, pc_data, buf, sizeof(buf));
534
535 if (find_type != FIND_ALL)
536 {
537 int index = col;
538
539 if (index < 0)
540 index = 0;
541 if (find_len > 0)
542 {
543 char *bcol = buf;
544 while (*bcol != '\0' && index > 0)
545 {
546 if (*bcol == colsep)
547 index--;
548 bcol++;
549 }
550 if (index > 0)
551 continue;
552
553 if (find_type == FIND_EXCLUDE)
554 {
555 if (rb->strncasecmp(bcol, find_exclude, find_len) == 0)
556 {
557 logf("[%s] exclude [%s]", find_exclude, bcol);
558 continue;
559 }
560 }
561 else if (find_type == FIND_INCLUDE)
562 {
563 if (rb->strncasecmp(bcol, find_exclude, find_len) != 0)
564 {
565 logf("%s include %s", find_exclude, bcol);
566 continue;
567 }
568 }
569 else if (find_type == FIND_EXCLUDE_CASE)
570 {
571 if (rb->strncmp(bcol, find_exclude, find_len) == 0)
572 {
573 logf("[%s] exclude case [%s]", find_exclude, bcol);
574 continue;
575 }
576 }
577 else if (find_type == FIND_INCLUDE_CASE)
578 {
579 if (rb->strncmp(bcol, find_exclude, find_len) != 0)
580 {
581 logf("%s include case %s", find_exclude, bcol);
582 continue;
583 }
584 }
585 else if (find_type == FIND_EXCLUDE_ANY)
586 {
587 bool found = false;
588 while (*bcol != '\0' && *bcol != colsep)
589 {
590 if (rb->strncasecmp(bcol, find_exclude, find_len) == 0)
591 {
592 logf("[%s] exclude any [%s]", find_exclude, bcol);
593 found = true;
594 break;
595 }
596 bcol++;
597 }
598 if (found)
599 continue;
600 }
601 else if (find_type == FIND_INCLUDE_ANY)
602 {
603 bool found = false;
604 while (*bcol != '\0' && *bcol != colsep)
605 {
606 if (rb->strncasecmp(bcol, find_exclude, find_len) == 0)
607 {
608 found = true;
609 logf("[%s] include any [%s]", find_exclude, bcol);
610 break;
611 }
612 bcol++;
613 }
614 if (!found)
615 continue;
616 }
617 }
618 }
619 int len = strlen(data);
620 if (len > 0)
621 {
622 logf("writing [%d bytes][%s]", len + 1, data);
623 rb->write(fd, data, len);
624 rb->write(fd, "\n", 1);
625 }
626 }
627 reload = true;
628 }
629
630 if (reload)
631 {
632 if (pc_data->fd_cur >= 0)
633 rb->close(pc_data->fd_cur);
634
635 pc_data->fd_cur = fd;
636 int items = file_load_entries(pc_data);
637 if (items >= 0)
638 {
639 lists.selected_item = 0;
640 rb->gui_synclist_set_nb_items(&lists, items);
641 }
642 }
643 logf("Col text '%s'", find_exclude);
644 logf("text '%s'", find_exclude_buf);
645
646 return pc_data->items_total;
647}
648
649static int scrobbler_context_menu(struct printcell_data_t *pc_data)
650{
651 struct viewport parentvp[NB_SCREENS];
652 FOR_NB_SCREENS(l)
653 {
654 rb->viewport_set_defaults(&parentvp[l], l);
655 rb->viewport_set_fullscreen(&parentvp[l], l);
656 }
657
658 int col = printcell_get_column_selected();
659 int selection = 0;
660 int items = 0;
661 uint32_t visible = printcell_get_column_visibility(-1);
662 bool hide_col = PRINTCELL_COLUMN_IS_VISIBLE(visible, col);
663
664 char namebuf[PRINTCELL_MAXLINELEN];
665 enum e_find_type find_type = FIND_ALL;
666
667 char *colname = pc_data->filename;
668 if (col >= 0)
669 colname = printcell_get_title_text(col, namebuf, sizeof(namebuf));
670
671#define MENUITEM_STRINGLIST_CUSTOM(name, str, callback, ... ) \
672 const char *name##_[] = {__VA_ARGS__}; \
673 const struct menu_callback_with_desc name##__ = \
674 {callback,str, Icon_NOICON}; \
675 const struct menu_item_ex name = \
676 {MT_RETURN_ID|MENU_HAS_DESC| \
677 MENU_ITEM_COUNT(sizeof( name##_)/sizeof(*name##_)), \
678 { .strings = name##_},{.callback_and_desc = & name##__}};
679
680 const char *menu_item[4];
681
682 menu_item[0]= hide_col ? "Hide":"Show";
683 menu_item[1]= "Exclude";
684 menu_item[2]= "Include";
685 menu_item[3]= "Custom Filter";
686
687 if (col == -1)
688 {
689 menu_item[0]= hide_col ? "Hide All":"Show All";
690 menu_item[1]= "Open";
691 menu_item[2]= "Reload";
692 menu_item[3]= ID2P(LANG_SETTINGS);
693 }
694
695 MENUITEM_STRINGLIST_CUSTOM(context_menu, colname, NULL,
696 menu_item[0],
697 menu_item[1],
698 menu_item[2],
699 menu_item[3],
700 ID2P(VOICE_BLANK),
701 ID2P(LANG_CANCEL_0),
702 ID2P(LANG_MENU_QUIT));
703
704#undef MENUITEM_STRINGLIST_CUSTOM
705 do {
706 selection=rb->do_menu(&context_menu,&selection, parentvp, true);
707 switch(selection) {
708
709 case 0:
710 {
711 printcell_set_column_visible(col, !hide_col);
712 if (hide_col)
713 {
714 do
715 {
716 col = printcell_increment_column(1, true);
717 } while (col >= 0 && printcell_get_column_visibility(col) == 1);
718 pc_data->view_lastcol = col;
719 }
720 gConfig.hidecol_flags = printcell_get_column_visibility(-1);
721 break;
722 }
723 case 1: /* Exclude / Open */
724 {
725 if (col == -1)/*Open*/
726 {
727 char buf[MAX_PATH];
728 browse_file(buf, sizeof(buf));
729 if (rb->file_exists(buf))
730 {
731 rb->strlcpy(pc_data->filename, buf, MAX_PATH);
732 if (pc_data->fd_cur >= 0)
733 rb->close(pc_data->fd_cur);
734 if (file_open(pc_data->filename, &pc_data->fd_cur) > 0)
735 items = file_load_entries(pc_data);
736 if (items >= 0)
737 synclist_set(0, items, 1, pc_data);
738 }
739 else
740 rb->splash(HZ *2, "Error Opening");
741
742 return CANCEL_CONTEXT_MENU;
743 }
744
745 find_type = FIND_EXCLUDE;
746 }
747 /* fall-through */
748 case 2: /* Include / Reload */
749 {
750 if (col == -1) /*Reload*/
751 {
752 if (pc_data->fd_cur >= 0)
753 rb->close(pc_data->fd_cur);
754 if (file_open(pc_data->filename, &pc_data->fd_cur) > 0)
755 items = file_load_entries(pc_data);
756 if (items >= 0)
757 rb->gui_synclist_set_nb_items(&lists, items);
758 return CANCEL_CONTEXT_MENU;
759 }
760
761 if (find_type == FIND_ALL)
762 find_type = FIND_INCLUDE;
763 /* fall-through */
764 }
765 case 3: /*Custom Filter / Settings */
766 {
767 if (col == -1)/*Settings*/
768 return config_settings_menu(pc_data);
769
770 if (find_type == FIND_ALL)
771 find_type = FIND_CUSTOM;
772 items = filter_items(pc_data, find_type, col);
773
774 if (items >= 0)
775 rb->gui_synclist_set_nb_items(&lists, items);
776 break;
777 }
778 case 4: /*sep*/
779 continue;
780 case 5:
781 return CANCEL_CONTEXT_MENU;
782 break;
783 case 6: /* Quit */
784 return QUIT_CONTEXT_MENU;
785 break;
786 case MENU_ATTACHED_USB:
787 return PLUGIN_USB_CONNECTED;
788 default:
789 return PLUGIN_OK;
790 }
791 } while ( selection < 0 );
792 return 0;
793}
794
795static void cleanup(void *parameter)
796{
797 struct printcell_data_t *pc_data = (struct printcell_data_t*) parameter;
798 if (pc_data->fd_cur >= 0)
799 rb->close(pc_data->fd_cur);
800 pc_data->fd_cur = -1;
801}
802
803static void menu_action_printcell(int *action, int selected_item, bool* exit, struct gui_synclist *lists)
804{
805 (void) exit;
806 struct printcell_data_t *pc_data = (struct printcell_data_t*) lists->data;
807 if (*action == ACTION_STD_OK)
808 {
809 if (selected_item < lists->nb_items)
810 {
811 pc_data->view_lastcol = printcell_increment_column(1, true);
812 *action = ACTION_NONE;
813 }
814 }
815 else if (*action == ACTION_STD_CANCEL)
816 {
817 pc_data->view_lastcol = printcell_increment_column(-1, true);
818 if (pc_data->view_lastcol != pc_data->view_columns - 1)
819 {
820 *action = ACTION_NONE;
821 }
822 }
823 else if (*action == ACTION_STD_CONTEXT)
824 {
825 int ctxret = scrobbler_context_menu(pc_data);
826 if (ctxret == QUIT_CONTEXT_MENU)
827 *exit = true;
828 }
829}
830
831int menu_action_cb(int *action, int selected_item, bool* exit, struct gui_synclist *lists)
832{
833
834 menu_action_printcell(action, selected_item, exit, lists);
835
836 if (rb->default_event_handler_ex(*action, cleanup, lists->data) == SYS_USB_CONNECTED)
837 {
838 *exit = true;
839 return PLUGIN_USB_CONNECTED;
840 }
841 return PLUGIN_OK;
842}
843
844static int count_max_columns(int items, char delimeter,
845 int expected_cols, struct printcell_data_t *pc_data)
846{
847 int max_cols = 0;
848 int cols = 0;
849 char buf[PRINTCELL_MAXLINELEN];
850 for (int i = 0; i < items; i++)
851 {
852 const char *txt = list_get_name_cb(i, pc_data, buf, sizeof(buf));
853 while (*txt != '\0')
854 {
855 if (*txt == delimeter)
856 {
857 cols++;
858 if (cols == expected_cols)
859 {
860 max_cols = cols;
861 break;
862 }
863 }
864 txt++;
865 }
866
867 if(max_cols < expected_cols && i > 32)
868 break;
869
870 if (cols > max_cols)
871 max_cols = cols;
872 cols = 0;
873 }
874 return max_cols;
875}
876
877static void synclist_set(int selected_item, int items, int sel_size, struct printcell_data_t *pc_data)
878{
879 if (items <= 0)
880 return;
881 if (selected_item < 0)
882 selected_item = 0;
883
884 rb->gui_synclist_init(&lists,list_get_name_cb,
885 pc_data, false, sel_size, NULL);
886
887 rb->gui_synclist_set_icon_callback(&lists, list_icon_cb);
888 rb->gui_synclist_set_voice_callback(&lists, list_voice_cb);
889 rb->gui_synclist_set_nb_items(&lists,items);
890 rb->gui_synclist_select_item(&lists, selected_item);
891
892 struct printcell_settings pcs = {.cell_separator = gConfig.separator,
893 .title_delimeter = '$',
894 .text_delimeter = '\t',
895 .hidecol_flags = gConfig.hidecol_flags};
896
897 pc_data->view_columns = printcell_set_columns(&lists, &pcs,
898 pc_data->header, Icon_Rockbox);
899 printcell_enable(true);
900
901
902 int max_cols = count_max_columns(items, pcs.text_delimeter,
903 SCROBBLER_MIN_COLUMNS, pc_data);
904 if (max_cols < SCROBBLER_MIN_COLUMNS) /* not a scrobbler file? */
905 {
906 rb->gui_synclist_set_voice_callback(&lists, NULL);
907 pc_data->view_columns = printcell_set_columns(&lists, NULL,
908 "$*512$", Icon_Questionmark);
909 }
910
911 int curcol = printcell_get_column_selected();
912 while (curcol >= 0)
913 curcol = printcell_increment_column(-1, false);
914
915 if (pc_data->view_lastcol >= pc_data->view_columns)
916 pc_data->view_lastcol = -1;
917
918 /* restore column position */
919 while (pc_data->view_lastcol > -1 && curcol != pc_data->view_lastcol)
920 {
921 curcol = printcell_increment_column(1, true);
922 }
923 pc_data->view_lastcol = curcol;
924 list_voice_cb(0, pc_data);
925}
926
927static void pc_data_set_header(struct printcell_data_t *pc_data)
928{
929 int col_w = gConfig.col_width;
930 rb->snprintf(pc_data->header, PRINTCELL_MAXLINELEN,
931 SCROBBLE_HDR_FMT, col_w, rb->str(LANG_ID3_ARTIST),
932 col_w, rb->str(LANG_ID3_ALBUM),
933 col_w, rb->str(LANG_ID3_TITLE));
934}
935
936enum plugin_status plugin_start(const void* parameter)
937{
938 int ret = PLUGIN_OK;
939 int selected_item = -1;
940 int action;
941 int items = 0;
942#if CONFIG_RTC
943 static char filename[MAX_PATH] = HOME_DIR "/.scrobbler.log";
944#else /* !CONFIG_RTC */
945 static char filename[MAX_PATH] = HOME_DIR "/.scrobbler-timeless.log";
946#endif /* CONFIG_RTC */
947 bool redraw = true;
948 bool exit = false;
949
950 static struct printcell_data_t printcell_data;
951
952 if (parameter)
953 {
954 rb->strlcpy(filename, (const char*)parameter, MAX_PATH);
955 filename[MAX_PATH - 1] = '\0';
956 }
957
958 if (!rb->file_exists(filename))
959 {
960 browse_file(filename, sizeof(filename));
961 if (!rb->file_exists(filename))
962 {
963 rb->splash(HZ, "No Scrobbler file Goodbye.");
964 return PLUGIN_ERROR;
965 }
966
967 }
968
969 config_set_defaults();
970 if (configfile_load(CFG_FILE, config, gCfg_sz, CFG_VER) < 0)
971 {
972 /* If the loading failed, save a new config file */
973 config_set_defaults();
974 configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER);
975
976 rb->splash(HZ, ID2P(LANG_REVERT_TO_DEFAULT_SETTINGS));
977 }
978
979 rb->memset(&printcell_data, 0, sizeof (struct printcell_data_t));
980 printcell_data.fd_cur = -1;
981 printcell_data.view_lastcol = -1;
982
983 if (rb->file_exists(filename))
984 {
985 if (file_open(filename, &printcell_data.fd_cur) == 0)
986 printcell_data.fd_cur = -1;
987 else
988 {
989 size_t buf_size;
990 printcell_data.buf = rb->plugin_get_buffer(&buf_size);
991 printcell_data.buf_size = buf_size;
992 printcell_data.buf_used = 0;
993 printcell_data.filename = filename;
994 items = file_load_entries(&printcell_data);
995 }
996 }
997 int col_w = gConfig.col_width;
998 rb->snprintf(printcell_data.header, PRINTCELL_MAXLINELEN,
999 SCROBBLE_HDR_FMT, col_w, rb->str(LANG_ID3_ARTIST),
1000 col_w, rb->str(LANG_ID3_ALBUM),
1001 col_w, rb->str(LANG_ID3_TITLE));
1002
1003 if (!exit && items > 0)
1004 {
1005 synclist_set(0, items, 1, &printcell_data);
1006 rb->gui_synclist_draw(&lists);
1007
1008 while (!exit)
1009 {
1010 action = rb->get_action(CONTEXT_LIST, HZ / 10);
1011
1012 if (action == ACTION_NONE)
1013 {
1014 if (redraw)
1015 {
1016 action = ACTION_REDRAW;
1017 redraw = false;
1018 }
1019 }
1020 else
1021 redraw = true;
1022
1023 ret = menu_action_cb(&action, selected_item, &exit, &lists);
1024 if (rb->gui_synclist_do_button(&lists, &action))
1025 continue;
1026 selected_item = rb->gui_synclist_get_sel_pos(&lists);
1027
1028 }
1029 }
1030 config_save();
1031 cleanup(&printcell_data);
1032 return ret;
1033}