nathan@0: /* nathan@0: * MP3/MPlayer plugin to VDR (C++) nathan@0: * nathan@2: * (C) 2001-2007 Stefan Huelswitt nathan@0: * nathan@0: * This code is free software; you can redistribute it and/or nathan@0: * modify it under the terms of the GNU General Public License nathan@0: * as published by the Free Software Foundation; either version 2 nathan@0: * of the License, or (at your option) any later version. nathan@0: * nathan@0: * This code is distributed in the hope that it will be useful, nathan@0: * but WITHOUT ANY WARRANTY; without even the implied warranty of nathan@0: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nathan@0: * GNU General Public License for more details. nathan@0: * nathan@0: * You should have received a copy of the GNU General Public License nathan@0: * along with this program; if not, write to the Free Software nathan@0: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. nathan@0: * Or, point your browser to http://www.gnu.org/copyleft/gpl.html nathan@0: */ nathan@0: nathan@0: #include nathan@0: #include nathan@0: #include nathan@0: #include nathan@0: #include nathan@0: nathan@0: #include nathan@0: nathan@0: #include "common.h" nathan@0: #include "data-mp3.h" nathan@0: #include "data-src.h" nathan@0: #include "decoder.h" nathan@0: #include "decoder-core.h" nathan@0: #include "decoder-mp3.h" nathan@0: #include "decoder-mp3-stream.h" nathan@0: #include "decoder-snd.h" nathan@0: #include "decoder-ogg.h" nathan@0: nathan@0: #define CACHEFILENAME "id3info.cache" nathan@0: #define CACHESAVETIMEOUT 120 // secs nathan@0: #define CACHEPURGETIMEOUT 120 // days nathan@0: nathan@0: extern cFileSources MP3Sources; nathan@0: nathan@0: cInfoCache InfoCache; nathan@0: char *cachedir=0; nathan@0: nathan@0: int MakeHashBuff(const char *buff, int len) nathan@0: { nathan@0: int h=len; nathan@0: while(len--) h=(h*13 + *buff++) & 0x7ff; nathan@0: return h; nathan@0: } nathan@0: nathan@0: // --- cSongInfo --------------------------------------------------------------- nathan@0: nathan@0: cSongInfo::cSongInfo(void) nathan@0: { nathan@0: Title=Artist=Album=0; nathan@0: Clear(); nathan@0: } nathan@0: nathan@0: cSongInfo::~cSongInfo() nathan@0: { nathan@0: Clear(); nathan@0: } nathan@0: nathan@0: void cSongInfo::Clear(void) nathan@0: { nathan@0: Frames=0; Total=-1; DecoderID=0; nathan@0: SampleFreq=Channels=Bitrate=MaxBitrate=ChMode=-1; nathan@0: free(Title); Title=0; nathan@0: free(Artist); Artist=0; nathan@0: free(Album); Album=0; nathan@0: Year=-1; nathan@0: Level=Peak=0.0; nathan@0: infoDone=false; nathan@0: } nathan@0: nathan@0: void cSongInfo::Set(cSongInfo *si) nathan@0: { nathan@0: Clear(); InfoDone(); nathan@0: Frames=si->Frames; nathan@0: Total=si->Total; nathan@0: SampleFreq=si->SampleFreq; nathan@0: Channels=si->Channels; nathan@0: Bitrate=si->Bitrate; nathan@0: MaxBitrate=si->MaxBitrate; nathan@0: ChMode=si->ChMode; nathan@0: Year=si->Year; nathan@0: Title=si->Title ? strdup(si->Title):0; nathan@0: Artist=si->Artist ? strdup(si->Artist):0; nathan@0: Album=si->Album ? strdup(si->Album):0; nathan@0: if(si->Level>0.0) { // preserve old level nathan@0: Level=si->Level; nathan@0: Peak=si->Peak; nathan@0: } nathan@0: DecoderID=si->DecoderID; nathan@0: } nathan@0: nathan@0: void cSongInfo::FakeTitle(const char *filename, const char *extention) nathan@0: { nathan@0: // if no title, try to build a reasonable from the filename nathan@0: if(!Title && filename) { nathan@0: char *s=rindex(filename,'/'); nathan@0: if(s && *s=='/') { nathan@0: s++; nathan@0: Title=strdup(s); nathan@0: strreplace(Title,'_',' '); nathan@0: if(extention) { // strip given extention nathan@0: int l=strlen(Title)-strlen(extention); nathan@0: if(l>0 && !strcasecmp(Title+l,extention)) Title[l]=0; nathan@0: } nathan@0: else { // strip any extention nathan@0: s=rindex(Title,'.'); nathan@0: if(s && *s=='.' && strlen(s)<=5) *s=0; nathan@0: } nathan@0: d(printf("mp3: faking title '%s' from filename '%s'\n",Title,filename)) nathan@0: } nathan@0: } nathan@0: } nathan@0: nathan@0: // --- cFileInfo --------------------------------------------------------------- nathan@0: nathan@0: cFileInfo::cFileInfo(void) nathan@0: { nathan@0: Filename=FsID=0; Clear(); nathan@0: } nathan@0: nathan@0: cFileInfo::cFileInfo(const char *Name) nathan@0: { nathan@0: Filename=FsID=0; Clear(); nathan@0: Filename=strdup(Name); nathan@0: } nathan@0: nathan@0: cFileInfo::~cFileInfo() nathan@0: { nathan@0: Clear(); nathan@0: } nathan@0: nathan@0: void cFileInfo::Clear(void) nathan@0: { nathan@0: free(Filename); Filename=0; nathan@0: free(FsID); FsID=0; nathan@0: Filesize=0; CTime=0; FsType=0; removable=-1; nathan@0: infoDone=false; nathan@0: } nathan@0: nathan@0: bool cFileInfo::Removable(void) nathan@0: { nathan@0: if(removable<0 && Filename) { nathan@0: cFileSource *src=MP3Sources.FindSource(Filename); nathan@0: if(src) removable=src->NeedsMount(); nathan@0: else removable=1; nathan@0: } nathan@0: return (removable!=0); nathan@0: } nathan@0: nathan@0: void cFileInfo::Set(cFileInfo *fi) nathan@0: { nathan@0: Clear(); InfoDone(); nathan@0: Filename=fi->Filename ? strdup(fi->Filename):0; nathan@0: FsID=fi->FsID ? strdup(fi->FsID):0; nathan@0: Filesize=fi->Filesize; nathan@0: CTime=fi->CTime; nathan@0: } nathan@0: nathan@0: nathan@0: bool cFileInfo::FileInfo(bool log) nathan@0: { nathan@0: if(Filename) { nathan@0: struct stat64 ds; nathan@0: if(!stat64(Filename,&ds)) { nathan@0: if(S_ISREG(ds.st_mode)) { nathan@0: free(FsID); FsID=0; nathan@0: FsType=0; nathan@0: struct statfs64 sfs; nathan@0: if(!statfs64(Filename,&sfs)) { nathan@0: if(Removable()) asprintf(&FsID,"%llx:%llx",sfs.f_blocks,sfs.f_files); nathan@0: FsType=sfs.f_type; nathan@0: } nathan@0: else if(errno!=ENOSYS && log) { esyslog("ERROR: can't statfs %s: %s",Filename,strerror(errno)); } nathan@0: Filesize=ds.st_size; nathan@0: CTime=ds.st_ctime; nathan@0: #ifdef CDFS_MAGIC nathan@0: if(FsType==CDFS_MAGIC) CTime=0; // CDFS returns mount time as ctime nathan@0: #endif nathan@0: InfoDone(); nathan@0: return true; nathan@0: } nathan@0: else if(log) { esyslog("ERROR: %s is not a regular file",Filename); } nathan@0: } nathan@0: else if(log) { esyslog("ERROR: can't stat %s: %s",Filename,strerror(errno)); } nathan@0: } nathan@0: return false; nathan@0: } nathan@0: nathan@0: // --- cDecoders --------------------------------------------------------------- nathan@0: nathan@0: cDecoder *cDecoders::FindDecoder(cFileObj *Obj) nathan@0: { nathan@0: const char *full=Obj->FullPath(); nathan@0: cFileInfo fi(full); nathan@0: cCacheData *dat; nathan@0: cDecoder *decoder=0; nathan@0: if(fi.FileInfo(false) && (dat=InfoCache.Search(&fi))) { nathan@0: if(dat->DecoderID) { nathan@0: //d(printf("mp3: found DecoderID '%s' for %s from cache\n",cDecoders::ID2Str(dat->DecoderID),Filename)) nathan@0: switch(dat->DecoderID) { nathan@0: case DEC_MP3: decoder=new cMP3Decoder(full); break; nathan@0: case DEC_MP3S: decoder=new cMP3StreamDecoder(full); break; nathan@0: #ifdef HAVE_SNDFILE nathan@0: case DEC_SND: decoder=new cSndDecoder(full); break; nathan@0: #endif nathan@0: #ifdef HAVE_VORBISFILE nathan@0: case DEC_OGG: decoder=new cOggDecoder(full); break; nathan@0: #endif nathan@0: default: esyslog("ERROR: bad DecoderID '%s' from info cache: %s",cDecoders::ID2Str(dat->DecoderID),full); break; nathan@0: } nathan@0: } nathan@0: dat->Unlock(); nathan@0: } nathan@0: nathan@0: if(!decoder || !decoder->Valid()) { nathan@0: // no decoder in cache or cached decoder doesn't matches. nathan@0: // try to detect a decoder nathan@0: nathan@0: delete decoder; decoder=0; nathan@0: #ifdef HAVE_SNDFILE nathan@0: if(!decoder) { nathan@0: decoder=new cSndDecoder(full); nathan@0: if(!decoder || !decoder->Valid()) { delete decoder; decoder=0; } nathan@0: } nathan@0: #endif nathan@0: #ifdef HAVE_VORBISFILE nathan@0: if(!decoder) { nathan@0: decoder=new cOggDecoder(full); nathan@0: if(!decoder || !decoder->Valid()) { delete decoder; decoder=0; } nathan@0: } nathan@0: #endif nathan@0: if(!decoder) { nathan@0: decoder=new cMP3StreamDecoder(full); nathan@0: if(!decoder || !decoder->Valid()) { delete decoder; decoder=0; } nathan@0: } nathan@0: if(!decoder) { nathan@0: decoder=new cMP3Decoder(full); nathan@0: if(!decoder || !decoder->Valid()) { delete decoder; decoder=0; } nathan@0: } nathan@0: if(!decoder) esyslog("ERROR: no decoder found for %s",Obj->Name()); nathan@0: } nathan@0: return decoder; nathan@0: } nathan@0: nathan@0: const char *cDecoders::ID2Str(int id) nathan@0: { nathan@0: switch(id) { nathan@0: case DEC_MP3: return DEC_MP3_STR; nathan@0: case DEC_MP3S: return DEC_MP3S_STR; nathan@0: case DEC_SND: return DEC_SND_STR; nathan@0: case DEC_OGG: return DEC_OGG_STR; nathan@0: } nathan@0: return 0; nathan@0: } nathan@0: nathan@0: int cDecoders::Str2ID(const char *str) nathan@0: { nathan@0: if (!strcmp(str,DEC_MP3_STR )) return DEC_MP3; nathan@0: else if(!strcmp(str,DEC_MP3S_STR)) return DEC_MP3S; nathan@0: else if(!strcmp(str,DEC_SND_STR )) return DEC_SND; nathan@0: else if(!strcmp(str,DEC_OGG_STR )) return DEC_OGG; nathan@0: return 0; nathan@0: } nathan@0: nathan@0: // --- cDecoder ---------------------------------------------------------------- nathan@0: nathan@0: cDecoder::cDecoder(const char *Filename) nathan@0: { nathan@0: filename=strdup(Filename); nathan@0: locked=0; urgentLock=playing=false; nathan@0: } nathan@0: nathan@0: cDecoder::~cDecoder() nathan@0: { nathan@0: free(filename); nathan@0: } nathan@0: nathan@0: void cDecoder::Lock(bool urgent) nathan@0: { nathan@0: locklock.Lock(); nathan@0: if(urgent && locked) urgentLock=true; // signal other locks to release quickly nathan@0: locked++; nathan@0: locklock.Unlock(); // don't hold the "locklock" when locking "lock", may cause a deadlock nathan@0: lock.Lock(); nathan@0: urgentLock=false; nathan@0: } nathan@0: nathan@0: void cDecoder::Unlock(void) nathan@0: { nathan@0: locklock.Lock(); nathan@0: locked--; nathan@0: lock.Unlock(); nathan@0: locklock.Unlock(); nathan@0: } nathan@0: nathan@0: bool cDecoder::TryLock(void) nathan@0: { nathan@0: bool res=false; nathan@0: locklock.Lock(); nathan@0: if(!locked && !playing) { nathan@0: Lock(); nathan@0: res=true; nathan@0: } nathan@0: locklock.Unlock(); nathan@0: return res; nathan@0: } nathan@0: nathan@0: // --- cCacheData ----------------------------------------------------- nathan@0: nathan@0: cCacheData::cCacheData(void) nathan@0: { nathan@0: touch=0; version=0; nathan@0: } nathan@0: nathan@0: void cCacheData::Touch(void) nathan@0: { nathan@0: touch=time(0); nathan@0: } nathan@0: nathan@0: #define SECS_PER_DAY (24*60*60) nathan@0: nathan@0: bool cCacheData::Purge(void) nathan@0: { nathan@0: time_t now=time(0); nathan@0: //XXX does this realy made sense? nathan@0: //if(touch+CACHEPURGETIMEOUT*SECS_PER_DAY < now) { nathan@0: // d(printf("cache: purged: timeout %s\n",Filename)) nathan@0: // return true; nathan@0: // } nathan@0: if(touch+CACHEPURGETIMEOUT*SECS_PER_DAY/10 < now) { nathan@0: if(!Removable()) { // is this a permant source? nathan@0: struct stat64 ds; // does the file exists? if not, purge nathan@0: if(stat64(Filename,&ds) || !S_ISREG(ds.st_mode) || access(Filename,R_OK)) { nathan@0: d(printf("cache: purged: file not found %s\n",Filename)) nathan@0: return true; nathan@0: } nathan@0: } nathan@0: } nathan@0: return false; nathan@0: } nathan@0: nathan@0: bool cCacheData::Upgrade(void) nathan@0: { nathan@0: if(version<7) { nathan@0: if(DecoderID==DEC_SND || (Title && startswith(Title,"track-"))) nathan@0: return false; // Trash older SND entries (incomplete) nathan@0: nathan@0: if(Removable()) { nathan@0: if(!FsID) FsID=strdup("old"); // Dummy entry, will be replaced in InfoCache::Search() nathan@0: } nathan@0: else { free(FsID); FsID=0; } nathan@0: } nathan@0: if(version<4) { nathan@0: Touch(); // Touch entry nathan@0: } nathan@0: if(version<3 && !Title) { nathan@0: FakeTitle(Filename,".mp3"); // Fake title nathan@0: } nathan@0: if(version<2 && Bitrate<=0) { nathan@0: return false; // Trash entry without bitrate nathan@0: } nathan@0: return true; nathan@0: } nathan@0: nathan@0: void cCacheData::Create(cFileInfo *fi, cSongInfo *si) nathan@0: { nathan@0: cFileInfo::Set(fi); nathan@0: cSongInfo::Set(si); nathan@0: hash=MakeHash(Filename); nathan@0: Touch(); nathan@0: } nathan@0: nathan@0: bool cCacheData::Save(FILE *f) nathan@0: { nathan@0: fprintf(f,"##BEGIN\n" nathan@0: "Filename=%s\n" nathan@0: "Filesize=%lld\n" nathan@0: "Timestamp=%ld\n" nathan@0: "Touch=%ld\n" nathan@0: "Version=%d\n" nathan@0: "Frames=%d\n" nathan@0: "Total=%d\n" nathan@0: "SampleFreq=%d\n" nathan@0: "Channels=%d\n" nathan@0: "Bitrate=%d\n" nathan@0: "MaxBitrate=%d\n" nathan@0: "ChMode=%d\n" nathan@0: "Year=%d\n" nathan@0: "Level=%.4f\n" nathan@0: "Peak=%.4f\n", nathan@0: Filename,Filesize,CTime,touch,CACHE_VERSION,Frames,Total,SampleFreq,Channels,Bitrate,MaxBitrate,ChMode,Year,Level,Peak); nathan@0: if(Title) fprintf(f,"Title=%s\n" ,Title); nathan@0: if(Artist) fprintf(f,"Artist=%s\n" ,Artist); nathan@0: if(Album) fprintf(f,"Album=%s\n" ,Album); nathan@0: if(DecoderID) fprintf(f,"DecoderID=%s\n",cDecoders::ID2Str(DecoderID)); nathan@0: if(FsID) fprintf(f,"FsID=%s\n" ,FsID); nathan@0: fprintf(f,"##END\n"); nathan@0: return ferror(f)==0; nathan@0: } nathan@0: nathan@0: bool cCacheData::Load(FILE *f) nathan@0: { nathan@0: char buf[1024], *delimiters="=\n"; nathan@0: nathan@0: cFileInfo::Clear(); nathan@0: cSongInfo::Clear(); nathan@0: while(fgets(buf,sizeof(buf),f)) { nathan@0: char *ptrptr; nathan@0: char *name =strtok_r(buf ,delimiters,&ptrptr); nathan@0: char *value=strtok_r(0,delimiters,&ptrptr); nathan@0: if(name) { nathan@0: if(!strcasecmp(name,"##END")) break; nathan@0: if(value) { nathan@0: if (!strcasecmp(name,"Filename")) Filename =strdup(value); nathan@0: else if(!strcasecmp(name,"Filesize") || nathan@0: !strcasecmp(name,"Size")) Filesize =atoll(value); nathan@0: else if(!strcasecmp(name,"FsID")) FsID =strdup(value); nathan@0: else if(!strcasecmp(name,"Timestamp")) CTime =atol(value); nathan@0: else if(!strcasecmp(name,"Touch")) touch =atol(value); nathan@0: else if(!strcasecmp(name,"Version")) version =atoi(value); nathan@0: else if(!strcasecmp(name,"DecoderID")) DecoderID =cDecoders::Str2ID(value); nathan@0: else if(!strcasecmp(name,"Frames")) Frames =atoi(value); nathan@0: else if(!strcasecmp(name,"Total")) Total =atoi(value); nathan@0: else if(!strcasecmp(name,"SampleFreq")) SampleFreq=atoi(value); nathan@0: else if(!strcasecmp(name,"Channels")) Channels =atoi(value); nathan@0: else if(!strcasecmp(name,"Bitrate")) Bitrate =atoi(value); nathan@0: else if(!strcasecmp(name,"MaxBitrate")) MaxBitrate=atoi(value); nathan@0: else if(!strcasecmp(name,"ChMode")) ChMode =atoi(value); nathan@0: else if(!strcasecmp(name,"Year")) Year =atoi(value); nathan@0: else if(!strcasecmp(name,"Title")) Title =strdup(value); nathan@0: else if(!strcasecmp(name,"Artist")) Artist =strdup(value); nathan@0: else if(!strcasecmp(name,"Album")) Album =strdup(value); nathan@0: else if(!strcasecmp(name,"Level")) Level =atof(value); nathan@0: else if(!strcasecmp(name,"Peak")) Peak =atof(value); nathan@0: else d(printf("cache: ignoring bad token '%s' from cache file\n",name)) nathan@0: } nathan@0: } nathan@0: } nathan@0: nathan@0: if(ferror(f) || !Filename) return false; nathan@0: hash=MakeHash(Filename); nathan@0: return true; nathan@0: } nathan@0: nathan@0: // --- cInfoCache ---------------------------------------------------- nathan@0: nathan@0: cInfoCache::cInfoCache(void) nathan@0: { nathan@0: lasttime=0; modified=false; nathan@0: lastpurge=time(0)-(50*60); nathan@0: } nathan@0: nathan@2: void cInfoCache::Shutdown(void) nathan@2: { nathan@2: Cancel(10); nathan@2: Save(true); nathan@2: } nathan@2: nathan@0: void cInfoCache::Cache(cSongInfo *info, cFileInfo *file) nathan@0: { nathan@0: lock.Lock(); nathan@0: cCacheData *dat=Search(file); nathan@0: if(dat) { nathan@0: dat->Create(file,info); nathan@0: Modified(); nathan@0: dat->Unlock(); nathan@0: d(printf("cache: updating infos for %s\n",file->Filename)) nathan@0: } nathan@0: else { nathan@0: dat=new cCacheData; nathan@0: dat->Create(file,info); nathan@0: AddEntry(dat); nathan@0: d(printf("cache: caching infos for %s\n",file->Filename)) nathan@0: } nathan@0: lock.Unlock(); nathan@0: } nathan@0: nathan@0: cCacheData *cInfoCache::Search(cFileInfo *file) nathan@0: { nathan@0: int hash=MakeHash(file->Filename); nathan@0: lock.Lock(); nathan@0: cCacheData *dat=FirstEntry(hash); nathan@0: while(dat) { nathan@0: if(dat->hash==hash && !strcmp(dat->Filename,file->Filename) && dat->Filesize==file->Filesize) { nathan@0: dat->Lock(); nathan@0: if(file->FsID && dat->FsID && !strcmp(dat->FsID,"old")) { // duplicate FsID for old entries nathan@0: dat->FsID=strdup(file->FsID); nathan@0: dat->Touch(); Modified(); nathan@0: //d(printf("adding FsID for %s\n",dat->Filename)) nathan@0: } nathan@0: nathan@0: if((!file->FsID && !dat->FsID) || (file->FsID && dat->FsID && !strcmp(dat->FsID,file->FsID))) { nathan@0: //d(printf("cache: found cache entry for %s\n",dat->Filename)) nathan@0: dat->Touch(); Modified(); nathan@0: if(dat->CTime!=file->CTime) { nathan@0: d(printf("cache: ctime differs, removing from cache: %s\n",dat->Filename)) nathan@0: DelEntry(dat); dat=0; nathan@0: } nathan@0: break; nathan@0: } nathan@0: dat->Unlock(); nathan@0: } nathan@0: dat=(cCacheData *)dat->Next(); nathan@0: } nathan@0: lock.Unlock(); nathan@0: return dat; nathan@0: } nathan@0: nathan@0: void cInfoCache::AddEntry(cCacheData *dat) nathan@0: { nathan@0: lists[dat->hash%CACHELINES].Add(dat); nathan@0: Modified(); nathan@0: } nathan@0: nathan@0: void cInfoCache::DelEntry(cCacheData *dat) nathan@0: { nathan@0: dat->Lock(); nathan@0: lists[dat->hash%CACHELINES].Del(dat); nathan@0: Modified(); nathan@0: } nathan@0: nathan@0: cCacheData *cInfoCache::FirstEntry(int hash) nathan@0: { nathan@0: return lists[hash%CACHELINES].First(); nathan@0: } nathan@0: nathan@0: bool cInfoCache::Purge(void) nathan@0: { nathan@0: time_t now=time(0); nathan@0: if(now-lastpurge>(60*60)) { nathan@0: isyslog("cleaning up id3 cache"); nathan@0: Start(); nathan@0: lastpurge=now; nathan@0: } nathan@0: return Active(); nathan@0: } nathan@0: nathan@0: void cInfoCache::Action(void) nathan@0: { nathan@0: d(printf("cache: id3 cache purge thread started (pid=%d)\n",getpid())) nathan@0: nice(3); nathan@0: lock.Lock(); nathan@0: for(int i=0,n=0 ; iNext(); nathan@0: if(dat->Purge()) DelEntry(dat); nathan@0: dat=ndat; nathan@0: nathan@0: if(++n>30) { nathan@0: lastmod=false; n=0; nathan@0: lock.Unlock(); lock.Lock(); // give concurrent thread an access chance nathan@0: if(lastmod) dat=FirstEntry(i); // restart line, if cache changed meanwhile nathan@0: } nathan@0: } nathan@0: } nathan@0: lock.Unlock(); nathan@0: d(printf("cache: id3 cache purge thread ended (pid=%d)\n",getpid())) nathan@0: } nathan@0: nathan@0: char *cInfoCache::CacheFile(void) nathan@0: { nathan@0: return AddPath(cachedir?cachedir:VideoDirectory,CACHEFILENAME); nathan@0: } nathan@0: nathan@0: void cInfoCache::Save(bool force) nathan@0: { nathan@2: if(modified && (force || (!Purge() && time(0)>lasttime))) { nathan@0: char *name=CacheFile(); nathan@0: cSafeFile f(name); nathan@0: free(name); nathan@0: if(f.Open()) { nathan@0: lock.Lock(); nathan@0: fprintf(f,"## This is a generated file. DO NOT EDIT!!\n" nathan@0: "## This file will be OVERWRITTEN WITHOUT WARNING!!\n"); nathan@0: for(int i=0 ; iSave(f)) { i=CACHELINES+1; break; } nathan@0: dat=(cCacheData *)dat->Next(); nathan@0: } nathan@0: } nathan@0: lock.Unlock(); nathan@0: f.Close(); nathan@0: modified=false; lasttime=time(0)+CACHESAVETIMEOUT; nathan@0: d(printf("cache: saved cache to file\n")) nathan@0: } nathan@0: } nathan@0: } nathan@0: nathan@0: void cInfoCache::Load(void) nathan@0: { nathan@0: char *name=CacheFile(); nathan@0: if(access(name,F_OK)==0) { nathan@0: isyslog("loading id3 cache from %s",name); nathan@0: FILE *f=fopen(name,"r"); nathan@0: if(f) { nathan@0: char buf[256]; nathan@0: bool mod=false; nathan@0: lock.Lock(); nathan@0: for(int i=0 ; iLoad(f)) { nathan@0: if(dat->version!=CACHE_VERSION) { nathan@0: if(dat->Upgrade()) mod=true; nathan@0: else { delete dat; continue; } nathan@0: } nathan@0: AddEntry(dat); nathan@0: } nathan@0: else { nathan@0: delete dat; nathan@0: if(ferror(f)) { nathan@0: esyslog("ERROR: failed to load id3 cache"); nathan@0: break; nathan@0: } nathan@0: } nathan@0: } nathan@0: } nathan@0: lock.Unlock(); nathan@0: fclose(f); nathan@0: modified=false; if(mod) Modified(); nathan@0: } nathan@0: else LOG_ERROR_STR(name); nathan@0: } nathan@0: free(name); nathan@0: }