decoder.c
author nathan
Sun, 06 Dec 2009 08:48:57 +0800
branchtrunk
changeset 34 afc13760179b
parent 33 65ed49cbc08b
permissions -rw-r--r--
fixed gcc 4.4.1 const errors
     1 /*
     2  * MP3/MPlayer plugin to VDR (C++)
     3  *
     4  * (C) 2001-2009 Stefan Huelswitt <s.huelswitt@gmx.de>
     5  *
     6  * This code is free software; you can redistribute it and/or
     7  * modify it under the terms of the GNU General Public License
     8  * as published by the Free Software Foundation; either version 2
     9  * of the License, or (at your option) any later version.
    10  *
    11  * This code is distributed in the hope that it will be useful,
    12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  * GNU General Public License for more details.
    15  *
    16  * You should have received a copy of the GNU General Public License
    17  * along with this program; if not, write to the Free Software
    18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
    19  * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
    20  */
    21 
    22 #include <stdlib.h>
    23 #include <stdio.h>
    24 #include <errno.h>
    25 #include <sys/stat.h>
    26 #include <sys/vfs.h>
    27 
    28 #include <vdr/videodir.h>
    29 
    30 #include "common.h"
    31 #include "data-mp3.h"
    32 #include "data-src.h"
    33 #include "decoder.h"
    34 #include "decoder-core.h"
    35 #include "decoder-mp3.h"
    36 #include "decoder-mp3-stream.h"
    37 #include "decoder-snd.h"
    38 #include "decoder-ogg.h"
    39 #include "decoder-ogg-stream.h"
    40 
    41 #define CACHEFILENAME     "id3info.cache"
    42 #define CACHESAVETIMEOUT  120 // secs
    43 #define CACHEPURGETIMEOUT 120 // days
    44 
    45 extern cFileSources MP3Sources;
    46 
    47 cInfoCache InfoCache;
    48 char *cachedir=0;
    49 
    50 int MakeHashBuff(const char *buff, int len)
    51 {
    52   int h=len;
    53   while(len--) h=(h*13 + *buff++) & 0x7ff;
    54   return h;
    55 }
    56 
    57 // --- cStrConv ----------------------------------------------------------------
    58 
    59 class cStrConv : private cMutex {
    60 private:
    61   cCharSetConv toSys;
    62 public:
    63   cStrConv(void):toSys("UTF-8",cCharSetConv::SystemCharacterTable()) {}
    64   char *ToSys(char *from);
    65   };
    66 
    67 static cStrConv *strconv;
    68   
    69 char *cStrConv::ToSys(char *from)
    70 {
    71   if(from) {
    72     Lock();
    73     const char *r=toSys.Convert(from);
    74     Unlock();
    75     if(r!=from) {
    76       char *n=strdup(r);
    77       if(n) {
    78         free(from);
    79         return n;
    80         }
    81       }
    82     }
    83   return from;
    84 }
    85 
    86 // --- cSongInfo ---------------------------------------------------------------
    87 
    88 cSongInfo::cSongInfo(void)
    89 {
    90   Title=Artist=Album=0;
    91   Clear();
    92 }
    93 
    94 cSongInfo::~cSongInfo()
    95 {
    96   Clear();
    97 }
    98 
    99 void cSongInfo::Clear(void)
   100 {
   101   Frames=0; Total=-1; DecoderID=0;
   102   SampleFreq=Channels=Bitrate=MaxBitrate=ChMode=-1;
   103   free(Title); Title=0;
   104   free(Artist); Artist=0;
   105   free(Album); Album=0;
   106   Year=-1;
   107   Level=Peak=0.0;
   108   infoDone=false; utf8clean=true;
   109 }
   110 
   111 void cSongInfo::Set(cSongInfo *si, bool update)
   112 {
   113   if(!update || si->Utf8Clean()) {
   114     Clear();
   115     Title=si->Title ? strdup(si->Title):0;
   116     Artist=si->Artist ? strdup(si->Artist):0;
   117     Album=si->Album ? strdup(si->Album):0;
   118     utf8clean=si->utf8clean;
   119     }
   120   Frames=si->Frames;
   121   Total=si->Total;
   122   SampleFreq=si->SampleFreq;
   123   Channels=si->Channels;
   124   Bitrate=si->Bitrate;
   125   MaxBitrate=si->MaxBitrate;
   126   ChMode=si->ChMode;
   127   Year=si->Year;
   128   if(si->Level>0.0) { // preserve old level
   129     Level=si->Level;
   130     Peak=si->Peak;
   131     }
   132   DecoderID=si->DecoderID;
   133   InfoDone();
   134 }
   135 
   136 void cSongInfo::FakeTitle(const char *filename, const char *extention)
   137 {
   138   // if no title, try to build a reasonable from the filename
   139   if(!Title && filename)  {
   140     const char *s=rindex(filename,'/');
   141     if(s && *s=='/') {
   142       s++;
   143       Title=strdup(s);
   144       strreplace(Title,'_',' ');
   145       if(extention) {                            // strip given extention
   146         int l=strlen(Title)-strlen(extention);
   147         if(l>0 && !strcasecmp(Title+l,extention)) Title[l]=0;
   148         }
   149       else {                                     // strip any extention
   150         char *e=rindex(Title,'.');
   151         if(e && *e=='.' && strlen(e)<=5) *e=0;
   152         }
   153       d(printf("mp3: faking title '%s' from filename '%s'\n",Title,filename))
   154       }
   155     }
   156 }
   157 
   158 void cSongInfo::ConvertToSys(void)
   159 {
   160   if(cCharSetConv::SystemCharacterTable()) {
   161     Title=strconv->ToSys(Title);
   162     Artist=strconv->ToSys(Artist);
   163     Album=strconv->ToSys(Album);
   164     utf8clean=false;
   165     }
   166 }
   167 
   168 // --- cFileInfo ---------------------------------------------------------------
   169 
   170 cFileInfo::cFileInfo(void)
   171 {
   172   Filename=FsID=0; Clear();
   173 }
   174 
   175 cFileInfo::cFileInfo(const char *Name)
   176 {
   177   Filename=FsID=0; Clear();
   178   Filename=strdup(Name);
   179 }
   180 
   181 cFileInfo::~cFileInfo()
   182 {
   183   Clear();
   184 }
   185 
   186 void cFileInfo::Clear(void)
   187 {
   188   free(Filename); Filename=0;
   189   free(FsID); FsID=0;
   190   Filesize=0; CTime=0; FsType=0; removable=-1;
   191   infoDone=false;
   192 }
   193 
   194 bool cFileInfo::Removable(void)
   195 {
   196   if(removable<0 && Filename) {
   197     cFileSource *src=MP3Sources.FindSource(Filename);
   198     if(src) removable=src->NeedsMount();
   199     else removable=1;
   200     }
   201   return (removable!=0);
   202 }
   203 
   204 void cFileInfo::Set(cFileInfo *fi)
   205 {
   206   Clear(); InfoDone();
   207   Filename=fi->Filename ? strdup(fi->Filename):0;
   208   FsID=fi->FsID ? strdup(fi->FsID):0;
   209   Filesize=fi->Filesize;
   210   CTime=fi->CTime;
   211 }
   212 
   213 
   214 bool cFileInfo::FileInfo(bool log)
   215 {
   216   if(Filename) {
   217     struct stat64 ds;
   218     if(!stat64(Filename,&ds)) {
   219       if(S_ISREG(ds.st_mode)) {
   220         free(FsID); FsID=0;
   221         FsType=0;
   222         struct statfs64 sfs;
   223         if(!statfs64(Filename,&sfs)) {
   224           if(Removable()) FsID=aprintf("%llx:%llx",sfs.f_blocks,sfs.f_files);
   225           FsType=sfs.f_type;
   226           }
   227         else if(errno!=ENOSYS && log) { esyslog("ERROR: can't statfs %s: %s",Filename,strerror(errno)); }
   228         Filesize=ds.st_size;
   229         CTime=ds.st_ctime;
   230 #ifdef CDFS_MAGIC
   231         if(FsType==CDFS_MAGIC) CTime=0; // CDFS returns mount time as ctime
   232 #endif
   233         InfoDone();
   234         return true;
   235         }
   236       else if(log) { esyslog("ERROR: %s is not a regular file",Filename); }
   237       }
   238     else if(log) { esyslog("ERROR: can't stat %s: %s",Filename,strerror(errno)); }
   239     }
   240   return false;
   241 }
   242 
   243 // --- cDecoders ---------------------------------------------------------------
   244 
   245 cDecoder *cDecoders::FindDecoder(cFileObj *Obj)
   246 {
   247   const char *full=Obj->FullPath();
   248   cFileInfo fi(full);
   249   cCacheData *dat;
   250   cDecoder *decoder=0;
   251   if(fi.FileInfo(false) && (dat=InfoCache.Search(&fi))) {
   252     if(dat->DecoderID) {
   253       //d(printf("mp3: found DecoderID '%s' for %s from cache\n",cDecoders::ID2Str(dat->DecoderID),Filename))
   254       switch(dat->DecoderID) {
   255         case DEC_MP3:  decoder=new cMP3Decoder(full); break;
   256         case DEC_MP3S: decoder=new cMP3StreamDecoder(full); break;
   257 #ifdef HAVE_SNDFILE
   258         case DEC_SND:  decoder=new cSndDecoder(full); break;
   259 #endif
   260 #ifdef HAVE_VORBISFILE
   261         case DEC_OGG:  decoder=new cOggDecoder(full); break;
   262         case DEC_OGGS: decoder=new cOggStreamDecoder(full); break;
   263 #endif
   264         default:       esyslog("ERROR: bad DecoderID '%s' from info cache: %s",cDecoders::ID2Str(dat->DecoderID),full); break;
   265         }
   266       }
   267     dat->Unlock();
   268     }
   269 
   270   if(!decoder || !decoder->Valid()) {
   271     // no decoder in cache or cached decoder doesn't matches.
   272     // try to detect a decoder
   273 
   274     delete decoder; decoder=0;
   275 #ifdef HAVE_SNDFILE
   276     if(!decoder) {
   277       decoder=new cSndDecoder(full);
   278       if(!decoder || !decoder->Valid()) { delete decoder; decoder=0; }
   279       }
   280 #endif
   281 #ifdef HAVE_VORBISFILE
   282     if(!decoder) {
   283       decoder=new cOggDecoder(full);
   284       if(!decoder || !decoder->Valid()) { delete decoder; decoder=0; }
   285       }
   286     if(!decoder) {
   287       decoder=new cOggStreamDecoder(full);
   288       if(!decoder || !decoder->Valid()) { delete decoder; decoder=0; }
   289       }
   290 #endif
   291     if(!decoder) {
   292       decoder=new cMP3StreamDecoder(full);
   293       if(!decoder || !decoder->Valid()) { delete decoder; decoder=0; }
   294       }
   295     if(!decoder) {
   296       decoder=new cMP3Decoder(full);
   297       if(!decoder || !decoder->Valid()) { delete decoder; decoder=0; }
   298       }
   299     if(!decoder) esyslog("ERROR: no decoder found for %s",Obj->Name());
   300     }
   301   return decoder;
   302 }
   303 
   304 const char *cDecoders::ID2Str(int id)
   305 {
   306   switch(id) {
   307     case DEC_MP3:  return DEC_MP3_STR;
   308     case DEC_MP3S: return DEC_MP3S_STR;
   309     case DEC_SND:  return DEC_SND_STR;
   310     case DEC_OGG:  return DEC_OGG_STR;
   311     case DEC_OGGS: return DEC_OGGS_STR;
   312     }
   313   return 0;
   314 }
   315 
   316 int cDecoders::Str2ID(const char *str)
   317 {
   318   if     (!strcmp(str,DEC_MP3_STR )) return DEC_MP3;
   319   else if(!strcmp(str,DEC_MP3S_STR)) return DEC_MP3S;
   320   else if(!strcmp(str,DEC_SND_STR )) return DEC_SND;
   321   else if(!strcmp(str,DEC_OGG_STR )) return DEC_OGG;
   322   else if(!strcmp(str,DEC_OGGS_STR)) return DEC_OGGS;
   323   return 0;
   324 }
   325 
   326 // --- cDecoder ----------------------------------------------------------------
   327 
   328 cDecoder::cDecoder(const char *Filename)
   329 {
   330   filename=strdup(Filename);
   331   locked=0; urgentLock=playing=false;
   332 }
   333 
   334 cDecoder::~cDecoder()
   335 {
   336   free(filename);
   337 }
   338 
   339 void cDecoder::Lock(bool urgent)
   340 {
   341   locklock.Lock();
   342   if(urgent && locked) urgentLock=true; // signal other locks to release quickly
   343   locked++;
   344   locklock.Unlock(); // don't hold the "locklock" when locking "lock", may cause a deadlock
   345   lock.Lock();
   346   urgentLock=false;
   347 }
   348 
   349 void cDecoder::Unlock(void)
   350 {
   351   locklock.Lock();
   352   locked--;
   353   lock.Unlock();
   354   locklock.Unlock();
   355 }
   356 
   357 bool cDecoder::TryLock(void)
   358 {
   359   bool res=false;
   360   locklock.Lock();
   361   if(!locked && !playing) {
   362     Lock();
   363     res=true;
   364     }
   365   locklock.Unlock();
   366   return res;
   367 }
   368 
   369 // --- cCacheData -----------------------------------------------------
   370 
   371 cCacheData::cCacheData(void)
   372 {
   373   touch=0; version=0;
   374 }
   375 
   376 void cCacheData::Touch(void)
   377 {
   378   touch=time(0);
   379 }
   380 
   381 #define SECS_PER_DAY (24*60*60)
   382 
   383 bool cCacheData::Purge(void)
   384 {
   385   time_t now=time(0);
   386   //XXX does this realy made sense?
   387   //if(touch+CACHEPURGETIMEOUT*SECS_PER_DAY < now) {
   388   //  d(printf("cache: purged: timeout %s\n",Filename))
   389   //  return true;
   390   //  }
   391   if(touch+CACHEPURGETIMEOUT*SECS_PER_DAY/10 < now) {
   392     if(!Removable()) {                            // is this a permant source?
   393       struct stat64 ds;                           // does the file exists? if not, purge
   394       if(stat64(Filename,&ds) || !S_ISREG(ds.st_mode) || access(Filename,R_OK)) {
   395         d(printf("cache: purged: file not found %s\n",Filename))
   396         return true;
   397         }
   398       }
   399     }
   400   return false;
   401 }
   402 
   403 bool cCacheData::Check8bit(const char *str)
   404 {
   405   if(str) while(*str) if(*str++ & 0x80) return true;
   406   return false;
   407 }
   408 
   409 bool cCacheData::Upgrade(void)
   410 {
   411   if(version<8) {
   412     if(Check8bit(Title) || Check8bit(Artist) || Check8bit(Album))
   413       return false;              // Trash entries not 7bit clean
   414     }
   415   if(version<7) {
   416     if(DecoderID==DEC_SND || (Title && startswith(Title,"track-")))
   417       return false;              // Trash older SND entries (incomplete)
   418 
   419     if(Removable()) {
   420       if(!FsID) FsID=strdup("old"); // Dummy entry, will be replaced in InfoCache::Search()
   421       }
   422     else { free(FsID); FsID=0; }
   423     }
   424   if(version<4) {
   425     Touch();                     // Touch entry
   426     }
   427   if(version<3 && !Title) {
   428     FakeTitle(Filename,".mp3");  // Fake title
   429     }
   430   if(version<2 && Bitrate<=0) {
   431     return false;                // Trash entry without bitrate
   432     }
   433   return true;
   434 }
   435 
   436 void cCacheData::Create(cFileInfo *fi, cSongInfo *si, bool update)
   437 {
   438   cFileInfo::Set(fi);
   439   cSongInfo::Set(si,update);
   440   hash=MakeHash(Filename);
   441   Touch();
   442 }
   443 
   444 bool cCacheData::Save(FILE *f)
   445 {
   446   fprintf(f,"##BEGIN\n"
   447             "Filename=%s\n"
   448             "Filesize=%lld\n"
   449             "Timestamp=%ld\n"
   450             "Touch=%ld\n"
   451             "Version=%d\n"
   452             "Frames=%d\n"
   453             "Total=%d\n"
   454             "SampleFreq=%d\n"
   455             "Channels=%d\n"
   456             "Bitrate=%d\n"
   457             "MaxBitrate=%d\n"
   458             "ChMode=%d\n"
   459             "Year=%d\n"
   460             "Level=%.4f\n"
   461             "Peak=%.4f\n",
   462             Filename,Filesize,CTime,touch,CACHE_VERSION,Frames,Total,SampleFreq,Channels,Bitrate,MaxBitrate,ChMode,Year,Level,Peak);
   463   if(Title)     fprintf(f,"Title=%s\n"    ,Title);
   464   if(Artist)    fprintf(f,"Artist=%s\n"   ,Artist);
   465   if(Album)     fprintf(f,"Album=%s\n"    ,Album);
   466   if(DecoderID) fprintf(f,"DecoderID=%s\n",cDecoders::ID2Str(DecoderID));
   467   if(FsID)      fprintf(f,"FsID=%s\n"     ,FsID);
   468   fprintf(f,"##END\n");
   469   return ferror(f)==0;
   470 }
   471 
   472 bool cCacheData::Load(FILE *f)
   473 {
   474   static const char delimiters[] = { "=\n" };
   475   char buf[1024];
   476 
   477   cFileInfo::Clear();
   478   cSongInfo::Clear();
   479   while(fgets(buf,sizeof(buf),f)) {
   480     char *ptrptr;
   481     char *name =strtok_r(buf ,delimiters,&ptrptr);
   482     char *value=strtok_r(0,delimiters,&ptrptr);
   483     if(name) {
   484       if(!strcasecmp(name,"##END")) break;
   485       if(value) {
   486         if     (!strcasecmp(name,"Filename"))   Filename  =strdup(value);
   487         else if(!strcasecmp(name,"Filesize") ||
   488                 !strcasecmp(name,"Size"))       Filesize  =atoll(value);
   489         else if(!strcasecmp(name,"FsID"))       FsID      =strdup(value);
   490         else if(!strcasecmp(name,"Timestamp"))  CTime     =atol(value);
   491         else if(!strcasecmp(name,"Touch"))      touch     =atol(value);
   492         else if(!strcasecmp(name,"Version"))    version   =atoi(value);
   493         else if(!strcasecmp(name,"DecoderID"))  DecoderID =cDecoders::Str2ID(value);
   494         else if(!strcasecmp(name,"Frames"))     Frames    =atoi(value);
   495         else if(!strcasecmp(name,"Total"))      Total     =atoi(value);
   496         else if(!strcasecmp(name,"SampleFreq")) SampleFreq=atoi(value);
   497         else if(!strcasecmp(name,"Channels"))   Channels  =atoi(value);
   498         else if(!strcasecmp(name,"Bitrate"))    Bitrate   =atoi(value);
   499         else if(!strcasecmp(name,"MaxBitrate")) MaxBitrate=atoi(value);
   500         else if(!strcasecmp(name,"ChMode"))     ChMode    =atoi(value);
   501         else if(!strcasecmp(name,"Year"))       Year      =atoi(value);
   502         else if(!strcasecmp(name,"Title"))      Title     =strdup(value);
   503         else if(!strcasecmp(name,"Artist"))     Artist    =strdup(value);
   504         else if(!strcasecmp(name,"Album"))      Album     =strdup(value);
   505         else if(!strcasecmp(name,"Level"))      Level     =atof(value);
   506         else if(!strcasecmp(name,"Peak"))       Peak      =atof(value);
   507         else d(printf("cache: ignoring bad token '%s' from cache file\n",name))
   508         }
   509       }
   510     }
   511 
   512   if(ferror(f) || !Filename) return false;
   513   hash=MakeHash(Filename);
   514   return true;
   515 }
   516 
   517 // --- cInfoCache ----------------------------------------------------
   518 
   519 cInfoCache::cInfoCache(void)
   520 {
   521   lasttime=0; modified=false;
   522   lastpurge=time(0)-(50*60);
   523 }
   524 
   525 void cInfoCache::Shutdown(void)
   526 {
   527   Cancel(10);
   528   Save(true);
   529 }
   530 
   531 void cInfoCache::Cache(cSongInfo *info, cFileInfo *file)
   532 {
   533   lock.Lock();
   534   cCacheData *dat=Search(file);
   535   if(dat) {
   536     dat->Create(file,info,true);
   537     Modified();
   538     dat->Unlock();
   539     d(printf("cache: updating infos for %s\n",file->Filename))
   540     }
   541   else {
   542     dat=new cCacheData;
   543     dat->Create(file,info,false);
   544     AddEntry(dat);
   545     d(printf("cache: caching infos for %s\n",file->Filename))
   546     }
   547   lock.Unlock();
   548 }
   549 
   550 cCacheData *cInfoCache::Search(cFileInfo *file)
   551 {
   552   int hash=MakeHash(file->Filename);
   553   lock.Lock();
   554   cCacheData *dat=FirstEntry(hash);
   555   while(dat) {
   556     if(dat->hash==hash && !strcmp(dat->Filename,file->Filename) && dat->Filesize==file->Filesize) {
   557       dat->Lock();
   558       if(file->FsID && dat->FsID && !strcmp(dat->FsID,"old")) { // duplicate FsID for old entries
   559         dat->FsID=strdup(file->FsID);
   560         dat->Touch(); Modified();
   561         //d(printf("adding FsID for %s\n",dat->Filename))
   562         }
   563 
   564       if((!file->FsID && !dat->FsID) || (file->FsID && dat->FsID && !strcmp(dat->FsID,file->FsID))) {
   565         //d(printf("cache: found cache entry for %s\n",dat->Filename))
   566         dat->Touch(); Modified();
   567         if(dat->CTime!=file->CTime) {
   568           d(printf("cache: ctime differs, removing from cache: %s\n",dat->Filename))
   569           DelEntry(dat); dat=0;
   570           }
   571         break;
   572         }
   573       dat->Unlock();
   574       }
   575     dat=(cCacheData *)dat->Next();
   576     }
   577   lock.Unlock();
   578   return dat;
   579 }
   580 
   581 void cInfoCache::AddEntry(cCacheData *dat)
   582 {
   583   lists[dat->hash%CACHELINES].Add(dat);
   584   Modified();
   585 }
   586 
   587 void cInfoCache::DelEntry(cCacheData *dat)
   588 {
   589   dat->Lock();
   590   lists[dat->hash%CACHELINES].Del(dat);
   591   Modified();
   592 }
   593 
   594 cCacheData *cInfoCache::FirstEntry(int hash)
   595 {
   596   return lists[hash%CACHELINES].First();
   597 }
   598 
   599 bool cInfoCache::Purge(void)
   600 {
   601   time_t now=time(0);
   602   if(now-lastpurge>(60*60)) {
   603     isyslog("cleaning up id3 cache");
   604     Start();
   605     lastpurge=now;
   606     }
   607   return Active();
   608 }
   609 
   610 void cInfoCache::Action(void)
   611 {
   612   d(printf("cache: id3 cache purge thread started (pid=%d)\n",getpid()))
   613   if(nice(3)<0);
   614   lock.Lock();
   615   for(int i=0,n=0 ; i<CACHELINES && Running(); i++) {
   616     cCacheData *dat=FirstEntry(i);
   617     while(dat && Running()) {
   618       cCacheData *ndat=(cCacheData *)dat->Next();
   619       if(dat->Purge()) DelEntry(dat);
   620       dat=ndat;
   621 
   622       if(++n>30) {
   623         lastmod=false; n=0;
   624         lock.Unlock(); lock.Lock();    // give concurrent thread an access chance
   625         if(lastmod) dat=FirstEntry(i); // restart line, if cache changed meanwhile
   626         }
   627       }
   628     }
   629   lock.Unlock();
   630   d(printf("cache: id3 cache purge thread ended (pid=%d)\n",getpid()))
   631 }
   632 
   633 char *cInfoCache::CacheFile(void)
   634 {
   635   return AddPath(cachedir?cachedir:VideoDirectory,CACHEFILENAME);
   636 }
   637 
   638 void cInfoCache::Save(bool force)
   639 {
   640   if(modified && (force || (!Purge() && time(0)>lasttime))) {
   641     char *name=CacheFile();
   642     cSafeFile f(name);
   643     free(name);
   644     if(f.Open()) {
   645       lock.Lock();
   646       fprintf(f,"## This is a generated file. DO NOT EDIT!!\n"
   647                 "## This file will be OVERWRITTEN WITHOUT WARNING!!\n");
   648       for(int i=0 ; i<CACHELINES ; i++) {
   649         cCacheData *dat=FirstEntry(i);
   650         while(dat) {
   651           if(!dat->Save(f)) { i=CACHELINES+1; break; }
   652           dat=(cCacheData *)dat->Next();
   653           }
   654         }
   655       lock.Unlock();
   656       f.Close();
   657       modified=false; lasttime=time(0)+CACHESAVETIMEOUT;
   658       d(printf("cache: saved cache to file\n"))
   659       }
   660     }
   661 }
   662 
   663 void cInfoCache::Load(void)
   664 {
   665   if(!strconv) strconv=new cStrConv;
   666 
   667   char *name=CacheFile();
   668   if(access(name,F_OK)==0) {
   669     isyslog("loading id3 cache from %s",name);
   670     FILE *f=fopen(name,"r");
   671     if(f) {
   672       char buf[256];
   673       bool mod=false;
   674       lock.Lock();
   675       for(int i=0 ; i<CACHELINES ; i++) lists[i].Clear();
   676       while(fgets(buf,sizeof(buf),f)) {
   677         if(!strcasecmp(buf,"##BEGIN\n")) {
   678           cCacheData *dat=new cCacheData;
   679           if(dat->Load(f)) {
   680             if(dat->version!=CACHE_VERSION) {
   681               if(dat->Upgrade()) mod=true;
   682               else { delete dat; continue; }
   683               }
   684             AddEntry(dat);
   685             }
   686           else {
   687             delete dat;
   688             if(ferror(f)) {
   689               esyslog("ERROR: failed to load id3 cache");
   690               break;
   691               }
   692             }
   693           }
   694         }
   695       lock.Unlock();
   696       fclose(f);
   697       modified=false; if(mod) Modified();
   698       }
   699     else LOG_ERROR_STR(name);
   700     }
   701   free(name);
   702 }