Prepare new maemo release
[maemo-rb.git] / apps / scrobbler.c
blob1a0ad9390ed31efa7f19c5da430d5a5e41f8c0de
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
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
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 "settings.h"
35 #include "ata_idle_notify.h"
36 #include "filefuncs.h"
37 #include "appevents.h"
39 #if CONFIG_RTC
40 #include "time.h"
41 #include "timefuncs.h"
42 #endif
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;
57 static int cache_pos;
58 static struct mp3entry scrobbler_entry;
59 static bool pending = false;
60 static bool scrobbler_initialised = false;
61 #if CONFIG_RTC
62 static time_t timestamp;
63 #else
64 static unsigned long timestamp;
65 #endif
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)
71 return 120000;
73 #endif
75 static void get_scrobbler_filename(char *path, size_t size)
77 int used;
79 #if CONFIG_RTC
80 const char *base_filename = ".scrobbler.log";
81 #else
82 const char *base_filename = ".scrobbler-timeless.log";
83 #endif
85 /* Get location of USB mass storage area */
86 #ifdef APPLICATION
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 #elif defined (SAMSUNG_YPR0)
92 used = snprintf(path, size, "%s/%s", HOME_DIR, base_filename);
93 #else /* SDL/unknown RaaA build */
94 used = snprintf(path, size, "%s/%s", ROCKBOX_DIR, base_filename);
95 #endif /* (CONFIG_PLATFORM & PLATFORM_MAEMO) */
97 #else
98 used = snprintf(path, size, "/%s", base_filename);
99 #endif
101 if (used >= (int)size)
103 logf("SCROBBLER: not enough buffer space for log file");
104 memset(path, 0, size);
108 static void write_cache(void)
110 int i;
111 int fd;
113 char scrobbler_file[MAX_PATH];
114 get_scrobbler_filename(scrobbler_file, MAX_PATH);
116 /* If the file doesn't exist, create it.
117 Check at each write since file may be deleted at any time */
118 if(!file_exists(scrobbler_file))
120 fd = open(scrobbler_file, O_RDWR | O_CREAT, 0666);
121 if(fd >= 0)
123 fdprintf(fd, "#AUDIOSCROBBLER/" SCROBBLER_VERSION "\n"
124 "#TZ/UNKNOWN\n"
125 #if CONFIG_RTC
126 "#CLIENT/Rockbox " TARGET_NAME SCROBBLER_REVISION "\n");
127 #else
128 "#CLIENT/Rockbox " TARGET_NAME SCROBBLER_REVISION " Timeless\n");
129 #endif
131 close(fd);
133 else
135 logf("SCROBBLER: cannot create log file");
139 /* write the cache entries */
140 fd = open(scrobbler_file, O_WRONLY | O_APPEND);
141 if(fd >= 0)
143 logf("SCROBBLER: writing %d entries", cache_pos);
144 /* copy data to temporary storage in case data moves during I/O */
145 char temp_buf[SCROBBLER_CACHE_LEN];
146 for ( i=0; i < cache_pos; i++ )
148 logf("SCROBBLER: write %d", i);
149 char* scrobbler_buf = core_get_data(scrobbler_cache);
150 ssize_t len = strlcpy(temp_buf, scrobbler_buf+(SCROBBLER_CACHE_LEN*i),
151 sizeof(temp_buf));
152 if (write(fd, temp_buf, len) != len)
153 break;
155 close(fd);
157 else
159 logf("SCROBBLER: error writing file");
162 /* clear even if unsuccessful - don't want to overflow the buffer */
163 cache_pos = 0;
166 static void scrobbler_flush_callback(void *data)
168 (void)data;
169 if (scrobbler_initialised && cache_pos)
170 write_cache();
173 static void add_to_cache(unsigned long play_length)
175 if ( cache_pos >= SCROBBLER_MAX_CACHE )
176 write_cache();
178 int ret;
179 char rating = 'S'; /* Skipped */
180 char* scrobbler_buf = core_get_data(scrobbler_cache);
182 logf("SCROBBLER: add_to_cache[%d]", cache_pos);
184 if ( play_length > (scrobbler_entry.length/2) )
185 rating = 'L'; /* Listened */
187 if (scrobbler_entry.tracknum > 0)
189 ret = snprintf(scrobbler_buf+(SCROBBLER_CACHE_LEN*cache_pos),
190 SCROBBLER_CACHE_LEN,
191 "%s\t%s\t%s\t%d\t%d\t%c\t%ld\t%s\n",
192 scrobbler_entry.artist,
193 scrobbler_entry.album?scrobbler_entry.album:"",
194 scrobbler_entry.title,
195 scrobbler_entry.tracknum,
196 (int)scrobbler_entry.length/1000,
197 rating,
198 (long)timestamp,
199 scrobbler_entry.mb_track_id?scrobbler_entry.mb_track_id:"");
200 } else {
201 ret = snprintf(scrobbler_buf+(SCROBBLER_CACHE_LEN*cache_pos),
202 SCROBBLER_CACHE_LEN,
203 "%s\t%s\t%s\t\t%d\t%c\t%ld\t%s\n",
204 scrobbler_entry.artist,
205 scrobbler_entry.album?scrobbler_entry.album:"",
206 scrobbler_entry.title,
207 (int)scrobbler_entry.length/1000,
208 rating,
209 (long)timestamp,
210 scrobbler_entry.mb_track_id?scrobbler_entry.mb_track_id:"");
213 if ( ret >= SCROBBLER_CACHE_LEN )
215 logf("SCROBBLER: entry too long:");
216 logf("SCROBBLER: %s", scrobbler_entry.path);
217 } else {
218 cache_pos++;
219 register_storage_idle_func(scrobbler_flush_callback);
224 static void scrobbler_change_event(void *data)
226 struct mp3entry *id = (struct mp3entry*)data;
227 /* add entry using the previous scrobbler_entry and timestamp */
228 if (pending)
229 add_to_cache(audio_prev_elapsed());
231 /* check if track was resumed > %50 played
232 check for blank artist or track name */
233 if ((id->elapsed > (id->length/2)) ||
234 (!id->artist ) || (!id->title ) )
236 pending = false;
237 logf("SCROBBLER: skipping file %s", id->path);
239 else
241 logf("SCROBBLER: add pending");
242 copy_mp3entry(&scrobbler_entry, id);
243 #if CONFIG_RTC
244 timestamp = mktime(get_time());
245 #else
246 timestamp = 0;
247 #endif
248 pending = true;
252 int scrobbler_init(void)
254 logf("SCROBBLER: init %d", global_settings.audioscrobbler);
256 if(!global_settings.audioscrobbler)
257 return -1;
259 scrobbler_cache = core_alloc("scrobbler", SCROBBLER_MAX_CACHE*SCROBBLER_CACHE_LEN);
260 if (scrobbler_cache <= 0)
262 logf("SCROOBLER: OOM");
263 return -1;
266 add_event(PLAYBACK_EVENT_TRACK_CHANGE, false, scrobbler_change_event);
267 cache_pos = 0;
268 pending = false;
269 scrobbler_initialised = true;
271 return 1;
274 static void scrobbler_flush_cache(void)
276 if (scrobbler_initialised)
278 /* Add any pending entries to the cache */
279 if(pending)
280 add_to_cache(audio_prev_elapsed());
282 /* Write the cache to disk if needed */
283 if (cache_pos)
284 write_cache();
286 pending = false;
290 void scrobbler_shutdown(void)
292 scrobbler_flush_cache();
294 if (scrobbler_initialised)
296 remove_event(PLAYBACK_EVENT_TRACK_CHANGE, scrobbler_change_event);
297 scrobbler_initialised = false;
298 /* get rid of the buffer */
299 core_free(scrobbler_cache);
300 scrobbler_cache = 0;
304 void scrobbler_poweroff(void)
306 if (scrobbler_initialised && pending)
308 if ( audio_status() )
309 add_to_cache(audio_current_track()->elapsed);
310 else
311 add_to_cache(audio_prev_elapsed());
313 /* scrobbler_shutdown is called later, the cache will be written
314 * make sure the final track isn't added twice when that happens */
315 pending = false;
319 bool scrobbler_is_enabled(void)
321 return scrobbler_initialised;