da326050de
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@7004 a1c6a512-1295-4272-9138-f99709370657
526 lines
14 KiB
C
526 lines
14 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2005 by Michiel van der Kolk
|
|
*
|
|
* 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.
|
|
*
|
|
****************************************************************************/
|
|
#define _GNU_SOURCE
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include "file.h"
|
|
#include "screens.h"
|
|
#include "kernel.h"
|
|
#include "tree.h"
|
|
#include "lcd.h"
|
|
#include "font.h"
|
|
#include "settings.h"
|
|
#include "icons.h"
|
|
#include "status.h"
|
|
#include "debug.h"
|
|
#include "button.h"
|
|
#include "menu.h"
|
|
#include "main_menu.h"
|
|
#include "mpeg.h"
|
|
#include "misc.h"
|
|
#include "ata.h"
|
|
#include "wps.h"
|
|
#include "filetypes.h"
|
|
#include "applimits.h"
|
|
#include "icons.h"
|
|
#include "lang.h"
|
|
#include "keyboard.h"
|
|
#include "database.h"
|
|
#include "autoconf.h"
|
|
#include "playback.h"
|
|
#include "logf.h"
|
|
|
|
/* internal functions */
|
|
void writetagdbheader(void);
|
|
void writefentry(struct mp3entry *id);
|
|
void getfentrybyoffset(struct mp3entry *id,int offset);
|
|
void update_fentryoffsets(int start, int end);
|
|
void writerundbheader(void);
|
|
void getrundbentrybyoffset(struct mp3entry *id,int offset);
|
|
void writerundbentry(struct mp3entry *id);
|
|
int getfentrybyfilename(struct mp3entry *id);
|
|
void clearfileentryinfo(struct mp3entry *id);
|
|
void clearruntimeinfo(struct mp3entry *id);
|
|
int findrundbentry(struct mp3entry *id);
|
|
|
|
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[MAX_PATH];
|
|
|
|
int tagdb_fd = -1;
|
|
int tagdb_initialized = 0;
|
|
struct tagdb_header tagdbheader;
|
|
|
|
int tagdb_init(void)
|
|
{
|
|
unsigned char* ptr = (char*)&tagdbheader.version;
|
|
#ifdef ROCKBOX_LITTLE_ENDIAN
|
|
int i, *p;
|
|
#endif
|
|
|
|
tagdb_fd = open(ROCKBOX_DIR "/rockbox.tagdb", O_RDWR);
|
|
if (tagdb_fd < 0) {
|
|
DEBUGF("Failed opening database\n");
|
|
return -1;
|
|
}
|
|
read(tagdb_fd, &tagdbheader, 68);
|
|
|
|
if (ptr[0] != 'R' ||
|
|
ptr[1] != 'D' ||
|
|
ptr[2] != 'B')
|
|
{
|
|
splash(HZ,true,"Not a rockbox ID3 database!");
|
|
return -1;
|
|
}
|
|
#ifdef ROCKBOX_LITTLE_ENDIAN
|
|
p=(int *)&tagdbheader;
|
|
for(i=0;i<17;i++) {
|
|
*p=BE32(*p);
|
|
p++;
|
|
}
|
|
#endif
|
|
if ( (tagdbheader.version&0xFF) != TAGDB_VERSION)
|
|
{
|
|
splash(HZ,true,"Unsupported database version %d!",
|
|
tagdbheader.version&0xFF);
|
|
return -1;
|
|
}
|
|
|
|
if (tagdbheader.songstart > tagdbheader.filestart ||
|
|
tagdbheader.albumstart > tagdbheader.songstart ||
|
|
tagdbheader.artiststart > tagdbheader.albumstart)
|
|
{
|
|
splash(HZ,true,"Corrupt ID3 database!");
|
|
return -1;
|
|
}
|
|
|
|
tagdb_initialized = 1;
|
|
return 0;
|
|
}
|
|
|
|
void tagdb_shutdown(void)
|
|
{
|
|
if (tagdb_fd >= 0)
|
|
close(tagdb_fd);
|
|
tagdb_initialized = 0;
|
|
}
|
|
|
|
/* NOTE: All these functions below are yet untested. */
|
|
|
|
/*** TagDatabase code ***/
|
|
|
|
void writetagdbheader(void)
|
|
{
|
|
lseek(tagdb_fd,0,SEEK_SET);
|
|
write(tagdb_fd, &tagdbheader, 68);
|
|
fsync(tagdb_fd);
|
|
}
|
|
|
|
void writefentry(struct mp3entry *id)
|
|
{ long temp;
|
|
if(!id->fileentryoffset)
|
|
return;
|
|
lseek(tagdb_fd,id->fileentryoffset,SEEK_SET);
|
|
write(tagdb_fd,id->path,tagdbheader.filelen);
|
|
temp=id->filehash;
|
|
write(tagdb_fd,&temp,4);
|
|
temp=id->songentryoffset;
|
|
write(tagdb_fd,&temp,4);
|
|
temp=id->rundbentryoffset;
|
|
write(tagdb_fd,&temp,4);
|
|
}
|
|
|
|
void getfentrybyoffset(struct mp3entry *id,int offset)
|
|
{
|
|
clearfileentryinfo(id);
|
|
lseek(tagdb_fd,offset,SEEK_SET);
|
|
read(tagdb_fd,sbuf,tagdbheader.filelen);
|
|
id->fileentryoffset=offset;
|
|
read(tagdb_fd,&id->filehash,4);
|
|
read(tagdb_fd,&id->songentryoffset,4);
|
|
read(tagdb_fd,&id->rundbentryoffset,4);
|
|
}
|
|
|
|
#define getfentrybyrecord(_y_,_x_) getfentrybyoffset(_y_,FILERECORD2OFFSET(_x_))
|
|
|
|
int getfentrybyfilename(struct mp3entry *id)
|
|
{
|
|
int min=0;
|
|
int max=tagdbheader.filecount;
|
|
while(min<max) {
|
|
int mid=(min+max)/2;
|
|
int compare;
|
|
getfentrybyrecord(id,mid);
|
|
compare=strcasecmp(id->path,sbuf);
|
|
if(compare==0)
|
|
return 1;
|
|
else if(compare<0)
|
|
max=mid;
|
|
else
|
|
min=mid+1;
|
|
}
|
|
clearfileentryinfo(id);
|
|
return 0;
|
|
}
|
|
|
|
#if 0
|
|
int getfentrybyhash(int hash)
|
|
{
|
|
int min=0;
|
|
for(min=0;min<tagdbheader.filecount;min++) {
|
|
getfentrybyrecord(min);
|
|
if(hash==fe.hash)
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int deletefentry(char *fname)
|
|
{
|
|
if(!getfentrybyfilename(fname))
|
|
return 0;
|
|
int restrecord = currentferecord+1;
|
|
if(currentferecord!=tagdbheader.filecount) /* file is not last entry */
|
|
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);
|
|
writetagdbheader();
|
|
return 1;
|
|
}
|
|
|
|
void update_fentryoffsets(int start, int end)
|
|
{
|
|
int i;
|
|
for(i=start;i<end;i++) {
|
|
getfentrybyrecord(i);
|
|
if(fe.songentry!=-1) {
|
|
int p;
|
|
lseek(tagdb_fd,fe.songentry+tagdbheader.songlen+8,SEEK_SET);
|
|
read(tagdb_fd,&p,sizeof(int));
|
|
if(p!=currentfeoffset) {
|
|
p=currentfeoffset;
|
|
lseek(tagdb_fd,fe.songentry+tagdbheader.songlen+8,SEEK_SET);
|
|
write(tagdb_fd,&p,sizeof(int));
|
|
}
|
|
}
|
|
if(fe.rundbentry!=-1) {
|
|
splash(HZ*2,true, "o.o.. found a rundbentry? o.o; didn't update "
|
|
"it, update the code o.o;");
|
|
}
|
|
}
|
|
}
|
|
|
|
int tagdb_shiftdown(int targetoffset, int startingoffset, int bytes)
|
|
{
|
|
int amount;
|
|
if(targetoffset>=startingoffset) {
|
|
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))) {
|
|
int written;
|
|
startingoffset+=amount;
|
|
lseek(tagdb_fd,targetoffset,SEEK_SET);
|
|
written=write(tagdb_fd,sbuf,amount);
|
|
targetoffset+=written;
|
|
if(amount!=written) {
|
|
splash(HZ*2,true,"Something went very wrong. expect database "
|
|
"corruption. (tagdb_shiftdown)");
|
|
return 0;
|
|
}
|
|
lseek(tagdb_fd,startingoffset,SEEK_SET);
|
|
bytes-=amount;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int tagdb_shiftup(int targetoffset, int startingoffset, int bytes)
|
|
{
|
|
int amount,amount2;
|
|
int readpos,writepos,filelen;
|
|
if(targetoffset<=startingoffset) {
|
|
splash(HZ*2,true,"Um. no. (tagdb_shiftup)");
|
|
return 0;
|
|
}
|
|
filelen=lseek(tagdb_fd,0,SEEK_END);
|
|
readpos=startingoffset+bytes;
|
|
do {
|
|
amount=bytes>1024 ? 1024 : bytes;
|
|
readpos-=amount;
|
|
writepos=readpos+targetoffset-startingoffset;
|
|
lseek(tagdb_fd,readpos,SEEK_SET);
|
|
amount2=read(tagdb_fd,sbuf,amount);
|
|
if(amount2!=amount) {
|
|
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,true,"Something went very wrong. expect database "
|
|
"corruption. (tagdb_shiftup)");
|
|
return 0;
|
|
}
|
|
bytes-=amount;
|
|
} while (amount>0);
|
|
if(bytes==0)
|
|
return 1;
|
|
else {
|
|
splash(HZ*2,true,"Something went wrong, >.>;; (tagdb_shiftup)");
|
|
return 0;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*** end TagDatabase code ***/
|
|
|
|
int rundb_fd = -1;
|
|
int rundb_initialized = 0;
|
|
struct rundb_header rundbheader;
|
|
|
|
static long rundbsize;
|
|
|
|
/*** RuntimeDatabase code ***/
|
|
|
|
void rundb_unbuffer_track(struct mp3entry *id, bool last_track) {
|
|
writeruntimeinfo(id);
|
|
if(last_track) {
|
|
fsync(rundb_fd);
|
|
fsync(tagdb_fd);
|
|
}
|
|
}
|
|
|
|
void rundb_track_change(struct track_info *ti) {
|
|
ti->id3.playcount++;
|
|
}
|
|
|
|
void rundb_buffer_track(struct mp3entry *id, bool last_track) {
|
|
loadruntimeinfo(id);
|
|
if(last_track) {
|
|
fsync(rundb_fd);
|
|
fsync(tagdb_fd);
|
|
}
|
|
}
|
|
|
|
int rundb_init(void)
|
|
{
|
|
#if CONFIG_HWCODEC != MASNONE
|
|
return -1;
|
|
#else
|
|
unsigned char* ptr = (char*)&rundbheader.version;
|
|
#ifdef ROCKBOX_LITTLE_ENDIAN
|
|
int i, *p;
|
|
#endif
|
|
if(!tagdb_initialized) /* forget it.*/
|
|
return -1;
|
|
|
|
if(!global_settings.runtimedb) /* user doesn't care */
|
|
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;
|
|
/* hooks disabled for archos, rendering the runtime database not working,
|
|
* re enable when these callbacks are implemented in mpeg.c */
|
|
#if CONFIG_HWCODEC == MASNONE
|
|
audio_set_track_buffer_event(&rundb_buffer_track);
|
|
audio_set_track_changed_event(&rundb_track_change);
|
|
audio_set_track_unbuffer_event(&rundb_unbuffer_track);
|
|
#endif
|
|
rundbsize=lseek(rundb_fd,0,SEEK_END);
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
void rundb_shutdown(void)
|
|
{
|
|
if (rundb_fd >= 0)
|
|
close(rundb_fd);
|
|
rundb_initialized = 0;
|
|
#if CONFIG_HWCODEC == MASNONE
|
|
audio_set_track_buffer_event(NULL);
|
|
audio_set_track_unbuffer_event(NULL);
|
|
audio_set_track_changed_event(NULL);
|
|
#endif
|
|
}
|
|
|
|
void writerundbheader(void)
|
|
{
|
|
lseek(rundb_fd,0,SEEK_SET);
|
|
write(rundb_fd, &rundbheader, 8);
|
|
}
|
|
|
|
#define getrundbentrybyrecord(_y_,_x_) getrundbentrybyoffset(_y_,8+_x_*20)
|
|
|
|
void getrundbentrybyoffset(struct mp3entry *id,int offset) {
|
|
lseek(rundb_fd,offset+4,SEEK_SET); // skip fileentry offset
|
|
id->rundbentryoffset=offset;
|
|
read(rundb_fd,&id->rundbhash,4);
|
|
read(rundb_fd,&id->rating,2);
|
|
read(rundb_fd,&id->voladjust,2);
|
|
read(rundb_fd,&id->playcount,4);
|
|
read(rundb_fd,&id->lastplayed,4);
|
|
#ifdef ROCKBOX_LITTLE_ENDIAN
|
|
id->rundbhash=BE32(id->rundbhash);
|
|
id->rating=BE16(id->rating);
|
|
id->voladjust=BE16(id->voladjust);
|
|
id->playcount=BE32(id->playcount);
|
|
id->lastplayed=BE32(id->lastplayed);
|
|
#endif
|
|
}
|
|
|
|
int getrundbentrybyhash(struct mp3entry *id)
|
|
{
|
|
int min=0;
|
|
for(min=0;min<rundbheader.entrycount;min++) {
|
|
getrundbentrybyrecord(id,min);
|
|
if(id->filehash==id->rundbhash)
|
|
return 1;
|
|
}
|
|
clearruntimeinfo(id);
|
|
return 0;
|
|
}
|
|
|
|
void writerundbentry(struct mp3entry *id)
|
|
{
|
|
if(id->rundbhash==0) /* 0 = invalid rundb info. */
|
|
return;
|
|
lseek(rundb_fd,id->rundbentryoffset,SEEK_SET);
|
|
write(rundb_fd,&id->fileentryoffset,4);
|
|
write(rundb_fd,&id->rundbhash,4);
|
|
write(rundb_fd,&id->rating,2);
|
|
write(rundb_fd,&id->voladjust,2);
|
|
write(rundb_fd,&id->playcount,4);
|
|
write(rundb_fd,&id->lastplayed,4);
|
|
}
|
|
|
|
void writeruntimeinfo(struct mp3entry *id) {
|
|
if(!id->rundbhash)
|
|
addrundbentry(id);
|
|
else
|
|
writerundbentry(id);
|
|
}
|
|
|
|
void clearfileentryinfo(struct mp3entry *id) {
|
|
id->fileentryoffset=0;
|
|
id->filehash=0;
|
|
id->songentryoffset=0;
|
|
id->rundbentryoffset=0;
|
|
}
|
|
|
|
void clearruntimeinfo(struct mp3entry *id) {
|
|
id->rundbhash=0;
|
|
id->rating=0;
|
|
id->voladjust=0;
|
|
id->playcount=0;
|
|
id->lastplayed=0;
|
|
}
|
|
|
|
void loadruntimeinfo(struct mp3entry *id)
|
|
{
|
|
clearruntimeinfo(id);
|
|
clearfileentryinfo(id);
|
|
if(!getfentrybyfilename(id)) {
|
|
logf("tagdb fail: %s",id->path);
|
|
return; /* file is not in tagdatabase, could not load. */
|
|
}
|
|
if(id->rundbentryoffset!=-1 && id->rundbentryoffset<rundbsize) {
|
|
logf("load rundbentry: 0x%x", id->rundbentryoffset);
|
|
getrundbentrybyoffset(id, id->rundbentryoffset);
|
|
if(id->filehash != id->rundbhash) {
|
|
logf("Rundb: Hash mismatch. trying to repair entry.",
|
|
id->filehash, id->rundbhash);
|
|
findrundbentry(id);
|
|
}
|
|
}
|
|
else
|
|
#ifdef ROCKBOX_HAS_LOGF
|
|
if(!findrundbentry(id))
|
|
logf("rundb:no entry and not found.");
|
|
#else
|
|
findrundbentry(id);
|
|
#endif
|
|
}
|
|
|
|
int findrundbentry(struct mp3entry *id) {
|
|
if(getrundbentrybyhash(id)) {
|
|
logf("Found existing rundb entry: 0x%x",id->rundbentryoffset);
|
|
writefentry(id);
|
|
return 1;
|
|
}
|
|
clearruntimeinfo(id);
|
|
return 0;
|
|
}
|
|
|
|
void addrundbentry(struct mp3entry *id)
|
|
{
|
|
/* first search for an entry with an equal hash. */
|
|
/* if(findrundbentry(id))
|
|
return; disabled cause this SHOULD have been checked at the buffer event.. */
|
|
rundbheader.entrycount++;
|
|
writerundbheader();
|
|
id->rundbentryoffset=lseek(rundb_fd,0,SEEK_END);
|
|
logf("Add rundb entry: 0x%x hash: 0x%x",id->rundbentryoffset,id->filehash);
|
|
id->rundbhash=id->filehash;
|
|
writefentry(id);
|
|
writerundbentry(id);
|
|
rundbsize=lseek(rundb_fd,0,SEEK_END);
|
|
}
|
|
|
|
/*** end RuntimeDatabase code ***/
|