1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
10 * Copyright (C) 2006-2008 Robert Keevil
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.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
22 Audioscrobbler spec at:
23 http://www.audioscrobbler.net/wiki/Portable_Player_Logging
33 #include "core_alloc.h"
35 #include "ata_idle_notify.h"
36 #include "filefuncs.h"
37 #include "appevents.h"
41 #include "timefuncs.h"
44 #include "scrobbler.h"
46 #define SCROBBLER_VERSION "1.1"
48 /* increment this on any code change that effects output */
49 #define SCROBBLER_REVISION " $Revision$"
51 #define SCROBBLER_MAX_CACHE 32
52 /* longest entry I've had is 323, add a safety margin */
53 #define SCROBBLER_CACHE_LEN 512
55 static int scrobbler_cache
;
58 static struct mp3entry scrobbler_entry
;
59 static bool pending
= false;
60 static bool scrobbler_initialised
= false;
62 static time_t timestamp
;
64 static unsigned long timestamp
;
67 /* Crude work-around for Archos Sims - return a set amount */
68 #if (CONFIG_CODEC != SWCODEC) && (CONFIG_PLATFORM & PLATFORM_HOSTED)
69 unsigned long audio_prev_elapsed(void)
75 static void get_scrobbler_filename(char *path
, size_t size
)
80 const char *base_filename
= ".scrobbler.log";
82 const char *base_filename
= ".scrobbler-timeless.log";
85 /* Get location of USB mass storage area */
87 #if (CONFIG_PLATFORM & PLATFORM_MAEMO)
88 used
= snprintf(path
, size
, "/home/user/MyDocs/%s", base_filename
);
89 #elif (CONFIG_PLATFORM & PLATFORM_ANDROID)
90 used
= snprintf(path
, size
, "/sdcard/%s", base_filename
);
91 #else /* SDL/unknown RaaA build */
92 used
= snprintf(path
, size
, "%s/%s", ROCKBOX_DIR
, base_filename
);
93 #endif /* (CONFIG_PLATFORM & PLATFORM_MAEMO) */
96 used
= snprintf(path
, size
, "/%s", base_filename
);
99 if (used
>= (int)size
)
101 logf("SCROBBLER: not enough buffer space for log file");
102 memset(path
, 0, size
);
106 static void write_cache(void)
111 char scrobbler_file
[MAX_PATH
];
112 get_scrobbler_filename(scrobbler_file
, MAX_PATH
);
114 /* If the file doesn't exist, create it.
115 Check at each write since file may be deleted at any time */
116 if(!file_exists(scrobbler_file
))
118 fd
= open(scrobbler_file
, O_RDWR
| O_CREAT
, 0666);
121 fdprintf(fd
, "#AUDIOSCROBBLER/" SCROBBLER_VERSION
"\n"
124 "#CLIENT/Rockbox " TARGET_NAME SCROBBLER_REVISION
"\n");
126 "#CLIENT/Rockbox " TARGET_NAME SCROBBLER_REVISION
" Timeless\n");
133 logf("SCROBBLER: cannot create log file");
137 /* write the cache entries */
138 fd
= open(scrobbler_file
, O_WRONLY
| O_APPEND
);
141 logf("SCROBBLER: writing %d entries", cache_pos
);
142 /* copy data to temporary storage in case data moves during I/O */
143 char temp_buf
[SCROBBLER_CACHE_LEN
];
144 for ( i
=0; i
< cache_pos
; i
++ )
146 logf("SCROBBLER: write %d", i
);
147 char* scrobbler_buf
= core_get_data(scrobbler_cache
);
148 ssize_t len
= strlcpy(temp_buf
, scrobbler_buf
+(SCROBBLER_CACHE_LEN
*i
),
150 if (write(fd
, temp_buf
, len
) != len
)
157 logf("SCROBBLER: error writing file");
160 /* clear even if unsuccessful - don't want to overflow the buffer */
164 static void scrobbler_flush_callback(void *data
)
167 if (scrobbler_initialised
&& cache_pos
)
171 static void add_to_cache(unsigned long play_length
)
173 if ( cache_pos
>= SCROBBLER_MAX_CACHE
)
177 char rating
= 'S'; /* Skipped */
178 char* scrobbler_buf
= core_get_data(scrobbler_cache
);
180 logf("SCROBBLER: add_to_cache[%d]", cache_pos
);
182 if ( play_length
> (scrobbler_entry
.length
/2) )
183 rating
= 'L'; /* Listened */
185 if (scrobbler_entry
.tracknum
> 0)
187 ret
= snprintf(scrobbler_buf
+(SCROBBLER_CACHE_LEN
*cache_pos
),
189 "%s\t%s\t%s\t%d\t%d\t%c\t%ld\t%s\n",
190 scrobbler_entry
.artist
,
191 scrobbler_entry
.album
?scrobbler_entry
.album
:"",
192 scrobbler_entry
.title
,
193 scrobbler_entry
.tracknum
,
194 (int)scrobbler_entry
.length
/1000,
197 scrobbler_entry
.mb_track_id
?scrobbler_entry
.mb_track_id
:"");
199 ret
= snprintf(scrobbler_buf
+(SCROBBLER_CACHE_LEN
*cache_pos
),
201 "%s\t%s\t%s\t\t%d\t%c\t%ld\t%s\n",
202 scrobbler_entry
.artist
,
203 scrobbler_entry
.album
?scrobbler_entry
.album
:"",
204 scrobbler_entry
.title
,
205 (int)scrobbler_entry
.length
/1000,
208 scrobbler_entry
.mb_track_id
?scrobbler_entry
.mb_track_id
:"");
211 if ( ret
>= SCROBBLER_CACHE_LEN
)
213 logf("SCROBBLER: entry too long:");
214 logf("SCROBBLER: %s", scrobbler_entry
.path
);
217 register_storage_idle_func(scrobbler_flush_callback
);
222 static void scrobbler_change_event(void *data
)
224 struct mp3entry
*id
= (struct mp3entry
*)data
;
225 /* add entry using the previous scrobbler_entry and timestamp */
227 add_to_cache(audio_prev_elapsed());
229 /* check if track was resumed > %50 played
230 check for blank artist or track name */
231 if ((id
->elapsed
> (id
->length
/2)) ||
232 (!id
->artist
) || (!id
->title
) )
235 logf("SCROBBLER: skipping file %s", id
->path
);
239 logf("SCROBBLER: add pending");
240 copy_mp3entry(&scrobbler_entry
, id
);
242 timestamp
= mktime(get_time());
250 int scrobbler_init(void)
252 logf("SCROBBLER: init %d", global_settings
.audioscrobbler
);
254 if(!global_settings
.audioscrobbler
)
257 scrobbler_cache
= core_alloc("scrobbler", SCROBBLER_MAX_CACHE
*SCROBBLER_CACHE_LEN
);
258 if (scrobbler_cache
< 0)
260 logf("SCROOBLER: OOM");
264 add_event(PLAYBACK_EVENT_TRACK_CHANGE
, false, scrobbler_change_event
);
267 scrobbler_initialised
= true;
272 static void scrobbler_flush_cache(void)
274 if (scrobbler_initialised
)
276 /* Add any pending entries to the cache */
278 add_to_cache(audio_prev_elapsed());
280 /* Write the cache to disk if needed */
288 void scrobbler_shutdown(void)
290 scrobbler_flush_cache();
292 if (scrobbler_initialised
)
294 remove_event(PLAYBACK_EVENT_TRACK_CHANGE
, scrobbler_change_event
);
295 scrobbler_initialised
= false;
296 /* get rid of the buffer */
297 core_free(scrobbler_cache
);
302 void scrobbler_poweroff(void)
304 if (scrobbler_initialised
&& pending
)
306 if ( audio_status() )
307 add_to_cache(audio_current_track()->elapsed
);
309 add_to_cache(audio_prev_elapsed());
311 /* scrobbler_shutdown is called later, the cache will be written
312 * make sure the final track isn't added twice when that happens */
317 bool scrobbler_is_enabled(void)
319 return scrobbler_initialised
;