First runtime database support, self repairing, only playcount works for now,
which is still rather crude; playcount gets increased even if the song started playback but was skipped... track rating should be trivial to add, autorating also works since its based on playcount.


git-svn-id: svn://svn.rockbox.org/rockbox/trunk@6969 a1c6a512-1295-4272-9138-f99709370657
diff --git a/apps/database.c b/apps/database.c
index c753362..06bac4a 100644
--- a/apps/database.c
+++ b/apps/database.c
@@ -43,14 +43,26 @@
 #include "keyboard.h"
 #include "database.h"
 #include "autoconf.h"
+#include "playback.h"
+#include "logf.h"
 
-#undef NEW_DB_CODE
-
-#ifdef NEW_DB_CODE
+/* internal functions */
+void writetagdbheader(void);
+void writefentry(void);
+void getfentrybyoffset(int offset);
+void update_fentryoffsets(int start, int end);
+void writerundbheader(void);
+void getrundbentrybyoffset(int offset);
+void writerundbentry(void);
+int getfentrybyfilename(char *fname);
+int getfentrybyhash(int hash);
+int deletefentry(char *fname);
+int tagdb_shiftdown(int targetoffset, int startingoffset, int bytes);
+int tagdb_shiftup(int targetoffset, int startingoffset, int bytes);
+                    
 static char sbuf[1024];
 static struct file_entry fe;
 static int currentfeoffset, currentferecord;
-#endif
 
 int tagdb_fd = -1;
 int tagdb_initialized = 0;
@@ -63,7 +75,7 @@
     int i, *p;
 #endif
 
-    tagdb_fd = open(ROCKBOX_DIR "/rockbox.id3db", O_RDONLY);
+    tagdb_fd = open(ROCKBOX_DIR "/rockbox.tagdb", O_RDWR);
     if (tagdb_fd < 0) {
         DEBUGF("Failed opening database\n");
         return -1;
@@ -111,19 +123,26 @@
 
 /* NOTE: All these functions below are yet untested. */
 
-#ifdef NEW_DB_CODE
-
 /*** TagDatabase code ***/
 
-void writetagdbheader() {
+void writetagdbheader(void) {
     lseek(tagdb_fd,0,SEEK_SET);
     write(tagdb_fd, &tagdbheader, 68);
+    fsync(tagdb_fd);
+}
+
+void writefentry(void) {
+    lseek(tagdb_fd,currentfeoffset,SEEK_SET);
+    write(tagdb_fd,sbuf,tagdbheader.filelen);
+    write(tagdb_fd,&fe.hash,12);
+    fsync(tagdb_fd);
 }
 
 void getfentrybyoffset(int offset) {
+    memset(&fe,0,sizeof(struct file_entry));
     lseek(tagdb_fd,offset,SEEK_SET);
-    fread(tagdb_fd,sbuf,tagdbheader.filelen);
-    fread(tagdb_fd,&fe+sizeof(char *),12);
+    read(tagdb_fd,sbuf,tagdbheader.filelen);
+    read(tagdb_fd,&fe.hash,12);
     fe.name=sbuf;
     currentfeoffset=offset;
     currentferecord=(offset-tagdbheader.filestart)/FILEENTRY_SIZE;
@@ -138,7 +157,7 @@
         int mid=(min+max)/2;
         int compare;
         getfentrybyrecord(mid);
-        compare=strcasecmp(fname,fe.name));
+        compare=strcasecmp(fname,fe.name);
         if(compare==0)
             return 1;
         else if(compare<0)
@@ -164,7 +183,7 @@
         return 0;
     int restrecord = currentferecord+1; 
     if(currentferecord!=tagdbheader.filecount) /* file is not last entry */
-         shiftdown(FILERECORD2OFFSET(currentferecord),FILERECORD2OFFSET(restrecord),(tagdbheader.filecount-restrecord)*FILEENTRY_SIZE);
+         tagdb_shiftdown(FILERECORD2OFFSET(currentferecord),FILERECORD2OFFSET(restrecord),(tagdbheader.filecount-restrecord)*FILEENTRY_SIZE);
     ftruncate(tagdb_fd,lseek(tagdb_fd,0,SEEK_END)-FILEENTRY_SIZE);
     tagdbheader.filecount--;
     update_fentryoffsets(restrecord,tagdbheader.filecount);
@@ -172,9 +191,9 @@
     return 1;
 }
 
-int update_fentryoffsets(int start, int end) {
+void update_fentryoffsets(int start, int end) {
     int i;
-    for(int i=start;i<end;i++) {
+    for(i=start;i<end;i++) {
         getfentrybyrecord(i);
         if(fe.songentry!=-1) {
              int p;
@@ -187,7 +206,7 @@
              }
          }
          if(fe.rundbentry!=-1) {
-              splash(HZ*2, "o.o.. found a rundbentry? o.o; didn't update it, update the code o.o;");
+              splash(HZ*2,true, "o.o.. found a rundbentry? o.o; didn't update it, update the code o.o;");
          }
     }
 }
@@ -195,18 +214,18 @@
 int tagdb_shiftdown(int targetoffset, int startingoffset, int bytes) {
     int amount;
     if(targetoffset>=startingoffset) {
-        splash(HZ*2,"Woah. no beeping way. (tagdb_shiftdown)");
+        splash(HZ*2,true,"Woah. no beeping way. (tagdb_shiftdown)");
         return 0;
     }
     lseek(tagdb_fd,startingoffset,SEEK_SET);
-    while(amount=read(tagdb_fd,sbuf,bytes > 1024 ? 1024 : bytes)) {
+    while((amount=read(tagdb_fd,sbuf,(bytes > 1024) ? 1024 : bytes))) {
         int written;
         startingoffset+=amount;
         lseek(tagdb_fd,targetoffset,SEEK_SET);
         written=write(tagdb_fd,sbuf,amount);
         targetoffset+=written;
         if(amount!=written) {
-            splash(HZ*2,"Something went very wrong. expect database corruption. (tagdb_shiftdown)");
+            splash(HZ*2,true,"Something went very wrong. expect database corruption. (tagdb_shiftdown)");
             return 0;
         }
         lseek(tagdb_fd,startingoffset,SEEK_SET);
@@ -218,9 +237,8 @@
 int tagdb_shiftup(int targetoffset, int startingoffset, int bytes) {
     int amount,amount2;
     int readpos,writepos,filelen;
-    int ok;
     if(targetoffset<=startingoffset) {
-        splash(HZ*2,"Um. no. (tagdb_shiftup)");
+        splash(HZ*2,true,"Um. no. (tagdb_shiftup)");
         return 0;
     }
     filelen=lseek(tagdb_fd,0,SEEK_END);
@@ -232,13 +250,13 @@
         lseek(tagdb_fd,readpos,SEEK_SET);
         amount2=read(tagdb_fd,sbuf,amount);
         if(amount2!=amount) {
-             splash(HZ*2,"Something went very wrong. expect database corruption. (tagdb_shiftup)");
+             splash(HZ*2,true,"Something went very wrong. expect database corruption. (tagdb_shiftup)");
              return 0;
         }
         lseek(tagdb_fd,writepos,SEEK_SET);
         amount=write(tagdb_fd,sbuf,amount2);
         if(amount2!=amount) {
-            splash(HZ*2,"Something went very wrong. expect database corruption. (tagdb_shiftup)");
+            splash(HZ*2,true,"Something went very wrong. expect database corruption. (tagdb_shiftup)");
             return 0;
         }
         bytes-=amount;
@@ -246,17 +264,168 @@
     if(bytes==0)
         return 1;
     else {
-        splash(HZ*2,"Something went wrong, >.>;; (tagdb_shiftup)");
+        splash(HZ*2,true,"Something went wrong, >.>;; (tagdb_shiftup)");
         return 0;
     }
 }
 
 /*** end TagDatabase code ***/
 
+int rundb_fd = -1;
+int rundb_initialized = 0;
+struct rundb_header rundbheader;
+
+static int valid_file, currentreoffset,rundbsize;
+static struct rundb_entry rundbentry;
+
 /*** RuntimeDatabase code ***/
 
+void rundb_track_changed(struct track_info *ti) {
+    increaseplaycount();
+    logf("rundb new track: %s", ti->id3.path);
+    loadruntimeinfo(ti->id3.path);
+}
 
+int rundb_init(void)
+{
+    unsigned char* ptr = (char*)&rundbheader.version;
+#ifdef ROCKBOX_LITTLE_ENDIAN
+    int i, *p;
+#endif
+
+    if(!tagdb_initialized) /* forget it.*/
+        return -1;
+    
+    rundb_fd = open(ROCKBOX_DIR "/rockbox.rundb", O_CREAT|O_RDWR);
+    if (rundb_fd < 0) {
+        DEBUGF("Failed opening database\n");
+        return -1;
+    }
+    if(read(rundb_fd, &rundbheader, 8)!=8) {
+        ptr[0]=ptr[1]='R';
+        ptr[2]='D';
+        ptr[3]=0x1;
+        rundbheader.entrycount=0;
+        writerundbheader();
+    }
+
+    if (ptr[0] != 'R' ||
+        ptr[1] != 'R' ||
+        ptr[2] != 'D')
+    {
+        splash(HZ,true,"Not a rockbox runtime database!");
+        return -1;
+    }
+#ifdef ROCKBOX_LITTLE_ENDIAN
+    p=(int *)&rundbheader;
+    for(i=0;i<2;i++) {
+        *p=BE32(*p);
+        p++;
+    }
+#endif
+    if ( (rundbheader.version&0xFF) != RUNDB_VERSION)
+    {
+        splash(HZ,true,"Unsupported runtime database version %d!", rundbheader.version&0xFF);
+        return -1;
+    }
+
+    rundb_initialized = 1;
+    audio_set_track_changed_event(&rundb_track_changed);
+    memset(&rundbentry,0,sizeof(struct rundb_entry));
+    rundbsize=lseek(rundb_fd,0,SEEK_END);
+    return 0;
+}
+
+void writerundbheader(void) {
+  lseek(rundb_fd,0,SEEK_SET);
+  write(rundb_fd, &rundbheader, 8);
+  fsync(rundb_fd);
+}
+
+#define getrundbentrybyrecord(_x_)  getrundbentrybyoffset(8+_x_*20)
+
+void getrundbentrybyoffset(int offset) {
+    lseek(rundb_fd,offset,SEEK_SET);
+    read(rundb_fd,&rundbentry,20);
+    currentreoffset=offset;
+#ifdef ROCKBOX_LITTLE_ENDIAN
+    rundbentry.fileentry=BE32(rundbentry.fileentry);
+    rundbentry.hash=BE32(rundbentry.hash);
+    rundbentry.rating=BE16(rundbentry.rating);
+    rundbentry.voladjust=BE16(rundbentry.voladjust);
+    rundbentry.playcount=BE32(rundbentry.playcount);
+    rundbentry.lastplayed=BE32(rundbentry.lastplayed);
+#endif
+}
+
+int getrundbentrybyhash(int hash) {
+    int min=0;
+    for(min=0;min<rundbheader.entrycount;min++) {
+        getrundbentrybyrecord(min);
+        if(hash==rundbentry.hash)
+             return 1;
+    }
+    memset(&rundbentry,0,sizeof(struct rundb_entry));
+    return 0;
+}
+
+void writerundbentry(void) {
+    if(rundbentry.hash==0) // 0 = invalid rundb info.
+       return;
+    lseek(rundb_fd,currentreoffset,SEEK_SET);
+    write(rundb_fd,&rundbentry,20);
+    fsync(rundb_fd);
+}
+
+void loadruntimeinfo(char *filename) {
+    memset(&rundbentry,0,sizeof(struct rundb_entry));
+    valid_file=0;
+    if(!getfentrybyfilename(filename)) 
+        return; /* file is not in tagdatabase, could not load. */
+    valid_file=1;
+    if(fe.rundbentry!=-1&&fe.rundbentry<rundbsize) {
+        logf("load rundbentry: 0x%x",fe.rundbentry);
+        getrundbentrybyoffset(fe.rundbentry);
+        if(fe.hash!=rundbentry.hash) {
+            logf("Rundb: Hash mismatch. trying to repair entry.",fe.hash,rundbentry.hash);
+            addrundbentry();
+        }
+    }
+    else  // add new rundb entry.
+        addrundbentry();
+}
+
+void addrundbentry() {
+    // first search for an entry with an equal hash.
+    if(getrundbentrybyhash(fe.hash)) {
+        logf("Found existing rundb entry: 0x%x",currentreoffset);
+        fe.rundbentry=currentreoffset;
+        writefentry();
+        return;
+    }
+    rundbheader.entrycount++;
+    writerundbheader();
+    fe.rundbentry=currentreoffset=lseek(rundb_fd,0,SEEK_END);
+    logf("Add rundb entry: 0x%x hash: 0x%x",fe.rundbentry,fe.hash);
+    rundbentry.hash=fe.hash;
+    rundbentry.fileentry=currentfeoffset;
+    writefentry();
+    writerundbentry();
+    rundbsize=lseek(rundb_fd,0,SEEK_END);
+}
+
+void increaseplaycount(void) {
+    if(rundbentry.hash==0) // 0 = invalid rundb info.
+       return;
+    rundbentry.playcount++;
+    writerundbentry();
+}
+
+void setrating(int rating) {
+    if(rundbentry.hash==0) // 0 = invalid rundb info.
+       return;
+    rundbentry.rating=rating;
+    writerundbentry();
+}
 
 /*** end RuntimeDatabase code ***/
-
-#endif
diff --git a/apps/database.h b/apps/database.h
index 28840a6..2c8de59 100644
--- a/apps/database.h
+++ b/apps/database.h
@@ -74,6 +74,9 @@
 
 #define TAGDB_VERSION 3
 
+extern int rundb_fd, rundb_initialized;
+extern struct rundb_header rundbheader;
+
 struct rundb_header {
         int version;
         int entrycount;
@@ -92,4 +95,9 @@
 
 #define RUNDB_VERSION 1
 
+void tagdb_shutdown(void);
+void addrundbentry(void);
+void loadruntimeinfo(char *filename);
+void increaseplaycount(void);
+void setrating(int rating);
 #endif
diff --git a/apps/main.c b/apps/main.c
index 9b9bae5..56d1e5c 100644
--- a/apps/main.c
+++ b/apps/main.c
@@ -288,6 +288,7 @@
     pcm_init_recording();
 #endif
     talk_init();
+    rundb_init();
 
 #ifdef AUTOROCK
     {
diff --git a/apps/plugin.c b/apps/plugin.c
index e51a00c..c92813d 100644
--- a/apps/plugin.c
+++ b/apps/plugin.c
@@ -329,6 +329,9 @@
 #ifdef ROCKBOX_HAS_LOGF
     logf,
 #endif
+    &rundbheader,
+    &rundb_fd,
+    &rundb_initialized,            
     strncmp,
 };
 
diff --git a/apps/plugin.h b/apps/plugin.h
index 0010d6d..3f3ed32 100644
--- a/apps/plugin.h
+++ b/apps/plugin.h
@@ -87,7 +87,7 @@
 #endif
 
 /* increase this every time the api struct changes */
-#define PLUGIN_API_VERSION 42
+#define PLUGIN_API_VERSION 44
 
 /* update this to latest version if a change to the api struct breaks
    backwards compatibility (and please take the opportunity to sort in any 
@@ -399,6 +399,9 @@
 #ifdef ROCKBOX_HAS_LOGF
     void (*logf)(const char *fmt, ...);
 #endif
+    struct rundb_header *rundbheader;
+    int *rundb_fd;
+    int *rundb_initialized;            
     int (*strncmp)(const char *, const char *, size_t);
 };
 
diff --git a/apps/plugins/databox/edittoken.h b/apps/plugins/databox/edittoken.h
index 77c948b..2fcb874 100644
--- a/apps/plugins/databox/edittoken.h
+++ b/apps/plugins/databox/edittoken.h
@@ -88,7 +88,7 @@
 
 struct token {
     char kind;
-    char spelling[SPELLING_LENGTH + 1];
+    char spelling[SPELLING_LENGTH + 3];
     long intvalue;
 };
 char *tokentypetostring(int tokentype);
diff --git a/apps/plugins/searchengine/dbinterface.c b/apps/plugins/searchengine/dbinterface.c
index c098079..e105106 100644
--- a/apps/plugins/searchengine/dbinterface.c
+++ b/apps/plugins/searchengine/dbinterface.c
@@ -29,6 +29,7 @@
 #define FILEENTRY_SIZE    (rb->tagdbheader->filelen+12)
 #define ALBUMENTRY_SIZE   (rb->tagdbheader->albumlen+4+rb->tagdbheader->songarraylen*4)
 #define ARTISTENTRY_SIZE  (rb->tagdbheader->artistlen+rb->tagdbheader->albumarraylen*4)
+#define RUNDBENTRY_SIZE   20
 
 #define FILERECORD2OFFSET(_x_) (rb->tagdbheader->filestart + _x_ * FILEENTRY_SIZE)
 
@@ -45,7 +46,7 @@
     // zero all entries.
     for(i=0;i<sizeof(struct entry)*rb->tagdbheader->filecount;i++) 
         *(p++)=0;
-    if(*rb->tagdb_initialized!=1) {
+    if(!*rb->tagdb_initialized) {
         if(!rb->tagdb_init()) {
             // failed loading db
             return -1;
@@ -110,8 +111,18 @@
 }
 
 void loadrundbdata() {
-    // we don't do this yet.
     currententry->loadedrundbdata=1;
+    if(!*rb->rundb_initialized) 
+        return;
+    if(currententry->rundbentry==-1)
+        return;
+    rb->lseek(*rb->rundb_fd,currententry->rundbentry,SEEK_SET);
+    currententry->rundbfe=readlong(*rb->rundb_fd);
+    currententry->rundbhash=readlong(*rb->rundb_fd);
+    currententry->rating=readshort(*rb->rundb_fd);
+    currententry->voladj=readshort(*rb->rundb_fd);
+    currententry->playcount=readlong(*rb->rundb_fd);
+    currententry->lastplayed=readlong(*rb->rundb_fd);
 }
 
 void loadartistname() {
diff --git a/apps/plugins/searchengine/dbinterface.h b/apps/plugins/searchengine/dbinterface.h
index 98596ae..b168456 100644
--- a/apps/plugins/searchengine/dbinterface.h
+++ b/apps/plugins/searchengine/dbinterface.h
@@ -30,13 +30,15 @@
         loadedalbumname,
         loadedartistname;
     char *filename;
-    long hash;
-    long songentry;
+    long hash,rundbhash;
+    long songentry,rundbfe;
     long rundbentry;
     short year;
     short bitrate;
-    long rating;
+    short rating;
     long playcount;
+    long lastplayed;
+    short voladj;
     char *title;
     char *genre;
     long artistoffset;
diff --git a/apps/plugins/searchengine/token.h b/apps/plugins/searchengine/token.h
index cd33769..6deecd8 100644
--- a/apps/plugins/searchengine/token.h
+++ b/apps/plugins/searchengine/token.h
@@ -54,13 +54,12 @@
 #define INTVALUE_GENRE        17
 #define INTVALUE_FILENAME     18
 
-/* static char *spelling[] = { "not", "and", "or",">",">=","<", "<=","==","!=",
-            "contains","(",")" }; */
+#define SPELLING_LENGTH 100
 
 struct token {
-      unsigned char kind;
-      char spelling[255]; // 255 should make it aligned again..
-      long intvalue;
+    char kind;
+    char spelling[SPELLING_LENGTH + 3];
+    long intvalue;
 };
 
 char *getstring(struct token *token);
diff --git a/tools/songdb.pl b/tools/songdb.pl
index 9ed0c54..202ab06 100755
--- a/tools/songdb.pl
+++ b/tools/songdb.pl
@@ -9,7 +9,7 @@
 
 use vorbiscomm;
 
-my $db = "rockbox.id3db";
+my $db = "rockbox.tagdb";
 my $dir;
 my $strip;
 my $add;
@@ -626,7 +626,7 @@
         my $str = $f."\x00" x ($maxfilelen- length($f));
 	my $id3 = $entries{$f}; 
         print DB $str;
-	dumpint(0);
+	    dumpint(0); # TODO: add hashing; 0 for now.
         dumpint($id3->{'songoffset'});
         dumpint(-1);
     }
diff --git a/tools/testdbv2.c b/tools/testdbv2.c
index 60cdcc7..c800437 100644
--- a/tools/testdbv2.c
+++ b/tools/testdbv2.c
@@ -128,12 +128,13 @@
 void showrundb(int offset) {
     fseek(fp2,offset,SEEK_SET);
     fread(&RundbEntry,sizeof(struct RundbEntry),1,fp2);
-   RundbEntry.hash=BE32(RundbEntry.hash);
-   RundbEntry.playcount=BE32(RundbEntry.playcount);
-   RundbEntry.lastplayed=BE32(RundbEntry.lastplayed);
-   RundbEntry.rating=BE16(RundbEntry.rating);
-   RundbEntry.voladj=BE16(RundbEntry.voladj);
-    printf("Offset: 0x%x\nHash: 0x%x\nRating: %d\nVoladj: 0x%x\n",offset,RundbEntry.hash,RundbEntry.rating,RundbEntry.voladj);
+    RundbEntry.file=BE32(RundbEntry.file);
+    RundbEntry.hash=BE32(RundbEntry.hash);
+    RundbEntry.playcount=BE32(RundbEntry.playcount);
+    RundbEntry.lastplayed=BE32(RundbEntry.lastplayed);
+    RundbEntry.rating=BE16(RundbEntry.rating);
+    RundbEntry.voladj=BE16(RundbEntry.voladj);
+    printf("Offset: 0x%x\nFileEntry: 0x%x\nHash: 0x%x\nRating: %d\nVoladj: 0x%x\n",offset,RundbEntry.file,RundbEntry.hash,RundbEntry.rating,RundbEntry.voladj);
     printf("Playcount: 0x%x\nLastplayed: %d\n",RundbEntry.playcount,RundbEntry.lastplayed);
 }