vdr  2.0.4
recording.c
Go to the documentation of this file.
1 /*
2  * recording.c: Recording file handling
3  *
4  * See the main source file 'vdr.c' for copyright information and
5  * how to reach the author.
6  *
7  * $Id: recording.c 2.91.1.2 2013/08/21 13:58:35 kls Exp $
8  */
9 
10 #include "recording.h"
11 #include <ctype.h>
12 #include <dirent.h>
13 #include <errno.h>
14 #include <fcntl.h>
15 #define __STDC_FORMAT_MACROS // Required for format specifiers
16 #include <inttypes.h>
17 #include <math.h>
18 #include <stdio.h>
19 #include <string.h>
20 #include <sys/stat.h>
21 #include <unistd.h>
22 #include "channels.h"
23 #include "i18n.h"
24 #include "interface.h"
25 #include "remux.h"
26 #include "ringbuffer.h"
27 #include "skins.h"
28 #include "tools.h"
29 #include "videodir.h"
30 
31 #define SUMMARYFALLBACK
32 
33 #define RECEXT ".rec"
34 #define DELEXT ".del"
35 /* This was the original code, which works fine in a Linux only environment.
36  Unfortunately, because of Windows and its brain dead file system, we have
37  to use a more complicated approach, in order to allow users who have enabled
38  the --vfat command line option to see their recordings even if they forget to
39  enable --vfat when restarting VDR... Gee, do I hate Windows.
40  (kls 2002-07-27)
41 #define DATAFORMAT "%4d-%02d-%02d.%02d:%02d.%02d.%02d" RECEXT
42 #define NAMEFORMAT "%s/%s/" DATAFORMAT
43 */
44 #define DATAFORMATPES "%4d-%02d-%02d.%02d%*c%02d.%02d.%02d" RECEXT
45 #define NAMEFORMATPES "%s/%s/" "%4d-%02d-%02d.%02d.%02d.%02d.%02d" RECEXT
46 #define DATAFORMATTS "%4d-%02d-%02d.%02d.%02d.%d-%d" RECEXT
47 #define NAMEFORMATTS "%s/%s/" DATAFORMATTS
48 
49 #define RESUMEFILESUFFIX "/resume%s%s"
50 #ifdef SUMMARYFALLBACK
51 #define SUMMARYFILESUFFIX "/summary.vdr"
52 #endif
53 #define INFOFILESUFFIX "/info"
54 #define MARKSFILESUFFIX "/marks"
55 
56 #define SORTMODEFILE ".sort"
57 
58 #define MINDISKSPACE 1024 // MB
59 
60 #define REMOVECHECKDELTA 60 // seconds between checks for removing deleted files
61 #define DELETEDLIFETIME 300 // seconds after which a deleted recording will be actually removed
62 #define DISKCHECKDELTA 100 // seconds between checks for free disk space
63 #define REMOVELATENCY 10 // seconds to wait until next check after removing a file
64 #define MARKSUPDATEDELTA 10 // seconds between checks for updating editing marks
65 #define MININDEXAGE 3600 // seconds before an index file is considered no longer to be written
66 
67 #define MAX_LINK_LEVEL 6
68 
69 int DirectoryPathMax = PATH_MAX - 1;
70 int DirectoryNameMax = NAME_MAX;
71 bool DirectoryEncoding = false;
72 int InstanceId = 0;
73 
75 
76 // --- cRemoveDeletedRecordingsThread ----------------------------------------
77 
79 protected:
80  virtual void Action(void);
81 public:
83  };
84 
86 :cThread("remove deleted recordings", true)
87 {
88 }
89 
91 {
92  // Make sure only one instance of VDR does this:
93  cLockFile LockFile(VideoDirectory);
94  if (LockFile.Lock()) {
95  bool deleted = false;
96  cThreadLock DeletedRecordingsLock(&DeletedRecordings);
97  for (cRecording *r = DeletedRecordings.First(); r; ) {
99  return;
100  if (r->Deleted() && time(NULL) - r->Deleted() > DELETEDLIFETIME) {
101  cRecording *next = DeletedRecordings.Next(r);
102  r->Remove();
104  r = next;
105  deleted = true;
106  continue;
107  }
108  r = DeletedRecordings.Next(r);
109  }
110  if (deleted) {
111  const char *IgnoreFiles[] = { SORTMODEFILE, NULL };
112  RemoveEmptyVideoDirectories(IgnoreFiles);
113  }
114  }
115 }
116 
118 
119 // ---
120 
122 {
123  static time_t LastRemoveCheck = 0;
124  if (time(NULL) - LastRemoveCheck > REMOVECHECKDELTA) {
125  if (!RemoveDeletedRecordingsThread.Active()) {
126  cThreadLock DeletedRecordingsLock(&DeletedRecordings);
127  for (cRecording *r = DeletedRecordings.First(); r; r = DeletedRecordings.Next(r)) {
128  if (r->Deleted() && time(NULL) - r->Deleted() > DELETEDLIFETIME) {
129  RemoveDeletedRecordingsThread.Start();
130  break;
131  }
132  }
133  }
134  LastRemoveCheck = time(NULL);
135  }
136 }
137 
138 void AssertFreeDiskSpace(int Priority, bool Force)
139 {
140  static cMutex Mutex;
141  cMutexLock MutexLock(&Mutex);
142  // With every call to this function we try to actually remove
143  // a file, or mark a file for removal ("delete" it), so that
144  // it will get removed during the next call.
145  static time_t LastFreeDiskCheck = 0;
146  int Factor = (Priority == -1) ? 10 : 1;
147  if (Force || time(NULL) - LastFreeDiskCheck > DISKCHECKDELTA / Factor) {
149  // Make sure only one instance of VDR does this:
150  cLockFile LockFile(VideoDirectory);
151  if (!LockFile.Lock())
152  return;
153  // Remove the oldest file that has been "deleted":
154  isyslog("low disk space while recording, trying to remove a deleted recording...");
155  cThreadLock DeletedRecordingsLock(&DeletedRecordings);
156  if (DeletedRecordings.Count()) {
158  cRecording *r0 = NULL;
159  while (r) {
160  if (r->IsOnVideoDirectoryFileSystem()) { // only remove recordings that will actually increase the free video disk space
161  if (!r0 || r->Start() < r0->Start())
162  r0 = r;
163  }
164  r = DeletedRecordings.Next(r);
165  }
166  if (r0) {
167  if (r0->Remove())
168  LastFreeDiskCheck += REMOVELATENCY / Factor;
170  return;
171  }
172  }
173  else {
174  // DeletedRecordings was empty, so to be absolutely sure there are no
175  // deleted recordings we need to double check:
177  if (DeletedRecordings.Count())
178  return; // the next call will actually remove it
179  }
180  // No "deleted" files to remove, so let's see if we can delete a recording:
181  if (Priority > 0) {
182  isyslog("...no deleted recording found, trying to delete an old recording...");
183  cThreadLock RecordingsLock(&Recordings);
184  if (Recordings.Count()) {
185  cRecording *r = Recordings.First();
186  cRecording *r0 = NULL;
187  while (r) {
188  if (r->IsOnVideoDirectoryFileSystem()) { // only delete recordings that will actually increase the free video disk space
189  if (!r->IsEdited() && r->Lifetime() < MAXLIFETIME) { // edited recordings and recordings with MAXLIFETIME live forever
190  if ((r->Lifetime() == 0 && Priority > r->Priority()) || // the recording has no guaranteed lifetime and the new recording has higher priority
191  (r->Lifetime() > 0 && (time(NULL) - r->Start()) / SECSINDAY >= r->Lifetime())) { // the recording's guaranteed lifetime has expired
192  if (r0) {
193  if (r->Priority() < r0->Priority() || (r->Priority() == r0->Priority() && r->Start() < r0->Start()))
194  r0 = r; // in any case we delete the one with the lowest priority (or the older one in case of equal priorities)
195  }
196  else
197  r0 = r;
198  }
199  }
200  }
201  r = Recordings.Next(r);
202  }
203  if (r0 && r0->Delete()) {
204  Recordings.Del(r0);
205  return;
206  }
207  }
208  // Unable to free disk space, but there's nothing we can do about that...
209  isyslog("...no old recording found, giving up");
210  }
211  else
212  isyslog("...no deleted recording found, priority %d too low to trigger deleting an old recording", Priority);
213  Skins.QueueMessage(mtWarning, tr("Low disk space!"), 5, -1);
214  }
215  LastFreeDiskCheck = time(NULL);
216  }
217 }
218 
219 // --- cResumeFile -----------------------------------------------------------
220 
221 cResumeFile::cResumeFile(const char *FileName, bool IsPesRecording)
222 {
223  isPesRecording = IsPesRecording;
224  const char *Suffix = isPesRecording ? RESUMEFILESUFFIX ".vdr" : RESUMEFILESUFFIX;
225  fileName = MALLOC(char, strlen(FileName) + strlen(Suffix) + 1);
226  if (fileName) {
227  strcpy(fileName, FileName);
228  sprintf(fileName + strlen(fileName), Suffix, Setup.ResumeID ? "." : "", Setup.ResumeID ? *itoa(Setup.ResumeID) : "");
229  }
230  else
231  esyslog("ERROR: can't allocate memory for resume file name");
232 }
233 
235 {
236  free(fileName);
237 }
238 
240 {
241  int resume = -1;
242  if (fileName) {
243  struct stat st;
244  if (stat(fileName, &st) == 0) {
245  if ((st.st_mode & S_IWUSR) == 0) // no write access, assume no resume
246  return -1;
247  }
248  if (isPesRecording) {
249  int f = open(fileName, O_RDONLY);
250  if (f >= 0) {
251  if (safe_read(f, &resume, sizeof(resume)) != sizeof(resume)) {
252  resume = -1;
254  }
255  close(f);
256  }
257  else if (errno != ENOENT)
259  }
260  else {
261  FILE *f = fopen(fileName, "r");
262  if (f) {
263  cReadLine ReadLine;
264  char *s;
265  int line = 0;
266  while ((s = ReadLine.Read(f)) != NULL) {
267  ++line;
268  char *t = skipspace(s + 1);
269  switch (*s) {
270  case 'I': resume = atoi(t);
271  break;
272  default: ;
273  }
274  }
275  fclose(f);
276  }
277  else if (errno != ENOENT)
279  }
280  }
281  return resume;
282 }
283 
284 bool cResumeFile::Save(int Index)
285 {
286  if (fileName) {
287  if (isPesRecording) {
288  int f = open(fileName, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE);
289  if (f >= 0) {
290  if (safe_write(f, &Index, sizeof(Index)) < 0)
292  close(f);
294  return true;
295  }
296  }
297  else {
298  FILE *f = fopen(fileName, "w");
299  if (f) {
300  fprintf(f, "I %d\n", Index);
301  fclose(f);
303  }
304  else
306  return true;
307  }
308  }
309  return false;
310 }
311 
313 {
314  if (fileName) {
315  if (remove(fileName) == 0)
317  else if (errno != ENOENT)
319  }
320 }
321 
322 // --- cRecordingInfo --------------------------------------------------------
323 
324 cRecordingInfo::cRecordingInfo(const cChannel *Channel, const cEvent *Event)
325 {
326  channelID = Channel ? Channel->GetChannelID() : tChannelID::InvalidID;
327  channelName = Channel ? strdup(Channel->Name()) : NULL;
328  ownEvent = Event ? NULL : new cEvent(0);
329  event = ownEvent ? ownEvent : Event;
330  aux = NULL;
334  fileName = NULL;
335  if (Channel) {
336  // Since the EPG data's component records can carry only a single
337  // language code, let's see whether the channel's PID data has
338  // more information:
340  if (!Components)
341  Components = new cComponents;
342  for (int i = 0; i < MAXAPIDS; i++) {
343  const char *s = Channel->Alang(i);
344  if (*s) {
345  tComponent *Component = Components->GetComponent(i, 2, 3);
346  if (!Component)
347  Components->SetComponent(Components->NumComponents(), 2, 3, s, NULL);
348  else if (strlen(s) > strlen(Component->language))
349  strn0cpy(Component->language, s, sizeof(Component->language));
350  }
351  }
352  // There's no "multiple languages" for Dolby Digital tracks, but
353  // we do the same procedure here, too, in case there is no component
354  // information at all:
355  for (int i = 0; i < MAXDPIDS; i++) {
356  const char *s = Channel->Dlang(i);
357  if (*s) {
358  tComponent *Component = Components->GetComponent(i, 4, 0); // AC3 component according to the DVB standard
359  if (!Component)
360  Component = Components->GetComponent(i, 2, 5); // fallback "Dolby" component according to the "Premiere pseudo standard"
361  if (!Component)
362  Components->SetComponent(Components->NumComponents(), 2, 5, s, NULL);
363  else if (strlen(s) > strlen(Component->language))
364  strn0cpy(Component->language, s, sizeof(Component->language));
365  }
366  }
367  // The same applies to subtitles:
368  for (int i = 0; i < MAXSPIDS; i++) {
369  const char *s = Channel->Slang(i);
370  if (*s) {
371  tComponent *Component = Components->GetComponent(i, 3, 3);
372  if (!Component)
373  Components->SetComponent(Components->NumComponents(), 3, 3, s, NULL);
374  else if (strlen(s) > strlen(Component->language))
375  strn0cpy(Component->language, s, sizeof(Component->language));
376  }
377  }
378  if (Components != event->Components())
379  ((cEvent *)event)->SetComponents(Components);
380  }
381 }
382 
383 cRecordingInfo::cRecordingInfo(const char *FileName)
384 {
386  channelName = NULL;
387  ownEvent = new cEvent(0);
388  event = ownEvent;
389  aux = NULL;
393  fileName = strdup(cString::sprintf("%s%s", FileName, INFOFILESUFFIX));
394 }
395 
397 {
398  delete ownEvent;
399  free(aux);
400  free(channelName);
401  free(fileName);
402 }
403 
404 void cRecordingInfo::SetData(const char *Title, const char *ShortText, const char *Description)
405 {
406  if (!isempty(Title))
407  ((cEvent *)event)->SetTitle(Title);
408  if (!isempty(ShortText))
409  ((cEvent *)event)->SetShortText(ShortText);
410  if (!isempty(Description))
411  ((cEvent *)event)->SetDescription(Description);
412 }
413 
414 void cRecordingInfo::SetAux(const char *Aux)
415 {
416  free(aux);
417  aux = Aux ? strdup(Aux) : NULL;
418 }
419 
420 void cRecordingInfo::SetFramesPerSecond(double FramesPerSecond)
421 {
423 }
424 
425 bool cRecordingInfo::Read(FILE *f)
426 {
427  if (ownEvent) {
428  cReadLine ReadLine;
429  char *s;
430  int line = 0;
431  while ((s = ReadLine.Read(f)) != NULL) {
432  ++line;
433  char *t = skipspace(s + 1);
434  switch (*s) {
435  case 'C': {
436  char *p = strchr(t, ' ');
437  if (p) {
438  free(channelName);
439  channelName = strdup(compactspace(p));
440  *p = 0; // strips optional channel name
441  }
442  if (*t)
444  }
445  break;
446  case 'E': {
447  unsigned int EventID;
448  time_t StartTime;
449  int Duration;
450  unsigned int TableID = 0;
451  unsigned int Version = 0xFF;
452  int n = sscanf(t, "%u %ld %d %X %X", &EventID, &StartTime, &Duration, &TableID, &Version);
453  if (n >= 3 && n <= 5) {
454  ownEvent->SetEventID(EventID);
455  ownEvent->SetStartTime(StartTime);
456  ownEvent->SetDuration(Duration);
457  ownEvent->SetTableID(uchar(TableID));
458  ownEvent->SetVersion(uchar(Version));
459  }
460  }
461  break;
462  case 'F': framesPerSecond = atod(t);
463  break;
464  case 'L': lifetime = atoi(t);
465  break;
466  case 'P': priority = atoi(t);
467  break;
468  case '@': free(aux);
469  aux = strdup(t);
470  break;
471  case '#': break; // comments are ignored
472  default: if (!ownEvent->Parse(s)) {
473  esyslog("ERROR: EPG data problem in line %d", line);
474  return false;
475  }
476  break;
477  }
478  }
479  return true;
480  }
481  return false;
482 }
483 
484 bool cRecordingInfo::Write(FILE *f, const char *Prefix) const
485 {
486  if (channelID.Valid())
487  fprintf(f, "%sC %s%s%s\n", Prefix, *channelID.ToString(), channelName ? " " : "", channelName ? channelName : "");
488  event->Dump(f, Prefix, true);
489  fprintf(f, "%sF %s\n", Prefix, *dtoa(framesPerSecond, "%.10g"));
490  fprintf(f, "%sP %d\n", Prefix, priority);
491  fprintf(f, "%sL %d\n", Prefix, lifetime);
492  if (aux)
493  fprintf(f, "%s@ %s\n", Prefix, aux);
494  return true;
495 }
496 
498 {
499  bool Result = false;
500  if (fileName) {
501  FILE *f = fopen(fileName, "r");
502  if (f) {
503  if (Read(f))
504  Result = true;
505  else
506  esyslog("ERROR: EPG data problem in file %s", fileName);
507  fclose(f);
508  }
509  else if (errno != ENOENT)
511  }
512  return Result;
513 }
514 
515 bool cRecordingInfo::Write(void) const
516 {
517  bool Result = false;
518  if (fileName) {
519  cSafeFile f(fileName);
520  if (f.Open()) {
521  if (Write(f))
522  Result = true;
523  f.Close();
524  }
525  else
527  }
528  return Result;
529 }
530 
531 // --- cRecording ------------------------------------------------------------
532 
533 #define RESUME_NOT_INITIALIZED (-2)
534 
535 struct tCharExchange { char a; char b; };
537  { FOLDERDELIMCHAR, '/' },
538  { '/', FOLDERDELIMCHAR },
539  { ' ', '_' },
540  // backwards compatibility:
541  { '\'', '\'' },
542  { '\'', '\x01' },
543  { '/', '\x02' },
544  { 0, 0 }
545  };
546 
547 const char *InvalidChars = "\"\\/:*?|<>#";
548 
549 bool NeedsConversion(const char *p)
550 {
551  return DirectoryEncoding &&
552  (strchr(InvalidChars, *p) // characters that can't be part of a Windows file/directory name
553  || *p == '.' && (!*(p + 1) || *(p + 1) == FOLDERDELIMCHAR)); // Windows can't handle '.' at the end of file/directory names
554 }
555 
556 char *ExchangeChars(char *s, bool ToFileSystem)
557 {
558  char *p = s;
559  while (*p) {
560  if (DirectoryEncoding) {
561  // Some file systems can't handle all characters, so we
562  // have to take extra efforts to encode/decode them:
563  if (ToFileSystem) {
564  switch (*p) {
565  // characters that can be mapped to other characters:
566  case ' ': *p = '_'; break;
567  case FOLDERDELIMCHAR: *p = '/'; break;
568  case '/': *p = FOLDERDELIMCHAR; break;
569  // characters that have to be encoded:
570  default:
571  if (NeedsConversion(p)) {
572  int l = p - s;
573  if (char *NewBuffer = (char *)realloc(s, strlen(s) + 10)) {
574  s = NewBuffer;
575  p = s + l;
576  char buf[4];
577  sprintf(buf, "#%02X", (unsigned char)*p);
578  memmove(p + 2, p, strlen(p) + 1);
579  strncpy(p, buf, 3);
580  p += 2;
581  }
582  else
583  esyslog("ERROR: out of memory");
584  }
585  }
586  }
587  else {
588  switch (*p) {
589  // mapped characters:
590  case '_': *p = ' '; break;
591  case FOLDERDELIMCHAR: *p = '/'; break;
592  case '/': *p = FOLDERDELIMCHAR; break;
593  // encoded characters:
594  case '#': {
595  if (strlen(p) > 2 && isxdigit(*(p + 1)) && isxdigit(*(p + 2))) {
596  char buf[3];
597  sprintf(buf, "%c%c", *(p + 1), *(p + 2));
598  uchar c = uchar(strtol(buf, NULL, 16));
599  if (c) {
600  *p = c;
601  memmove(p + 1, p + 3, strlen(p) - 2);
602  }
603  }
604  }
605  break;
606  // backwards compatibility:
607  case '\x01': *p = '\''; break;
608  case '\x02': *p = '/'; break;
609  case '\x03': *p = ':'; break;
610  default: ;
611  }
612  }
613  }
614  else {
615  for (struct tCharExchange *ce = CharExchange; ce->a && ce->b; ce++) {
616  if (*p == (ToFileSystem ? ce->a : ce->b)) {
617  *p = ToFileSystem ? ce->b : ce->a;
618  break;
619  }
620  }
621  }
622  p++;
623  }
624  return s;
625 }
626 
627 char *LimitNameLengths(char *s, int PathMax, int NameMax)
628 {
629  // Limits the total length of the directory path in 's' to PathMax, and each
630  // individual directory name to NameMax. The lengths of characters that need
631  // conversion when using 's' as a file name are taken into account accordingly.
632  // If a directory name exceeds NameMax, it will be truncated. If the whole
633  // directory path exceeds PathMax, individual directory names will be shortened
634  // (from right to left) until the limit is met, or until the currently handled
635  // directory name consists of only a single character. All operations are performed
636  // directly on the given 's', which may become shorter (but never longer) than
637  // the original value.
638  // Returns a pointer to 's'.
639  int Length = strlen(s);
640  int PathLength = 0;
641  // Collect the resulting lengths of each character:
642  bool NameTooLong = false;
643  int8_t a[Length];
644  int n = 0;
645  int NameLength = 0;
646  for (char *p = s; *p; p++) {
647  if (*p == FOLDERDELIMCHAR) {
648  a[n] = -1; // FOLDERDELIMCHAR is a single character, neg. sign marks it
649  NameTooLong |= NameLength > NameMax;
650  NameLength = 0;
651  PathLength += 1;
652  }
653  else if (NeedsConversion(p)) {
654  a[n] = 3; // "#xx"
655  NameLength += 3;
656  PathLength += 3;
657  }
658  else {
659  int8_t l = Utf8CharLen(p);
660  a[n] = l;
661  NameLength += l;
662  PathLength += l;
663  while (l-- > 1) {
664  a[++n] = 0;
665  p++;
666  }
667  }
668  n++;
669  }
670  NameTooLong |= NameLength > NameMax;
671  // Limit names to NameMax:
672  if (NameTooLong) {
673  while (n > 0) {
674  // Calculate the length of the current name:
675  int NameLength = 0;
676  int i = n;
677  int b = i;
678  while (i-- > 0 && a[i] >= 0) {
679  NameLength += a[i];
680  b = i;
681  }
682  // Shorten the name if necessary:
683  if (NameLength > NameMax) {
684  int l = 0;
685  i = n;
686  while (i-- > 0 && a[i] >= 0) {
687  l += a[i];
688  if (NameLength - l <= NameMax) {
689  memmove(s + i, s + n, Length - n + 1);
690  memmove(a + i, a + n, Length - n + 1);
691  Length -= n - i;
692  PathLength -= l;
693  break;
694  }
695  }
696  }
697  // Switch to the next name:
698  n = b - 1;
699  }
700  }
701  // Limit path to PathMax:
702  n = Length;
703  while (PathLength > PathMax && n > 0) {
704  // Calculate how much to cut off the current name:
705  int i = n;
706  int b = i;
707  int l = 0;
708  while (--i > 0 && a[i - 1] >= 0) {
709  if (a[i] > 0) {
710  l += a[i];
711  b = i;
712  if (PathLength - l <= PathMax)
713  break;
714  }
715  }
716  // Shorten the name if necessary:
717  if (l > 0) {
718  memmove(s + b, s + n, Length - n + 1);
719  Length -= n - b;
720  PathLength -= l;
721  }
722  // Switch to the next name:
723  n = i - 1;
724  }
725  return s;
726 }
727 
728 cRecording::cRecording(cTimer *Timer, const cEvent *Event)
729 {
731  titleBuffer = NULL;
733  fileName = NULL;
734  name = NULL;
735  fileSizeMB = -1; // unknown
736  channel = Timer->Channel()->Number();
738  isPesRecording = false;
739  isOnVideoDirectoryFileSystem = -1; // unknown
741  numFrames = -1;
742  deleted = 0;
743  // set up the actual name:
744  const char *Title = Event ? Event->Title() : NULL;
745  const char *Subtitle = Event ? Event->ShortText() : NULL;
746  if (isempty(Title))
747  Title = Timer->Channel()->Name();
748  if (isempty(Subtitle))
749  Subtitle = " ";
750  const char *macroTITLE = strstr(Timer->File(), TIMERMACRO_TITLE);
751  const char *macroEPISODE = strstr(Timer->File(), TIMERMACRO_EPISODE);
752  if (macroTITLE || macroEPISODE) {
753  name = strdup(Timer->File());
754  name = strreplace(name, TIMERMACRO_TITLE, Title);
755  name = strreplace(name, TIMERMACRO_EPISODE, Subtitle);
756  // avoid blanks at the end:
757  int l = strlen(name);
758  while (l-- > 2) {
759  if (name[l] == ' ' && name[l - 1] != FOLDERDELIMCHAR)
760  name[l] = 0;
761  else
762  break;
763  }
764  if (Timer->IsSingleEvent()) {
765  Timer->SetFile(name); // this was an instant recording, so let's set the actual data
767  }
768  }
769  else if (Timer->IsSingleEvent() || !Setup.UseSubtitle)
770  name = strdup(Timer->File());
771  else
772  name = strdup(cString::sprintf("%s~%s", Timer->File(), Subtitle));
773  // substitute characters that would cause problems in file names:
774  strreplace(name, '\n', ' ');
775  start = Timer->StartTime();
776  priority = Timer->Priority();
777  lifetime = Timer->Lifetime();
778  // handle info:
779  info = new cRecordingInfo(Timer->Channel(), Event);
780  info->SetAux(Timer->Aux());
783 }
784 
785 cRecording::cRecording(const char *FileName)
786 {
788  fileSizeMB = -1; // unknown
789  channel = -1;
790  instanceId = -1;
791  priority = MAXPRIORITY; // assume maximum in case there is no info file
793  isPesRecording = false;
794  isOnVideoDirectoryFileSystem = -1; // unknown
796  numFrames = -1;
797  deleted = 0;
798  titleBuffer = NULL;
800  FileName = fileName = strdup(FileName);
801  if (*(fileName + strlen(fileName) - 1) == '/')
802  *(fileName + strlen(fileName) - 1) = 0;
803  if (strstr(FileName, VideoDirectory) == FileName)
804  FileName += strlen(VideoDirectory) + 1;
805  const char *p = strrchr(FileName, '/');
806 
807  name = NULL;
809  if (p) {
810  time_t now = time(NULL);
811  struct tm tm_r;
812  struct tm t = *localtime_r(&now, &tm_r); // this initializes the time zone in 't'
813  t.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting
814  if (7 == sscanf(p + 1, DATAFORMATTS, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &channel, &instanceId)
815  || 7 == sscanf(p + 1, DATAFORMATPES, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &priority, &lifetime)) {
816  t.tm_year -= 1900;
817  t.tm_mon--;
818  t.tm_sec = 0;
819  start = mktime(&t);
820  name = MALLOC(char, p - FileName + 1);
821  strncpy(name, FileName, p - FileName);
822  name[p - FileName] = 0;
823  name = ExchangeChars(name, false);
825  }
826  else
827  return;
828  GetResume();
829  // read an optional info file:
830  cString InfoFileName = cString::sprintf("%s%s", fileName, isPesRecording ? INFOFILESUFFIX ".vdr" : INFOFILESUFFIX);
831  FILE *f = fopen(InfoFileName, "r");
832  if (f) {
833  if (!info->Read(f))
834  esyslog("ERROR: EPG data problem in file %s", *InfoFileName);
835  else if (!isPesRecording) {
839  }
840  fclose(f);
841  }
842  else if (errno == ENOENT)
844  else
845  LOG_ERROR_STR(*InfoFileName);
846 #ifdef SUMMARYFALLBACK
847  // fall back to the old 'summary.vdr' if there was no 'info.vdr':
848  if (isempty(info->Title())) {
849  cString SummaryFileName = cString::sprintf("%s%s", fileName, SUMMARYFILESUFFIX);
850  FILE *f = fopen(SummaryFileName, "r");
851  if (f) {
852  int line = 0;
853  char *data[3] = { NULL };
854  cReadLine ReadLine;
855  char *s;
856  while ((s = ReadLine.Read(f)) != NULL) {
857  if (*s || line > 1) {
858  if (data[line]) {
859  int len = strlen(s);
860  len += strlen(data[line]) + 1;
861  if (char *NewBuffer = (char *)realloc(data[line], len + 1)) {
862  data[line] = NewBuffer;
863  strcat(data[line], "\n");
864  strcat(data[line], s);
865  }
866  else
867  esyslog("ERROR: out of memory");
868  }
869  else
870  data[line] = strdup(s);
871  }
872  else
873  line++;
874  }
875  fclose(f);
876  if (!data[2]) {
877  data[2] = data[1];
878  data[1] = NULL;
879  }
880  else if (data[1] && data[2]) {
881  // if line 1 is too long, it can't be the short text,
882  // so assume the short text is missing and concatenate
883  // line 1 and line 2 to be the long text:
884  int len = strlen(data[1]);
885  if (len > 80) {
886  if (char *NewBuffer = (char *)realloc(data[1], len + 1 + strlen(data[2]) + 1)) {
887  data[1] = NewBuffer;
888  strcat(data[1], "\n");
889  strcat(data[1], data[2]);
890  free(data[2]);
891  data[2] = data[1];
892  data[1] = NULL;
893  }
894  else
895  esyslog("ERROR: out of memory");
896  }
897  }
898  info->SetData(data[0], data[1], data[2]);
899  for (int i = 0; i < 3; i ++)
900  free(data[i]);
901  }
902  else if (errno != ENOENT)
903  LOG_ERROR_STR(*SummaryFileName);
904  }
905 #endif
906  }
907 }
908 
910 {
911  free(titleBuffer);
912  free(sortBufferName);
913  free(sortBufferTime);
914  free(fileName);
915  free(name);
916  delete info;
917 }
918 
919 char *cRecording::StripEpisodeName(char *s, bool Strip)
920 {
921  char *t = s, *s1 = NULL, *s2 = NULL;
922  while (*t) {
923  if (*t == '/') {
924  if (s1) {
925  if (s2)
926  s1 = s2;
927  s2 = t;
928  }
929  else
930  s1 = t;
931  }
932  t++;
933  }
934  if (s1 && s2) {
935  // To have folders sorted before plain recordings, the '/' s1 points to
936  // is replaced by the character '1'. All other slashes will be replaced
937  // by '0' in SortName() (see below), which will result in the desired
938  // sequence:
939  *s1 = '1';
940  if (Strip) {
941  s1++;
942  memmove(s1, s2, t - s2 + 1);
943  }
944  }
945  return s;
946 }
947 
948 char *cRecording::SortName(void) const
949 {
951  if (!*sb) {
952  char *s = strdup(FileName() + strlen(VideoDirectory));
955  strreplace(s, '/', '0'); // some locales ignore '/' when sorting
956  int l = strxfrm(NULL, s, 0) + 1;
957  *sb = MALLOC(char, l);
958  strxfrm(*sb, s, l);
959  free(s);
960  }
961  return *sb;
962 }
963 
965 {
968 }
969 
970 int cRecording::GetResume(void) const
971 {
973  cResumeFile ResumeFile(FileName(), isPesRecording);
974  resume = ResumeFile.Read();
975  }
976  return resume;
977 }
978 
979 int cRecording::Compare(const cListObject &ListObject) const
980 {
981  cRecording *r = (cRecording *)&ListObject;
982  return strcasecmp(SortName(), r->SortName());
983 }
984 
985 const char *cRecording::FileName(void) const
986 {
987  if (!fileName) {
988  struct tm tm_r;
989  struct tm *t = localtime_r(&start, &tm_r);
990  const char *fmt = isPesRecording ? NAMEFORMATPES : NAMEFORMATTS;
991  int ch = isPesRecording ? priority : channel;
992  int ri = isPesRecording ? lifetime : instanceId;
993  char *Name = LimitNameLengths(strdup(name), DirectoryPathMax - strlen(VideoDirectory) - 1 - 42, DirectoryNameMax); // 42 = length of an actual recording directory name (generated with DATAFORMATTS) plus some reserve
994  if (strcmp(Name, name) != 0)
995  dsyslog("recording file name '%s' truncated to '%s'", name, Name);
996  Name = ExchangeChars(Name, true);
997  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));
998  free(Name);
999  }
1000  return fileName;
1001 }
1002 
1003 const char *cRecording::Title(char Delimiter, bool NewIndicator, int Level) const
1004 {
1005  char New = NewIndicator && IsNew() ? '*' : ' ';
1006  free(titleBuffer);
1007  titleBuffer = NULL;
1008  if (Level < 0 || Level == HierarchyLevels()) {
1009  struct tm tm_r;
1010  struct tm *t = localtime_r(&start, &tm_r);
1011  char *s;
1012  if (Level > 0 && (s = strrchr(name, FOLDERDELIMCHAR)) != NULL)
1013  s++;
1014  else
1015  s = name;
1016  cString Length("");
1017  if (NewIndicator) {
1018  int Minutes = max(0, (LengthInSeconds() + 30) / 60);
1019  Length = cString::sprintf("%c%d:%02d",
1020  Delimiter,
1021  Minutes / 60,
1022  Minutes % 60
1023  );
1024  }
1025  titleBuffer = strdup(cString::sprintf("%02d.%02d.%02d%c%02d:%02d%s%c%c%s",
1026  t->tm_mday,
1027  t->tm_mon + 1,
1028  t->tm_year % 100,
1029  Delimiter,
1030  t->tm_hour,
1031  t->tm_min,
1032  *Length,
1033  New,
1034  Delimiter,
1035  s));
1036  // let's not display a trailing FOLDERDELIMCHAR:
1037  if (!NewIndicator)
1039  s = &titleBuffer[strlen(titleBuffer) - 1];
1040  if (*s == FOLDERDELIMCHAR)
1041  *s = 0;
1042  }
1043  else if (Level < HierarchyLevels()) {
1044  const char *s = name;
1045  const char *p = s;
1046  while (*++s) {
1047  if (*s == FOLDERDELIMCHAR) {
1048  if (Level--)
1049  p = s + 1;
1050  else
1051  break;
1052  }
1053  }
1054  titleBuffer = MALLOC(char, s - p + 3);
1055  *titleBuffer = Delimiter;
1056  *(titleBuffer + 1) = Delimiter;
1057  strn0cpy(titleBuffer + 2, p, s - p + 1);
1058  }
1059  else
1060  return "";
1061  return titleBuffer;
1062 }
1063 
1064 const char *cRecording::PrefixFileName(char Prefix)
1065 {
1066  cString p = PrefixVideoFileName(FileName(), Prefix);
1067  if (*p) {
1068  free(fileName);
1069  fileName = strdup(p);
1070  return fileName;
1071  }
1072  return NULL;
1073 }
1074 
1075 const char *cRecording::UpdateFileName(const char *FileName)
1076 {
1077  if (FileName && *FileName) {
1078  free(fileName);
1079  fileName = strdup(FileName);
1080  return fileName;
1081  }
1082  return NULL;
1083 }
1084 
1086 {
1087  const char *s = name;
1088  int level = 0;
1089  while (*++s) {
1090  if (*s == FOLDERDELIMCHAR)
1091  level++;
1092  }
1093  return level;
1094 }
1095 
1096 bool cRecording::IsEdited(void) const
1097 {
1098  const char *s = strrchr(name, FOLDERDELIMCHAR);
1099  s = !s ? name : s + 1;
1100  return *s == '%';
1101 }
1102 
1104 {
1108 }
1109 
1111 {
1112  info->Read();
1113  priority = info->priority;
1114  lifetime = info->lifetime;
1116 }
1117 
1119 {
1120  cString InfoFileName = cString::sprintf("%s%s", fileName, isPesRecording ? INFOFILESUFFIX ".vdr" : INFOFILESUFFIX);
1121  FILE *f = fopen(InfoFileName, "w");
1122  if (f) {
1123  info->Write(f);
1124  fclose(f);
1125  }
1126  else
1127  LOG_ERROR_STR(*InfoFileName);
1128  return true;
1129 }
1130 
1131 void cRecording::SetStartTime(time_t Start)
1132 {
1133  start = Start;
1134  free(fileName);
1135  fileName = NULL;
1136 }
1137 
1139 {
1140  bool result = true;
1141  char *NewName = strdup(FileName());
1142  char *ext = strrchr(NewName, '.');
1143  if (ext && strcmp(ext, RECEXT) == 0) {
1144  strncpy(ext, DELEXT, strlen(ext));
1145  if (access(NewName, F_OK) == 0) {
1146  // the new name already exists, so let's remove that one first:
1147  isyslog("removing recording '%s'", NewName);
1148  RemoveVideoFile(NewName);
1149  }
1150  isyslog("deleting recording '%s'", FileName());
1151  if (access(FileName(), F_OK) == 0) {
1152  result = RenameVideoFile(FileName(), NewName);
1154  }
1155  else {
1156  isyslog("recording '%s' vanished", FileName());
1157  result = true; // well, we were going to delete it, anyway
1158  }
1159  }
1160  free(NewName);
1161  return result;
1162 }
1163 
1165 {
1166  // let's do a final safety check here:
1167  if (!endswith(FileName(), DELEXT)) {
1168  esyslog("attempt to remove recording %s", FileName());
1169  return false;
1170  }
1171  isyslog("removing recording %s", FileName());
1172  return RemoveVideoFile(FileName());
1173 }
1174 
1176 {
1177  bool result = true;
1178  char *NewName = strdup(FileName());
1179  char *ext = strrchr(NewName, '.');
1180  if (ext && strcmp(ext, DELEXT) == 0) {
1181  strncpy(ext, RECEXT, strlen(ext));
1182  if (access(NewName, F_OK) == 0) {
1183  // the new name already exists, so let's not remove that one:
1184  esyslog("ERROR: attempt to undelete '%s', while recording '%s' exists", FileName(), NewName);
1185  result = false;
1186  }
1187  else {
1188  isyslog("undeleting recording '%s'", FileName());
1189  if (access(FileName(), F_OK) == 0)
1190  result = RenameVideoFile(FileName(), NewName);
1191  else {
1192  isyslog("deleted recording '%s' vanished", FileName());
1193  result = false;
1194  }
1195  }
1196  }
1197  free(NewName);
1198  return result;
1199 }
1200 
1201 void cRecording::ResetResume(void) const
1202 {
1204 }
1205 
1206 int cRecording::NumFrames(void) const
1207 {
1208  if (numFrames < 0) {
1211  return nf; // check again later for ongoing recordings
1212  numFrames = nf;
1213  }
1214  return numFrames;
1215 }
1216 
1218 {
1219  int nf = NumFrames();
1220  if (nf >= 0)
1221  return int(nf / FramesPerSecond());
1222  return -1;
1223 }
1224 
1225 int cRecording::FileSizeMB(void) const
1226 {
1227  if (fileSizeMB < 0) {
1228  int fs = DirSizeMB(FileName());
1230  return fs; // check again later for ongoing recordings
1231  fileSizeMB = fs;
1232  }
1233  return fileSizeMB;
1234 }
1235 
1236 // --- cRecordings -----------------------------------------------------------
1237 
1239 
1240 char *cRecordings::updateFileName = NULL;
1241 
1243 :cThread("video directory scanner")
1244 {
1245  deleted = Deleted;
1246  lastUpdate = 0;
1247  state = 0;
1248 }
1249 
1251 {
1252  Cancel(3);
1253 }
1254 
1256 {
1257  Refresh();
1258 }
1259 
1261 {
1262  if (!updateFileName)
1263  updateFileName = strdup(AddDirectory(VideoDirectory, ".update"));
1264  return updateFileName;
1265 }
1266 
1267 void cRecordings::Refresh(bool Foreground)
1268 {
1269  lastUpdate = time(NULL); // doing this first to make sure we don't miss anything
1270  Lock();
1271  Clear();
1272  ChangeState();
1273  Unlock();
1274  ScanVideoDir(VideoDirectory, Foreground);
1275 }
1276 
1277 void cRecordings::ScanVideoDir(const char *DirName, bool Foreground, int LinkLevel)
1278 {
1279  cReadDir d(DirName);
1280  struct dirent *e;
1281  while ((Foreground || Running()) && (e = d.Next()) != NULL) {
1282  cString buffer = AddDirectory(DirName, e->d_name);
1283  struct stat st;
1284  if (lstat(buffer, &st) == 0) {
1285  int Link = 0;
1286  if (S_ISLNK(st.st_mode)) {
1287  if (LinkLevel > MAX_LINK_LEVEL) {
1288  isyslog("max link level exceeded - not scanning %s", *buffer);
1289  continue;
1290  }
1291  Link = 1;
1292  if (stat(buffer, &st) != 0)
1293  continue;
1294  }
1295  if (S_ISDIR(st.st_mode)) {
1296  if (endswith(buffer, deleted ? DELEXT : RECEXT)) {
1297  cRecording *r = new cRecording(buffer);
1298  if (r->Name()) {
1299  r->NumFrames(); // initializes the numFrames member
1300  r->FileSizeMB(); // initializes the fileSizeMB member
1301  if (deleted)
1302  r->deleted = time(NULL);
1303  Lock();
1304  Add(r);
1305  ChangeState();
1306  Unlock();
1307  }
1308  else
1309  delete r;
1310  }
1311  else
1312  ScanVideoDir(buffer, Foreground, LinkLevel + Link);
1313  }
1314  }
1315  }
1316 }
1317 
1319 {
1320  int NewState = state;
1321  bool Result = State != NewState;
1322  State = state;
1323  return Result;
1324 }
1325 
1327 {
1328  bool needsUpdate = NeedsUpdate();
1330  if (!needsUpdate)
1331  lastUpdate = time(NULL); // make sure we don't trigger ourselves
1332 }
1333 
1335 {
1336  time_t lastModified = LastModifiedTime(UpdateFileName());
1337  if (lastModified > time(NULL))
1338  return false; // somebody's clock isn't running correctly
1339  return lastUpdate < lastModified;
1340 }
1341 
1342 bool cRecordings::Update(bool Wait)
1343 {
1344  if (Wait) {
1345  Refresh(true);
1346  return Count() > 0;
1347  }
1348  else
1349  Start();
1350  return false;
1351 }
1352 
1353 cRecording *cRecordings::GetByName(const char *FileName)
1354 {
1355  if (FileName) {
1356  for (cRecording *recording = First(); recording; recording = Next(recording)) {
1357  if (strcmp(recording->FileName(), FileName) == 0)
1358  return recording;
1359  }
1360  }
1361  return NULL;
1362 }
1363 
1364 void cRecordings::AddByName(const char *FileName, bool TriggerUpdate)
1365 {
1366  LOCK_THREAD;
1367  cRecording *recording = GetByName(FileName);
1368  if (!recording) {
1369  recording = new cRecording(FileName);
1370  Add(recording);
1371  ChangeState();
1372  if (TriggerUpdate)
1373  TouchUpdate();
1374  }
1375 }
1376 
1377 void cRecordings::DelByName(const char *FileName, bool RemoveRecording)
1378 {
1379  LOCK_THREAD;
1380  cRecording *recording = GetByName(FileName);
1381  if (recording) {
1382  cThreadLock DeletedRecordingsLock(&DeletedRecordings);
1383  Del(recording, false);
1384  char *ext = strrchr(recording->fileName, '.');
1385  if (ext && RemoveRecording) {
1386  strncpy(ext, DELEXT, strlen(ext));
1387  if (access(recording->FileName(), F_OK) == 0) {
1388  recording->deleted = time(NULL);
1389  DeletedRecordings.Add(recording);
1390  recording = NULL; // to prevent it from being deleted below
1391  }
1392  }
1393  delete recording;
1394  ChangeState();
1395  TouchUpdate();
1396  }
1397 }
1398 
1399 void cRecordings::UpdateByName(const char *FileName)
1400 {
1401  LOCK_THREAD;
1402  cRecording *recording = GetByName(FileName);
1403  if (recording)
1404  recording->ReadInfo();
1405 }
1406 
1408 {
1409  int size = 0;
1410  LOCK_THREAD;
1411  for (cRecording *recording = First(); recording; recording = Next(recording)) {
1412  int FileSizeMB = recording->FileSizeMB();
1413  if (FileSizeMB > 0 && recording->IsOnVideoDirectoryFileSystem())
1414  size += FileSizeMB;
1415  }
1416  return size;
1417 }
1418 
1420 {
1421  int size = 0;
1422  int length = 0;
1423  LOCK_THREAD;
1424  for (cRecording *recording = First(); recording; recording = Next(recording)) {
1425  if (recording->IsOnVideoDirectoryFileSystem()) {
1426  int FileSizeMB = recording->FileSizeMB();
1427  if (FileSizeMB > 0) {
1428  int LengthInSeconds = recording->LengthInSeconds();
1429  if (LengthInSeconds > 0) {
1430  size += FileSizeMB;
1431  length += LengthInSeconds;
1432  }
1433  }
1434  }
1435  }
1436  return (size && length) ? double(size) * 60 / length : -1;
1437 }
1438 
1439 void cRecordings::ResetResume(const char *ResumeFileName)
1440 {
1441  LOCK_THREAD;
1442  for (cRecording *recording = First(); recording; recording = Next(recording)) {
1443  if (!ResumeFileName || strncmp(ResumeFileName, recording->FileName(), strlen(recording->FileName())) == 0)
1444  recording->ResetResume();
1445  }
1446  ChangeState();
1447 }
1448 
1450 {
1451  LOCK_THREAD;
1452  for (cRecording *recording = First(); recording; recording = Next(recording))
1453  recording->ClearSortName();
1454 }
1455 
1456 // --- cMark -----------------------------------------------------------------
1457 
1460 
1461 cMark::cMark(int Position, const char *Comment, double FramesPerSecond)
1462 {
1463  position = Position;
1464  comment = Comment;
1465  framesPerSecond = FramesPerSecond;
1466 }
1467 
1469 {
1470 }
1471 
1473 {
1474  return cString::sprintf("%s%s%s\n", *IndexToHMSF(position, true, framesPerSecond), Comment() ? " " : "", Comment() ? Comment() : "");
1475 }
1476 
1477 bool cMark::Parse(const char *s)
1478 {
1479  comment = NULL;
1482  const char *p = strchr(s, ' ');
1483  if (p) {
1484  p = skipspace(p);
1485  if (*p)
1486  comment = strdup(p);
1487  }
1488  return true;
1489 }
1490 
1491 bool cMark::Save(FILE *f)
1492 {
1493  return fprintf(f, "%s", *ToText()) > 0;
1494 }
1495 
1496 // --- cMarks ----------------------------------------------------------------
1497 
1498 bool cMarks::Load(const char *RecordingFileName, double FramesPerSecond, bool IsPesRecording)
1499 {
1500  recordingFileName = RecordingFileName;
1501  fileName = AddDirectory(RecordingFileName, IsPesRecording ? MARKSFILESUFFIX ".vdr" : MARKSFILESUFFIX);
1502  framesPerSecond = FramesPerSecond;
1503  isPesRecording = IsPesRecording;
1504  nextUpdate = 0;
1505  lastFileTime = -1; // the first call to Load() must take place!
1506  lastChange = 0;
1507  return Update();
1508 }
1509 
1510 bool cMarks::Update(void)
1511 {
1512  time_t t = time(NULL);
1513  if (t > nextUpdate) {
1514  time_t LastModified = LastModifiedTime(fileName);
1515  if (LastModified != lastFileTime) // change detected, or first run
1516  lastChange = LastModified > 0 ? LastModified : t;
1517  int d = t - lastChange;
1518  if (d < 60)
1519  d = 1; // check frequently if the file has just been modified
1520  else if (d < 3600)
1521  d = 10; // older files are checked less frequently
1522  else
1523  d /= 360; // phase out checking for very old files
1524  nextUpdate = t + d;
1525  if (LastModified != lastFileTime) { // change detected, or first run
1526  lastFileTime = LastModified;
1527  if (lastFileTime == t)
1528  lastFileTime--; // make sure we don't miss updates in the remaining second
1529  cMutexLock MutexLock(&MutexMarkFramesPerSecond);
1532  Align();
1533  Sort();
1534  return true;
1535  }
1536  }
1537  }
1538  return false;
1539 }
1540 
1541 bool cMarks::Save(void)
1542 {
1543  if (cConfig<cMark>::Save()) {
1545  return true;
1546  }
1547  return false;
1548 }
1549 
1550 void cMarks::Align(void)
1551 {
1552  cIndexFile IndexFile(recordingFileName, false, isPesRecording);
1553  for (cMark *m = First(); m; m = Next(m)) {
1554  int p = IndexFile.GetClosestIFrame(m->Position());
1555  if (int d = m->Position() - p) {
1556  isyslog("aligned editing mark %s to %s (off by %d frame%s)", *IndexToHMSF(m->Position(), true, framesPerSecond), *IndexToHMSF(p, true, framesPerSecond), d, abs(d) > 1 ? "s" : "");
1557  m->SetPosition(p);
1558  }
1559  }
1560 }
1561 
1562 void cMarks::Sort(void)
1563 {
1564  for (cMark *m1 = First(); m1; m1 = Next(m1)) {
1565  for (cMark *m2 = Next(m1); m2; m2 = Next(m2)) {
1566  if (m2->Position() < m1->Position()) {
1567  swap(m1->position, m2->position);
1568  swap(m1->comment, m2->comment);
1569  }
1570  }
1571  }
1572 }
1573 
1574 void cMarks::Add(int Position)
1575 {
1576  cConfig<cMark>::Add(new cMark(Position, NULL, framesPerSecond));
1577  Sort();
1578 }
1579 
1580 cMark *cMarks::Get(int Position)
1581 {
1582  for (cMark *mi = First(); mi; mi = Next(mi)) {
1583  if (mi->Position() == Position)
1584  return mi;
1585  }
1586  return NULL;
1587 }
1588 
1589 cMark *cMarks::GetPrev(int Position)
1590 {
1591  for (cMark *mi = Last(); mi; mi = Prev(mi)) {
1592  if (mi->Position() < Position)
1593  return mi;
1594  }
1595  return NULL;
1596 }
1597 
1598 cMark *cMarks::GetNext(int Position)
1599 {
1600  for (cMark *mi = First(); mi; mi = Next(mi)) {
1601  if (mi->Position() > Position)
1602  return mi;
1603  }
1604  return NULL;
1605 }
1606 
1608 {
1609  cMark *BeginMark = EndMark ? Next(EndMark) : First();
1610  if (BeginMark) {
1611  while (cMark *NextMark = Next(BeginMark)) {
1612  if (BeginMark->Position() == NextMark->Position()) { // skip Begin/End at the same position
1613  if (!(BeginMark = Next(NextMark)))
1614  break;
1615  }
1616  else
1617  break;
1618  }
1619  }
1620  return BeginMark;
1621 }
1622 
1624 {
1625  if (!BeginMark)
1626  return NULL;
1627  cMark *EndMark = Next(BeginMark);
1628  if (EndMark) {
1629  while (cMark *NextMark = Next(EndMark)) {
1630  if (EndMark->Position() == NextMark->Position()) { // skip End/Begin at the same position
1631  if (!(EndMark = Next(NextMark)))
1632  break;
1633  }
1634  else
1635  break;
1636  }
1637  }
1638  return EndMark;
1639 }
1640 
1642 {
1643  int NumSequences = 0;
1644  if (cMark *BeginMark = GetNextBegin()) {
1645  while (cMark *EndMark = GetNextEnd(BeginMark)) {
1646  NumSequences++;
1647  BeginMark = GetNextBegin(EndMark);
1648  }
1649  if (BeginMark) {
1650  NumSequences++; // the last sequence had no actual "end" mark
1651  if (NumSequences == 1 && BeginMark->Position() == 0)
1652  NumSequences = 0; // there is only one actual "begin" mark at offset zero, and no actual "end" mark
1653  }
1654  }
1655  return NumSequences;
1656 }
1657 
1658 // --- cRecordingUserCommand -------------------------------------------------
1659 
1660 const char *cRecordingUserCommand::command = NULL;
1661 
1662 void cRecordingUserCommand::InvokeCommand(const char *State, const char *RecordingFileName, const char *SourceFileName)
1663 {
1664  if (command) {
1665  cString cmd;
1666  if (SourceFileName)
1667  cmd = cString::sprintf("%s %s \"%s\" \"%s\"", command, State, *strescape(RecordingFileName, "\\\"$"), *strescape(SourceFileName, "\\\"$"));
1668  else
1669  cmd = cString::sprintf("%s %s \"%s\"", command, State, *strescape(RecordingFileName, "\\\"$"));
1670  isyslog("executing '%s'", *cmd);
1671  SystemExec(cmd);
1672  }
1673 }
1674 
1675 // --- cIndexFileGenerator ---------------------------------------------------
1676 
1677 #define IFG_BUFFER_SIZE KILOBYTE(100)
1678 
1680 private:
1682 protected:
1683  virtual void Action(void);
1684 public:
1685  cIndexFileGenerator(const char *RecordingName);
1687  };
1688 
1690 :cThread("index file generator")
1691 ,recordingName(RecordingName)
1692 {
1693  Start();
1694 }
1695 
1697 {
1698  Cancel(3);
1699 }
1700 
1702 {
1703  bool IndexFileComplete = false;
1704  bool IndexFileWritten = false;
1705  bool Rewind = false;
1706  cFileName FileName(recordingName, false);
1707  cUnbufferedFile *ReplayFile = FileName.Open();
1709  cPatPmtParser PatPmtParser;
1710  cFrameDetector FrameDetector;
1711  cIndexFile IndexFile(recordingName, true);
1712  int BufferChunks = KILOBYTE(1); // no need to read a lot at the beginning when parsing PAT/PMT
1713  off_t FileSize = 0;
1714  off_t FrameOffset = -1;
1715  Skins.QueueMessage(mtInfo, tr("Regenerating index file"));
1716  while (Running()) {
1717  // Rewind input file:
1718  if (Rewind) {
1719  ReplayFile = FileName.SetOffset(1);
1720  Buffer.Clear();
1721  Rewind = false;
1722  }
1723  // Process data:
1724  int Length;
1725  uchar *Data = Buffer.Get(Length);
1726  if (Data) {
1727  if (FrameDetector.Synced()) {
1728  // Step 3 - generate the index:
1729  if (TsPid(Data) == PATPID)
1730  FrameOffset = FileSize; // the PAT/PMT is at the beginning of an I-frame
1731  int Processed = FrameDetector.Analyze(Data, Length);
1732  if (Processed > 0) {
1733  if (FrameDetector.NewFrame()) {
1734  IndexFile.Write(FrameDetector.IndependentFrame(), FileName.Number(), FrameOffset >= 0 ? FrameOffset : FileSize);
1735  FrameOffset = -1;
1736  IndexFileWritten = true;
1737  }
1738  FileSize += Processed;
1739  Buffer.Del(Processed);
1740  }
1741  }
1742  else if (PatPmtParser.Vpid()) {
1743  // Step 2 - sync FrameDetector:
1744  int Processed = FrameDetector.Analyze(Data, Length);
1745  if (Processed > 0) {
1746  if (FrameDetector.Synced()) {
1747  // Synced FrameDetector, so rewind for actual processing:
1748  Rewind = true;
1749  }
1750  Buffer.Del(Processed);
1751  }
1752  }
1753  else {
1754  // Step 1 - parse PAT/PMT:
1755  uchar *p = Data;
1756  while (Length >= TS_SIZE) {
1757  int Pid = TsPid(p);
1758  if (Pid == PATPID)
1759  PatPmtParser.ParsePat(p, TS_SIZE);
1760  else if (PatPmtParser.IsPmtPid(Pid))
1761  PatPmtParser.ParsePmt(p, TS_SIZE);
1762  Length -= TS_SIZE;
1763  p += TS_SIZE;
1764  if (PatPmtParser.Vpid()) {
1765  // Found Vpid, so rewind to sync FrameDetector:
1766  FrameDetector.SetPid(PatPmtParser.Vpid(), PatPmtParser.Vtype());
1767  BufferChunks = IFG_BUFFER_SIZE;
1768  Rewind = true;
1769  break;
1770  }
1771  }
1772  Buffer.Del(p - Data);
1773  }
1774  }
1775  // Read data:
1776  else if (ReplayFile) {
1777  int Result = Buffer.Read(ReplayFile, BufferChunks);
1778  if (Result == 0) { // EOF
1779  ReplayFile = FileName.NextFile();
1780  FileSize = 0;
1781  FrameOffset = -1;
1782  Buffer.Clear();
1783  }
1784  }
1785  // Recording has been processed:
1786  else {
1787  IndexFileComplete = true;
1788  break;
1789  }
1790  }
1791  if (IndexFileComplete) {
1792  if (IndexFileWritten) {
1793  cRecordingInfo RecordingInfo(recordingName);
1794  if (RecordingInfo.Read()) {
1795  if (FrameDetector.FramesPerSecond() > 0 && !DoubleEqual(RecordingInfo.FramesPerSecond(), FrameDetector.FramesPerSecond())) {
1796  RecordingInfo.SetFramesPerSecond(FrameDetector.FramesPerSecond());
1797  RecordingInfo.Write();
1798  Recordings.UpdateByName(recordingName);
1799  }
1800  }
1801  Skins.QueueMessage(mtInfo, tr("Index file regeneration complete"));
1802  return;
1803  }
1804  else
1805  Skins.QueueMessage(mtError, tr("Index file regeneration failed!"));
1806  }
1807  // Delete the index file if the recording has not been processed entirely:
1808  IndexFile.Delete();
1809 }
1810 
1811 // --- cIndexFile ------------------------------------------------------------
1812 
1813 #define INDEXFILESUFFIX "/index"
1814 
1815 // The maximum time to wait before giving up while catching up on an index file:
1816 #define MAXINDEXCATCHUP 8 // number of retries
1817 #define INDEXCATCHUPWAIT 100 // milliseconds
1818 
1819 struct tIndexPes {
1820  uint32_t offset;
1823  uint16_t reserved;
1824  };
1825 
1826 struct tIndexTs {
1827  uint64_t offset:40; // up to 1TB per file (not using off_t here - must definitely be exactly 64 bit!)
1828  int reserved:7; // reserved for future use
1829  int independent:1; // marks frames that can be displayed by themselves (for trick modes)
1830  uint16_t number:16; // up to 64K files per recording
1831  tIndexTs(off_t Offset, bool Independent, uint16_t Number)
1832  {
1833  offset = Offset;
1834  reserved = 0;
1835  independent = Independent;
1836  number = Number;
1837  }
1838  };
1839 
1840 #define MAXWAITFORINDEXFILE 10 // max. time to wait for the regenerated index file (seconds)
1841 #define INDEXFILECHECKINTERVAL 500 // ms between checks for existence of the regenerated index file
1842 #define INDEXFILETESTINTERVAL 10 // ms between tests for the size of the index file in case of pausing live video
1843 
1844 cIndexFile::cIndexFile(const char *FileName, bool Record, bool IsPesRecording, bool PauseLive)
1845 :resumeFile(FileName, IsPesRecording)
1846 {
1847  f = -1;
1848  size = 0;
1849  last = -1;
1850  index = NULL;
1851  isPesRecording = IsPesRecording;
1852  indexFileGenerator = NULL;
1853  if (FileName) {
1854  fileName = IndexFileName(FileName, isPesRecording);
1855  if (!Record && PauseLive) {
1856  // Wait until the index file contains at least two frames:
1857  time_t tmax = time(NULL) + MAXWAITFORINDEXFILE;
1858  while (time(NULL) < tmax && FileSize(fileName) < off_t(2 * sizeof(tIndexTs)))
1860  }
1861  int delta = 0;
1862  if (!Record && access(fileName, R_OK) != 0) {
1863  // Index file doesn't exist, so try to regenerate it:
1864  if (!isPesRecording) { // sorry, can only do this for TS recordings
1865  resumeFile.Delete(); // just in case
1866  indexFileGenerator = new cIndexFileGenerator(FileName);
1867  // Wait until the index file exists:
1868  time_t tmax = time(NULL) + MAXWAITFORINDEXFILE;
1869  do {
1870  cCondWait::SleepMs(INDEXFILECHECKINTERVAL); // start with a sleep, to give it a head start
1871  } while (access(fileName, R_OK) != 0 && time(NULL) < tmax);
1872  }
1873  }
1874  if (access(fileName, R_OK) == 0) {
1875  struct stat buf;
1876  if (stat(fileName, &buf) == 0) {
1877  delta = int(buf.st_size % sizeof(tIndexTs));
1878  if (delta) {
1879  delta = sizeof(tIndexTs) - delta;
1880  esyslog("ERROR: invalid file size (%"PRId64") in '%s'", buf.st_size, *fileName);
1881  }
1882  last = int((buf.st_size + delta) / sizeof(tIndexTs) - 1);
1883  if (!Record && last >= 0) {
1884  size = last + 1;
1885  index = MALLOC(tIndexTs, size);
1886  if (index) {
1887  f = open(fileName, O_RDONLY);
1888  if (f >= 0) {
1889  if (safe_read(f, index, size_t(buf.st_size)) != buf.st_size) {
1890  esyslog("ERROR: can't read from file '%s'", *fileName);
1891  free(index);
1892  index = NULL;
1893  }
1894  else if (isPesRecording)
1896  if (!index || time(NULL) - buf.st_mtime >= MININDEXAGE) {
1897  close(f);
1898  f = -1;
1899  }
1900  // otherwise we don't close f here, see CatchUp()!
1901  }
1902  else
1904  }
1905  else
1906  esyslog("ERROR: can't allocate %zd bytes for index '%s'", size * sizeof(tIndexTs), *fileName);
1907  }
1908  }
1909  else
1910  LOG_ERROR;
1911  }
1912  else if (!Record)
1913  isyslog("missing index file %s", *fileName);
1914  if (Record) {
1915  if ((f = open(fileName, O_WRONLY | O_CREAT | O_APPEND, DEFFILEMODE)) >= 0) {
1916  if (delta) {
1917  esyslog("ERROR: padding index file with %d '0' bytes", delta);
1918  while (delta--)
1919  writechar(f, 0);
1920  }
1921  }
1922  else
1924  }
1925  }
1926 }
1927 
1929 {
1930  if (f >= 0)
1931  close(f);
1932  free(index);
1933  delete indexFileGenerator;
1934 }
1935 
1936 cString cIndexFile::IndexFileName(const char *FileName, bool IsPesRecording)
1937 {
1938  return cString::sprintf("%s%s", FileName, IsPesRecording ? INDEXFILESUFFIX ".vdr" : INDEXFILESUFFIX);
1939 }
1940 
1941 void cIndexFile::ConvertFromPes(tIndexTs *IndexTs, int Count)
1942 {
1943  tIndexPes IndexPes;
1944  while (Count-- > 0) {
1945  memcpy(&IndexPes, IndexTs, sizeof(IndexPes));
1946  IndexTs->offset = IndexPes.offset;
1947  IndexTs->independent = IndexPes.type == 1; // I_FRAME
1948  IndexTs->number = IndexPes.number;
1949  IndexTs++;
1950  }
1951 }
1952 
1953 void cIndexFile::ConvertToPes(tIndexTs *IndexTs, int Count)
1954 {
1955  tIndexPes IndexPes;
1956  while (Count-- > 0) {
1957  IndexPes.offset = uint32_t(IndexTs->offset);
1958  IndexPes.type = uchar(IndexTs->independent ? 1 : 2); // I_FRAME : "not I_FRAME" (exact frame type doesn't matter)
1959  IndexPes.number = uchar(IndexTs->number);
1960  IndexPes.reserved = 0;
1961  memcpy(IndexTs, &IndexPes, sizeof(*IndexTs));
1962  IndexTs++;
1963  }
1964 }
1965 
1966 bool cIndexFile::CatchUp(int Index)
1967 {
1968  // returns true unless something really goes wrong, so that 'index' becomes NULL
1969  if (index && f >= 0) {
1970  cMutexLock MutexLock(&mutex);
1971  // Note that CatchUp() is triggered even if Index is 'last' (and thus valid).
1972  // This is done to make absolutely sure we don't miss any data at the very end.
1973  for (int i = 0; i <= MAXINDEXCATCHUP && (Index < 0 || Index >= last); i++) {
1974  struct stat buf;
1975  if (fstat(f, &buf) == 0) {
1976  int newLast = int(buf.st_size / sizeof(tIndexTs) - 1);
1977  if (newLast > last) {
1978  int NewSize = size;
1979  if (NewSize <= newLast) {
1980  NewSize *= 2;
1981  if (NewSize <= newLast)
1982  NewSize = newLast + 1;
1983  }
1984  if (tIndexTs *NewBuffer = (tIndexTs *)realloc(index, NewSize * sizeof(tIndexTs))) {
1985  size = NewSize;
1986  index = NewBuffer;
1987  int offset = (last + 1) * sizeof(tIndexTs);
1988  int delta = (newLast - last) * sizeof(tIndexTs);
1989  if (lseek(f, offset, SEEK_SET) == offset) {
1990  if (safe_read(f, &index[last + 1], delta) != delta) {
1991  esyslog("ERROR: can't read from index");
1992  free(index);
1993  index = NULL;
1994  close(f);
1995  f = -1;
1996  break;
1997  }
1998  if (isPesRecording)
1999  ConvertFromPes(&index[last + 1], newLast - last);
2000  last = newLast;
2001  }
2002  else
2004  }
2005  else {
2006  esyslog("ERROR: can't realloc() index");
2007  break;
2008  }
2009  }
2010  }
2011  else
2013  if (Index < last)
2014  break;
2015  cCondVar CondVar;
2016  CondVar.TimedWait(mutex, INDEXCATCHUPWAIT);
2017  }
2018  }
2019  return index != NULL;
2020 }
2021 
2022 bool cIndexFile::Write(bool Independent, uint16_t FileNumber, off_t FileOffset)
2023 {
2024  if (f >= 0) {
2025  tIndexTs i(FileOffset, Independent, FileNumber);
2026  if (isPesRecording)
2027  ConvertToPes(&i, 1);
2028  if (safe_write(f, &i, sizeof(i)) < 0) {
2030  close(f);
2031  f = -1;
2032  return false;
2033  }
2034  last++;
2035  }
2036  return f >= 0;
2037 }
2038 
2039 bool cIndexFile::Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent, int *Length)
2040 {
2041  if (CatchUp(Index)) {
2042  if (Index >= 0 && Index <= last) {
2043  *FileNumber = index[Index].number;
2044  *FileOffset = index[Index].offset;
2045  if (Independent)
2046  *Independent = index[Index].independent;
2047  if (Length) {
2048  if (Index < last) {
2049  uint16_t fn = index[Index + 1].number;
2050  off_t fo = index[Index + 1].offset;
2051  if (fn == *FileNumber)
2052  *Length = int(fo - *FileOffset);
2053  else
2054  *Length = -1; // this means "everything up to EOF" (the buffer's Read function will act accordingly)
2055  }
2056  else
2057  *Length = -1;
2058  }
2059  return true;
2060  }
2061  }
2062  return false;
2063 }
2064 
2065 int cIndexFile::GetNextIFrame(int Index, bool Forward, uint16_t *FileNumber, off_t *FileOffset, int *Length)
2066 {
2067  if (CatchUp()) {
2068  int d = Forward ? 1 : -1;
2069  for (;;) {
2070  Index += d;
2071  if (Index >= 0 && Index <= last) {
2072  if (index[Index].independent) {
2073  uint16_t fn;
2074  if (!FileNumber)
2075  FileNumber = &fn;
2076  off_t fo;
2077  if (!FileOffset)
2078  FileOffset = &fo;
2079  *FileNumber = index[Index].number;
2080  *FileOffset = index[Index].offset;
2081  if (Length) {
2082  if (Index < last) {
2083  uint16_t fn = index[Index + 1].number;
2084  off_t fo = index[Index + 1].offset;
2085  if (fn == *FileNumber)
2086  *Length = int(fo - *FileOffset);
2087  else
2088  *Length = -1; // this means "everything up to EOF" (the buffer's Read function will act accordingly)
2089  }
2090  else
2091  *Length = -1;
2092  }
2093  return Index;
2094  }
2095  }
2096  else
2097  break;
2098  }
2099  }
2100  return -1;
2101 }
2102 
2104 {
2105  if (last > 0) {
2106  Index = constrain(Index, 0, last);
2107  if (index[Index].independent)
2108  return Index;
2109  int il = Index - 1;
2110  int ih = Index + 1;
2111  for (;;) {
2112  if (il >= 0) {
2113  if (index[il].independent)
2114  return il;
2115  il--;
2116  }
2117  else if (ih > last)
2118  break;
2119  if (ih <= last) {
2120  if (index[ih].independent)
2121  return ih;
2122  ih++;
2123  }
2124  else if (il < 0)
2125  break;
2126  }
2127  }
2128  return 0;
2129 }
2130 
2131 int cIndexFile::Get(uint16_t FileNumber, off_t FileOffset)
2132 {
2133  if (CatchUp()) {
2134  //TODO implement binary search!
2135  int i;
2136  for (i = 0; i <= last; i++) {
2137  if (index[i].number > FileNumber || (index[i].number == FileNumber) && off_t(index[i].offset) >= FileOffset)
2138  break;
2139  }
2140  return i;
2141  }
2142  return -1;
2143 }
2144 
2146 {
2147  return f >= 0;
2148 }
2149 
2151 {
2152  if (*fileName) {
2153  dsyslog("deleting index file '%s'", *fileName);
2154  if (f >= 0) {
2155  close(f);
2156  f = -1;
2157  }
2158  unlink(fileName);
2159  }
2160 }
2161 
2162 int cIndexFile::GetLength(const char *FileName, bool IsPesRecording)
2163 {
2164  struct stat buf;
2165  cString s = IndexFileName(FileName, IsPesRecording);
2166  if (*s && stat(s, &buf) == 0)
2167  return buf.st_size / (IsPesRecording ? sizeof(tIndexTs) : sizeof(tIndexPes));
2168  return -1;
2169 }
2170 
2171 bool GenerateIndex(const char *FileName)
2172 {
2173  if (DirectoryOk(FileName)) {
2174  cRecording Recording(FileName);
2175  if (Recording.Name()) {
2176  if (!Recording.IsPesRecording()) {
2177  cString IndexFileName = AddDirectory(FileName, INDEXFILESUFFIX);
2178  unlink(IndexFileName);
2179  cIndexFileGenerator *IndexFileGenerator = new cIndexFileGenerator(FileName);
2180  while (IndexFileGenerator->Active())
2182  if (access(IndexFileName, R_OK) == 0)
2183  return true;
2184  else
2185  fprintf(stderr, "cannot create '%s'\n", *IndexFileName);
2186  }
2187  else
2188  fprintf(stderr, "'%s' is not a TS recording\n", FileName);
2189  }
2190  else
2191  fprintf(stderr, "'%s' is not a recording\n", FileName);
2192  }
2193  else
2194  fprintf(stderr, "'%s' is not a directory\n", FileName);
2195  return false;
2196 }
2197 
2198 // --- cFileName -------------------------------------------------------------
2199 
2200 #define MAXFILESPERRECORDINGPES 255
2201 #define RECORDFILESUFFIXPES "/%03d.vdr"
2202 #define MAXFILESPERRECORDINGTS 65535
2203 #define RECORDFILESUFFIXTS "/%05d.ts"
2204 #define RECORDFILESUFFIXLEN 20 // some additional bytes for safety...
2205 
2206 cFileName::cFileName(const char *FileName, bool Record, bool Blocking, bool IsPesRecording)
2207 {
2208  file = NULL;
2209  fileNumber = 0;
2210  record = Record;
2211  blocking = Blocking;
2212  isPesRecording = IsPesRecording;
2213  // Prepare the file name:
2214  fileName = MALLOC(char, strlen(FileName) + RECORDFILESUFFIXLEN);
2215  if (!fileName) {
2216  esyslog("ERROR: can't copy file name '%s'", fileName);
2217  return;
2218  }
2219  strcpy(fileName, FileName);
2220  pFileNumber = fileName + strlen(fileName);
2221  SetOffset(1);
2222 }
2223 
2225 {
2226  Close();
2227  free(fileName);
2228 }
2229 
2230 bool cFileName::GetLastPatPmtVersions(int &PatVersion, int &PmtVersion)
2231 {
2232  if (fileName && !isPesRecording) {
2233  // Find the last recording file:
2234  int Number = 1;
2235  for (; Number <= MAXFILESPERRECORDINGTS + 1; Number++) { // +1 to correctly set Number in case there actually are that many files
2236  sprintf(pFileNumber, RECORDFILESUFFIXTS, Number);
2237  if (access(fileName, F_OK) != 0) { // file doesn't exist
2238  Number--;
2239  break;
2240  }
2241  }
2242  for (; Number > 0; Number--) {
2243  // Search for a PAT packet from the end of the file:
2244  cPatPmtParser PatPmtParser;
2245  sprintf(pFileNumber, RECORDFILESUFFIXTS, Number);
2246  int fd = open(fileName, O_RDONLY | O_LARGEFILE, DEFFILEMODE);
2247  if (fd >= 0) {
2248  off_t pos = lseek(fd, -TS_SIZE, SEEK_END);
2249  while (pos >= 0) {
2250  // Read and parse the PAT/PMT:
2251  uchar buf[TS_SIZE];
2252  while (read(fd, buf, sizeof(buf)) == sizeof(buf)) {
2253  if (buf[0] == TS_SYNC_BYTE) {
2254  int Pid = TsPid(buf);
2255  if (Pid == PATPID)
2256  PatPmtParser.ParsePat(buf, sizeof(buf));
2257  else if (PatPmtParser.IsPmtPid(Pid)) {
2258  PatPmtParser.ParsePmt(buf, sizeof(buf));
2259  if (PatPmtParser.GetVersions(PatVersion, PmtVersion)) {
2260  close(fd);
2261  return true;
2262  }
2263  }
2264  else
2265  break; // PAT/PMT is always in one sequence
2266  }
2267  else
2268  return false;
2269  }
2270  pos = lseek(fd, pos - TS_SIZE, SEEK_SET);
2271  }
2272  close(fd);
2273  }
2274  else
2275  break;
2276  }
2277  }
2278  return false;
2279 }
2280 
2282 {
2283  if (!file) {
2284  int BlockingFlag = blocking ? 0 : O_NONBLOCK;
2285  if (record) {
2286  dsyslog("recording to '%s'", fileName);
2287  file = OpenVideoFile(fileName, O_RDWR | O_CREAT | O_LARGEFILE | BlockingFlag);
2288  if (!file)
2290  }
2291  else {
2292  if (access(fileName, R_OK) == 0) {
2293  dsyslog("playing '%s'", fileName);
2294  file = cUnbufferedFile::Create(fileName, O_RDONLY | O_LARGEFILE | BlockingFlag);
2295  if (!file)
2297  }
2298  else if (errno != ENOENT)
2300  }
2301  }
2302  return file;
2303 }
2304 
2306 {
2307  if (file) {
2308  if (CloseVideoFile(file) < 0)
2310  file = NULL;
2311  }
2312 }
2313 
2314 cUnbufferedFile *cFileName::SetOffset(int Number, off_t Offset)
2315 {
2316  if (fileNumber != Number)
2317  Close();
2318  int MaxFilesPerRecording = isPesRecording ? MAXFILESPERRECORDINGPES : MAXFILESPERRECORDINGTS;
2319  if (0 < Number && Number <= MaxFilesPerRecording) {
2320  fileNumber = uint16_t(Number);
2322  if (record) {
2323  if (access(fileName, F_OK) == 0) {
2324  // file exists, check if it has non-zero size
2325  struct stat buf;
2326  if (stat(fileName, &buf) == 0) {
2327  if (buf.st_size != 0)
2328  return SetOffset(Number + 1); // file exists and has non zero size, let's try next suffix
2329  else {
2330  // zero size file, remove it
2331  dsyslog("cFileName::SetOffset: removing zero-sized file %s", fileName);
2332  unlink(fileName);
2333  }
2334  }
2335  else
2336  return SetOffset(Number + 1); // error with fstat - should not happen, just to be on the safe side
2337  }
2338  else if (errno != ENOENT) { // something serious has happened
2340  return NULL;
2341  }
2342  // found a non existing file suffix
2343  }
2344  if (Open() >= 0) {
2345  if (!record && Offset >= 0 && file && file->Seek(Offset, SEEK_SET) != Offset) {
2347  return NULL;
2348  }
2349  }
2350  return file;
2351  }
2352  esyslog("ERROR: max number of files (%d) exceeded", MaxFilesPerRecording);
2353  return NULL;
2354 }
2355 
2357 {
2358  return SetOffset(fileNumber + 1);
2359 }
2360 
2361 // --- Index stuff -----------------------------------------------------------
2362 
2363 cString IndexToHMSF(int Index, bool WithFrame, double FramesPerSecond)
2364 {
2365  const char *Sign = "";
2366  if (Index < 0) {
2367  Index = -Index;
2368  Sign = "-";
2369  }
2370  double Seconds;
2371  int f = int(modf((Index + 0.5) / FramesPerSecond, &Seconds) * FramesPerSecond + 1);
2372  int s = int(Seconds);
2373  int m = s / 60 % 60;
2374  int h = s / 3600;
2375  s %= 60;
2376  return cString::sprintf(WithFrame ? "%s%d:%02d:%02d.%02d" : "%s%d:%02d:%02d", Sign, h, m, s, f);
2377 }
2378 
2379 int HMSFToIndex(const char *HMSF, double FramesPerSecond)
2380 {
2381  int h, m, s, f = 1;
2382  int n = sscanf(HMSF, "%d:%d:%d.%d", &h, &m, &s, &f);
2383  if (n == 1)
2384  return h - 1; // plain frame number
2385  if (n >= 3)
2386  return int(round((h * 3600 + m * 60 + s) * FramesPerSecond)) + f - 1;
2387  return 0;
2388 }
2389 
2390 int SecondsToFrames(int Seconds, double FramesPerSecond)
2391 {
2392  return int(round(Seconds * FramesPerSecond));
2393 }
2394 
2395 // --- ReadFrame -------------------------------------------------------------
2396 
2397 int ReadFrame(cUnbufferedFile *f, uchar *b, int Length, int Max)
2398 {
2399  if (Length == -1)
2400  Length = Max; // this means we read up to EOF (see cIndex)
2401  else if (Length > Max) {
2402  esyslog("ERROR: frame larger than buffer (%d > %d)", Length, Max);
2403  Length = Max;
2404  }
2405  int r = f->Read(b, Length);
2406  if (r < 0)
2407  LOG_ERROR;
2408  return r;
2409 }
2410 
2411 // --- Recordings Sort Mode --------------------------------------------------
2412 
2414 
2415 bool HasRecordingsSortMode(const char *Directory)
2416 {
2417  return access(AddDirectory(Directory, SORTMODEFILE), R_OK) == 0;
2418 }
2419 
2420 void GetRecordingsSortMode(const char *Directory)
2421 {
2422  if (FILE *f = fopen(AddDirectory(Directory, SORTMODEFILE), "r")) {
2423  char buf[8];
2424  if (fgets(buf, sizeof(buf), f))
2426  fclose(f);
2427  }
2428 }
2429 
2430 void SetRecordingsSortMode(const char *Directory, eRecordingsSortMode SortMode)
2431 {
2432  if (FILE *f = fopen(AddDirectory(Directory, SORTMODEFILE), "w")) {
2433  fputs(cString::sprintf("%d\n", SortMode), f);
2434  fclose(f);
2435  }
2436 }
2437 
2438 void IncRecordingsSortMode(const char *Directory)
2439 {
2440  GetRecordingsSortMode(Directory);
2445 }
cString dtoa(double d, const char *Format)
Converts the given double value to a string, making sure it uses a &#39;.
Definition: tools.c:329
struct dirent * Next(void)
Definition: tools.c:1397
cString itoa(int n)
Definition: tools.c:339
#define REMOVECHECKDELTA
Definition: recording.c:60
void SetModified(void)
Definition: timers.c:768
unsigned char uchar
Definition: tools.h:30
void ParsePat(const uchar *Data, int Length)
Parses the PAT data from the single TS packet in Data.
Definition: remux.c:647
Definition: epg.h:71
void SetFramesPerSecond(double FramesPerSecond)
Definition: recording.c:420
uint64_t offset
Definition: recording.c:1827
int Priority(void) const
Definition: recording.h:113
bool DirectoryEncoding
Definition: recording.c:71
int Position(void) const
Definition: recording.h:217
virtual void Clear(void)
Immediately clears the ring buffer.
Definition: ringbuffer.c:217
int Number(void) const
Definition: channels.h:191
bool Update(bool Wait=false)
Triggers an update of the list of recordings, which will run as a separate thread if Wait is false...
Definition: recording.c:1342
int TotalFileSizeMB(void)
Definition: recording.c:1407
tIndexTs * index
Definition: recording.h:294
tComponent * GetComponent(int Index, uchar Stream, uchar Type)
Definition: epg.c:98
#define NAMEFORMATTS
Definition: recording.c:47
int NumFrames(void) const
Returns the number of frames in this recording.
Definition: recording.c:1206
bool isempty(const char *s)
Definition: tools.c:248
static tChannelID FromString(const char *s)
Definition: channels.c:25
bool RemoveVideoFile(const char *FileName)
Definition: videodir.c:171
#define dsyslog(a...)
Definition: tools.h:36
cString AddDirectory(const char *DirName, const char *FileName)
Definition: tools.c:301
void Refresh(bool Foreground=false)
Definition: recording.c:1267
#define DELEXT
Definition: recording.c:34
static char * StripEpisodeName(char *s, bool Strip)
Definition: recording.c:919
const char * UpdateFileName(const char *FileName)
Definition: recording.c:1075
void SetPid(int Pid, int Type)
Sets the Pid and stream Type to detect frames for.
Definition: remux.c:1457
void SetComponent(int Index, const char *s)
Definition: epg.c:78
#define DEFAULTFRAMESPERSECOND
Definition: recording.h:206
time_t Start(void) const
Definition: recording.h:112
bool Close(void)
Definition: tools.c:1603
cRecordingInfo * info
Definition: recording.h:97
cEvent * ownEvent
Definition: recording.h:53
char * fileName
Definition: recording.h:58
char * sortBufferName
Definition: recording.h:86
void ParsePmt(const uchar *Data, int Length)
Parses the PMT data from the single TS packet in Data.
Definition: remux.c:679
const char * InvalidChars
Definition: recording.c:547
void SetStartTime(time_t StartTime)
Definition: epg.c:212
void SetDuration(int Duration)
Definition: epg.c:223
cMark * GetPrev(int Position)
Definition: recording.c:1589
bool IsPmtPid(int Pid) const
Returns true if Pid the one of the PMT pids as defined by the current PAT.
Definition: remux.h:390
#define LOG_ERROR
Definition: tools.h:38
void SetRecordingsSortMode(const char *Directory, eRecordingsSortMode SortMode)
Definition: recording.c:2430
void ResetResume(const char *ResumeFileName=NULL)
Definition: recording.c:1439
void SetTableID(uchar TableID)
Definition: epg.c:163
const cEvent * event
Definition: recording.h:52
void Add(cListObject *Object, cListObject *After=NULL)
Definition: tools.c:1945
bool CatchUp(int Index=-1)
Definition: recording.c:1966
cResumeFile(const char *FileName, bool IsPesRecording)
Definition: recording.c:221
bool Save(FILE *f)
Definition: recording.c:1491
void ChangeState(void)
Definition: recording.h:189
bool isPesRecording
Definition: recording.h:295
char * stripspace(char *s)
Definition: tools.c:176
bool IsEdited(void) const
Definition: recording.c:1096
bool DirectoryOk(const char *DirName, bool LogErrors)
Definition: tools.c:378
void DelByName(const char *FileName, bool RemoveRecording=true)
Definition: recording.c:1377
char * LimitNameLengths(char *s, int PathMax, int NameMax)
Definition: recording.c:627
char * name
Definition: recording.h:89
void Sort(void)
Definition: recording.c:1562
bool Open(void)
Definition: tools.c:1593
static void InvokeCommand(const char *State, const char *RecordingFileName, const char *SourceFileName=NULL)
Definition: recording.c:1662
double FramesPerSecond(void) const
Definition: recording.h:125
eRecordingsSortMode RecordingsSortMode
Definition: recording.c:2413
ssize_t Read(void *Data, size_t Size)
Definition: tools.c:1705
char language[MAXLANGCODE2]
Definition: epg.h:45
cMark * GetNextBegin(cMark *EndMark=NULL)
Returns the next &quot;begin&quot; mark after EndMark, skipping any marks at the same position as EndMark...
Definition: recording.c:1607
cUnbufferedFile * SetOffset(int Number, off_t Offset=0)
Definition: recording.c:2314
const char * Title(char Delimiter= ' ', bool NewIndicator=false, int Level=-1) const
Definition: recording.c:1003
cTimers Timers
Definition: timers.c:694
bool VideoFileSpaceAvailable(int SizeMB)
Definition: videodir.c:176
bool endswith(const char *s, const char *p)
Definition: tools.c:237
#define TIMERMACRO_EPISODE
Definition: config.h:48
static cString sprintf(const char *fmt,...) __attribute__((format(printf
Definition: tools.c:1011
off_t Seek(off_t Offset, int Whence)
Definition: tools.c:1697
int DirectoryNameMax
Definition: recording.c:70
time_t deleted
Definition: recording.h:107
int Analyze(const uchar *Data, int Length)
Analyzes the TS packets pointed to by Data.
Definition: remux.c:1474
const char * VideoDirectory
Definition: videodir.c:22
int GetNextIFrame(int Index, bool Forward, uint16_t *FileNumber=NULL, off_t *FileOffset=NULL, int *Length=NULL)
Definition: recording.c:2065
int QueueMessage(eMessageType Type, const char *s, int Seconds=0, int Timeout=0)
Like Message(), but this function may be called from a background thread.
Definition: skins.c:277
void Align(void)
Definition: recording.c:1550
#define DELETEDLIFETIME
Definition: recording.c:61
#define esyslog(a...)
Definition: tools.h:34
bool IsOnVideoDirectoryFileSystem(void) const
Definition: recording.c:1103
char * strn0cpy(char *dest, const char *src, size_t n)
Definition: tools.c:131
cMutex mutex
Definition: recording.h:298
int fileSizeMB
Definition: recording.h:90
cUnbufferedFile * NextFile(void)
Definition: recording.c:2356
#define RECORDFILESUFFIXTS
Definition: recording.c:2203
int AlwaysSortFoldersFirst
Definition: config.h:302
double MarkFramesPerSecond
Definition: recording.c:1458
#define LOG_ERROR_STR(s)
Definition: tools.h:39
int Vpid(void) const
Returns the video pid as defined by the current PMT, or 0 if no video pid has been detected...
Definition: remux.h:393
#define SORTMODEFILE
Definition: recording.c:56
T max(T a, T b)
Definition: tools.h:55
tChannelID channelID
Definition: recording.h:50
const cComponents * Components(void) const
Definition: recording.h:72
#define RESUMEFILESUFFIX
Definition: recording.c:49
virtual ~cMark()
Definition: recording.c:1468
int UseSubtitle
Definition: config.h:297
int ReadFrame(cUnbufferedFile *f, uchar *b, int Length, int Max)
Definition: recording.c:2397
uint16_t reserved
Definition: recording.c:1823
cUnbufferedFile * OpenVideoFile(const char *FileName, int Flags)
Definition: videodir.c:111
double FramesPerSecond(void) const
Definition: recording.h:74
#define MAXWAITFORINDEXFILE
Definition: recording.c:1840
void ResetResume(void) const
Definition: recording.c:1201
void RemoveEmptyVideoDirectories(const char *IgnoreFiles[])
Definition: videodir.c:248
#define REMOVELATENCY
Definition: recording.c:63
time_t StartTime(void) const
Definition: timers.c:497
cRecording(const cRecording &)
const char * Dlang(int i) const
Definition: channels.h:175
#define INDEXFILETESTINTERVAL
Definition: recording.c:1842
bool IsNew(void) const
Definition: recording.h:134
double atod(const char *s)
Converts the given string, which is a floating point number using a &#39;.
Definition: tools.c:308
int DirSizeMB(const char *DirName)
returns the total size of the files in the given directory, or -1 in case of an error ...
Definition: tools.c:536
time_t start
Definition: recording.h:104
void SetAux(const char *Aux)
Definition: recording.c:414
int Count(void) const
Definition: tools.h:475
#define RECORDFILESUFFIXPES
Definition: recording.c:2201
#define TS_SYNC_BYTE
Definition: remux.h:33
eRecordingsSortMode
Definition: recording.h:364
cUnbufferedFile is used for large files that are mainly written or read in a streaming manner...
Definition: tools.h:408
static cString IndexFileName(const char *FileName, bool IsPesRecording)
Definition: recording.c:1936
bool GetLastPatPmtVersions(int &PatVersion, int &PmtVersion)
Definition: recording.c:2230
#define INFOFILESUFFIX
Definition: recording.c:53
#define IFG_BUFFER_SIZE
Definition: recording.c:1677
const char * Alang(int i) const
Definition: channels.h:174
bool Synced(void)
Returns true if the frame detector has synced on the data stream.
Definition: remux.h:516
uint16_t Number(void)
Definition: recording.h:339
static const char * command
Definition: recording.h:267
uint16_t number
Definition: recording.c:1830
char * Read(FILE *f)
Definition: tools.c:1329
cString IndexToHMSF(int Index, bool WithFrame, double FramesPerSecond)
Definition: recording.c:2363
#define MALLOC(type, size)
Definition: tools.h:46
virtual void Clear(void)
Definition: tools.c:2018
const cChannel * Channel(void) const
Definition: timers.h:56
int TsPid(const uchar *p)
Definition: remux.h:85
#define TIMERMACRO_TITLE
Definition: config.h:47
Definition: timers.h:27
bool Read(FILE *f)
Definition: recording.c:425
bool Save(int Index)
Definition: recording.c:284
bool isPesRecording
Definition: recording.h:38
char * SortName(void) const
Definition: recording.c:948
#define MAXFILESPERRECORDINGPES
Definition: recording.c:2200
bool GenerateIndex(const char *FileName)
Definition: recording.c:2171
cMark * Last(void) const
Definition: tools.h:483
cMark * GetNextEnd(cMark *BeginMark)
Returns the next &quot;end&quot; mark after BeginMark, skipping any marks at the same position as BeginMark...
Definition: recording.c:1623
#define DATAFORMATPES
Definition: recording.c:44
int priority
Definition: recording.h:105
int Lifetime(void) const
Definition: timers.h:62
double framesPerSecond
Definition: recording.h:96
cMark(int Position=0, const char *Comment=NULL, double FramesPerSecond=DEFAULTFRAMESPERSECOND)
Definition: recording.c:1461
void SetTitle(const char *Title)
Definition: epg.c:180
void ScanVideoDir(const char *DirName, bool Foreground=false, int LinkLevel=0)
Definition: recording.c:1277
void Unlock(void)
Definition: thread.h:93
tCharExchange CharExchange[]
Definition: recording.c:536
double framesPerSecond
Definition: recording.h:55
int Vtype(void) const
Returns the video stream type as defined by the current PMT, or 0 if no video stream type has been de...
Definition: remux.h:399
cRecording * GetByName(const char *FileName)
Definition: recording.c:1353
const char * Name(void) const
Definition: channels.c:121
cIndexFile(const char *FileName, bool Record, bool IsPesRecording=false, bool PauseLive=false)
Definition: recording.c:1844
cMark * GetNext(int Position)
Definition: recording.c:1598
T * Next(const T *object) const
Definition: tools.h:485
void ReadInfo(void)
Definition: recording.c:1110
const char * Comment(void) const
Definition: recording.h:218
ssize_t safe_write(int filedes, const void *buffer, size_t size)
Definition: tools.c:65
void GetRecordingsSortMode(const char *Directory)
Definition: recording.c:2420
bool isPesRecording
Definition: recording.h:231
T constrain(T v, T l, T h)
Definition: tools.h:60
int Read(int FileHandle, int Max=0)
Reads at most Max bytes from FileHandle and stores them in the ring buffer.
Definition: ringbuffer.c:229
char * aux
Definition: recording.h:54
int reserved
Definition: recording.c:1828
time_t lastChange
Definition: recording.h:234
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
Definition: recording.c:90
bool deleted
Definition: recording.h:163
virtual ~cRecording()
Definition: recording.c:909
char * sortBufferTime
Definition: recording.h:87
bool record
Definition: recording.h:332
bool isPesRecording
Definition: recording.h:94
#define FOLDERDELIMCHAR
Definition: recording.h:21
bool Write(FILE *f, const char *Prefix="") const
Definition: recording.c:484
#define INDEXFILESUFFIX
Definition: recording.c:1813
void SetData(const char *Title, const char *ShortText, const char *Description)
Definition: recording.c:404
#define DATAFORMATTS
Definition: recording.c:46
int GetResume(void) const
Definition: recording.c:970
int LengthInSeconds(void) const
Returns the length (in seconds) of this recording, or -1 in case of error.
Definition: recording.c:1217
bool blocking
Definition: recording.h:333
void RemoveDeletedRecordings(void)
Definition: recording.c:121
void swap(T &a, T &b)
Definition: tools.h:57
tIndexTs(off_t Offset, bool Independent, uint16_t Number)
Definition: recording.c:1831
static void SleepMs(int TimeoutMs)
Creates a cCondWait object and uses it to sleep for TimeoutMs milliseconds, immediately giving up the...
Definition: thread.c:57
int instanceId
Definition: recording.h:93
void UpdateByName(const char *FileName)
Definition: recording.c:1399
int HMSFToIndex(const char *HMSF, double FramesPerSecond)
Definition: recording.c:2379
char * channelName
Definition: recording.h:51
int position
Definition: recording.h:212
void bool Start(void)
Actually starts the thread.
Definition: thread.c:273
int lifetime
Definition: recording.h:106
uint32_t offset
Definition: recording.c:1820
#define RECEXT
Definition: recording.c:33
void SetStartTime(time_t Start)
Sets the start time of this recording to the given value.
Definition: recording.c:1131
Definition: skins.h:23
bool NeedsConversion(const char *p)
Definition: recording.c:549
int FileSizeMB(void) const
Returns the total file size of this recording (in MB), or -1 if the file size is unknown.
Definition: recording.c:1225
bool Delete(void)
Changes the file name so that it will no longer be visible in the &quot;Recordings&quot; menu Returns false in ...
Definition: recording.c:1138
#define MAXLIFETIME
Definition: config.h:44
void ConvertToPes(tIndexTs *IndexTs, int Count)
Definition: recording.c:1953
cUnbufferedFile * Open(void)
Definition: recording.c:2281
#define MININDEXAGE
Definition: recording.c:65
cSetup Setup
Definition: config.c:373
static int GetLength(const char *FileName, bool IsPesRecording=false)
Calculates the recording length (number of frames) without actually reading the index file...
Definition: recording.c:2162
int Lifetime(void) const
Definition: recording.h:114
static int Utf8CharLen(const char *s)
Definition: si.c:409
tChannelID GetChannelID(void) const
Definition: channels.h:202
int isOnVideoDirectoryFileSystem
Definition: recording.h:95
void ConvertFromPes(tIndexTs *IndexTs, int Count)
Definition: recording.c:1941
char * pFileNumber
Definition: recording.h:331
int GetClosestIFrame(int Index)
Returns the index of the I-frame that is closest to the given Index (or Index itself, if it already points to an I-frame).
Definition: recording.c:2103
bool Write(void) const
Definition: recording.c:515
uchar type
Definition: recording.c:1821
char * fileName
Definition: recording.h:331
bool Running(void)
Returns false if a derived cThread object shall leave its Action() function.
Definition: thread.h:99
static char * updateFileName
Definition: recording.h:162
bool HasRecordingsSortMode(const char *Directory)
Definition: recording.c:2415
Definition: thread.h:63
int InstanceId
Definition: recording.c:72
bool TimedWait(cMutex &Mutex, int TimeoutMs)
Definition: thread.c:117
bool Read(void)
Definition: recording.c:497
int SystemExec(const char *Command, bool Detached)
Definition: thread.c:560
int HierarchyLevels(void) const
Definition: recording.c:1085
void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
Definition: recording.c:1255
bool WriteInfo(void)
Definition: recording.c:1118
void TouchUpdate(void)
Touches the &#39;.update&#39; file in the video directory, so that other instances of VDR that access the sam...
Definition: recording.c:1326
int independent
Definition: recording.c:1829
void Del(int Count)
Deletes at most Count bytes from the ring buffer.
Definition: ringbuffer.c:370
bool Parse(const char *s)
Definition: recording.c:1477
#define MAXFILESPERRECORDINGTS
Definition: recording.c:2202
const char * UpdateFileName(void)
Definition: recording.c:1260
const char * Title(void) const
Definition: epg.h:100
bool Valid(void) const
Definition: channels.h:62
bool Parse(char *s)
Definition: epg.c:466
bool Lock(int WaitSeconds=0)
Definition: tools.c:1843
bool Remove(void)
Actually removes the file from the disk Returns false in case of error.
Definition: recording.c:1164
cString fileName
Definition: recording.h:229
cRecordings DeletedRecordings
cIndexFileGenerator * indexFileGenerator
Definition: recording.h:297
cString comment
Definition: recording.h:213
cRecordings(bool Deleted=false)
Definition: recording.c:1242
Definition: epg.h:42
Definition: skins.h:23
#define RECORDFILESUFFIXLEN
Definition: recording.c:2204
int DirectoryPathMax
Definition: recording.c:69
char * titleBuffer
Definition: recording.h:85
int GetNumSequences(void)
Returns the actual number of sequences to be cut from the recording.
Definition: recording.c:1641
#define MAXDPIDS
Definition: channels.h:35
T * First(void) const
Definition: tools.h:482
#define MIN_TS_PACKETS_FOR_FRAME_DETECTOR
Definition: remux.h:480
void Del(cListObject *Object, bool DeleteObject=true)
Definition: tools.c:1977
cString ToString(void) const
Definition: channels.c:42
int channel
Definition: recording.h:92
#define MARKSFILESUFFIX
Definition: recording.c:54
cString PrefixVideoFileName(const char *FileName, char Prefix)
Definition: videodir.c:212
#define PATPID
Definition: remux.h:52
cMark * Get(int Position)
Definition: recording.c:1580
bool Active(void)
Checks whether the thread is still alive.
Definition: thread.c:298
cFileName(const char *FileName, bool Record, bool Blocking=false, bool IsPesRecording=false)
Definition: recording.c:2206
cString fileName
Definition: recording.h:292
#define MAXPRIORITY
Definition: config.h:39
void Delete(void)
Definition: recording.c:2150
time_t lastFileTime
Definition: recording.h:233
bool NeedsUpdate(void)
Definition: recording.c:1334
off_t FileSize(const char *FileName)
returns the size of the given file, or -1 in case of an error (e.g. if the file doesn&#39;t exist) ...
Definition: tools.c:628
#define RESUME_NOT_INITIALIZED
Definition: recording.c:533
bool NewFrame(void)
Returns true if the data given to the last call to Analyze() started a new frame. ...
Definition: remux.h:518
#define KILOBYTE(n)
Definition: tools.h:43
bool IndependentFrame(void)
Returns true if a new frame was detected and this is an independent frame (i.e.
Definition: remux.h:521
#define tr(s)
Definition: i18n.h:85
void Close(void)
Definition: recording.c:2305
const char * File(void) const
Definition: timers.h:63
bool GetVersions(int &PatVersion, int &PmtVersion) const
Returns true if a valid PAT/PMT has been parsed and stores the current version numbers in the given v...
Definition: remux.c:945
void Delete(void)
Definition: recording.c:312
bool IsSingleEvent(void) const
Definition: timers.c:346
void ClearSortName(void)
Definition: recording.c:964
cRecordings Recordings
Definition: recording.c:1238
uchar * Get(int &Count)
Gets data from the ring buffer.
Definition: ringbuffer.c:345
double MBperMinute(void)
Returns the average data rate (in MB/min) of all recordings, or -1 if this value is unknown...
Definition: recording.c:1419
void DELETENULL(T *&p)
Definition: tools.h:48
char * skipspace(const char *s)
Definition: tools.h:194
#define SECSINDAY
Definition: tools.h:41
void Add(int Position)
Definition: recording.c:1574
void IncRecordingsSortMode(const char *Directory)
Definition: recording.c:2438
int NumComponents(void) const
Definition: epg.h:59
const char * Name(void) const
Definition: recording.h:117
#define isyslog(a...)
Definition: tools.h:35
void AssertFreeDiskSpace(int Priority, bool Force)
The special Priority value -1 means that we shall get rid of any deleted recordings faster than norma...
Definition: recording.c:138
double framesPerSecond
Definition: recording.h:230
cResumeFile resumeFile
Definition: recording.h:296
Definition: thread.h:77
double FramesPerSecond(void)
Returns the number of frames per second, or 0 if this information is not available.
Definition: remux.h:525
char * fileName
Definition: recording.h:37
static bool Engaged(void)
Returns true if any I/O throttling object is currently active.
Definition: thread.c:443
const char * Title(void) const
Definition: recording.h:69
bool Update(void)
Definition: recording.c:1510
#define MAXSPIDS
Definition: channels.h:36
void SetVersion(uchar Version)
Definition: epg.c:168
void ClearSortNames(void)
Definition: recording.c:1449
int SecondsToFrames(int Seconds, double FramesPerSecond)
Definition: recording.c:2390
ssize_t safe_read(int filedes, void *buffer, size_t size)
Definition: tools.c:53
bool StateChanged(int &State)
Definition: recording.c:1318
const char * Slang(int i) const
Definition: channels.h:176
cMutex MutexMarkFramesPerSecond
Definition: recording.c:1459
const cComponents * Components(void) const
Definition: epg.h:103
cIndexFileGenerator(const char *RecordingName)
Definition: recording.c:1689
int Read(void)
Definition: recording.c:239
int resume
Definition: recording.h:84
static const tChannelID InvalidID
Definition: channels.h:72
int Priority(void) const
Definition: timers.h:61
bool Load(const char *RecordingFileName, double FramesPerSecond=DEFAULTFRAMESPERSECOND, bool IsPesRecording=false)
Definition: recording.c:1498
cRecordingInfo(const cChannel *Channel=NULL, const cEvent *Event=NULL)
Definition: recording.c:324
#define TS_SIZE
Definition: remux.h:34
int CloseVideoFile(cUnbufferedFile *File)
Definition: videodir.c:152
static cUnbufferedFile * Create(const char *FileName, int Flags, mode_t Mode=DEFFILEMODE)
Definition: tools.c:1814
time_t nextUpdate
Definition: recording.h:232
bool Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent=NULL, int *Length=NULL)
Definition: recording.c:2039
void TouchFile(const char *FileName)
Definition: tools.c:614
#define DISKCHECKDELTA
Definition: recording.c:62
#define MINDISKSPACE
Definition: recording.c:58
bool DoubleEqual(double a, double b)
Definition: tools.h:85
bool IsStillRecording(void)
Definition: recording.c:2145
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
Definition: recording.c:1701
char * compactspace(char *s)
Definition: tools.c:188
cString strescape(const char *s, const char *chars)
Definition: tools.c:205
#define LOCK_THREAD
Definition: thread.h:161
void SetEventID(tEventID EventID)
Definition: epg.c:152
#define INDEXFILECHECKINTERVAL
Definition: recording.c:1841
char * ExchangeChars(char *s, bool ToFileSystem)
Definition: recording.c:556
bool isPesRecording
Definition: recording.h:334
uint16_t fileNumber
Definition: recording.h:330
cString recordingFileName
Definition: recording.h:228
char * fileName
Definition: recording.h:88
const char * ShortText(void) const
Definition: epg.h:101
const char * FileName(void) const
Definition: recording.c:985
#define INDEXCATCHUPWAIT
Definition: recording.c:1817
time_t lastUpdate
Definition: recording.h:164
bool RenameVideoFile(const char *OldName, const char *NewName)
Definition: videodir.c:159
#define NAMEFORMATPES
Definition: recording.c:45
virtual int Compare(const cListObject &ListObject) const
Must return 0 if this object is equal to ListObject, a positive value if it is &quot;greater&quot;, and a negative value if it is &quot;smaller&quot;.
Definition: recording.c:979
void Cancel(int WaitSeconds=0)
Cancels the thread by first setting &#39;running&#39; to false, so that the Action() loop can finish in an or...
Definition: thread.c:323
const char * PrefixFileName(char Prefix)
Definition: recording.c:1064
const char * Aux(void) const
Definition: timers.h:65
bool Save(void)
Definition: recording.c:1541
cMark * Prev(const cMark *object) const
Definition: tools.h:484
static cRemoveDeletedRecordingsThread RemoveDeletedRecordingsThread
Definition: recording.c:117
cUnbufferedFile * file
Definition: recording.h:329
time_t LastModifiedTime(const char *FileName)
Definition: tools.c:620
void SetFile(const char *File)
Definition: timers.c:392
int numFrames
Definition: recording.h:91
char * strreplace(char *s, char c1, char c2)
Definition: tools.c:139
void AddByName(const char *FileName, bool TriggerUpdate=true)
Definition: recording.c:1364
bool IsPesRecording(void) const
Definition: recording.h:136
#define MAX_LINK_LEVEL
Definition: recording.c:67
virtual ~cRecordings()
Definition: recording.c:1250
bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset)
Definition: recording.c:2022
#define RUC_DELETERECORDING
Definition: recording.h:263
double framesPerSecond
Definition: recording.h:211
#define SUMMARYFILESUFFIX
Definition: recording.c:51
int ResumeID
Definition: config.h:337
bool Undelete(void)
Changes the file name so that it will be visible in the &quot;Recordings&quot; menu again and not processed by ...
Definition: recording.c:1175
void writechar(int filedes, char c)
Definition: tools.c:85
Definition: tools.h:166
cString ToText(void)
Definition: recording.c:1472
void Lock(void)
Definition: thread.h:92
cSkins Skins
Definition: skins.c:203
#define MAXAPIDS
Definition: channels.h:34
uchar number
Definition: recording.c:1822