From 1d392613389d36e41edc56b9dff166c8b0172d0a Mon Sep 17 00:00:00 2001 From: William Wilgus Date: Fri, 27 May 2022 22:04:25 -0400 Subject: [PATCH] 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 --- apps/plugins/lastfm_scrobbler.c | 179 +++++++++++++++++++++++++------- 1 file 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 */ #include "plugin.h" +#include "lib/configfile.h" #ifndef UNTAGGED #define UNTAGGED "" @@ -55,6 +56,9 @@ http://www.audioscrobbler.net/wiki/Portable_Player_Logging #define ITEM_HDR "#ARTIST #ALBUM #TITLE #TRACKNUM #LENGTH #RATING #TIMESTAMP #MUSICBRAINZ_TRACKID\n" +#define CFG_FILE "/lastfm_scrobbler.cfg" +#define CFG_VER 1 + #if CONFIG_RTC static time_t timestamp; #define BASE_FILENAME HOME_DIR "/.scrobbler.log" @@ -71,7 +75,6 @@ static time_t timestamp; #define THREAD_STACK_SIZE 4*DEFAULT_STACK_SIZE /****************** prototypes ******************/ -int plugin_main(const void* parameter); /* main loop */ enum plugin_status plugin_start(const void* parameter); /* entry */ /****************** globals ******************/ @@ -95,6 +98,86 @@ static struct bool force_flush; } gCache; +static struct +{ + int savepct; + bool playback; + bool verbose; +} gConfig; + +static struct configdata config[] = +{ + {TYPE_INT, 0, 100, { .int_p = &gConfig.savepct }, "SavePct", NULL}, + {TYPE_BOOL, 0, 1, { .bool_p = &gConfig.playback }, "Playback", NULL}, + {TYPE_BOOL, 0, 1, { .bool_p = &gConfig.verbose }, "Verbose", NULL}, +}; +const int gCfg_sz = sizeof(config)/sizeof(*config); +/****************** config functions *****************/ +static void config_set_defaults(void) +{ + gConfig.savepct = 50; + gConfig.playback = false; + gConfig.verbose = true; +} + +static int config_settings_menu(void) +{ + 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_RESUME_PLAYBACK), + "Save Threshold", + "Verbose", + 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(str(LANG_RESUME_PLAYBACK), &gConfig.playback); + break; + case 1: + rb->set_int("Save Threshold", "%", UNIT_PERCENT, + &gConfig.savepct, NULL, 10, 0, 100, NULL ); + break; + case 2: + rb->set_bool("Verbose", &gConfig.verbose); + break; + case 3: /*sep*/ + continue; + case 4: + return -1; + break; + case 5: + { + int res = configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER); + if (res >= 0) + { + logf("Scrobbler cfg saved %s %d bytes", CFG_FILE, gCfg_sz); + return PLUGIN_OK; + } + logf("Scrobbler 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; +} + /****************** helper fuctions ******************/ int scrobbler_init(void) @@ -210,6 +293,13 @@ static inline char* str_chk_valid(char *s, char *alt) return (s != NULL ? s : alt); } +static unsigned long scrobbler_get_threshold(unsigned long length) +{ + /* length is assumed to be in miliseconds */ + return length / 100 * gConfig.savepct; + +} + static void scrobbler_add_to_cache(const struct mp3entry *id) { static uint32_t last_crc = 0; @@ -223,7 +313,7 @@ static void scrobbler_add_to_cache(const struct mp3entry *id) logf("SCROBBLER: add_to_cache[%d]", gCache.pos); - if (id->elapsed > id->length / 2) + if (id->elapsed >= scrobbler_get_threshold(id->length)) rating = 'L'; /* Listened */ char tracknum[11] = { "" }; @@ -294,9 +384,9 @@ static void scrobbler_change_event(unsigned short id, void *ev_data) logf("%s", __func__); struct mp3entry *id3 = ((struct track_event *)ev_data)->id3; - /* check if track was resumed > %50 played ( likely got saved ) + /* check if track was resumed > %threshold played ( likely got saved ) check for blank artist or track name */ - if ((id3->elapsed > id3->length / 2) + if ((id3->elapsed > scrobbler_get_threshold(id3->length)) || (!id3->artist && !id3->albumartist) || !id3->title) { gCache.pending = false; @@ -332,25 +422,32 @@ static void scrobbler_finish_event(unsigned short id, void *ev_data) { (void)id; struct track_event *te = ((struct track_event *)ev_data); - struct mp3entry *id3 = te->id3; logf("%s %s %s", __func__, gCache.pending?"True":"False", track_event_info(te)); /* add entry using the currently ending track */ if (gCache.pending && (te->flags & TEF_CURRENT) && !(te->flags & TEF_REWIND)) { gCache.pending = false; - if (id3->elapsed*2 >= id3->length) - scrobbler_add_to_cache(te->id3); - else - { - logf("%s Discarding < 50%% played", __func__); - } + scrobbler_add_to_cache(te->id3); } } -/****************** main thread + helper ******************/ +/****************** main thread + helpers ******************/ +static void events_unregister(void) +{ + /* we don't want any more events */ + rb->remove_event(PLAYBACK_EVENT_TRACK_CHANGE, scrobbler_change_event); + rb->remove_event(PLAYBACK_EVENT_TRACK_FINISH, scrobbler_finish_event); +} + +static void events_register(void) +{ + rb->add_event(PLAYBACK_EVENT_TRACK_CHANGE, scrobbler_change_event); + rb->add_event(PLAYBACK_EVENT_TRACK_FINISH, scrobbler_finish_event); +} + void thread(void) { bool in_usb = false; @@ -371,6 +468,7 @@ void thread(void) in_usb = false; /*fall through*/ case EV_STARTUP: + events_register(); rb->beep_play(1500, 100, 1000); break; case SYS_POWEROFF: @@ -384,6 +482,7 @@ void thread(void) if (!in_usb) scrobbler_flush_cache(); #endif + events_unregister(); return; case EV_FLUSHCACHE: scrobbler_flush_cache(); @@ -412,22 +511,19 @@ void thread_create(void) void thread_quit(void) { if (!gThread.exiting) { + gThread.exiting = true; rb->queue_post(&gThread.queue, EV_EXIT, 0); rb->thread_wait(gThread.id); - /* we don't want any more events */ - rb->remove_event(PLAYBACK_EVENT_TRACK_CHANGE, scrobbler_change_event); - rb->remove_event(PLAYBACK_EVENT_TRACK_FINISH, scrobbler_finish_event); /* remove the thread's queue from the broadcast list */ rb->queue_delete(&gThread.queue); - gThread.exiting = true; } } /* callback to end the TSR plugin, called before a new one gets loaded */ static bool exit_tsr(bool reenter) { - MENUITEM_STRINGLIST(menu, ID2P(LANG_AUDIOSCROBBLER), NULL, - "Flush Cache", "Quit", "Back"); + MENUITEM_STRINGLIST(menu, ID2P(LANG_AUDIOSCROBBLER), NULL, ID2P(LANG_SETTINGS), + "Flush Cache", "Exit Plugin", ID2P(LANG_BACK)); const struct text_message quit_prompt = { (const char*[]){ ID2P(LANG_AUDIOSCROBBLER), @@ -437,22 +533,25 @@ static bool exit_tsr(bool reenter) while(true) { - int result = reenter ? rb->do_menu(&menu, NULL, NULL, false) : 1; + int result = reenter ? rb->do_menu(&menu, NULL, NULL, false) : 2; switch(result) { - case 0: /* flush cache */ + case 0: /* settings */ + config_settings_menu(); + break; + case 1: /* flush cache */ rb->queue_send(&gThread.queue, EV_FLUSHCACHE, 0); - rb->splashf(2*HZ, "%s Cache Flushed", str(LANG_AUDIOSCROBBLER)); + if (gConfig.verbose) + rb->splashf(2*HZ, "%s Cache Flushed", str(LANG_AUDIOSCROBBLER)); break; - case 1: /* quit */ + case 2: /* exit plugin - quit */ if(rb->gui_syncyesno_run(&quit_prompt, NULL, NULL) == YESNO_YES) { - rb->queue_post(&gThread.queue, EV_EXIT, 0); - rb->thread_wait(gThread.id); - /* remove the thread's queue from the broadcast list */ - rb->queue_delete(&gThread.queue); - return true; + thread_quit(); + if (reenter) + rb->plugin_tsr(NULL); /* remove TSR cb */ + return !reenter; } if(!reenter) @@ -460,29 +559,29 @@ static bool exit_tsr(bool reenter) break; - case 2: /* back to menu */ + case 3: /* back to menu */ return false; } } } /****************** main ******************/ - -int plugin_main(const void* parameter) +static int plugin_main(const void* parameter) { (void)parameter; rb->memset(&gThread, 0, sizeof(gThread)); - rb->splashf(HZ / 2, "%s Started",str(LANG_AUDIOSCROBBLER)); + if (gConfig.verbose) + rb->splashf(HZ / 2, "%s Started",str(LANG_AUDIOSCROBBLER)); logf("%s: %s Started", __func__, str(LANG_AUDIOSCROBBLER)); rb->plugin_tsr(exit_tsr); /* stay resident */ - rb->add_event(PLAYBACK_EVENT_TRACK_CHANGE, scrobbler_change_event); - rb->add_event(PLAYBACK_EVENT_TRACK_FINISH, scrobbler_finish_event); thread_create(); - return 0; + if (gConfig.playback) + return PLUGIN_GOTO_WPS; + return PLUGIN_OK; } /***************** Plugin Entry Point *****************/ @@ -495,6 +594,16 @@ enum plugin_status plugin_start(const void* parameter) language_strings = rb->language_strings; if (scrobbler_init() < 0) return PLUGIN_ERROR; + + 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)); + } + int ret = plugin_main(parameter); - return (ret==0) ? PLUGIN_OK : PLUGIN_ERROR; + return ret; }