summaryrefslogtreecommitdiffstats
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
parent50d40ea43409745bc828e56af5e3879ea6b48cf1 (diff)
downloadrockbox-7c4e0c8730d5b076d4db4206361bc38d5256a23f.tar.gz
rockbox-7c4e0c8730d5b076d4db4206361bc38d5256a23f.tar.bz2
rockbox-7c4e0c8730d5b076d4db4206361bc38d5256a23f.zip
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
-rw-r--r--apps/SOURCES6
-rw-r--r--apps/codecs.c6
-rw-r--r--apps/codecs.h10
-rw-r--r--apps/debug_menu.c39
-rw-r--r--apps/lang/english.lang42
-rw-r--r--apps/main.c27
-rw-r--r--apps/metadata.c11
-rw-r--r--apps/metadata.h1
-rw-r--r--apps/onplay.c4
-rw-r--r--apps/plugin.c10
-rw-r--r--apps/plugin.h14
-rw-r--r--apps/plugins/Makefile3
-rw-r--r--apps/plugins/SOURCES2
-rw-r--r--apps/settings.c5
-rw-r--r--apps/settings.h1
-rw-r--r--apps/settings_menu.c22
-rw-r--r--apps/tagcache.c1868
-rw-r--r--apps/tagcache.h78
-rw-r--r--apps/tagtree.c536
-rw-r--r--apps/tagtree.h41
-rw-r--r--apps/tree.c31
-rw-r--r--apps/tree.h1
-rw-r--r--firmware/common/dircache.c4
23 files changed, 2698 insertions, 64 deletions
diff --git a/apps/SOURCES b/apps/SOURCES
index e872531408..4c479b7a60 100644
--- a/apps/SOURCES
+++ b/apps/SOURCES
@@ -25,8 +25,7 @@ status.c
talk.c
#endif
tree.c
-dbtree.c
-database.c
+tagtree.c
filetree.c
screen_access.c
@@ -69,7 +68,6 @@ recorder/recording.c
#if CONFIG_CODEC == SWCODEC
pcmbuf.c
playback.c
-metadata.c
codecs.c
dsp.c
eq.c
@@ -81,3 +79,5 @@ eq_arm.S
#endif
eq_menu.c
#endif
+metadata.c
+tagcache.c
diff --git a/apps/codecs.c b/apps/codecs.c
index 298e5e1964..95a07539b9 100644
--- a/apps/codecs.c
+++ b/apps/codecs.c
@@ -177,12 +177,6 @@ struct codec_api ci = {
audio_flush_and_reload_tracks,
audio_get_file_pos,
- /* tag database */
- &tagdbheader,
- &tagdb_fd,
- &tagdb_initialized,
- tagdb_init,
-
/* misc */
srand,
rand,
diff --git a/apps/codecs.h b/apps/codecs.h
index d28afc7292..50ab4cda0e 100644
--- a/apps/codecs.h
+++ b/apps/codecs.h
@@ -85,12 +85,12 @@
#define CODEC_MAGIC 0x52434F44 /* RCOD */
/* increase this every time the api struct changes */
-#define CODEC_API_VERSION 5
+#define CODEC_API_VERSION 6
/* update this to latest version if a change to the api struct breaks
backwards compatibility (and please take the opportunity to sort in any
new function which are "waiting" at the end of the function table) */
-#define CODEC_MIN_API_VERSION 5
+#define CODEC_MIN_API_VERSION 6
/* codec return codes */
enum codec_status {
@@ -248,12 +248,6 @@ struct codec_api {
void (*audio_flush_and_reload_tracks)(void);
int (*audio_get_file_pos)(void);
- /* tag database */
- struct tagdb_header *tagdbheader;
- int *tagdb_fd;
- int *tagdb_initialized;
- int (*tagdb_init) (void);
-
/* misc */
void (*srand)(unsigned int seed);
int (*rand)(void);
diff --git a/apps/debug_menu.c b/apps/debug_menu.c
index 9ab505061e..c62f65e33d 100644
--- a/apps/debug_menu.c
+++ b/apps/debug_menu.c
@@ -49,6 +49,8 @@
#include "screens.h"
#include "misc.h"
#include "splash.h"
+#include "dircache.h"
+#include "tagcache.h"
#include "lcd-remote.h"
#ifdef HAVE_LCD_BITMAP
@@ -1855,6 +1857,10 @@ static bool dbg_dircache_info(void)
dircache_get_build_ticks() / HZ);
lcd_puts(0, line++, buf);
+ snprintf(buf, sizeof(buf), "Entry count: %d",
+ dircache_get_entry_count());
+ lcd_puts(0, line++, buf);
+
lcd_update();
switch (button_get_w_tmo(HZ/2))
@@ -1871,6 +1877,38 @@ static bool dbg_dircache_info(void)
#endif /* HAVE_DIRCACHE */
+static bool dbg_tagcache_info(void)
+{
+ bool done = false;
+ int line;
+ char buf[32];
+
+ lcd_setmargins(0, 0);
+ lcd_setfont(FONT_SYSFIXED);
+
+ while (!done)
+ {
+ line = 0;
+
+ lcd_clear_display();
+ snprintf(buf, sizeof(buf), "Current progress: %d%%",
+ tagcache_get_progress());
+ lcd_puts(0, line++, buf);
+
+ lcd_update();
+
+ switch (button_get_w_tmo(HZ/2))
+ {
+ case SETTINGS_OK:
+ case SETTINGS_CANCEL:
+ done = true;
+ break;
+ }
+ }
+
+ return false;
+}
+
#if CONFIG_CPU == SH7034
bool dbg_save_roms(void)
{
@@ -2014,6 +2052,7 @@ bool debug_menu(void)
#ifdef HAVE_DIRCACHE
{ "View dircache info", dbg_dircache_info },
#endif
+ { "View tagcache info", dbg_tagcache_info },
#ifdef HAVE_LCD_BITMAP
{ "View audio thread", dbg_audio_thread },
#ifdef PM_DEBUG
diff --git a/apps/lang/english.lang b/apps/lang/english.lang
index 868b20646e..9923fa9caf 100644
--- a/apps/lang/english.lang
+++ b/apps/lang/english.lang
@@ -3838,3 +3838,45 @@ desc: Backlight behaviour setting
eng: "First keypress enables backlight only"
voice: "First keypress enables backlight only"
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_INIT
+desc: while initializing tagcache on boot
+eng: "Committing tagcache"
+voice:
+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 in background"
+voice: "Updating in background"
+new:
diff --git a/apps/main.c b/apps/main.c
index 09c1e4bea5..915aec27d3 100644
--- a/apps/main.c
+++ b/apps/main.c
@@ -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"
@@ -138,6 +139,29 @@ void init_dircache(void)
# define init_dircache(...)
#endif
+void init_tagcache(void)
+{
+ int font_w, font_h;
+
+#ifdef HAVE_LCD_BITMAP
+ /* Print "Scanning disk..." to the display. */
+ lcd_getstringsize("A", &font_w, &font_h);
+ lcd_putsxy((LCD_WIDTH/2) - ((strlen(str(LANG_TAGCACHE_INIT))*font_w)/2),
+ LCD_HEIGHT-font_h*3, str(LANG_TAGCACHE_INIT));
+ lcd_update();
+#endif
+
+ tagcache_init();
+
+#ifdef HAVE_LCD_BITMAP
+ /* Clean the text when we are done. */
+ lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
+ lcd_fillrect(0, LCD_HEIGHT-font_h*3, LCD_WIDTH, font_h);
+ lcd_set_drawmode(DRMODE_SOLID);
+ lcd_update();
+#endif
+}
+
#ifdef SIMULATOR
void init(void)
@@ -162,6 +186,7 @@ void init(void)
gui_sync_wps_init();
settings_apply();
init_dircache();
+ init_tagcache();
sleep(HZ/2);
tree_init();
playlist_init();
@@ -350,6 +375,7 @@ void init(void)
init_dircache();
+ init_tagcache();
gui_sync_wps_init();
settings_apply();
@@ -379,7 +405,6 @@ void init(void)
#endif
talk_init();
/* runtime database has to be initialized after audio_init() */
- rundb_init();
cpu_boost(false);
#ifdef AUTOROCK
diff --git a/apps/metadata.c b/apps/metadata.c
index 5481cbef58..09ccb3928d 100644
--- a/apps/metadata.c
+++ b/apps/metadata.c
@@ -68,6 +68,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" },
@@ -80,8 +81,10 @@ static const struct format_list formats[] =
{ AFMT_SHN, "shn" },
{ AFMT_AIFF, "aif" },
{ AFMT_AIFF, "aiff" },
+#endif
};
+#if CONFIG_CODEC == SWCODEC
static const unsigned short a52_bitrates[] =
{
32, 40, 48, 56, 64, 80, 96, 112, 128, 160,
@@ -1246,6 +1249,7 @@ static bool get_musepack_metadata(int fd, struct mp3entry *id3)
id3->bitrate = id3->filesize*8/id3->length;
return true;
}
+#endif /* CONFIG_CODEC == SWCODEC */
static bool get_aiff_metadata(int fd, struct mp3entry* id3)
{
@@ -1318,7 +1322,7 @@ static bool get_aiff_metadata(int fd, struct mp3entry* id3)
}
/* 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;
@@ -1349,9 +1353,11 @@ static unsigned int probe_file_format(const char *filename)
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);
@@ -1369,6 +1375,7 @@ bool get_metadata(struct track_info* track, int fd, const char* trackname,
break;
+#if CONFIG_CODEC == SWCODEC
case AFMT_FLAC:
if (!get_flac_metadata(fd, &(track->id3)))
{
@@ -1519,6 +1526,7 @@ bool get_metadata(struct track_info* track, int fd, const char* trackname,
}
/* TODO: read the id3v2 header if it exists */
break;
+#endif /* CONFIG_CODEC == SWCODEC */
case AFMT_AIFF:
if (!get_aiff_metadata(fd, &(track->id3)))
@@ -1543,3 +1551,4 @@ bool get_metadata(struct track_info* track, int fd, const char* trackname,
return true;
}
+
diff --git a/apps/metadata.h b/apps/metadata.h
index 1e07a18277..f790146041 100644
--- a/apps/metadata.h
+++ b/apps/metadata.h
@@ -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);
diff --git a/apps/onplay.c b/apps/onplay.c
index 2beb374956..5dcbe6e13e 100644
--- a/apps/onplay.c
+++ b/apps/onplay.c
@@ -820,12 +820,12 @@ int onplay(char* file, int attr, int from)
items[i].desc = ID2P(LANG_MENU_SHOW_ID3_INFO);
items[i].function = browse_id3;
i++;
- if(rundb_initialized)
+/* if(rundb_initialized)
{
items[i].desc = ID2P(LANG_MENU_SET_RATING);
items[i].function = set_rating;
i++;
- }
+ }*/
}
#ifdef HAVE_MULTIVOLUME
diff --git a/apps/plugin.c b/apps/plugin.c
index 4395195d0f..51e071020a 100644
--- a/apps/plugin.c
+++ b/apps/plugin.c
@@ -333,16 +333,6 @@ static const struct plugin_api rockbox_api = {
#endif
#endif /* !SIMULATOR && CONFIG_CODEC != SWCODEC */
- /* tag database */
- &tagdbheader,
- &tagdb_fd,
- &tagdb_initialized,
- tagdb_init,
- /* runtime database */
- &rundbheader,
- &rundb_fd,
- &rundb_initialized,
-
/* menu */
menu_init,
menu_exit,
diff --git a/apps/plugin.h b/apps/plugin.h
index 6584dbeed7..99dd3dc6ea 100644
--- a/apps/plugin.h
+++ b/apps/plugin.h
@@ -99,12 +99,12 @@
#define PLUGIN_MAGIC 0x526F634B /* RocK */
/* increase this every time the api struct changes */
-#define PLUGIN_API_VERSION 13
+#define PLUGIN_API_VERSION 14
/* update this to latest version if a change to the api struct breaks
backwards compatibility (and please take the opportunity to sort in any
new function which are "waiting" at the end of the function table) */
-#define PLUGIN_MIN_API_VERSION 13
+#define PLUGIN_MIN_API_VERSION 14
/* plugin return codes */
enum plugin_status {
@@ -387,16 +387,6 @@ struct plugin_api {
#endif
#endif
- /* tag database */
- struct tagdb_header *tagdbheader;
- int *tagdb_fd;
- int *tagdb_initialized;
- int (*tagdb_init) (void);
- /* runtime database */
- struct rundb_header *rundbheader;
- int *rundb_fd;
- int *rundb_initialized;
-
/* menu */
int (*menu_init)(const struct menu_item* mitems, int count,
int (*callback)(int, int),
diff --git a/apps/plugins/Makefile b/apps/plugins/Makefile
index 54917ab658..586eb605da 100644
--- a/apps/plugins/Makefile
+++ b/apps/plugins/Makefile
@@ -60,7 +60,8 @@ DIRS = .
# Subdirs containing multi-file plugins
#for all targets
-SUBDIRS += searchengine databox
+# SUBDIRS += searchengine databox
+SUBDIRS += databox
#for any recorder, iRiver or iPod model
ifneq (,$(strip $(foreach tgt,RECORDER IRIVER IPOD_COLOR IPOD_VIDEO GIGABEAT,$(findstring $(tgt),$(TARGET)))))
diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES
index 5ba983e4f7..68d7929b81 100644
--- a/apps/plugins/SOURCES
+++ b/apps/plugins/SOURCES
@@ -15,7 +15,7 @@ metronome.c
#endif
mosaique.c
rockbox_flash.c
-search.c
+/* search.c */
snow.c
sort.c
stats.c
diff --git a/apps/settings.c b/apps/settings.c
index 8c4e3dea71..6fe9665086 100644
--- a/apps/settings.c
+++ b/apps/settings.c
@@ -565,6 +565,11 @@ static const struct bit_entry hd_bits[] =
{LCD_DEPTH,S_O(fg_color),LCD_DEFAULT_FG,"foreground color","rgb"},
{LCD_DEPTH,S_O(bg_color),LCD_DEFAULT_BG,"background color","rgb"},
#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 */
diff --git a/apps/settings.h b/apps/settings.h
index 424341f340..fb8e11ffde 100644
--- a/apps/settings.h
+++ b/apps/settings.h
@@ -419,6 +419,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
diff --git a/apps/settings_menu.c b/apps/settings_menu.c
index ab0e21d098..e381a14595 100644
--- a/apps/settings_menu.c
+++ b/apps/settings_menu.c
@@ -49,6 +49,7 @@
#include "database.h"
#include "dir.h"
#include "dircache.h"
+#include "tagcache.h"
#include "rbunicode.h"
#include "splash.h"
#include "yesno.h"
@@ -1259,15 +1260,15 @@ static bool next_folder(void)
static bool runtimedb(void)
{
bool rc;
- bool old = global_settings.runtimedb;
+// bool old = global_settings.runtimedb;
rc = set_bool( str(LANG_RUNTIMEDB_ACTIVE),
&global_settings.runtimedb );
- if (old && !global_settings.runtimedb)
+/* if (old && !global_settings.runtimedb)
rundb_shutdown();
if (!old && global_settings.runtimedb)
rundb_init();
-
+*/
return rc;
}
@@ -1479,6 +1480,7 @@ static bool beep(void)
}
#endif
+
#ifdef HAVE_DIRCACHE
static bool dircache(void)
{
@@ -1497,6 +1499,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)
@@ -1523,6 +1535,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 },
};
diff --git a/apps/tagcache.c b/apps/tagcache.c
new file mode 100644
index 0000000000..6c6dd00a60
--- /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);
+}
+
+
diff --git a/apps/tagcache.h b/apps/tagcache.h
new file mode 100644
index 0000000000..a405764644
--- /dev/null
+++ b/apps/tagcache.h
@@ -0,0 +1,78 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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
+
+#include "id3.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
+
+/* Allow a little drift to the filename ordering. */
+#define POS_HISTORY_COUNT 4
+
+/* How many entries we can create in one tag file (for sorting). */
+#define TAGFILE_MAX_ENTRIES 20000
+
+#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;
+ bool valid;
+
+ /* Exported variables. */
+ bool ramsearch;
+ int type;
+ char *result;
+ int result_len;
+ long result_seek;
+};
+
+bool tagcache_fill_tags(struct mp3entry *id3, const char *filename);
+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);
+#ifdef HAVE_TC_RAMCACHE
+bool tagcache_is_ramcache(void);
+#endif
+void tagcache_init(void);
+void tagcache_start_scan(void);
+void tagcache_stop_scan(void);
+bool tagcache_force_update(void);
+
+#endif
diff --git a/apps/tagtree.c b/apps/tagtree.c
new file mode 100644
index 0000000000..cb4135c204
--- /dev/null
+++ b/apps/tagtree.c
@@ -0,0 +1,536 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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"
+
+#define CHUNKED_NEXT -2
+
+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;
+
+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);
+
+ if (!table)
+ {
+ c->dirfull = false;
+ table = root;
+ c->currtable = table;
+ }
+
+ if (c->dirfull)
+ table = chunked_next;
+
+ 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);
+ if (extra2 > 0)
+ 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;
+
+ case chunked_next:
+ logf("chunked next...");
+ break;
+
+ default:
+ logf("Unsupported table %d\n", table);
+ return -1;
+ }
+
+ i = 0;
+ namebufused = 0;
+ c->dirfull = false;
+ while (tagcache_get_next(&tcs))
+ {
+ dptr->newtable = tcs.result_seek;
+ if (!tcs.ramsearch)
+ {
+ dptr->name = &c->name_buffer[namebufused];
+ namebufused += tcs.result_len;
+ if (namebufused > c->name_buffer_size)
+ {
+ logf("buffer full, 1 entry missed.");
+ c->dirfull = true;
+ break ;
+ }
+ strcpy(dptr->name, tcs.result);
+ }
+ else
+ dptr->name = tcs.result;
+ dptr++;
+ i++;
+
+ /**
+ * Estimate when we are running out of space so we can stop
+ * and enabled chunked browsing without missing entries.
+ */
+ if (i >= global_settings.max_files_in_dir - 1
+ || namebufused + 200 > c->name_buffer_size)
+ {
+ c->dirfull = true;
+ break ;
+ }
+
+ }
+
+ if (c->dirfull)
+ {
+ dptr->name = "===>";
+ dptr->newtable = chunked_next;
+ dptr++;
+ i++;
+ }
+ else
+ tagcache_search_finish(&tcs);
+
+ c->dirlength = c->filesindir = i;
+
+ 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;
+
+ if (dptr->newtable == chunked_next)
+ {
+ c->selected_item=0;
+ gui_synclist_select_item(&tree_lists, c->selected_item);
+ return 0;
+ }
+
+ c->dirfull = false;
+ 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:
+ c->currtable = songs4album;
+ c->currextra = newextra;
+ c->currextra2 = -1;
+ break;
+ 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->dirfull = false;
+ 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];
+
+ /* Just to be sure when chunked browsing is used. */
+ tagcache_search_finish(&tcs);
+}
+
+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;
+}
+
+#if 0
+bool tagtree_rename_tag(struct tree_context *c, const char *newtext)
+{
+ struct tagentry *dptr = (struct tagentry *)c->dircache;
+ int extra, extra2;
+ int type;
+
+ dptr += c->selected_item;
+ extra = dptr->newtable;
+ extra2 = dptr->extraseek;
+
+ switch (c->currtable) {
+ case allgenres:
+ tagcache_search(&tcs, tag_title);
+ tagcache_search_add_filter(&tcs, tag_genre, extra);
+ type = tag_genre;
+ break;
+
+ case allalbums:
+ tagcache_search(&tcs, tag_title);
+ tagcache_search_add_filter(&tcs, tag_album, extra);
+ type = tag_album;
+ break;
+
+ case allartists:
+ tagcache_search(&tcs, tag_title);
+ tagcache_search_add_filter(&tcs, tag_artist, extra);
+ type = tag_artist;
+ break;
+
+ case artist4genres:
+ tagcache_search(&tcs, tag_title);
+ tagcache_search_add_filter(&tcs, tag_genre, extra);
+ type = tag_artist;
+ break;
+
+ case albums4artist:
+ tagcache_search(&tcs, tag_title);
+ tagcache_search_add_filter(&tcs, tag_album, extra);
+ tagcache_search_add_filter(&tcs, tag_artist, extra2);
+ type = tag_album;
+ break;
+
+ default:
+ logf("wrong table");
+ return false;
+ }
+
+ while (tagcache_get_next(&tcs))
+ {
+ // tagcache_modify(&tcs, type, newtext);
+ }
+
+ tagcache_search_finish(&tcs);
+ return true;
+}
+#endif
+
+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);
+ tagcache_search_finish(&tcs);
+ }
+
+#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
+}
diff --git a/apps/tagtree.h b/apps/tagtree.h
new file mode 100644
index 0000000000..7eafb7061d
--- /dev/null
+++ b/apps/tagtree.h
@@ -0,0 +1,41 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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,
+ chunked_next };
+
+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
+
diff --git a/apps/tree.c b/apps/tree.c
index b11d28f1f8..5efacd5993 100644
--- a/apps/tree.c
+++ b/apps/tree.c
@@ -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 */
@@ -164,7 +165,8 @@ char * tree_get_filename(int selected_item, void * data, char *buffer)
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;
@@ -189,7 +191,7 @@ void tree_get_fileicon(int selected_item, void * data, ICON * icon)
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;
@@ -267,6 +269,7 @@ struct tree_context* tree_get_context(void)
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++)
{
@@ -292,7 +295,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;
@@ -494,7 +497,7 @@ static bool check_changed_id3mode(bool currmode)
currmode = global_settings.dirfilter == SHOW_ID3DB;
if (currmode) {
curr_context=CONTEXT_ID3DB;
- db_load(&tc);
+ tagtree_load(&tc);
}
else
{
@@ -600,7 +603,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;
@@ -624,7 +627,7 @@ static bool dirbrowse(void)
break;
if (id3db)
- db_exit(&tc);
+ tagtree_exit(&tc);
else
if (ft_exit(&tc) == 3)
exit_func = true;
@@ -732,6 +735,7 @@ static bool dirbrowse(void)
restore = true;
id3db = check_changed_id3mode(id3db);
+ reload_dir = true;
break;
}
@@ -772,7 +776,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;
}
}
@@ -906,6 +910,7 @@ static bool dirbrowse(void)
lastextra = -1;
reload_root = false;
}
+
if (! reload_dir )
{
gui_synclist_select_item(&tree_lists, 0);
@@ -935,6 +940,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;
@@ -1177,8 +1183,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);
@@ -1331,9 +1335,9 @@ void ft_play_filename(char *dir, char *file)
/* These two functions are called by the USB and shutdown handlers */
void tree_flush(void)
{
- rundb_shutdown();
- tagdb_shutdown();
+ tagcache_stop_scan();
playlist_shutdown();
+
#ifdef HAVE_DIRCACHE
if (global_settings.dircache)
{
@@ -1351,8 +1355,6 @@ void tree_flush(void)
void tree_restore(void)
{
- tagdb_init();
- rundb_init();
#ifdef HAVE_DIRCACHE
if (global_settings.dircache)
{
@@ -1376,5 +1378,6 @@ void tree_restore(void)
gui_textarea_clear(&screens[i]);
}
}
+ tagcache_start_scan();
#endif
}
diff --git a/apps/tree.h b/apps/tree.h
index 9b4888c46a..fe903250d7 100644
--- a/apps/tree.h
+++ b/apps/tree.h
@@ -213,6 +213,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) */
diff --git a/firmware/common/dircache.c b/firmware/common/dircache.c
index 4facbb8ab2..f9fd63b8e4 100644
--- a/firmware/common/dircache.c
+++ b/firmware/common/dircache.c
@@ -84,8 +84,8 @@ static struct dircache_entry* allocate_entry(void)
/* Make sure the entry is long aligned. */
if ((long)next_entry & 0x03)
{
- next_entry = (struct dircache_entry *)(((long)next_entry & ~0x03) + 0x04);
dircache_size += 4 - ((long)next_entry & 0x03);
+ next_entry = (struct dircache_entry *)(((long)next_entry & ~0x03) + 0x04);
}
#endif
next_entry->name_len = 0;
@@ -95,7 +95,6 @@ static struct dircache_entry* allocate_entry(void)
next_entry->next = NULL;
dircache_size += sizeof(struct dircache_entry);
- entry_count++;
return next_entry;
}
@@ -186,6 +185,7 @@ static int dircache_scan(struct travel_data *td)
td->ce->wrttime = td->entry.wrttime;
memcpy(td->ce->d_name, td->entry.name, td->ce->name_len);
dircache_size += td->ce->name_len;
+ entry_count++;
if (td->entry.attr & FAT_ATTR_DIRECTORY)
{