diff --git a/apps/SOURCES b/apps/SOURCES index f440104a19..444951bbcb 100644 --- a/apps/SOURCES +++ b/apps/SOURCES @@ -58,7 +58,6 @@ tree.c tagtree.c #endif filetree.c -scrobbler.c #ifdef IPOD_ACCESSORY_PROTOCOL iap/iap-core.c iap/iap-lingo0.c diff --git a/apps/lang/english.lang b/apps/lang/english.lang index c951028494..04bbf70a6f 100644 --- a/apps/lang/english.lang +++ b/apps/lang/english.lang @@ -1772,16 +1772,16 @@ id: LANG_AUDIOSCROBBLER - desc: "Last.fm Log" in the playback menu + desc: "Last.fm Logger" in Plugin/apps/scrobbler user: core - *: "Last.fm Log" + *: "Last.fm Logger" - *: "Last.fm Log" + *: "Last.fm Logger" - *: "Last.fm Log" + *: "Last.fm Logger" diff --git a/apps/main.c b/apps/main.c index 2f3b246210..dff9dc5778 100644 --- a/apps/main.c +++ b/apps/main.c @@ -70,7 +70,6 @@ #include "string.h" #include "splash.h" #include "eeprom_settings.h" -#include "scrobbler.h" #include "icon.h" #include "viewport.h" #include "skin_engine/skin_engine.h" @@ -373,9 +372,6 @@ static void init(void) playlist_init(); shortcuts_init(); - if (global_settings.audioscrobbler) - scrobbler_init(); - audio_init(); talk_announce_voice_invalid(); /* notify user w/ voice prompt if voice file invalid */ settings_apply_skins(); @@ -630,9 +626,6 @@ static void init(void) tree_mem_init(); filetype_init(); - if (global_settings.audioscrobbler) - scrobbler_init(); - shortcuts_init(); CHART(">audio_init"); diff --git a/apps/menus/playback_menu.c b/apps/menus/playback_menu.c index fe319d6027..881a4b5a99 100644 --- a/apps/menus/playback_menu.c +++ b/apps/menus/playback_menu.c @@ -31,7 +31,6 @@ #include "sound_menu.h" #include "kernel.h" #include "playlist.h" -#include "scrobbler.h" #include "audio.h" #include "cuesheet.h" #include "misc.h" @@ -150,26 +149,6 @@ MENUITEM_SETTING(spdif_enable, &global_settings.spdif_enable, NULL); MENUITEM_SETTING(next_folder, &global_settings.next_folder, NULL); MENUITEM_SETTING(constrain_next_folder, &global_settings.constrain_next_folder, NULL); -static int audioscrobbler_callback(int action, - const struct menu_item_ex *this_item, - struct gui_synclist *this_list) -{ - (void)this_item; - (void)this_list; - switch (action) - { - case ACTION_EXIT_MENUITEM: /* on exit */ - if (!scrobbler_is_enabled() && global_settings.audioscrobbler) - scrobbler_init(); - - if(scrobbler_is_enabled() && !global_settings.audioscrobbler) - scrobbler_shutdown(false); - break; - } - return action; -} -MENUITEM_SETTING(audioscrobbler, &global_settings.audioscrobbler, audioscrobbler_callback); - static int cuesheet_callback(int action, const struct menu_item_ex *this_item, @@ -242,7 +221,7 @@ MAKE_MENU(playback_settings,ID2P(LANG_PLAYBACK),0, #ifdef HAVE_SPDIF_POWER &spdif_enable, #endif - &next_folder, &constrain_next_folder, &audioscrobbler, &cuesheet + &next_folder, &constrain_next_folder, &cuesheet #ifdef HAVE_HEADPHONE_DETECTION ,&unplug_menu #endif diff --git a/apps/misc.c b/apps/misc.c index a4958a59ea..4d8c2e975a 100644 --- a/apps/misc.c +++ b/apps/misc.c @@ -58,7 +58,6 @@ #include "font.h" #include "splash.h" #include "tagcache.h" -#include "scrobbler.h" #include "sound.h" #include "playlist.h" #include "yesno.h" @@ -365,7 +364,6 @@ static bool clean_shutdown(void (*callback)(void *), void *parameter) #if defined(HAVE_RECORDING) audio_close_recording(); #endif - scrobbler_shutdown(true); system_flush(); #ifdef HAVE_EEPROM_SETTINGS diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES index 89aba0e32f..0ef3fe81a4 100644 --- a/apps/plugins/CATEGORIES +++ b/apps/plugins/CATEGORIES @@ -49,6 +49,7 @@ jpeg,viewers keybox,apps keyremap,apps lamp,apps +lastfm_scrobbler,apps logo,demos lrcplayer,apps lua,viewers diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES index d2f3c39d54..e3a10c9d15 100644 --- a/apps/plugins/SOURCES +++ b/apps/plugins/SOURCES @@ -12,6 +12,7 @@ dict.c jackpot.c keybox.c keyremap.c +lastfm_scrobbler.c logo.c lrcplayer.c mosaique.c diff --git a/apps/plugins/lastfm_scrobbler.c b/apps/plugins/lastfm_scrobbler.c new file mode 100644 index 0000000000..db75427895 --- /dev/null +++ b/apps/plugins/lastfm_scrobbler.c @@ -0,0 +1,584 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2006-2008 Robert Keevil + * Converted to Plugin + * Copyright (C) 2022 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. + * + ****************************************************************************/ +/* Scrobbler Plugin +Audioscrobbler spec at: +http://www.audioscrobbler.net/wiki/Portable_Player_Logging +*/ + +#include "plugin.h" + +#ifdef ROCKBOX_HAS_LOGF +#define logf rb->logf +#else +#define logf(...) do { } while(0) +#endif + + +/****************** constants ******************/ +#define EV_EXIT MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0xFF) +#define EV_OTHINSTANCE MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0xFE) +#define EV_STARTUP MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0x01) +#define EV_TRACKCHANGE MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0x02) +#define EV_TRACKFINISH MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0x03) + +#define SCROBBLER_VERSION "1.1" + +/* increment this on any code change that effects output */ +#define SCROBBLER_REVISION " $Revision$" + +#define SCROBBLER_MAX_CACHE 32 +/* longest entry I've had is 323, add a safety margin */ +#define SCROBBLER_CACHE_LEN 512 + +#define ITEM_HDR "#ARTIST #ALBUM #TITLE #TRACKNUM #LENGTH #RATING #TIMESTAMMP #MUSICBRAINZ_TRACKID\n" + +#if CONFIG_RTC +static time_t timestamp; +#define BASE_FILENAME HOME_DIR "/.scrobbler.log" +#define HDR_STR_TIMELESS +#define get_timestamp() ((long)timestamp) +#define record_timestamp() ((void)(timestamp = rb->mktime(rb->get_time()))) +#else /* !CONFIG_RTC */ +#define HDR_STR_TIMELESS " Timeless" +#define BASE_FILENAME ".scrobbler-timeless.log" +#define get_timestamp() (0l) +#define record_timestamp() ({}) +#endif /* CONFIG_RTC */ + +#define THREAD_STACK_SIZE 4*DEFAULT_STACK_SIZE + +#if (CONFIG_KEYPAD == IRIVER_H100_PAD) || \ + (CONFIG_KEYPAD == IRIVER_H300_PAD) +#define SCROBBLE_OFF BUTTON_OFF +#define SCROBBLE_OFF_TXT "STOP" +#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \ + (CONFIG_KEYPAD == IPOD_3G_PAD) || \ + (CONFIG_KEYPAD == IPOD_1G2G_PAD) +#define SCROBBLE_OFF BUTTON_MENU +#define SCROBBLE_OFF_TXT "MENU" +#elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD || \ + CONFIG_KEYPAD == AGPTEK_ROCKER_PAD +#define SCROBBLE_OFF BUTTON_POWER +#define SCROBBLE_OFF_TXT "POWER" +#elif (CONFIG_KEYPAD == SANSA_E200_PAD) || \ + (CONFIG_KEYPAD == SANSA_C200_PAD) || \ + (CONFIG_KEYPAD == SANSA_CLIP_PAD) || \ + (CONFIG_KEYPAD == SANSA_M200_PAD) +#define SCROBBLE_OFF BUTTON_POWER +#define SCROBBLE_OFF_TXT "POWER" +#elif (CONFIG_KEYPAD == SANSA_FUZE_PAD) +#define SCROBBLE_OFF BUTTON_HOME +#define SCROBBLE_OFF_TXT "HOME" +#elif (CONFIG_KEYPAD == IRIVER_H10_PAD || \ + CONFIG_KEYPAD == CREATIVE_ZENXFI3_PAD || \ + CONFIG_KEYPAD == SONY_NWZ_PAD || \ + CONFIG_KEYPAD == XDUOO_X3_PAD || \ + CONFIG_KEYPAD == IHIFI_770_PAD || \ + CONFIG_KEYPAD == IHIFI_800_PAD || \ + CONFIG_KEYPAD == XDUOO_X3II_PAD || \ + CONFIG_KEYPAD == XDUOO_X20_PAD || \ + CONFIG_KEYPAD == FIIO_M3K_LINUX_PAD || \ + CONFIG_KEYPAD == EROSQ_PAD) +#define SCROBBLE_OFF BUTTON_POWER +#define SCROBBLE_OFF_TXT "POWER" +#elif CONFIG_KEYPAD == GIGABEAT_PAD +#define SCROBBLE_OFF BUTTON_POWER +#define SCROBBLE_OFF_TXT "POWER" +#elif CONFIG_KEYPAD == GIGABEAT_S_PAD \ + || CONFIG_KEYPAD == SAMSUNG_YPR0_PAD \ + || CONFIG_KEYPAD == CREATIVE_ZEN_PAD +#define SCROBBLE_OFF BUTTON_BACK +#define SCROBBLE_OFF_TXT "BACK" +#elif CONFIG_KEYPAD == MROBE500_PAD +#define SCROBBLE_OFF BUTTON_POWER +#define SCROBBLE_OFF_TXT "POWER" +#elif CONFIG_KEYPAD == MROBE100_PAD +#define SCROBBLE_OFF BUTTON_POWER +#define SCROBBLE_OFF_TXT "POWER" +#elif CONFIG_KEYPAD == IAUDIO_M3_PAD +#define SCROBBLE_OFF BUTTON_REC +#define BATTERY_RC_OFF BUTTON_RC_REC +#define SCROBBLE_OFF_TXT "REC" +#elif CONFIG_KEYPAD == COWON_D2_PAD +#define SCROBBLE_OFF BUTTON_POWER +#define SCROBBLE_OFF_TXT "POWER" +#elif CONFIG_KEYPAD == CREATIVEZVM_PAD +#define SCROBBLE_OFF BUTTON_BACK +#define SCROBBLE_OFF_TXT "BACK" +#elif CONFIG_KEYPAD == PHILIPS_HDD1630_PAD +#define SCROBBLE_OFF BUTTON_POWER +#define SCROBBLE_OFF_TXT "POWER" +#elif CONFIG_KEYPAD == PHILIPS_HDD6330_PAD +#define SCROBBLE_OFF BUTTON_POWER +#define SCROBBLE_OFF_TXT "POWER" +#elif CONFIG_KEYPAD == PHILIPS_SA9200_PAD +#define SCROBBLE_OFF BUTTON_POWER +#define SCROBBLE_OFF_TXT "POWER" +#elif CONFIG_KEYPAD == ONDAVX747_PAD +#define SCROBBLE_OFF BUTTON_POWER +#define SCROBBLE_OFF_TXT "POWER" +#elif CONFIG_KEYPAD == ONDAVX777_PAD +#define SCROBBLE_OFF BUTTON_POWER +#define SCROBBLE_OFF_TXT "POWER" +#elif (CONFIG_KEYPAD == SAMSUNG_YH820_PAD) || \ + (CONFIG_KEYPAD == SAMSUNG_YH92X_PAD) +#define SCROBBLE_OFF BUTTON_RIGHT +#define SCROBBLE_OFF_TXT "RIGHT" +#elif CONFIG_KEYPAD == PBELL_VIBE500_PAD +#define SCROBBLE_OFF BUTTON_REC +#define SCROBBLE_OFF_TXT "REC" +#elif CONFIG_KEYPAD == MPIO_HD200_PAD +#define SCROBBLE_OFF BUTTON_REC +#define SCROBBLE_OFF_TXT "REC" +#elif CONFIG_KEYPAD == MPIO_HD300_PAD +#define SCROBBLE_OFF BUTTON_REC +#define SCROBBLE_OFF_TXT "REC" +#elif CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD +#define SCROBBLE_OFF BUTTON_POWER +#define SCROBBLE_OFF_TXT "POWER" +#elif CONFIG_KEYPAD == SANSA_CONNECT_PAD +#define SCROBBLE_OFF BUTTON_POWER +#define SCROBBLE_OFF_TXT "POWER" +#elif (CONFIG_KEYPAD == HM60X_PAD) || (CONFIG_KEYPAD == HM801_PAD) +#define SCROBBLE_OFF BUTTON_POWER +#define SCROBBLE_OFF_TXT "POWER" +#elif CONFIG_KEYPAD == DX50_PAD +#define SCROBBLE_OFF BUTTON_POWER_LONG +#define SCROBBLE_OFF_TXT "Power Long" +#elif CONFIG_KEYPAD == CREATIVE_ZENXFI2_PAD +#define SCROBBLE_OFF BUTTON_POWER +#define SCROBBLE_OFF_TXT "Power" +#elif CONFIG_KEYPAD == FIIO_M3K_PAD +#define SCROBBLE_OFF BUTTON_POWER +#define SCROBBLE_OFF_TXT "Power" +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD +/* use touchscreen */ +#else +#error "No keymap defined!" +#endif +#if defined(HAVE_TOUCHSCREEN) +#ifndef SCROBBLE_OFF +#define SCROBBLE_OFF BUTTON_TOPLEFT +#endif +#ifndef SCROBBLE_OFF_TXT +#define SCROBBLE_OFF_TXT "TOPLEFT" +#endif +#endif +/****************** prototypes ******************/ +int plugin_main(const void* parameter); /* main loop */ +enum plugin_status plugin_start(const void* parameter); /* entry */ + +/****************** globals ******************/ +unsigned char **language_strings; /* for use with str() macro; must be init */ +/* communication to the worker thread */ +static struct +{ + bool exiting; /* signal to the thread that we want to exit */ + unsigned int id; /* worker thread id */ + struct event_queue queue; /* thread event queue */ + long stack[THREAD_STACK_SIZE / sizeof(long)]; +} gThread; + +static struct +{ + char *buf; + int pos; + size_t size; + bool pending; + bool force_flush; +} gCache; + +/****************** helper fuctions ******************/ + +int scrobbler_init(void) +{ + gCache.buf = rb->plugin_get_buffer(&gCache.size); + + size_t reqsz = SCROBBLER_MAX_CACHE*SCROBBLER_CACHE_LEN; + gCache.size = PLUGIN_BUFFER_SIZE - rb->plugin_reserve_buffer(reqsz); + + if (gCache.size < reqsz) + { + logf("SCROBBLER: OOM , %ld < req:%ld", gCache.size, reqsz); + return -1; + } + + gCache.pos = 0; + gCache.pending = false; + gCache.force_flush = true; + logf("Scrobbler Initialized"); + return 1; +} + +static void get_scrobbler_filename(char *path, size_t size) +{ + int used; + + used = rb->snprintf(path, size, "/%s", BASE_FILENAME); + + if (used >= (int)size) + { + logf("%s: not enough buffer space for log file", __func__); + rb->memset(path, 0, size); + } +} + +static void scrobbler_write_cache(void) +{ + int i; + int fd; + logf("%s", __func__); + char scrobbler_file[MAX_PATH]; + get_scrobbler_filename(scrobbler_file, sizeof(scrobbler_file)); + + /* If the file doesn't exist, create it. + Check at each write since file may be deleted at any time */ + if(!rb->file_exists(scrobbler_file)) + { + fd = rb->open(scrobbler_file, O_RDWR | O_CREAT, 0666); + if(fd >= 0) + { + rb->fdprintf(fd, "#AUDIOSCROBBLER/" SCROBBLER_VERSION "\n" + "#TZ/UNKNOWN\n" "#CLIENT/Rockbox " + TARGET_NAME SCROBBLER_REVISION + HDR_STR_TIMELESS "\n"); + rb->fdprintf(fd, ITEM_HDR); + + rb->close(fd); + } + else + { + logf("SCROBBLER: cannot create log file (%s)", scrobbler_file); + } + } + + /* write the cache entries */ + fd = rb->open(scrobbler_file, O_WRONLY | O_APPEND); + if(fd >= 0) + { + logf("SCROBBLER: writing %d entries", gCache.pos); + /* copy data to temporary storage in case data moves during I/O */ + char temp_buf[SCROBBLER_CACHE_LEN]; + for ( i=0; i < gCache.pos; i++ ) + { + logf("SCROBBLER: write %d", i); + char* scrobbler_buf = gCache.buf; + ssize_t len = rb->strlcpy(temp_buf, scrobbler_buf+(SCROBBLER_CACHE_LEN*i), + sizeof(temp_buf)); + if (rb->write(fd, temp_buf, len) != len) + break; + } + rb->close(fd); + } + else + { + logf("SCROBBLER: error writing file"); + } + + /* clear even if unsuccessful - don't want to overflow the buffer */ + gCache.pos = 0; +} + +static void scrobbler_flush_callback(void) +{ + (void) gCache.force_flush; + if(gCache.pos <= 0) + return; +#if (CONFIG_STORAGE & STORAGE_ATA) + else +#else + if ((gCache.pos >= SCROBBLER_MAX_CACHE / 2) || gCache.force_flush == true) +#endif + { + gCache.force_flush = false; + logf("%s", __func__); + scrobbler_write_cache(); + } +} + +static void scrobbler_add_to_cache(const struct mp3entry *id) +{ + if ( gCache.pos >= SCROBBLER_MAX_CACHE ) + scrobbler_write_cache(); + + char rating = 'S'; /* Skipped */ + char* scrobbler_buf = gCache.buf; + + logf("SCROBBLER: add_to_cache[%d]", gCache.pos); + + if (id->elapsed > id->length / 2) + rating = 'L'; /* Listened */ + + char tracknum[11] = { "" }; + + if (id->tracknum > 0) + rb->snprintf(tracknum, sizeof (tracknum), "%d", id->tracknum); + + int ret = rb->snprintf(&scrobbler_buf[(SCROBBLER_CACHE_LEN*gCache.pos)], + SCROBBLER_CACHE_LEN, + "%s\t%s\t%s\t%s\t%d\t%c\t%ld\t%s\n", + id->artist, + id->album ?: "", + id->title, + tracknum, + (int)(id->length / 1000), + rating, + get_timestamp(), + id->mb_track_id ?id->mb_track_id: ""); + + if ( ret >= SCROBBLER_CACHE_LEN ) + { + logf("SCROBBLER: entry too long:"); + logf("SCROBBLER: %s", id->path); + } + else + { + logf("Added %s", scrobbler_buf); + gCache.pos++; + rb->register_storage_idle_func(scrobbler_flush_callback); + } + +} + +static void scrobbler_flush_cache(void) +{ + logf("%s", __func__); + /* Add any pending entries to the cache */ + if (gCache.pending) + { + gCache.pending = false; + if (rb->audio_status()) + scrobbler_add_to_cache(rb->audio_current_track()); + } + + /* Write the cache to disk if needed */ + if (gCache.pos > 0) + { + scrobbler_write_cache(); + } +} + +static void scrobbler_change_event(unsigned short id, void *ev_data) +{ + (void)id; + logf("%s", __func__); + struct mp3entry *id3 = ((struct track_event *)ev_data)->id3; + + /* check if track was resumed > %50 played ( likely got saved ) + check for blank artist or track name */ + if ((id3->elapsed > id3->length / 2) || !id3->artist || !id3->title) + { + gCache.pending = false; + logf("SCROBBLER: skipping file %s", id3->path); + } + else + { + logf("SCROBBLER: add pending %s",id3->path); + record_timestamp(); + gCache.pending = true; + } +} +#ifdef ROCKBOX_HAS_LOGF +static const char* track_event_info(struct track_event* te) +{ + + static const char *strflags[] = {"TEF_NONE", "TEF_CURRENT", + "TEF_AUTOSKIP", "TEF_CUR|ASKIP", + "TEF_REWIND", "TEF_CUR|REW", + "TEF_ASKIP|REW", "TEF_CUR|ASKIP|REW"}; +/*TEF_NONE = 0x0, no flags are set +* TEF_CURRENT = 0x1, event is for the current track +* TEF_AUTO_SKIP = 0x2, event is sent in context of auto skip +* TEF_REWIND = 0x4, interpret as rewind, id3->elapsed is the + position before the seek back to 0 +*/ + logf("flag %d", te->flags); + return strflags[te->flags&0x7]; +} + +#endif +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__); + } + } + + +} + +/****************** main thread + helper ******************/ +void thread(void) +{ + bool in_usb = false; + + struct queue_event ev; + while (!gThread.exiting) + { + rb->queue_wait(&gThread.queue, &ev); + + switch (ev.id) + { + case SYS_USB_CONNECTED: + scrobbler_flush_cache(); + rb->usb_acknowledge(SYS_USB_CONNECTED_ACK); + in_usb = true; + break; + case SYS_USB_DISCONNECTED: + in_usb = false; + /*fall through*/ + case EV_STARTUP: + rb->beep_play(1500, 100, 1000); + break; + case SYS_POWEROFF: + gCache.force_flush = true; + /*fall through*/ + case EV_EXIT: + rb->unregister_storage_idle_func(scrobbler_flush_callback, !in_usb); + return; + case EV_OTHINSTANCE: + scrobbler_flush_cache(); + rb->splashf(HZ * 2, "%s Cache Flushed", str(LANG_AUDIOSCROBBLER)); + break; + default: + logf("default %ld", ev.id); + break; + } + } +} + +void thread_create(void) +{ + /* put the thread's queue in the bcast list */ + rb->queue_init(&gThread.queue, true); + gThread.id = rb->create_thread(thread, gThread.stack, sizeof(gThread.stack), + 0, "Last.Fm_TSR" + IF_PRIO(, PRIORITY_BACKGROUND) + IF_COP(, CPU)); + rb->queue_post(&gThread.queue, EV_STARTUP, 0); + rb->yield(); +} + +void thread_quit(void) +{ + if (!gThread.exiting) { + 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) +{ + logf("%s", __func__); + bool is_exit = false; + int button; + if (reenter) + { + logf(" reenter other instance "); + rb->queue_post(&gThread.queue, EV_OTHINSTANCE, 0); + return false; /* dont let it start again */ + } + rb->lcd_clear_display(); + rb->lcd_puts_scroll(0, 0, "Scrobbler is currently running."); + rb->lcd_puts_scroll(0, 1, "Press " SCROBBLE_OFF_TXT " to exit"); + rb->lcd_puts_scroll(0, 2, "Anything else will resume"); + + rb->lcd_update(); + rb->button_clear_queue(); + while (1) + { + button = rb->button_get(true); + if (IS_SYSEVENT(button)) + continue; + if (button == SCROBBLE_OFF) + { + 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); + is_exit = true; + } + else is_exit = false; + + break; + } + FOR_NB_SCREENS(idx) + rb->screens[idx]->scroll_stop(); + + if (is_exit) + thread_quit(); + + return is_exit; +} + +/****************** main ******************/ + +int plugin_main(const void* parameter) +{ + (void)parameter; + + rb->memset(&gThread, 0, sizeof(gThread)); + 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; +} + +/***************** Plugin Entry Point *****************/ + +enum plugin_status plugin_start(const void* parameter) +{ + /* now go ahead and have fun! */ + if (rb->usb_inserted() == true) + return PLUGIN_USB_CONNECTED; + language_strings = rb->language_strings; + if (scrobbler_init() < 0) + return PLUGIN_ERROR; + int ret = plugin_main(parameter); + return (ret==0) ? PLUGIN_OK : PLUGIN_ERROR; +} diff --git a/apps/scrobbler.c b/apps/scrobbler.c deleted file mode 100644 index f5ccf4a61c..0000000000 --- a/apps/scrobbler.c +++ /dev/null @@ -1,287 +0,0 @@ -/*************************************************************************** - * __________ __ ___. - * Open \______ \ ____ ____ | | _\_ |__ _______ ___ - * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / - * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < - * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ - * \/ \/ \/ \/ \/ - * $Id$ - * - * Copyright (C) 2006-2008 Robert Keevil - * - * 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. - * - ****************************************************************************/ -/* -Audioscrobbler spec at: -http://www.audioscrobbler.net/wiki/Portable_Player_Logging -*/ - -#include -#include -#include "file.h" -#include "logf.h" -#include "metadata.h" -#include "kernel.h" -#include "audio.h" -#include "core_alloc.h" -#include "rbpaths.h" -#include "ata_idle_notify.h" -#include "pathfuncs.h" -#include "appevents.h" -#include "string-extra.h" -#if CONFIG_RTC -#include "time.h" -#include "timefuncs.h" -#endif - -#include "scrobbler.h" - -#define SCROBBLER_VERSION "1.1" - -/* increment this on any code change that effects output */ -#define SCROBBLER_REVISION " $Revision$" - -#define SCROBBLER_MAX_CACHE 32 -/* longest entry I've had is 323, add a safety margin */ -#define SCROBBLER_CACHE_LEN 512 - -static bool scrobbler_initialised = false; -static int scrobbler_cache = 0; -static int cache_pos = 0; -static bool pending = false; -#if CONFIG_RTC -static time_t timestamp; -#define BASE_FILENAME HOME_DIR "/.scrobbler.log" -#define HDR_STR_TIMELESS -#define get_timestamp() ((long)timestamp) -#define record_timestamp() ((void)(timestamp = mktime(get_time()))) -#else /* !CONFIG_RTC */ -#define HDR_STR_TIMELESS " Timeless" -#define BASE_FILENAME ".scrobbler-timeless.log" -#define get_timestamp() (0l) -#define record_timestamp() ({}) -#endif /* CONFIG_RTC */ - -static void get_scrobbler_filename(char *path, size_t size) -{ - int used; - - used = snprintf(path, size, "/%s", BASE_FILENAME); - - if (used >= (int)size) - { - logf("SCROBBLER: not enough buffer space for log file"); - memset(path, 0, size); - } -} - -static void write_cache(void) -{ - int i; - int fd; - - char scrobbler_file[MAX_PATH]; - get_scrobbler_filename(scrobbler_file, MAX_PATH); - - /* If the file doesn't exist, create it. - Check at each write since file may be deleted at any time */ - if(!file_exists(scrobbler_file)) - { - fd = open(scrobbler_file, O_RDWR | O_CREAT, 0666); - if(fd >= 0) - { - fdprintf(fd, "#AUDIOSCROBBLER/" SCROBBLER_VERSION "\n" - "#TZ/UNKNOWN\n" "#CLIENT/Rockbox " - TARGET_NAME SCROBBLER_REVISION - HDR_STR_TIMELESS "\n"); - - close(fd); - } - else - { - logf("SCROBBLER: cannot create log file (%s)", scrobbler_file); - } - } - - /* write the cache entries */ - fd = open(scrobbler_file, O_WRONLY | O_APPEND); - if(fd >= 0) - { - logf("SCROBBLER: writing %d entries", cache_pos); - /* copy data to temporary storage in case data moves during I/O */ - char temp_buf[SCROBBLER_CACHE_LEN]; - for ( i=0; i < cache_pos; i++ ) - { - logf("SCROBBLER: write %d", i); - char* scrobbler_buf = core_get_data(scrobbler_cache); - ssize_t len = strlcpy(temp_buf, scrobbler_buf+(SCROBBLER_CACHE_LEN*i), - sizeof(temp_buf)); - if (write(fd, temp_buf, len) != len) - break; - } - close(fd); - } - else - { - logf("SCROBBLER: error writing file"); - } - - /* clear even if unsuccessful - don't want to overflow the buffer */ - cache_pos = 0; -} - -static void scrobbler_flush_callback(void) -{ - if (scrobbler_initialised && cache_pos) - write_cache(); -} - -static void add_to_cache(const struct mp3entry *id) -{ - if ( cache_pos >= SCROBBLER_MAX_CACHE ) - write_cache(); - - char rating = 'S'; /* Skipped */ - char* scrobbler_buf = core_get_data(scrobbler_cache); - - logf("SCROBBLER: add_to_cache[%d]", cache_pos); - - if (id->elapsed > id->length / 2) - rating = 'L'; /* Listened */ - - char tracknum[11] = { "" }; - - if (id->tracknum > 0) - snprintf(tracknum, sizeof (tracknum), "%d", id->tracknum); - - int ret = snprintf(scrobbler_buf+(SCROBBLER_CACHE_LEN*cache_pos), - SCROBBLER_CACHE_LEN, - "%s\t%s\t%s\t%s\t%d\t%c\t%ld\t%s\n", - id->artist, - id->album ?: "", - id->title, - tracknum, - (int)(id->length / 1000), - rating, - get_timestamp(), - id->mb_track_id ?: ""); - - if ( ret >= SCROBBLER_CACHE_LEN ) - { - logf("SCROBBLER: entry too long:"); - logf("SCROBBLER: %s", id->path); - } - else - { - cache_pos++; - register_storage_idle_func(scrobbler_flush_callback); - } - -} - -static void scrobbler_change_event(unsigned short id, void *ev_data) -{ - (void)id; - struct mp3entry *id3 = ((struct track_event *)ev_data)->id3; - - /* check if track was resumed > %50 played - check for blank artist or track name */ - if (id3->elapsed > id3->length / 2 || !id3->artist || !id3->title) - { - pending = false; - logf("SCROBBLER: skipping file %s", id3->path); - } - else - { - logf("SCROBBLER: add pending"); - record_timestamp(); - pending = true; - } -} - -static void scrobbler_finish_event(unsigned short id, void *data) -{ - (void)id; - struct track_event *te = (struct track_event *)data; - - /* add entry using the currently ending track */ - if (pending && (te->flags & TEF_CURRENT) - && !(te->flags & TEF_REWIND) - ) - { - pending = false; - add_to_cache(te->id3); - } -} - -int scrobbler_init(void) -{ - if (scrobbler_initialised) - return 1; - - scrobbler_cache = core_alloc("scrobbler", - SCROBBLER_MAX_CACHE*SCROBBLER_CACHE_LEN); - - if (scrobbler_cache <= 0) - { - logf("SCROOBLER: OOM"); - return -1; - } - - cache_pos = 0; - pending = false; - - scrobbler_initialised = true; - - add_event(PLAYBACK_EVENT_TRACK_CHANGE, scrobbler_change_event); - add_event(PLAYBACK_EVENT_TRACK_FINISH, scrobbler_finish_event); - - return 1; -} - -static void scrobbler_flush_cache(void) -{ - /* Add any pending entries to the cache */ - if (pending) - { - pending = false; - if (audio_status()) - add_to_cache(audio_current_track()); - } - - /* Write the cache to disk if needed */ - if (cache_pos) - write_cache(); -} - -void scrobbler_shutdown(bool poweroff) -{ - if (!scrobbler_initialised) - return; - - remove_event(PLAYBACK_EVENT_TRACK_CHANGE, scrobbler_change_event); - remove_event(PLAYBACK_EVENT_TRACK_FINISH, scrobbler_finish_event); - - scrobbler_flush_cache(); - - if (!poweroff) - { - /* get rid of the buffer */ - core_free(scrobbler_cache); - scrobbler_cache = 0; - } - - scrobbler_initialised = false; -} - -bool scrobbler_is_enabled(void) -{ - return scrobbler_initialised; -} diff --git a/apps/scrobbler.h b/apps/scrobbler.h deleted file mode 100644 index a3d1b361df..0000000000 --- a/apps/scrobbler.h +++ /dev/null @@ -1,29 +0,0 @@ -/*************************************************************************** - * __________ __ ___. - * Open \______ \ ____ ____ | | _\_ |__ _______ ___ - * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / - * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < - * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ - * \/ \/ \/ \/ \/ - * $Id$ - * - * Copyright (C) 2006 Robert Keevil - * - * 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. - * - ****************************************************************************/ - -#ifndef __SCROBBLER_H__ -#define __SCROBBLER_H__ - -int scrobbler_init(void); -void scrobbler_shutdown(bool poweroff); -bool scrobbler_is_enabled(void); - -#endif /* __SCROBBLER_H__ */ diff --git a/apps/settings.h b/apps/settings.h index 8ff006d682..53d7d35cae 100644 --- a/apps/settings.h +++ b/apps/settings.h @@ -503,7 +503,6 @@ struct user_settings int single_mode; /* single mode - stop after every track, album, album artist, artist, composer, work, or genre */ bool party_mode; /* party mode - unstoppable music */ - bool audioscrobbler; /* Audioscrobbler logging */ bool cuesheet; bool car_adapter_mode; /* 0=off 1=on */ int car_adapter_mode_delay; /* delay before resume, in seconds*/ diff --git a/apps/settings_list.c b/apps/settings_list.c index aa2ebbf883..3a731bac2c 100644 --- a/apps/settings_list.c +++ b/apps/settings_list.c @@ -1832,8 +1832,6 @@ const struct settings_list settings[] = { ID2P(LANG_FM_ITALY), ID2P(LANG_FM_OTHER)), #endif - OFFON_SETTING(F_BANFROMQS, audioscrobbler, LANG_AUDIOSCROBBLER, false, - "Last.fm Logging", NULL), #if CONFIG_TUNER TEXT_SETTING(0, fmr_file, "fmr", "-", FMPRESET_PATH "/", ".fmr"),