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 +}