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