vdr  1.7.27
recording.c
Go to the documentation of this file.
00001 /*
00002  * recording.c: Recording file handling
00003  *
00004  * See the main source file 'vdr.c' for copyright information and
00005  * how to reach the author.
00006  *
00007  * $Id: recording.c 2.53 2012/03/13 13:17:57 kls Exp $
00008  */
00009 
00010 #include "recording.h"
00011 #include <ctype.h>
00012 #include <dirent.h>
00013 #include <errno.h>
00014 #include <fcntl.h>
00015 #define __STDC_FORMAT_MACROS // Required for format specifiers
00016 #include <inttypes.h>
00017 #include <math.h>
00018 #include <stdio.h>
00019 #include <string.h>
00020 #include <sys/stat.h>
00021 #include <unistd.h>
00022 #include "channels.h"
00023 #include "i18n.h"
00024 #include "interface.h"
00025 #include "remux.h"
00026 #include "ringbuffer.h"
00027 #include "skins.h"
00028 #include "tools.h"
00029 #include "videodir.h"
00030 
00031 #define SUMMARYFALLBACK
00032 
00033 #define RECEXT       ".rec"
00034 #define DELEXT       ".del"
00035 /* This was the original code, which works fine in a Linux only environment.
00036    Unfortunately, because of Windows and its brain dead file system, we have
00037    to use a more complicated approach, in order to allow users who have enabled
00038    the --vfat command line option to see their recordings even if they forget to
00039    enable --vfat when restarting VDR... Gee, do I hate Windows.
00040    (kls 2002-07-27)
00041 #define DATAFORMAT   "%4d-%02d-%02d.%02d:%02d.%02d.%02d" RECEXT
00042 #define NAMEFORMAT   "%s/%s/" DATAFORMAT
00043 */
00044 #define DATAFORMATPES   "%4d-%02d-%02d.%02d%*c%02d.%02d.%02d" RECEXT
00045 #define NAMEFORMATPES   "%s/%s/" "%4d-%02d-%02d.%02d.%02d.%02d.%02d" RECEXT
00046 #define DATAFORMATTS    "%4d-%02d-%02d.%02d.%02d.%d-%d" RECEXT
00047 #define NAMEFORMATTS    "%s/%s/" DATAFORMATTS
00048 
00049 #define RESUMEFILESUFFIX  "/resume%s%s"
00050 #ifdef SUMMARYFALLBACK
00051 #define SUMMARYFILESUFFIX "/summary.vdr"
00052 #endif
00053 #define INFOFILESUFFIX    "/info"
00054 #define MARKSFILESUFFIX   "/marks"
00055 
00056 #define MINDISKSPACE 1024 // MB
00057 
00058 #define REMOVECHECKDELTA   60 // seconds between checks for removing deleted files
00059 #define DELETEDLIFETIME   300 // seconds after which a deleted recording will be actually removed
00060 #define DISKCHECKDELTA    100 // seconds between checks for free disk space
00061 #define REMOVELATENCY      10 // seconds to wait until next check after removing a file
00062 #define MARKSUPDATEDELTA   10 // seconds between checks for updating editing marks
00063 #define MININDEXAGE      3600 // seconds before an index file is considered no longer to be written
00064 
00065 #define MAX_SUBTITLE_LENGTH  40
00066 
00067 #define MAX_LINK_LEVEL  6
00068 
00069 bool VfatFileSystem = false;
00070 int InstanceId = 0;
00071 
00072 cRecordings DeletedRecordings(true);
00073 
00074 // --- cRemoveDeletedRecordingsThread ----------------------------------------
00075 
00076 class cRemoveDeletedRecordingsThread : public cThread {
00077 protected:
00078   virtual void Action(void);
00079 public:
00080   cRemoveDeletedRecordingsThread(void);
00081   };
00082 
00083 cRemoveDeletedRecordingsThread::cRemoveDeletedRecordingsThread(void)
00084 :cThread("remove deleted recordings")
00085 {
00086 }
00087 
00088 void cRemoveDeletedRecordingsThread::Action(void)
00089 {
00090   SetPriority(19);
00091   SetIOPriority(7);
00092   // Make sure only one instance of VDR does this:
00093   cLockFile LockFile(VideoDirectory);
00094   if (LockFile.Lock()) {
00095      bool deleted = false;
00096      cThreadLock DeletedRecordingsLock(&DeletedRecordings);
00097      for (cRecording *r = DeletedRecordings.First(); r; ) {
00098          if (r->Deleted() && time(NULL) - r->Deleted() > DELETEDLIFETIME) {
00099             cRecording *next = DeletedRecordings.Next(r);
00100             r->Remove();
00101             DeletedRecordings.Del(r);
00102             r = next;
00103             deleted = true;
00104             continue;
00105             }
00106          r = DeletedRecordings.Next(r);
00107          }
00108      if (deleted)
00109         RemoveEmptyVideoDirectories();
00110      }
00111 }
00112 
00113 static cRemoveDeletedRecordingsThread RemoveDeletedRecordingsThread;
00114 
00115 // ---
00116 
00117 void RemoveDeletedRecordings(void)
00118 {
00119   static time_t LastRemoveCheck = 0;
00120   if (time(NULL) - LastRemoveCheck > REMOVECHECKDELTA) {
00121      if (!RemoveDeletedRecordingsThread.Active()) {
00122         cThreadLock DeletedRecordingsLock(&DeletedRecordings);
00123         for (cRecording *r = DeletedRecordings.First(); r; r = DeletedRecordings.Next(r)) {
00124             if (r->Deleted() && time(NULL) - r->Deleted() > DELETEDLIFETIME) {
00125                RemoveDeletedRecordingsThread.Start();
00126                break;
00127                }
00128             }
00129         }
00130      LastRemoveCheck = time(NULL);
00131      }
00132 }
00133 
00134 void AssertFreeDiskSpace(int Priority, bool Force)
00135 {
00136   static cMutex Mutex;
00137   cMutexLock MutexLock(&Mutex);
00138   // With every call to this function we try to actually remove
00139   // a file, or mark a file for removal ("delete" it), so that
00140   // it will get removed during the next call.
00141   static time_t LastFreeDiskCheck = 0;
00142   int Factor = (Priority == -1) ? 10 : 1;
00143   if (Force || time(NULL) - LastFreeDiskCheck > DISKCHECKDELTA / Factor) {
00144      if (!VideoFileSpaceAvailable(MINDISKSPACE)) {
00145         // Make sure only one instance of VDR does this:
00146         cLockFile LockFile(VideoDirectory);
00147         if (!LockFile.Lock())
00148            return;
00149         // Remove the oldest file that has been "deleted":
00150         isyslog("low disk space while recording, trying to remove a deleted recording...");
00151         cThreadLock DeletedRecordingsLock(&DeletedRecordings);
00152         if (DeletedRecordings.Count()) {
00153            cRecording *r = DeletedRecordings.First();
00154            cRecording *r0 = NULL;
00155            while (r) {
00156                  if (IsOnVideoDirectoryFileSystem(r->FileName())) { // only remove recordings that will actually increase the free video disk space
00157                     if (!r0 || r->Start() < r0->Start())
00158                        r0 = r;
00159                     }
00160                  r = DeletedRecordings.Next(r);
00161                  }
00162            if (r0) {
00163               if (r0->Remove())
00164                  LastFreeDiskCheck += REMOVELATENCY / Factor;
00165               DeletedRecordings.Del(r0);
00166               return;
00167               }
00168            }
00169         else {
00170            // DeletedRecordings was empty, so to be absolutely sure there are no
00171            // deleted recordings we need to double check:
00172            DeletedRecordings.Update(true);
00173            if (DeletedRecordings.Count())
00174               return; // the next call will actually remove it
00175            }
00176         // No "deleted" files to remove, so let's see if we can delete a recording:
00177         isyslog("...no deleted recording found, trying to delete an old recording...");
00178         cThreadLock RecordingsLock(&Recordings);
00179         if (Recordings.Count()) {
00180            cRecording *r = Recordings.First();
00181            cRecording *r0 = NULL;
00182            while (r) {
00183                  if (IsOnVideoDirectoryFileSystem(r->FileName())) { // only delete recordings that will actually increase the free video disk space
00184                     if (!r->IsEdited() && r->Lifetime() < MAXLIFETIME) { // edited recordings and recordings with MAXLIFETIME live forever
00185                        if ((r->Lifetime() == 0 && Priority > r->Priority()) || // the recording has no guaranteed lifetime and the new recording has higher priority
00186                            (r->Lifetime() > 0 && (time(NULL) - r->Start()) / SECSINDAY >= r->Lifetime())) { // the recording's guaranteed lifetime has expired
00187                           if (r0) {
00188                              if (r->Priority() < r0->Priority() || (r->Priority() == r0->Priority() && r->Start() < r0->Start()))
00189                                 r0 = r; // in any case we delete the one with the lowest priority (or the older one in case of equal priorities)
00190                              }
00191                           else
00192                              r0 = r;
00193                           }
00194                        }
00195                     }
00196                  r = Recordings.Next(r);
00197                  }
00198            if (r0 && r0->Delete()) {
00199               Recordings.Del(r0);
00200               return;
00201               }
00202            }
00203         // Unable to free disk space, but there's nothing we can do about that...
00204         isyslog("...no old recording found, giving up");
00205         Skins.QueueMessage(mtWarning, tr("Low disk space!"), 5, -1);
00206         }
00207      LastFreeDiskCheck = time(NULL);
00208      }
00209 }
00210 
00211 // --- cResumeFile -----------------------------------------------------------
00212 
00213 cResumeFile::cResumeFile(const char *FileName, bool IsPesRecording)
00214 {
00215   isPesRecording = IsPesRecording;
00216   const char *Suffix = isPesRecording ? RESUMEFILESUFFIX ".vdr" : RESUMEFILESUFFIX;
00217   fileName = MALLOC(char, strlen(FileName) + strlen(Suffix) + 1);
00218   if (fileName) {
00219      strcpy(fileName, FileName);
00220      sprintf(fileName + strlen(fileName), Suffix, Setup.ResumeID ? "." : "", Setup.ResumeID ? *itoa(Setup.ResumeID) : "");
00221      }
00222   else
00223      esyslog("ERROR: can't allocate memory for resume file name");
00224 }
00225 
00226 cResumeFile::~cResumeFile()
00227 {
00228   free(fileName);
00229 }
00230 
00231 int cResumeFile::Read(void)
00232 {
00233   int resume = -1;
00234   if (fileName) {
00235      struct stat st;
00236      if (stat(fileName, &st) == 0) {
00237         if ((st.st_mode & S_IWUSR) == 0) // no write access, assume no resume
00238            return -1;
00239         }
00240      if (isPesRecording) {
00241         int f = open(fileName, O_RDONLY);
00242         if (f >= 0) {
00243            if (safe_read(f, &resume, sizeof(resume)) != sizeof(resume)) {
00244               resume = -1;
00245               LOG_ERROR_STR(fileName);
00246               }
00247            close(f);
00248            }
00249         else if (errno != ENOENT)
00250            LOG_ERROR_STR(fileName);
00251         }
00252      else {
00253         FILE *f = fopen(fileName, "r");
00254         if (f) {
00255            cReadLine ReadLine;
00256            char *s;
00257            int line = 0;
00258            while ((s = ReadLine.Read(f)) != NULL) {
00259                  ++line;
00260                  char *t = skipspace(s + 1);
00261                  switch (*s) {
00262                    case 'I': resume = atoi(t);
00263                              break;
00264                    default: ;
00265                    }
00266                  }
00267            fclose(f);
00268            }
00269         else if (errno != ENOENT)
00270            LOG_ERROR_STR(fileName);
00271         }
00272      }
00273   return resume;
00274 }
00275 
00276 bool cResumeFile::Save(int Index)
00277 {
00278   if (fileName) {
00279      if (isPesRecording) {
00280         int f = open(fileName, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE);
00281         if (f >= 0) {
00282            if (safe_write(f, &Index, sizeof(Index)) < 0)
00283               LOG_ERROR_STR(fileName);
00284            close(f);
00285            Recordings.ResetResume(fileName);
00286            return true;
00287            }
00288         }
00289      else {
00290         FILE *f = fopen(fileName, "w");
00291         if (f) {
00292            fprintf(f, "I %d\n", Index);
00293            fclose(f);
00294            Recordings.ResetResume(fileName);
00295            }
00296         else
00297            LOG_ERROR_STR(fileName);
00298         return true;
00299         }
00300      }
00301   return false;
00302 }
00303 
00304 void cResumeFile::Delete(void)
00305 {
00306   if (fileName) {
00307      if (remove(fileName) == 0)
00308         Recordings.ResetResume(fileName);
00309      else if (errno != ENOENT)
00310         LOG_ERROR_STR(fileName);
00311      }
00312 }
00313 
00314 // --- cRecordingInfo --------------------------------------------------------
00315 
00316 cRecordingInfo::cRecordingInfo(const cChannel *Channel, const cEvent *Event)
00317 {
00318   channelID = Channel ? Channel->GetChannelID() : tChannelID::InvalidID;
00319   channelName = Channel ? strdup(Channel->Name()) : NULL;
00320   ownEvent = Event ? NULL : new cEvent(0);
00321   event = ownEvent ? ownEvent : Event;
00322   aux = NULL;
00323   framesPerSecond = DEFAULTFRAMESPERSECOND;
00324   priority = MAXPRIORITY;
00325   lifetime = MAXLIFETIME;
00326   fileName = NULL;
00327   if (Channel) {
00328      // Since the EPG data's component records can carry only a single
00329      // language code, let's see whether the channel's PID data has
00330      // more information:
00331      cComponents *Components = (cComponents *)event->Components();
00332      if (!Components)
00333         Components = new cComponents;
00334      for (int i = 0; i < MAXAPIDS; i++) {
00335          const char *s = Channel->Alang(i);
00336          if (*s) {
00337             tComponent *Component = Components->GetComponent(i, 2, 3);
00338             if (!Component)
00339                Components->SetComponent(Components->NumComponents(), 2, 3, s, NULL);
00340             else if (strlen(s) > strlen(Component->language))
00341                strn0cpy(Component->language, s, sizeof(Component->language));
00342             }
00343          }
00344      // There's no "multiple languages" for Dolby Digital tracks, but
00345      // we do the same procedure here, too, in case there is no component
00346      // information at all:
00347      for (int i = 0; i < MAXDPIDS; i++) {
00348          const char *s = Channel->Dlang(i);
00349          if (*s) {
00350             tComponent *Component = Components->GetComponent(i, 4, 0); // AC3 component according to the DVB standard
00351             if (!Component)
00352                Component = Components->GetComponent(i, 2, 5); // fallback "Dolby" component according to the "Premiere pseudo standard"
00353             if (!Component)
00354                Components->SetComponent(Components->NumComponents(), 2, 5, s, NULL);
00355             else if (strlen(s) > strlen(Component->language))
00356                strn0cpy(Component->language, s, sizeof(Component->language));
00357             }
00358          }
00359      // The same applies to subtitles:
00360      for (int i = 0; i < MAXSPIDS; i++) {
00361          const char *s = Channel->Slang(i);
00362          if (*s) {
00363             tComponent *Component = Components->GetComponent(i, 3, 3);
00364             if (!Component)
00365                Components->SetComponent(Components->NumComponents(), 3, 3, s, NULL);
00366             else if (strlen(s) > strlen(Component->language))
00367                strn0cpy(Component->language, s, sizeof(Component->language));
00368             }
00369          }
00370      if (Components != event->Components())
00371         ((cEvent *)event)->SetComponents(Components);
00372      }
00373 }
00374 
00375 cRecordingInfo::cRecordingInfo(const char *FileName)
00376 {
00377   channelID = tChannelID::InvalidID;
00378   channelName = NULL;
00379   ownEvent = new cEvent(0);
00380   event = ownEvent;
00381   aux = NULL;
00382   framesPerSecond = DEFAULTFRAMESPERSECOND;
00383   priority = MAXPRIORITY;
00384   lifetime = MAXLIFETIME;
00385   fileName = strdup(cString::sprintf("%s%s", FileName, INFOFILESUFFIX));
00386 }
00387 
00388 cRecordingInfo::~cRecordingInfo()
00389 {
00390   delete ownEvent;
00391   free(aux);
00392   free(channelName);
00393   free(fileName);
00394 }
00395 
00396 void cRecordingInfo::SetData(const char *Title, const char *ShortText, const char *Description)
00397 {
00398   if (!isempty(Title))
00399      ((cEvent *)event)->SetTitle(Title);
00400   if (!isempty(ShortText))
00401      ((cEvent *)event)->SetShortText(ShortText);
00402   if (!isempty(Description))
00403      ((cEvent *)event)->SetDescription(Description);
00404 }
00405 
00406 void cRecordingInfo::SetAux(const char *Aux)
00407 {
00408   free(aux);
00409   aux = Aux ? strdup(Aux) : NULL;
00410 }
00411 
00412 void cRecordingInfo::SetFramesPerSecond(double FramesPerSecond)
00413 {
00414   framesPerSecond = FramesPerSecond;
00415 }
00416 
00417 bool cRecordingInfo::Read(FILE *f)
00418 {
00419   if (ownEvent) {
00420      cReadLine ReadLine;
00421      char *s;
00422      int line = 0;
00423      while ((s = ReadLine.Read(f)) != NULL) {
00424            ++line;
00425            char *t = skipspace(s + 1);
00426            switch (*s) {
00427              case 'C': {
00428                          char *p = strchr(t, ' ');
00429                          if (p) {
00430                             free(channelName);
00431                             channelName = strdup(compactspace(p));
00432                             *p = 0; // strips optional channel name
00433                             }
00434                          if (*t)
00435                             channelID = tChannelID::FromString(t);
00436                        }
00437                        break;
00438              case 'E': {
00439                          unsigned int EventID;
00440                          time_t StartTime;
00441                          int Duration;
00442                          unsigned int TableID = 0;
00443                          unsigned int Version = 0xFF;
00444                          int n = sscanf(t, "%u %ld %d %X %X", &EventID, &StartTime, &Duration, &TableID, &Version);
00445                          if (n >= 3 && n <= 5) {
00446                             ownEvent->SetEventID(EventID);
00447                             ownEvent->SetStartTime(StartTime);
00448                             ownEvent->SetDuration(Duration);
00449                             ownEvent->SetTableID(uchar(TableID));
00450                             ownEvent->SetVersion(uchar(Version));
00451                             }
00452                        }
00453                        break;
00454              case 'F': framesPerSecond = atof(t);
00455                        break;
00456              case 'L': lifetime = atoi(t);
00457                        break;
00458              case 'P': priority = atoi(t);
00459                        break;
00460              case '@': free(aux);
00461                        aux = strdup(t);
00462                        break;
00463              case '#': break; // comments are ignored
00464              default: if (!ownEvent->Parse(s)) {
00465                          esyslog("ERROR: EPG data problem in line %d", line);
00466                          return false;
00467                          }
00468                       break;
00469              }
00470            }
00471      return true;
00472      }
00473   return false;
00474 }
00475 
00476 bool cRecordingInfo::Write(FILE *f, const char *Prefix) const
00477 {
00478   if (channelID.Valid())
00479      fprintf(f, "%sC %s%s%s\n", Prefix, *channelID.ToString(), channelName ? " " : "", channelName ? channelName : "");
00480   event->Dump(f, Prefix, true);
00481   fprintf(f, "%sF %.10g\n", Prefix, framesPerSecond);
00482   fprintf(f, "%sP %d\n", Prefix, priority);
00483   fprintf(f, "%sL %d\n", Prefix, lifetime);
00484   if (aux)
00485      fprintf(f, "%s@ %s\n", Prefix, aux);
00486   return true;
00487 }
00488 
00489 bool cRecordingInfo::Read(void)
00490 {
00491   bool Result = false;
00492   if (fileName) {
00493      FILE *f = fopen(fileName, "r");
00494      if (f) {
00495         if (Read(f))
00496            Result = true;
00497         else
00498            esyslog("ERROR: EPG data problem in file %s", fileName);
00499         fclose(f);
00500         }
00501      else if (errno != ENOENT)
00502         LOG_ERROR_STR(fileName);
00503      }
00504   return Result;
00505 }
00506 
00507 bool cRecordingInfo::Write(void) const
00508 {
00509   bool Result = false;
00510   if (fileName) {
00511      cSafeFile f(fileName);
00512      if (f.Open()) {
00513         if (Write(f))
00514            Result = true;
00515         f.Close();
00516         }
00517      else
00518         LOG_ERROR_STR(fileName);
00519      }
00520   return Result;
00521 }
00522 
00523 // --- cRecording ------------------------------------------------------------
00524 
00525 #define RESUME_NOT_INITIALIZED (-2)
00526 
00527 struct tCharExchange { char a; char b; };
00528 tCharExchange CharExchange[] = {
00529   { FOLDERDELIMCHAR,  '/' },
00530   { '/',  FOLDERDELIMCHAR },
00531   { ' ',  '_'    },
00532   // backwards compatibility:
00533   { '\'', '\''   },
00534   { '\'', '\x01' },
00535   { '/',  '\x02' },
00536   { 0, 0 }
00537   };
00538 
00539 char *ExchangeChars(char *s, bool ToFileSystem)
00540 {
00541   char *p = s;
00542   while (*p) {
00543         if (VfatFileSystem) {
00544            // The VFAT file system can't handle all characters, so we
00545            // have to take extra efforts to encode/decode them:
00546            if (ToFileSystem) {
00547               const char *InvalidChars = "\"\\/:*?|<>#";
00548               switch (*p) {
00549                      // characters that can be mapped to other characters:
00550                      case ' ': *p = '_'; break;
00551                      case FOLDERDELIMCHAR: *p = '/'; break;
00552                      // characters that have to be encoded:
00553                      default:
00554                        if (strchr(InvalidChars, *p) || *p == '.' && (!*(p + 1) || *(p + 1) == FOLDERDELIMCHAR)) { // Windows can't handle '.' at the end of file/directory names
00555                           int l = p - s;
00556                           if (char *NewBuffer = (char *)realloc(s, strlen(s) + 10)) {
00557                              s = NewBuffer;
00558                              p = s + l;
00559                              char buf[4];
00560                              sprintf(buf, "#%02X", (unsigned char)*p);
00561                              memmove(p + 2, p, strlen(p) + 1);
00562                              strncpy(p, buf, 3);
00563                              p += 2;
00564                              }
00565                           else
00566                              esyslog("ERROR: out of memory");
00567                           }
00568                      }
00569               }
00570            else {
00571               switch (*p) {
00572                 // mapped characters:
00573                 case '_': *p = ' '; break;
00574                 case '/': *p = FOLDERDELIMCHAR; break;
00575                 // encoded characters:
00576                 case '#': {
00577                      if (strlen(p) > 2 && isxdigit(*(p + 1)) && isxdigit(*(p + 2))) {
00578                         char buf[3];
00579                         sprintf(buf, "%c%c", *(p + 1), *(p + 2));
00580                         uchar c = uchar(strtol(buf, NULL, 16));
00581                         if (c) {
00582                            *p = c;
00583                            memmove(p + 1, p + 3, strlen(p) - 2);
00584                            }
00585                         }
00586                      }
00587                      break;
00588                 // backwards compatibility:
00589                 case '\x01': *p = '\''; break;
00590                 case '\x02': *p = '/';  break;
00591                 case '\x03': *p = ':';  break;
00592                 default: ;
00593                 }
00594               }
00595            }
00596         else {
00597            for (struct tCharExchange *ce = CharExchange; ce->a && ce->b; ce++) {
00598                if (*p == (ToFileSystem ? ce->a : ce->b)) {
00599                   *p = ToFileSystem ? ce->b : ce->a;
00600                   break;
00601                   }
00602                }
00603            }
00604         p++;
00605         }
00606   return s;
00607 }
00608 
00609 cRecording::cRecording(cTimer *Timer, const cEvent *Event)
00610 {
00611   resume = RESUME_NOT_INITIALIZED;
00612   titleBuffer = NULL;
00613   sortBuffer = NULL;
00614   fileName = NULL;
00615   name = NULL;
00616   fileSizeMB = -1; // unknown
00617   channel = Timer->Channel()->Number();
00618   instanceId = InstanceId;
00619   isPesRecording = false;
00620   framesPerSecond = DEFAULTFRAMESPERSECOND;
00621   numFrames = -1;
00622   deleted = 0;
00623   // set up the actual name:
00624   const char *Title = Event ? Event->Title() : NULL;
00625   const char *Subtitle = Event ? Event->ShortText() : NULL;
00626   char SubtitleBuffer[MAX_SUBTITLE_LENGTH];
00627   if (isempty(Title))
00628      Title = Timer->Channel()->Name();
00629   if (isempty(Subtitle))
00630      Subtitle = " ";
00631   else if (strlen(Subtitle) > MAX_SUBTITLE_LENGTH) {
00632      // let's make sure the Subtitle doesn't produce too long a file name:
00633      Utf8Strn0Cpy(SubtitleBuffer, Subtitle, MAX_SUBTITLE_LENGTH);
00634      Subtitle = SubtitleBuffer;
00635      }
00636   const char *macroTITLE   = strstr(Timer->File(), TIMERMACRO_TITLE);
00637   const char *macroEPISODE = strstr(Timer->File(), TIMERMACRO_EPISODE);
00638   if (macroTITLE || macroEPISODE) {
00639      name = strdup(Timer->File());
00640      name = strreplace(name, TIMERMACRO_TITLE, Title);
00641      name = strreplace(name, TIMERMACRO_EPISODE, Subtitle);
00642      // avoid blanks at the end:
00643      int l = strlen(name);
00644      while (l-- > 2) {
00645            if (name[l] == ' ' && name[l - 1] != FOLDERDELIMCHAR)
00646               name[l] = 0;
00647            else
00648               break;
00649            }
00650      if (Timer->IsSingleEvent()) {
00651         Timer->SetFile(name); // this was an instant recording, so let's set the actual data
00652         Timers.SetModified();
00653         }
00654      }
00655   else if (Timer->IsSingleEvent() || !Setup.UseSubtitle)
00656      name = strdup(Timer->File());
00657   else
00658      name = strdup(cString::sprintf("%s~%s", Timer->File(), Subtitle));
00659   // substitute characters that would cause problems in file names:
00660   strreplace(name, '\n', ' ');
00661   start = Timer->StartTime();
00662   priority = Timer->Priority();
00663   lifetime = Timer->Lifetime();
00664   // handle info:
00665   info = new cRecordingInfo(Timer->Channel(), Event);
00666   info->SetAux(Timer->Aux());
00667   info->priority = priority;
00668   info->lifetime = lifetime;
00669 }
00670 
00671 cRecording::cRecording(const char *FileName)
00672 {
00673   resume = RESUME_NOT_INITIALIZED;
00674   fileSizeMB = -1; // unknown
00675   channel = -1;
00676   instanceId = -1;
00677   priority = MAXPRIORITY; // assume maximum in case there is no info file
00678   lifetime = MAXLIFETIME;
00679   isPesRecording = false;
00680   framesPerSecond = DEFAULTFRAMESPERSECOND;
00681   numFrames = -1;
00682   deleted = 0;
00683   titleBuffer = NULL;
00684   sortBuffer = NULL;
00685   FileName = fileName = strdup(FileName);
00686   if (*(fileName + strlen(fileName) - 1) == '/')
00687      *(fileName + strlen(fileName) - 1) = 0;
00688   FileName += strlen(VideoDirectory) + 1;
00689   const char *p = strrchr(FileName, '/');
00690 
00691   name = NULL;
00692   info = new cRecordingInfo(fileName);
00693   if (p) {
00694      time_t now = time(NULL);
00695      struct tm tm_r;
00696      struct tm t = *localtime_r(&now, &tm_r); // this initializes the time zone in 't'
00697      t.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting
00698      if (7 == sscanf(p + 1, DATAFORMATTS, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &channel, &instanceId)
00699       || 7 == sscanf(p + 1, DATAFORMATPES, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &priority, &lifetime)) {
00700         t.tm_year -= 1900;
00701         t.tm_mon--;
00702         t.tm_sec = 0;
00703         start = mktime(&t);
00704         name = MALLOC(char, p - FileName + 1);
00705         strncpy(name, FileName, p - FileName);
00706         name[p - FileName] = 0;
00707         name = ExchangeChars(name, false);
00708         isPesRecording = instanceId < 0;
00709         }
00710      else
00711         return;
00712      GetResume();
00713      // read an optional info file:
00714      cString InfoFileName = cString::sprintf("%s%s", fileName, isPesRecording ? INFOFILESUFFIX ".vdr" : INFOFILESUFFIX);
00715      FILE *f = fopen(InfoFileName, "r");
00716      if (f) {
00717         if (!info->Read(f))
00718            esyslog("ERROR: EPG data problem in file %s", *InfoFileName);
00719         else if (!isPesRecording) {
00720            priority = info->priority;
00721            lifetime = info->lifetime;
00722            framesPerSecond = info->framesPerSecond;
00723            }
00724         fclose(f);
00725         }
00726      else if (errno != ENOENT)
00727         LOG_ERROR_STR(*InfoFileName);
00728 #ifdef SUMMARYFALLBACK
00729      // fall back to the old 'summary.vdr' if there was no 'info.vdr':
00730      if (isempty(info->Title())) {
00731         cString SummaryFileName = cString::sprintf("%s%s", fileName, SUMMARYFILESUFFIX);
00732         FILE *f = fopen(SummaryFileName, "r");
00733         if (f) {
00734            int line = 0;
00735            char *data[3] = { NULL };
00736            cReadLine ReadLine;
00737            char *s;
00738            while ((s = ReadLine.Read(f)) != NULL) {
00739                  if (*s || line > 1) {
00740                     if (data[line]) {
00741                        int len = strlen(s);
00742                        len += strlen(data[line]) + 1;
00743                        if (char *NewBuffer = (char *)realloc(data[line], len + 1)) {
00744                           data[line] = NewBuffer;
00745                           strcat(data[line], "\n");
00746                           strcat(data[line], s);
00747                           }
00748                        else
00749                           esyslog("ERROR: out of memory");
00750                        }
00751                     else
00752                        data[line] = strdup(s);
00753                     }
00754                  else
00755                     line++;
00756                  }
00757            fclose(f);
00758            if (!data[2]) {
00759               data[2] = data[1];
00760               data[1] = NULL;
00761               }
00762            else if (data[1] && data[2]) {
00763               // if line 1 is too long, it can't be the short text,
00764               // so assume the short text is missing and concatenate
00765               // line 1 and line 2 to be the long text:
00766               int len = strlen(data[1]);
00767               if (len > 80) {
00768                  if (char *NewBuffer = (char *)realloc(data[1], len + 1 + strlen(data[2]) + 1)) {
00769                     data[1] = NewBuffer;
00770                     strcat(data[1], "\n");
00771                     strcat(data[1], data[2]);
00772                     free(data[2]);
00773                     data[2] = data[1];
00774                     data[1] = NULL;
00775                     }
00776                  else
00777                     esyslog("ERROR: out of memory");
00778                  }
00779               }
00780            info->SetData(data[0], data[1], data[2]);
00781            for (int i = 0; i < 3; i ++)
00782                free(data[i]);
00783            }
00784         else if (errno != ENOENT)
00785            LOG_ERROR_STR(*SummaryFileName);
00786         }
00787 #endif
00788      }
00789 }
00790 
00791 cRecording::~cRecording()
00792 {
00793   free(titleBuffer);
00794   free(sortBuffer);
00795   free(fileName);
00796   free(name);
00797   delete info;
00798 }
00799 
00800 char *cRecording::StripEpisodeName(char *s)
00801 {
00802   char *t = s, *s1 = NULL, *s2 = NULL;
00803   while (*t) {
00804         if (*t == '/') {
00805            if (s1) {
00806               if (s2)
00807                  s1 = s2;
00808               s2 = t;
00809               }
00810            else
00811               s1 = t;
00812            }
00813         t++;
00814         }
00815   if (s1 && s2)
00816      memmove(s1 + 1, s2, t - s2 + 1);
00817   return s;
00818 }
00819 
00820 char *cRecording::SortName(void) const
00821 {
00822   if (!sortBuffer) {
00823      char *s = StripEpisodeName(strdup(FileName() + strlen(VideoDirectory) + 1));
00824      strreplace(s, '/', 'a'); // some locales ignore '/' when sorting
00825      int l = strxfrm(NULL, s, 0) + 1;
00826      sortBuffer = MALLOC(char, l);
00827      strxfrm(sortBuffer, s, l);
00828      free(s);
00829      }
00830   return sortBuffer;
00831 }
00832 
00833 int cRecording::GetResume(void) const
00834 {
00835   if (resume == RESUME_NOT_INITIALIZED) {
00836      cResumeFile ResumeFile(FileName(), isPesRecording);
00837      resume = ResumeFile.Read();
00838      }
00839   return resume;
00840 }
00841 
00842 int cRecording::Compare(const cListObject &ListObject) const
00843 {
00844   cRecording *r = (cRecording *)&ListObject;
00845   return strcasecmp(SortName(), r->SortName());
00846 }
00847 
00848 const char *cRecording::FileName(void) const
00849 {
00850   if (!fileName) {
00851      struct tm tm_r;
00852      struct tm *t = localtime_r(&start, &tm_r);
00853      const char *fmt = isPesRecording ? NAMEFORMATPES : NAMEFORMATTS;
00854      int ch = isPesRecording ? priority : channel;
00855      int ri = isPesRecording ? lifetime : instanceId;
00856      name = ExchangeChars(name, true);
00857      fileName = strdup(cString::sprintf(fmt, VideoDirectory, name, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, ch, ri));
00858      name = ExchangeChars(name, false);
00859      }
00860   return fileName;
00861 }
00862 
00863 const char *cRecording::Title(char Delimiter, bool NewIndicator, int Level) const
00864 {
00865   char New = NewIndicator && IsNew() ? '*' : ' ';
00866   free(titleBuffer);
00867   titleBuffer = NULL;
00868   if (Level < 0 || Level == HierarchyLevels()) {
00869      struct tm tm_r;
00870      struct tm *t = localtime_r(&start, &tm_r);
00871      char *s;
00872      if (Level > 0 && (s = strrchr(name, FOLDERDELIMCHAR)) != NULL)
00873         s++;
00874      else
00875         s = name;
00876      cString Length("");
00877      if (NewIndicator) {
00878         int Minutes = max(0, (LengthInSeconds() + 30) / 60);
00879         Length = cString::sprintf("%c%d:%02d",
00880                    Delimiter,
00881                    Minutes / 60,
00882                    Minutes % 60
00883                    );
00884         }
00885      titleBuffer = strdup(cString::sprintf("%02d.%02d.%02d%c%02d:%02d%s%c%c%s",
00886                             t->tm_mday,
00887                             t->tm_mon + 1,
00888                             t->tm_year % 100,
00889                             Delimiter,
00890                             t->tm_hour,
00891                             t->tm_min,
00892                             *Length,
00893                             New,
00894                             Delimiter,
00895                             s));
00896      // let's not display a trailing FOLDERDELIMCHAR:
00897      if (!NewIndicator)
00898         stripspace(titleBuffer);
00899      s = &titleBuffer[strlen(titleBuffer) - 1];
00900      if (*s == FOLDERDELIMCHAR)
00901         *s = 0;
00902      }
00903   else if (Level < HierarchyLevels()) {
00904      const char *s = name;
00905      const char *p = s;
00906      while (*++s) {
00907            if (*s == FOLDERDELIMCHAR) {
00908               if (Level--)
00909                  p = s + 1;
00910               else
00911                  break;
00912               }
00913            }
00914      titleBuffer = MALLOC(char, s - p + 3);
00915      *titleBuffer = Delimiter;
00916      *(titleBuffer + 1) = Delimiter;
00917      strn0cpy(titleBuffer + 2, p, s - p + 1);
00918      }
00919   else
00920      return "";
00921   return titleBuffer;
00922 }
00923 
00924 const char *cRecording::PrefixFileName(char Prefix)
00925 {
00926   cString p = PrefixVideoFileName(FileName(), Prefix);
00927   if (*p) {
00928      free(fileName);
00929      fileName = strdup(p);
00930      return fileName;
00931      }
00932   return NULL;
00933 }
00934 
00935 const char *cRecording::UpdateFileName(const char *FileName)
00936 {
00937   if (FileName && *FileName) {
00938      free(fileName);
00939      fileName = strdup(FileName);
00940      return fileName;
00941      }
00942   return NULL;
00943 }
00944 
00945 int cRecording::HierarchyLevels(void) const
00946 {
00947   const char *s = name;
00948   int level = 0;
00949   while (*++s) {
00950         if (*s == FOLDERDELIMCHAR)
00951            level++;
00952         }
00953   return level;
00954 }
00955 
00956 bool cRecording::IsEdited(void) const
00957 {
00958   const char *s = strrchr(name, FOLDERDELIMCHAR);
00959   s = !s ? name : s + 1;
00960   return *s == '%';
00961 }
00962 
00963 void cRecording::ReadInfo(void)
00964 {
00965   info->Read();
00966   priority = info->priority;
00967   lifetime = info->lifetime;
00968   framesPerSecond = info->framesPerSecond;
00969 }
00970 
00971 bool cRecording::WriteInfo(void)
00972 {
00973   cString InfoFileName = cString::sprintf("%s%s", fileName, isPesRecording ? INFOFILESUFFIX ".vdr" : INFOFILESUFFIX);
00974   FILE *f = fopen(InfoFileName, "w");
00975   if (f) {
00976      info->Write(f);
00977      fclose(f);
00978      }
00979   else
00980      LOG_ERROR_STR(*InfoFileName);
00981   return true;
00982 }
00983 
00984 void cRecording::SetStartTime(time_t Start) 
00985 {
00986   start = Start;
00987   free(fileName);
00988   fileName = NULL;
00989 }
00990 
00991 bool cRecording::Delete(void)
00992 {
00993   bool result = true;
00994   char *NewName = strdup(FileName());
00995   char *ext = strrchr(NewName, '.');
00996   if (ext && strcmp(ext, RECEXT) == 0) {
00997      strncpy(ext, DELEXT, strlen(ext));
00998      if (access(NewName, F_OK) == 0) {
00999         // the new name already exists, so let's remove that one first:
01000         isyslog("removing recording '%s'", NewName);
01001         RemoveVideoFile(NewName);
01002         }
01003      isyslog("deleting recording '%s'", FileName());
01004      if (access(FileName(), F_OK) == 0)
01005         result = RenameVideoFile(FileName(), NewName);
01006      else {
01007         isyslog("recording '%s' vanished", FileName());
01008         result = true; // well, we were going to delete it, anyway
01009         }
01010      }
01011   free(NewName);
01012   return result;
01013 }
01014 
01015 bool cRecording::Remove(void)
01016 {
01017   // let's do a final safety check here:
01018   if (!endswith(FileName(), DELEXT)) {
01019      esyslog("attempt to remove recording %s", FileName());
01020      return false;
01021      }
01022   isyslog("removing recording %s", FileName());
01023   return RemoveVideoFile(FileName());
01024 }
01025 
01026 bool cRecording::Undelete(void)
01027 {
01028   bool result = true;
01029   char *NewName = strdup(FileName());
01030   char *ext = strrchr(NewName, '.');
01031   if (ext && strcmp(ext, DELEXT) == 0) {
01032      strncpy(ext, RECEXT, strlen(ext));
01033      if (access(NewName, F_OK) == 0) {
01034         // the new name already exists, so let's not remove that one:
01035         esyslog("ERROR: attempt to undelete '%s', while recording '%s' exists", FileName(), NewName);
01036         result = false;
01037         }
01038      else {
01039         isyslog("undeleting recording '%s'", FileName());
01040         if (access(FileName(), F_OK) == 0)
01041            result = RenameVideoFile(FileName(), NewName);
01042         else {
01043            isyslog("deleted recording '%s' vanished", FileName());
01044            result = false;
01045            }
01046         }
01047      }
01048   free(NewName);
01049   return result;
01050 }
01051 
01052 void cRecording::ResetResume(void) const
01053 {
01054   resume = RESUME_NOT_INITIALIZED;
01055 }
01056 
01057 int cRecording::NumFrames(void) const
01058 {
01059   if (numFrames < 0) {
01060      int nf = cIndexFile::GetLength(FileName(), IsPesRecording());
01061      if (time(NULL) - LastModifiedTime(FileName()) < MININDEXAGE)
01062         return nf; // check again later for ongoing recordings
01063      numFrames = nf;
01064      }
01065   return numFrames;
01066 }
01067 
01068 int cRecording::LengthInSeconds(void) const
01069 {
01070   int nf = NumFrames();
01071   if (nf >= 0)
01072      return int(nf / FramesPerSecond());
01073   return -1;
01074 }
01075 
01076 int cRecording::FileSizeMB(void) const
01077 {
01078   if (fileSizeMB < 0) {
01079      int fs = DirSizeMB(FileName());
01080      if (time(NULL) - LastModifiedTime(FileName()) < MININDEXAGE)
01081         return fs; // check again later for ongoing recordings
01082      fileSizeMB = fs;
01083      }
01084   return fileSizeMB;
01085 }
01086 
01087 // --- cRecordings -----------------------------------------------------------
01088 
01089 cRecordings Recordings;
01090 
01091 char *cRecordings::updateFileName = NULL;
01092 
01093 cRecordings::cRecordings(bool Deleted)
01094 :cThread("video directory scanner")
01095 {
01096   deleted = Deleted;
01097   lastUpdate = 0;
01098   state = 0;
01099 }
01100 
01101 cRecordings::~cRecordings()
01102 {
01103   Cancel(3);
01104 }
01105 
01106 void cRecordings::Action(void)
01107 {
01108   Refresh();
01109 }
01110 
01111 const char *cRecordings::UpdateFileName(void)
01112 {
01113   if (!updateFileName)
01114      updateFileName = strdup(AddDirectory(VideoDirectory, ".update"));
01115   return updateFileName;
01116 }
01117 
01118 void cRecordings::Refresh(bool Foreground)
01119 {
01120   lastUpdate = time(NULL); // doing this first to make sure we don't miss anything
01121   Lock();
01122   Clear();
01123   ChangeState();
01124   Unlock();
01125   ScanVideoDir(VideoDirectory, Foreground);
01126 }
01127 
01128 void cRecordings::ScanVideoDir(const char *DirName, bool Foreground, int LinkLevel)
01129 {
01130   cReadDir d(DirName);
01131   struct dirent *e;
01132   while ((Foreground || Running()) && (e = d.Next()) != NULL) {
01133         cString buffer = AddDirectory(DirName, e->d_name);
01134         struct stat st;
01135         if (lstat(buffer, &st) == 0) {
01136            int Link = 0;
01137            if (S_ISLNK(st.st_mode)) {
01138               if (LinkLevel > MAX_LINK_LEVEL) {
01139                  isyslog("max link level exceeded - not scanning %s", *buffer);
01140                  continue;
01141                  }
01142               Link = 1;
01143               if (stat(buffer, &st) != 0)
01144                  continue;
01145               }
01146            if (S_ISDIR(st.st_mode)) {
01147               if (endswith(buffer, deleted ? DELEXT : RECEXT)) {
01148                  cRecording *r = new cRecording(buffer);
01149                  if (r->Name()) {
01150                     r->NumFrames(); // initializes the numFrames member
01151                     r->FileSizeMB(); // initializes the fileSizeMB member
01152                     if (deleted)
01153                        r->deleted = time(NULL);
01154                     Lock();
01155                     Add(r);
01156                     ChangeState();
01157                     Unlock();
01158                     }
01159                  else
01160                     delete r;
01161                  }
01162               else
01163                  ScanVideoDir(buffer, Foreground, LinkLevel + Link);
01164               }
01165            }
01166         }
01167 }
01168 
01169 bool cRecordings::StateChanged(int &State)
01170 {
01171   int NewState = state;
01172   bool Result = State != NewState;
01173   State = state;
01174   return Result;
01175 }
01176 
01177 void cRecordings::TouchUpdate(void)
01178 {
01179   bool needsUpdate = NeedsUpdate();
01180   TouchFile(UpdateFileName());
01181   if (!needsUpdate)
01182      lastUpdate = time(NULL); // make sure we don't trigger ourselves
01183 }
01184 
01185 bool cRecordings::NeedsUpdate(void)
01186 {
01187   time_t lastModified = LastModifiedTime(UpdateFileName());
01188   if (lastModified > time(NULL))
01189      return false; // somebody's clock isn't running correctly
01190   return lastUpdate < lastModified;
01191 }
01192 
01193 bool cRecordings::Update(bool Wait)
01194 {
01195   if (Wait) {
01196      Refresh(true);
01197      return Count() > 0;
01198      }
01199   else
01200      Start();
01201   return false;
01202 }
01203 
01204 cRecording *cRecordings::GetByName(const char *FileName)
01205 {
01206   if (FileName) {
01207      for (cRecording *recording = First(); recording; recording = Next(recording)) {
01208          if (strcmp(recording->FileName(), FileName) == 0)
01209             return recording;
01210          }
01211      }
01212   return NULL;
01213 }
01214 
01215 void cRecordings::AddByName(const char *FileName, bool TriggerUpdate)
01216 {
01217   LOCK_THREAD;
01218   cRecording *recording = GetByName(FileName);
01219   if (!recording) {
01220      recording = new cRecording(FileName);
01221      Add(recording);
01222      ChangeState();
01223      if (TriggerUpdate)
01224         TouchUpdate();
01225      }
01226 }
01227 
01228 void cRecordings::DelByName(const char *FileName, bool RemoveRecording)
01229 {
01230   LOCK_THREAD;
01231   cRecording *recording = GetByName(FileName);
01232   if (recording) {
01233      cThreadLock DeletedRecordingsLock(&DeletedRecordings);
01234      Del(recording, false);
01235      char *ext = strrchr(recording->fileName, '.');
01236      if (ext && RemoveRecording) {
01237         strncpy(ext, DELEXT, strlen(ext));
01238         if (access(recording->FileName(), F_OK) == 0) {
01239            recording->deleted = time(NULL);
01240            DeletedRecordings.Add(recording);
01241            recording = NULL; // to prevent it from being deleted below
01242            }
01243         }
01244      delete recording;
01245      ChangeState();
01246      TouchUpdate();
01247      }
01248 }
01249 
01250 void cRecordings::UpdateByName(const char *FileName)
01251 {
01252   LOCK_THREAD;
01253   cRecording *recording = GetByName(FileName);
01254   if (recording)
01255      recording->ReadInfo();
01256 }
01257 
01258 int cRecordings::TotalFileSizeMB(void)
01259 {
01260   int size = 0;
01261   LOCK_THREAD;
01262   for (cRecording *recording = First(); recording; recording = Next(recording)) {
01263       int FileSizeMB = recording->FileSizeMB();
01264       if (FileSizeMB > 0 && IsOnVideoDirectoryFileSystem(recording->FileName()))
01265          size += FileSizeMB;
01266       }
01267   return size;
01268 }
01269 
01270 double cRecordings::MBperMinute(void)
01271 {
01272   int size = 0;
01273   int length = 0;
01274   LOCK_THREAD;
01275   for (cRecording *recording = First(); recording; recording = Next(recording)) {
01276       if (IsOnVideoDirectoryFileSystem(recording->FileName())) {
01277          int FileSizeMB = recording->FileSizeMB();
01278          if (FileSizeMB > 0) {
01279             int LengthInSeconds = recording->LengthInSeconds();
01280             if (LengthInSeconds > 0) {
01281                size += FileSizeMB;
01282                length += LengthInSeconds;
01283                }
01284             }
01285          }
01286       }
01287   return (size && length) ? double(size) * 60 / length : -1;
01288 }
01289 
01290 void cRecordings::ResetResume(const char *ResumeFileName)
01291 {
01292   LOCK_THREAD;
01293   for (cRecording *recording = First(); recording; recording = Next(recording)) {
01294       if (!ResumeFileName || strncmp(ResumeFileName, recording->FileName(), strlen(recording->FileName())) == 0)
01295          recording->ResetResume();
01296       }
01297   ChangeState();
01298 }
01299 
01300 // --- cMark -----------------------------------------------------------------
01301 
01302 double MarkFramesPerSecond = DEFAULTFRAMESPERSECOND;
01303 cMutex MutexMarkFramesPerSecond;
01304 
01305 cMark::cMark(int Position, const char *Comment, double FramesPerSecond)
01306 {
01307   position = Position;
01308   comment = Comment;
01309   framesPerSecond = FramesPerSecond;
01310 }
01311 
01312 cMark::~cMark()
01313 {
01314 }
01315 
01316 cString cMark::ToText(void)
01317 {
01318   return cString::sprintf("%s%s%s\n", *IndexToHMSF(position, true, framesPerSecond), Comment() ? " " : "", Comment() ? Comment() : "");
01319 }
01320 
01321 bool cMark::Parse(const char *s)
01322 {
01323   comment = NULL;
01324   framesPerSecond = MarkFramesPerSecond;
01325   position = HMSFToIndex(s, framesPerSecond);
01326   const char *p = strchr(s, ' ');
01327   if (p) {
01328      p = skipspace(p);
01329      if (*p)
01330         comment = strdup(p);
01331      }
01332   return true;
01333 }
01334 
01335 bool cMark::Save(FILE *f)
01336 {
01337   return fprintf(f, "%s", *ToText()) > 0;
01338 }
01339 
01340 // --- cMarks ----------------------------------------------------------------
01341 
01342 bool cMarks::Load(const char *RecordingFileName, double FramesPerSecond, bool IsPesRecording)
01343 {
01344   fileName = AddDirectory(RecordingFileName, IsPesRecording ? MARKSFILESUFFIX ".vdr" : MARKSFILESUFFIX);
01345   framesPerSecond = FramesPerSecond;
01346   nextUpdate = 0;
01347   lastFileTime = -1; // the first call to Load() must take place!
01348   lastChange = 0;
01349   return Update();
01350 }
01351 
01352 bool cMarks::Update(void)
01353 {
01354   time_t t = time(NULL);
01355   if (t > nextUpdate) {
01356      time_t LastModified = LastModifiedTime(fileName);
01357      if (LastModified != lastFileTime) // change detected, or first run
01358         lastChange = LastModified > 0 ? LastModified : t;
01359      int d = t - lastChange;
01360      if (d < 60)
01361         d = 1; // check frequently if the file has just been modified
01362      else if (d < 3600)
01363         d = 10; // older files are checked less frequently
01364      else
01365         d /= 360; // phase out checking for very old files
01366      nextUpdate = t + d;
01367      if (LastModified != lastFileTime) { // change detected, or first run
01368         lastFileTime = LastModified;
01369         if (lastFileTime == t)
01370            lastFileTime--; // make sure we don't miss updates in the remaining second
01371         cMutexLock MutexLock(&MutexMarkFramesPerSecond);
01372         MarkFramesPerSecond = framesPerSecond;
01373         if (cConfig<cMark>::Load(fileName)) {
01374            Sort();
01375            return true;
01376            }
01377         }
01378      }
01379   return false;
01380 }
01381 
01382 void cMarks::Sort(void)
01383 {
01384   for (cMark *m1 = First(); m1; m1 = Next(m1)) {
01385       for (cMark *m2 = Next(m1); m2; m2 = Next(m2)) {
01386           if (m2->Position() < m1->Position()) {
01387              swap(m1->position, m2->position);
01388              swap(m1->comment, m2->comment);
01389              }
01390           }
01391       }
01392 }
01393 
01394 cMark *cMarks::Add(int Position)
01395 {
01396   cMark *m = Get(Position);
01397   if (!m) {
01398      cConfig<cMark>::Add(m = new cMark(Position, NULL, framesPerSecond));
01399      Sort();
01400      }
01401   return m;
01402 }
01403 
01404 cMark *cMarks::Get(int Position)
01405 {
01406   for (cMark *mi = First(); mi; mi = Next(mi)) {
01407       if (mi->Position() == Position)
01408          return mi;
01409       }
01410   return NULL;
01411 }
01412 
01413 cMark *cMarks::GetPrev(int Position)
01414 {
01415   for (cMark *mi = Last(); mi; mi = Prev(mi)) {
01416       if (mi->Position() < Position)
01417          return mi;
01418       }
01419   return NULL;
01420 }
01421 
01422 cMark *cMarks::GetNext(int Position)
01423 {
01424   for (cMark *mi = First(); mi; mi = Next(mi)) {
01425       if (mi->Position() > Position)
01426          return mi;
01427       }
01428   return NULL;
01429 }
01430 
01431 // --- cRecordingUserCommand -------------------------------------------------
01432 
01433 const char *cRecordingUserCommand::command = NULL;
01434 
01435 void cRecordingUserCommand::InvokeCommand(const char *State, const char *RecordingFileName)
01436 {
01437   if (command) {
01438      cString cmd = cString::sprintf("%s %s \"%s\"", command, State, *strescape(RecordingFileName, "\\\"$"));
01439      isyslog("executing '%s'", *cmd);
01440      SystemExec(cmd);
01441      }
01442 }
01443 
01444 // --- cIndexFileGenerator ---------------------------------------------------
01445 
01446 #define IFG_BUFFER_SIZE KILOBYTE(100)
01447 
01448 class cIndexFileGenerator : public cThread {
01449 private:
01450   cString recordingName;
01451 protected:
01452   virtual void Action(void);
01453 public:
01454   cIndexFileGenerator(const char *RecordingName);
01455   ~cIndexFileGenerator();
01456   };
01457 
01458 cIndexFileGenerator::cIndexFileGenerator(const char *RecordingName)
01459 :cThread("index file generator")
01460 ,recordingName(RecordingName)
01461 {
01462   Start();
01463 }
01464 
01465 cIndexFileGenerator::~cIndexFileGenerator()
01466 {
01467   Cancel(3);
01468 }
01469 
01470 void cIndexFileGenerator::Action(void)
01471 {
01472   bool IndexFileComplete = false;
01473   bool Rewind = false;
01474   cFileName FileName(recordingName, false);
01475   cUnbufferedFile *ReplayFile = FileName.Open();
01476   cRingBufferLinear Buffer(IFG_BUFFER_SIZE, MIN_TS_PACKETS_FOR_FRAME_DETECTOR * TS_SIZE);
01477   cPatPmtParser PatPmtParser;
01478   cFrameDetector FrameDetector;
01479   cIndexFile IndexFile(recordingName, true);
01480   int BufferChunks = KILOBYTE(1); // no need to read a lot at the beginning when parsing PAT/PMT
01481   off_t FileSize = 0;
01482   off_t FrameOffset = -1;
01483   Skins.QueueMessage(mtInfo, tr("Regenerating index file"));
01484   while (Running()) {
01485         // Rewind input file:
01486         if (Rewind) {
01487            ReplayFile = FileName.SetOffset(1);
01488            Buffer.Clear();
01489            Rewind = false;
01490            }
01491         // Process data:
01492         int Length;
01493         uchar *Data = Buffer.Get(Length);
01494         if (Data) {
01495            if (FrameDetector.Synced()) {
01496               // Step 3 - generate the index:
01497               if (TsPid(Data) == PATPID)
01498                  FrameOffset = FileSize; // the PAT/PMT is at the beginning of an I-frame
01499               int Processed = FrameDetector.Analyze(Data, Length);
01500               if (Processed > 0) {
01501                  if (FrameDetector.NewFrame()) {
01502                     IndexFile.Write(FrameDetector.IndependentFrame(), FileName.Number(), FrameOffset >= 0 ? FrameOffset : FileSize);
01503                     FrameOffset = -1;
01504                     }
01505                  FileSize += Processed;
01506                  Buffer.Del(Processed);
01507                  }
01508               }
01509            else if (PatPmtParser.Vpid()) {
01510               // Step 2 - sync FrameDetector:
01511               int Processed = FrameDetector.Analyze(Data, Length);
01512               if (Processed > 0) {
01513                  if (FrameDetector.Synced()) {
01514                     // Synced FrameDetector, so rewind for actual processing:
01515                     FrameDetector.Reset();
01516                     Rewind = true;
01517                     }
01518                  Buffer.Del(Processed);
01519                  }
01520               }
01521            else {
01522               // Step 1 - parse PAT/PMT:
01523               uchar *p = Data;
01524               while (Length >= TS_SIZE) {
01525                     int Pid = TsPid(p);
01526                     if (Pid == 0)
01527                        PatPmtParser.ParsePat(p, TS_SIZE);
01528                     else if (Pid == PatPmtParser.PmtPid())
01529                        PatPmtParser.ParsePmt(p, TS_SIZE);
01530                     Length -= TS_SIZE;
01531                     p += TS_SIZE;
01532                     if (PatPmtParser.Vpid()) {
01533                        // Found Vpid, so rewind to sync FrameDetector:
01534                        FrameDetector.SetPid(PatPmtParser.Vpid(), PatPmtParser.Vtype());
01535                        BufferChunks = IFG_BUFFER_SIZE;
01536                        Rewind = true;
01537                        break;
01538                        }
01539                     }
01540               Buffer.Del(p - Data);
01541               }
01542            }
01543         // Read data:
01544         else if (ReplayFile) {
01545            int Result = Buffer.Read(ReplayFile, BufferChunks);
01546            if (Result == 0) { // EOF
01547               ReplayFile = FileName.NextFile();
01548               FileSize = 0;
01549               FrameOffset = -1;
01550               }
01551            }
01552         // Recording has been processed:
01553         else {
01554            IndexFileComplete = true;
01555            break;
01556            }
01557         }
01558   // Delete the index file if the recording has not been processed entirely:
01559   if (IndexFileComplete)
01560      Skins.QueueMessage(mtInfo, tr("Index file regeneration complete"));
01561   else
01562      IndexFile.Delete();
01563 }
01564 
01565 // --- cIndexFile ------------------------------------------------------------
01566 
01567 #define INDEXFILESUFFIX     "/index"
01568 
01569 // The maximum time to wait before giving up while catching up on an index file:
01570 #define MAXINDEXCATCHUP   8 // seconds
01571 
01572 struct tIndexPes {
01573   uint32_t offset;
01574   uchar type;
01575   uchar number;
01576   uint16_t reserved;
01577   };
01578 
01579 struct tIndexTs {
01580   uint64_t offset:40; // up to 1TB per file (not using off_t here - must definitely be exactly 64 bit!)
01581   int reserved:7;     // reserved for future use
01582   int independent:1;  // marks frames that can be displayed by themselves (for trick modes)
01583   uint16_t number:16; // up to 64K files per recording
01584   tIndexTs(off_t Offset, bool Independent, uint16_t Number)
01585   {
01586     offset = Offset;
01587     reserved = 0;
01588     independent = Independent;
01589     number = Number;
01590   }
01591   };
01592 
01593 #define MAXWAITFORINDEXFILE     10 // max. time to wait for the regenerated index file (seconds)
01594 #define INDEXFILECHECKINTERVAL 500 // ms between checks for existence of the regenerated index file
01595 #define INDEXFILETESTINTERVAL   10 // ms between tests for the size of the index file in case of pausing live video
01596 
01597 cIndexFile::cIndexFile(const char *FileName, bool Record, bool IsPesRecording, bool PauseLive)
01598 :resumeFile(FileName, IsPesRecording)
01599 {
01600   f = -1;
01601   size = 0;
01602   last = -1;
01603   index = NULL;
01604   isPesRecording = IsPesRecording;
01605   indexFileGenerator = NULL;
01606   if (FileName) {
01607      fileName = IndexFileName(FileName, isPesRecording);
01608      if (!Record && PauseLive) {
01609         // Wait until the index file contains at least two frames:
01610         time_t tmax = time(NULL) + MAXWAITFORINDEXFILE;
01611         while (time(NULL) < tmax && FileSize(fileName) < off_t(2 * sizeof(tIndexTs)))
01612               cCondWait::SleepMs(INDEXFILETESTINTERVAL);
01613         }
01614      int delta = 0;
01615      if (!Record && access(fileName, R_OK) != 0) {
01616         // Index file doesn't exist, so try to regenerate it:
01617         if (!isPesRecording) { // sorry, can only do this for TS recordings
01618            resumeFile.Delete(); // just in case
01619            indexFileGenerator = new cIndexFileGenerator(FileName);
01620            // Wait until the index file exists:
01621            time_t tmax = time(NULL) + MAXWAITFORINDEXFILE;
01622            do {
01623               cCondWait::SleepMs(INDEXFILECHECKINTERVAL); // start with a sleep, to give it a head start
01624               } while (access(fileName, R_OK) != 0 && time(NULL) < tmax);
01625            }
01626         }
01627      if (access(fileName, R_OK) == 0) {
01628         struct stat buf;
01629         if (stat(fileName, &buf) == 0) {
01630            delta = int(buf.st_size % sizeof(tIndexTs));
01631            if (delta) {
01632               delta = sizeof(tIndexTs) - delta;
01633               esyslog("ERROR: invalid file size (%"PRId64") in '%s'", buf.st_size, *fileName);
01634               }
01635            last = int((buf.st_size + delta) / sizeof(tIndexTs) - 1);
01636            if (!Record && last >= 0) {
01637               size = last + 1;
01638               index = MALLOC(tIndexTs, size);
01639               if (index) {
01640                  f = open(fileName, O_RDONLY);
01641                  if (f >= 0) {
01642                     if (safe_read(f, index, size_t(buf.st_size)) != buf.st_size) {
01643                        esyslog("ERROR: can't read from file '%s'", *fileName);
01644                        free(index);
01645                        index = NULL;
01646                        close(f);
01647                        f = -1;
01648                        }
01649                     // we don't close f here, see CatchUp()!
01650                     else if (isPesRecording)
01651                        ConvertFromPes(index, size);
01652                     }
01653                  else
01654                     LOG_ERROR_STR(*fileName);
01655                  }
01656               else
01657                  esyslog("ERROR: can't allocate %zd bytes for index '%s'", size * sizeof(tIndexTs), *fileName);
01658               }
01659            }
01660         else
01661            LOG_ERROR;
01662         }
01663      else if (!Record)
01664         isyslog("missing index file %s", *fileName);
01665      if (Record) {
01666         if ((f = open(fileName, O_WRONLY | O_CREAT | O_APPEND, DEFFILEMODE)) >= 0) {
01667            if (delta) {
01668               esyslog("ERROR: padding index file with %d '0' bytes", delta);
01669               while (delta--)
01670                     writechar(f, 0);
01671               }
01672            }
01673         else
01674            LOG_ERROR_STR(*fileName);
01675         }
01676      }
01677 }
01678 
01679 cIndexFile::~cIndexFile()
01680 {
01681   if (f >= 0)
01682      close(f);
01683   free(index);
01684   delete indexFileGenerator;
01685 }
01686 
01687 cString cIndexFile::IndexFileName(const char *FileName, bool IsPesRecording)
01688 {
01689   return cString::sprintf("%s%s", FileName, IsPesRecording ? INDEXFILESUFFIX ".vdr" : INDEXFILESUFFIX);
01690 }
01691 
01692 void cIndexFile::ConvertFromPes(tIndexTs *IndexTs, int Count)
01693 {
01694   tIndexPes IndexPes;
01695   while (Count-- > 0) {
01696         memcpy(&IndexPes, IndexTs, sizeof(IndexPes));
01697         IndexTs->offset = IndexPes.offset;
01698         IndexTs->independent = IndexPes.type == 1; // I_FRAME
01699         IndexTs->number = IndexPes.number;
01700         IndexTs++;
01701         }
01702 }
01703 
01704 void cIndexFile::ConvertToPes(tIndexTs *IndexTs, int Count)
01705 {
01706   tIndexPes IndexPes;
01707   while (Count-- > 0) {
01708         IndexPes.offset = uint32_t(IndexTs->offset);
01709         IndexPes.type = uchar(IndexTs->independent ? 1 : 2); // I_FRAME : "not I_FRAME" (exact frame type doesn't matter)
01710         IndexPes.number = uchar(IndexTs->number);
01711         IndexPes.reserved = 0;
01712         memcpy(IndexTs, &IndexPes, sizeof(*IndexTs));
01713         IndexTs++;
01714         }
01715 }
01716 
01717 bool cIndexFile::CatchUp(int Index)
01718 {
01719   // returns true unless something really goes wrong, so that 'index' becomes NULL
01720   if (index && f >= 0) {
01721      cMutexLock MutexLock(&mutex);
01722      for (int i = 0; i <= MAXINDEXCATCHUP && (Index < 0 || Index >= last); i++) {
01723          struct stat buf;
01724          if (fstat(f, &buf) == 0) {
01725             if (time(NULL) - buf.st_mtime > MININDEXAGE) {
01726                // apparently the index file is not being written any more
01727                close(f);
01728                f = -1;
01729                break;
01730                }
01731             int newLast = int(buf.st_size / sizeof(tIndexTs) - 1);
01732             if (newLast > last) {
01733                int NewSize = size;
01734                if (NewSize <= newLast) {
01735                   NewSize *= 2;
01736                   if (NewSize <= newLast)
01737                      NewSize = newLast + 1;
01738                   }
01739                if (tIndexTs *NewBuffer = (tIndexTs *)realloc(index, NewSize * sizeof(tIndexTs))) {
01740                   size = NewSize;
01741                   index = NewBuffer;
01742                   int offset = (last + 1) * sizeof(tIndexTs);
01743                   int delta = (newLast - last) * sizeof(tIndexTs);
01744                   if (lseek(f, offset, SEEK_SET) == offset) {
01745                      if (safe_read(f, &index[last + 1], delta) != delta) {
01746                         esyslog("ERROR: can't read from index");
01747                         free(index);
01748                         index = NULL;
01749                         close(f);
01750                         f = -1;
01751                         break;
01752                         }
01753                      if (isPesRecording)
01754                         ConvertFromPes(&index[last + 1], newLast - last);
01755                      last = newLast;
01756                      }
01757                   else
01758                      LOG_ERROR_STR(*fileName);
01759                   }
01760                else {
01761                   esyslog("ERROR: can't realloc() index");
01762                   break;
01763                   }
01764                }
01765             }
01766          else
01767             LOG_ERROR_STR(*fileName);
01768          if (Index < last)
01769             break;
01770          cCondWait::SleepMs(1000);
01771          }
01772      }
01773   return index != NULL;
01774 }
01775 
01776 bool cIndexFile::Write(bool Independent, uint16_t FileNumber, off_t FileOffset)
01777 {
01778   if (f >= 0) {
01779      tIndexTs i(FileOffset, Independent, FileNumber);
01780      if (isPesRecording)
01781         ConvertToPes(&i, 1);
01782      if (safe_write(f, &i, sizeof(i)) < 0) {
01783         LOG_ERROR_STR(*fileName);
01784         close(f);
01785         f = -1;
01786         return false;
01787         }
01788      last++;
01789      }
01790   return f >= 0;
01791 }
01792 
01793 bool cIndexFile::Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent, int *Length)
01794 {
01795   if (CatchUp(Index)) {
01796      if (Index >= 0 && Index < last) {
01797         *FileNumber = index[Index].number;
01798         *FileOffset = index[Index].offset;
01799         if (Independent)
01800            *Independent = index[Index].independent;
01801         if (Length) {
01802            uint16_t fn = index[Index + 1].number;
01803            off_t fo = index[Index + 1].offset;
01804            if (fn == *FileNumber)
01805               *Length = int(fo - *FileOffset);
01806            else
01807               *Length = -1; // this means "everything up to EOF" (the buffer's Read function will act accordingly)
01808            }
01809         return true;
01810         }
01811      }
01812   return false;
01813 }
01814 
01815 int cIndexFile::GetNextIFrame(int Index, bool Forward, uint16_t *FileNumber, off_t *FileOffset, int *Length)
01816 {
01817   if (CatchUp()) {
01818      int d = Forward ? 1 : -1;
01819      for (;;) {
01820          Index += d;
01821          if (Index >= 0 && Index < last) {
01822             if (index[Index].independent) {
01823                uint16_t fn;
01824                if (!FileNumber)
01825                   FileNumber = &fn;
01826                off_t fo;
01827                if (!FileOffset)
01828                   FileOffset = &fo;
01829                *FileNumber = index[Index].number;
01830                *FileOffset = index[Index].offset;
01831                if (Length) {
01832                   // all recordings end with a non-independent frame, so the following should be safe:
01833                   uint16_t fn = index[Index + 1].number;
01834                   off_t fo = index[Index + 1].offset;
01835                   if (fn == *FileNumber)
01836                      *Length = int(fo - *FileOffset);
01837                   else {
01838                      esyslog("ERROR: 'I' frame at end of file #%d", *FileNumber);
01839                      *Length = -1;
01840                      }
01841                   }
01842                return Index;
01843                }
01844             }
01845          else
01846             break;
01847          }
01848      }
01849   return -1;
01850 }
01851 
01852 int cIndexFile::Get(uint16_t FileNumber, off_t FileOffset)
01853 {
01854   if (CatchUp()) {
01855      //TODO implement binary search!
01856      int i;
01857      for (i = 0; i < last; i++) {
01858          if (index[i].number > FileNumber || (index[i].number == FileNumber) && off_t(index[i].offset) >= FileOffset)
01859             break;
01860          }
01861      return i;
01862      }
01863   return -1;
01864 }
01865 
01866 bool cIndexFile::IsStillRecording()
01867 {
01868   return f >= 0;
01869 }
01870 
01871 void cIndexFile::Delete(void)
01872 {
01873   if (*fileName) {
01874      dsyslog("deleting index file '%s'", *fileName);
01875      if (f >= 0) {
01876         close(f);
01877         f = -1;
01878         }
01879      unlink(fileName);
01880      }
01881 }
01882 
01883 int cIndexFile::GetLength(const char *FileName, bool IsPesRecording)
01884 {
01885   struct stat buf;
01886   cString s = IndexFileName(FileName, IsPesRecording);
01887   if (*s && stat(s, &buf) == 0)
01888      return buf.st_size / (IsPesRecording ? sizeof(tIndexTs) : sizeof(tIndexPes));
01889   return -1;
01890 }
01891 
01892 bool GenerateIndex(const char *FileName) 
01893 {
01894   if (DirectoryOk(FileName)) {
01895      cRecording Recording(FileName);
01896      if (Recording.Name()) {
01897         if (!Recording.IsPesRecording()) {
01898            cString IndexFileName = AddDirectory(FileName, INDEXFILESUFFIX);
01899            unlink(IndexFileName);
01900            cIndexFileGenerator *IndexFileGenerator = new cIndexFileGenerator(FileName);
01901            while (IndexFileGenerator->Active())
01902                  cCondWait::SleepMs(INDEXFILECHECKINTERVAL);
01903            if (access(IndexFileName, R_OK) == 0)
01904               return true;
01905            else
01906               fprintf(stderr, "cannot create '%s'\n", *IndexFileName);
01907            }
01908         else
01909            fprintf(stderr, "'%s' is not a TS recording\n", FileName);
01910         }
01911      else
01912         fprintf(stderr, "'%s' is not a recording\n", FileName);
01913      }
01914   else
01915      fprintf(stderr, "'%s' is not a directory\n", FileName);
01916   return false;
01917 }
01918 
01919 // --- cFileName -------------------------------------------------------------
01920 
01921 #define MAXFILESPERRECORDINGPES 255
01922 #define RECORDFILESUFFIXPES     "/%03d.vdr"
01923 #define MAXFILESPERRECORDINGTS  65535
01924 #define RECORDFILESUFFIXTS      "/%05d.ts"
01925 #define RECORDFILESUFFIXLEN 20 // some additional bytes for safety...
01926 
01927 cFileName::cFileName(const char *FileName, bool Record, bool Blocking, bool IsPesRecording)
01928 {
01929   file = NULL;
01930   fileNumber = 0;
01931   record = Record;
01932   blocking = Blocking;
01933   isPesRecording = IsPesRecording;
01934   // Prepare the file name:
01935   fileName = MALLOC(char, strlen(FileName) + RECORDFILESUFFIXLEN);
01936   if (!fileName) {
01937      esyslog("ERROR: can't copy file name '%s'", fileName);
01938      return;
01939      }
01940   strcpy(fileName, FileName);
01941   pFileNumber = fileName + strlen(fileName);
01942   SetOffset(1);
01943 }
01944 
01945 cFileName::~cFileName()
01946 {
01947   Close();
01948   free(fileName);
01949 }
01950 
01951 bool cFileName::GetLastPatPmtVersions(int &PatVersion, int &PmtVersion)
01952 {
01953   if (fileName && !isPesRecording) {
01954      // Find the last recording file:
01955      int Number = 1;
01956      for (; Number <= MAXFILESPERRECORDINGTS + 1; Number++) { // +1 to correctly set Number in case there actually are that many files
01957          sprintf(pFileNumber, RECORDFILESUFFIXTS, Number);
01958          if (access(fileName, F_OK) != 0) { // file doesn't exist
01959             Number--;
01960             break;
01961             }
01962          }
01963      for (; Number > 0; Number--) {
01964          // Search for a PAT packet from the end of the file:
01965          cPatPmtParser PatPmtParser;
01966          sprintf(pFileNumber, RECORDFILESUFFIXTS, Number);
01967          int fd = open(fileName, O_RDONLY | O_LARGEFILE, DEFFILEMODE);
01968          if (fd >= 0) {
01969             off_t pos = lseek(fd, -TS_SIZE, SEEK_END);
01970             while (pos >= 0) {
01971                   // Read and parse the PAT/PMT:
01972                   uchar buf[TS_SIZE];
01973                   while (read(fd, buf, sizeof(buf)) == sizeof(buf)) {
01974                         if (buf[0] == TS_SYNC_BYTE) {
01975                            int Pid = TsPid(buf);
01976                            if (Pid == 0)
01977                               PatPmtParser.ParsePat(buf, sizeof(buf));
01978                            else if (Pid == PatPmtParser.PmtPid()) {
01979                               PatPmtParser.ParsePmt(buf, sizeof(buf));
01980                               if (PatPmtParser.GetVersions(PatVersion, PmtVersion)) {
01981                                  close(fd);
01982                                  return true;
01983                                  }
01984                               }
01985                            else
01986                               break; // PAT/PMT is always in one sequence
01987                            }
01988                         else
01989                            return false;
01990                         }
01991                   pos = lseek(fd, pos - TS_SIZE, SEEK_SET);
01992                   }
01993             close(fd);
01994             }
01995          else
01996             break;
01997          }
01998      }
01999   return false;
02000 }
02001 
02002 cUnbufferedFile *cFileName::Open(void)
02003 {
02004   if (!file) {
02005      int BlockingFlag = blocking ? 0 : O_NONBLOCK;
02006      if (record) {
02007         dsyslog("recording to '%s'", fileName);
02008         file = OpenVideoFile(fileName, O_RDWR | O_CREAT | O_LARGEFILE | BlockingFlag);
02009         if (!file)
02010            LOG_ERROR_STR(fileName);
02011         }
02012      else {
02013         if (access(fileName, R_OK) == 0) {
02014            dsyslog("playing '%s'", fileName);
02015            file = cUnbufferedFile::Create(fileName, O_RDONLY | O_LARGEFILE | BlockingFlag);
02016            if (!file)
02017               LOG_ERROR_STR(fileName);
02018            }
02019         else if (errno != ENOENT)
02020            LOG_ERROR_STR(fileName);
02021         }
02022      }
02023   return file;
02024 }
02025 
02026 void cFileName::Close(void)
02027 {
02028   if (file) {
02029      if (CloseVideoFile(file) < 0)
02030         LOG_ERROR_STR(fileName);
02031      file = NULL;
02032      }
02033 }
02034 
02035 cUnbufferedFile *cFileName::SetOffset(int Number, off_t Offset)
02036 {
02037   if (fileNumber != Number)
02038      Close();
02039   int MaxFilesPerRecording = isPesRecording ? MAXFILESPERRECORDINGPES : MAXFILESPERRECORDINGTS;
02040   if (0 < Number && Number <= MaxFilesPerRecording) {
02041      fileNumber = uint16_t(Number);
02042      sprintf(pFileNumber, isPesRecording ? RECORDFILESUFFIXPES : RECORDFILESUFFIXTS, fileNumber);
02043      if (record) {
02044         if (access(fileName, F_OK) == 0) {
02045            // file exists, check if it has non-zero size
02046            struct stat buf;
02047            if (stat(fileName, &buf) == 0) {
02048               if (buf.st_size != 0)
02049                  return SetOffset(Number + 1); // file exists and has non zero size, let's try next suffix
02050               else {
02051                  // zero size file, remove it
02052                  dsyslog("cFileName::SetOffset: removing zero-sized file %s", fileName);
02053                  unlink(fileName);
02054                  }
02055               }
02056            else
02057               return SetOffset(Number + 1); // error with fstat - should not happen, just to be on the safe side
02058            }
02059         else if (errno != ENOENT) { // something serious has happened
02060            LOG_ERROR_STR(fileName);
02061            return NULL;
02062            }
02063         // found a non existing file suffix
02064         }
02065      if (Open() >= 0) {
02066         if (!record && Offset >= 0 && file && file->Seek(Offset, SEEK_SET) != Offset) {
02067            LOG_ERROR_STR(fileName);
02068            return NULL;
02069            }
02070         }
02071      return file;
02072      }
02073   esyslog("ERROR: max number of files (%d) exceeded", MaxFilesPerRecording);
02074   return NULL;
02075 }
02076 
02077 off_t cFileName::MaxFileSize() {
02078   const int maxVideoFileSize = isPesRecording ? MAXVIDEOFILESIZEPES : MAXVIDEOFILESIZETS;
02079   const int setupMaxVideoFileSize = min(maxVideoFileSize, Setup.MaxVideoFileSize);
02080   const int maxFileNumber = isPesRecording ? 255 : 65535;
02081 
02082   const off_t smallFiles = (maxFileNumber * off_t(maxVideoFileSize) - 1024 * Setup.MaxRecordingSize)
02083                            / max(maxVideoFileSize - setupMaxVideoFileSize, 1);
02084 
02085   if (fileNumber <= smallFiles)
02086      return MEGABYTE(off_t(setupMaxVideoFileSize));
02087   
02088   return MEGABYTE(off_t(maxVideoFileSize));
02089 }
02090 
02091 cUnbufferedFile *cFileName::NextFile(void)
02092 {
02093   return SetOffset(fileNumber + 1);
02094 }
02095 
02096 // --- Index stuff -----------------------------------------------------------
02097 
02098 cString IndexToHMSF(int Index, bool WithFrame, double FramesPerSecond)
02099 {
02100   const char *Sign = "";
02101   if (Index < 0) {
02102      Index = -Index;
02103      Sign = "-";
02104      }
02105   double Seconds;
02106   int f = int(modf((Index + 0.5) / FramesPerSecond, &Seconds) * FramesPerSecond + 1);
02107   int s = int(Seconds);
02108   int m = s / 60 % 60;
02109   int h = s / 3600;
02110   s %= 60;
02111   return cString::sprintf(WithFrame ? "%s%d:%02d:%02d.%02d" : "%s%d:%02d:%02d", Sign, h, m, s, f);
02112 }
02113 
02114 int HMSFToIndex(const char *HMSF, double FramesPerSecond)
02115 {
02116   int h, m, s, f = 1;
02117   int n = sscanf(HMSF, "%d:%d:%d.%d", &h, &m, &s, &f);
02118   if (n == 1)
02119      return h - 1; // plain frame number
02120   if (n >= 3)
02121      return int(round((h * 3600 + m * 60 + s) * FramesPerSecond)) + f - 1;
02122   return 0;
02123 }
02124 
02125 int SecondsToFrames(int Seconds, double FramesPerSecond)
02126 {
02127   return int(round(Seconds * FramesPerSecond));
02128 }
02129 
02130 // --- ReadFrame -------------------------------------------------------------
02131 
02132 int ReadFrame(cUnbufferedFile *f, uchar *b, int Length, int Max)
02133 {
02134   if (Length == -1)
02135      Length = Max; // this means we read up to EOF (see cIndex)
02136   else if (Length > Max) {
02137      esyslog("ERROR: frame larger than buffer (%d > %d)", Length, Max);
02138      Length = Max;
02139      }
02140   int r = f->Read(b, Length);
02141   if (r < 0)
02142      LOG_ERROR;
02143   return r;
02144 }