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