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