LastFm remove scrobbler from core make a TSR plugin WIP

remove scrobbler from core make it a plugin

Change-Id: I606810eba7d570dfb332789aed913c6f8adc7fb7
This commit is contained in:
William Wilgus 2022-03-25 09:33:10 -04:00
parent 8eb4689ab1
commit fd15ea25d3
12 changed files with 591 additions and 355 deletions

View file

@ -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

View file

@ -1772,16 +1772,16 @@
</phrase>
<phrase>
id: LANG_AUDIOSCROBBLER
desc: "Last.fm Log" in the playback menu
desc: "Last.fm Logger" in Plugin/apps/scrobbler
user: core
<source>
*: "Last.fm Log"
*: "Last.fm Logger"
</source>
<dest>
*: "Last.fm Log"
*: "Last.fm Logger"
</dest>
<voice>
*: "Last.fm Log"
*: "Last.fm Logger"
</voice>
</phrase>
<phrase>

View file

@ -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");

View file

@ -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

View file

@ -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

View file

@ -49,6 +49,7 @@ jpeg,viewers
keybox,apps
keyremap,apps
lamp,apps
lastfm_scrobbler,apps
logo,demos
lrcplayer,apps
lua,viewers

View file

@ -12,6 +12,7 @@ dict.c
jackpot.c
keybox.c
keyremap.c
lastfm_scrobbler.c
logo.c
lrcplayer.c
mosaique.c

View file

@ -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;
}

View file

@ -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 <stdio.h>
#include <config.h>
#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;
}

View file

@ -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__ */

View file

@ -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*/

View file

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