summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWilliam Wilgus <wilgus.william@gmail.com>2022-05-27 22:04:25 -0400
committerWilliam Wilgus <me.theuser@yahoo.com>2022-05-28 06:23:37 -0400
commit1d392613389d36e41edc56b9dff166c8b0172d0a (patch)
tree603c9707bd2c571077aa5500edc2702efe262a33
parent7345666d9ce52234236cc144062bdc75d3bcf23c (diff)
downloadrockbox-1d392613389d36e41edc56b9dff166c8b0172d0a.tar.gz
rockbox-1d392613389d36e41edc56b9dff166c8b0172d0a.zip
lastfm_scrobbler Add settings WIP
add settings to the scrobbler plugin Start Playback -- resume playback at plugin start (while enabled if nothing to resume will bring you back to scrobbler menu) Save Threshold 0-100% -- when this much time has passed the track will be saved and marked (L)istened Verbose -- Supress messages such as 'Scrobbler Started' and 'Scrobbler Flushed' run the plugin a second time to bring up the menu if you have resume playback enabled and there is nothing to resume it brings you back to the scrobbler menu as well Change-Id: I48d96ea3dc8f37d76a723136004af149429e0b2e
-rw-r--r--apps/plugins/lastfm_scrobbler.c179
1 files changed, 144 insertions, 35 deletions
diff --git a/apps/plugins/lastfm_scrobbler.c b/apps/plugins/lastfm_scrobbler.c
index 5e9e903a82..3269f1820b 100644
--- a/apps/plugins/lastfm_scrobbler.c
+++ b/apps/plugins/lastfm_scrobbler.c
@@ -26,6 +26,7 @@ http://www.audioscrobbler.net/wiki/Portable_Player_Logging
26*/ 26*/
27 27
28#include "plugin.h" 28#include "plugin.h"
29#include "lib/configfile.h"
29 30
30#ifndef UNTAGGED 31#ifndef UNTAGGED
31 #define UNTAGGED "<UNTAGGED>" 32 #define UNTAGGED "<UNTAGGED>"
@@ -55,6 +56,9 @@ http://www.audioscrobbler.net/wiki/Portable_Player_Logging
55 56
56#define ITEM_HDR "#ARTIST #ALBUM #TITLE #TRACKNUM #LENGTH #RATING #TIMESTAMP #MUSICBRAINZ_TRACKID\n" 57#define ITEM_HDR "#ARTIST #ALBUM #TITLE #TRACKNUM #LENGTH #RATING #TIMESTAMP #MUSICBRAINZ_TRACKID\n"
57 58
59#define CFG_FILE "/lastfm_scrobbler.cfg"
60#define CFG_VER 1
61
58#if CONFIG_RTC 62#if CONFIG_RTC
59static time_t timestamp; 63static time_t timestamp;
60#define BASE_FILENAME HOME_DIR "/.scrobbler.log" 64#define BASE_FILENAME HOME_DIR "/.scrobbler.log"
@@ -71,7 +75,6 @@ static time_t timestamp;
71#define THREAD_STACK_SIZE 4*DEFAULT_STACK_SIZE 75#define THREAD_STACK_SIZE 4*DEFAULT_STACK_SIZE
72 76
73/****************** prototypes ******************/ 77/****************** prototypes ******************/
74int plugin_main(const void* parameter); /* main loop */
75enum plugin_status plugin_start(const void* parameter); /* entry */ 78enum plugin_status plugin_start(const void* parameter); /* entry */
76 79
77/****************** globals ******************/ 80/****************** globals ******************/
@@ -95,6 +98,86 @@ static struct
95 bool force_flush; 98 bool force_flush;
96} gCache; 99} gCache;
97 100
101static struct
102{
103 int savepct;
104 bool playback;
105 bool verbose;
106} gConfig;
107
108static struct configdata config[] =
109{
110 {TYPE_INT, 0, 100, { .int_p = &gConfig.savepct }, "SavePct", NULL},
111 {TYPE_BOOL, 0, 1, { .bool_p = &gConfig.playback }, "Playback", NULL},
112 {TYPE_BOOL, 0, 1, { .bool_p = &gConfig.verbose }, "Verbose", NULL},
113};
114const int gCfg_sz = sizeof(config)/sizeof(*config);
115/****************** config functions *****************/
116static void config_set_defaults(void)
117{
118 gConfig.savepct = 50;
119 gConfig.playback = false;
120 gConfig.verbose = true;
121}
122
123static int config_settings_menu(void)
124{
125 int selection = 0;
126
127 struct viewport parentvp[NB_SCREENS];
128 FOR_NB_SCREENS(l)
129 {
130 rb->viewport_set_defaults(&parentvp[l], l);
131 rb->viewport_set_fullscreen(&parentvp[l], l);
132 }
133
134 MENUITEM_STRINGLIST(settings_menu, ID2P(LANG_SETTINGS), NULL,
135 ID2P(LANG_RESUME_PLAYBACK),
136 "Save Threshold",
137 "Verbose",
138 ID2P(VOICE_BLANK),
139 ID2P(LANG_CANCEL_0),
140 ID2P(LANG_SAVE_EXIT));
141
142 do {
143 selection=rb->do_menu(&settings_menu,&selection, parentvp, true);
144 switch(selection) {
145
146 case 0:
147 rb->set_bool(str(LANG_RESUME_PLAYBACK), &gConfig.playback);
148 break;
149 case 1:
150 rb->set_int("Save Threshold", "%", UNIT_PERCENT,
151 &gConfig.savepct, NULL, 10, 0, 100, NULL );
152 break;
153 case 2:
154 rb->set_bool("Verbose", &gConfig.verbose);
155 break;
156 case 3: /*sep*/
157 continue;
158 case 4:
159 return -1;
160 break;
161 case 5:
162 {
163 int res = configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER);
164 if (res >= 0)
165 {
166 logf("Scrobbler cfg saved %s %d bytes", CFG_FILE, gCfg_sz);
167 return PLUGIN_OK;
168 }
169 logf("Scrobbler cfg FAILED (%d) %s", res, CFG_FILE);
170 return PLUGIN_ERROR;
171 }
172 case MENU_ATTACHED_USB:
173 return PLUGIN_USB_CONNECTED;
174 default:
175 return PLUGIN_OK;
176 }
177 } while ( selection >= 0 );
178 return 0;
179}
180
98/****************** helper fuctions ******************/ 181/****************** helper fuctions ******************/
99 182
100int scrobbler_init(void) 183int scrobbler_init(void)
@@ -210,6 +293,13 @@ static inline char* str_chk_valid(char *s, char *alt)
210 return (s != NULL ? s : alt); 293 return (s != NULL ? s : alt);
211} 294}
212 295
296static unsigned long scrobbler_get_threshold(unsigned long length)
297{
298 /* length is assumed to be in miliseconds */
299 return length / 100 * gConfig.savepct;
300
301}
302
213static void scrobbler_add_to_cache(const struct mp3entry *id) 303static void scrobbler_add_to_cache(const struct mp3entry *id)
214{ 304{
215 static uint32_t last_crc = 0; 305 static uint32_t last_crc = 0;
@@ -223,7 +313,7 @@ static void scrobbler_add_to_cache(const struct mp3entry *id)
223 313
224 logf("SCROBBLER: add_to_cache[%d]", gCache.pos); 314 logf("SCROBBLER: add_to_cache[%d]", gCache.pos);
225 315
226 if (id->elapsed > id->length / 2) 316 if (id->elapsed >= scrobbler_get_threshold(id->length))
227 rating = 'L'; /* Listened */ 317 rating = 'L'; /* Listened */
228 318
229 char tracknum[11] = { "" }; 319 char tracknum[11] = { "" };
@@ -294,9 +384,9 @@ static void scrobbler_change_event(unsigned short id, void *ev_data)
294 logf("%s", __func__); 384 logf("%s", __func__);
295 struct mp3entry *id3 = ((struct track_event *)ev_data)->id3; 385 struct mp3entry *id3 = ((struct track_event *)ev_data)->id3;
296 386
297 /* check if track was resumed > %50 played ( likely got saved ) 387 /* check if track was resumed > %threshold played ( likely got saved )
298 check for blank artist or track name */ 388 check for blank artist or track name */
299 if ((id3->elapsed > id3->length / 2) 389 if ((id3->elapsed > scrobbler_get_threshold(id3->length))
300 || (!id3->artist && !id3->albumartist) || !id3->title) 390 || (!id3->artist && !id3->albumartist) || !id3->title)
301 { 391 {
302 gCache.pending = false; 392 gCache.pending = false;
@@ -332,25 +422,32 @@ static void scrobbler_finish_event(unsigned short id, void *ev_data)
332{ 422{
333 (void)id; 423 (void)id;
334 struct track_event *te = ((struct track_event *)ev_data); 424 struct track_event *te = ((struct track_event *)ev_data);
335 struct mp3entry *id3 = te->id3;
336 logf("%s %s %s", __func__, gCache.pending?"True":"False", track_event_info(te)); 425 logf("%s %s %s", __func__, gCache.pending?"True":"False", track_event_info(te));
337 /* add entry using the currently ending track */ 426 /* add entry using the currently ending track */
338 if (gCache.pending && (te->flags & TEF_CURRENT) && !(te->flags & TEF_REWIND)) 427 if (gCache.pending && (te->flags & TEF_CURRENT) && !(te->flags & TEF_REWIND))
339 { 428 {
340 gCache.pending = false; 429 gCache.pending = false;
341 430
342 if (id3->elapsed*2 >= id3->length) 431 scrobbler_add_to_cache(te->id3);
343 scrobbler_add_to_cache(te->id3);
344 else
345 {
346 logf("%s Discarding < 50%% played", __func__);
347 }
348 } 432 }
349 433
350 434
351} 435}
352 436
353/****************** main thread + helper ******************/ 437/****************** main thread + helpers ******************/
438static void events_unregister(void)
439{
440 /* we don't want any more events */
441 rb->remove_event(PLAYBACK_EVENT_TRACK_CHANGE, scrobbler_change_event);
442 rb->remove_event(PLAYBACK_EVENT_TRACK_FINISH, scrobbler_finish_event);
443}
444
445static void events_register(void)
446{
447 rb->add_event(PLAYBACK_EVENT_TRACK_CHANGE, scrobbler_change_event);
448 rb->add_event(PLAYBACK_EVENT_TRACK_FINISH, scrobbler_finish_event);
449}
450
354void thread(void) 451void thread(void)
355{ 452{
356 bool in_usb = false; 453 bool in_usb = false;
@@ -371,6 +468,7 @@ void thread(void)
371 in_usb = false; 468 in_usb = false;
372 /*fall through*/ 469 /*fall through*/
373 case EV_STARTUP: 470 case EV_STARTUP:
471 events_register();
374 rb->beep_play(1500, 100, 1000); 472 rb->beep_play(1500, 100, 1000);
375 break; 473 break;
376 case SYS_POWEROFF: 474 case SYS_POWEROFF:
@@ -384,6 +482,7 @@ void thread(void)
384 if (!in_usb) 482 if (!in_usb)
385 scrobbler_flush_cache(); 483 scrobbler_flush_cache();
386#endif 484#endif
485 events_unregister();
387 return; 486 return;
388 case EV_FLUSHCACHE: 487 case EV_FLUSHCACHE:
389 scrobbler_flush_cache(); 488 scrobbler_flush_cache();
@@ -412,22 +511,19 @@ void thread_create(void)
412void thread_quit(void) 511void thread_quit(void)
413{ 512{
414 if (!gThread.exiting) { 513 if (!gThread.exiting) {
514 gThread.exiting = true;
415 rb->queue_post(&gThread.queue, EV_EXIT, 0); 515 rb->queue_post(&gThread.queue, EV_EXIT, 0);
416 rb->thread_wait(gThread.id); 516 rb->thread_wait(gThread.id);
417 /* we don't want any more events */
418 rb->remove_event(PLAYBACK_EVENT_TRACK_CHANGE, scrobbler_change_event);
419 rb->remove_event(PLAYBACK_EVENT_TRACK_FINISH, scrobbler_finish_event);
420 /* remove the thread's queue from the broadcast list */ 517 /* remove the thread's queue from the broadcast list */
421 rb->queue_delete(&gThread.queue); 518 rb->queue_delete(&gThread.queue);
422 gThread.exiting = true;
423 } 519 }
424} 520}
425 521
426/* callback to end the TSR plugin, called before a new one gets loaded */ 522/* callback to end the TSR plugin, called before a new one gets loaded */
427static bool exit_tsr(bool reenter) 523static bool exit_tsr(bool reenter)
428{ 524{
429 MENUITEM_STRINGLIST(menu, ID2P(LANG_AUDIOSCROBBLER), NULL, 525 MENUITEM_STRINGLIST(menu, ID2P(LANG_AUDIOSCROBBLER), NULL, ID2P(LANG_SETTINGS),
430 "Flush Cache", "Quit", "Back"); 526 "Flush Cache", "Exit Plugin", ID2P(LANG_BACK));
431 527
432 const struct text_message quit_prompt = { 528 const struct text_message quit_prompt = {
433 (const char*[]){ ID2P(LANG_AUDIOSCROBBLER), 529 (const char*[]){ ID2P(LANG_AUDIOSCROBBLER),
@@ -437,22 +533,25 @@ static bool exit_tsr(bool reenter)
437 533
438 while(true) 534 while(true)
439 { 535 {
440 int result = reenter ? rb->do_menu(&menu, NULL, NULL, false) : 1; 536 int result = reenter ? rb->do_menu(&menu, NULL, NULL, false) : 2;
441 switch(result) 537 switch(result)
442 { 538 {
443 case 0: /* flush cache */ 539 case 0: /* settings */
540 config_settings_menu();
541 break;
542 case 1: /* flush cache */
444 rb->queue_send(&gThread.queue, EV_FLUSHCACHE, 0); 543 rb->queue_send(&gThread.queue, EV_FLUSHCACHE, 0);
445 rb->splashf(2*HZ, "%s Cache Flushed", str(LANG_AUDIOSCROBBLER)); 544 if (gConfig.verbose)
545 rb->splashf(2*HZ, "%s Cache Flushed", str(LANG_AUDIOSCROBBLER));
446 break; 546 break;
447 547
448 case 1: /* quit */ 548 case 2: /* exit plugin - quit */
449 if(rb->gui_syncyesno_run(&quit_prompt, NULL, NULL) == YESNO_YES) 549 if(rb->gui_syncyesno_run(&quit_prompt, NULL, NULL) == YESNO_YES)
450 { 550 {
451 rb->queue_post(&gThread.queue, EV_EXIT, 0); 551 thread_quit();
452 rb->thread_wait(gThread.id); 552 if (reenter)
453 /* remove the thread's queue from the broadcast list */ 553 rb->plugin_tsr(NULL); /* remove TSR cb */
454 rb->queue_delete(&gThread.queue); 554 return !reenter;
455 return true;
456 } 555 }
457 556
458 if(!reenter) 557 if(!reenter)
@@ -460,29 +559,29 @@ static bool exit_tsr(bool reenter)
460 559
461 break; 560 break;
462 561
463 case 2: /* back to menu */ 562 case 3: /* back to menu */
464 return false; 563 return false;
465 } 564 }
466 } 565 }
467} 566}
468 567
469/****************** main ******************/ 568/****************** main ******************/
470 569static int plugin_main(const void* parameter)
471int plugin_main(const void* parameter)
472{ 570{
473 (void)parameter; 571 (void)parameter;
474 572
475 rb->memset(&gThread, 0, sizeof(gThread)); 573 rb->memset(&gThread, 0, sizeof(gThread));
476 rb->splashf(HZ / 2, "%s Started",str(LANG_AUDIOSCROBBLER)); 574 if (gConfig.verbose)
575 rb->splashf(HZ / 2, "%s Started",str(LANG_AUDIOSCROBBLER));
477 logf("%s: %s Started", __func__, str(LANG_AUDIOSCROBBLER)); 576 logf("%s: %s Started", __func__, str(LANG_AUDIOSCROBBLER));
478 577
479 rb->plugin_tsr(exit_tsr); /* stay resident */ 578 rb->plugin_tsr(exit_tsr); /* stay resident */
480 579
481 rb->add_event(PLAYBACK_EVENT_TRACK_CHANGE, scrobbler_change_event);
482 rb->add_event(PLAYBACK_EVENT_TRACK_FINISH, scrobbler_finish_event);
483 thread_create(); 580 thread_create();
484 581
485 return 0; 582 if (gConfig.playback)
583 return PLUGIN_GOTO_WPS;
584 return PLUGIN_OK;
486} 585}
487 586
488/***************** Plugin Entry Point *****************/ 587/***************** Plugin Entry Point *****************/
@@ -495,6 +594,16 @@ enum plugin_status plugin_start(const void* parameter)
495 language_strings = rb->language_strings; 594 language_strings = rb->language_strings;
496 if (scrobbler_init() < 0) 595 if (scrobbler_init() < 0)
497 return PLUGIN_ERROR; 596 return PLUGIN_ERROR;
597
598 if (configfile_load(CFG_FILE, config, gCfg_sz, CFG_VER) < 0)
599 {
600 /* If the loading failed, save a new config file */
601 config_set_defaults();
602 configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER);
603
604 rb->splash(HZ, ID2P(LANG_REVERT_TO_DEFAULT_SETTINGS));
605 }
606
498 int ret = plugin_main(parameter); 607 int ret = plugin_main(parameter);
499 return (ret==0) ? PLUGIN_OK : PLUGIN_ERROR; 608 return ret;
500} 609}