decoder.c
branchtrunk
changeset 0 474a1293c3c0
child 2 4c1f7b705009
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/decoder.c	Sat Dec 29 14:47:40 2007 +0100
     1.3 @@ -0,0 +1,632 @@
     1.4 +/*
     1.5 + * MP3/MPlayer plugin to VDR (C++)
     1.6 + *
     1.7 + * (C) 2001-2005 Stefan Huelswitt <s.huelswitt@gmx.de>
     1.8 + *
     1.9 + * This code is free software; you can redistribute it and/or
    1.10 + * modify it under the terms of the GNU General Public License
    1.11 + * as published by the Free Software Foundation; either version 2
    1.12 + * of the License, or (at your option) any later version.
    1.13 + *
    1.14 + * This code is distributed in the hope that it will be useful,
    1.15 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    1.16 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    1.17 + * GNU General Public License for more details.
    1.18 + *
    1.19 + * You should have received a copy of the GNU General Public License
    1.20 + * along with this program; if not, write to the Free Software
    1.21 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
    1.22 + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
    1.23 + */
    1.24 +
    1.25 +#include <stdlib.h>
    1.26 +#include <stdio.h>
    1.27 +#include <errno.h>
    1.28 +#include <sys/stat.h>
    1.29 +#include <sys/vfs.h>
    1.30 +
    1.31 +#include <vdr/videodir.h>
    1.32 +
    1.33 +#include "common.h"
    1.34 +#include "data-mp3.h"
    1.35 +#include "data-src.h"
    1.36 +#include "decoder.h"
    1.37 +#include "decoder-core.h"
    1.38 +#include "decoder-mp3.h"
    1.39 +#include "decoder-mp3-stream.h"
    1.40 +#include "decoder-snd.h"
    1.41 +#include "decoder-ogg.h"
    1.42 +
    1.43 +#define CACHEFILENAME     "id3info.cache"
    1.44 +#define CACHESAVETIMEOUT  120 // secs
    1.45 +#define CACHEPURGETIMEOUT 120 // days
    1.46 +
    1.47 +extern cFileSources MP3Sources;
    1.48 +
    1.49 +cInfoCache InfoCache;
    1.50 +char *cachedir=0;
    1.51 +
    1.52 +int MakeHashBuff(const char *buff, int len)
    1.53 +{
    1.54 +  int h=len;
    1.55 +  while(len--) h=(h*13 + *buff++) & 0x7ff;
    1.56 +  return h;
    1.57 +}
    1.58 +
    1.59 +// --- cSongInfo ---------------------------------------------------------------
    1.60 +
    1.61 +cSongInfo::cSongInfo(void)
    1.62 +{
    1.63 +  Title=Artist=Album=0;
    1.64 +  Clear();
    1.65 +}
    1.66 +
    1.67 +cSongInfo::~cSongInfo()
    1.68 +{
    1.69 +  Clear();
    1.70 +}
    1.71 +
    1.72 +void cSongInfo::Clear(void)
    1.73 +{
    1.74 +  Frames=0; Total=-1; DecoderID=0;
    1.75 +  SampleFreq=Channels=Bitrate=MaxBitrate=ChMode=-1;
    1.76 +  free(Title); Title=0;
    1.77 +  free(Artist); Artist=0;
    1.78 +  free(Album); Album=0;
    1.79 +  Year=-1;
    1.80 +  Level=Peak=0.0;
    1.81 +  infoDone=false;
    1.82 +}
    1.83 +
    1.84 +void cSongInfo::Set(cSongInfo *si)
    1.85 +{
    1.86 +  Clear(); InfoDone();
    1.87 +  Frames=si->Frames;
    1.88 +  Total=si->Total;
    1.89 +  SampleFreq=si->SampleFreq;
    1.90 +  Channels=si->Channels;
    1.91 +  Bitrate=si->Bitrate;
    1.92 +  MaxBitrate=si->MaxBitrate;
    1.93 +  ChMode=si->ChMode;
    1.94 +  Year=si->Year;
    1.95 +  Title=si->Title ? strdup(si->Title):0;
    1.96 +  Artist=si->Artist ? strdup(si->Artist):0;
    1.97 +  Album=si->Album ? strdup(si->Album):0;
    1.98 +  if(si->Level>0.0) { // preserve old level
    1.99 +    Level=si->Level;
   1.100 +    Peak=si->Peak;
   1.101 +    }
   1.102 +  DecoderID=si->DecoderID;
   1.103 +}
   1.104 +
   1.105 +void cSongInfo::FakeTitle(const char *filename, const char *extention)
   1.106 +{
   1.107 +  // if no title, try to build a reasonable from the filename
   1.108 +  if(!Title && filename)  {
   1.109 +    char *s=rindex(filename,'/');
   1.110 +    if(s && *s=='/') {
   1.111 +      s++;
   1.112 +      Title=strdup(s);
   1.113 +      strreplace(Title,'_',' ');
   1.114 +      if(extention) {                            // strip given extention
   1.115 +        int l=strlen(Title)-strlen(extention);
   1.116 +        if(l>0 && !strcasecmp(Title+l,extention)) Title[l]=0;
   1.117 +        }
   1.118 +      else {                                     // strip any extention
   1.119 +        s=rindex(Title,'.');
   1.120 +        if(s && *s=='.' && strlen(s)<=5) *s=0;
   1.121 +        }
   1.122 +      d(printf("mp3: faking title '%s' from filename '%s'\n",Title,filename))
   1.123 +      }
   1.124 +    }
   1.125 +}
   1.126 +
   1.127 +// --- cFileInfo ---------------------------------------------------------------
   1.128 +
   1.129 +cFileInfo::cFileInfo(void)
   1.130 +{
   1.131 +  Filename=FsID=0; Clear();
   1.132 +}
   1.133 +
   1.134 +cFileInfo::cFileInfo(const char *Name)
   1.135 +{
   1.136 +  Filename=FsID=0; Clear();
   1.137 +  Filename=strdup(Name);
   1.138 +}
   1.139 +
   1.140 +cFileInfo::~cFileInfo()
   1.141 +{
   1.142 +  Clear();
   1.143 +}
   1.144 +
   1.145 +void cFileInfo::Clear(void)
   1.146 +{
   1.147 +  free(Filename); Filename=0;
   1.148 +  free(FsID); FsID=0;
   1.149 +  Filesize=0; CTime=0; FsType=0; removable=-1;
   1.150 +  infoDone=false;
   1.151 +}
   1.152 +
   1.153 +bool cFileInfo::Removable(void)
   1.154 +{
   1.155 +  if(removable<0 && Filename) {
   1.156 +    cFileSource *src=MP3Sources.FindSource(Filename);
   1.157 +    if(src) removable=src->NeedsMount();
   1.158 +    else removable=1;
   1.159 +    }
   1.160 +  return (removable!=0);
   1.161 +}
   1.162 +
   1.163 +void cFileInfo::Set(cFileInfo *fi)
   1.164 +{
   1.165 +  Clear(); InfoDone();
   1.166 +  Filename=fi->Filename ? strdup(fi->Filename):0;
   1.167 +  FsID=fi->FsID ? strdup(fi->FsID):0;
   1.168 +  Filesize=fi->Filesize;
   1.169 +  CTime=fi->CTime;
   1.170 +}
   1.171 +
   1.172 +
   1.173 +bool cFileInfo::FileInfo(bool log)
   1.174 +{
   1.175 +  if(Filename) {
   1.176 +    struct stat64 ds;
   1.177 +    if(!stat64(Filename,&ds)) {
   1.178 +      if(S_ISREG(ds.st_mode)) {
   1.179 +        free(FsID); FsID=0;
   1.180 +        FsType=0;
   1.181 +        struct statfs64 sfs;
   1.182 +        if(!statfs64(Filename,&sfs)) {
   1.183 +          if(Removable()) asprintf(&FsID,"%llx:%llx",sfs.f_blocks,sfs.f_files);
   1.184 +          FsType=sfs.f_type;
   1.185 +          }
   1.186 +        else if(errno!=ENOSYS && log) { esyslog("ERROR: can't statfs %s: %s",Filename,strerror(errno)); }
   1.187 +        Filesize=ds.st_size;
   1.188 +        CTime=ds.st_ctime;
   1.189 +#ifdef CDFS_MAGIC
   1.190 +        if(FsType==CDFS_MAGIC) CTime=0; // CDFS returns mount time as ctime
   1.191 +#endif
   1.192 +        InfoDone();
   1.193 +        return true;
   1.194 +        }
   1.195 +      else if(log) { esyslog("ERROR: %s is not a regular file",Filename); }
   1.196 +      }
   1.197 +    else if(log) { esyslog("ERROR: can't stat %s: %s",Filename,strerror(errno)); }
   1.198 +    }
   1.199 +  return false;
   1.200 +}
   1.201 +
   1.202 +// --- cDecoders ---------------------------------------------------------------
   1.203 +
   1.204 +cDecoder *cDecoders::FindDecoder(cFileObj *Obj)
   1.205 +{
   1.206 +  const char *full=Obj->FullPath();
   1.207 +  cFileInfo fi(full);
   1.208 +  cCacheData *dat;
   1.209 +  cDecoder *decoder=0;
   1.210 +  if(fi.FileInfo(false) && (dat=InfoCache.Search(&fi))) {
   1.211 +    if(dat->DecoderID) {
   1.212 +      //d(printf("mp3: found DecoderID '%s' for %s from cache\n",cDecoders::ID2Str(dat->DecoderID),Filename))
   1.213 +      switch(dat->DecoderID) {
   1.214 +        case DEC_MP3:  decoder=new cMP3Decoder(full); break;
   1.215 +        case DEC_MP3S: decoder=new cMP3StreamDecoder(full); break;
   1.216 +#ifdef HAVE_SNDFILE
   1.217 +        case DEC_SND:  decoder=new cSndDecoder(full); break;
   1.218 +#endif
   1.219 +#ifdef HAVE_VORBISFILE
   1.220 +        case DEC_OGG:  decoder=new cOggDecoder(full); break;
   1.221 +#endif
   1.222 +        default:       esyslog("ERROR: bad DecoderID '%s' from info cache: %s",cDecoders::ID2Str(dat->DecoderID),full); break;
   1.223 +        }
   1.224 +      }
   1.225 +    dat->Unlock();
   1.226 +    }
   1.227 +
   1.228 +  if(!decoder || !decoder->Valid()) {
   1.229 +    // no decoder in cache or cached decoder doesn't matches.
   1.230 +    // try to detect a decoder
   1.231 +
   1.232 +    delete decoder; decoder=0;
   1.233 +#ifdef HAVE_SNDFILE
   1.234 +    if(!decoder) {
   1.235 +      decoder=new cSndDecoder(full);
   1.236 +      if(!decoder || !decoder->Valid()) { delete decoder; decoder=0; }
   1.237 +      }
   1.238 +#endif
   1.239 +#ifdef HAVE_VORBISFILE
   1.240 +    if(!decoder) {
   1.241 +      decoder=new cOggDecoder(full);
   1.242 +      if(!decoder || !decoder->Valid()) { delete decoder; decoder=0; }
   1.243 +      }
   1.244 +#endif
   1.245 +    if(!decoder) {
   1.246 +      decoder=new cMP3StreamDecoder(full);
   1.247 +      if(!decoder || !decoder->Valid()) { delete decoder; decoder=0; }
   1.248 +      }
   1.249 +    if(!decoder) {
   1.250 +      decoder=new cMP3Decoder(full);
   1.251 +      if(!decoder || !decoder->Valid()) { delete decoder; decoder=0; }
   1.252 +      }
   1.253 +    if(!decoder) esyslog("ERROR: no decoder found for %s",Obj->Name());
   1.254 +    }
   1.255 +  return decoder;
   1.256 +}
   1.257 +
   1.258 +const char *cDecoders::ID2Str(int id)
   1.259 +{
   1.260 +  switch(id) {
   1.261 +    case DEC_MP3:  return DEC_MP3_STR;
   1.262 +    case DEC_MP3S: return DEC_MP3S_STR;
   1.263 +    case DEC_SND:  return DEC_SND_STR;
   1.264 +    case DEC_OGG:  return DEC_OGG_STR;
   1.265 +    }
   1.266 +  return 0;
   1.267 +}
   1.268 +
   1.269 +int cDecoders::Str2ID(const char *str)
   1.270 +{
   1.271 +  if     (!strcmp(str,DEC_MP3_STR )) return DEC_MP3;
   1.272 +  else if(!strcmp(str,DEC_MP3S_STR)) return DEC_MP3S;
   1.273 +  else if(!strcmp(str,DEC_SND_STR )) return DEC_SND;
   1.274 +  else if(!strcmp(str,DEC_OGG_STR )) return DEC_OGG;
   1.275 +  return 0;
   1.276 +}
   1.277 +
   1.278 +// --- cDecoder ----------------------------------------------------------------
   1.279 +
   1.280 +cDecoder::cDecoder(const char *Filename)
   1.281 +{
   1.282 +  filename=strdup(Filename);
   1.283 +  locked=0; urgentLock=playing=false;
   1.284 +}
   1.285 +
   1.286 +cDecoder::~cDecoder()
   1.287 +{
   1.288 +  free(filename);
   1.289 +}
   1.290 +
   1.291 +void cDecoder::Lock(bool urgent)
   1.292 +{
   1.293 +  locklock.Lock();
   1.294 +  if(urgent && locked) urgentLock=true; // signal other locks to release quickly
   1.295 +  locked++;
   1.296 +  locklock.Unlock(); // don't hold the "locklock" when locking "lock", may cause a deadlock
   1.297 +  lock.Lock();
   1.298 +  urgentLock=false;
   1.299 +}
   1.300 +
   1.301 +void cDecoder::Unlock(void)
   1.302 +{
   1.303 +  locklock.Lock();
   1.304 +  locked--;
   1.305 +  lock.Unlock();
   1.306 +  locklock.Unlock();
   1.307 +}
   1.308 +
   1.309 +bool cDecoder::TryLock(void)
   1.310 +{
   1.311 +  bool res=false;
   1.312 +  locklock.Lock();
   1.313 +  if(!locked && !playing) {
   1.314 +    Lock();
   1.315 +    res=true;
   1.316 +    }
   1.317 +  locklock.Unlock();
   1.318 +  return res;
   1.319 +}
   1.320 +
   1.321 +// --- cCacheData -----------------------------------------------------
   1.322 +
   1.323 +cCacheData::cCacheData(void)
   1.324 +{
   1.325 +  touch=0; version=0;
   1.326 +}
   1.327 +
   1.328 +void cCacheData::Touch(void)
   1.329 +{
   1.330 +  touch=time(0);
   1.331 +}
   1.332 +
   1.333 +#define SECS_PER_DAY (24*60*60)
   1.334 +
   1.335 +bool cCacheData::Purge(void)
   1.336 +{
   1.337 +  time_t now=time(0);
   1.338 +  //XXX does this realy made sense?
   1.339 +  //if(touch+CACHEPURGETIMEOUT*SECS_PER_DAY < now) {
   1.340 +  //  d(printf("cache: purged: timeout %s\n",Filename))
   1.341 +  //  return true;
   1.342 +  //  }
   1.343 +  if(touch+CACHEPURGETIMEOUT*SECS_PER_DAY/10 < now) {
   1.344 +    if(!Removable()) {                            // is this a permant source?
   1.345 +      struct stat64 ds;                           // does the file exists? if not, purge
   1.346 +      if(stat64(Filename,&ds) || !S_ISREG(ds.st_mode) || access(Filename,R_OK)) {
   1.347 +        d(printf("cache: purged: file not found %s\n",Filename))
   1.348 +        return true;
   1.349 +        }
   1.350 +      }
   1.351 +    }
   1.352 +  return false;
   1.353 +}
   1.354 +
   1.355 +bool cCacheData::Upgrade(void)
   1.356 +{
   1.357 +  if(version<7) {
   1.358 +    if(DecoderID==DEC_SND || (Title && startswith(Title,"track-")))
   1.359 +      return false;              // Trash older SND entries (incomplete)
   1.360 +
   1.361 +    if(Removable()) {
   1.362 +      if(!FsID) FsID=strdup("old"); // Dummy entry, will be replaced in InfoCache::Search()
   1.363 +      }
   1.364 +    else { free(FsID); FsID=0; }
   1.365 +    }
   1.366 +  if(version<4) {
   1.367 +    Touch();                     // Touch entry
   1.368 +    }
   1.369 +  if(version<3 && !Title) {
   1.370 +    FakeTitle(Filename,".mp3");  // Fake title
   1.371 +    }
   1.372 +  if(version<2 && Bitrate<=0) {
   1.373 +    return false;                // Trash entry without bitrate
   1.374 +    }
   1.375 +  return true;
   1.376 +}
   1.377 +
   1.378 +void cCacheData::Create(cFileInfo *fi, cSongInfo *si)
   1.379 +{
   1.380 +  cFileInfo::Set(fi);
   1.381 +  cSongInfo::Set(si);
   1.382 +  hash=MakeHash(Filename);
   1.383 +  Touch();
   1.384 +}
   1.385 +
   1.386 +bool cCacheData::Save(FILE *f)
   1.387 +{
   1.388 +  fprintf(f,"##BEGIN\n"
   1.389 +            "Filename=%s\n"
   1.390 +            "Filesize=%lld\n"
   1.391 +            "Timestamp=%ld\n"
   1.392 +            "Touch=%ld\n"
   1.393 +            "Version=%d\n"
   1.394 +            "Frames=%d\n"
   1.395 +            "Total=%d\n"
   1.396 +            "SampleFreq=%d\n"
   1.397 +            "Channels=%d\n"
   1.398 +            "Bitrate=%d\n"
   1.399 +            "MaxBitrate=%d\n"
   1.400 +            "ChMode=%d\n"
   1.401 +            "Year=%d\n"
   1.402 +            "Level=%.4f\n"
   1.403 +            "Peak=%.4f\n",
   1.404 +            Filename,Filesize,CTime,touch,CACHE_VERSION,Frames,Total,SampleFreq,Channels,Bitrate,MaxBitrate,ChMode,Year,Level,Peak);
   1.405 +  if(Title)     fprintf(f,"Title=%s\n"    ,Title);
   1.406 +  if(Artist)    fprintf(f,"Artist=%s\n"   ,Artist);
   1.407 +  if(Album)     fprintf(f,"Album=%s\n"    ,Album);
   1.408 +  if(DecoderID) fprintf(f,"DecoderID=%s\n",cDecoders::ID2Str(DecoderID));
   1.409 +  if(FsID)      fprintf(f,"FsID=%s\n"     ,FsID);
   1.410 +  fprintf(f,"##END\n");
   1.411 +  return ferror(f)==0;
   1.412 +}
   1.413 +
   1.414 +bool cCacheData::Load(FILE *f)
   1.415 +{
   1.416 +  char buf[1024], *delimiters="=\n";
   1.417 +
   1.418 +  cFileInfo::Clear();
   1.419 +  cSongInfo::Clear();
   1.420 +  while(fgets(buf,sizeof(buf),f)) {
   1.421 +    char *ptrptr;
   1.422 +    char *name =strtok_r(buf ,delimiters,&ptrptr);
   1.423 +    char *value=strtok_r(0,delimiters,&ptrptr);
   1.424 +    if(name) {
   1.425 +      if(!strcasecmp(name,"##END")) break;
   1.426 +      if(value) {
   1.427 +        if     (!strcasecmp(name,"Filename"))   Filename  =strdup(value);
   1.428 +        else if(!strcasecmp(name,"Filesize") ||
   1.429 +                !strcasecmp(name,"Size"))       Filesize  =atoll(value);
   1.430 +        else if(!strcasecmp(name,"FsID"))       FsID      =strdup(value);
   1.431 +        else if(!strcasecmp(name,"Timestamp"))  CTime     =atol(value);
   1.432 +        else if(!strcasecmp(name,"Touch"))      touch     =atol(value);
   1.433 +        else if(!strcasecmp(name,"Version"))    version   =atoi(value);
   1.434 +        else if(!strcasecmp(name,"DecoderID"))  DecoderID =cDecoders::Str2ID(value);
   1.435 +        else if(!strcasecmp(name,"Frames"))     Frames    =atoi(value);
   1.436 +        else if(!strcasecmp(name,"Total"))      Total     =atoi(value);
   1.437 +        else if(!strcasecmp(name,"SampleFreq")) SampleFreq=atoi(value);
   1.438 +        else if(!strcasecmp(name,"Channels"))   Channels  =atoi(value);
   1.439 +        else if(!strcasecmp(name,"Bitrate"))    Bitrate   =atoi(value);
   1.440 +        else if(!strcasecmp(name,"MaxBitrate")) MaxBitrate=atoi(value);
   1.441 +        else if(!strcasecmp(name,"ChMode"))     ChMode    =atoi(value);
   1.442 +        else if(!strcasecmp(name,"Year"))       Year      =atoi(value);
   1.443 +        else if(!strcasecmp(name,"Title"))      Title     =strdup(value);
   1.444 +        else if(!strcasecmp(name,"Artist"))     Artist    =strdup(value);
   1.445 +        else if(!strcasecmp(name,"Album"))      Album     =strdup(value);
   1.446 +        else if(!strcasecmp(name,"Level"))      Level     =atof(value);
   1.447 +        else if(!strcasecmp(name,"Peak"))       Peak      =atof(value);
   1.448 +        else d(printf("cache: ignoring bad token '%s' from cache file\n",name))
   1.449 +        }
   1.450 +      }
   1.451 +    }
   1.452 +
   1.453 +  if(ferror(f) || !Filename) return false;
   1.454 +  hash=MakeHash(Filename);
   1.455 +  return true;
   1.456 +}
   1.457 +
   1.458 +// --- cInfoCache ----------------------------------------------------
   1.459 +
   1.460 +cInfoCache::cInfoCache(void)
   1.461 +{
   1.462 +  lasttime=0; modified=false;
   1.463 +  lastpurge=time(0)-(50*60);
   1.464 +}
   1.465 +
   1.466 +void cInfoCache::Cache(cSongInfo *info, cFileInfo *file)
   1.467 +{
   1.468 +  lock.Lock();
   1.469 +  cCacheData *dat=Search(file);
   1.470 +  if(dat) {
   1.471 +    dat->Create(file,info);
   1.472 +    Modified();
   1.473 +    dat->Unlock();
   1.474 +    d(printf("cache: updating infos for %s\n",file->Filename))
   1.475 +    }
   1.476 +  else {
   1.477 +    dat=new cCacheData;
   1.478 +    dat->Create(file,info);
   1.479 +    AddEntry(dat);
   1.480 +    d(printf("cache: caching infos for %s\n",file->Filename))
   1.481 +    }
   1.482 +  lock.Unlock();
   1.483 +}
   1.484 +
   1.485 +cCacheData *cInfoCache::Search(cFileInfo *file)
   1.486 +{
   1.487 +  int hash=MakeHash(file->Filename);
   1.488 +  lock.Lock();
   1.489 +  cCacheData *dat=FirstEntry(hash);
   1.490 +  while(dat) {
   1.491 +    if(dat->hash==hash && !strcmp(dat->Filename,file->Filename) && dat->Filesize==file->Filesize) {
   1.492 +      dat->Lock();
   1.493 +      if(file->FsID && dat->FsID && !strcmp(dat->FsID,"old")) { // duplicate FsID for old entries
   1.494 +        dat->FsID=strdup(file->FsID);
   1.495 +        dat->Touch(); Modified();
   1.496 +        //d(printf("adding FsID for %s\n",dat->Filename))
   1.497 +        }
   1.498 +
   1.499 +      if((!file->FsID && !dat->FsID) || (file->FsID && dat->FsID && !strcmp(dat->FsID,file->FsID))) {
   1.500 +        //d(printf("cache: found cache entry for %s\n",dat->Filename))
   1.501 +        dat->Touch(); Modified();
   1.502 +        if(dat->CTime!=file->CTime) {
   1.503 +          d(printf("cache: ctime differs, removing from cache: %s\n",dat->Filename))
   1.504 +          DelEntry(dat); dat=0;
   1.505 +          }
   1.506 +        break;
   1.507 +        }
   1.508 +      dat->Unlock();
   1.509 +      }
   1.510 +    dat=(cCacheData *)dat->Next();
   1.511 +    }
   1.512 +  lock.Unlock();
   1.513 +  return dat;
   1.514 +}
   1.515 +
   1.516 +void cInfoCache::AddEntry(cCacheData *dat)
   1.517 +{
   1.518 +  lists[dat->hash%CACHELINES].Add(dat);
   1.519 +  Modified();
   1.520 +}
   1.521 +
   1.522 +void cInfoCache::DelEntry(cCacheData *dat)
   1.523 +{
   1.524 +  dat->Lock();
   1.525 +  lists[dat->hash%CACHELINES].Del(dat);
   1.526 +  Modified();
   1.527 +}
   1.528 +
   1.529 +cCacheData *cInfoCache::FirstEntry(int hash)
   1.530 +{
   1.531 +  return lists[hash%CACHELINES].First();
   1.532 +}
   1.533 +
   1.534 +bool cInfoCache::Purge(void)
   1.535 +{
   1.536 +  time_t now=time(0);
   1.537 +  if(now-lastpurge>(60*60)) {
   1.538 +    isyslog("cleaning up id3 cache");
   1.539 +    Start();
   1.540 +    lastpurge=now;
   1.541 +    }
   1.542 +  return Active();
   1.543 +}
   1.544 +
   1.545 +void cInfoCache::Action(void)
   1.546 +{
   1.547 +  d(printf("cache: id3 cache purge thread started (pid=%d)\n",getpid()))
   1.548 +  nice(3);
   1.549 +  lock.Lock();
   1.550 +  for(int i=0,n=0 ; i<CACHELINES && Running(); i++) {
   1.551 +    cCacheData *dat=FirstEntry(i);
   1.552 +    while(dat && Running()) {
   1.553 +      cCacheData *ndat=(cCacheData *)dat->Next();
   1.554 +      if(dat->Purge()) DelEntry(dat);
   1.555 +      dat=ndat;
   1.556 +
   1.557 +      if(++n>30) {
   1.558 +        lastmod=false; n=0;
   1.559 +        lock.Unlock(); lock.Lock();    // give concurrent thread an access chance
   1.560 +        if(lastmod) dat=FirstEntry(i); // restart line, if cache changed meanwhile
   1.561 +        }
   1.562 +      }
   1.563 +    }
   1.564 +  lock.Unlock();
   1.565 +  d(printf("cache: id3 cache purge thread ended (pid=%d)\n",getpid()))
   1.566 +}
   1.567 +
   1.568 +char *cInfoCache::CacheFile(void)
   1.569 +{
   1.570 +  return AddPath(cachedir?cachedir:VideoDirectory,CACHEFILENAME);
   1.571 +}
   1.572 +
   1.573 +void cInfoCache::Save(bool force)
   1.574 +{
   1.575 +  if(!Purge() && modified && (force || time(0)>lasttime)) {
   1.576 +    char *name=CacheFile();
   1.577 +    cSafeFile f(name);
   1.578 +    free(name);
   1.579 +    if(f.Open()) {
   1.580 +      lock.Lock();
   1.581 +      fprintf(f,"## This is a generated file. DO NOT EDIT!!\n"
   1.582 +                "## This file will be OVERWRITTEN WITHOUT WARNING!!\n");
   1.583 +      for(int i=0 ; i<CACHELINES ; i++) {
   1.584 +        cCacheData *dat=FirstEntry(i);
   1.585 +        while(dat) {
   1.586 +          if(!dat->Save(f)) { i=CACHELINES+1; break; }
   1.587 +          dat=(cCacheData *)dat->Next();
   1.588 +          }
   1.589 +        }
   1.590 +      lock.Unlock();
   1.591 +      f.Close();
   1.592 +      modified=false; lasttime=time(0)+CACHESAVETIMEOUT;
   1.593 +      d(printf("cache: saved cache to file\n"))
   1.594 +      }
   1.595 +    }
   1.596 +}
   1.597 +
   1.598 +void cInfoCache::Load(void)
   1.599 +{
   1.600 +  char *name=CacheFile();
   1.601 +  if(access(name,F_OK)==0) {
   1.602 +    isyslog("loading id3 cache from %s",name);
   1.603 +    FILE *f=fopen(name,"r");
   1.604 +    if(f) {
   1.605 +      char buf[256];
   1.606 +      bool mod=false;
   1.607 +      lock.Lock();
   1.608 +      for(int i=0 ; i<CACHELINES ; i++) lists[i].Clear();
   1.609 +      while(fgets(buf,sizeof(buf),f)) {
   1.610 +        if(!strcasecmp(buf,"##BEGIN\n")) {
   1.611 +          cCacheData *dat=new cCacheData;
   1.612 +          if(dat->Load(f)) {
   1.613 +            if(dat->version!=CACHE_VERSION) {
   1.614 +              if(dat->Upgrade()) mod=true;
   1.615 +              else { delete dat; continue; }
   1.616 +              }
   1.617 +            AddEntry(dat);
   1.618 +            }
   1.619 +          else {
   1.620 +            delete dat;
   1.621 +            if(ferror(f)) {
   1.622 +              esyslog("ERROR: failed to load id3 cache");
   1.623 +              break;
   1.624 +              }
   1.625 +            }
   1.626 +          }
   1.627 +        }
   1.628 +      lock.Unlock();
   1.629 +      fclose(f);
   1.630 +      modified=false; if(mod) Modified();
   1.631 +      }
   1.632 +    else LOG_ERROR_STR(name);
   1.633 +    }
   1.634 +  free(name);
   1.635 +}