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"
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"),