--- apps/Makefile	(.../../../trunk)	(revision 347)
+++ apps/Makefile	(revision 347)
@@ -55,7 +55,8 @@ ifdef APPEXTRA
 endif
 
 CFLAGS = $(INCLUDES) $(GCCOPTS) $(TARGET) $(DEFINES) -DTARGET_ID=$(TARGET_ID)	\
- -DAPPSVERSION=\"$(VERSION)\" $(EXTRA_DEFINES) -DMEM=${MEMORYSIZE}
+ -DAPPSVERSION=\"$(VERSION)\" $(EXTRA_DEFINES) -DMEM=${MEMORYSIZE} \
+ -DTARGET_NAME=\"$(ARCHOS)\"
 
 OBJS2 := $(OBJDIR)/lang.o $(patsubst %.c, $(OBJDIR)/%.o, $(SRC))
 OBJS = $(patsubst %.S, $(OBJDIR)/%.o, $(OBJS2))
--- apps/tree.c	(.../../../trunk)	(revision 347)
+++ apps/tree.c	(revision 347)
@@ -65,6 +65,7 @@
 #include "yesno.h"
 #include "gwps-common.h"
 #include "eeprom_settings.h"
+#include "scrobbler.h"
 
 /* gui api */
 #include "list.h"
@@ -1381,6 +1382,7 @@ void ft_play_filename(char *dir, char *f
 /* These two functions are called by the USB and shutdown handlers */
 void tree_flush(void)
 {
+    scrobbler_shutdown();
     tagcache_shutdown();
     playlist_shutdown();
 
@@ -1442,4 +1444,5 @@ void tree_restore(void)
     }
 #endif
     tagcache_start_scan();
+    scrobbler_init();
 }
--- apps/lang/english.lang	(.../../../trunk)	(revision 347)
+++ apps/lang/english.lang	(revision 347)
@@ -9862,3 +9862,17 @@
     *: "Disable auto-resume if phones not present"
   </voice>
 </phrase>
+<phrase>
+  id: LANG_AUDIOSCROBBLER
+  desc: "Last.fm Log" in the playback menu
+  user:
+  <source>
+    *: "Last.fm Log"
+  </source>
+  <dest>
+    *: "Last.fm Log"
+  </dest>
+  <voice>
+    *: "Last.fm Log"
+  </voice>
+</phrase>
--- apps/settings.c	(.../../../trunk)	(revision 347)
+++ apps/settings.c	(revision 347)
@@ -662,6 +662,10 @@ static const struct bit_entry hd_bits[] 
     {1, S_O(unplug_autoresume), 0, "disable autoresume if phones not present", off_on },
 #endif
 
+#if CONFIG_CODEC == SWCODEC
+    {1, S_O(audioscrobbler), false, "Last.fm Logging", off_on},
+#endif
+
     /* If values are just added to the end, no need to bump the version. */
     /* new stuff to be added at the end */
 
--- apps/settings.h	(.../../../trunk)	(revision 347)
+++ apps/settings.h	(revision 347)
@@ -491,6 +491,10 @@ struct user_settings
     int unplug_rw; /* time in s to rewind when pausing */
     bool unplug_autoresume; /* disable auto-resume if no phones */
 #endif
+
+#if CONFIG_CODEC == SWCODEC
+    bool audioscrobbler; /* Audioscrobbler logging  */
+#endif
 };
 
 enum optiontype { INT, BOOL };
--- apps/settings_menu.c	(.../../../trunk)	(revision 347)
+++ apps/settings_menu.c	(revision 347)
@@ -1380,6 +1380,13 @@ static bool next_folder(void)
     return set_bool( str(LANG_NEXT_FOLDER), &global_settings.next_folder );
 }
 
+#if CONFIG_CODEC == SWCODEC
+static bool audioscrobbler(void)
+{
+    return set_bool( str(LANG_AUDIOSCROBBLER), &global_settings.audioscrobbler );
+}
+#endif
+
 static bool codepage_setting(void)
 {
     static const struct opt_items names[] = {
@@ -1740,7 +1747,10 @@ static bool playback_settings_menu(void)
         { ID2P(LANG_ID3_ORDER), id3_order },
         { ID2P(LANG_NEXT_FOLDER), next_folder },
 #ifdef HAVE_HEADPHONE_DETECTION
-        { ID2P(LANG_UNPLUG), unplug_menu }
+        { ID2P(LANG_UNPLUG), unplug_menu },
+#endif
+#if CONFIG_CODEC == SWCODEC
+        { ID2P(LANG_AUDIOSCROBBLER), audioscrobbler}
 #endif
     };
 
--- apps/playback.c	(.../../../trunk)	(revision 347)
+++ apps/playback.c	(revision 347)
@@ -211,6 +211,9 @@ static int track_ridx;
 static int track_widx;
 static bool track_changed;                      /* Audio and codec threads */
 
+/* Play time of the previous track */
+unsigned long prev_track_elapsed;
+
 /* Partially loaded song's file handle to continue buffering later. */
 static int current_fd;
 
@@ -425,6 +428,12 @@ struct mp3entry* audio_next_track(void)
     return &tracks[next_idx].id3;
 }
 
+unsigned long audio_prev_elapsed(void)
+{
+    return prev_track_elapsed;
+}
+
+
 bool audio_has_changed_track(void)
 {
     if (track_changed) 
@@ -1659,6 +1668,8 @@ static bool codec_load_next_track(void) 
 {
     struct event ev;
 
+    prev_track_elapsed = cur_ti->id3.elapsed;
+
     if (ci.seek_time)
         codec_seek_complete_callback();
 
--- apps/SOURCES	(.../../../trunk)	(revision 347)
+++ apps/SOURCES	(revision 347)
@@ -69,6 +69,7 @@ recorder/recording.c
 #if CONFIG_CODEC == SWCODEC
 pcmbuf.c
 playback.c
+scrobbler.c
 codecs.c
 dsp.c
 eq.c	 	
--- apps/scrobbler.c	(.../../../trunk)	(revision 0)
+++ apps/scrobbler.c	(revision 347)
@@ -0,0 +1,228 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id:$
+ *
+ * Copyright (C) 2006 Robert Keevil
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * 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 "file.h"
+#include "sprintf.h"
+#include "playback.h"
+#include "logf.h"
+#include "id3.h"
+#include "kernel.h"
+#include "audio.h"
+#include "buffer.h"
+#ifndef SIMULATOR
+#include "ata.h"
+#endif
+#include "settings.h"
+#ifdef CONFIG_RTC
+#include "time.h"
+#include "timefuncs.h"
+#endif
+
+#include "scrobbler.h"
+
+#define SCROBBLER_VERSION "1.0"
+
+#ifdef CONFIG_RTC
+#define SCROBBLER_FILE "/.scrobbler.log"
+#else
+#define SCROBBLER_FILE "/.scrobbler-timeless.log"
+#endif
+
+/* increment this on any code change that effects output */
+/* replace with CVS Revision keyword? */
+#define SCROBBLER_REVISION "1.0"
+
+#define SCROBBLER_MAX_CACHE 32
+/* longest entry I've had is 323, add a safety margin */
+#define SCROBBLER_CACHE_LEN 512
+
+char* scrobbler_cache;
+
+int scrobbler_fd = -1;
+int cache_pos;
+struct mp3entry scrobbler_entry;
+bool pending = false;
+bool scrobbler_initialised = false;
+#ifdef CONFIG_RTC
+time_t timestamp;
+#else
+unsigned long timestamp;
+#endif
+
+void write_cache(void)
+{
+    int i;
+
+    scrobbler_fd = open(SCROBBLER_FILE, O_RDWR | O_APPEND);
+    if(scrobbler_fd >= 0)
+    {
+        logf("SCROBBLER: writing %d entries", cache_pos); 
+
+        for ( i=0; i < cache_pos; i++ )
+        {
+            logf("SCROBBLER: write %d", i);
+            fdprintf(scrobbler_fd, "%s", scrobbler_cache+(SCROBBLER_CACHE_LEN*i));
+        }
+        close(scrobbler_fd);
+    }
+    else
+    {
+        logf("SCROBBLER: error writing file");
+    }
+
+    cache_pos = 0;
+    scrobbler_fd = -1;
+}
+
+void add_to_cache(void)
+{
+/* using HAVE_MMC to check for Ondios - anything better to use? */
+#if defined(SIMULATOR) || defined(IPOD_NANO) || defined(HAVE_MMC)
+    if ( cache_pos >= SCROBBLER_MAX_CACHE )
+#else
+    if ( ( cache_pos >= SCROBBLER_MAX_CACHE ) || ( ata_disk_is_active() ) )
+#endif
+        write_cache();
+
+    int ret;
+    char rating = 'S'; /* Skipped */
+
+    logf("SCROBBLER: add_to_cache[%d]", cache_pos);
+
+    if ( audio_prev_elapsed() >
+        (scrobbler_entry.length/2) )
+        rating = 'L';
+
+    if (scrobbler_entry.tracknum > 0)
+    {
+        ret = snprintf(scrobbler_cache+(SCROBBLER_CACHE_LEN*cache_pos),
+                SCROBBLER_CACHE_LEN,
+                "%s\t%s\t%s\t%d\t%d\t%c\t%ld\n",
+                scrobbler_entry.artist,
+                scrobbler_entry.album?scrobbler_entry.album:"",
+                scrobbler_entry.title,
+                scrobbler_entry.tracknum,
+                (int)scrobbler_entry.length/1000,
+                rating,
+                (long)timestamp);
+    } else {
+        ret = snprintf(scrobbler_cache+(SCROBBLER_CACHE_LEN*cache_pos),
+                SCROBBLER_CACHE_LEN,
+                "%s\t%s\t%s\t\t%d\t%c\t%ld\n",
+                scrobbler_entry.artist,
+                scrobbler_entry.album?scrobbler_entry.album:"",
+                scrobbler_entry.title,
+                (int)scrobbler_entry.length/1000,
+                rating,
+                (long)timestamp);
+    }
+
+    if ( ret >= SCROBBLER_CACHE_LEN )
+    {
+        logf("SCROBBLER: entry too long:");
+        logf("SCROBBLER: %s", scrobbler_entry.path);
+    } else
+        cache_pos++;
+}
+
+void scrobbler_change_event(struct mp3entry *id)
+{
+    /* add entry using the previous scrobbler_entry and timestamp */
+    if (pending)
+        add_to_cache();
+
+    /*  check if track was resumed > %50 played
+        check for blank artist or track name */
+    if ((id->elapsed > (id->length/2)) ||
+        (!id->artist ) || (!id->title ) )
+    {
+        pending = false;
+        logf("SCROBBLER: skipping file %s", id->path);
+    }
+    else
+    {
+        copy_mp3entry(&scrobbler_entry, id);
+#ifdef CONFIG_RTC
+        timestamp = mktime(get_time());
+#else
+        timestamp = 0;
+#endif
+        pending = true;
+    }
+}
+
+int scrobbler_init(void)
+{
+    if(!global_settings.audioscrobbler)
+        return -1;
+
+    scrobbler_cache = buffer_alloc(SCROBBLER_MAX_CACHE*SCROBBLER_CACHE_LEN);
+
+    scrobbler_fd = open(SCROBBLER_FILE, O_RDONLY);
+    if(scrobbler_fd < 0)
+    {
+        scrobbler_fd = open(SCROBBLER_FILE, O_RDWR | O_CREAT);
+        if(scrobbler_fd >= 0)
+        {
+            fdprintf(scrobbler_fd, "#AUDIOSCROBBLER/%s\n", SCROBBLER_VERSION);
+            fdprintf(scrobbler_fd, "#TZ/UNKNOWN\n");
+#ifdef CONFIG_RTC
+            fdprintf(scrobbler_fd, "#CLIENT/Rockbox %s %s\n",
+                TARGET_NAME, SCROBBLER_REVISION);
+#else
+            fdprintf(scrobbler_fd, "#CLIENT/Rockbox %s %s Timeless\n",
+                TARGET_NAME, SCROBBLER_REVISION);
+#endif
+            close(scrobbler_fd);
+        }
+        else
+        {
+            logf("SCROBBLER: cannnot create log file");
+            return -1;
+        }
+    }
+    close(scrobbler_fd);
+    scrobbler_fd = -1;
+
+    audio_set_track_changed_event(&scrobbler_change_event);
+    cache_pos = 0;
+    pending = false;
+    scrobbler_initialised = true;
+
+    return 1;
+}
+
+void scrobbler_shutdown(void)
+{
+    if (scrobbler_initialised)
+    {
+        /* check that the last add_to_cache didn't fail */
+        if (cache_pos)
+            write_cache();
+
+        pending = false;
+
+        audio_set_track_changed_event(NULL);
+        scrobbler_initialised = false;
+    }
+}
--- apps/scrobbler.h	(.../../../trunk)	(revision 0)
+++ apps/scrobbler.h	(revision 347)
@@ -0,0 +1,27 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id:$
+ *
+ * Copyright (C) 2006 Robert Keevil
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+ 
+#if CONFIG_CODEC == SWCODEC
+void scrobbler_change_event(struct mp3entry *id);
+int scrobbler_init(void);
+void scrobbler_shutdown(void);
+#else
+#define scrobbler_init()
+#define scrobbler_shutdown()
+#endif
--- apps/main.c	(.../../../trunk)	(revision 347)
+++ apps/main.c	(revision 347)
@@ -66,6 +66,7 @@
 #include "string.h"
 #include "splash.h"
 #include "eeprom_settings.h"
+#include "scrobbler.h"
 
 #if (CONFIG_CODEC == SWCODEC)
 #include "playback.h"
@@ -455,7 +456,8 @@ void init(void)
     status_init();
     playlist_init();
     tree_init();
-        
+    scrobbler_init();
+
     /* No buffer allocation (see buffer.c) may take place after the call to
        audio_init() since the mpeg thread takes the rest of the buffer space */
     mp3_init( global_settings.volume,
--- firmware/export/audio.h	(.../../../trunk)	(revision 347)
+++ firmware/export/audio.h	(revision 347)
@@ -79,6 +79,7 @@ void audio_ff_rewind(long newtime);
 void audio_flush_and_reload_tracks(void);
 struct mp3entry* audio_current_track(void);
 struct mp3entry* audio_next_track(void);
+unsigned long audio_prev_elapsed(void);
 bool audio_has_changed_track(void);
 void audio_get_debugdata(struct audio_debug *dbgdata);
 void audio_set_crossfade(int type);
--- firmware/include/timefuncs.h	(.../../../trunk)	(revision 347)
+++ firmware/include/timefuncs.h	(revision 347)
@@ -27,5 +27,6 @@
 struct tm *get_time(void);
 int set_time(const struct tm *tm);
 bool valid_time(const struct tm *tm);
+time_t mktime(struct tm *t);
 
 #endif /* _TIMEFUNCS_H_ */
--- firmware/include/time.h	(.../../../trunk)	(revision 347)
+++ firmware/include/time.h	(revision 347)
@@ -20,8 +20,7 @@ struct tm
   int	tm_isdst;
 };
 
-#if defined(SIMULATOR) && !defined(_TIME_T_DEFINED) && !defined(_TIME_T_DECLARED)
-/* for non-win32 simulators */
+#if !defined(_TIME_T_DEFINED) && !defined(_TIME_T_DECLARED)
 typedef long time_t;
 
 /* this define below is used by the mingw headers to prevent duplicate
--- firmware/common/timefuncs.c	(.../../../trunk)	(revision 347)
+++ firmware/common/timefuncs.c	(revision 347)
@@ -130,3 +130,38 @@ int set_time(const struct tm *tm)
     return 0;
 #endif
 }
+
+/* mktime() code taken from lynx-2.8.5 source, written
+ by Philippe De Muyter <phdm@macqel.be> */
+time_t mktime(struct tm *t)
+{
+    short month, year;
+    time_t result;
+    static int m_to_d[12] =
+        {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
+
+    month = t->tm_mon;
+    year = t->tm_year + month / 12 + 1900;
+    month %= 12;
+    if (month < 0)
+    {
+        year -= 1;
+        month += 12;
+    }
+    result = (year - 1970) * 365 + (year - 1969) / 4 + m_to_d[month];
+    result = (year - 1970) * 365 + m_to_d[month];
+    if (month <= 1)
+        year -= 1;
+    result += (year - 1968) / 4;
+    result -= (year - 1900) / 100;
+    result += (year - 1600) / 400;
+    result += t->tm_mday;
+    result -= 1;
+    result *= 24;
+    result += t->tm_hour;
+    result *= 60;
+    result += t->tm_min;
+    result *= 60;
+    result += t->tm_sec;
+    return(result);
+}
--- firmware/mpeg.c	(.../../../trunk)	(revision 347)
+++ firmware/mpeg.c	(revision 347)
@@ -108,6 +108,9 @@ static struct trackdata trackdata[MAX_TR
 static unsigned int current_track_counter = 0;
 static unsigned int last_track_counter = 0;
 
+/* Play time of the previous track */
+unsigned long prev_track_elapsed;
+
 #ifndef SIMULATOR
 static int track_read_idx = 0;
 static int track_write_idx = 0;
@@ -1056,6 +1059,9 @@ static void track_change(void)
 {
     DEBUGF("Track change\n");
 
+    struct trackdata *track = get_trackdata(0);
+    prev_track_elapsed = track->id3.elapsed;
+
 #if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F)
     /* Reset the AVC */
     sound_set_avc(-1);
@@ -1072,6 +1078,11 @@ static void track_change(void)
     current_track_counter++;
 }
 
+unsigned long audio_prev_elapsed(void)
+{
+    return prev_track_elapsed;
+}
+
 #ifdef DEBUG
 void hexdump(const unsigned char *buf, int len)
 {
