Index: apps/tagcache.c
===================================================================
RCS file: apps/tagcache.c
diff -N apps/tagcache.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ apps/tagcache.c	27 Dec 2005 07:08:40 -0000
@@ -0,0 +1,1554 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2005 by Miika Pekkarinen
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+#include <stdio.h>
+#include "thread.h"
+#include "kernel.h"
+#include "system.h"
+#include "logf.h"
+#include "string.h"
+#include "usb.h"
+#include "tagcache.h"
+#include "dircache.h"
+#include "metadata.h"
+#include "id3.h"
+#include "settings.h"
+#include "splash.h"
+#include "lang.h"
+
+/* External reference to the big audiobuffer. */
+extern char *audiobuf;
+
+/* Tag Cache thread. */
+static struct event_queue tagcache_queue;
+static long tagcache_stack[(DEFAULT_STACK_SIZE + 0x4000)/sizeof(long)];
+static const char tagcache_thread_name[] = "tagcache";
+
+/* Previous path when scanning directory tree recursively. */
+static char curpath[MAX_PATH*2];
+static long curpath_size = sizeof(curpath);
+
+/* Used when removing duplicates. */
+#define TEMPBUF_SIZE 0x40000
+static char *tempbuf;     /* Allocated when needed. */
+static long tempbufidx;   /* Current location in buffer. */
+static long tempbuf_size; /* Buffer size (TEMPBUF_SIZE). */
+static long tempbuf_left; /* Buffer space left. */
+
+/* Tags we want to be unique (loaded to the tempbuf). */
+static const int unique_tags[] = { tag_artist, tag_album, tag_genre };
+
+/* Queue commands. */
+#define Q_STOP_SCAN     0
+#define Q_START_SCAN    1
+#define Q_FORCE_UPDATE  2
+
+/* Tag database files. */
+#define TAGCACHE_FILE_TEMP    ROCKBOX_DIR "/tagcache_tmp.tcd"
+#define TAGCACHE_FILE_MASTER  ROCKBOX_DIR "/tagcache_idx.tcd"
+#define TAGCACHE_FILE_INDEX   ROCKBOX_DIR "/tagcache_%d.tcd"
+
+/* Tag database structures. */
+#define TAGCACHE_MAGIC  0x01020316
+
+/* Variable-length tag entry in tag files. */
+struct tagfile_entry {
+    short tag_length;
+    char tag_data[0];
+};
+
+/* Fixed-size tag entry in master db index. */
+struct index_entry {
+    long tag_seek[TAG_COUNT];
+};
+
+/* Header is the same in every file. */
+struct tagcache_header {
+    long magic;
+    long datasize;
+    long entry_count;
+};
+
+#ifdef HAVE_TC_RAMCACHE
+/* Header is created when loading database to ram. */
+struct ramcache_header {
+    struct tagcache_header h;
+    struct index_entry *indices;
+    char *tags[TAG_COUNT];
+    int entry_count[TAG_COUNT];
+};
+
+static struct ramcache_header *hdr;
+static bool ramcache = false;
+static long tagcache_size = 0;
+#endif
+
+/** 
+ * Full tag entries stored in a temporary file waiting 
+ * for commit to the cache. */
+struct temp_file_entry {
+    long tag_offset[TAG_COUNT];
+    short tag_length[TAG_COUNT];
+
+    long data_length;
+};
+
+/* Used when building the temporary file. */
+static int cachefd = -1, filenametag_fd;
+static int total_entry_count = 0;
+static int data_size = 0;
+static int processed_dir_count;
+
+#ifdef HAVE_TC_RAMCACHE
+static struct index_entry *find_entry_ram(const char *filename,
+                                          const struct dircache_entry *dc)
+{
+    static long last_pos = 0;
+    int counter = 0;
+    int i;
+    
+    /* Check if we tagcache is loaded into ram. */
+    if (!ramcache)
+        return NULL;
+
+    if (dc == NULL)
+        dc = dircache_get_entry_ptr(filename);
+    
+    if (dc == NULL)
+    {
+        logf("tagcache: file not found.");
+        return NULL;
+    }
+
+    try_again:
+            
+    if (last_pos > 0)
+        i = last_pos;
+    else
+        i = 0;
+    
+    for (; i < hdr->h.entry_count - last_pos; i++)
+    {
+        if (hdr->indices[i].tag_seek[tag_filename] == (long)dc)
+        {
+            last_pos = MAX(0, i - 3);
+            return &hdr->indices[i];
+        }
+        
+        if (++counter == 100)
+        {
+            yield();
+            counter = 0;
+        }
+    }
+
+    if (last_pos > 0)
+    {
+        last_pos = 0;
+        goto try_again;
+    }
+
+    return NULL;
+}
+#endif
+
+/* Allow a little drift to the filename ordering. */
+#define POS_HISTORY_COUNT 3
+
+static struct index_entry *find_entry_disk(const char *filename, bool retrieve)
+{
+    static struct index_entry idx;
+    static long last_pos = -1;
+    long pos_history[POS_HISTORY_COUNT];
+    long pos_history_idx = 0;
+    struct tagcache_header tch;
+    bool found = false;
+    struct tagfile_entry tfe;
+    int masterfd, fd = filenametag_fd;
+    char buf[MAX_PATH];
+    int i;
+    int pos = -1;
+
+    if (fd < 0)
+    {
+        last_pos = -1;
+        return NULL;
+    }
+    
+    check_again:
+    
+    if (last_pos > 0)
+        lseek(fd, last_pos, SEEK_SET);
+    else
+        lseek(fd, sizeof(struct tagcache_header), SEEK_SET);
+
+    while (true)
+    {
+        pos = lseek(fd, 0, SEEK_CUR);
+        for (i = pos_history_idx-1; i >= 0; i--)
+            pos_history[i+1] = pos_history[i];
+        pos_history[0] = pos;
+
+        if (read(fd, &tfe, sizeof(struct tagfile_entry)) !=
+            sizeof(struct tagfile_entry))
+        {
+            break ;
+        }
+        
+        if (tfe.tag_length >= (long)sizeof(buf))
+        {
+            logf("too long tag");
+            close(fd);
+            last_pos = -1;
+            return NULL;
+        }
+        
+        if (read(fd, buf, tfe.tag_length) != tfe.tag_length)
+        {
+            logf("read error #2");
+            close(fd);
+            last_pos = -1;
+            return NULL;
+        }
+        
+        if (!strcasecmp(filename, buf))
+        {
+            last_pos = pos_history[pos_history_idx];
+            found = true;
+            break ;
+        }
+        
+        if (pos_history_idx < POS_HISTORY_COUNT - 1)
+            pos_history_idx++;
+    }
+    
+    /* Not found? */
+    if (!found)
+    {
+        if (last_pos > 0)
+        {
+            last_pos = -1;
+            logf("seek again");
+            goto check_again;
+        }
+        //close(fd);
+        return NULL;
+    }
+    
+    if (!retrieve)
+    {
+        /* Just return a valid pointer without a valid entry. */
+        return &idx;
+    }
+    
+    /* Found. Now read the index_entry (if requested). */
+    masterfd = open(TAGCACHE_FILE_MASTER, O_RDONLY);
+    if (masterfd < 0)
+    {
+        logf("open fail");
+        return NULL;
+    }
+    
+    if (read(fd, &tch, sizeof(struct tagcache_header)) !=
+        sizeof(struct tagcache_header) || tch.magic != TAGCACHE_MAGIC)
+    {
+        logf("header error");
+        return NULL;
+    }
+    
+    for (i = 0; i < tch.entry_count; i++)
+    {
+        if (read(masterfd, &idx, sizeof(struct index_entry)) !=
+            sizeof(struct index_entry))
+        {
+            logf("read error #3");
+            close(fd);
+            return NULL;
+        }
+        
+        if (idx.tag_seek[tag_filename] == pos)
+            break ;
+    }
+    close(masterfd);
+    
+    /* Not found? */
+    if (i == tch.entry_count)
+    {
+        logf("not found!");
+        return NULL;
+    }
+    
+    return &idx;
+}
+
+static bool build_lookup_list(struct tagcache_search *tcs)
+{
+    struct tagcache_header header;
+    struct index_entry entry;
+    int masterfd;
+    int i;
+    
+    tcs->seek_list_count = 0;
+    
+#ifdef HAVE_TC_RAMCACHE
+    if (tcs->ramsearch)
+    {
+        int j;
+
+        for (i = tcs->seek_pos; i < hdr->h.entry_count - tcs->seek_pos; i++)
+        {
+            if (tcs->seek_list_count == SEEK_LIST_SIZE)
+                break ;
+
+            for (j = 0; j < tcs->filter_count; j++)
+            {
+                if (hdr->indices[i].tag_seek[tcs->filter_tag[j]] !=
+                    tcs->filter_seek[j])
+                    break ;
+            }
+            
+            if (j < tcs->filter_count)
+                continue ;
+            
+            /* Add to the seek list if not already there. */
+            for (j = 0; j < tcs->seek_list_count; j++)
+            {
+                if (tcs->seek_list[j] == hdr->indices[i].tag_seek[tcs->type])
+                    break ;
+            }
+
+            /* Lets add it. */
+            if (j == tcs->seek_list_count)
+            {
+                tcs->seek_list[tcs->seek_list_count] =
+                        hdr->indices[i].tag_seek[tcs->type];
+                tcs->seek_list_count++;
+            }
+        }
+        
+        tcs->seek_pos = i;
+
+        return tcs->seek_list_count > 0;
+    }
+#endif
+    
+    masterfd = open(TAGCACHE_FILE_MASTER, O_RDONLY);
+    if (masterfd < 0)
+    {
+        logf("cannot open master index");
+        return false;
+    }
+        
+    /* Load the header. */
+    if (read(masterfd, &header, sizeof(struct tagcache_header)) !=
+        sizeof(struct tagcache_header) || header.magic != TAGCACHE_MAGIC)
+    {
+        logf("read error");
+        close(masterfd);
+        return false;
+    }
+
+    lseek(masterfd, tcs->seek_pos * sizeof(struct index_entry) +
+            sizeof(struct tagcache_header), SEEK_SET);
+    
+    while (read(masterfd, &entry, sizeof(struct index_entry)) ==
+           sizeof(struct index_entry))
+    {
+        if (tcs->seek_list_count == SEEK_LIST_SIZE)
+            break ;
+
+        for (i = 0; i < tcs->filter_count; i++)
+        {
+            if (entry.tag_seek[tcs->filter_tag[i]] != tcs->filter_seek[i])
+                break ;
+        }
+        
+        tcs->seek_pos++;
+        
+        if (i < tcs->filter_count)
+            continue ;
+        
+        /* Add to the seek list if not already there. */
+        for (i = 0; i < tcs->seek_list_count; i++)
+        {
+            if (tcs->seek_list[i] == entry.tag_seek[tcs->type])
+                break ;
+        }
+
+        /* Lets add it. */
+        if (i == tcs->seek_list_count)
+        {
+            tcs->seek_list[tcs->seek_list_count] =
+                    entry.tag_seek[tcs->type];
+            tcs->seek_list_count++;
+        }
+        
+    }
+    close(masterfd);
+
+    return tcs->seek_list_count > 0;
+}
+
+bool tagcache_search(struct tagcache_search *tcs, int tag)
+{
+    struct tagcache_header h;
+    char buf[MAX_PATH];
+
+    tcs->position = sizeof(struct tagcache_header);
+    tcs->fd = -1;
+    tcs->type = tag;
+    tcs->seek_pos = 0;
+    tcs->seek_list_count = 0;
+    tcs->filter_count = 0;
+
+#ifndef HAVE_TC_RAMCACHE
+    tcs->ramsearch = false;
+#else
+    tcs->ramsearch = ramcache;
+    if (tcs->ramsearch)
+    {
+        tcs->entry_count = hdr->entry_count[tcs->type];
+    }
+    else
+#endif
+    {
+        snprintf(buf, sizeof buf, TAGCACHE_FILE_INDEX, tcs->type);
+        tcs->fd = open(buf, O_RDONLY);
+        if (tcs->fd < 0)
+        {
+            logf("failed to open index");
+            return false;
+        }
+
+        /* Check the header. */
+        if (read(tcs->fd, &h, sizeof(struct tagcache_header)) !=
+            sizeof(struct tagcache_header) || h.magic != TAGCACHE_MAGIC)
+        {
+            logf("incorrect header");
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool tagcache_search_add_filter(struct tagcache_search *tcs,
+                                int tag, int seek)
+{
+    if (tcs->filter_count == TAGCACHE_MAX_FILTERS)
+        return false;
+
+    tcs->filter_tag[tcs->filter_count] = tag;
+    tcs->filter_seek[tcs->filter_count] = seek;
+    tcs->filter_count++;
+
+    return true;
+}
+
+bool tagcache_get_next(struct tagcache_search *tcs)
+{
+    static char buf[MAX_PATH];
+    struct tagfile_entry entry;
+    
+    if (tcs->fd < 0 
+#ifdef HAVE_TC_RAMCACHE
+        && !tcs->ramsearch
+#endif
+        )
+        return false;
+    
+    /* Relative fetch. */
+    if (tcs->filter_count > 0)
+    {
+        /* Check for end of list. */
+        if (tcs->seek_list_count == 0)
+        {
+            /* Try to fetch more. */
+            if (!build_lookup_list(tcs))
+                return false;
+        }
+        
+        tcs->seek_list_count--;
+        
+        /* Seek stream to the correct position and continue to direct fetch. */
+        if (!tcs->ramsearch)
+            lseek(tcs->fd, tcs->seek_list[tcs->seek_list_count], SEEK_SET);
+        else
+            tcs->position = tcs->seek_list[tcs->seek_list_count];
+    }
+    
+    /* Direct fetch. */
+#ifdef HAVE_TC_RAMCACHE
+    if (tcs->ramsearch)
+    {
+        struct tagfile_entry *ep;
+        
+        if (tcs->entry_count == 0)
+            return false;
+        tcs->entry_count--;
+        tcs->result_seek = tcs->position;
+        
+        if (tcs->type == tag_filename)
+        {
+            dircache_copy_path((struct dircache_entry *)tcs->position,
+                                buf, sizeof buf);
+            tcs->result = buf;
+            tcs->result_len = strlen(buf) + 1;
+            
+            return true;
+        }
+        
+        ep = (struct tagfile_entry *)&hdr->tags[tcs->type][tcs->position];
+        tcs->position += sizeof(struct tagfile_entry) + ep->tag_length;
+        tcs->result = ep->tag_data;
+        tcs->result_len = ep->tag_length;
+
+        return true;
+    }
+    else
+#endif
+    {
+        tcs->result_seek = lseek(tcs->fd, 0, SEEK_CUR);
+        if (read(tcs->fd, &entry, sizeof(struct tagfile_entry)) !=
+            sizeof(struct tagfile_entry))
+        {
+            /* End of data. */
+            return false;
+        }
+    }
+    
+    if (entry.tag_length > (long)sizeof(buf))
+    {
+        logf("too long tag");
+        return false;
+    }
+    
+    if (read(tcs->fd, buf, entry.tag_length) != entry.tag_length)
+    {
+        logf("read error");
+        return false;
+    }
+    
+    tcs->result = buf;
+    tcs->result_len = entry.tag_length;
+    
+    return true;
+}
+
+void tagcache_search_finish(struct tagcache_search *tcs)
+{
+    if (tcs->fd >= 0)
+    {
+        close(tcs->fd);
+        tcs->fd = -1;
+    }
+}
+
+#ifdef HAVE_TC_RAMCACHE
+struct tagfile_entry *get_tag(struct index_entry *entry, int tag)
+{
+    return (struct tagfile_entry *)&hdr->tags[tag][entry->tag_seek[tag]];
+}
+
+bool tagcache_fill_tags(struct mp3entry *id3, const char *filename)
+{
+    struct index_entry *entry;
+    
+    /* Find the corresponding entry in tagcache. */
+    entry = find_entry_ram(filename, NULL);
+    if (entry == NULL || !ramcache)
+        return false;
+    
+    id3->title = get_tag(entry, tag_title)->tag_data;
+    id3->artist = get_tag(entry, tag_artist)->tag_data;
+    id3->album = get_tag(entry, tag_album)->tag_data;
+    id3->genre_string = get_tag(entry, tag_genre)->tag_data;
+
+    return true;
+}
+#endif
+
+static inline void write_item(const char *item)
+{
+    int len = strlen(item) + 1;
+    
+    data_size += len;
+    write(cachefd, item, len);
+}
+
+inline void check_if_empty(char **tag)
+{
+    if (tag == NULL || *tag == NULL || *tag[0] == '\0')
+        *tag = "Unknown";
+}
+
+#define CRC_BUF_LEN 8
+
+#ifdef HAVE_TC_RAMCACHE
+static void add_tagcache(const char *path, const struct dircache_entry *dc)
+#else
+static void add_tagcache(const char *path)
+#endif
+{
+    struct track_info track;
+    struct temp_file_entry entry;
+    bool ret;
+    int fd;
+    //uint32_t crcbuf[CRC_BUF_LEN];
+
+    if (cachefd < 0)
+        return ;
+
+    /* Check if the file is supported. */
+    if (probe_file_format(path) == AFMT_UNKNOWN)
+        return ;
+    
+    processed_dir_count++;
+    
+    /* Check if the file is already cached. */
+#ifdef HAVE_TC_RAMCACHE
+    if (ramcache)
+    {
+        if (find_entry_ram(path, dc))
+            return ;
+    }
+    else
+#endif
+    {
+        if (find_entry_disk(path, false))
+            return ;
+    }
+    
+    fd = open(path, O_RDONLY);
+    if (fd < 0)
+    {
+        logf("open fail: %s", path);
+        return ;
+    }
+
+    memset(&track, 0, sizeof(struct track_info));
+    ret = get_metadata(&track, fd, path, false);
+    close(fd);
+
+    if (!ret)
+    {
+        track.id3.title = (char *)path;
+        track.id3.artist = "Unknown";
+        track.id3.album = "Unknown";
+        track.id3.genre_string = "Unknown";
+    }
+    else
+        check_if_empty(&track.id3.title);
+    check_if_empty(&track.id3.artist);
+    check_if_empty(&track.id3.album);
+    check_if_empty(&track.id3.genre_string);
+    
+    entry.tag_length[tag_filename] = strlen(path) + 1;
+    entry.tag_length[tag_title] = strlen(track.id3.title) + 1;
+    entry.tag_length[tag_artist] = strlen(track.id3.artist) + 1;
+    entry.tag_length[tag_album] = strlen(track.id3.album) + 1;
+    entry.tag_length[tag_genre] = strlen(track.id3.genre_string) + 1;
+    
+    entry.tag_offset[tag_filename] = 0;
+    entry.tag_offset[tag_title] = entry.tag_offset[tag_filename] + entry.tag_length[tag_filename];
+    entry.tag_offset[tag_artist] = entry.tag_offset[tag_title] + entry.tag_length[tag_title];
+    entry.tag_offset[tag_album] = entry.tag_offset[tag_artist] + entry.tag_length[tag_artist];
+    entry.tag_offset[tag_genre] = entry.tag_offset[tag_album] + entry.tag_length[tag_album];
+    entry.data_length = entry.tag_offset[tag_genre] + entry.tag_length[tag_genre];
+    
+    write(cachefd, &entry, sizeof(struct temp_file_entry));
+    write_item(path);
+    write_item(track.id3.title);
+    write_item(track.id3.artist);
+    write_item(track.id3.album);
+    write_item(track.id3.genre_string);
+    total_entry_count++;    
+}
+
+static void remove_files(void)
+{
+    int i;
+    char buf[MAX_PATH];
+    
+    remove(TAGCACHE_FILE_MASTER);
+    for (i = 0; i < TAG_COUNT; i++)
+    {
+        snprintf(buf, sizeof buf, TAGCACHE_FILE_INDEX, i);
+        remove(buf);
+    }
+}
+
+static long unique_insert(char *data)
+{
+    struct tagfile_entry *entry;
+    int len = strlen(data)+1;
+    int i;
+    
+    entry = (struct tagfile_entry *)tempbuf;
+    for (i = 0; i < tempbufidx; i++)
+    {
+        if (!strcasecmp(data, entry->tag_data))
+        {
+            return (int)entry - (int)tempbuf;
+        }
+        entry = (struct tagfile_entry *)&entry->tag_data[entry->tag_length];
+    }
+    tempbuf_left -= len + sizeof(struct tagfile_entry);
+    if (tempbuf_left < 0)
+        return -1;
+    
+    tempbufidx++;
+    entry->tag_length = len;
+    memcpy(entry->tag_data, data, len);
+
+    return (int)entry - (int)tempbuf;
+}
+
+static bool is_unique_tag(int type)
+{
+    int i;
+
+    for (i = 0; i < (int)(sizeof(unique_tags)/sizeof(unique_tags[0])); i++)
+    {
+        if (type == unique_tags[i])
+            return true;
+    }
+
+    return false;
+}
+
+static bool build_index(int index_type, struct tagcache_header *h, int tmpfd)
+{
+    int i;
+    struct tagcache_header tch;
+    struct temp_file_entry entry;
+    struct index_entry idx;
+    char buf[MAX_PATH];
+    int fd = -1, masterfd;
+    bool error = false;
+    bool init;
+    
+    logf("Building index: %d", index_type);
+    
+    tempbufidx = 0;
+    tempbuf_left = tempbuf_size;
+    
+    snprintf(buf, sizeof buf, TAGCACHE_FILE_INDEX, index_type);
+    fd = open(buf, O_RDWR);
+
+    if (fd >= 0)
+    {
+        if (read(fd, &tch, sizeof(struct tagcache_header)) !=
+            sizeof(struct tagcache_header) || tch.magic != TAGCACHE_MAGIC)
+        {
+            logf("header error");
+            close(fd);
+            return false;
+        }
+        tempbufidx = tch.entry_count;
+            
+        /* Load the tag file. */
+        if (is_unique_tag(index_type))
+        {
+            if (tch.datasize >= tempbuf_left)
+            {
+                logf("not enough buffer");
+                close(fd);
+                return false;
+            }
+
+            if (read(fd, tempbuf, tch.datasize) != tch.datasize)
+            {
+                logf("read error");
+                close(fd);
+                return false;
+            }
+
+            tempbuf_left -= tch.datasize;
+        }
+        else
+            lseek(fd, 0, SEEK_END);
+    }
+    else
+    {
+        fd = open(buf, O_WRONLY | O_CREAT | O_TRUNC);
+        tch.magic = TAGCACHE_MAGIC;
+        tch.entry_count = 0;
+        tch.datasize = 0;
+        if (write(fd, &tch, sizeof(struct tagcache_header)) !=
+            sizeof(struct tagcache_header))
+        {
+            logf("header write failed");
+            close(fd);
+            return false;
+        }
+    }
+    
+    if (fd < 0)
+    {
+        logf("%s open fail", buf);
+        return false;
+    }
+
+    logf("Loading index file");
+    masterfd = open(TAGCACHE_FILE_MASTER, O_RDWR);
+    
+    if (masterfd < 0)
+    {
+        logf("Creating new index");
+        masterfd = open(TAGCACHE_FILE_MASTER, O_WRONLY | O_CREAT | O_TRUNC);
+        
+        if (masterfd < 0)
+        {
+            logf("Failure to create index file");
+            close(fd);
+            return false;
+        }
+        
+        /* Write the header (write real values later). */
+        tch = *h;
+        tch.entry_count = 0;
+        tch.datasize = 0;
+        write(masterfd, &tch, sizeof(struct tagcache_header));
+        init = true;
+    }
+    else
+    {
+        init = false;
+        
+        if (read(masterfd, &tch, sizeof(struct tagcache_header)) !=
+            sizeof(struct tagcache_header) || tch.magic != TAGCACHE_MAGIC)
+        {
+            logf("header error");
+            close(fd);
+            close(masterfd);
+            return false;
+        }
+        
+        /* Append to the index? */
+        i = lseek(masterfd, tch.entry_count * sizeof(struct index_entry),
+            SEEK_CUR);
+        if (i == filesize(masterfd))
+        {
+            logf("appending...");
+            init = true;
+        }
+    }
+    
+    lseek(tmpfd, sizeof(struct tagcache_header), SEEK_SET);
+    
+    for (i = 0; i < h->entry_count; i++)
+    {
+        /* Read entry headers. */
+        
+        //logf("pos0=0x%02x", lseek(tmpfd, 0, SEEK_CUR));
+        if (read(tmpfd, &entry, sizeof(struct temp_file_entry)) !=
+            sizeof(struct temp_file_entry))
+        {
+            logf("read fail #1");
+            error = true;
+            break ;
+        }
+
+        //logf("pos1=0x%02x", lseek(tmpfd, 0, SEEK_CUR));
+        if (init)
+        {
+            memset(&idx, 0, sizeof(struct index_entry));
+        }
+        else
+        {
+            if (read(masterfd, &idx, sizeof(struct index_entry)) !=
+                sizeof(struct index_entry))
+            {
+                logf("read fail #2");
+                error = true;
+                break ;
+            }
+            lseek(masterfd, -sizeof(struct index_entry), SEEK_CUR);
+        }
+
+        /* Read data. */
+        if (entry.tag_length[index_type] >= (long)sizeof(buf))
+        {
+            logf("too long entry!");
+            logf("length=%d", entry.tag_length[index_type]);
+            logf("pos=0x%02x", lseek(tmpfd, 0, SEEK_CUR));
+            error = true;
+            break ;
+        }
+
+        //logf("offset=0x%02x", entry.tag_offset[index_type]);
+        lseek(tmpfd, entry.tag_offset[index_type], SEEK_CUR);
+        //logf("pos2=0x%02x", lseek(tmpfd, 0, SEEK_CUR));
+        if (read(tmpfd, buf, entry.tag_length[index_type]) !=
+            entry.tag_length[index_type])
+        {
+            logf("read fail #3");
+            logf("offset=0x%02x", entry.tag_offset[index_type]);
+            logf("length=0x%02x", entry.tag_length[index_type]);
+            error = true;
+            break ;
+        }
+
+        /* Insert buffer. */
+        if (is_unique_tag(index_type))
+        {
+            idx.tag_seek[index_type] = unique_insert(buf) +
+                    sizeof(struct tagcache_header);
+        }
+        else
+        {
+            struct tagfile_entry entry;
+            idx.tag_seek[index_type] = lseek(fd, 0, SEEK_CUR);
+            entry.tag_length = strlen(buf) + 1;
+            write(fd, &entry, sizeof(struct tagfile_entry));
+            write(fd, buf, entry.tag_length);
+            tempbufidx++;
+        }
+
+        //idx.tag_seek[index_type] = index_type * 2;
+        if (idx.tag_seek[index_type] < 0)
+        {
+            logf("temp buffer ran out of space");
+            error = true;
+            break ;
+        }
+
+        /* Write index. */
+        if (write(masterfd, &idx, sizeof(struct index_entry)) !=
+            sizeof(struct index_entry))
+        {
+            logf("tagcache: write fail #4");
+            error = true;
+            break ;
+        }
+        
+        //logf("pos3=0x%02x", lseek(tmpfd, 0, SEEK_CUR));
+        /* Skip to next. */
+        lseek(tmpfd, entry.data_length - entry.tag_offset[index_type] -
+              entry.tag_length[index_type], SEEK_CUR);
+        
+        //logf("pos4=0x%02x", lseek(tmpfd, 0, SEEK_CUR));
+    }
+
+    /* Finally write the uniqued tag index file. */
+    lseek(fd, 0, SEEK_SET);
+    if (is_unique_tag(index_type))
+    {
+        tch.magic = TAGCACHE_MAGIC;
+        tch.entry_count = tempbufidx;
+        tch.datasize = tempbuf_size - tempbuf_left;
+        write(fd, &tch, sizeof(struct tagcache_header));
+        write(fd, tempbuf, tempbuf_size - tempbuf_left);
+    }
+    else
+    {
+        tch.magic = TAGCACHE_MAGIC;
+        tch.entry_count = tempbufidx;
+        tch.datasize = lseek(fd, 0, SEEK_CUR) - sizeof(struct tagcache_header);
+        write(fd, &tch, sizeof(struct tagcache_header));
+    }
+    
+    close(fd);
+    close(masterfd);
+
+    return !error;
+}
+
+static bool commit(void)
+{
+    struct tagcache_header header, header_old;
+    int i, len, rc;
+    int tmpfd;
+    int masterfd;
+    
+    logf("committing tagcache");
+    
+    tmpfd = open(TAGCACHE_FILE_TEMP, O_RDONLY);
+    if (tmpfd < 0)
+    {
+        logf("nothing to commit");
+        return true;
+    }
+    
+    /* Load the header. */
+    len = sizeof(struct tagcache_header);
+    rc = read(tmpfd, &header, len);
+    
+    if (header.magic != TAGCACHE_MAGIC || rc != len)
+    {
+        logf("incorrect header");
+        close(tmpfd);
+        remove_files();
+        remove(TAGCACHE_FILE_TEMP);
+        return false;
+    }
+
+    if (header.entry_count == 0)
+    {
+        logf("nothing to commit");
+        close(tmpfd);
+        remove(TAGCACHE_FILE_TEMP);
+        return true;
+    }
+
+    if (tempbuf_size == 0)
+    {
+        logf("delaying commit until next boot");
+        close(tmpfd);
+        return false;
+    }
+    
+    logf("commit %d entries...", header.entry_count);
+    
+    /* Now create the index files. */
+    for (i = 0; i < TAG_COUNT; i++)
+    {
+        if (!build_index(i, &header, tmpfd))
+        {
+            logf("tagcache failed init");
+            remove_files();
+            return false;
+        }
+    }
+    
+    close(tmpfd);
+    
+    /* Update the master index headers. */
+    masterfd = open(TAGCACHE_FILE_MASTER, O_RDWR);
+    if (masterfd < 0)
+    {
+        logf("failed to open master index");
+        return false;
+    }
+
+    if (read(masterfd, &header_old, sizeof(struct tagcache_header))
+        != sizeof(struct tagcache_header) ||
+        header_old.magic != TAGCACHE_MAGIC)
+    {
+        logf("incorrect header");
+        close(masterfd);
+        remove_files();
+        return false;
+    }
+
+    header.entry_count += header_old.entry_count;
+    header.datasize += header_old.datasize;
+
+    lseek(masterfd, 0, SEEK_SET);
+    write(masterfd, &header, sizeof(struct tagcache_header));
+    close(masterfd);
+    
+    logf("tagcache committed");
+    remove(TAGCACHE_FILE_TEMP);
+    
+    return true;
+}
+
+static void allocate_tempbuf(void)
+{
+    /* Yeah, malloc would be really nice now :) */
+    tempbuf = audiobuf;
+    tempbuf_size = TEMPBUF_SIZE;
+    audiobuf += TEMPBUF_SIZE;
+}
+
+static void free_tempbuf(void)
+{
+    if (tempbuf_size == 0)
+        return ;
+    
+    audiobuf -= TEMPBUF_SIZE;
+    tempbuf = NULL;
+    tempbuf_size = 0;
+}
+
+#ifdef HAVE_TC_RAMCACHE
+static bool allocate_tagcache(void)
+{
+    int rc, len;
+    int fd;
+
+    hdr = NULL;
+    
+    fd = open(TAGCACHE_FILE_MASTER, O_RDONLY);
+    if (fd < 0)
+    {
+        logf("no tagcache file found.");
+        return false;
+    }
+
+    /* Load the header. */
+    hdr = (struct ramcache_header *)audiobuf;
+    memset(hdr, 0, sizeof(struct ramcache_header));
+    len = sizeof(struct tagcache_header);
+    rc = read(fd, &hdr->h, len);
+    close(fd);
+    
+    if (hdr->h.magic != TAGCACHE_MAGIC || rc != len)
+    {
+        logf("incorrect header");
+        remove_files();
+        hdr = NULL;
+        return false;
+    }
+
+    hdr->indices = (struct index_entry *)(hdr + 1);
+    
+    /* Now calculate the required cache size. */
+    tagcache_size = hdr->h.datasize + 
+        sizeof(struct index_entry) * hdr->h.entry_count +
+        sizeof(struct ramcache_header) + TAG_COUNT*sizeof(void *);
+    logf("tagcache: %d bytes allocated.", tagcache_size);
+    logf("at: 0x%04x", audiobuf);
+    audiobuf += (long)((tagcache_size & ~0x03) + 0x04);
+
+    return true;
+}
+
+static bool load_tagcache(void)
+{
+    struct tagcache_header *tch;
+    long bytesleft = tagcache_size;
+    struct index_entry *idx;
+    int rc, fd;
+    char *p;
+    int i;
+
+    /* We really need the dircache for this. */
+    while (!dircache_is_enabled())
+        sleep(HZ);
+
+    logf("loading tagcache to ram...");
+    
+    fd = open(TAGCACHE_FILE_MASTER, O_RDONLY);
+    if (fd < 0)
+    {
+        logf("tagcache open failed");
+        return false;
+    }
+
+    lseek(fd, sizeof(struct tagcache_header), SEEK_SET);
+    
+    idx = hdr->indices;
+
+    /* Load the master index table. */
+    for (i = 0; i < hdr->h.entry_count; i++)
+    {
+        rc = read(fd, idx, sizeof(struct index_entry));
+        if (rc != sizeof(struct index_entry))
+        {
+            logf("read error #1");
+            close(fd);
+            return false;
+        }
+    
+        bytesleft -= sizeof(struct index_entry);
+        if (bytesleft < 0 || ((long)idx - (long)hdr->indices) >= tagcache_size)
+        {
+            logf("too big tagcache.");
+            close(fd);
+            return false;
+        }
+
+        idx++;
+    }
+
+    close(fd);
+
+    /* Load the tags. */
+    p = (char *)idx;
+    for (i = 0; i < TAG_COUNT; i++)
+    {
+        struct tagfile_entry *fe;
+        char buf[MAX_PATH];
+
+        //p = ((void *)p+1);
+        p = (char *)((long)p & ~0x03) + 0x04;
+        hdr->tags[i] = p;
+
+        snprintf(buf, sizeof buf, TAGCACHE_FILE_INDEX, i);
+        fd = open(buf, O_RDONLY);
+        
+        if (fd < 0)
+        {
+            logf("%s open fail", buf);
+            return false;
+        }
+
+        /* Check the header. */
+        tch = (struct tagcache_header *)p;
+        rc = read(fd, tch, sizeof(struct tagcache_header));
+        p += rc;
+        if (rc != sizeof(struct tagcache_header) ||
+            tch->magic != TAGCACHE_MAGIC)
+        {
+            logf("incorrect header");
+            close(fd);
+            return false;
+        }
+        
+        for (hdr->entry_count[i] = 0;
+             hdr->entry_count[i] < tch->entry_count;
+             hdr->entry_count[i]++)
+        {
+            yield();
+            fe = (struct tagfile_entry *)p;
+            rc = read(fd, fe, sizeof(struct tagfile_entry));
+            if (rc != sizeof(struct tagfile_entry))
+            {
+                /* End of lookup table. */
+                logf("read error");
+                close(fd);
+                return false;
+            }
+
+            /* We have a special handling for the filename tags. */
+            if (i == tag_filename)
+            {
+                const struct dircache_entry *dc;
+                
+                if (fe->tag_length >= (long)sizeof(buf)-1)
+                {
+                    logf("too long filename");
+                    close(fd);
+                    return false;
+                }
+                
+                rc = read(fd, buf, fe->tag_length);
+                if (rc != fe->tag_length)
+                {
+                    logf("read error #3");
+                    close(fd);
+                    return false;
+                }
+                
+                dc = dircache_get_entry_ptr(buf);
+                if (dc == NULL)
+                {
+                    logf("Entry no longer valid.");
+                    logf("-> %s", buf);
+                    continue ;
+                }
+
+                hdr->indices[hdr->entry_count[i]].tag_seek[tag_filename]
+                        = (long)dc;
+                
+                continue ;
+            }
+
+            bytesleft -= sizeof(struct tagfile_entry) + fe->tag_length;
+            if (bytesleft < 0)
+            {
+                logf("too big tagcache.");
+                close(fd);
+                return false;
+            }
+
+            p = fe->tag_data;
+            rc = read(fd, fe->tag_data, fe->tag_length);
+            p += rc;
+
+            if (rc != fe->tag_length)
+            {
+                logf("read error #4");
+                logf("rc=0x%04x", rc); // 0x431
+                logf("len=0x%04x", fe->tag_length); // 0x4000
+                logf("pos=0x%04x", lseek(fd, 0, SEEK_CUR)); // 0x433
+                logf("i=0x%02x", i); // 0x00
+                close(fd);
+                return false;
+            }
+        }
+        close(fd);
+    }
+    
+    logf("tagcache loaded into ram!");
+
+    return true;
+}
+#endif
+
+static bool check_dir(const char *dirname)
+{
+    DIRCACHED *dir;
+    int len;
+    int success = false;
+
+    dir = opendir_cached(dirname);
+    if (!dir)
+    {
+        logf("tagcache: opendir_cached() failed");
+        return false;
+    }
+    
+    /* Recursively scan the dir. */
+    while (queue_empty(&tagcache_queue))
+    {
+        struct dircache_entry *entry;
+
+        entry = readdir_cached(dir);
+        if (entry == NULL)
+        {
+            success = true;
+            break ;
+        }
+        
+        if (!strcmp(entry->d_name, ".") ||
+            !strcmp(entry->d_name, ".."))
+            continue;
+
+        yield();
+        
+        len = strlen(curpath);
+        snprintf(&curpath[len], curpath_size - len, "/%s",
+                 entry->d_name);
+        
+        if (entry->attribute & ATTR_DIRECTORY)
+            check_dir(curpath);
+        else
+#ifdef HAVE_TC_RAMCACHE
+            add_tagcache(curpath, dir->internal_entry);
+#else
+            add_tagcache(curpath);
+#endif
+
+        curpath[len] = '\0';
+    }
+    
+    closedir_cached(dir);
+
+    return success;
+}
+
+static void build_tagcache(void)
+{
+    struct tagcache_header header;
+    bool ret;
+    char buf[MAX_PATH];
+
+    curpath[0] = '\0';
+    data_size = 0;
+    total_entry_count = 0;
+    processed_dir_count = 0;
+    
+    logf("updating tagcache");
+    
+    cachefd = open(TAGCACHE_FILE_TEMP, O_RDONLY);
+    if (cachefd >= 0)
+    {
+        logf("skipping, cache already waiting for commit");
+        close(cachefd);
+        return ;
+    }
+    
+    cachefd = open(TAGCACHE_FILE_TEMP, O_RDWR | O_CREAT | O_TRUNC);
+    if (cachefd < 0)
+    {
+        logf("master file open failed");
+        return ;
+    }
+
+    snprintf(buf, sizeof buf, TAGCACHE_FILE_INDEX, tag_filename);
+    filenametag_fd = open(buf, O_RDONLY);
+
+    if (filenametag_fd >= 0)
+    {
+        if (read(filenametag_fd, &header, sizeof(struct tagcache_header)) !=
+            sizeof(struct tagcache_header) || header.magic != TAGCACHE_MAGIC)
+        {
+            logf("header error");
+            close(filenametag_fd);
+            filenametag_fd = -1;
+        }
+    }
+
+            
+    cpu_boost(true);
+
+    /* Scan for new files. */
+    memset(&header, 0, sizeof(struct tagcache_header));
+    write(cachefd, &header, sizeof(struct tagcache_header));
+
+    //strcpy(curpath, "/Best");
+    ret = check_dir("/");
+    
+    /* Write the header. */
+    header.magic = TAGCACHE_MAGIC;
+    header.datasize = data_size;
+    header.entry_count = total_entry_count;
+    lseek(cachefd, 0, SEEK_SET);
+    write(cachefd, &header, sizeof(struct tagcache_header));
+    close(cachefd);
+
+    if (filenametag_fd >= 0)
+    {
+        close(filenametag_fd);
+        filenametag_fd = -1;
+    }
+
+    if (!ret)
+    {
+        logf("Aborted.");
+        cpu_boost(false);
+        return ;
+    }
+
+    /* Commit changes to the database. */
+    if (commit())
+    {
+        remove(TAGCACHE_FILE_TEMP);
+        logf("tagcache built!");
+    }
+    
+    cpu_boost(false);
+}
+
+#ifdef HAVE_TC_RAMCACHE
+static void load_ramcache(void)
+{
+    if (!hdr)
+        return ;
+        
+    cpu_boost(true);
+    
+    /* At first we should load the cache (if exists). */
+    ramcache = load_tagcache();
+
+    if (!ramcache)
+    {
+        hdr = NULL;
+        remove_files();
+    }
+    
+    cpu_boost(false);
+}
+#endif
+
+static void tagcache_thread(void)
+{
+    struct event ev;
+    bool check_done = false;
+
+    while (1)
+    {
+        queue_wait_w_tmo(&tagcache_queue, &ev, HZ);
+
+        switch (ev.id)
+        {
+            case Q_START_SCAN:
+                check_done = false;
+                break ;
+
+            case Q_FORCE_UPDATE:
+                //remove_files();
+                build_tagcache();
+                break ;
+                
+#ifdef HAVE_TC_RAMCACHE
+            case SYS_TIMEOUT:
+                if (check_done || !dircache_is_enabled())
+                    break ;
+                
+                if (!ramcache && global_settings.tagcache_ram)
+                    load_ramcache();
+    
+                if (global_settings.tagcache_ram)
+                    build_tagcache();
+                
+                check_done = true;
+                break ;
+#endif
+
+            case Q_STOP_SCAN:
+                break ;
+                
+            case SYS_POWEROFF:
+                break ;
+                
+#ifndef SIMULATOR
+            case SYS_USB_CONNECTED:
+                usb_acknowledge(SYS_USB_CONNECTED_ACK);
+                usb_wait_for_disconnect(&tagcache_queue);
+                break ;
+#endif
+        }
+    }
+}
+
+int tagcache_get_progress(void)
+{
+    int total_count = -1;
+
+#ifdef HAVE_DIRCACHE
+    if (dircache_is_enabled())
+    {
+        total_count = dircache_get_entry_count();
+    }
+    else
+    {
+        if (hdr)
+            total_count = hdr->h.entry_count;
+    }
+#endif
+
+    if (total_count < 0)
+        return -1;
+    
+    return processed_dir_count * 100 / total_count;
+}
+
+void tagcache_start_scan(void)
+{
+    queue_post(&tagcache_queue, Q_START_SCAN, 0);
+}
+
+bool tagcache_force_update(void)
+{
+    queue_post(&tagcache_queue, Q_FORCE_UPDATE, 0);
+    gui_syncsplash(HZ*2, true, str(LANG_TAGCACHE_FORCE_UPDATE_SPLASH));
+    
+    return false;
+}
+
+void tagcache_stop_scan(void)
+{
+    queue_post(&tagcache_queue, Q_STOP_SCAN, 0);
+}
+
+void tagcache_init(void)
+{
+    
+    /* If the previous cache build/update was interrupted, commit
+     * the changes first. */
+    allocate_tempbuf();
+    commit();
+    free_tempbuf();
+    
+#ifdef HAVE_TC_RAMCACHE
+    /* Allocate space for the tagcache if found on disk. */
+    allocate_tagcache();
+#endif
+    
+    queue_init(&tagcache_queue);
+    create_thread(tagcache_thread, tagcache_stack,
+                  sizeof(tagcache_stack), tagcache_thread_name);
+}
+
+
Index: apps/tagcache.h
===================================================================
RCS file: apps/tagcache.h
diff -N apps/tagcache.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ apps/tagcache.h	27 Dec 2005 07:08:40 -0000
@@ -0,0 +1,65 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2005 by Miika Pekkarinen
+ *
+ * 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.
+ *
+ ****************************************************************************/
+#ifndef _TAGCACHE_H
+#define _TAGCACHE_H
+
+enum tag_type { tag_artist = 0, tag_album, tag_genre, tag_title,
+    tag_filename/*, tag_checksum*/ };
+#define TAG_COUNT 5
+
+#ifdef HAVE_DIRCACHE
+#define HAVE_TC_RAMCACHE 1
+#endif
+
+#define SEEK_LIST_SIZE 50
+#define TAGCACHE_MAX_FILTERS 3
+
+struct tagcache_search {
+    /* For internal use only. */
+    int fd;
+    long seek_list[SEEK_LIST_SIZE];
+    long filter_tag[TAGCACHE_MAX_FILTERS];
+    long filter_seek[TAGCACHE_MAX_FILTERS];
+    int filter_count;
+    int seek_list_count;
+    int seek_pos;
+    long position;
+    int entry_count;
+
+    /* Exported variables. */
+    bool ramsearch;
+    int type;
+    char *result;
+    int result_len;
+    long result_seek;
+};
+
+bool tagcache_search(struct tagcache_search *tcs, int tag);
+bool tagcache_search_add_filter(struct tagcache_search *tcs,
+                                int tag, int seek);
+bool tagcache_get_next(struct tagcache_search *tcs);
+void tagcache_search_finish(struct tagcache_search *tcs);
+
+int tagcache_get_progress(void);
+void tagcache_init(void);
+void tagcache_start_scan(void);
+void tagcache_stop_scan(void);
+bool tagcache_force_update(void);
+
+#endif
Index: apps/tagtree.c
===================================================================
RCS file: apps/tagtree.c
diff -N apps/tagtree.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ apps/tagtree.c	27 Dec 2005 07:08:40 -0000
@@ -0,0 +1,524 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2005 by Miika Pekkarinen
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+/** 
+ * Basic structure on this file was copied from dbtree.c and modified to
+ * support the tag cache interface.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "system.h"
+#include "kernel.h"
+#include "splash.h"
+#include "icons.h"
+#include "tree.h"
+#include "settings.h"
+#include "tagcache.h"
+#include "tagtree.h"
+#include "lang.h"
+#include "logf.h"
+#include "playlist.h"
+#include "keyboard.h"
+#include "gui/list.h"
+
+static int tagtree_play_folder(struct tree_context* c);
+static int tagtree_search(struct tree_context* c, char* string);
+
+static char searchstring[32];
+struct tagentry {
+    char *name;
+    int newtable;
+    int extraseek;
+};
+
+static struct tagcache_search tcs;
+
+static int compare(const void* p1, const void* p2)
+{
+    struct tagentry* e1 = (struct tagentry*)p1;
+    struct tagentry* e2 = (struct tagentry*)p2;
+    const char *n1 = e1->name;
+    const char *n2 = e2->name;
+
+    if (!strncasecmp("the ", n1, 4))
+        n1 = &n1[4];
+    if (!strncasecmp("the ", n2, 4))
+        n2 = &n2[4];
+
+    /* sort alphabetically asc */
+    if (global_settings.sort_case)
+        return strncmp(n1, n2, MAX_PATH);
+    else
+        return strncasecmp(n1, n2, MAX_PATH);
+}
+
+int tagtree_load(struct tree_context* c)
+{
+    int i;
+    int namebufused = 0;
+    struct tagentry *dptr = (struct tagentry *)c->dircache;
+
+    int table = c->currtable;
+    int extra = c->currextra;
+    int extra2 = c->currextra2;
+
+    c->dentry_size = sizeof(struct tagentry);
+    c->dirfull = false;
+
+    if (!table)
+    {
+        table = root;
+        c->currtable = table;
+    }
+
+    switch (table) {
+        case root: {
+            static const int tables[] = {allartists, allalbums, allgenres, allsongs,
+                                         search };
+            unsigned char* labels[] = { str(LANG_ID3DB_ARTISTS),
+                                        str(LANG_ID3DB_ALBUMS),
+                                        str(LANG_ID3DB_GENRES),
+                                        str(LANG_ID3DB_SONGS),
+                                        str(LANG_ID3DB_SEARCH)};
+
+            for (i = 0; i < 5; i++) {
+                dptr->name = &c->name_buffer[namebufused];
+                dptr->newtable = tables[i];
+                strcpy(dptr->name, (char *)labels[i]);
+                namebufused += strlen(dptr->name) + 1;
+                dptr++;
+            }
+            c->dirlength = c->filesindir = i;
+            return i;
+        }
+
+        case search: {
+            static const int tables[] = {searchartists,
+                                         searchalbums,
+                                         searchsongs};
+            unsigned char* labels[] = { str(LANG_ID3DB_SEARCH_ARTISTS),
+                                        str(LANG_ID3DB_SEARCH_ALBUMS),
+                                        str(LANG_ID3DB_SEARCH_SONGS)};
+            
+            for (i = 0; i < 3; i++) {
+                dptr->name = &c->name_buffer[namebufused];
+                dptr->newtable = tables[i];
+                strcpy(dptr->name, (char *)labels[i]);
+                namebufused += strlen(dptr->name) + 1;
+                dptr++;
+            }
+            c->dirlength = c->filesindir = i;
+            return i;
+        }
+
+        case searchartists:
+        case searchalbums:
+        case searchsongs:
+            i = tagtree_search(c, searchstring);
+            c->dirlength = c->filesindir = i;
+            if (c->dirfull) {
+                gui_syncsplash(HZ, true, (unsigned char *)"%s %s",
+                               str(LANG_SHOWDIR_ERROR_BUFFER),
+                               str(LANG_SHOWDIR_ERROR_FULL));
+                c->dirfull = false;
+            }
+            else
+                gui_syncsplash(HZ, true, str(LANG_ID3DB_MATCHES), i);
+            return i;
+
+        case allsongs:
+            logf("songs..");
+            tagcache_search(&tcs, tag_title);
+            break;
+
+        case allgenres:
+            logf("genres..");
+            tagcache_search(&tcs, tag_genre);
+            break;
+
+        case allalbums:
+            logf("albums..");
+            tagcache_search(&tcs, tag_album);
+            break;
+
+        case allartists:
+            logf("artists..");
+            tagcache_search(&tcs, tag_artist);
+            break;
+
+        case artist4genres:
+            logf("artist4genres..");
+            tagcache_search(&tcs, tag_artist);
+            tagcache_search_add_filter(&tcs, tag_genre, extra);
+            break;
+    
+        case albums4artist:
+            logf("albums4artist..");
+            tagcache_search(&tcs, tag_album);
+            tagcache_search_add_filter(&tcs, tag_artist, extra);
+            break;
+
+        case songs4album:
+            logf("songs4album..");
+            tagcache_search(&tcs, tag_title);
+            tagcache_search_add_filter(&tcs, tag_album, extra);
+            tagcache_search_add_filter(&tcs, tag_artist, extra2);
+            break;
+
+        case songs4artist:
+            logf("songs4artist..");
+            tagcache_search(&tcs, tag_title);
+            tagcache_search_add_filter(&tcs, tag_artist, extra);
+            break;
+
+        default:
+            logf("Unsupported table %d\n", table);
+            return -1;
+    }
+    
+    i = 0;
+    namebufused = 0;
+    while (tagcache_get_next(&tcs))
+    {
+        if (i == global_settings.max_files_in_dir)
+        {
+            c->dirfull = true;
+            break ;
+        }
+        
+        dptr->newtable = tcs.result_seek;
+        if (!tcs.ramsearch)
+        {
+            dptr->name = &c->name_buffer[namebufused];
+            namebufused += tcs.result_len;
+            if (namebufused > c->name_buffer_size)
+            {
+                c->dirfull = true;
+                break ;
+            }
+            strcpy(dptr->name, tcs.result);
+        }
+        else
+            dptr->name = tcs.result;
+        dptr++;
+        i++;
+    }
+
+    tagcache_search_finish(&tcs);
+    qsort(c->dircache,i,sizeof(struct tagentry),compare);
+    c->dirlength = c->filesindir = i;
+
+    if (c->dirfull)
+    {
+        gui_syncsplash(HZ, true, (unsigned char *)"%s %s",
+                       str(LANG_SHOWDIR_ERROR_BUFFER),
+                       str(LANG_SHOWDIR_ERROR_FULL));
+    }
+    
+    return i;
+}
+
+static int tagtree_search(struct tree_context* c, char* string)
+{
+    struct tagentry *dptr = (struct tagentry *)c->dircache;
+    int hits = 0;
+    int namebufused = 0;
+
+    switch (c->currtable) {
+        case searchartists:
+            tagcache_search(&tcs, tag_artist);
+            break;
+
+        case searchalbums:
+            tagcache_search(&tcs, tag_album);
+            break;
+
+        case searchsongs:
+            tagcache_search(&tcs, tag_title);
+            break;
+
+        default:
+            logf("Invalid table %d\n", c->currtable);
+            return 0;
+    }
+
+    while (tagcache_get_next(&tcs))
+    {
+        if (!strcasestr(tcs.result, string))
+            continue ;
+
+        if (!tcs.ramsearch)
+        {
+            dptr->name = &c->name_buffer[namebufused];
+            namebufused += tcs.result_len;
+            strcpy(dptr->name, tcs.result);
+        }
+        else
+            dptr->name = tcs.result;
+        
+        dptr->newtable = tcs.result_seek;
+        dptr++;
+        hits++;
+    }
+
+    tagcache_search_finish(&tcs);
+
+    return hits;
+}
+
+int tagtree_enter(struct tree_context* c)
+{
+    int rc = 0;
+    struct tagentry *dptr = (struct tagentry *)c->dircache;
+    int newextra;
+
+    dptr += c->selected_item;
+    newextra = dptr->newtable;
+
+    if (c->dirlevel >= MAX_DIR_LEVELS)
+        return 0;
+
+    c->selected_item_history[c->dirlevel]=c->selected_item;
+    c->table_history[c->dirlevel] = c->currtable;
+    c->extra_history[c->dirlevel] = c->currextra;
+    c->pos_history[c->dirlevel] = c->firstpos;
+    c->dirlevel++;
+
+    switch (c->currtable) {
+        case root:
+            c->currtable = newextra;
+            c->currextra = newextra;
+            break;
+
+        case allartists:
+        case searchartists:
+            c->currtable = albums4artist;
+            c->currextra = newextra;
+            break;
+
+        case allgenres:
+            c->currtable = artist4genres;
+            c->currextra = newextra;
+            break;
+        
+        case artist4genres:
+            c->currtable = albums4artist;
+            c->currextra = newextra;
+            break;
+            
+        case allalbums:
+        case albums4artist:
+        case searchalbums:
+            c->currtable = songs4album;
+            c->currextra2 = c->currextra;
+            c->currextra = newextra;
+            break;
+
+        case allsongs:
+        case songs4album:
+        case songs4artist:
+        case searchsongs:
+            c->dirlevel--;
+            if (tagtree_play_folder(c) >= 0)
+                rc = 2;
+            break;
+
+        case search:
+            rc = kbd_input(searchstring, sizeof(searchstring));
+            if (rc == -1 || !searchstring[0])
+                c->dirlevel--;
+            else
+                c->currtable = newextra;
+            break;
+
+        default:
+            c->dirlevel--;
+            break;
+    }
+    c->selected_item=0;
+    gui_synclist_select_item(&tree_lists, c->selected_item);
+
+    return rc;
+}
+
+void tagtree_exit(struct tree_context* c)
+{
+    c->dirlevel--;
+    c->selected_item=c->selected_item_history[c->dirlevel];
+    gui_synclist_select_item(&tree_lists, c->selected_item);
+    c->currtable = c->table_history[c->dirlevel];
+    c->currextra = c->extra_history[c->dirlevel];
+    c->firstpos  = c->pos_history[c->dirlevel];
+}
+
+int tagtree_get_filename(struct tree_context* c, char *buf, int buflen)
+{
+    struct tagentry *entry = (struct tagentry *)c->dircache;
+    
+    entry += c->selected_item;
+
+    tagcache_search(&tcs, tag_filename);
+    tagcache_search_add_filter(&tcs, tag_title, entry->newtable);
+    
+    if (!tagcache_get_next(&tcs))
+    {
+        tagcache_search_finish(&tcs);
+        return -1;
+    }
+
+    strncpy(buf, tcs.result, buflen-1);
+    tagcache_search_finish(&tcs);
+    
+    return 0;
+}
+
+bool tagtree_rename_tag(struct tree_context *c)
+{/*
+    struct tagentry *dptr = (struct tagentry *)c->dircache;
+    int extra, extra2;
+    
+    dptr += c->selected_item;
+    extra = dptr->newtable;
+    extra2 = dptr->extraseek;
+
+    switch (c->currtable) {
+        case allsongs:
+            tagcache_search(&tcs, tag_title);
+            break;
+
+        case allgenres:
+            tagcache_search(&tcs, tag_genre);
+            break;
+
+        case allalbums:
+            tagcache_search(&tcs, tag_album);
+            break;
+
+        case allartists:
+            tagcache_search(&tcs, tag_artist);
+            break;
+
+        case artist4genres:
+            tagcache_search(&tcs, tag_artist);
+            tagcache_search_add_filter(&tcs, tag_genre, extra);
+            break;
+    
+        case albums4artist:
+            tagcache_search(&tcs, tag_album);
+            tagcache_search_add_filter(&tcs, tag_artist, extra);
+            break;
+
+        case songs4album:
+            tagcache_search(&tcs, tag_title);
+            tagcache_search_add_filter(&tcs, tag_album, extra);
+            tagcache_search_add_filter(&tcs, tag_artist, extra2);
+            break;
+
+        case songs4artist:
+            tagcache_search(&tcs, tag_title);
+            tagcache_search_add_filter(&tcs, tag_artist, extra);
+            break;
+
+        default:
+            logf("wrong table");
+            return false;
+    }
+
+    while (tagcache_get_next(&tcs))
+    {
+        tagcache_modify(
+    }
+    
+    tagcache_search_finish(&tcs);*/
+    return true;
+}
+   
+static int tagtree_play_folder(struct tree_context* c)
+{
+    struct tagentry *entry = (struct tagentry *)c->dircache;
+    int i;
+
+    if (playlist_create(NULL, NULL) < 0) {
+        logf("Failed creating playlist\n");
+        return -1;
+    }
+
+#ifdef HAVE_ADJUSTABLE_CPU_FREQ
+    cpu_boost(true);
+#endif
+
+    for (i=0; i < c->filesindir; i++) {
+        tagcache_search(&tcs, tag_filename);
+        tagcache_search_add_filter(&tcs, tag_title, entry[i].newtable);
+
+        if (!tagcache_get_next(&tcs))
+        {
+            tagcache_search_finish(&tcs);
+            continue ;
+        }
+        playlist_insert_track(NULL, tcs.result, PLAYLIST_INSERT, false, false);
+        tagcache_search_finish(&tcs);
+    }
+
+    playlist_flush(NULL);
+
+#ifdef HAVE_ADJUSTABLE_CPU_FREQ
+    cpu_boost(false);
+#endif
+    
+    if (global_settings.playlist_shuffle)
+        c->selected_item = playlist_shuffle(current_tick, c->selected_item);
+    if (!global_settings.play_selected)
+        c->selected_item = 0;
+    gui_synclist_select_item(&tree_lists, c->selected_item);
+
+    playlist_start(c->selected_item,0);
+
+    return 0;
+}
+
+#ifdef HAVE_LCD_BITMAP
+const char* tagtree_get_icon(struct tree_context* c)
+#else
+int   tagtree_get_icon(struct tree_context* c)
+#endif
+{
+    int icon;
+
+    switch (c->currtable)
+    {
+        case allsongs:
+        case songs4album:
+        case songs4artist:
+        case searchsongs:
+            icon = Icon_Audio;
+            break;
+
+        default:
+            icon = Icon_Folder;
+            break;
+    }
+
+#ifdef HAVE_LCD_BITMAP
+    return (char *)bitmap_icons_6x8[icon];
+#else
+    return icon;
+#endif
+}
Index: apps/tagtree.h
===================================================================
RCS file: apps/tagtree.h
diff -N apps/tagtree.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ apps/tagtree.h	27 Dec 2005 07:08:40 -0000
@@ -0,0 +1,40 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2005 by Miika Pekkarinen
+ *
+ * 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.
+ *
+ ****************************************************************************/
+#ifndef _TAGTREE_H
+#define _TAGTREE_H
+
+#include "tagcache.h"
+#include "tree.h"
+
+enum table { invalid, root, allsongs, allalbums, allartists, allgenres,
+             albums4artist, songs4album, songs4artist, artist4genres,
+             search, searchartists, searchalbums, searchsongs };
+
+int tagtree_enter(struct tree_context* c);
+void tagtree_exit(struct tree_context* c);
+int tagtree_load(struct tree_context* c);
+#ifdef HAVE_LCD_BITMAP
+const char* tagtree_get_icon(struct tree_context* c);
+#else
+int   tagtree_get_icon(struct tree_context* c);
+#endif
+int tagtree_get_filename(struct tree_context* c, char *buf, int buflen);
+
+#endif
+
Index: apps/crc32.c
===================================================================
RCS file: apps/crc32.c
diff -N apps/crc32.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ apps/crc32.c	27 Dec 2005 07:08:40 -0000
@@ -0,0 +1,90 @@
+/* crc32.c -- compute the CRC-32 of a data stream
+ * Copyright (C) 1995-1998 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h 
+ */
+
+#include <stdio.h>
+
+#include "crc32.h"
+/* ========================================================================
+ * Table of CRC-32's of all single-byte values (made by make_crc_table)
+ */
+static const uint32_t crc_table[256] = {
+  0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L,
+  0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L,
+  0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L,
+  0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL,
+  0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L,
+  0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L,
+  0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L,
+  0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL,
+  0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L,
+  0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL,
+  0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L,
+  0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L,
+  0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L,
+  0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL,
+  0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL,
+  0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L,
+  0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL,
+  0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L,
+  0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L,
+  0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L,
+  0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL,
+  0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L,
+  0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L,
+  0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL,
+  0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L,
+  0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L,
+  0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L,
+  0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L,
+  0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L,
+  0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL,
+  0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL,
+  0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L,
+  0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L,
+  0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL,
+  0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL,
+  0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L,
+  0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL,
+  0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L,
+  0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL,
+  0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L,
+  0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL,
+  0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L,
+  0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L,
+  0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL,
+  0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L,
+  0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L,
+  0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L,
+  0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L,
+  0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L,
+  0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L,
+  0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL,
+  0x2d02ef8dL
+};
+
+/* ========================================================================= */
+#define DO1(buf) crc = crc_table[((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8);
+#define DO2(buf)  DO1(buf); DO1(buf);
+#define DO4(buf)  DO2(buf); DO2(buf);
+#define DO8(buf)  DO4(buf); DO4(buf);
+
+/* ========================================================================= */
+uint32_t crc32(crc, buf, len)
+    uint32_t crc;
+    const uint8_t *buf;
+    uint16_t len;
+{
+    if (buf == NULL) return 0L;
+    crc = crc ^ 0xffffffffL;
+    while (len >= 8)
+    {
+      DO8(buf);
+      len -= 8;
+    }
+    if (len) do {
+      DO1(buf);
+    } while (--len);
+    return crc ^ 0xffffffffL;
+}
Index: apps/crc32.h
===================================================================
RCS file: apps/crc32.h
diff -N apps/crc32.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ apps/crc32.h	27 Dec 2005 07:08:40 -0000
@@ -0,0 +1,17 @@
+/* crc32.c -- compute the CRC-32 of a data stream
+ * Copyright (C) 1995-1998 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#define int8_t signed char
+#define int16_t short
+#define int32_t long
+#define int64_t long long
+
+#define uint8_t unsigned char
+#define uint16_t unsigned short
+#define uint32_t unsigned long
+#define uint64_t unsigned long long
+#define MIN(a, b) (((a)<(b))?(a):(b))
+
+uint32_t crc32(uint32_t crc, const uint8_t *buf, uint16_t len);
Index: apps/tree.h
===================================================================
RCS file: /cvsroot/rockbox/apps/tree.h,v
retrieving revision 1.56
diff -u -3 -p -u -r1.56 tree.h
--- apps/tree.h	19 Dec 2005 00:11:26 -0000	1.56
+++ apps/tree.h	27 Dec 2005 07:08:40 -0000
@@ -191,6 +191,7 @@ struct tree_context {
     int extra_history[MAX_DIR_LEVELS]; /* db use */
     int currtable; /* db use */
     int currextra; /* db use */
+    int currextra2; /* db use */
     /* A big buffer with plenty of entry structs,
      * contains all files and dirs in the current
      * dir (with filters applied) */
Index: apps/tree.c
===================================================================
RCS file: /cvsroot/rockbox/apps/tree.c,v
retrieving revision 1.381
diff -u -3 -p -u -r1.381 tree.c
--- apps/tree.c	5 Dec 2005 22:44:41 -0000	1.381
+++ apps/tree.c	27 Dec 2005 07:08:41 -0000
@@ -57,10 +57,11 @@
 #include "filetypes.h"
 #include "misc.h"
 #include "filetree.h"
-#include "dbtree.h"
+#include "tagtree.h"
 #include "recorder/recording.h"
 #include "rtc.h"
 #include "dircache.h"
+#include "tagcache.h"
 #include "yesno.h"
 
 /* gui api */
@@ -159,7 +160,8 @@ char * tree_get_filename(int selected_it
     bool id3db = *(local_tc->dirfilter) == SHOW_ID3DB;
 
     if (id3db) {
-        name = ((char**)local_tc->dircache)[selected_item * local_tc->dentry_size];
+        char **buf = local_tc->dircache;
+        name = buf[selected_item * (local_tc->dentry_size/sizeof(int))];
     }
     else {
         struct entry* dc = local_tc->dircache;
@@ -184,7 +186,7 @@ void tree_get_fileicon(int selected_item
     struct tree_context * local_tc=(struct tree_context *)data;
     bool id3db = *(local_tc->dirfilter) == SHOW_ID3DB;
     if (id3db) {
-        *icon = (ICON)db_get_icon(&tc);
+        *icon = (ICON)tagtree_get_icon(&tc);
     }
     else {
         struct entry* dc = local_tc->dircache;
@@ -262,6 +264,7 @@ struct tree_context* tree_get_context(vo
 int tree_get_file_position(char * filename)
 {
     int i;
+
     /* use lastfile to determine the selected item (default=0) */
     for (i=0; i < tc.filesindir; i++)
     {
@@ -287,7 +290,7 @@ static int update_dir(void)
             tc.currextra != lastextra ||
             tc.firstpos  != lastfirstpos)
         {
-            if (db_load(&tc) < 0)
+            if (tagtree_load(&tc) < 0)
                 return -1;
 
             lasttable = tc.currtable;
@@ -489,7 +492,7 @@ static bool check_changed_id3mode(bool c
         currmode = global_settings.dirfilter == SHOW_ID3DB;
         if (currmode) {
             curr_context=CONTEXT_ID3DB;
-            db_load(&tc);
+            tagtree_load(&tc);
         }
         else
         {
@@ -595,7 +598,7 @@ static bool dirbrowse(void)
                 if ( numentries == 0 )
                     break;
 
-                switch (id3db?db_enter(&tc):ft_enter(&tc))
+                switch (id3db?tagtree_enter(&tc):ft_enter(&tc))
                 {
                     case 1: reload_dir = true; break;
                     case 2: start_wps = true; break;
@@ -619,7 +622,7 @@ static bool dirbrowse(void)
                     break;
 
                 if (id3db)
-                    db_exit(&tc);
+                    tagtree_exit(&tc);
                 else
                     if (ft_exit(&tc) == 3)
                         exit_func = true;
@@ -724,6 +727,7 @@ static bool dirbrowse(void)
                     restore = true;
 
                     id3db = check_changed_id3mode(id3db);
+                    reload_dir = true;
                     break;
                 }
 
@@ -753,7 +757,7 @@ static bool dirbrowse(void)
                 int attr = 0;
 
                 if(!numentries)
-                    onplay_result = onplay(NULL, 0, curr_context);
+                    onplay_result = onplay(NULL, 0, curr_context, &tc);
                 else {
                     if (id3db)
                     {
@@ -764,7 +768,7 @@ static bool dirbrowse(void)
                             case songs4artist:
                             case searchsongs:
                                 attr=TREE_ATTR_MPA;
-                                db_get_filename(&tc, buf, sizeof(buf));
+                                tagtree_get_filename(&tc, buf, sizeof(buf));
                                 break;
                         }
                     }
@@ -780,7 +784,7 @@ static bool dirbrowse(void)
                             snprintf(buf, sizeof buf, "/%s",
                                      dircache[tc.selected_item].name);
                     }
-                    onplay_result = onplay(buf, attr, curr_context);
+                    onplay_result = onplay(buf, attr, curr_context, &tc);
                 }
                 switch (onplay_result)
                 {
@@ -859,6 +863,7 @@ static bool dirbrowse(void)
         if (start_wps && audio_status() )
         {
             int i;
+            
             FOR_NB_SCREENS(i)
                 screens[i].stop_scroll();
             if (gui_wps_show() == SYS_USB_CONNECTED)
@@ -888,6 +893,7 @@ static bool dirbrowse(void)
                 lastextra = -1;
                 reload_root = false;
             }
+            
             if (! reload_dir )
             {
                 gui_synclist_select_item(&tree_lists, 0);
@@ -917,6 +923,7 @@ static bool dirbrowse(void)
             need_update = true;
             reload_dir = false;
         }
+
         if(need_update) {
             tc.selected_item = gui_synclist_get_sel_pos(&tree_lists);
             need_update=false;
@@ -1154,8 +1161,6 @@ void tree_init(void)
     memset(&tc, 0, sizeof(tc));
     tc.dirfilter = &global_settings.dirfilter;
 
-    tagdb_init();
-
     tc.name_buffer_size = AVERAGE_FILENAME_LENGTH * max_files;
     tc.name_buffer = buffer_alloc(tc.name_buffer_size);
 
@@ -1308,8 +1313,8 @@ void ft_play_filename(char *dir, char *f
 /* These two functions are called by the USB and shutdown handlers */
 void tree_flush(void)
 {
-    rundb_shutdown();
-    tagdb_shutdown();
+    tagcache_stop_scan();
+    
 #ifdef HAVE_DIRCACHE
     if (global_settings.dircache)
     {
@@ -1327,8 +1332,6 @@ void tree_flush(void)
 
 void tree_restore(void)
 {
-    tagdb_init();
-    rundb_init();
 #ifdef HAVE_DIRCACHE
     if (global_settings.dircache)
     {
@@ -1352,5 +1355,6 @@ void tree_restore(void)
             gui_textarea_clear(&screens[i]);
         }
     }
+    tagcache_start_scan();
 #endif
 }
Index: apps/dbtree.c
===================================================================
RCS file: /cvsroot/rockbox/apps/dbtree.c,v
retrieving revision 1.28
diff -u -3 -p -u -r1.28 dbtree.c
--- apps/dbtree.c	5 Dec 2005 22:44:41 -0000	1.28
+++ apps/dbtree.c	27 Dec 2005 07:08:41 -0000
@@ -517,7 +517,7 @@ static int db_play_folder(struct tree_co
             return -2;
         }
 
-        playlist_insert_track(NULL, buf, PLAYLIST_INSERT, false);
+        playlist_insert_track(NULL, buf, PLAYLIST_INSERT, false, true);
     }
 
     if (global_settings.playlist_shuffle)
Index: apps/settings.h
===================================================================
RCS file: /cvsroot/rockbox/apps/settings.h,v
retrieving revision 1.187
diff -u -3 -p -u -r1.187 settings.h
--- apps/settings.h	22 Dec 2005 23:48:31 -0000	1.187
+++ apps/settings.h	27 Dec 2005 07:08:41 -0000
@@ -380,6 +380,7 @@ struct user_settings
 #ifdef HAVE_DIRCACHE
     bool dircache;          /* enable directory cache */
     int dircache_size;      /* directory cache structure last size, 22 bits */
+    bool tagcache_ram;      /* tag cache mode (0=disabled,1=disk,2=ram) */
 #endif
     int default_codepage;   /* set default codepage for tag conversion */
 #ifdef HAVE_REMOTE_LCD
Index: apps/settings.c
===================================================================
RCS file: /cvsroot/rockbox/apps/settings.c,v
retrieving revision 1.348
diff -u -3 -p -u -r1.348 settings.c
--- apps/settings.c	22 Dec 2005 13:31:14 -0000	1.348
+++ apps/settings.c	27 Dec 2005 07:08:41 -0000
@@ -492,6 +492,10 @@ static const struct bit_entry hd_bits[] 
     {4, S_O(brightness), 9, "brightness", NULL }, 
 #endif
 
+#ifdef HAVE_DIRCACHE
+    {1, S_O(tagcache_ram), 0, "tagcache_ram", 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 */
 
Index: apps/settings_menu.c
===================================================================
RCS file: /cvsroot/rockbox/apps/settings_menu.c,v
retrieving revision 1.230
diff -u -3 -p -u -r1.230 settings_menu.c
--- apps/settings_menu.c	22 Dec 2005 10:43:35 -0000	1.230
+++ apps/settings_menu.c	27 Dec 2005 07:08:42 -0000
@@ -50,6 +50,7 @@
 #include "database.h"
 #include "dir.h"
 #include "dircache.h"
+#include "tagcache.h"
 #include "rbunicode.h"
 #include "splash.h"
 #include "yesno.h"
@@ -1356,6 +1357,7 @@ static bool beep(void)
 }
 #endif
 
+
 #ifdef HAVE_DIRCACHE
 static bool dircache(void)
 {
@@ -1374,6 +1376,16 @@ static bool dircache(void)
     return result;
 }
 
+static bool tagcache_ram(void)
+{
+    bool result = set_bool_options(str(LANG_TAGCACHE),
+                                   &global_settings.tagcache_ram,
+                                   STR(LANG_TAGCACHE_RAM),
+                                   STR(LANG_TAGCACHE_DISK),
+                                   NULL);
+
+    return result;
+}
 #endif /* HAVE_DIRCACHE */
 
 static bool playback_settings_menu(void)
@@ -1399,6 +1411,10 @@ static bool playback_settings_menu(void)
 #endif
         { ID2P(LANG_ID3_ORDER), id3_order },
         { ID2P(LANG_NEXT_FOLDER), next_folder },
+#ifdef HAVE_DIRCACHE
+        { ID2P(LANG_TAGCACHE), tagcache_ram },
+#endif
+        { ID2P(LANG_TAGCACHE_FORCE_UPDATE), tagcache_force_update },
         { ID2P(LANG_RUNTIMEDB_ACTIVE), runtimedb },
     };
 
Index: apps/playlist.c
===================================================================
RCS file: /cvsroot/rockbox/apps/playlist.c,v
retrieving revision 1.139
diff -u -3 -p -u -r1.139 playlist.c
--- apps/playlist.c	5 Dec 2005 22:44:41 -0000	1.139
+++ apps/playlist.c	27 Dec 2005 07:08:43 -0000
@@ -2556,12 +2556,32 @@ void playlist_close(struct playlist_info
         remove(playlist->control_filename);
 }
 
+/**
+ * Sync fds and flush loaded tracks.
+ */
+void playlist_flush(struct playlist_info* playlist)
+{
+    if (!playlist)
+        playlist = &current_playlist;
+    
+    mutex_lock(&playlist->control_mutex);
+    fsync(playlist->control_fd);
+    mutex_unlock(&playlist->control_mutex);
+
+    if (audio_status() & AUDIO_STATUS_PLAY)
+        audio_flush_and_reload_tracks();
+        
+#ifdef HAVE_DIRCACHE
+        queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0);
+#endif
+}
+
 /*
  * Insert track into playlist at specified position (or one of the special
  * positions).  Returns position where track was inserted or -1 if error.
  */
-int playlist_insert_track(struct playlist_info* playlist,
-                          const char *filename, int position, bool queue)
+int playlist_insert_track(struct playlist_info* playlist, const char *filename,
+                          int position, bool queue, bool flush)
 {
     int result;
     
@@ -2576,19 +2596,8 @@ int playlist_insert_track(struct playlis
 
     result = add_track_to_playlist(playlist, filename, position, queue, -1);
 
-    if (result != -1)
-    {
-        mutex_lock(&playlist->control_mutex);
-        fsync(playlist->control_fd);
-        mutex_unlock(&playlist->control_mutex);
-
-        if (audio_status() & AUDIO_STATUS_PLAY)
-            audio_flush_and_reload_tracks();
-    }
-
-#ifdef HAVE_DIRCACHE
-    queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0);
-#endif
+    if (result != -1 && flush)
+        playlist_flush(playlist);
 
     return result;
 }
Index: apps/playlist.h
===================================================================
RCS file: /cvsroot/rockbox/apps/playlist.h,v
retrieving revision 1.40
diff -u -3 -p -u -r1.40 playlist.h
--- apps/playlist.h	17 Nov 2005 19:31:28 -0000	1.40
+++ apps/playlist.h	27 Dec 2005 07:08:43 -0000
@@ -95,8 +95,9 @@ int playlist_create_ex(struct playlist_i
                        void* temp_buffer, int temp_buffer_size);
 int playlist_set_current(struct playlist_info* playlist);
 void playlist_close(struct playlist_info* playlist);
+void playlist_flush(struct playlist_info* playlist);
 int playlist_insert_track(struct playlist_info* playlist, const char *filename,
-                          int position, bool queue);
+                          int position, bool queue, bool flush);
 int playlist_insert_directory(struct playlist_info* playlist,
                               const char *dirname, int position, bool queue,
                               bool recurse);
Index: apps/metadata.c
===================================================================
RCS file: /cvsroot/rockbox/apps/metadata.c,v
retrieving revision 1.38
diff -u -3 -p -u -r1.38 metadata.c
--- apps/metadata.c	6 Dec 2005 13:26:59 -0000	1.38
+++ apps/metadata.c	27 Dec 2005 07:08:43 -0000
@@ -29,6 +29,7 @@
 #include "replaygain.h"
 #include "debug.h"
 #include "system.h"
+#include "crc32.h"
 
 enum tagtype { TAGTYPE_APE = 1, TAGTYPE_VORBIS };
 
@@ -68,6 +69,7 @@ static const struct format_list formats[
     { AFMT_MPA_L2,        "mp2"  },
     { AFMT_MPA_L2,        "mpa"  },
     { AFMT_MPA_L3,        "mp3"  },
+#if CONFIG_CODEC == SWCODEC
     { AFMT_OGG_VORBIS,    "ogg"  },
     { AFMT_PCM_WAV,       "wav"  },
     { AFMT_FLAC,          "flac" },
@@ -78,8 +80,10 @@ static const struct format_list formats[
     { AFMT_ALAC,          "m4a"  },
     { AFMT_AAC,           "mp4"  },
     { AFMT_SHN,           "shn"  },
+#endif
 };
 
+#if CONFIG_CODEC == SWCODEC
 static const unsigned short a52_bitrates[] =
 {
      32,  40,  48,  56,  64,  80,  96, 112, 128, 160, 
@@ -1244,9 +1248,10 @@ static bool get_musepack_metadata(int fd
     id3->bitrate = id3->filesize*8/id3->length;
     return true;
 }
+#endif /* CONFIG_CODEC == SWCODEC */
 
 /* Simple file type probing by looking at the filename extension. */
-static unsigned int probe_file_format(const char *filename)
+unsigned int probe_file_format(const char *filename)
 {
     char *suffix;
     unsigned int i;
@@ -1277,9 +1282,11 @@ static unsigned int probe_file_format(co
 bool get_metadata(struct track_info* track, int fd, const char* trackname,
     bool v1first) 
 {
+#if CONFIG_CODEC == SWCODEC
     unsigned char* buf;
     unsigned long totalsamples;
     int i;
+#endif
 
     /* Take our best guess at the codec type based on file extension */
     track->id3.codectype = probe_file_format(trackname);
@@ -1297,6 +1304,7 @@ bool get_metadata(struct track_info* tra
 
         break;
 
+#if CONFIG_CODEC == SWCODEC
     case AFMT_FLAC:
         if (!get_flac_metadata(fd, &(track->id3)))
         {
@@ -1447,6 +1455,7 @@ bool get_metadata(struct track_info* tra
         }
         /* TODO: read the id3v2 header if it exists */
         break;
+#endif /* CONFIG_CODEC == SWCODEC */
 
     default:
         /* If we don't know how to read the metadata, assume we can't play 
@@ -1463,3 +1472,21 @@ bool get_metadata(struct track_info* tra
 
     return true;
 }
+
+#define CRC_CHUNK  4096
+/*bool get_file_fast_crc(const char *filename, uint32_t *crcbuf, int buflen)
+{
+    int fd;
+    
+    memset(crcbuf, 0, buflen*sizeof(uint32_t));
+
+    fd = open(filename, O_RDONLY);
+    if (fd < 0)
+    {
+        logf("open failed");
+        return false;
+    }
+
+    count = MIN(filesize(fd) / CRC_CHUNK, buflen);
+}*/
+
Index: apps/metadata.h
===================================================================
RCS file: /cvsroot/rockbox/apps/metadata.h,v
retrieving revision 1.3
diff -u -3 -p -u -r1.3 metadata.h
--- apps/metadata.h	1 Dec 2005 20:39:19 -0000	1.3
+++ apps/metadata.h	27 Dec 2005 07:08:43 -0000
@@ -22,6 +22,7 @@
 
 #include "playback.h"
 
+unsigned int probe_file_format(const char *filename);
 bool get_metadata(struct track_info* track, int fd, const char* trackname,
                   bool v1first);
 
Index: apps/SOURCES
===================================================================
RCS file: /cvsroot/rockbox/apps/SOURCES,v
retrieving revision 1.31
diff -u -3 -p -u -r1.31 SOURCES
--- apps/SOURCES	18 Dec 2005 13:03:59 -0000	1.31
+++ apps/SOURCES	27 Dec 2005 07:08:43 -0000
@@ -27,6 +27,7 @@ talk.c
 #endif
 tree.c
 dbtree.c
+tagtree.c
 database.c
 filetree.c
 
@@ -73,7 +74,9 @@ recorder/recording.c
 #if CONFIG_CODEC == SWCODEC
 pcmbuf.c
 playback.c
-metadata.c
 codecs.c
 dsp.c
 #endif
+metadata.c
+tagcache.c
+crc32.c
Index: apps/onplay.c
===================================================================
RCS file: /cvsroot/rockbox/apps/onplay.c,v
retrieving revision 1.65
diff -u -3 -p -u -r1.65 onplay.c
--- apps/onplay.c	5 Dec 2005 22:44:41 -0000	1.65
+++ apps/onplay.c	27 Dec 2005 07:08:43 -0000
@@ -49,6 +49,9 @@
 #include "action.h"
 #include "splash.h"
 #include "yesno.h"
+#include "tagcache.h"
+#include "tagtree.h"
+#include "logf.h"
 
 #ifdef HAVE_LCD_BITMAP
 #include "icons.h"
@@ -61,6 +64,7 @@ static int context;
 static char* selected_file = NULL;
 static int selected_file_attr = 0;
 static int onplay_result = ONPLAY_OK;
+static struct tree_context* tc;
 
 /* For playlist options */
 struct playlist_args {
@@ -170,7 +174,7 @@ static bool add_to_playlist(int position
         playlist_create(NULL, NULL);
 
     if ((selected_file_attr & TREE_ATTR_MASK) == TREE_ATTR_MPA)
-        playlist_insert_track(NULL, selected_file, position, queue);
+        playlist_insert_track(NULL, selected_file, position, queue, true);
     else if (selected_file_attr & ATTR_DIRECTORY)
     {
         bool recurse = false;
@@ -457,6 +461,21 @@ static bool rename_file(void)
     return false;
 }
 
+static bool rename_tag(void)
+{
+/*    struct tagcache_search tcs;
+    char newname[MAX_PATH];
+    
+    strncpy(newname, dptr->name, sizeof newname);
+    if (!kbd_input(newname, sizeof newname)) {
+        tagtree_rename_tag(tc);
+                 
+        onplay_result = ONPLAY_RELOAD_DIR;
+    }
+*/
+    return false;
+}
+
 bool create_dir(void)
 {
     char dirname[MAX_PATH];
@@ -503,7 +522,7 @@ static int onplay_callback(int key, int 
     return key;
 }
 
-int onplay(char* file, int attr, int from)
+int onplay(char* file, int attr, int from, struct tree_context* c)
 {
     struct menu_item items[8]; /* increase this if you add entries! */
     int m, i=0, result;
@@ -513,6 +532,7 @@ int onplay(char* file, int attr, int fro
     exit_to_main = false;
     selected_file = file;
     selected_file_attr = attr;
+    tc = c;
 
     if (context == CONTEXT_WPS)
     {
@@ -531,6 +551,13 @@ int onplay(char* file, int attr, int fro
         i++;
     }
 
+    if (context == CONTEXT_ID3DB)
+    {
+        items[i].desc = ID2P(LANG_RENAME);
+        items[i].function = rename_tag;
+        i++;
+    }
+    
     if (context == CONTEXT_WPS)
     {
         items[i].desc = ID2P(LANG_BOOKMARK_MENU);
Index: apps/onplay.h
===================================================================
RCS file: /cvsroot/rockbox/apps/onplay.h,v
retrieving revision 1.3
diff -u -3 -p -u -r1.3 onplay.h
--- apps/onplay.h	23 Jun 2005 01:31:25 -0000	1.3
+++ apps/onplay.h	27 Dec 2005 07:08:43 -0000
@@ -19,7 +19,7 @@
 #ifndef _ONPLAY_H_
 #define _ONPLAY_H_
 
-int onplay(char* file, int attr, int from_screen);
+int onplay(char* file, int attr, int from_screen, struct tree_context* c);
 
 enum {
     ONPLAY_OK,
Index: apps/playlist_viewer.c
===================================================================
RCS file: /cvsroot/rockbox/apps/playlist_viewer.c,v
retrieving revision 1.40
diff -u -3 -p -u -r1.40 playlist_viewer.c
--- apps/playlist_viewer.c	5 Dec 2005 22:44:41 -0000	1.40
+++ apps/playlist_viewer.c	27 Dec 2005 07:08:43 -0000
@@ -474,7 +474,7 @@ static int onplay_menu(int index)
                 break;
             case 2:
             {
-                onplay(current_track->name, TREE_ATTR_MPA, CONTEXT_TREE);
+                onplay(current_track->name, TREE_ATTR_MPA, CONTEXT_TREE, NULL);
 
                 if (!viewer.playlist)
                     ret = 1;
Index: apps/main.c
===================================================================
RCS file: /cvsroot/rockbox/apps/main.c,v
retrieving revision 1.160
diff -u -3 -p -u -r1.160 main.c
--- apps/main.c	17 Dec 2005 10:13:29 -0000	1.160
+++ apps/main.c	27 Dec 2005 07:08:44 -0000
@@ -60,6 +60,7 @@
 #include "misc.h"
 #include "database.h"
 #include "dircache.h"
+#include "tagcache.h"
 #include "lang.h"
 #include "string.h"
 #include "splash.h"
@@ -161,6 +162,7 @@ void init(void)
     gui_sync_wps_init();
     settings_apply();
     init_dircache();
+    tagcache_init();
     sleep(HZ/2);
     tree_init();
     playlist_init();
@@ -336,6 +338,7 @@ void init(void)
 
     
     init_dircache();
+    tagcache_init();
     gui_sync_wps_init();
     settings_apply();
 
Index: firmware/common/dircache.c
===================================================================
RCS file: /cvsroot/rockbox/firmware/common/dircache.c,v
retrieving revision 1.8
diff -u -3 -p -u -r1.8 dircache.c
--- firmware/common/dircache.c	26 Nov 2005 20:47:10 -0000	1.8
+++ firmware/common/dircache.c	27 Dec 2005 07:08:45 -0000
@@ -55,6 +55,7 @@ static bool dircache_initialized = false
 static bool thread_enabled = false;
 static unsigned long allocated_size = DIRCACHE_LIMIT;
 static unsigned long dircache_size = 0;
+static unsigned long entry_count = 0;
 static unsigned long reserve_used = 0;
 static unsigned int  cache_build_ticks = 0;
 static char dircache_cur_path[MAX_PATH];
@@ -79,12 +80,14 @@ static struct dircache_entry* allocate_e
     }
     
     next_entry = (struct dircache_entry *)((char *)dircache_root+dircache_size);
-    dircache_size += sizeof(struct dircache_entry);
     next_entry->name_len = 0;
     next_entry->d_name = NULL;
     next_entry->up = NULL;
     next_entry->down = NULL;
     next_entry->next = NULL;
+    
+    dircache_size += sizeof(struct dircache_entry);
+    entry_count++;
 
     return next_entry;
 }
@@ -316,6 +319,7 @@ int dircache_load(const char *path)
     }
 
     dircache_root = (struct dircache_entry *)audiobuf;
+    entry_count = maindata.entry_count;
     bytes_read = read(fd, dircache_root, MIN(DIRCACHE_LIMIT, maindata.size));
     close(fd);
     
@@ -359,6 +363,7 @@ int dircache_save(const char *path)
     maindata.magic = DIRCACHE_MAGIC;
     maindata.size = dircache_size;
     maindata.root_entry = dircache_root;
+    maindata.entry_count = entry_count;
 
     /* Save the info structure */
     bytes_written = write(fd, &maindata, sizeof(struct dircache_maindata));
@@ -541,6 +546,14 @@ bool dircache_is_enabled(void)
 }
 
 /**
+ * Returns the current number of entries (directories and files) in the cache.
+ */
+int dircache_get_entry_count(void)
+{
+    return entry_count;
+}
+
+/**
  * Returns the allocated space for dircache (without reserve space).
  */
 int dircache_get_cache_size(void)
@@ -896,7 +909,7 @@ struct dircache_entry* readdir_cached(DI
         regentry = readdir(dir->regulardir);
         if (regentry == NULL)
             return NULL;
-        
+
         strncpy(dir->secondary_entry.d_name, regentry->d_name, MAX_PATH-1);
         dir->secondary_entry.size = regentry->size;
         dir->secondary_entry.startcluster = regentry->startcluster;
@@ -928,6 +941,7 @@ struct dircache_entry* readdir_cached(DI
     dir->secondary_entry.wrttime = ce->wrttime;
     dir->secondary_entry.wrtdate = ce->wrtdate;
     dir->secondary_entry.next = NULL;
+    dir->internal_entry = ce;
 
     //logf("-> %s", ce->name);
     return &dir->secondary_entry;
Index: firmware/include/dircache.h
===================================================================
RCS file: /cvsroot/rockbox/firmware/include/dircache.h,v
retrieving revision 1.5
diff -u -3 -p -u -r1.5 dircache.h
--- firmware/include/dircache.h	26 Nov 2005 23:47:06 -0000	1.5
+++ firmware/include/dircache.h	27 Dec 2005 07:08:45 -0000
@@ -41,6 +41,7 @@ struct travel_data {
 struct dircache_maindata {
     long magic;
     long size;
+    long entry_count;
     struct dircache_entry *root_entry;
 };
 
@@ -61,6 +62,7 @@ struct dircache_entry {
 typedef struct {
     bool busy;
     struct dircache_entry *entry;
+    struct dircache_entry *internal_entry;
     struct dircache_entry secondary_entry;
     DIR *regulardir;
 } DIRCACHED;
@@ -70,6 +72,7 @@ int dircache_load(const char *path);
 int dircache_save(const char *path);
 int dircache_build(int last_size);
 bool dircache_is_enabled(void);
+int dircache_get_entry_count(void);
 int dircache_get_cache_size(void);
 int dircache_get_reserve_used(void);
 int dircache_get_build_ticks(void);
Index: apps/gui/gwps.c
===================================================================
RCS file: /cvsroot/rockbox/apps/gui/gwps.c,v
retrieving revision 1.17
diff -u -3 -p -u -r1.17 gwps.c
--- apps/gui/gwps.c	9 Dec 2005 01:11:14 -0000	1.17
+++ apps/gui/gwps.c	27 Dec 2005 07:08:45 -0000
@@ -225,7 +225,7 @@ long gui_wps_show(void)
 #ifdef WPS_RC_CONTEXT
             case WPS_RC_CONTEXT:
 #endif
-                onplay(wps_state.id3->path, TREE_ATTR_MPA, CONTEXT_WPS);
+                onplay(wps_state.id3->path, TREE_ATTR_MPA, CONTEXT_WPS, NULL);
 #ifdef HAVE_LCD_BITMAP
                 FOR_NB_SCREENS(i)
                 {
Index: apps/lang/english.lang
===================================================================
RCS file: /cvsroot/rockbox/apps/lang/english.lang,v
retrieving revision 1.209
diff -u -3 -p -u -r1.209 english.lang
--- apps/lang/english.lang	22 Dec 2005 10:43:35 -0000	1.209
+++ apps/lang/english.lang	27 Dec 2005 07:08:45 -0000
@@ -3557,3 +3557,38 @@ eng: "Brightness"
 voice: "Brightness"
 new:
 
+id: LANG_ID3DB_GENRES
+desc: in tag cache
+eng: "Genres"
+voice: "Genres"
+new:
+
+id: LANG_TAGCACHE
+desc: in tag cache settings
+eng: "Tag cache"
+voice: "Tag cache"
+new:
+
+id: LANG_TAGCACHE_DISK
+desc: in tag cache settings
+eng: "Keep on disk"
+voice: "Keep on disk"
+new:
+
+id: LANG_TAGCACHE_RAM
+desc: in tag cache settings
+eng: "Load to ram"
+voice: "Load to ram"
+new:
+
+id: LANG_TAGCACHE_FORCE_UPDATE
+desc: in tag cache settings
+eng: "Force tag cache update"
+voice: "Force tag cache update"
+new:
+
+id: LANG_TAGCACHE_FORCE_UPDATE_SPLASH
+desc: in tag cache settings
+eng: "Updating on background"
+voice: "Updating on background"
+new:
