summaryrefslogtreecommitdiff
path: root/apps/tagcache.c
diff options
context:
space:
mode:
authorMiika Pekkarinen <miipekk@ihme.org>2006-03-26 11:33:42 +0000
committerMiika Pekkarinen <miipekk@ihme.org>2006-03-26 11:33:42 +0000
commit7c4e0c8730d5b076d4db4206361bc38d5256a23f (patch)
tree43382ae25de9bfa0bbabdff7d51c32b651ad47b5 /apps/tagcache.c
parent50d40ea43409745bc828e56af5e3879ea6b48cf1 (diff)
downloadrockbox-7c4e0c8730d5b076d4db4206361bc38d5256a23f.zip
rockbox-7c4e0c8730d5b076d4db4206361bc38d5256a23f.tar.gz
rockbox-7c4e0c8730d5b076d4db4206361bc38d5256a23f.tar.bz2
rockbox-7c4e0c8730d5b076d4db4206361bc38d5256a23f.tar.xz
Initial version of tagcache! There are still some bugs in the engine
and much more problems with the UI. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@9256 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'apps/tagcache.c')
-rw-r--r--apps/tagcache.c1868
1 files changed, 1868 insertions, 0 deletions
diff --git a/apps/tagcache.c b/apps/tagcache.c
new file mode 100644
index 0000000..6c6dd00
--- /dev/null
+++ b/apps/tagcache.c
@@ -0,0 +1,1868 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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 "dircache.h"
+#include "metadata.h"
+#include "id3.h"
+#include "settings.h"
+#include "splash.h"
+#include "lang.h"
+#include "tagcache.h"
+
+/* External reference to the big audiobuffer. */
+extern char *audiobuf, *audiobufend;
+
+/* 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. */
+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. */
+static long tempbuf_pos;
+
+/* Tags we want to be unique (loaded to the tempbuf). */
+static const int unique_tags[] = { tag_artist, tag_album, tag_genre, tag_title };
+
+/* 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;
+};
+
+struct tempbuf_id {
+ int id;
+ struct tempbuf_id *next;
+};
+
+struct tempbuf_searchidx {
+ struct tempbuf_id *id;
+ char *str;
+ int seek;
+};
+
+
+/* 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
+
+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];
+
+ if (tcs->valid)
+ tagcache_search_finish(tcs);
+
+ 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;
+ tcs->valid = true;
+
+#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->valid)
+ return false;
+
+ 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)
+ {
+ tcs->valid = false;
+ 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. */
+ tcs->valid = false;
+ return false;
+ }
+ }
+
+ if (entry.tag_length > (long)sizeof(buf))
+ {
+ tcs->valid = false;
+ logf("too long tag");
+ return false;
+ }
+
+ if (read(tcs->fd, buf, entry.tag_length) != entry.tag_length)
+ {
+ tcs->valid = false;
+ logf("read error");
+ return false;
+ }
+
+ tcs->result = buf;
+ tcs->result_len = entry.tag_length;
+
+ return true;
+}
+
+#if 0
+void tagcache_modify(struct tagcache_search *tcs, int type, const char *text)
+{
+ struct tagentry *entry;
+
+ if (tcs->type != tag_title)
+ return ;
+
+ /* We will need reserve buffer for this. */
+ if (tcs->ramcache)
+ {
+ struct tagfile_entry *ep;
+
+ ep = (struct tagfile_entry *)&hdr->tags[tcs->type][tcs->result_seek];
+ tcs->seek_list[tcs->seek_list_count];
+ }
+
+ entry = find_entry_ram(
+
+}
+#endif
+
+void tagcache_search_finish(struct tagcache_search *tcs)
+{
+ if (tcs->fd >= 0)
+ {
+ close(tcs->fd);
+ tcs->fd = -1;
+ tcs->ramsearch = false;
+ tcs->valid = false;
+ }
+}
+
+#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 ;
+
+ /* 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 bool tempbuf_insert(char *str, int id)
+{
+ struct tempbuf_searchidx *index = (struct tempbuf_searchidx *)tempbuf;
+ int len = strlen(str)+1;
+
+ /* Insert it to the buffer. */
+ tempbuf_left -= len + sizeof(struct tempbuf_id);
+ if (tempbuf_left - 4 < 0 || tempbufidx >= TAGFILE_MAX_ENTRIES-1)
+ return false;
+
+ index[tempbufidx].id = (struct tempbuf_id *)&tempbuf[tempbuf_pos];
+#ifdef ROCKBOX_STRICT_ALIGN
+ /* Make sure the entry is long aligned. */
+ if ((long)index[tempbufidx].id & 0x03)
+ {
+ int fix = 4 - ((long)id & 0x03);
+ tempbuf_left -= fix;
+ tempbuf_pos += fix;
+ index[tempbufidx].id = (struct tempbuf_id *)((
+ (long)index[tempbufidx].id & ~0x03) + 0x04);
+ }
+#endif
+ index[tempbufidx].id->id = id;
+ index[tempbufidx].id->next = NULL;
+ tempbuf_pos += sizeof(struct tempbuf_id);
+
+ index[tempbufidx].seek = -1;
+ index[tempbufidx].str = &tempbuf[tempbuf_pos];
+ memcpy(index[tempbufidx].str, str, len);
+ tempbuf_pos += len;
+ tempbufidx++;
+
+ return true;
+}
+
+static bool tempbuf_unique_insert(char *str, int id)
+{
+ struct tempbuf_searchidx *index = (struct tempbuf_searchidx *)tempbuf;
+ struct tempbuf_id *idp;
+ int i;
+
+ /* Check if string already exists. */
+ for (i = 0; i < tempbufidx; i++)
+ {
+ if (!strcasecmp(str, index[i].str))
+ {
+ tempbuf_left -= sizeof(struct tempbuf_id);
+ if (tempbuf_left < 0)
+ return false;
+
+ idp = index[i].id;
+ while (idp->next != NULL)
+ idp = idp->next;
+
+ idp->next = (struct tempbuf_id *)&tempbuf[tempbuf_pos];
+ idp = idp->next;
+ idp->id = id;
+ idp->next = NULL;
+ tempbuf_pos += sizeof(struct tempbuf_id);
+
+ return true;
+ }
+ }
+
+ return tempbuf_insert(str, id);
+}
+
+static int compare(const void *p1, const void *p2)
+{
+ struct tempbuf_searchidx *e1 = (struct tempbuf_searchidx *)p1;
+ struct tempbuf_searchidx *e2 = (struct tempbuf_searchidx *)p2;
+
+ /*
+ if (!strncasecmp("the ", e1, 4))
+ e1 = &e1[4];
+ if (!strncasecmp("the ", e2, 4))
+ e2 = &e2[4];
+ */
+
+ return strncasecmp(e1->str, e2->str, MAX_PATH);
+}
+
+static int tempbuf_sort(int fd)
+{
+ struct tempbuf_searchidx *index = (struct tempbuf_searchidx *)tempbuf;
+ struct tagfile_entry fe;
+ int i;
+
+ qsort(index, tempbufidx, sizeof(struct tempbuf_searchidx), compare);
+
+ for (i = 0; i < tempbufidx; i++)
+ {
+ index[i].seek = lseek(fd, 0, SEEK_CUR);
+ fe.tag_length = strlen(index[i].str) + 1;
+ if (write(fd, &fe, sizeof(struct tagfile_entry)) !=
+ sizeof(struct tagfile_entry))
+ {
+ logf("tempbuf_sort: write error #1");
+ return -1;
+ }
+
+ if (write(fd, index[i].str, fe.tag_length) !=
+ fe.tag_length)
+ {
+ logf("tempbuf_sort: write error #2");
+ return -2;
+ }
+ }
+
+ return i;
+}
+
+
+static struct tempbuf_searchidx* tempbuf_locate(int id)
+{
+ struct tempbuf_searchidx *index = (struct tempbuf_searchidx *)tempbuf;
+ struct tempbuf_id *idp;
+ int i;
+
+ /* Check if string already exists. */
+ for (i = 0; i < tempbufidx; i++)
+ {
+ idp = index[i].id;
+ while (idp != NULL)
+ {
+ if (idp->id == id)
+ return &index[i];
+ idp = idp->next;
+ }
+ }
+
+ return NULL;
+}
+
+
+static int tempbuf_find_location(int id)
+{
+ struct tempbuf_searchidx *entry;
+
+ entry = tempbuf_locate(id);
+ if (entry == NULL)
+ return -1;
+
+ return entry->seek;
+}
+
+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 index_entry idx;
+ char buf[MAX_PATH];
+ int fd = -1, masterfd;
+ bool error = false;
+ int init;
+ int masterfd_pos;
+
+ logf("Building index: %d", index_type);
+
+ tempbufidx = 0;
+ tempbuf_pos = TAGFILE_MAX_ENTRIES * sizeof(struct tempbuf_searchidx);
+ tempbuf_left = tempbuf_size - tempbuf_pos;
+ if (tempbuf_left < 0)
+ {
+ logf("Buffer way too small!");
+ return false;
+ }
+
+ /* Open the index file, which contains the tag names. */
+ snprintf(buf, sizeof buf, TAGCACHE_FILE_INDEX, index_type);
+ fd = open(buf, O_RDWR);
+
+ if (fd >= 0)
+ {
+ /* Read the header. */
+ if (read(fd, &tch, sizeof(struct tagcache_header)) !=
+ sizeof(struct tagcache_header) || tch.magic != TAGCACHE_MAGIC)
+ {
+ logf("header error");
+ close(fd);
+ return false;
+ }
+
+ /**
+ * If tag file contains unique tags (sorted index), we will load
+ * it entirely into memory so we can resort it later for use with
+ * chunked browsing.
+ */
+ if (is_unique_tag(index_type))
+ {
+ for (i = 0; i < tch.entry_count; i++)
+ {
+ struct tagfile_entry entry;
+ int loc = lseek(fd, 0, SEEK_CUR);
+
+ if (read(fd, &entry, sizeof(struct tagfile_entry))
+ != sizeof(struct tagfile_entry))
+ {
+ logf("read error");
+ close(fd);
+ return false;
+ }
+
+ if (entry.tag_length >= (int)sizeof(buf))
+ {
+ logf("too long tag");
+ close(fd);
+ return false;
+ }
+
+ if (read(fd, buf, entry.tag_length) != entry.tag_length)
+ {
+ logf("read error #2");
+ close(fd);
+ return false;
+ }
+
+ /**
+ * Save the tag and tag id in the memory buffer. Tag id
+ * is saved so we can later reindex the master lookup
+ * table when the index gets resorted.
+ */
+ tempbuf_insert(buf, loc + TAGFILE_MAX_ENTRIES);
+ }
+ }
+ else
+ tempbufidx = tch.entry_count;
+ }
+ else
+ {
+ /**
+ * Creating new index file to store the tags. No need to preload
+ * anything whether the index type is sorted or not.
+ */
+ fd = open(buf, O_WRONLY | O_CREAT | O_TRUNC);
+ if (fd < 0)
+ {
+ logf("%s open fail", buf);
+ return false;
+ }
+
+ 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;
+ }
+ }
+
+ /* Loading the tag lookup file as "master file". */
+ 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;
+ masterfd_pos = lseek(masterfd, 0, SEEK_CUR);
+ }
+ else
+ {
+ /**
+ * Master file already exists so we need to process the current
+ * file first.
+ */
+ 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;
+ }
+
+ /**
+ * If we reach end of the master file, we need to expand it to
+ * hold new tags. If the current index is not sorted, we can
+ * simply append new data to end of the file.
+ * However, if the index is sorted, we need to update all tag
+ * pointers in the master file for the current index.
+ */
+ masterfd_pos = lseek(masterfd, tch.entry_count * sizeof(struct index_entry),
+ SEEK_CUR);
+ if (masterfd_pos == filesize(masterfd))
+ {
+ logf("appending...");
+ init = true;
+ }
+ }
+
+ /**
+ * Load new unique tags in memory to be sorted later and added
+ * to the master lookup file.
+ */
+ if (is_unique_tag(index_type))
+ {
+ lseek(tmpfd, sizeof(struct tagcache_header), SEEK_SET);
+ /* h is the header of the temporary file containing new tags. */
+ for (i = 0; i < h->entry_count; i++)
+ {
+ struct temp_file_entry entry;
+
+ if (read(tmpfd, &entry, sizeof(struct temp_file_entry)) !=
+ sizeof(struct temp_file_entry))
+ {
+ logf("read fail #1");
+ error = true;
+ goto error_exit;
+ }
+
+ /* Read data. */
+ if (entry.tag_length[index_type] >= (long)sizeof(buf))
+ {
+ logf("too long entry!");
+ error = true;
+ goto error_exit;
+ }
+
+ lseek(tmpfd, entry.tag_offset[index_type], SEEK_CUR);
+ if (read(tmpfd, buf, entry.tag_length[index_type]) !=
+ entry.tag_length[index_type])
+ {
+ logf("read fail #3");
+ error = true;
+ goto error_exit;
+ }
+
+ if (!tempbuf_unique_insert(buf, i))
+ {
+ logf("insert error");
+ error = true;
+ goto error_exit;
+ }
+
+ /* Skip to next. */
+ lseek(tmpfd, entry.data_length - entry.tag_offset[index_type] -
+ entry.tag_length[index_type], SEEK_CUR);
+ }
+
+ /* Sort the buffer data and write it to the index file. */
+ lseek(fd, sizeof(struct tagcache_header), SEEK_SET);
+ i = tempbuf_sort(fd);
+ if (i < 0)
+ goto error_exit;
+ logf("sorted %d tags", i);
+
+ /**
+ * Now update all indexes in the master lookup file.
+ */
+ lseek(masterfd, sizeof(struct tagcache_header), SEEK_SET);
+ for (i = 0; i < tch.entry_count; i++)
+ {
+ int loc = lseek(masterfd, 0, SEEK_CUR);
+
+ if (read(masterfd, &idx, sizeof(struct index_entry)) !=
+ sizeof(struct index_entry))
+ {
+ logf("read fail #2");
+ error = true;
+ goto error_exit ;
+ }
+ idx.tag_seek[index_type] = tempbuf_find_location(
+ idx.tag_seek[index_type]+TAGFILE_MAX_ENTRIES);
+ if (idx.tag_seek[index_type] < 0)
+ {
+ logf("update error: %d/%d", i, tch.entry_count);
+ error = true;
+ goto error_exit;
+ }
+
+ /* Write back the updated index. */
+ lseek(masterfd, loc, SEEK_SET);
+ if (write(masterfd, &idx, sizeof(struct index_entry)) !=
+ sizeof(struct index_entry))
+ {
+ logf("write fail");
+ error = true;
+ goto error_exit;
+ }
+ }
+ }
+
+ /**
+ * Walk through the temporary file containing the new tags.
+ */
+ // build_normal_index(h, tmpfd, masterfd, idx);
+ lseek(masterfd, masterfd_pos, SEEK_SET);
+ lseek(tmpfd, sizeof(struct tagcache_header), SEEK_SET);
+ lseek(fd, 0, SEEK_END);
+ for (i = 0; i < h->entry_count; i++)
+ {
+ 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 entry headers. */
+ if (!is_unique_tag(index_type))
+ {
+ struct temp_file_entry entry;
+ struct tagfile_entry fe;
+
+ if (read(tmpfd, &entry, sizeof(struct temp_file_entry)) !=
+ sizeof(struct temp_file_entry))
+ {
+ logf("read fail #1");
+ error = true;
+ break ;
+ }
+
+ /* Read data. */
+ if (entry.tag_length[index_type] >= (int)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 ;
+ }
+
+ lseek(tmpfd, entry.tag_offset[index_type], 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 ;
+ }
+
+ /* Write to index file. */
+ idx.tag_seek[index_type] = lseek(fd, 0, SEEK_CUR);
+ fe.tag_length = entry.tag_length[index_type];
+ write(fd, &fe, sizeof(struct tagfile_entry));
+ write(fd, buf, fe.tag_length);
+ tempbufidx++;
+
+ /* Skip to next. */
+ lseek(tmpfd, entry.data_length - entry.tag_offset[index_type] -
+ entry.tag_length[index_type], SEEK_CUR);
+ }
+ else
+ {
+ /* Locate the correct entry from the sorted array. */
+ idx.tag_seek[index_type] = tempbuf_find_location(i);
+ if (idx.tag_seek[index_type] < 0)
+ {
+ logf("entry not found (%d)");
+ 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 ;
+ }
+
+ }
+
+ /* Finally write the uniqued tag index file. */
+ if (is_unique_tag(index_type))
+ {
+ tch.magic = TAGCACHE_MAGIC;
+ tch.entry_count = tempbufidx;
+ tch.datasize = lseek(fd, 0, SEEK_END) - sizeof(struct tagcache_header);
+ lseek(fd, 0, SEEK_SET);
+ write(fd, &tch, sizeof(struct tagcache_header));
+ }
+ else
+ {
+ tch.magic = TAGCACHE_MAGIC;
+ tch.entry_count = tempbufidx;
+ tch.datasize = lseek(fd, 0, SEEK_CUR) - sizeof(struct tagcache_header);
+ lseek(fd, 0, SEEK_SET);
+ write(fd, &tch, sizeof(struct tagcache_header));
+ }
+
+ error_exit:
+
+ 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 = (char *)(((long)audiobuf & ~0x03) + 0x04);
+ tempbuf_size = (int)audiobufend - (int)audiobuf - 4;
+ 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);
+
+ processed_dir_count++;
+ 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);
+}
+
+#ifdef HAVE_TC_RAMCACHE
+bool tagcache_is_ramcache(void)
+{
+ return ramcache;
+}
+#endif
+
+void tagcache_init(void)
+{
+ /* If the previous cache build/update was interrupted, commit
+ * the changes first. */
+ cpu_boost(true);
+ allocate_tempbuf();
+ commit();
+ free_tempbuf();
+ cpu_boost(false);
+
+#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);
+}
+
+