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