summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWilliam Wilgus <wilgus.william@gmail.com>2022-03-25 09:33:10 -0400
committerWilliam Wilgus <wilgus.william@gmail.com>2022-03-26 02:50:11 -0400
commitfd15ea25d3c22992f62c4d64d2ee891461d84d74 (patch)
treee2ed0b9eada8f109dd00183113f976b2828b31fc
parent8eb4689ab1c6fbe1af66293b33c92f4f8dd2cb6c (diff)
downloadrockbox-fd15ea25d3c22992f62c4d64d2ee891461d84d74.tar.gz
rockbox-fd15ea25d3c22992f62c4d64d2ee891461d84d74.zip
LastFm remove scrobbler from core make a TSR plugin WIP
remove scrobbler from core make it a plugin Change-Id: I606810eba7d570dfb332789aed913c6f8adc7fb7
-rw-r--r--apps/SOURCES1
-rw-r--r--apps/lang/english.lang8
-rw-r--r--apps/main.c7
-rw-r--r--apps/menus/playback_menu.c23
-rw-r--r--apps/misc.c2
-rw-r--r--apps/plugins/CATEGORIES1
-rw-r--r--apps/plugins/SOURCES1
-rw-r--r--apps/plugins/lastfm_scrobbler.c584
-rw-r--r--apps/scrobbler.c287
-rw-r--r--apps/scrobbler.h29
-rw-r--r--apps/settings.h1
-rw-r--r--apps/settings_list.c2
12 files changed, 591 insertions, 355 deletions
diff --git a/apps/SOURCES b/apps/SOURCES
index f440104a19..444951bbcb 100644
--- a/apps/SOURCES
+++ b/apps/SOURCES
@@ -58,7 +58,6 @@ tree.c
58tagtree.c 58tagtree.c
59#endif 59#endif
60filetree.c 60filetree.c
61scrobbler.c
62#ifdef IPOD_ACCESSORY_PROTOCOL 61#ifdef IPOD_ACCESSORY_PROTOCOL
63iap/iap-core.c 62iap/iap-core.c
64iap/iap-lingo0.c 63iap/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 @@
1772</phrase> 1772</phrase>
1773<phrase> 1773<phrase>
1774 id: LANG_AUDIOSCROBBLER 1774 id: LANG_AUDIOSCROBBLER
1775 desc: "Last.fm Log" in the playback menu 1775 desc: "Last.fm Logger" in Plugin/apps/scrobbler
1776 user: core 1776 user: core
1777 <source> 1777 <source>
1778 *: "Last.fm Log" 1778 *: "Last.fm Logger"
1779 </source> 1779 </source>
1780 <dest> 1780 <dest>
1781 *: "Last.fm Log" 1781 *: "Last.fm Logger"
1782 </dest> 1782 </dest>
1783 <voice> 1783 <voice>
1784 *: "Last.fm Log" 1784 *: "Last.fm Logger"
1785 </voice> 1785 </voice>
1786</phrase> 1786</phrase>
1787<phrase> 1787<phrase>
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 @@
70#include "string.h" 70#include "string.h"
71#include "splash.h" 71#include "splash.h"
72#include "eeprom_settings.h" 72#include "eeprom_settings.h"
73#include "scrobbler.h"
74#include "icon.h" 73#include "icon.h"
75#include "viewport.h" 74#include "viewport.h"
76#include "skin_engine/skin_engine.h" 75#include "skin_engine/skin_engine.h"
@@ -373,9 +372,6 @@ static void init(void)
373 playlist_init(); 372 playlist_init();
374 shortcuts_init(); 373 shortcuts_init();
375 374
376 if (global_settings.audioscrobbler)
377 scrobbler_init();
378
379 audio_init(); 375 audio_init();
380 talk_announce_voice_invalid(); /* notify user w/ voice prompt if voice file invalid */ 376 talk_announce_voice_invalid(); /* notify user w/ voice prompt if voice file invalid */
381 settings_apply_skins(); 377 settings_apply_skins();
@@ -630,9 +626,6 @@ static void init(void)
630 tree_mem_init(); 626 tree_mem_init();
631 filetype_init(); 627 filetype_init();
632 628
633 if (global_settings.audioscrobbler)
634 scrobbler_init();
635
636 shortcuts_init(); 629 shortcuts_init();
637 630
638 CHART(">audio_init"); 631 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 @@
31#include "sound_menu.h" 31#include "sound_menu.h"
32#include "kernel.h" 32#include "kernel.h"
33#include "playlist.h" 33#include "playlist.h"
34#include "scrobbler.h"
35#include "audio.h" 34#include "audio.h"
36#include "cuesheet.h" 35#include "cuesheet.h"
37#include "misc.h" 36#include "misc.h"
@@ -150,26 +149,6 @@ MENUITEM_SETTING(spdif_enable, &global_settings.spdif_enable, NULL);
150MENUITEM_SETTING(next_folder, &global_settings.next_folder, NULL); 149MENUITEM_SETTING(next_folder, &global_settings.next_folder, NULL);
151MENUITEM_SETTING(constrain_next_folder, 150MENUITEM_SETTING(constrain_next_folder,
152 &global_settings.constrain_next_folder, NULL); 151 &global_settings.constrain_next_folder, NULL);
153static int audioscrobbler_callback(int action,
154 const struct menu_item_ex *this_item,
155 struct gui_synclist *this_list)
156{
157 (void)this_item;
158 (void)this_list;
159 switch (action)
160 {
161 case ACTION_EXIT_MENUITEM: /* on exit */
162 if (!scrobbler_is_enabled() && global_settings.audioscrobbler)
163 scrobbler_init();
164
165 if(scrobbler_is_enabled() && !global_settings.audioscrobbler)
166 scrobbler_shutdown(false);
167 break;
168 }
169 return action;
170}
171MENUITEM_SETTING(audioscrobbler, &global_settings.audioscrobbler, audioscrobbler_callback);
172
173 152
174static int cuesheet_callback(int action, 153static int cuesheet_callback(int action,
175 const struct menu_item_ex *this_item, 154 const struct menu_item_ex *this_item,
@@ -242,7 +221,7 @@ MAKE_MENU(playback_settings,ID2P(LANG_PLAYBACK),0,
242#ifdef HAVE_SPDIF_POWER 221#ifdef HAVE_SPDIF_POWER
243 &spdif_enable, 222 &spdif_enable,
244#endif 223#endif
245 &next_folder, &constrain_next_folder, &audioscrobbler, &cuesheet 224 &next_folder, &constrain_next_folder, &cuesheet
246#ifdef HAVE_HEADPHONE_DETECTION 225#ifdef HAVE_HEADPHONE_DETECTION
247 ,&unplug_menu 226 ,&unplug_menu
248#endif 227#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 @@
58#include "font.h" 58#include "font.h"
59#include "splash.h" 59#include "splash.h"
60#include "tagcache.h" 60#include "tagcache.h"
61#include "scrobbler.h"
62#include "sound.h" 61#include "sound.h"
63#include "playlist.h" 62#include "playlist.h"
64#include "yesno.h" 63#include "yesno.h"
@@ -365,7 +364,6 @@ static bool clean_shutdown(void (*callback)(void *), void *parameter)
365#if defined(HAVE_RECORDING) 364#if defined(HAVE_RECORDING)
366 audio_close_recording(); 365 audio_close_recording();
367#endif 366#endif
368 scrobbler_shutdown(true);
369 367
370 system_flush(); 368 system_flush();
371#ifdef HAVE_EEPROM_SETTINGS 369#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
49keybox,apps 49keybox,apps
50keyremap,apps 50keyremap,apps
51lamp,apps 51lamp,apps
52lastfm_scrobbler,apps
52logo,demos 53logo,demos
53lrcplayer,apps 54lrcplayer,apps
54lua,viewers 55lua,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
12jackpot.c 12jackpot.c
13keybox.c 13keybox.c
14keyremap.c 14keyremap.c
15lastfm_scrobbler.c
15logo.c 16logo.c
16lrcplayer.c 17lrcplayer.c
17mosaique.c 18mosaique.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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2006-2008 Robert Keevil
11 * Converted to Plugin
12 * Copyright (C) 2022 William Wilgus
13 *
14 * This program is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU General Public License
16 * as published by the Free Software Foundation; either version 2
17 * of the License, or (at your option) any later version.
18 *
19 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
20 * KIND, either express or implied.
21 *
22 ****************************************************************************/
23/* Scrobbler Plugin
24Audioscrobbler spec at:
25http://www.audioscrobbler.net/wiki/Portable_Player_Logging
26*/
27
28#include "plugin.h"
29
30#ifdef ROCKBOX_HAS_LOGF
31#define logf rb->logf
32#else
33#define logf(...) do { } while(0)
34#endif
35
36
37/****************** constants ******************/
38#define EV_EXIT MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0xFF)
39#define EV_OTHINSTANCE MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0xFE)
40#define EV_STARTUP MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0x01)
41#define EV_TRACKCHANGE MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0x02)
42#define EV_TRACKFINISH MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0x03)
43
44#define SCROBBLER_VERSION "1.1"
45
46/* increment this on any code change that effects output */
47#define SCROBBLER_REVISION " $Revision$"
48
49#define SCROBBLER_MAX_CACHE 32
50/* longest entry I've had is 323, add a safety margin */
51#define SCROBBLER_CACHE_LEN 512
52
53#define ITEM_HDR "#ARTIST #ALBUM #TITLE #TRACKNUM #LENGTH #RATING #TIMESTAMMP #MUSICBRAINZ_TRACKID\n"
54
55#if CONFIG_RTC
56static time_t timestamp;
57#define BASE_FILENAME HOME_DIR "/.scrobbler.log"
58#define HDR_STR_TIMELESS
59#define get_timestamp() ((long)timestamp)
60#define record_timestamp() ((void)(timestamp = rb->mktime(rb->get_time())))
61#else /* !CONFIG_RTC */
62#define HDR_STR_TIMELESS " Timeless"
63#define BASE_FILENAME ".scrobbler-timeless.log"
64#define get_timestamp() (0l)
65#define record_timestamp() ({})
66#endif /* CONFIG_RTC */
67
68#define THREAD_STACK_SIZE 4*DEFAULT_STACK_SIZE
69
70#if (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
71 (CONFIG_KEYPAD == IRIVER_H300_PAD)
72#define SCROBBLE_OFF BUTTON_OFF
73#define SCROBBLE_OFF_TXT "STOP"
74#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
75 (CONFIG_KEYPAD == IPOD_3G_PAD) || \
76 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
77#define SCROBBLE_OFF BUTTON_MENU
78#define SCROBBLE_OFF_TXT "MENU"
79#elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD || \
80 CONFIG_KEYPAD == AGPTEK_ROCKER_PAD
81#define SCROBBLE_OFF BUTTON_POWER
82#define SCROBBLE_OFF_TXT "POWER"
83#elif (CONFIG_KEYPAD == SANSA_E200_PAD) || \
84 (CONFIG_KEYPAD == SANSA_C200_PAD) || \
85 (CONFIG_KEYPAD == SANSA_CLIP_PAD) || \
86 (CONFIG_KEYPAD == SANSA_M200_PAD)
87#define SCROBBLE_OFF BUTTON_POWER
88#define SCROBBLE_OFF_TXT "POWER"
89#elif (CONFIG_KEYPAD == SANSA_FUZE_PAD)
90#define SCROBBLE_OFF BUTTON_HOME
91#define SCROBBLE_OFF_TXT "HOME"
92#elif (CONFIG_KEYPAD == IRIVER_H10_PAD || \
93 CONFIG_KEYPAD == CREATIVE_ZENXFI3_PAD || \
94 CONFIG_KEYPAD == SONY_NWZ_PAD || \
95 CONFIG_KEYPAD == XDUOO_X3_PAD || \
96 CONFIG_KEYPAD == IHIFI_770_PAD || \
97 CONFIG_KEYPAD == IHIFI_800_PAD || \
98 CONFIG_KEYPAD == XDUOO_X3II_PAD || \
99 CONFIG_KEYPAD == XDUOO_X20_PAD || \
100 CONFIG_KEYPAD == FIIO_M3K_LINUX_PAD || \
101 CONFIG_KEYPAD == EROSQ_PAD)
102#define SCROBBLE_OFF BUTTON_POWER
103#define SCROBBLE_OFF_TXT "POWER"
104#elif CONFIG_KEYPAD == GIGABEAT_PAD
105#define SCROBBLE_OFF BUTTON_POWER
106#define SCROBBLE_OFF_TXT "POWER"
107#elif CONFIG_KEYPAD == GIGABEAT_S_PAD \
108 || CONFIG_KEYPAD == SAMSUNG_YPR0_PAD \
109 || CONFIG_KEYPAD == CREATIVE_ZEN_PAD
110#define SCROBBLE_OFF BUTTON_BACK
111#define SCROBBLE_OFF_TXT "BACK"
112#elif CONFIG_KEYPAD == MROBE500_PAD
113#define SCROBBLE_OFF BUTTON_POWER
114#define SCROBBLE_OFF_TXT "POWER"
115#elif CONFIG_KEYPAD == MROBE100_PAD
116#define SCROBBLE_OFF BUTTON_POWER
117#define SCROBBLE_OFF_TXT "POWER"
118#elif CONFIG_KEYPAD == IAUDIO_M3_PAD
119#define SCROBBLE_OFF BUTTON_REC
120#define BATTERY_RC_OFF BUTTON_RC_REC
121#define SCROBBLE_OFF_TXT "REC"
122#elif CONFIG_KEYPAD == COWON_D2_PAD
123#define SCROBBLE_OFF BUTTON_POWER
124#define SCROBBLE_OFF_TXT "POWER"
125#elif CONFIG_KEYPAD == CREATIVEZVM_PAD
126#define SCROBBLE_OFF BUTTON_BACK
127#define SCROBBLE_OFF_TXT "BACK"
128#elif CONFIG_KEYPAD == PHILIPS_HDD1630_PAD
129#define SCROBBLE_OFF BUTTON_POWER
130#define SCROBBLE_OFF_TXT "POWER"
131#elif CONFIG_KEYPAD == PHILIPS_HDD6330_PAD
132#define SCROBBLE_OFF BUTTON_POWER
133#define SCROBBLE_OFF_TXT "POWER"
134#elif CONFIG_KEYPAD == PHILIPS_SA9200_PAD
135#define SCROBBLE_OFF BUTTON_POWER
136#define SCROBBLE_OFF_TXT "POWER"
137#elif CONFIG_KEYPAD == ONDAVX747_PAD
138#define SCROBBLE_OFF BUTTON_POWER
139#define SCROBBLE_OFF_TXT "POWER"
140#elif CONFIG_KEYPAD == ONDAVX777_PAD
141#define SCROBBLE_OFF BUTTON_POWER
142#define SCROBBLE_OFF_TXT "POWER"
143#elif (CONFIG_KEYPAD == SAMSUNG_YH820_PAD) || \
144 (CONFIG_KEYPAD == SAMSUNG_YH92X_PAD)
145#define SCROBBLE_OFF BUTTON_RIGHT
146#define SCROBBLE_OFF_TXT "RIGHT"
147#elif CONFIG_KEYPAD == PBELL_VIBE500_PAD
148#define SCROBBLE_OFF BUTTON_REC
149#define SCROBBLE_OFF_TXT "REC"
150#elif CONFIG_KEYPAD == MPIO_HD200_PAD
151#define SCROBBLE_OFF BUTTON_REC
152#define SCROBBLE_OFF_TXT "REC"
153#elif CONFIG_KEYPAD == MPIO_HD300_PAD
154#define SCROBBLE_OFF BUTTON_REC
155#define SCROBBLE_OFF_TXT "REC"
156#elif CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD
157#define SCROBBLE_OFF BUTTON_POWER
158#define SCROBBLE_OFF_TXT "POWER"
159#elif CONFIG_KEYPAD == SANSA_CONNECT_PAD
160#define SCROBBLE_OFF BUTTON_POWER
161#define SCROBBLE_OFF_TXT "POWER"
162#elif (CONFIG_KEYPAD == HM60X_PAD) || (CONFIG_KEYPAD == HM801_PAD)
163#define SCROBBLE_OFF BUTTON_POWER
164#define SCROBBLE_OFF_TXT "POWER"
165#elif CONFIG_KEYPAD == DX50_PAD
166#define SCROBBLE_OFF BUTTON_POWER_LONG
167#define SCROBBLE_OFF_TXT "Power Long"
168#elif CONFIG_KEYPAD == CREATIVE_ZENXFI2_PAD
169#define SCROBBLE_OFF BUTTON_POWER
170#define SCROBBLE_OFF_TXT "Power"
171#elif CONFIG_KEYPAD == FIIO_M3K_PAD
172#define SCROBBLE_OFF BUTTON_POWER
173#define SCROBBLE_OFF_TXT "Power"
174#elif CONFIG_KEYPAD == SHANLING_Q1_PAD
175/* use touchscreen */
176#else
177#error "No keymap defined!"
178#endif
179#if defined(HAVE_TOUCHSCREEN)
180#ifndef SCROBBLE_OFF
181#define SCROBBLE_OFF BUTTON_TOPLEFT
182#endif
183#ifndef SCROBBLE_OFF_TXT
184#define SCROBBLE_OFF_TXT "TOPLEFT"
185#endif
186#endif
187/****************** prototypes ******************/
188int plugin_main(const void* parameter); /* main loop */
189enum plugin_status plugin_start(const void* parameter); /* entry */
190
191/****************** globals ******************/
192unsigned char **language_strings; /* for use with str() macro; must be init */
193/* communication to the worker thread */
194static struct
195{
196 bool exiting; /* signal to the thread that we want to exit */
197 unsigned int id; /* worker thread id */
198 struct event_queue queue; /* thread event queue */
199 long stack[THREAD_STACK_SIZE / sizeof(long)];
200} gThread;
201
202static struct
203{
204 char *buf;
205 int pos;
206 size_t size;
207 bool pending;
208 bool force_flush;
209} gCache;
210
211/****************** helper fuctions ******************/
212
213int scrobbler_init(void)
214{
215 gCache.buf = rb->plugin_get_buffer(&gCache.size);
216
217 size_t reqsz = SCROBBLER_MAX_CACHE*SCROBBLER_CACHE_LEN;
218 gCache.size = PLUGIN_BUFFER_SIZE - rb->plugin_reserve_buffer(reqsz);
219
220 if (gCache.size < reqsz)
221 {
222 logf("SCROBBLER: OOM , %ld < req:%ld", gCache.size, reqsz);
223 return -1;
224 }
225
226 gCache.pos = 0;
227 gCache.pending = false;
228 gCache.force_flush = true;
229 logf("Scrobbler Initialized");
230 return 1;
231}
232
233static void get_scrobbler_filename(char *path, size_t size)
234{
235 int used;
236
237 used = rb->snprintf(path, size, "/%s", BASE_FILENAME);
238
239 if (used >= (int)size)
240 {
241 logf("%s: not enough buffer space for log file", __func__);
242 rb->memset(path, 0, size);
243 }
244}
245
246static void scrobbler_write_cache(void)
247{
248 int i;
249 int fd;
250 logf("%s", __func__);
251 char scrobbler_file[MAX_PATH];
252 get_scrobbler_filename(scrobbler_file, sizeof(scrobbler_file));
253
254 /* If the file doesn't exist, create it.
255 Check at each write since file may be deleted at any time */
256 if(!rb->file_exists(scrobbler_file))
257 {
258 fd = rb->open(scrobbler_file, O_RDWR | O_CREAT, 0666);
259 if(fd >= 0)
260 {
261 rb->fdprintf(fd, "#AUDIOSCROBBLER/" SCROBBLER_VERSION "\n"
262 "#TZ/UNKNOWN\n" "#CLIENT/Rockbox "
263 TARGET_NAME SCROBBLER_REVISION
264 HDR_STR_TIMELESS "\n");
265 rb->fdprintf(fd, ITEM_HDR);
266
267 rb->close(fd);
268 }
269 else
270 {
271 logf("SCROBBLER: cannot create log file (%s)", scrobbler_file);
272 }
273 }
274
275 /* write the cache entries */
276 fd = rb->open(scrobbler_file, O_WRONLY | O_APPEND);
277 if(fd >= 0)
278 {
279 logf("SCROBBLER: writing %d entries", gCache.pos);
280 /* copy data to temporary storage in case data moves during I/O */
281 char temp_buf[SCROBBLER_CACHE_LEN];
282 for ( i=0; i < gCache.pos; i++ )
283 {
284 logf("SCROBBLER: write %d", i);
285 char* scrobbler_buf = gCache.buf;
286 ssize_t len = rb->strlcpy(temp_buf, scrobbler_buf+(SCROBBLER_CACHE_LEN*i),
287 sizeof(temp_buf));
288 if (rb->write(fd, temp_buf, len) != len)
289 break;
290 }
291 rb->close(fd);
292 }
293 else
294 {
295 logf("SCROBBLER: error writing file");
296 }
297
298 /* clear even if unsuccessful - don't want to overflow the buffer */
299 gCache.pos = 0;
300}
301
302static void scrobbler_flush_callback(void)
303{
304 (void) gCache.force_flush;
305 if(gCache.pos <= 0)
306 return;
307#if (CONFIG_STORAGE & STORAGE_ATA)
308 else
309#else
310 if ((gCache.pos >= SCROBBLER_MAX_CACHE / 2) || gCache.force_flush == true)
311#endif
312 {
313 gCache.force_flush = false;
314 logf("%s", __func__);
315 scrobbler_write_cache();
316 }
317}
318
319static void scrobbler_add_to_cache(const struct mp3entry *id)
320{
321 if ( gCache.pos >= SCROBBLER_MAX_CACHE )
322 scrobbler_write_cache();
323
324 char rating = 'S'; /* Skipped */
325 char* scrobbler_buf = gCache.buf;
326
327 logf("SCROBBLER: add_to_cache[%d]", gCache.pos);
328
329 if (id->elapsed > id->length / 2)
330 rating = 'L'; /* Listened */
331
332 char tracknum[11] = { "" };
333
334 if (id->tracknum > 0)
335 rb->snprintf(tracknum, sizeof (tracknum), "%d", id->tracknum);
336
337 int ret = rb->snprintf(&scrobbler_buf[(SCROBBLER_CACHE_LEN*gCache.pos)],
338 SCROBBLER_CACHE_LEN,
339 "%s\t%s\t%s\t%s\t%d\t%c\t%ld\t%s\n",
340 id->artist,
341 id->album ?: "",
342 id->title,
343 tracknum,
344 (int)(id->length / 1000),
345 rating,
346 get_timestamp(),
347 id->mb_track_id ?id->mb_track_id: "");
348
349 if ( ret >= SCROBBLER_CACHE_LEN )
350 {
351 logf("SCROBBLER: entry too long:");
352 logf("SCROBBLER: %s", id->path);
353 }
354 else
355 {
356 logf("Added %s", scrobbler_buf);
357 gCache.pos++;
358 rb->register_storage_idle_func(scrobbler_flush_callback);
359 }
360
361}
362
363static void scrobbler_flush_cache(void)
364{
365 logf("%s", __func__);
366 /* Add any pending entries to the cache */
367 if (gCache.pending)
368 {
369 gCache.pending = false;
370 if (rb->audio_status())
371 scrobbler_add_to_cache(rb->audio_current_track());
372 }
373
374 /* Write the cache to disk if needed */
375 if (gCache.pos > 0)
376 {
377 scrobbler_write_cache();
378 }
379}
380
381static void scrobbler_change_event(unsigned short id, void *ev_data)
382{
383 (void)id;
384 logf("%s", __func__);
385 struct mp3entry *id3 = ((struct track_event *)ev_data)->id3;
386
387 /* check if track was resumed > %50 played ( likely got saved )
388 check for blank artist or track name */
389 if ((id3->elapsed > id3->length / 2) || !id3->artist || !id3->title)
390 {
391 gCache.pending = false;
392 logf("SCROBBLER: skipping file %s", id3->path);
393 }
394 else
395 {
396 logf("SCROBBLER: add pending %s",id3->path);
397 record_timestamp();
398 gCache.pending = true;
399 }
400}
401#ifdef ROCKBOX_HAS_LOGF
402static const char* track_event_info(struct track_event* te)
403{
404
405 static const char *strflags[] = {"TEF_NONE", "TEF_CURRENT",
406 "TEF_AUTOSKIP", "TEF_CUR|ASKIP",
407 "TEF_REWIND", "TEF_CUR|REW",
408 "TEF_ASKIP|REW", "TEF_CUR|ASKIP|REW"};
409/*TEF_NONE = 0x0, no flags are set
410* TEF_CURRENT = 0x1, event is for the current track
411* TEF_AUTO_SKIP = 0x2, event is sent in context of auto skip
412* TEF_REWIND = 0x4, interpret as rewind, id3->elapsed is the
413 position before the seek back to 0
414*/
415 logf("flag %d", te->flags);
416 return strflags[te->flags&0x7];
417}
418
419#endif
420static void scrobbler_finish_event(unsigned short id, void *ev_data)
421{
422 (void)id;
423 struct track_event *te = ((struct track_event *)ev_data);
424 struct mp3entry *id3 = te->id3;
425 logf("%s %s %s", __func__, gCache.pending?"True":"False", track_event_info(te));
426 /* add entry using the currently ending track */
427 if (gCache.pending && (te->flags & TEF_CURRENT) && !(te->flags & TEF_REWIND))
428 {
429 gCache.pending = false;
430
431 if (id3->elapsed*2 >= id3->length)
432 scrobbler_add_to_cache(te->id3);
433 else
434 {
435 logf("%s Discarding < 50%% played", __func__);
436 }
437 }
438
439
440}
441
442/****************** main thread + helper ******************/
443void thread(void)
444{
445 bool in_usb = false;
446
447 struct queue_event ev;
448 while (!gThread.exiting)
449 {
450 rb->queue_wait(&gThread.queue, &ev);
451
452 switch (ev.id)
453 {
454 case SYS_USB_CONNECTED:
455 scrobbler_flush_cache();
456 rb->usb_acknowledge(SYS_USB_CONNECTED_ACK);
457 in_usb = true;
458 break;
459 case SYS_USB_DISCONNECTED:
460 in_usb = false;
461 /*fall through*/
462 case EV_STARTUP:
463 rb->beep_play(1500, 100, 1000);
464 break;
465 case SYS_POWEROFF:
466 gCache.force_flush = true;
467 /*fall through*/
468 case EV_EXIT:
469 rb->unregister_storage_idle_func(scrobbler_flush_callback, !in_usb);
470 return;
471 case EV_OTHINSTANCE:
472 scrobbler_flush_cache();
473 rb->splashf(HZ * 2, "%s Cache Flushed", str(LANG_AUDIOSCROBBLER));
474 break;
475 default:
476 logf("default %ld", ev.id);
477 break;
478 }
479 }
480}
481
482void thread_create(void)
483{
484 /* put the thread's queue in the bcast list */
485 rb->queue_init(&gThread.queue, true);
486 gThread.id = rb->create_thread(thread, gThread.stack, sizeof(gThread.stack),
487 0, "Last.Fm_TSR"
488 IF_PRIO(, PRIORITY_BACKGROUND)
489 IF_COP(, CPU));
490 rb->queue_post(&gThread.queue, EV_STARTUP, 0);
491 rb->yield();
492}
493
494void thread_quit(void)
495{
496 if (!gThread.exiting) {
497 rb->queue_post(&gThread.queue, EV_EXIT, 0);
498 rb->thread_wait(gThread.id);
499 /* we don't want any more events */
500 rb->remove_event(PLAYBACK_EVENT_TRACK_CHANGE, scrobbler_change_event);
501 rb->remove_event(PLAYBACK_EVENT_TRACK_FINISH, scrobbler_finish_event);
502 /* remove the thread's queue from the broadcast list */
503 rb->queue_delete(&gThread.queue);
504 gThread.exiting = true;
505 }
506}
507
508/* callback to end the TSR plugin, called before a new one gets loaded */
509static bool exit_tsr(bool reenter)
510{
511 logf("%s", __func__);
512 bool is_exit = false;
513 int button;
514 if (reenter)
515 {
516 logf(" reenter other instance ");
517 rb->queue_post(&gThread.queue, EV_OTHINSTANCE, 0);
518 return false; /* dont let it start again */
519 }
520 rb->lcd_clear_display();
521 rb->lcd_puts_scroll(0, 0, "Scrobbler is currently running.");
522 rb->lcd_puts_scroll(0, 1, "Press " SCROBBLE_OFF_TXT " to exit");
523 rb->lcd_puts_scroll(0, 2, "Anything else will resume");
524
525 rb->lcd_update();
526 rb->button_clear_queue();
527 while (1)
528 {
529 button = rb->button_get(true);
530 if (IS_SYSEVENT(button))
531 continue;
532 if (button == SCROBBLE_OFF)
533 {
534 rb->queue_post(&gThread.queue, EV_EXIT, 0);
535 rb->thread_wait(gThread.id);
536 /* remove the thread's queue from the broadcast list */
537 rb->queue_delete(&gThread.queue);
538 is_exit = true;
539 }
540 else is_exit = false;
541
542 break;
543 }
544 FOR_NB_SCREENS(idx)
545 rb->screens[idx]->scroll_stop();
546
547 if (is_exit)
548 thread_quit();
549
550 return is_exit;
551}
552
553/****************** main ******************/
554
555int plugin_main(const void* parameter)
556{
557 (void)parameter;
558
559 rb->memset(&gThread, 0, sizeof(gThread));
560 rb->splashf(HZ / 2, "%s Started",str(LANG_AUDIOSCROBBLER));
561 logf("%s: %s Started", __func__, str(LANG_AUDIOSCROBBLER));
562
563 rb->plugin_tsr(exit_tsr); /* stay resident */
564
565 rb->add_event(PLAYBACK_EVENT_TRACK_CHANGE, scrobbler_change_event);
566 rb->add_event(PLAYBACK_EVENT_TRACK_FINISH, scrobbler_finish_event);
567 thread_create();
568
569 return 0;
570}
571
572/***************** Plugin Entry Point *****************/
573
574enum plugin_status plugin_start(const void* parameter)
575{
576 /* now go ahead and have fun! */
577 if (rb->usb_inserted() == true)
578 return PLUGIN_USB_CONNECTED;
579 language_strings = rb->language_strings;
580 if (scrobbler_init() < 0)
581 return PLUGIN_ERROR;
582 int ret = plugin_main(parameter);
583 return (ret==0) ? PLUGIN_OK : PLUGIN_ERROR;
584}
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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2006-2008 Robert Keevil
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21/*
22Audioscrobbler spec at:
23http://www.audioscrobbler.net/wiki/Portable_Player_Logging
24*/
25
26#include <stdio.h>
27#include <config.h>
28#include "file.h"
29#include "logf.h"
30#include "metadata.h"
31#include "kernel.h"
32#include "audio.h"
33#include "core_alloc.h"
34#include "rbpaths.h"
35#include "ata_idle_notify.h"
36#include "pathfuncs.h"
37#include "appevents.h"
38#include "string-extra.h"
39#if CONFIG_RTC
40#include "time.h"
41#include "timefuncs.h"
42#endif
43
44#include "scrobbler.h"
45
46#define SCROBBLER_VERSION "1.1"
47
48/* increment this on any code change that effects output */
49#define SCROBBLER_REVISION " $Revision$"
50
51#define SCROBBLER_MAX_CACHE 32
52/* longest entry I've had is 323, add a safety margin */
53#define SCROBBLER_CACHE_LEN 512
54
55static bool scrobbler_initialised = false;
56static int scrobbler_cache = 0;
57static int cache_pos = 0;
58static bool pending = false;
59#if CONFIG_RTC
60static time_t timestamp;
61#define BASE_FILENAME HOME_DIR "/.scrobbler.log"
62#define HDR_STR_TIMELESS
63#define get_timestamp() ((long)timestamp)
64#define record_timestamp() ((void)(timestamp = mktime(get_time())))
65#else /* !CONFIG_RTC */
66#define HDR_STR_TIMELESS " Timeless"
67#define BASE_FILENAME ".scrobbler-timeless.log"
68#define get_timestamp() (0l)
69#define record_timestamp() ({})
70#endif /* CONFIG_RTC */
71
72static void get_scrobbler_filename(char *path, size_t size)
73{
74 int used;
75
76 used = snprintf(path, size, "/%s", BASE_FILENAME);
77
78 if (used >= (int)size)
79 {
80 logf("SCROBBLER: not enough buffer space for log file");
81 memset(path, 0, size);
82 }
83}
84
85static void write_cache(void)
86{
87 int i;
88 int fd;
89
90 char scrobbler_file[MAX_PATH];
91 get_scrobbler_filename(scrobbler_file, MAX_PATH);
92
93 /* If the file doesn't exist, create it.
94 Check at each write since file may be deleted at any time */
95 if(!file_exists(scrobbler_file))
96 {
97 fd = open(scrobbler_file, O_RDWR | O_CREAT, 0666);
98 if(fd >= 0)
99 {
100 fdprintf(fd, "#AUDIOSCROBBLER/" SCROBBLER_VERSION "\n"
101 "#TZ/UNKNOWN\n" "#CLIENT/Rockbox "
102 TARGET_NAME SCROBBLER_REVISION
103 HDR_STR_TIMELESS "\n");
104
105 close(fd);
106 }
107 else
108 {
109 logf("SCROBBLER: cannot create log file (%s)", scrobbler_file);
110 }
111 }
112
113 /* write the cache entries */
114 fd = open(scrobbler_file, O_WRONLY | O_APPEND);
115 if(fd >= 0)
116 {
117 logf("SCROBBLER: writing %d entries", cache_pos);
118 /* copy data to temporary storage in case data moves during I/O */
119 char temp_buf[SCROBBLER_CACHE_LEN];
120 for ( i=0; i < cache_pos; i++ )
121 {
122 logf("SCROBBLER: write %d", i);
123 char* scrobbler_buf = core_get_data(scrobbler_cache);
124 ssize_t len = strlcpy(temp_buf, scrobbler_buf+(SCROBBLER_CACHE_LEN*i),
125 sizeof(temp_buf));
126 if (write(fd, temp_buf, len) != len)
127 break;
128 }
129 close(fd);
130 }
131 else
132 {
133 logf("SCROBBLER: error writing file");
134 }
135
136 /* clear even if unsuccessful - don't want to overflow the buffer */
137 cache_pos = 0;
138}
139
140static void scrobbler_flush_callback(void)
141{
142 if (scrobbler_initialised && cache_pos)
143 write_cache();
144}
145
146static void add_to_cache(const struct mp3entry *id)
147{
148 if ( cache_pos >= SCROBBLER_MAX_CACHE )
149 write_cache();
150
151 char rating = 'S'; /* Skipped */
152 char* scrobbler_buf = core_get_data(scrobbler_cache);
153
154 logf("SCROBBLER: add_to_cache[%d]", cache_pos);
155
156 if (id->elapsed > id->length / 2)
157 rating = 'L'; /* Listened */
158
159 char tracknum[11] = { "" };
160
161 if (id->tracknum > 0)
162 snprintf(tracknum, sizeof (tracknum), "%d", id->tracknum);
163
164 int ret = snprintf(scrobbler_buf+(SCROBBLER_CACHE_LEN*cache_pos),
165 SCROBBLER_CACHE_LEN,
166 "%s\t%s\t%s\t%s\t%d\t%c\t%ld\t%s\n",
167 id->artist,
168 id->album ?: "",
169 id->title,
170 tracknum,
171 (int)(id->length / 1000),
172 rating,
173 get_timestamp(),
174 id->mb_track_id ?: "");
175
176 if ( ret >= SCROBBLER_CACHE_LEN )
177 {
178 logf("SCROBBLER: entry too long:");
179 logf("SCROBBLER: %s", id->path);
180 }
181 else
182 {
183 cache_pos++;
184 register_storage_idle_func(scrobbler_flush_callback);
185 }
186
187}
188
189static void scrobbler_change_event(unsigned short id, void *ev_data)
190{
191 (void)id;
192 struct mp3entry *id3 = ((struct track_event *)ev_data)->id3;
193
194 /* check if track was resumed > %50 played
195 check for blank artist or track name */
196 if (id3->elapsed > id3->length / 2 || !id3->artist || !id3->title)
197 {
198 pending = false;
199 logf("SCROBBLER: skipping file %s", id3->path);
200 }
201 else
202 {
203 logf("SCROBBLER: add pending");
204 record_timestamp();
205 pending = true;
206 }
207}
208
209static void scrobbler_finish_event(unsigned short id, void *data)
210{
211 (void)id;
212 struct track_event *te = (struct track_event *)data;
213
214 /* add entry using the currently ending track */
215 if (pending && (te->flags & TEF_CURRENT)
216 && !(te->flags & TEF_REWIND)
217 )
218 {
219 pending = false;
220 add_to_cache(te->id3);
221 }
222}
223
224int scrobbler_init(void)
225{
226 if (scrobbler_initialised)
227 return 1;
228
229 scrobbler_cache = core_alloc("scrobbler",
230 SCROBBLER_MAX_CACHE*SCROBBLER_CACHE_LEN);
231
232 if (scrobbler_cache <= 0)
233 {
234 logf("SCROOBLER: OOM");
235 return -1;
236 }
237
238 cache_pos = 0;
239 pending = false;
240
241 scrobbler_initialised = true;
242
243 add_event(PLAYBACK_EVENT_TRACK_CHANGE, scrobbler_change_event);
244 add_event(PLAYBACK_EVENT_TRACK_FINISH, scrobbler_finish_event);
245
246 return 1;
247}
248
249static void scrobbler_flush_cache(void)
250{
251 /* Add any pending entries to the cache */
252 if (pending)
253 {
254 pending = false;
255 if (audio_status())
256 add_to_cache(audio_current_track());
257 }
258
259 /* Write the cache to disk if needed */
260 if (cache_pos)
261 write_cache();
262}
263
264void scrobbler_shutdown(bool poweroff)
265{
266 if (!scrobbler_initialised)
267 return;
268
269 remove_event(PLAYBACK_EVENT_TRACK_CHANGE, scrobbler_change_event);
270 remove_event(PLAYBACK_EVENT_TRACK_FINISH, scrobbler_finish_event);
271
272 scrobbler_flush_cache();
273
274 if (!poweroff)
275 {
276 /* get rid of the buffer */
277 core_free(scrobbler_cache);
278 scrobbler_cache = 0;
279 }
280
281 scrobbler_initialised = false;
282}
283
284bool scrobbler_is_enabled(void)
285{
286 return scrobbler_initialised;
287}
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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2006 Robert Keevil
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#ifndef __SCROBBLER_H__
23#define __SCROBBLER_H__
24
25int scrobbler_init(void);
26void scrobbler_shutdown(bool poweroff);
27bool scrobbler_is_enabled(void);
28
29#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
503 int single_mode; /* single mode - stop after every track, album, album artist, 503 int single_mode; /* single mode - stop after every track, album, album artist,
504 artist, composer, work, or genre */ 504 artist, composer, work, or genre */
505 bool party_mode; /* party mode - unstoppable music */ 505 bool party_mode; /* party mode - unstoppable music */
506 bool audioscrobbler; /* Audioscrobbler logging */
507 bool cuesheet; 506 bool cuesheet;
508 bool car_adapter_mode; /* 0=off 1=on */ 507 bool car_adapter_mode; /* 0=off 1=on */
509 int car_adapter_mode_delay; /* delay before resume, in seconds*/ 508 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[] = {
1832 ID2P(LANG_FM_ITALY), ID2P(LANG_FM_OTHER)), 1832 ID2P(LANG_FM_ITALY), ID2P(LANG_FM_OTHER)),
1833#endif 1833#endif
1834 1834
1835 OFFON_SETTING(F_BANFROMQS, audioscrobbler, LANG_AUDIOSCROBBLER, false,
1836 "Last.fm Logging", NULL),
1837#if CONFIG_TUNER 1835#if CONFIG_TUNER
1838 TEXT_SETTING(0, fmr_file, "fmr", "-", 1836 TEXT_SETTING(0, fmr_file, "fmr", "-",
1839 FMPRESET_PATH "/", ".fmr"), 1837 FMPRESET_PATH "/", ".fmr"),