diff options
author | William Wilgus <wilgus.william@gmail.com> | 2022-05-27 22:04:25 -0400 |
---|---|---|
committer | William Wilgus <me.theuser@yahoo.com> | 2022-05-28 06:23:37 -0400 |
commit | 1d392613389d36e41edc56b9dff166c8b0172d0a (patch) | |
tree | 603c9707bd2c571077aa5500edc2702efe262a33 | |
parent | 7345666d9ce52234236cc144062bdc75d3bcf23c (diff) | |
download | rockbox-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.c | 179 |
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 |
59 | static time_t timestamp; | 63 | static 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 ******************/ |
74 | int plugin_main(const void* parameter); /* main loop */ | ||
75 | enum plugin_status plugin_start(const void* parameter); /* entry */ | 78 | enum 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 | ||
101 | static struct | ||
102 | { | ||
103 | int savepct; | ||
104 | bool playback; | ||
105 | bool verbose; | ||
106 | } gConfig; | ||
107 | |||
108 | static 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 | }; | ||
114 | const int gCfg_sz = sizeof(config)/sizeof(*config); | ||
115 | /****************** config functions *****************/ | ||
116 | static void config_set_defaults(void) | ||
117 | { | ||
118 | gConfig.savepct = 50; | ||
119 | gConfig.playback = false; | ||
120 | gConfig.verbose = true; | ||
121 | } | ||
122 | |||
123 | static 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 | ||
100 | int scrobbler_init(void) | 183 | int 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 | ||
296 | static 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 | |||
213 | static void scrobbler_add_to_cache(const struct mp3entry *id) | 303 | static 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 ******************/ |
438 | static 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 | |||
445 | static 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 | |||
354 | void thread(void) | 451 | void 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) | |||
412 | void thread_quit(void) | 511 | void 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 */ |
427 | static bool exit_tsr(bool reenter) | 523 | static 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 | 569 | static int plugin_main(const void* parameter) | |
471 | int 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 | } |