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