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