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