vdr  2.2.0
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 3.28 2015/02/16 07:49:14 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 "cutter.h"
25 #include "i18n.h"
26 #include "interface.h"
27 #include "menu.h"
28 #include "remux.h"
29 #include "ringbuffer.h"
30 #include "skins.h"
31 #include "tools.h"
32 #include "videodir.h"
33 
34 #define SUMMARYFALLBACK
35 
36 #define RECEXT ".rec"
37 #define DELEXT ".del"
38 /* This was the original code, which works fine in a Linux only environment.
39  Unfortunately, because of Windows and its brain dead file system, we have
40  to use a more complicated approach, in order to allow users who have enabled
41  the --vfat command line option to see their recordings even if they forget to
42  enable --vfat when restarting VDR... Gee, do I hate Windows.
43  (kls 2002-07-27)
44 #define DATAFORMAT "%4d-%02d-%02d.%02d:%02d.%02d.%02d" RECEXT
45 #define NAMEFORMAT "%s/%s/" DATAFORMAT
46 */
47 #define DATAFORMATPES "%4d-%02d-%02d.%02d%*c%02d.%02d.%02d" RECEXT
48 #define NAMEFORMATPES "%s/%s/" "%4d-%02d-%02d.%02d.%02d.%02d.%02d" RECEXT
49 #define DATAFORMATTS "%4d-%02d-%02d.%02d.%02d.%d-%d" RECEXT
50 #define NAMEFORMATTS "%s/%s/" DATAFORMATTS
51 
52 #define RESUMEFILESUFFIX "/resume%s%s"
53 #ifdef SUMMARYFALLBACK
54 #define SUMMARYFILESUFFIX "/summary.vdr"
55 #endif
56 #define INFOFILESUFFIX "/info"
57 #define MARKSFILESUFFIX "/marks"
58 
59 #define SORTMODEFILE ".sort"
60 
61 #define MINDISKSPACE 1024 // MB
62 
63 #define REMOVECHECKDELTA 60 // seconds between checks for removing deleted files
64 #define DELETEDLIFETIME 300 // seconds after which a deleted recording will be actually removed
65 #define DISKCHECKDELTA 100 // seconds between checks for free disk space
66 #define REMOVELATENCY 10 // seconds to wait until next check after removing a file
67 #define MARKSUPDATEDELTA 10 // seconds between checks for updating editing marks
68 #define MININDEXAGE 3600 // seconds before an index file is considered no longer to be written
69 #define MAXREMOVETIME 10 // seconds after which to return from removing deleted recordings
70 
71 #define MAX_LINK_LEVEL 6
72 
73 #define LIMIT_SECS_PER_MB_RADIO 5 // radio recordings typically have more than this
74 
75 int DirectoryPathMax = PATH_MAX - 1;
76 int DirectoryNameMax = NAME_MAX;
77 bool DirectoryEncoding = false;
78 int InstanceId = 0;
79 
82 
83 // --- cRemoveDeletedRecordingsThread ----------------------------------------
84 
86 protected:
87  virtual void Action(void);
88 public:
90  };
91 
93 :cThread("remove deleted recordings", true)
94 {
95 }
96 
98 {
99  // Make sure only one instance of VDR does this:
100  cLockFile LockFile(cVideoDirectory::Name());
101  if (LockFile.Lock()) {
102  time_t StartTime = time(NULL);
103  bool deleted = false;
104  cThreadLock DeletedRecordingsLock(&DeletedRecordings);
105  for (cRecording *r = DeletedRecordings.First(); r; ) {
106  if (cIoThrottle::Engaged())
107  return;
108  if (time(NULL) - StartTime > MAXREMOVETIME)
109  return; // don't stay here too long
110  if (cRemote::HasKeys())
111  return; // react immediately on user input
112  if (r->Deleted() && time(NULL) - r->Deleted() > DELETEDLIFETIME) {
113  cRecording *next = DeletedRecordings.Next(r);
114  r->Remove();
116  r = next;
117  deleted = true;
118  continue;
119  }
120  r = DeletedRecordings.Next(r);
121  }
122  if (deleted) {
123  const char *IgnoreFiles[] = { SORTMODEFILE, NULL };
125  }
126  }
127 }
128 
130 
131 // ---
132 
134 {
135  static time_t LastRemoveCheck = 0;
136  if (time(NULL) - LastRemoveCheck > REMOVECHECKDELTA) {
137  if (!RemoveDeletedRecordingsThread.Active()) {
138  cThreadLock DeletedRecordingsLock(&DeletedRecordings);
139  for (cRecording *r = DeletedRecordings.First(); r; r = DeletedRecordings.Next(r)) {
140  if (r->Deleted() && time(NULL) - r->Deleted() > DELETEDLIFETIME) {
141  RemoveDeletedRecordingsThread.Start();
142  break;
143  }
144  }
145  }
146  LastRemoveCheck = time(NULL);
147  }
148 }
149 
150 void AssertFreeDiskSpace(int Priority, bool Force)
151 {
152  static cMutex Mutex;
153  cMutexLock MutexLock(&Mutex);
154  // With every call to this function we try to actually remove
155  // a file, or mark a file for removal ("delete" it), so that
156  // it will get removed during the next call.
157  static time_t LastFreeDiskCheck = 0;
158  int Factor = (Priority == -1) ? 10 : 1;
159  if (Force || time(NULL) - LastFreeDiskCheck > DISKCHECKDELTA / Factor) {
161  // Make sure only one instance of VDR does this:
162  cLockFile LockFile(cVideoDirectory::Name());
163  if (!LockFile.Lock())
164  return;
165  // Remove the oldest file that has been "deleted":
166  isyslog("low disk space while recording, trying to remove a deleted recording...");
167  cThreadLock DeletedRecordingsLock(&DeletedRecordings);
168  if (DeletedRecordings.Count()) {
170  cRecording *r0 = NULL;
171  while (r) {
172  if (r->IsOnVideoDirectoryFileSystem()) { // only remove recordings that will actually increase the free video disk space
173  if (!r0 || r->Start() < r0->Start())
174  r0 = r;
175  }
176  r = DeletedRecordings.Next(r);
177  }
178  if (r0) {
179  if (r0->Remove())
180  LastFreeDiskCheck += REMOVELATENCY / Factor;
182  return;
183  }
184  }
185  else {
186  // DeletedRecordings was empty, so to be absolutely sure there are no
187  // deleted recordings we need to double check:
189  if (DeletedRecordings.Count())
190  return; // the next call will actually remove it
191  }
192  // No "deleted" files to remove, so let's see if we can delete a recording:
193  if (Priority > 0) {
194  isyslog("...no deleted recording found, trying to delete an old recording...");
195  cThreadLock RecordingsLock(&Recordings);
196  if (Recordings.Count()) {
197  cRecording *r = Recordings.First();
198  cRecording *r0 = NULL;
199  while (r) {
200  if (r->IsOnVideoDirectoryFileSystem()) { // only delete recordings that will actually increase the free video disk space
201  if (!r->IsEdited() && r->Lifetime() < MAXLIFETIME) { // edited recordings and recordings with MAXLIFETIME live forever
202  if ((r->Lifetime() == 0 && Priority > r->Priority()) || // the recording has no guaranteed lifetime and the new recording has higher priority
203  (r->Lifetime() > 0 && (time(NULL) - r->Start()) / SECSINDAY >= r->Lifetime())) { // the recording's guaranteed lifetime has expired
204  if (r0) {
205  if (r->Priority() < r0->Priority() || (r->Priority() == r0->Priority() && r->Start() < r0->Start()))
206  r0 = r; // in any case we delete the one with the lowest priority (or the older one in case of equal priorities)
207  }
208  else
209  r0 = r;
210  }
211  }
212  }
213  r = Recordings.Next(r);
214  }
215  if (r0 && r0->Delete()) {
216  Recordings.Del(r0);
217  return;
218  }
219  }
220  // Unable to free disk space, but there's nothing we can do about that...
221  isyslog("...no old recording found, giving up");
222  }
223  else
224  isyslog("...no deleted recording found, priority %d too low to trigger deleting an old recording", Priority);
225  Skins.QueueMessage(mtWarning, tr("Low disk space!"), 5, -1);
226  }
227  LastFreeDiskCheck = time(NULL);
228  }
229 }
230 
231 // --- Clear vanished recordings ---------------------------------------------
232 
234 {
235  cThreadLock RecordingsLock(&Recordings); // yes, it *is* Recordings!
236  VanishedRecordings.Clear();
237 }
238 
239 // --- cResumeFile -----------------------------------------------------------
240 
241 cResumeFile::cResumeFile(const char *FileName, bool IsPesRecording)
242 {
243  isPesRecording = IsPesRecording;
244  const char *Suffix = isPesRecording ? RESUMEFILESUFFIX ".vdr" : RESUMEFILESUFFIX;
245  fileName = MALLOC(char, strlen(FileName) + strlen(Suffix) + 1);
246  if (fileName) {
247  strcpy(fileName, FileName);
248  sprintf(fileName + strlen(fileName), Suffix, Setup.ResumeID ? "." : "", Setup.ResumeID ? *itoa(Setup.ResumeID) : "");
249  }
250  else
251  esyslog("ERROR: can't allocate memory for resume file name");
252 }
253 
255 {
256  free(fileName);
257 }
258 
260 {
261  int resume = -1;
262  if (fileName) {
263  struct stat st;
264  if (stat(fileName, &st) == 0) {
265  if ((st.st_mode & S_IWUSR) == 0) // no write access, assume no resume
266  return -1;
267  }
268  if (isPesRecording) {
269  int f = open(fileName, O_RDONLY);
270  if (f >= 0) {
271  if (safe_read(f, &resume, sizeof(resume)) != sizeof(resume)) {
272  resume = -1;
274  }
275  close(f);
276  }
277  else if (errno != ENOENT)
279  }
280  else {
281  FILE *f = fopen(fileName, "r");
282  if (f) {
283  cReadLine ReadLine;
284  char *s;
285  int line = 0;
286  while ((s = ReadLine.Read(f)) != NULL) {
287  ++line;
288  char *t = skipspace(s + 1);
289  switch (*s) {
290  case 'I': resume = atoi(t);
291  break;
292  default: ;
293  }
294  }
295  fclose(f);
296  }
297  else if (errno != ENOENT)
299  }
300  }
301  return resume;
302 }
303 
304 bool cResumeFile::Save(int Index)
305 {
306  if (fileName) {
307  if (isPesRecording) {
308  int f = open(fileName, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE);
309  if (f >= 0) {
310  if (safe_write(f, &Index, sizeof(Index)) < 0)
312  close(f);
314  return true;
315  }
316  }
317  else {
318  FILE *f = fopen(fileName, "w");
319  if (f) {
320  fprintf(f, "I %d\n", Index);
321  fclose(f);
323  }
324  else
326  return true;
327  }
328  }
329  return false;
330 }
331 
333 {
334  if (fileName) {
335  if (remove(fileName) == 0)
337  else if (errno != ENOENT)
339  }
340 }
341 
342 // --- cRecordingInfo --------------------------------------------------------
343 
344 cRecordingInfo::cRecordingInfo(const cChannel *Channel, const cEvent *Event)
345 {
346  channelID = Channel ? Channel->GetChannelID() : tChannelID::InvalidID;
347  channelName = Channel ? strdup(Channel->Name()) : NULL;
348  ownEvent = Event ? NULL : new cEvent(0);
349  event = ownEvent ? ownEvent : Event;
350  aux = NULL;
354  fileName = NULL;
355  if (Channel) {
356  // Since the EPG data's component records can carry only a single
357  // language code, let's see whether the channel's PID data has
358  // more information:
360  if (!Components)
361  Components = new cComponents;
362  for (int i = 0; i < MAXAPIDS; i++) {
363  const char *s = Channel->Alang(i);
364  if (*s) {
365  tComponent *Component = Components->GetComponent(i, 2, 3);
366  if (!Component)
367  Components->SetComponent(Components->NumComponents(), 2, 3, s, NULL);
368  else if (strlen(s) > strlen(Component->language))
369  strn0cpy(Component->language, s, sizeof(Component->language));
370  }
371  }
372  // There's no "multiple languages" for Dolby Digital tracks, but
373  // we do the same procedure here, too, in case there is no component
374  // information at all:
375  for (int i = 0; i < MAXDPIDS; i++) {
376  const char *s = Channel->Dlang(i);
377  if (*s) {
378  tComponent *Component = Components->GetComponent(i, 4, 0); // AC3 component according to the DVB standard
379  if (!Component)
380  Component = Components->GetComponent(i, 2, 5); // fallback "Dolby" component according to the "Premiere pseudo standard"
381  if (!Component)
382  Components->SetComponent(Components->NumComponents(), 2, 5, s, NULL);
383  else if (strlen(s) > strlen(Component->language))
384  strn0cpy(Component->language, s, sizeof(Component->language));
385  }
386  }
387  // The same applies to subtitles:
388  for (int i = 0; i < MAXSPIDS; i++) {
389  const char *s = Channel->Slang(i);
390  if (*s) {
391  tComponent *Component = Components->GetComponent(i, 3, 3);
392  if (!Component)
393  Components->SetComponent(Components->NumComponents(), 3, 3, s, NULL);
394  else if (strlen(s) > strlen(Component->language))
395  strn0cpy(Component->language, s, sizeof(Component->language));
396  }
397  }
398  if (Components != event->Components())
399  ((cEvent *)event)->SetComponents(Components);
400  }
401 }
402 
403 cRecordingInfo::cRecordingInfo(const char *FileName)
404 {
406  channelName = NULL;
407  ownEvent = new cEvent(0);
408  event = ownEvent;
409  aux = NULL;
413  fileName = strdup(cString::sprintf("%s%s", FileName, INFOFILESUFFIX));
414 }
415 
417 {
418  delete ownEvent;
419  free(aux);
420  free(channelName);
421  free(fileName);
422 }
423 
424 void cRecordingInfo::SetData(const char *Title, const char *ShortText, const char *Description)
425 {
426  if (!isempty(Title))
427  ((cEvent *)event)->SetTitle(Title);
428  if (!isempty(ShortText))
429  ((cEvent *)event)->SetShortText(ShortText);
430  if (!isempty(Description))
431  ((cEvent *)event)->SetDescription(Description);
432 }
433 
434 void cRecordingInfo::SetAux(const char *Aux)
435 {
436  free(aux);
437  aux = Aux ? strdup(Aux) : NULL;
438 }
439 
440 void cRecordingInfo::SetFramesPerSecond(double FramesPerSecond)
441 {
443 }
444 
445 void cRecordingInfo::SetFileName(const char *FileName)
446 {
447  bool IsPesRecording = fileName && endswith(fileName, ".vdr");
448  free(fileName);
449  fileName = strdup(cString::sprintf("%s%s", FileName, IsPesRecording ? INFOFILESUFFIX ".vdr" : INFOFILESUFFIX));
450 }
451 
452 bool cRecordingInfo::Read(FILE *f)
453 {
454  if (ownEvent) {
455  cReadLine ReadLine;
456  char *s;
457  int line = 0;
458  while ((s = ReadLine.Read(f)) != NULL) {
459  ++line;
460  char *t = skipspace(s + 1);
461  switch (*s) {
462  case 'C': {
463  char *p = strchr(t, ' ');
464  if (p) {
465  free(channelName);
466  channelName = strdup(compactspace(p));
467  *p = 0; // strips optional channel name
468  }
469  if (*t)
471  }
472  break;
473  case 'E': {
474  unsigned int EventID;
475  time_t StartTime;
476  int Duration;
477  unsigned int TableID = 0;
478  unsigned int Version = 0xFF;
479  int n = sscanf(t, "%u %ld %d %X %X", &EventID, &StartTime, &Duration, &TableID, &Version);
480  if (n >= 3 && n <= 5) {
481  ownEvent->SetEventID(EventID);
482  ownEvent->SetStartTime(StartTime);
483  ownEvent->SetDuration(Duration);
484  ownEvent->SetTableID(uchar(TableID));
485  ownEvent->SetVersion(uchar(Version));
486  }
487  }
488  break;
489  case 'F': framesPerSecond = atod(t);
490  break;
491  case 'L': lifetime = atoi(t);
492  break;
493  case 'P': priority = atoi(t);
494  break;
495  case '@': free(aux);
496  aux = strdup(t);
497  break;
498  case '#': break; // comments are ignored
499  default: if (!ownEvent->Parse(s)) {
500  esyslog("ERROR: EPG data problem in line %d", line);
501  return false;
502  }
503  break;
504  }
505  }
506  return true;
507  }
508  return false;
509 }
510 
511 bool cRecordingInfo::Write(FILE *f, const char *Prefix) const
512 {
513  if (channelID.Valid())
514  fprintf(f, "%sC %s%s%s\n", Prefix, *channelID.ToString(), channelName ? " " : "", channelName ? channelName : "");
515  event->Dump(f, Prefix, true);
516  fprintf(f, "%sF %s\n", Prefix, *dtoa(framesPerSecond, "%.10g"));
517  fprintf(f, "%sP %d\n", Prefix, priority);
518  fprintf(f, "%sL %d\n", Prefix, lifetime);
519  if (aux)
520  fprintf(f, "%s@ %s\n", Prefix, aux);
521  return true;
522 }
523 
525 {
526  bool Result = false;
527  if (fileName) {
528  FILE *f = fopen(fileName, "r");
529  if (f) {
530  if (Read(f))
531  Result = true;
532  else
533  esyslog("ERROR: EPG data problem in file %s", fileName);
534  fclose(f);
535  }
536  else if (errno != ENOENT)
538  }
539  return Result;
540 }
541 
542 bool cRecordingInfo::Write(void) const
543 {
544  bool Result = false;
545  if (fileName) {
546  cSafeFile f(fileName);
547  if (f.Open()) {
548  if (Write(f))
549  Result = true;
550  f.Close();
551  }
552  else
554  }
555  return Result;
556 }
557 
558 // --- cRecording ------------------------------------------------------------
559 
560 #define RESUME_NOT_INITIALIZED (-2)
561 
562 struct tCharExchange { char a; char b; };
564  { FOLDERDELIMCHAR, '/' },
565  { '/', FOLDERDELIMCHAR },
566  { ' ', '_' },
567  // backwards compatibility:
568  { '\'', '\'' },
569  { '\'', '\x01' },
570  { '/', '\x02' },
571  { 0, 0 }
572  };
573 
574 const char *InvalidChars = "\"\\/:*?|<>#";
575 
576 bool NeedsConversion(const char *p)
577 {
578  return DirectoryEncoding &&
579  (strchr(InvalidChars, *p) // characters that can't be part of a Windows file/directory name
580  || *p == '.' && (!*(p + 1) || *(p + 1) == FOLDERDELIMCHAR)); // Windows can't handle '.' at the end of file/directory names
581 }
582 
583 char *ExchangeChars(char *s, bool ToFileSystem)
584 {
585  char *p = s;
586  while (*p) {
587  if (DirectoryEncoding) {
588  // Some file systems can't handle all characters, so we
589  // have to take extra efforts to encode/decode them:
590  if (ToFileSystem) {
591  switch (*p) {
592  // characters that can be mapped to other characters:
593  case ' ': *p = '_'; break;
594  case FOLDERDELIMCHAR: *p = '/'; break;
595  case '/': *p = FOLDERDELIMCHAR; break;
596  // characters that have to be encoded:
597  default:
598  if (NeedsConversion(p)) {
599  int l = p - s;
600  if (char *NewBuffer = (char *)realloc(s, strlen(s) + 10)) {
601  s = NewBuffer;
602  p = s + l;
603  char buf[4];
604  sprintf(buf, "#%02X", (unsigned char)*p);
605  memmove(p + 2, p, strlen(p) + 1);
606  strncpy(p, buf, 3);
607  p += 2;
608  }
609  else
610  esyslog("ERROR: out of memory");
611  }
612  }
613  }
614  else {
615  switch (*p) {
616  // mapped characters:
617  case '_': *p = ' '; break;
618  case FOLDERDELIMCHAR: *p = '/'; break;
619  case '/': *p = FOLDERDELIMCHAR; break;
620  // encoded characters:
621  case '#': {
622  if (strlen(p) > 2 && isxdigit(*(p + 1)) && isxdigit(*(p + 2))) {
623  char buf[3];
624  sprintf(buf, "%c%c", *(p + 1), *(p + 2));
625  uchar c = uchar(strtol(buf, NULL, 16));
626  if (c) {
627  *p = c;
628  memmove(p + 1, p + 3, strlen(p) - 2);
629  }
630  }
631  }
632  break;
633  // backwards compatibility:
634  case '\x01': *p = '\''; break;
635  case '\x02': *p = '/'; break;
636  case '\x03': *p = ':'; break;
637  default: ;
638  }
639  }
640  }
641  else {
642  for (struct tCharExchange *ce = CharExchange; ce->a && ce->b; ce++) {
643  if (*p == (ToFileSystem ? ce->a : ce->b)) {
644  *p = ToFileSystem ? ce->b : ce->a;
645  break;
646  }
647  }
648  }
649  p++;
650  }
651  return s;
652 }
653 
654 char *LimitNameLengths(char *s, int PathMax, int NameMax)
655 {
656  // Limits the total length of the directory path in 's' to PathMax, and each
657  // individual directory name to NameMax. The lengths of characters that need
658  // conversion when using 's' as a file name are taken into account accordingly.
659  // If a directory name exceeds NameMax, it will be truncated. If the whole
660  // directory path exceeds PathMax, individual directory names will be shortened
661  // (from right to left) until the limit is met, or until the currently handled
662  // directory name consists of only a single character. All operations are performed
663  // directly on the given 's', which may become shorter (but never longer) than
664  // the original value.
665  // Returns a pointer to 's'.
666  int Length = strlen(s);
667  int PathLength = 0;
668  // Collect the resulting lengths of each character:
669  bool NameTooLong = false;
670  int8_t a[Length];
671  int n = 0;
672  int NameLength = 0;
673  for (char *p = s; *p; p++) {
674  if (*p == FOLDERDELIMCHAR) {
675  a[n] = -1; // FOLDERDELIMCHAR is a single character, neg. sign marks it
676  NameTooLong |= NameLength > NameMax;
677  NameLength = 0;
678  PathLength += 1;
679  }
680  else if (NeedsConversion(p)) {
681  a[n] = 3; // "#xx"
682  NameLength += 3;
683  PathLength += 3;
684  }
685  else {
686  int8_t l = Utf8CharLen(p);
687  a[n] = l;
688  NameLength += l;
689  PathLength += l;
690  while (l-- > 1) {
691  a[++n] = 0;
692  p++;
693  }
694  }
695  n++;
696  }
697  NameTooLong |= NameLength > NameMax;
698  // Limit names to NameMax:
699  if (NameTooLong) {
700  while (n > 0) {
701  // Calculate the length of the current name:
702  int NameLength = 0;
703  int i = n;
704  int b = i;
705  while (i-- > 0 && a[i] >= 0) {
706  NameLength += a[i];
707  b = i;
708  }
709  // Shorten the name if necessary:
710  if (NameLength > NameMax) {
711  int l = 0;
712  i = n;
713  while (i-- > 0 && a[i] >= 0) {
714  l += a[i];
715  if (NameLength - l <= NameMax) {
716  memmove(s + i, s + n, Length - n + 1);
717  memmove(a + i, a + n, Length - n + 1);
718  Length -= n - i;
719  PathLength -= l;
720  break;
721  }
722  }
723  }
724  // Switch to the next name:
725  n = b - 1;
726  }
727  }
728  // Limit path to PathMax:
729  n = Length;
730  while (PathLength > PathMax && n > 0) {
731  // Calculate how much to cut off the current name:
732  int i = n;
733  int b = i;
734  int l = 0;
735  while (--i > 0 && a[i - 1] >= 0) {
736  if (a[i] > 0) {
737  l += a[i];
738  b = i;
739  if (PathLength - l <= PathMax)
740  break;
741  }
742  }
743  // Shorten the name if necessary:
744  if (l > 0) {
745  memmove(s + b, s + n, Length - n + 1);
746  Length -= n - b;
747  PathLength -= l;
748  }
749  // Switch to the next name:
750  n = i - 1;
751  }
752  return s;
753 }
754 
755 cRecording::cRecording(cTimer *Timer, const cEvent *Event)
756 {
758  titleBuffer = NULL;
760  fileName = NULL;
761  name = NULL;
762  fileSizeMB = -1; // unknown
763  channel = Timer->Channel()->Number();
765  isPesRecording = false;
766  isOnVideoDirectoryFileSystem = -1; // unknown
768  numFrames = -1;
769  deleted = 0;
770  // set up the actual name:
771  const char *Title = Event ? Event->Title() : NULL;
772  const char *Subtitle = Event ? Event->ShortText() : NULL;
773  if (isempty(Title))
774  Title = Timer->Channel()->Name();
775  if (isempty(Subtitle))
776  Subtitle = " ";
777  const char *macroTITLE = strstr(Timer->File(), TIMERMACRO_TITLE);
778  const char *macroEPISODE = strstr(Timer->File(), TIMERMACRO_EPISODE);
779  if (macroTITLE || macroEPISODE) {
780  name = strdup(Timer->File());
781  name = strreplace(name, TIMERMACRO_TITLE, Title);
782  name = strreplace(name, TIMERMACRO_EPISODE, Subtitle);
783  // avoid blanks at the end:
784  int l = strlen(name);
785  while (l-- > 2) {
786  if (name[l] == ' ' && name[l - 1] != FOLDERDELIMCHAR)
787  name[l] = 0;
788  else
789  break;
790  }
791  if (Timer->IsSingleEvent()) {
792  Timer->SetFile(name); // this was an instant recording, so let's set the actual data
794  }
795  }
796  else if (Timer->IsSingleEvent() || !Setup.UseSubtitle)
797  name = strdup(Timer->File());
798  else
799  name = strdup(cString::sprintf("%s%c%s", Timer->File(), FOLDERDELIMCHAR, Subtitle));
800  // substitute characters that would cause problems in file names:
801  strreplace(name, '\n', ' ');
802  start = Timer->StartTime();
803  priority = Timer->Priority();
804  lifetime = Timer->Lifetime();
805  // handle info:
806  info = new cRecordingInfo(Timer->Channel(), Event);
807  info->SetAux(Timer->Aux());
810 }
811 
812 cRecording::cRecording(const char *FileName)
813 {
815  fileSizeMB = -1; // unknown
816  channel = -1;
817  instanceId = -1;
818  priority = MAXPRIORITY; // assume maximum in case there is no info file
820  isPesRecording = false;
821  isOnVideoDirectoryFileSystem = -1; // unknown
823  numFrames = -1;
824  deleted = 0;
825  titleBuffer = NULL;
827  FileName = fileName = strdup(FileName);
828  if (*(fileName + strlen(fileName) - 1) == '/')
829  *(fileName + strlen(fileName) - 1) = 0;
830  if (strstr(FileName, cVideoDirectory::Name()) == FileName)
831  FileName += strlen(cVideoDirectory::Name()) + 1;
832  const char *p = strrchr(FileName, '/');
833 
834  name = NULL;
836  if (p) {
837  time_t now = time(NULL);
838  struct tm tm_r;
839  struct tm t = *localtime_r(&now, &tm_r); // this initializes the time zone in 't'
840  t.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting
841  if (7 == sscanf(p + 1, DATAFORMATTS, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &channel, &instanceId)
842  || 7 == sscanf(p + 1, DATAFORMATPES, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &priority, &lifetime)) {
843  t.tm_year -= 1900;
844  t.tm_mon--;
845  t.tm_sec = 0;
846  start = mktime(&t);
847  name = MALLOC(char, p - FileName + 1);
848  strncpy(name, FileName, p - FileName);
849  name[p - FileName] = 0;
850  name = ExchangeChars(name, false);
852  }
853  else
854  return;
855  GetResume();
856  // read an optional info file:
857  cString InfoFileName = cString::sprintf("%s%s", fileName, isPesRecording ? INFOFILESUFFIX ".vdr" : INFOFILESUFFIX);
858  FILE *f = fopen(InfoFileName, "r");
859  if (f) {
860  if (!info->Read(f))
861  esyslog("ERROR: EPG data problem in file %s", *InfoFileName);
862  else if (!isPesRecording) {
866  }
867  fclose(f);
868  }
869  else if (errno == ENOENT)
871  else
872  LOG_ERROR_STR(*InfoFileName);
873 #ifdef SUMMARYFALLBACK
874  // fall back to the old 'summary.vdr' if there was no 'info.vdr':
875  if (isempty(info->Title())) {
876  cString SummaryFileName = cString::sprintf("%s%s", fileName, SUMMARYFILESUFFIX);
877  FILE *f = fopen(SummaryFileName, "r");
878  if (f) {
879  int line = 0;
880  char *data[3] = { NULL };
881  cReadLine ReadLine;
882  char *s;
883  while ((s = ReadLine.Read(f)) != NULL) {
884  if (*s || line > 1) {
885  if (data[line]) {
886  int len = strlen(s);
887  len += strlen(data[line]) + 1;
888  if (char *NewBuffer = (char *)realloc(data[line], len + 1)) {
889  data[line] = NewBuffer;
890  strcat(data[line], "\n");
891  strcat(data[line], s);
892  }
893  else
894  esyslog("ERROR: out of memory");
895  }
896  else
897  data[line] = strdup(s);
898  }
899  else
900  line++;
901  }
902  fclose(f);
903  if (!data[2]) {
904  data[2] = data[1];
905  data[1] = NULL;
906  }
907  else if (data[1] && data[2]) {
908  // if line 1 is too long, it can't be the short text,
909  // so assume the short text is missing and concatenate
910  // line 1 and line 2 to be the long text:
911  int len = strlen(data[1]);
912  if (len > 80) {
913  if (char *NewBuffer = (char *)realloc(data[1], len + 1 + strlen(data[2]) + 1)) {
914  data[1] = NewBuffer;
915  strcat(data[1], "\n");
916  strcat(data[1], data[2]);
917  free(data[2]);
918  data[2] = data[1];
919  data[1] = NULL;
920  }
921  else
922  esyslog("ERROR: out of memory");
923  }
924  }
925  info->SetData(data[0], data[1], data[2]);
926  for (int i = 0; i < 3; i ++)
927  free(data[i]);
928  }
929  else if (errno != ENOENT)
930  LOG_ERROR_STR(*SummaryFileName);
931  }
932 #endif
933  }
934 }
935 
937 {
938  free(titleBuffer);
939  free(sortBufferName);
940  free(sortBufferTime);
941  free(fileName);
942  free(name);
943  delete info;
944 }
945 
946 char *cRecording::StripEpisodeName(char *s, bool Strip)
947 {
948  char *t = s, *s1 = NULL, *s2 = NULL;
949  while (*t) {
950  if (*t == '/') {
951  if (s1) {
952  if (s2)
953  s1 = s2;
954  s2 = t;
955  }
956  else
957  s1 = t;
958  }
959  t++;
960  }
961  if (s1 && s2) {
962  // To have folders sorted before plain recordings, the '/' s1 points to
963  // is replaced by the character '1'. All other slashes will be replaced
964  // by '0' in SortName() (see below), which will result in the desired
965  // sequence:
966  *s1 = '1';
967  if (Strip) {
968  s1++;
969  memmove(s1, s2, t - s2 + 1);
970  }
971  }
972  return s;
973 }
974 
975 char *cRecording::SortName(void) const
976 {
978  if (!*sb) {
980  char buf[32];
981  struct tm tm_r;
982  strftime(buf, sizeof(buf), "%Y%m%d%H%I", localtime_r(&start, &tm_r));
983  *sb = strdup(buf);
984  }
985  else {
986  char *s = strdup(FileName() + strlen(cVideoDirectory::Name()));
989  strreplace(s, '/', '0'); // some locales ignore '/' when sorting
990  int l = strxfrm(NULL, s, 0) + 1;
991  *sb = MALLOC(char, l);
992  strxfrm(*sb, s, l);
993  free(s);
994  }
995  }
996  return *sb;
997 }
998 
1000 {
1001  free(sortBufferName);
1002  free(sortBufferTime);
1003  sortBufferName = sortBufferTime = NULL;
1004 }
1005 
1006 int cRecording::GetResume(void) const
1007 {
1008  if (resume == RESUME_NOT_INITIALIZED) {
1009  cResumeFile ResumeFile(FileName(), isPesRecording);
1010  resume = ResumeFile.Read();
1011  }
1012  return resume;
1013 }
1014 
1015 int cRecording::Compare(const cListObject &ListObject) const
1016 {
1017  cRecording *r = (cRecording *)&ListObject;
1018  return strcasecmp(SortName(), r->SortName());
1019 }
1020 
1021 bool cRecording::IsInPath(const char *Path)
1022 {
1023  if (isempty(Path))
1024  return true;
1025  int l = strlen(Path);
1026  return strncmp(Path, name, l) == 0 && (name[l] == FOLDERDELIMCHAR);
1027 }
1028 
1030 {
1031  if (char *s = strrchr(name, FOLDERDELIMCHAR))
1032  return cString(name, s);
1033  return "";
1034 }
1035 
1037 {
1038  if (char *s = strrchr(name, FOLDERDELIMCHAR))
1039  return cString(s + 1);
1040  return name;
1041 }
1042 
1043 const char *cRecording::FileName(void) const
1044 {
1045  if (!fileName) {
1046  struct tm tm_r;
1047  struct tm *t = localtime_r(&start, &tm_r);
1048  const char *fmt = isPesRecording ? NAMEFORMATPES : NAMEFORMATTS;
1049  int ch = isPesRecording ? priority : channel;
1050  int ri = isPesRecording ? lifetime : instanceId;
1051  char *Name = LimitNameLengths(strdup(name), DirectoryPathMax - strlen(cVideoDirectory::Name()) - 1 - 42, DirectoryNameMax); // 42 = length of an actual recording directory name (generated with DATAFORMATTS) plus some reserve
1052  if (strcmp(Name, name) != 0)
1053  dsyslog("recording file name '%s' truncated to '%s'", name, Name);
1054  Name = ExchangeChars(Name, true);
1055  fileName = strdup(cString::sprintf(fmt, cVideoDirectory::Name(), Name, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, ch, ri));
1056  free(Name);
1057  }
1058  return fileName;
1059 }
1060 
1061 const char *cRecording::Title(char Delimiter, bool NewIndicator, int Level) const
1062 {
1063  const char *New = NewIndicator && IsNew() ? Setup.WarEagleIcons ? IsLangUtf8() ? ICON_NEW_UTF8 : ICON_NEW : "*" : " ";
1064  free(titleBuffer);
1065  titleBuffer = NULL;
1066  if (Level < 0 || Level == HierarchyLevels()) {
1067  struct tm tm_r;
1068  struct tm *t = localtime_r(&start, &tm_r);
1069  char *s;
1070  if (Level > 0 && (s = strrchr(name, FOLDERDELIMCHAR)) != NULL)
1071  s++;
1072  else
1073  s = name;
1074  cString Length("");
1075  if (NewIndicator) {
1076  int Minutes = max(0, (LengthInSeconds() + 30) / 60);
1077  Length = cString::sprintf("%c%d:%02d",
1078  Delimiter,
1079  Minutes / 60,
1080  Minutes % 60
1081  );
1082  }
1083  titleBuffer = strdup(cString::sprintf("%02d.%02d.%02d%c%02d:%02d%s%s%c%s",
1084  t->tm_mday,
1085  t->tm_mon + 1,
1086  t->tm_year % 100,
1087  Delimiter,
1088  t->tm_hour,
1089  t->tm_min,
1090  *Length,
1091  New,
1092  Delimiter,
1093  s));
1094  // let's not display a trailing FOLDERDELIMCHAR:
1095  if (!NewIndicator)
1097  s = &titleBuffer[strlen(titleBuffer) - 1];
1098  if (*s == FOLDERDELIMCHAR)
1099  *s = 0;
1100  }
1101  else if (Level < HierarchyLevels()) {
1102  const char *s = name;
1103  const char *p = s;
1104  while (*++s) {
1105  if (*s == FOLDERDELIMCHAR) {
1106  if (Level--)
1107  p = s + 1;
1108  else
1109  break;
1110  }
1111  }
1112  titleBuffer = MALLOC(char, s - p + 3);
1113  *titleBuffer = Delimiter;
1114  *(titleBuffer + 1) = Delimiter;
1115  strn0cpy(titleBuffer + 2, p, s - p + 1);
1116  }
1117  else
1118  return "";
1119  return titleBuffer;
1120 }
1121 
1122 const char *cRecording::PrefixFileName(char Prefix)
1123 {
1125  if (*p) {
1126  free(fileName);
1127  fileName = strdup(p);
1128  return fileName;
1129  }
1130  return NULL;
1131 }
1132 
1134 {
1135  const char *s = name;
1136  int level = 0;
1137  while (*++s) {
1138  if (*s == FOLDERDELIMCHAR)
1139  level++;
1140  }
1141  return level;
1142 }
1143 
1144 bool cRecording::IsEdited(void) const
1145 {
1146  const char *s = strrchr(name, FOLDERDELIMCHAR);
1147  s = !s ? name : s + 1;
1148  return *s == '%';
1149 }
1150 
1152 {
1156 }
1157 
1159 {
1160  return access(cMarks::MarksFileName(this), F_OK) == 0;
1161 }
1162 
1164 {
1165  if (remove(cMarks::MarksFileName(this)) < 0) {
1166  if (errno != ENOENT) {
1168  return false;
1169  }
1170  }
1171  return true;
1172 }
1173 
1175 {
1176  info->Read();
1177  priority = info->priority;
1178  lifetime = info->lifetime;
1180 }
1181 
1182 bool cRecording::WriteInfo(const char *OtherFileName)
1183 {
1184  cString InfoFileName = cString::sprintf("%s%s", OtherFileName ? OtherFileName : FileName(), isPesRecording ? INFOFILESUFFIX ".vdr" : INFOFILESUFFIX);
1185  cSafeFile f(InfoFileName);
1186  if (f.Open()) {
1187  info->Write(f);
1188  f.Close();
1189  }
1190  else
1191  LOG_ERROR_STR(*InfoFileName);
1192  return true;
1193 }
1194 
1195 void cRecording::SetStartTime(time_t Start)
1196 {
1197  start = Start;
1198  free(fileName);
1199  fileName = NULL;
1200 }
1201 
1202 bool cRecording::ChangePriorityLifetime(int NewPriority, int NewLifetime)
1203 {
1204  if (NewPriority != Priority() || NewLifetime != Lifetime()) {
1205  dsyslog("changing priority/lifetime of '%s' to %d/%d", Name(), NewPriority, NewLifetime);
1206  if (IsPesRecording()) {
1207  cString OldFileName = FileName();
1208  priority = NewPriority;
1209  lifetime = NewLifetime;
1210  free(fileName);
1211  fileName = NULL;
1212  cString NewFileName = FileName();
1213  if (!cVideoDirectory::RenameVideoFile(OldFileName, NewFileName))
1214  return false;
1215  info->SetFileName(NewFileName);
1216  }
1217  else {
1218  priority = info->priority = NewPriority;
1219  lifetime = info->lifetime = NewLifetime;
1220  if (!WriteInfo())
1221  return false;
1222  }
1225  }
1226  return true;
1227 }
1228 
1229 bool cRecording::ChangeName(const char *NewName)
1230 {
1231  if (strcmp(NewName, Name())) {
1232  dsyslog("changing name of '%s' to '%s'", Name(), NewName);
1233  cString OldName = Name();
1234  cString OldFileName = FileName();
1235  free(fileName);
1236  fileName = NULL;
1237  free(name);
1238  name = strdup(NewName);
1239  cString NewFileName = FileName();
1240  if (!(MakeDirs(NewFileName, true) && cVideoDirectory::MoveVideoFile(OldFileName, NewFileName))) {
1241  free(name);
1242  name = strdup(OldName);
1243  free(fileName);
1244  fileName = strdup(OldFileName);
1245  return false;
1246  }
1247  isOnVideoDirectoryFileSystem = -1; // it might have been moved to a different file system
1248  ClearSortName();
1251  }
1252  return true;
1253 }
1254 
1256 {
1257  bool result = true;
1258  char *NewName = strdup(FileName());
1259  char *ext = strrchr(NewName, '.');
1260  if (ext && strcmp(ext, RECEXT) == 0) {
1261  strncpy(ext, DELEXT, strlen(ext));
1262  if (access(NewName, F_OK) == 0) {
1263  // the new name already exists, so let's remove that one first:
1264  isyslog("removing recording '%s'", NewName);
1266  }
1267  isyslog("deleting recording '%s'", FileName());
1268  if (access(FileName(), F_OK) == 0) {
1269  result = cVideoDirectory::RenameVideoFile(FileName(), NewName);
1271  }
1272  else {
1273  isyslog("recording '%s' vanished", FileName());
1274  result = true; // well, we were going to delete it, anyway
1275  }
1276  }
1277  free(NewName);
1278  return result;
1279 }
1280 
1282 {
1283  // let's do a final safety check here:
1284  if (!endswith(FileName(), DELEXT)) {
1285  esyslog("attempt to remove recording %s", FileName());
1286  return false;
1287  }
1288  isyslog("removing recording %s", FileName());
1290 }
1291 
1293 {
1294  bool result = true;
1295  char *NewName = strdup(FileName());
1296  char *ext = strrchr(NewName, '.');
1297  if (ext && strcmp(ext, DELEXT) == 0) {
1298  strncpy(ext, RECEXT, strlen(ext));
1299  if (access(NewName, F_OK) == 0) {
1300  // the new name already exists, so let's not remove that one:
1301  esyslog("ERROR: attempt to undelete '%s', while recording '%s' exists", FileName(), NewName);
1302  result = false;
1303  }
1304  else {
1305  isyslog("undeleting recording '%s'", FileName());
1306  if (access(FileName(), F_OK) == 0)
1307  result = cVideoDirectory::RenameVideoFile(FileName(), NewName);
1308  else {
1309  isyslog("deleted recording '%s' vanished", FileName());
1310  result = false;
1311  }
1312  }
1313  }
1314  free(NewName);
1315  return result;
1316 }
1317 
1318 int cRecording::IsInUse(void) const
1319 {
1320  int Use = ruNone;
1322  Use |= ruTimer;
1324  Use |= ruReplay;
1326  return Use;
1327 }
1328 
1329 void cRecording::ResetResume(void) const
1330 {
1332 }
1333 
1334 int cRecording::NumFrames(void) const
1335 {
1336  if (numFrames < 0) {
1339  return nf; // check again later for ongoing recordings
1340  numFrames = nf;
1341  }
1342  return numFrames;
1343 }
1344 
1346 {
1347  int nf = NumFrames();
1348  if (nf >= 0)
1349  return int(nf / FramesPerSecond());
1350  return -1;
1351 }
1352 
1353 int cRecording::FileSizeMB(void) const
1354 {
1355  if (fileSizeMB < 0) {
1356  int fs = DirSizeMB(FileName());
1358  return fs; // check again later for ongoing recordings
1359  fileSizeMB = fs;
1360  }
1361  return fileSizeMB;
1362 }
1363 
1364 // --- cRecordings -----------------------------------------------------------
1365 
1367 
1368 char *cRecordings::updateFileName = NULL;
1369 
1371 :cThread("video directory scanner")
1372 {
1373  deleted = Deleted;
1374  initial = true;
1375  lastUpdate = 0;
1376  state = 0;
1377 }
1378 
1380 {
1381  Cancel(3);
1382 }
1383 
1385 {
1386  Refresh();
1387 }
1388 
1390 {
1391  if (!updateFileName)
1392  updateFileName = strdup(AddDirectory(cVideoDirectory::Name(), ".update"));
1393  return updateFileName;
1394 }
1395 
1396 void cRecordings::Refresh(bool Foreground)
1397 {
1398  lastUpdate = time(NULL); // doing this first to make sure we don't miss anything
1399  initial = Count() == 0; // no name checking if the list is initially empty
1400  if (deleted) {
1401  Lock();
1402  Clear();
1403  ChangeState();
1404  Unlock();
1405  }
1406  ScanVideoDir(cVideoDirectory::Name(), Foreground);
1407 }
1408 
1409 bool cRecordings::ScanVideoDir(const char *DirName, bool Foreground, int LinkLevel, int DirLevel)
1410 {
1411  bool DoChangeState = false;
1412  // Find any new recordings:
1413  cReadDir d(DirName);
1414  struct dirent *e;
1415  while ((Foreground || Running()) && (e = d.Next()) != NULL) {
1416  cString buffer = AddDirectory(DirName, e->d_name);
1417  struct stat st;
1418  if (lstat(buffer, &st) == 0) {
1419  int Link = 0;
1420  if (S_ISLNK(st.st_mode)) {
1421  if (LinkLevel > MAX_LINK_LEVEL) {
1422  isyslog("max link level exceeded - not scanning %s", *buffer);
1423  continue;
1424  }
1425  Link = 1;
1426  if (stat(buffer, &st) != 0)
1427  continue;
1428  }
1429  if (S_ISDIR(st.st_mode)) {
1430  if (endswith(buffer, deleted ? DELEXT : RECEXT)) {
1431  if (deleted || initial || !GetByName(buffer)) {
1432  cRecording *r = new cRecording(buffer);
1433  if (r->Name()) {
1434  r->NumFrames(); // initializes the numFrames member
1435  r->FileSizeMB(); // initializes the fileSizeMB member
1436  r->IsOnVideoDirectoryFileSystem(); // initializes the isOnVideoDirectoryFileSystem member
1437  if (deleted)
1438  r->deleted = time(NULL);
1439  Lock();
1440  Add(r);
1441  if (initial)
1442  ChangeState();
1443  else
1444  DoChangeState = true;
1445  Unlock();
1446  }
1447  else
1448  delete r;
1449  }
1450  }
1451  else
1452  DoChangeState |= ScanVideoDir(buffer, Foreground, LinkLevel + Link, DirLevel + 1);
1453  }
1454  }
1455  }
1456  // Handle any vanished recordings:
1457  if (!deleted && !initial && DirLevel == 0) {
1458  for (cRecording *recording = First(); recording; ) {
1459  cRecording *r = recording;
1460  recording = Next(recording);
1461  if (access(r->FileName(), F_OK) != 0) {
1462  Lock();
1463  Del(r, false);
1464  VanishedRecordings.Add(r);
1465  DoChangeState = true;
1466  Unlock();
1467  }
1468  }
1469  }
1470  if (DoChangeState && DirLevel == 0)
1471  ChangeState();
1472  return DoChangeState;
1473 }
1474 
1476 {
1477  int NewState = state;
1478  bool Result = State != NewState;
1479  State = state;
1480  return Result;
1481 }
1482 
1484 {
1485  bool needsUpdate = NeedsUpdate();
1487  if (!needsUpdate)
1488  lastUpdate = time(NULL); // make sure we don't trigger ourselves
1489 }
1490 
1492 {
1493  time_t lastModified = LastModifiedTime(UpdateFileName());
1494  if (lastModified > time(NULL))
1495  return false; // somebody's clock isn't running correctly
1496  return lastUpdate < lastModified;
1497 }
1498 
1499 bool cRecordings::Update(bool Wait)
1500 {
1501  if (Wait) {
1502  Refresh(true);
1503  return Count() > 0;
1504  }
1505  else
1506  Start();
1507  return false;
1508 }
1509 
1510 cRecording *cRecordings::GetByName(const char *FileName)
1511 {
1512  if (FileName) {
1513  LOCK_THREAD;
1514  for (cRecording *recording = First(); recording; recording = Next(recording)) {
1515  if (strcmp(recording->FileName(), FileName) == 0)
1516  return recording;
1517  }
1518  }
1519  return NULL;
1520 }
1521 
1522 void cRecordings::AddByName(const char *FileName, bool TriggerUpdate)
1523 {
1524  LOCK_THREAD;
1525  cRecording *recording = GetByName(FileName);
1526  if (!recording) {
1527  recording = new cRecording(FileName);
1528  Add(recording);
1529  ChangeState();
1530  if (TriggerUpdate)
1531  TouchUpdate();
1532  }
1533 }
1534 
1535 void cRecordings::DelByName(const char *FileName)
1536 {
1537  LOCK_THREAD;
1538  cRecording *recording = GetByName(FileName);
1539  cRecording *dummy = NULL;
1540  if (!recording)
1541  recording = dummy = new cRecording(FileName); // allows us to use a FileName that is not in the Recordings list
1542  cThreadLock DeletedRecordingsLock(&DeletedRecordings);
1543  if (!dummy)
1544  Del(recording, false);
1545  char *ext = strrchr(recording->fileName, '.');
1546  if (ext) {
1547  strncpy(ext, DELEXT, strlen(ext));
1548  if (access(recording->FileName(), F_OK) == 0) {
1549  recording->deleted = time(NULL);
1550  DeletedRecordings.Add(recording);
1551  recording = NULL; // to prevent it from being deleted below
1552  }
1553  }
1554  delete recording;
1555  ChangeState();
1556  TouchUpdate();
1557 }
1558 
1559 void cRecordings::UpdateByName(const char *FileName)
1560 {
1561  LOCK_THREAD;
1562  cRecording *recording = GetByName(FileName);
1563  if (recording)
1564  recording->ReadInfo();
1565 }
1566 
1568 {
1569  int size = 0;
1570  LOCK_THREAD;
1571  for (cRecording *recording = First(); recording; recording = Next(recording)) {
1572  int FileSizeMB = recording->FileSizeMB();
1573  if (FileSizeMB > 0 && recording->IsOnVideoDirectoryFileSystem())
1574  size += FileSizeMB;
1575  }
1576  return size;
1577 }
1578 
1580 {
1581  int size = 0;
1582  int length = 0;
1583  LOCK_THREAD;
1584  for (cRecording *recording = First(); recording; recording = Next(recording)) {
1585  if (recording->IsOnVideoDirectoryFileSystem()) {
1586  int FileSizeMB = recording->FileSizeMB();
1587  if (FileSizeMB > 0) {
1588  int LengthInSeconds = recording->LengthInSeconds();
1589  if (LengthInSeconds > 0) {
1590  if (LengthInSeconds / FileSizeMB < LIMIT_SECS_PER_MB_RADIO) { // don't count radio recordings
1591  size += FileSizeMB;
1592  length += LengthInSeconds;
1593  }
1594  }
1595  }
1596  }
1597  }
1598  return (size && length) ? double(size) * 60 / length : -1;
1599 }
1600 
1601 int cRecordings::PathIsInUse(const char *Path)
1602 {
1603  LOCK_THREAD;
1604  int Use = ruNone;
1605  for (cRecording *recording = First(); recording; recording = Next(recording)) {
1606  if (recording->IsInPath(Path))
1607  Use |= recording->IsInUse();
1608  }
1609  return Use;
1610 }
1611 
1613 {
1614  LOCK_THREAD;
1615  int n = 0;
1616  for (cRecording *recording = First(); recording; recording = Next(recording)) {
1617  if (recording->IsInPath(Path))
1618  n++;
1619  }
1620  return n;
1621 }
1622 
1623 bool cRecordings::MoveRecordings(const char *OldPath, const char *NewPath)
1624 {
1625  if (OldPath && NewPath && strcmp(OldPath, NewPath)) {
1626  LOCK_THREAD;
1627  dsyslog("moving '%s' to '%s'", OldPath, NewPath);
1628  for (cRecording *recording = First(); recording; recording = Next(recording)) {
1629  if (recording->IsInPath(OldPath)) {
1630  const char *p = recording->Name() + strlen(OldPath);
1631  cString NewName = cString::sprintf("%s%s", NewPath, p);
1632  if (!recording->ChangeName(NewName))
1633  return false;
1634  ChangeState();
1635  }
1636  }
1637  }
1638  return true;
1639 }
1640 
1641 void cRecordings::ResetResume(const char *ResumeFileName)
1642 {
1643  LOCK_THREAD;
1644  for (cRecording *recording = First(); recording; recording = Next(recording)) {
1645  if (!ResumeFileName || strncmp(ResumeFileName, recording->FileName(), strlen(recording->FileName())) == 0)
1646  recording->ResetResume();
1647  }
1648  ChangeState();
1649 }
1650 
1652 {
1653  LOCK_THREAD;
1654  for (cRecording *recording = First(); recording; recording = Next(recording))
1655  recording->ClearSortName();
1656 }
1657 
1658 // --- cDirCopier ------------------------------------------------------------
1659 
1660 class cDirCopier : public cThread {
1661 private:
1664  bool error;
1666  bool Throttled(void);
1667  virtual void Action(void);
1668 public:
1669  cDirCopier(const char *DirNameSrc, const char *DirNameDst);
1670  virtual ~cDirCopier();
1671  void Stop(void);
1672  bool Error(void) { return error; }
1673  };
1674 
1675 cDirCopier::cDirCopier(const char *DirNameSrc, const char *DirNameDst)
1676 :cThread("file copier", true)
1677 {
1678  dirNameSrc = DirNameSrc;
1679  dirNameDst = DirNameDst;
1680  error = true; // prepare for the worst!
1681  suspensionLogged = false;
1682 }
1683 
1685 {
1686  Stop();
1687 }
1688 
1690 {
1691  if (cIoThrottle::Engaged()) {
1692  if (!suspensionLogged) {
1693  dsyslog("suspending copy thread");
1694  suspensionLogged = true;
1695  }
1696  return true;
1697  }
1698  else if (suspensionLogged) {
1699  dsyslog("resuming copy thread");
1700  suspensionLogged = false;
1701  }
1702  return false;
1703 }
1704 
1706 {
1707  if (DirectoryOk(dirNameDst, true)) {
1708  cReadDir d(dirNameSrc);
1709  if (d.Ok()) {
1710  dsyslog("copying directory '%s' to '%s'", *dirNameSrc, *dirNameDst);
1711  dirent *e = NULL;
1712  cString FileNameSrc;
1713  cString FileNameDst;
1714  int From = -1;
1715  int To = -1;
1716  size_t BufferSize = BUFSIZ;
1717  while (Running()) {
1718  // Suspend cutting if we have severe throughput problems:
1719  if (Throttled()) {
1720  cCondWait::SleepMs(100);
1721  continue;
1722  }
1723  // Copy all files in the source directory to the destination directory:
1724  if (e) {
1725  // We're currently copying a file:
1726  uchar Buffer[BufferSize];
1727  size_t Read = safe_read(From, Buffer, sizeof(Buffer));
1728  if (Read > 0) {
1729  size_t Written = safe_write(To, Buffer, Read);
1730  if (Written != Read) {
1731  esyslog("ERROR: can't write to destination file '%s': %m", *FileNameDst);
1732  break;
1733  }
1734  }
1735  else if (Read == 0) { // EOF on From
1736  e = NULL; // triggers switch to next entry
1737  if (fsync(To) < 0) {
1738  esyslog("ERROR: can't sync destination file '%s': %m", *FileNameDst);
1739  break;
1740  }
1741  if (close(From) < 0) {
1742  esyslog("ERROR: can't close source file '%s': %m", *FileNameSrc);
1743  break;
1744  }
1745  if (close(To) < 0) {
1746  esyslog("ERROR: can't close destination file '%s': %m", *FileNameDst);
1747  break;
1748  }
1749  // Plausibility check:
1750  off_t FileSizeSrc = FileSize(FileNameSrc);
1751  off_t FileSizeDst = FileSize(FileNameDst);
1752  if (FileSizeSrc != FileSizeDst) {
1753  esyslog("ERROR: file size discrepancy: %" PRId64 " != %" PRId64, FileSizeSrc, FileSizeDst);
1754  break;
1755  }
1756  }
1757  else {
1758  esyslog("ERROR: can't read from source file '%s': %m", *FileNameSrc);
1759  break;
1760  }
1761  }
1762  else if ((e = d.Next()) != NULL) {
1763  // We're switching to the next directory entry:
1764  FileNameSrc = AddDirectory(dirNameSrc, e->d_name);
1765  FileNameDst = AddDirectory(dirNameDst, e->d_name);
1766  struct stat st;
1767  if (stat(FileNameSrc, &st) < 0) {
1768  esyslog("ERROR: can't access source file '%s': %m", *FileNameSrc);
1769  break;
1770  }
1771  if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) {
1772  esyslog("ERROR: source file '%s' is neither a regular file nor a symbolic link", *FileNameSrc);
1773  break;
1774  }
1775  dsyslog("copying file '%s' to '%s'", *FileNameSrc, *FileNameDst);
1776  BufferSize = max(size_t(st.st_blksize * 10), size_t(BUFSIZ));
1777  if (access(FileNameDst, F_OK) == 0) {
1778  esyslog("ERROR: destination file '%s' already exists", *FileNameDst);
1779  break;
1780  }
1781  if ((From = open(FileNameSrc, O_RDONLY)) < 0) {
1782  esyslog("ERROR: can't open source file '%s': %m", *FileNameSrc);
1783  break;
1784  }
1785  if ((To = open(FileNameDst, O_WRONLY | O_CREAT | O_EXCL, DEFFILEMODE)) < 0) {
1786  esyslog("ERROR: can't open destination file '%s': %m", *FileNameDst);
1787  close(From);
1788  break;
1789  }
1790  }
1791  else {
1792  // We're done:
1793  dsyslog("done copying directory '%s' to '%s'", *dirNameSrc, *dirNameDst);
1794  error = false;
1795  return;
1796  }
1797  }
1798  close(From); // just to be absolutely sure
1799  close(To);
1800  esyslog("ERROR: copying directory '%s' to '%s' ended prematurely", *dirNameSrc, *dirNameDst);
1801  }
1802  else
1803  esyslog("ERROR: can't open '%s'", *dirNameSrc);
1804  }
1805  else
1806  esyslog("ERROR: can't access '%s'", *dirNameDst);
1807 }
1808 
1810 {
1811  Cancel(3);
1812  if (error) {
1814  Recordings.AddByName(dirNameSrc);
1815  Recordings.DelByName(dirNameDst);
1816  }
1817 }
1818 
1819 // --- cRecordingsHandlerEntry -----------------------------------------------
1820 
1822 private:
1823  int usage;
1828  void ClearPending(void) { usage &= ~ruPending; }
1829 public:
1830  cRecordingsHandlerEntry(int Usage, const char *FileNameSrc, const char *FileNameDst);
1832  int Usage(const char *FileName = NULL) const;
1833  const char *FileNameSrc(void) const { return fileNameSrc; }
1834  const char *FileNameDst(void) const { return fileNameDst; }
1835  bool Active(bool &Error);
1836  };
1837 
1838 cRecordingsHandlerEntry::cRecordingsHandlerEntry(int Usage, const char *FileNameSrc, const char *FileNameDst)
1839 {
1840  usage = Usage;
1843  cutter = NULL;
1844  copier = NULL;
1845 }
1846 
1848 {
1849  delete cutter;
1850  delete copier;
1851 }
1852 
1853 int cRecordingsHandlerEntry::Usage(const char *FileName) const
1854 {
1855  int u = usage;
1856  if (FileName && *FileName) {
1857  if (strcmp(FileName, fileNameSrc) == 0)
1858  u |= ruSrc;
1859  else if (strcmp(FileName, fileNameDst) == 0)
1860  u |= ruDst;
1861  }
1862  return u;
1863 }
1864 
1866 {
1867  bool CopierFinishedOk = false;
1868  // First test whether there is an ongoing operation:
1869  if (cutter) {
1870  if (cutter->Active())
1871  return true;
1872  Error |= cutter->Error();
1873  delete cutter;
1874  cutter = NULL;
1875  }
1876  else if (copier) {
1877  if (copier->Active())
1878  return true;
1879  Error |= copier->Error();
1880  CopierFinishedOk = !copier->Error();
1881  delete copier;
1882  copier = NULL;
1883  }
1884  // Now check if there is something to start:
1885  if ((Usage() & ruPending) != 0) {
1886  if ((Usage() & ruCut) != 0) {
1887  cutter = new cCutter(FileNameSrc());
1888  cutter->Start();
1889  }
1890  else if ((Usage() & (ruMove | ruCopy)) != 0) {
1892  copier->Start();
1893  }
1894  ClearPending();
1895  Recordings.ChangeState();
1896  return true;
1897  }
1898  // Clean up:
1899  if (CopierFinishedOk && (Usage() & ruMove) != 0) {
1900  cRecording Recording(FileNameSrc());
1901  if (Recording.Delete())
1902  Recordings.DelByName(Recording.FileName());
1903  }
1904  Recordings.ChangeState();
1905  Recordings.TouchUpdate();
1906  return false;
1907 }
1908 
1909 // --- cRecordingsHandler ----------------------------------------------------
1910 
1912 
1914 {
1915  finished = true;
1916  error = false;
1917 }
1918 
1920 {
1921 }
1922 
1924 {
1925  if (FileName && *FileName) {
1926  for (cRecordingsHandlerEntry *r = operations.First(); r; r = operations.Next(r)) {
1927  if (strcmp(FileName, r->FileNameSrc()) == 0 || strcmp(FileName, r->FileNameDst()) == 0)
1928  return r;
1929  }
1930  }
1931  return NULL;
1932 }
1933 
1934 bool cRecordingsHandler::Add(int Usage, const char *FileNameSrc, const char *FileNameDst)
1935 {
1936  dsyslog("recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
1937  cMutexLock MutexLock(&mutex);
1938  if (Usage == ruCut || Usage == ruMove || Usage == ruCopy) {
1939  if (FileNameSrc && *FileNameSrc) {
1940  if (Usage == ruCut || FileNameDst && *FileNameDst) {
1941  cString fnd;
1942  if (Usage == ruCut && !FileNameDst)
1943  FileNameDst = fnd = cCutter::EditedFileName(FileNameSrc);
1944  if (!Get(FileNameSrc) && !Get(FileNameDst)) {
1945  Usage |= ruPending;
1946  operations.Add(new cRecordingsHandlerEntry(Usage, FileNameSrc, FileNameDst));
1947  finished = false;
1948  Active(); // start it right away if possible
1949  Recordings.ChangeState();
1950  return true;
1951  }
1952  else
1953  esyslog("ERROR: file name already present in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
1954  }
1955  else
1956  esyslog("ERROR: missing dst file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
1957  }
1958  else
1959  esyslog("ERROR: missing src file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
1960  }
1961  else
1962  esyslog("ERROR: invalid usage in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
1963  return false;
1964 }
1965 
1966 void cRecordingsHandler::Del(const char *FileName)
1967 {
1968  cMutexLock MutexLock(&mutex);
1969  if (cRecordingsHandlerEntry *r = Get(FileName)) {
1970  operations.Del(r);
1971  Recordings.ChangeState();
1972  }
1973 }
1974 
1976 {
1977  cMutexLock MutexLock(&mutex);
1978  operations.Clear();
1979  Recordings.ChangeState();
1980 }
1981 
1982 int cRecordingsHandler::GetUsage(const char *FileName)
1983 {
1984  cMutexLock MutexLock(&mutex);
1985  if (cRecordingsHandlerEntry *r = Get(FileName))
1986  return r->Usage(FileName);
1987  return ruNone;
1988 }
1989 
1991 {
1992  cMutexLock MutexLock(&mutex);
1993  while (cRecordingsHandlerEntry *r = operations.First()) {
1994  if (r->Active(error))
1995  return true;
1996  else
1997  operations.Del(r);
1998  }
1999  return false;
2000 }
2001 
2003 {
2004  cMutexLock MutexLock(&mutex);
2005  if (!finished && operations.Count() == 0) {
2006  finished = true;
2007  Error = error;
2008  error = false;
2009  return true;
2010  }
2011  return false;
2012 }
2013 
2014 // --- cMark -----------------------------------------------------------------
2015 
2018 
2019 cMark::cMark(int Position, const char *Comment, double FramesPerSecond)
2020 {
2021  position = Position;
2022  comment = Comment;
2023  framesPerSecond = FramesPerSecond;
2024 }
2025 
2027 {
2028 }
2029 
2031 {
2032  return cString::sprintf("%s%s%s\n", *IndexToHMSF(position, true, framesPerSecond), Comment() ? " " : "", Comment() ? Comment() : "");
2033 }
2034 
2035 bool cMark::Parse(const char *s)
2036 {
2037  comment = NULL;
2040  const char *p = strchr(s, ' ');
2041  if (p) {
2042  p = skipspace(p);
2043  if (*p)
2044  comment = strdup(p);
2045  }
2046  return true;
2047 }
2048 
2049 bool cMark::Save(FILE *f)
2050 {
2051  return fprintf(f, "%s", *ToText()) > 0;
2052 }
2053 
2054 // --- cMarks ----------------------------------------------------------------
2055 
2057 {
2058  return AddDirectory(Recording->FileName(), Recording->IsPesRecording() ? MARKSFILESUFFIX ".vdr" : MARKSFILESUFFIX);
2059 }
2060 
2061 bool cMarks::Load(const char *RecordingFileName, double FramesPerSecond, bool IsPesRecording)
2062 {
2063  cMutexLock MutexLock(this);
2064  recordingFileName = RecordingFileName;
2065  fileName = AddDirectory(RecordingFileName, IsPesRecording ? MARKSFILESUFFIX ".vdr" : MARKSFILESUFFIX);
2066  framesPerSecond = FramesPerSecond;
2067  isPesRecording = IsPesRecording;
2068  nextUpdate = 0;
2069  lastFileTime = -1; // the first call to Load() must take place!
2070  lastChange = 0;
2071  return Update();
2072 }
2073 
2074 bool cMarks::Update(void)
2075 {
2076  cMutexLock MutexLock(this);
2077  time_t t = time(NULL);
2078  if (t > nextUpdate && *fileName) {
2079  time_t LastModified = LastModifiedTime(fileName);
2080  if (LastModified != lastFileTime) // change detected, or first run
2081  lastChange = LastModified > 0 ? LastModified : t;
2082  int d = t - lastChange;
2083  if (d < 60)
2084  d = 1; // check frequently if the file has just been modified
2085  else if (d < 3600)
2086  d = 10; // older files are checked less frequently
2087  else
2088  d /= 360; // phase out checking for very old files
2089  nextUpdate = t + d;
2090  if (LastModified != lastFileTime) { // change detected, or first run
2091  lastFileTime = LastModified;
2092  if (lastFileTime == t)
2093  lastFileTime--; // make sure we don't miss updates in the remaining second
2094  cMutexLock MutexLock(&MutexMarkFramesPerSecond);
2097  Align();
2098  Sort();
2099  return true;
2100  }
2101  }
2102  }
2103  return false;
2104 }
2105 
2106 bool cMarks::Save(void)
2107 {
2108  cMutexLock MutexLock(this);
2109  if (cConfig<cMark>::Save()) {
2111  return true;
2112  }
2113  return false;
2114 }
2115 
2116 void cMarks::Align(void)
2117 {
2118  cMutexLock MutexLock(this);
2119  cIndexFile IndexFile(recordingFileName, false, isPesRecording);
2120  for (cMark *m = First(); m; m = Next(m)) {
2121  int p = IndexFile.GetClosestIFrame(m->Position());
2122  if (int d = m->Position() - p) {
2123  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" : "");
2124  m->SetPosition(p);
2125  }
2126  }
2127 }
2128 
2129 void cMarks::Sort(void)
2130 {
2131  cMutexLock MutexLock(this);
2132  for (cMark *m1 = First(); m1; m1 = Next(m1)) {
2133  for (cMark *m2 = Next(m1); m2; m2 = Next(m2)) {
2134  if (m2->Position() < m1->Position()) {
2135  swap(m1->position, m2->position);
2136  swap(m1->comment, m2->comment);
2137  }
2138  }
2139  }
2140 }
2141 
2142 void cMarks::Add(int Position)
2143 {
2144  cMutexLock MutexLock(this);
2145  cConfig<cMark>::Add(new cMark(Position, NULL, framesPerSecond));
2146  Sort();
2147 }
2148 
2149 cMark *cMarks::Get(int Position)
2150 {
2151  for (cMark *mi = First(); mi; mi = Next(mi)) {
2152  if (mi->Position() == Position)
2153  return mi;
2154  }
2155  return NULL;
2156 }
2157 
2158 cMark *cMarks::GetPrev(int Position)
2159 {
2160  for (cMark *mi = Last(); mi; mi = Prev(mi)) {
2161  if (mi->Position() < Position)
2162  return mi;
2163  }
2164  return NULL;
2165 }
2166 
2167 cMark *cMarks::GetNext(int Position)
2168 {
2169  for (cMark *mi = First(); mi; mi = Next(mi)) {
2170  if (mi->Position() > Position)
2171  return mi;
2172  }
2173  return NULL;
2174 }
2175 
2177 {
2178  cMark *BeginMark = EndMark ? Next(EndMark) : First();
2179  if (BeginMark && EndMark && BeginMark->Position() == EndMark->Position()) {
2180  while (cMark *NextMark = Next(BeginMark)) {
2181  if (BeginMark->Position() == NextMark->Position()) { // skip Begin/End at the same position
2182  if (!(BeginMark = Next(NextMark)))
2183  break;
2184  }
2185  else
2186  break;
2187  }
2188  }
2189  return BeginMark;
2190 }
2191 
2193 {
2194  if (!BeginMark)
2195  return NULL;
2196  cMark *EndMark = Next(BeginMark);
2197  if (EndMark && BeginMark && BeginMark->Position() == EndMark->Position()) {
2198  while (cMark *NextMark = Next(EndMark)) {
2199  if (EndMark->Position() == NextMark->Position()) { // skip End/Begin at the same position
2200  if (!(EndMark = Next(NextMark)))
2201  break;
2202  }
2203  else
2204  break;
2205  }
2206  }
2207  return EndMark;
2208 }
2209 
2211 {
2212  cMutexLock MutexLock(this);
2213  int NumSequences = 0;
2214  if (cMark *BeginMark = GetNextBegin()) {
2215  while (cMark *EndMark = GetNextEnd(BeginMark)) {
2216  NumSequences++;
2217  BeginMark = GetNextBegin(EndMark);
2218  }
2219  if (BeginMark) {
2220  NumSequences++; // the last sequence had no actual "end" mark
2221  if (NumSequences == 1 && BeginMark->Position() == 0)
2222  NumSequences = 0; // there is only one actual "begin" mark at offset zero, and no actual "end" mark
2223  }
2224  }
2225  return NumSequences;
2226 }
2227 
2228 // --- cRecordingUserCommand -------------------------------------------------
2229 
2230 const char *cRecordingUserCommand::command = NULL;
2231 
2232 void cRecordingUserCommand::InvokeCommand(const char *State, const char *RecordingFileName, const char *SourceFileName)
2233 {
2234  if (command) {
2235  cString cmd;
2236  if (SourceFileName)
2237  cmd = cString::sprintf("%s %s \"%s\" \"%s\"", command, State, *strescape(RecordingFileName, "\\\"$"), *strescape(SourceFileName, "\\\"$"));
2238  else
2239  cmd = cString::sprintf("%s %s \"%s\"", command, State, *strescape(RecordingFileName, "\\\"$"));
2240  isyslog("executing '%s'", *cmd);
2241  SystemExec(cmd);
2242  }
2243 }
2244 
2245 // --- cIndexFileGenerator ---------------------------------------------------
2246 
2247 #define IFG_BUFFER_SIZE KILOBYTE(100)
2248 
2250 private:
2252  bool update;
2253 protected:
2254  virtual void Action(void);
2255 public:
2256  cIndexFileGenerator(const char *RecordingName, bool Update = false);
2258  };
2259 
2260 cIndexFileGenerator::cIndexFileGenerator(const char *RecordingName, bool Update)
2261 :cThread("index file generator")
2262 ,recordingName(RecordingName)
2263 {
2264  update = Update;
2265  Start();
2266 }
2267 
2269 {
2270  Cancel(3);
2271 }
2272 
2274 {
2275  bool IndexFileComplete = false;
2276  bool IndexFileWritten = false;
2277  bool Rewind = false;
2278  cFileName FileName(recordingName, false);
2279  cUnbufferedFile *ReplayFile = FileName.Open();
2281  cPatPmtParser PatPmtParser;
2282  cFrameDetector FrameDetector;
2283  cIndexFile IndexFile(recordingName, true, false, false, true);
2284  int BufferChunks = KILOBYTE(1); // no need to read a lot at the beginning when parsing PAT/PMT
2285  off_t FileSize = 0;
2286  off_t FrameOffset = -1;
2287  uint16_t FileNumber = 1;
2288  off_t FileOffset = 0;
2289  int Last = -1;
2290  if (update) {
2291  // Look for current index and position to end of it if present:
2292  bool Independent;
2293  int Length;
2294  Last = IndexFile.Last();
2295  if (Last >= 0 && !IndexFile.Get(Last, &FileNumber, &FileOffset, &Independent, &Length))
2296  Last = -1; // reset Last if an error occurred
2297  if (Last >= 0) {
2298  Rewind = true;
2299  isyslog("updating index file");
2300  }
2301  else
2302  isyslog("generating index file");
2303  }
2304  Skins.QueueMessage(mtInfo, tr("Regenerating index file"));
2305  bool Stuffed = false;
2306  while (Running()) {
2307  // Rewind input file:
2308  if (Rewind) {
2309  ReplayFile = FileName.SetOffset(FileNumber, FileOffset);
2310  FileSize = FileOffset;
2311  Buffer.Clear();
2312  Rewind = false;
2313  }
2314  // Process data:
2315  int Length;
2316  uchar *Data = Buffer.Get(Length);
2317  if (Data) {
2318  if (FrameDetector.Synced()) {
2319  // Step 3 - generate the index:
2320  if (TsPid(Data) == PATPID)
2321  FrameOffset = FileSize; // the PAT/PMT is at the beginning of an I-frame
2322  int Processed = FrameDetector.Analyze(Data, Length);
2323  if (Processed > 0) {
2324  if (FrameDetector.NewFrame()) {
2325  if (IndexFileWritten || Last < 0) // check for first frame and do not write if in update mode
2326  IndexFile.Write(FrameDetector.IndependentFrame(), FileName.Number(), FrameOffset >= 0 ? FrameOffset : FileSize);
2327  FrameOffset = -1;
2328  IndexFileWritten = true;
2329  }
2330  FileSize += Processed;
2331  Buffer.Del(Processed);
2332  }
2333  }
2334  else if (PatPmtParser.Vpid()) {
2335  // Step 2 - sync FrameDetector:
2336  int Processed = FrameDetector.Analyze(Data, Length);
2337  if (Processed > 0) {
2338  if (FrameDetector.Synced()) {
2339  // Synced FrameDetector, so rewind for actual processing:
2340  Rewind = true;
2341  }
2342  Buffer.Del(Processed);
2343  }
2344  }
2345  else {
2346  // Step 1 - parse PAT/PMT:
2347  uchar *p = Data;
2348  while (Length >= TS_SIZE) {
2349  int Pid = TsPid(p);
2350  if (Pid == PATPID)
2351  PatPmtParser.ParsePat(p, TS_SIZE);
2352  else if (PatPmtParser.IsPmtPid(Pid))
2353  PatPmtParser.ParsePmt(p, TS_SIZE);
2354  Length -= TS_SIZE;
2355  p += TS_SIZE;
2356  if (PatPmtParser.Vpid()) {
2357  // Found Vpid, so rewind to sync FrameDetector:
2358  FrameDetector.SetPid(PatPmtParser.Vpid(), PatPmtParser.Vtype());
2359  BufferChunks = IFG_BUFFER_SIZE;
2360  Rewind = true;
2361  break;
2362  }
2363  }
2364  Buffer.Del(p - Data);
2365  }
2366  }
2367  // Read data:
2368  else if (ReplayFile) {
2369  int Result = Buffer.Read(ReplayFile, BufferChunks);
2370  if (Result == 0) { // EOF
2371  if (Buffer.Available() > 0 && !Stuffed) {
2372  // So the last call to Buffer.Get() returned NULL, but there is still
2373  // data in the buffer, and we're at the end of the current TS file.
2374  // The remaining data in the buffer is less than what's needed for the
2375  // frame detector to analyze frames, so we need to put some stuffing
2376  // packets into the buffer to flush out the rest of the data (otherwise
2377  // any frames within the remaining data would not be seen here):
2378  uchar StuffingPacket[TS_SIZE] = { TS_SYNC_BYTE, 0xFF };
2379  for (int i = 0; i <= MIN_TS_PACKETS_FOR_FRAME_DETECTOR; i++)
2380  Buffer.Put(StuffingPacket, sizeof(StuffingPacket));
2381  Stuffed = true;
2382  }
2383  else {
2384  ReplayFile = FileName.NextFile();
2385  FileSize = 0;
2386  FrameOffset = -1;
2387  Buffer.Clear();
2388  Stuffed = false;
2389  }
2390  }
2391  }
2392  // Recording has been processed:
2393  else {
2394  IndexFileComplete = true;
2395  break;
2396  }
2397  }
2398  if (IndexFileComplete) {
2399  if (IndexFileWritten) {
2400  cRecordingInfo RecordingInfo(recordingName);
2401  if (RecordingInfo.Read()) {
2402  if (FrameDetector.FramesPerSecond() > 0 && !DoubleEqual(RecordingInfo.FramesPerSecond(), FrameDetector.FramesPerSecond())) {
2403  RecordingInfo.SetFramesPerSecond(FrameDetector.FramesPerSecond());
2404  RecordingInfo.Write();
2405  Recordings.UpdateByName(recordingName);
2406  }
2407  }
2408  Skins.QueueMessage(mtInfo, tr("Index file regeneration complete"));
2409  return;
2410  }
2411  else
2412  Skins.QueueMessage(mtError, tr("Index file regeneration failed!"));
2413  }
2414  // Delete the index file if the recording has not been processed entirely:
2415  IndexFile.Delete();
2416 }
2417 
2418 // --- cIndexFile ------------------------------------------------------------
2419 
2420 #define INDEXFILESUFFIX "/index"
2421 
2422 // The maximum time to wait before giving up while catching up on an index file:
2423 #define MAXINDEXCATCHUP 8 // number of retries
2424 #define INDEXCATCHUPWAIT 100 // milliseconds
2425 
2426 struct tIndexPes {
2427  uint32_t offset;
2430  uint16_t reserved;
2431  };
2432 
2433 struct tIndexTs {
2434  uint64_t offset:40; // up to 1TB per file (not using off_t here - must definitely be exactly 64 bit!)
2435  int reserved:7; // reserved for future use
2436  int independent:1; // marks frames that can be displayed by themselves (for trick modes)
2437  uint16_t number:16; // up to 64K files per recording
2438  tIndexTs(off_t Offset, bool Independent, uint16_t Number)
2439  {
2440  offset = Offset;
2441  reserved = 0;
2442  independent = Independent;
2443  number = Number;
2444  }
2445  };
2446 
2447 #define MAXWAITFORINDEXFILE 10 // max. time to wait for the regenerated index file (seconds)
2448 #define INDEXFILECHECKINTERVAL 500 // ms between checks for existence of the regenerated index file
2449 #define INDEXFILETESTINTERVAL 10 // ms between tests for the size of the index file in case of pausing live video
2450 
2451 cIndexFile::cIndexFile(const char *FileName, bool Record, bool IsPesRecording, bool PauseLive, bool Update)
2452 :resumeFile(FileName, IsPesRecording)
2453 {
2454  f = -1;
2455  size = 0;
2456  last = -1;
2457  index = NULL;
2458  isPesRecording = IsPesRecording;
2459  indexFileGenerator = NULL;
2460  if (FileName) {
2461  fileName = IndexFileName(FileName, isPesRecording);
2462  if (!Record && PauseLive) {
2463  // Wait until the index file contains at least two frames:
2464  time_t tmax = time(NULL) + MAXWAITFORINDEXFILE;
2465  while (time(NULL) < tmax && FileSize(fileName) < off_t(2 * sizeof(tIndexTs)))
2467  }
2468  int delta = 0;
2469  if (!Record && access(fileName, R_OK) != 0) {
2470  // Index file doesn't exist, so try to regenerate it:
2471  if (!isPesRecording) { // sorry, can only do this for TS recordings
2472  resumeFile.Delete(); // just in case
2473  indexFileGenerator = new cIndexFileGenerator(FileName);
2474  // Wait until the index file exists:
2475  time_t tmax = time(NULL) + MAXWAITFORINDEXFILE;
2476  do {
2477  cCondWait::SleepMs(INDEXFILECHECKINTERVAL); // start with a sleep, to give it a head start
2478  } while (access(fileName, R_OK) != 0 && time(NULL) < tmax);
2479  }
2480  }
2481  if (access(fileName, R_OK) == 0) {
2482  struct stat buf;
2483  if (stat(fileName, &buf) == 0) {
2484  delta = int(buf.st_size % sizeof(tIndexTs));
2485  if (delta) {
2486  delta = sizeof(tIndexTs) - delta;
2487  esyslog("ERROR: invalid file size (%" PRId64 ") in '%s'", buf.st_size, *fileName);
2488  }
2489  last = int((buf.st_size + delta) / sizeof(tIndexTs) - 1);
2490  if ((!Record || Update) && last >= 0) {
2491  size = last + 1;
2492  index = MALLOC(tIndexTs, size);
2493  if (index) {
2494  f = open(fileName, O_RDONLY);
2495  if (f >= 0) {
2496  if (safe_read(f, index, size_t(buf.st_size)) != buf.st_size) {
2497  esyslog("ERROR: can't read from file '%s'", *fileName);
2498  free(index);
2499  index = NULL;
2500  }
2501  else if (isPesRecording)
2503  if (!index || time(NULL) - buf.st_mtime >= MININDEXAGE) {
2504  close(f);
2505  f = -1;
2506  }
2507  // otherwise we don't close f here, see CatchUp()!
2508  }
2509  else
2511  }
2512  else
2513  esyslog("ERROR: can't allocate %zd bytes for index '%s'", size * sizeof(tIndexTs), *fileName);
2514  }
2515  }
2516  else
2517  LOG_ERROR;
2518  }
2519  else if (!Record)
2520  isyslog("missing index file %s", *fileName);
2521  if (Record) {
2522  if ((f = open(fileName, O_WRONLY | O_CREAT | O_APPEND, DEFFILEMODE)) >= 0) {
2523  if (delta) {
2524  esyslog("ERROR: padding index file with %d '0' bytes", delta);
2525  while (delta--)
2526  writechar(f, 0);
2527  }
2528  }
2529  else
2531  }
2532  }
2533 }
2534 
2536 {
2537  if (f >= 0)
2538  close(f);
2539  free(index);
2540  delete indexFileGenerator;
2541 }
2542 
2543 cString cIndexFile::IndexFileName(const char *FileName, bool IsPesRecording)
2544 {
2545  return cString::sprintf("%s%s", FileName, IsPesRecording ? INDEXFILESUFFIX ".vdr" : INDEXFILESUFFIX);
2546 }
2547 
2548 void cIndexFile::ConvertFromPes(tIndexTs *IndexTs, int Count)
2549 {
2550  tIndexPes IndexPes;
2551  while (Count-- > 0) {
2552  memcpy(&IndexPes, IndexTs, sizeof(IndexPes));
2553  IndexTs->offset = IndexPes.offset;
2554  IndexTs->independent = IndexPes.type == 1; // I_FRAME
2555  IndexTs->number = IndexPes.number;
2556  IndexTs++;
2557  }
2558 }
2559 
2560 void cIndexFile::ConvertToPes(tIndexTs *IndexTs, int Count)
2561 {
2562  tIndexPes IndexPes;
2563  while (Count-- > 0) {
2564  IndexPes.offset = uint32_t(IndexTs->offset);
2565  IndexPes.type = uchar(IndexTs->independent ? 1 : 2); // I_FRAME : "not I_FRAME" (exact frame type doesn't matter)
2566  IndexPes.number = uchar(IndexTs->number);
2567  IndexPes.reserved = 0;
2568  memcpy(IndexTs, &IndexPes, sizeof(*IndexTs));
2569  IndexTs++;
2570  }
2571 }
2572 
2573 bool cIndexFile::CatchUp(int Index)
2574 {
2575  // returns true unless something really goes wrong, so that 'index' becomes NULL
2576  if (index && f >= 0) {
2577  cMutexLock MutexLock(&mutex);
2578  // Note that CatchUp() is triggered even if Index is 'last' (and thus valid).
2579  // This is done to make absolutely sure we don't miss any data at the very end.
2580  for (int i = 0; i <= MAXINDEXCATCHUP && (Index < 0 || Index >= last); i++) {
2581  struct stat buf;
2582  if (fstat(f, &buf) == 0) {
2583  int newLast = int(buf.st_size / sizeof(tIndexTs) - 1);
2584  if (newLast > last) {
2585  int NewSize = size;
2586  if (NewSize <= newLast) {
2587  NewSize *= 2;
2588  if (NewSize <= newLast)
2589  NewSize = newLast + 1;
2590  }
2591  if (tIndexTs *NewBuffer = (tIndexTs *)realloc(index, NewSize * sizeof(tIndexTs))) {
2592  size = NewSize;
2593  index = NewBuffer;
2594  int offset = (last + 1) * sizeof(tIndexTs);
2595  int delta = (newLast - last) * sizeof(tIndexTs);
2596  if (lseek(f, offset, SEEK_SET) == offset) {
2597  if (safe_read(f, &index[last + 1], delta) != delta) {
2598  esyslog("ERROR: can't read from index");
2599  free(index);
2600  index = NULL;
2601  close(f);
2602  f = -1;
2603  break;
2604  }
2605  if (isPesRecording)
2606  ConvertFromPes(&index[last + 1], newLast - last);
2607  last = newLast;
2608  }
2609  else
2611  }
2612  else {
2613  esyslog("ERROR: can't realloc() index");
2614  break;
2615  }
2616  }
2617  }
2618  else
2620  if (Index < last)
2621  break;
2622  cCondVar CondVar;
2623  CondVar.TimedWait(mutex, INDEXCATCHUPWAIT);
2624  }
2625  }
2626  return index != NULL;
2627 }
2628 
2629 bool cIndexFile::Write(bool Independent, uint16_t FileNumber, off_t FileOffset)
2630 {
2631  if (f >= 0) {
2632  tIndexTs i(FileOffset, Independent, FileNumber);
2633  if (isPesRecording)
2634  ConvertToPes(&i, 1);
2635  if (safe_write(f, &i, sizeof(i)) < 0) {
2637  close(f);
2638  f = -1;
2639  return false;
2640  }
2641  last++;
2642  }
2643  return f >= 0;
2644 }
2645 
2646 bool cIndexFile::Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent, int *Length)
2647 {
2648  if (CatchUp(Index)) {
2649  if (Index >= 0 && Index <= last) {
2650  *FileNumber = index[Index].number;
2651  *FileOffset = index[Index].offset;
2652  if (Independent)
2653  *Independent = index[Index].independent;
2654  if (Length) {
2655  if (Index < last) {
2656  uint16_t fn = index[Index + 1].number;
2657  off_t fo = index[Index + 1].offset;
2658  if (fn == *FileNumber)
2659  *Length = int(fo - *FileOffset);
2660  else
2661  *Length = -1; // this means "everything up to EOF" (the buffer's Read function will act accordingly)
2662  }
2663  else
2664  *Length = -1;
2665  }
2666  return true;
2667  }
2668  }
2669  return false;
2670 }
2671 
2672 int cIndexFile::GetNextIFrame(int Index, bool Forward, uint16_t *FileNumber, off_t *FileOffset, int *Length)
2673 {
2674  if (CatchUp()) {
2675  int d = Forward ? 1 : -1;
2676  for (;;) {
2677  Index += d;
2678  if (Index >= 0 && Index <= last) {
2679  if (index[Index].independent) {
2680  uint16_t fn;
2681  if (!FileNumber)
2682  FileNumber = &fn;
2683  off_t fo;
2684  if (!FileOffset)
2685  FileOffset = &fo;
2686  *FileNumber = index[Index].number;
2687  *FileOffset = index[Index].offset;
2688  if (Length) {
2689  if (Index < last) {
2690  uint16_t fn = index[Index + 1].number;
2691  off_t fo = index[Index + 1].offset;
2692  if (fn == *FileNumber)
2693  *Length = int(fo - *FileOffset);
2694  else
2695  *Length = -1; // this means "everything up to EOF" (the buffer's Read function will act accordingly)
2696  }
2697  else
2698  *Length = -1;
2699  }
2700  return Index;
2701  }
2702  }
2703  else
2704  break;
2705  }
2706  }
2707  return -1;
2708 }
2709 
2711 {
2712  if (last > 0) {
2713  Index = constrain(Index, 0, last);
2714  if (index[Index].independent)
2715  return Index;
2716  int il = Index - 1;
2717  int ih = Index + 1;
2718  for (;;) {
2719  if (il >= 0) {
2720  if (index[il].independent)
2721  return il;
2722  il--;
2723  }
2724  else if (ih > last)
2725  break;
2726  if (ih <= last) {
2727  if (index[ih].independent)
2728  return ih;
2729  ih++;
2730  }
2731  else if (il < 0)
2732  break;
2733  }
2734  }
2735  return 0;
2736 }
2737 
2738 int cIndexFile::Get(uint16_t FileNumber, off_t FileOffset)
2739 {
2740  if (CatchUp()) {
2741  //TODO implement binary search!
2742  int i;
2743  for (i = 0; i <= last; i++) {
2744  if (index[i].number > FileNumber || (index[i].number == FileNumber) && off_t(index[i].offset) >= FileOffset)
2745  break;
2746  }
2747  return i;
2748  }
2749  return -1;
2750 }
2751 
2753 {
2754  return f >= 0;
2755 }
2756 
2758 {
2759  if (*fileName) {
2760  dsyslog("deleting index file '%s'", *fileName);
2761  if (f >= 0) {
2762  close(f);
2763  f = -1;
2764  }
2765  unlink(fileName);
2766  }
2767 }
2768 
2769 int cIndexFile::GetLength(const char *FileName, bool IsPesRecording)
2770 {
2771  struct stat buf;
2772  cString s = IndexFileName(FileName, IsPesRecording);
2773  if (*s && stat(s, &buf) == 0)
2774  return buf.st_size / (IsPesRecording ? sizeof(tIndexTs) : sizeof(tIndexPes));
2775  return -1;
2776 }
2777 
2778 bool GenerateIndex(const char *FileName, bool Update)
2779 {
2780  if (DirectoryOk(FileName)) {
2781  cRecording Recording(FileName);
2782  if (Recording.Name()) {
2783  if (!Recording.IsPesRecording()) {
2784  cString IndexFileName = AddDirectory(FileName, INDEXFILESUFFIX);
2785  if (!Update)
2786  unlink(IndexFileName);
2787  cIndexFileGenerator *IndexFileGenerator = new cIndexFileGenerator(FileName, Update);
2788  while (IndexFileGenerator->Active())
2790  if (access(IndexFileName, R_OK) == 0)
2791  return true;
2792  else
2793  fprintf(stderr, "cannot create '%s'\n", *IndexFileName);
2794  }
2795  else
2796  fprintf(stderr, "'%s' is not a TS recording\n", FileName);
2797  }
2798  else
2799  fprintf(stderr, "'%s' is not a recording\n", FileName);
2800  }
2801  else
2802  fprintf(stderr, "'%s' is not a directory\n", FileName);
2803  return false;
2804 }
2805 
2806 // --- cFileName -------------------------------------------------------------
2807 
2808 #define MAXFILESPERRECORDINGPES 255
2809 #define RECORDFILESUFFIXPES "/%03d.vdr"
2810 #define MAXFILESPERRECORDINGTS 65535
2811 #define RECORDFILESUFFIXTS "/%05d.ts"
2812 #define RECORDFILESUFFIXLEN 20 // some additional bytes for safety...
2813 
2814 cFileName::cFileName(const char *FileName, bool Record, bool Blocking, bool IsPesRecording)
2815 {
2816  file = NULL;
2817  fileNumber = 0;
2818  record = Record;
2819  blocking = Blocking;
2820  isPesRecording = IsPesRecording;
2821  // Prepare the file name:
2822  fileName = MALLOC(char, strlen(FileName) + RECORDFILESUFFIXLEN);
2823  if (!fileName) {
2824  esyslog("ERROR: can't copy file name '%s'", fileName);
2825  return;
2826  }
2827  strcpy(fileName, FileName);
2828  pFileNumber = fileName + strlen(fileName);
2829  SetOffset(1);
2830 }
2831 
2833 {
2834  Close();
2835  free(fileName);
2836 }
2837 
2838 bool cFileName::GetLastPatPmtVersions(int &PatVersion, int &PmtVersion)
2839 {
2840  if (fileName && !isPesRecording) {
2841  // Find the last recording file:
2842  int Number = 1;
2843  for (; Number <= MAXFILESPERRECORDINGTS + 1; Number++) { // +1 to correctly set Number in case there actually are that many files
2844  sprintf(pFileNumber, RECORDFILESUFFIXTS, Number);
2845  if (access(fileName, F_OK) != 0) { // file doesn't exist
2846  Number--;
2847  break;
2848  }
2849  }
2850  for (; Number > 0; Number--) {
2851  // Search for a PAT packet from the end of the file:
2852  cPatPmtParser PatPmtParser;
2853  sprintf(pFileNumber, RECORDFILESUFFIXTS, Number);
2854  int fd = open(fileName, O_RDONLY | O_LARGEFILE, DEFFILEMODE);
2855  if (fd >= 0) {
2856  off_t pos = lseek(fd, -TS_SIZE, SEEK_END);
2857  while (pos >= 0) {
2858  // Read and parse the PAT/PMT:
2859  uchar buf[TS_SIZE];
2860  while (read(fd, buf, sizeof(buf)) == sizeof(buf)) {
2861  if (buf[0] == TS_SYNC_BYTE) {
2862  int Pid = TsPid(buf);
2863  if (Pid == PATPID)
2864  PatPmtParser.ParsePat(buf, sizeof(buf));
2865  else if (PatPmtParser.IsPmtPid(Pid)) {
2866  PatPmtParser.ParsePmt(buf, sizeof(buf));
2867  if (PatPmtParser.GetVersions(PatVersion, PmtVersion)) {
2868  close(fd);
2869  return true;
2870  }
2871  }
2872  else
2873  break; // PAT/PMT is always in one sequence
2874  }
2875  else
2876  return false;
2877  }
2878  pos = lseek(fd, pos - TS_SIZE, SEEK_SET);
2879  }
2880  close(fd);
2881  }
2882  else
2883  break;
2884  }
2885  }
2886  return false;
2887 }
2888 
2890 {
2891  if (!file) {
2892  int BlockingFlag = blocking ? 0 : O_NONBLOCK;
2893  if (record) {
2894  dsyslog("recording to '%s'", fileName);
2895  file = cVideoDirectory::OpenVideoFile(fileName, O_RDWR | O_CREAT | O_LARGEFILE | BlockingFlag);
2896  if (!file)
2898  }
2899  else {
2900  if (access(fileName, R_OK) == 0) {
2901  dsyslog("playing '%s'", fileName);
2902  file = cUnbufferedFile::Create(fileName, O_RDONLY | O_LARGEFILE | BlockingFlag);
2903  if (!file)
2905  }
2906  else if (errno != ENOENT)
2908  }
2909  }
2910  return file;
2911 }
2912 
2914 {
2915  if (file) {
2916  if (file->Close() < 0)
2918  delete file;
2919  file = NULL;
2920  }
2921 }
2922 
2923 cUnbufferedFile *cFileName::SetOffset(int Number, off_t Offset)
2924 {
2925  if (fileNumber != Number)
2926  Close();
2927  int MaxFilesPerRecording = isPesRecording ? MAXFILESPERRECORDINGPES : MAXFILESPERRECORDINGTS;
2928  if (0 < Number && Number <= MaxFilesPerRecording) {
2929  fileNumber = uint16_t(Number);
2931  if (record) {
2932  if (access(fileName, F_OK) == 0) {
2933  // file exists, check if it has non-zero size
2934  struct stat buf;
2935  if (stat(fileName, &buf) == 0) {
2936  if (buf.st_size != 0)
2937  return SetOffset(Number + 1); // file exists and has non zero size, let's try next suffix
2938  else {
2939  // zero size file, remove it
2940  dsyslog("cFileName::SetOffset: removing zero-sized file %s", fileName);
2941  unlink(fileName);
2942  }
2943  }
2944  else
2945  return SetOffset(Number + 1); // error with fstat - should not happen, just to be on the safe side
2946  }
2947  else if (errno != ENOENT) { // something serious has happened
2949  return NULL;
2950  }
2951  // found a non existing file suffix
2952  }
2953  if (Open() >= 0) {
2954  if (!record && Offset >= 0 && file && file->Seek(Offset, SEEK_SET) != Offset) {
2956  return NULL;
2957  }
2958  }
2959  return file;
2960  }
2961  esyslog("ERROR: max number of files (%d) exceeded", MaxFilesPerRecording);
2962  return NULL;
2963 }
2964 
2966 {
2967  return SetOffset(fileNumber + 1);
2968 }
2969 
2970 // --- Index stuff -----------------------------------------------------------
2971 
2972 cString IndexToHMSF(int Index, bool WithFrame, double FramesPerSecond)
2973 {
2974  const char *Sign = "";
2975  if (Index < 0) {
2976  Index = -Index;
2977  Sign = "-";
2978  }
2979  double Seconds;
2980  int f = int(modf((Index + 0.5) / FramesPerSecond, &Seconds) * FramesPerSecond);
2981  int s = int(Seconds);
2982  int m = s / 60 % 60;
2983  int h = s / 3600;
2984  s %= 60;
2985  return cString::sprintf(WithFrame ? "%s%d:%02d:%02d.%02d" : "%s%d:%02d:%02d", Sign, h, m, s, f);
2986 }
2987 
2988 int HMSFToIndex(const char *HMSF, double FramesPerSecond)
2989 {
2990  int h, m, s, f = 0;
2991  int n = sscanf(HMSF, "%d:%d:%d.%d", &h, &m, &s, &f);
2992  if (n == 1)
2993  return h; // plain frame number
2994  if (n >= 3)
2995  return int(round((h * 3600 + m * 60 + s) * FramesPerSecond)) + f;
2996  return 0;
2997 }
2998 
2999 int SecondsToFrames(int Seconds, double FramesPerSecond)
3000 {
3001  return int(round(Seconds * FramesPerSecond));
3002 }
3003 
3004 // --- ReadFrame -------------------------------------------------------------
3005 
3006 int ReadFrame(cUnbufferedFile *f, uchar *b, int Length, int Max)
3007 {
3008  if (Length == -1)
3009  Length = Max; // this means we read up to EOF (see cIndex)
3010  else if (Length > Max) {
3011  esyslog("ERROR: frame larger than buffer (%d > %d)", Length, Max);
3012  Length = Max;
3013  }
3014  int r = f->Read(b, Length);
3015  if (r < 0)
3016  LOG_ERROR;
3017  return r;
3018 }
3019 
3020 // --- Recordings Sort Mode --------------------------------------------------
3021 
3023 
3024 bool HasRecordingsSortMode(const char *Directory)
3025 {
3026  return access(AddDirectory(Directory, SORTMODEFILE), R_OK) == 0;
3027 }
3028 
3029 void GetRecordingsSortMode(const char *Directory)
3030 {
3032  if (FILE *f = fopen(AddDirectory(Directory, SORTMODEFILE), "r")) {
3033  char buf[8];
3034  if (fgets(buf, sizeof(buf), f))
3036  fclose(f);
3037  }
3038 }
3039 
3040 void SetRecordingsSortMode(const char *Directory, eRecordingsSortMode SortMode)
3041 {
3042  if (FILE *f = fopen(AddDirectory(Directory, SORTMODEFILE), "w")) {
3043  fputs(cString::sprintf("%d\n", SortMode), f);
3044  fclose(f);
3045  }
3046 }
3047 
3048 void IncRecordingsSortMode(const char *Directory)
3049 {
3050  GetRecordingsSortMode(Directory);
3055 }
bool Start(void)
Starts the actual cutting process.
Definition: cutter.c:669
cString dtoa(double d, const char *Format)
Converts the given double value to a string, making sure it uses a &#39;.
Definition: tools.c:378
bool initial
Definition: recording.h:223
struct dirent * Next(void)
Definition: tools.c:1466
cString itoa(int n)
Definition: tools.c:388
static bool RenameVideoFile(const char *OldName, const char *NewName)
Definition: videodir.c:120
void ClearVanishedRecordings(void)
Definition: recording.c:233
#define REMOVECHECKDELTA
Definition: recording.c:63
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:440
uint64_t offset
Definition: recording.c:2434
int Priority(void) const
Definition: recording.h:129
bool DirectoryEncoding
Definition: recording.c:77
int Position(void) const
Definition: recording.h:344
virtual void Clear(void)
Immediately clears the ring buffer.
Definition: ringbuffer.c:217
bool Error(void)
Definition: recording.c:1672
int Number(void) const
Definition: channels.h:179
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:1499
static cString MarksFileName(const cRecording *Recording)
Returns the marks file name for the given Recording (regardless whether such a file actually exists)...
Definition: recording.c:2056
int TotalFileSizeMB(void)
Definition: recording.c:1567
tIndexTs * index
Definition: recording.h:430
tComponent * GetComponent(int Index, uchar Stream, uchar Type)
Definition: epg.c:98
#define NAMEFORMATTS
Definition: recording.c:50
int NumFrames(void) const
Returns the number of frames in this recording.
Definition: recording.c:1334
bool isempty(const char *s)
Definition: tools.c:297
static tChannelID FromString(const char *s)
Definition: channels.c:25
#define MAXREMOVETIME
Definition: recording.c:69
#define dsyslog(a...)
Definition: tools.h:36
cString AddDirectory(const char *DirName, const char *FileName)
Definition: tools.c:350
void Refresh(bool Foreground=false)
Definition: recording.c:1396
#define DELEXT
Definition: recording.c:37
cRecordingsHandler(void)
Definition: recording.c:1913
static char * StripEpisodeName(char *s, bool Strip)
Definition: recording.c:946
void SetPid(int Pid, int Type)
Sets the Pid and stream Type to detect frames for.
Definition: remux.c:1455
void SetComponent(int Index, const char *s)
Definition: epg.c:78
bool Active(void)
Returns true if the cutter is currently active.
Definition: cutter.c:709
#define DEFAULTFRAMESPERSECOND
Definition: recording.h:333
time_t Start(void) const
Definition: recording.h:128
bool Close(void)
Definition: tools.c:1672
cRecordingInfo * info
Definition: recording.h:114
cEvent * ownEvent
Definition: recording.h:69
char * fileName
Definition: recording.h:74
char * sortBufferName
Definition: recording.h:103
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:574
void SetStartTime(time_t StartTime)
Definition: epg.c:212
void SetDuration(int Duration)
Definition: epg.c:223
cMark * GetPrev(int Position)
Definition: recording.c:2158
bool IsPmtPid(int Pid) const
Returns true if Pid the one of the PMT pids as defined by the current PAT.
Definition: remux.h:388
#define LOG_ERROR
Definition: tools.h:38
void SetRecordingsSortMode(const char *Directory, eRecordingsSortMode SortMode)
Definition: recording.c:3040
void ResetResume(const char *ResumeFileName=NULL)
Definition: recording.c:1641
void SetTableID(uchar TableID)
Definition: epg.c:163
const cEvent * event
Definition: recording.h:68
void Add(cListObject *Object, cListObject *After=NULL)
Definition: tools.c:2014
bool CatchUp(int Index=-1)
Definition: recording.c:2573
cResumeFile(const char *FileName, bool IsPesRecording)
Definition: recording.c:241
bool Save(FILE *f)
Definition: recording.c:2049
void ChangeState(void)
Definition: recording.h:249
bool isPesRecording
Definition: recording.h:431
char * stripspace(char *s)
Definition: tools.c:201
bool IsEdited(void) const
Definition: recording.c:1144
bool DirectoryOk(const char *DirName, bool LogErrors)
Definition: tools.c:427
char * LimitNameLengths(char *s, int PathMax, int NameMax)
Definition: recording.c:654
char * name
Definition: recording.h:106
void Sort(void)
Definition: recording.c:2129
bool Open(void)
Definition: tools.c:1662
bool IsInPath(const char *Path)
Returns true if this recording is stored anywhere under the given Path.
Definition: recording.c:1021
static void InvokeCommand(const char *State, const char *RecordingFileName, const char *SourceFileName=NULL)
Definition: recording.c:2232
double FramesPerSecond(void) const
Definition: recording.h:153
virtual ~cDirCopier()
Definition: recording.c:1684
eRecordingsSortMode RecordingsSortMode
Definition: recording.c:3022
ssize_t Read(void *Data, size_t Size)
Definition: tools.c:1774
char language[MAXLANGCODE2]
Definition: epg.h:45
cMark * GetNextBegin(cMark *EndMark=NULL)
Returns the next &quot;begin&quot; mark after EndMark, skipping any marks at the same position as EndMark...
Definition: recording.c:2176
cUnbufferedFile * SetOffset(int Number, off_t Offset=0)
Definition: recording.c:2923
const char * Title(char Delimiter= ' ', bool NewIndicator=false, int Level=-1) const
Definition: recording.c:1061
bool MakeDirs(const char *FileName, bool IsDirectory)
Definition: tools.c:445
cTimers Timers
Definition: timers.c:694
bool endswith(const char *s, const char *p)
Definition: tools.c:286
#define TIMERMACRO_EPISODE
Definition: config.h:52
static cString sprintf(const char *fmt,...) __attribute__((format(printf
Definition: tools.c:1080
off_t Seek(off_t Offset, int Whence)
Definition: tools.c:1766
int DirectoryNameMax
Definition: recording.c:76
time_t deleted
Definition: recording.h:123
int Analyze(const uchar *Data, int Length)
Analyzes the TS packets pointed to by Data.
Definition: remux.c:1472
int GetNextIFrame(int Index, bool Forward, uint16_t *FileNumber=NULL, off_t *FileOffset=NULL, int *Length=NULL)
Definition: recording.c:2672
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:293
void Align(void)
Definition: recording.c:2116
#define DELETEDLIFETIME
Definition: recording.c:64
#define esyslog(a...)
Definition: tools.h:34
bool IsOnVideoDirectoryFileSystem(void) const
Definition: recording.c:1151
bool ChangePriorityLifetime(int NewPriority, int NewLifetime)
Changes the priority and lifetime of this recording to the given values.
Definition: recording.c:1202
char * strn0cpy(char *dest, const char *src, size_t n)
Definition: tools.c:131
cMutex mutex
Definition: recording.h:434
int fileSizeMB
Definition: recording.h:107
cUnbufferedFile * NextFile(void)
Definition: recording.c:2965
static cRecordings VanishedRecordings
Definition: recording.c:81
#define RECORDFILESUFFIXTS
Definition: recording.c:2811
int AlwaysSortFoldersFirst
Definition: config.h:308
double MarkFramesPerSecond
Definition: recording.c:2016
#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:391
#define SORTMODEFILE
Definition: recording.c:59
T max(T a, T b)
Definition: tools.h:55
tChannelID channelID
Definition: recording.h:66
const cComponents * Components(void) const
Definition: recording.h:88
#define RESUMEFILESUFFIX
Definition: recording.c:52
int RecordingDirs
Definition: config.h:306
virtual ~cMark()
Definition: recording.c:2026
int UseSubtitle
Definition: config.h:303
int ReadFrame(cUnbufferedFile *f, uchar *b, int Length, int Max)
Definition: recording.c:3006
uint16_t reserved
Definition: recording.c:2430
double FramesPerSecond(void) const
Definition: recording.h:90
#define MAXWAITFORINDEXFILE
Definition: recording.c:2447
#define ICON_NEW
Definition: iconpatch.h:37
void ResetResume(void) const
Definition: recording.c:1329
static bool VideoFileSpaceAvailable(int SizeMB)
Definition: videodir.c:135
#define REMOVELATENCY
Definition: recording.c:66
time_t StartTime(void) const
Definition: timers.c:497
int Put(const uchar *Data, int Count)
Puts at most Count bytes of Data into the ring buffer.
Definition: ringbuffer.c:305
cRecording(const cRecording &)
const char * Dlang(int i) const
Definition: channels.h:164
#define INDEXFILETESTINTERVAL
Definition: recording.c:2449
bool IsNew(void) const
Definition: recording.h:165
int Last(void)
Returns the index of the last entry in this file, or -1 if the file is empty.
Definition: recording.h:451
double atod(const char *s)
Converts the given string, which is a floating point number using a &#39;.
Definition: tools.c:357
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:585
time_t start
Definition: recording.h:120
void SetAux(const char *Aux)
Definition: recording.c:434
int Count(void) const
Definition: tools.h:485
#define RECORDFILESUFFIXPES
Definition: recording.c:2809
#define TS_SYNC_BYTE
Definition: remux.h:33
eRecordingsSortMode
Definition: recording.h:504
cUnbufferedFile is used for large files that are mainly written or read in a streaming manner...
Definition: tools.h:418
static cString IndexFileName(const char *FileName, bool IsPesRecording)
Definition: recording.c:2543
bool GetLastPatPmtVersions(int &PatVersion, int &PmtVersion)
Definition: recording.c:2838
#define INFOFILESUFFIX
Definition: recording.c:56
#define IFG_BUFFER_SIZE
Definition: recording.c:2247
const char * Alang(int i) const
Definition: channels.h:163
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:475
static const char * command
Definition: recording.h:403
uint16_t number
Definition: recording.c:2437
char * Read(FILE *f)
Definition: tools.c:1398
cString IndexToHMSF(int Index, bool WithFrame, double FramesPerSecond)
Definition: recording.c:2972
#define MALLOC(type, size)
Definition: tools.h:46
virtual void Clear(void)
Definition: tools.c:2087
const cChannel * Channel(void) const
Definition: timers.h:56
int TsPid(const uchar *p)
Definition: remux.h:81
cString dirNameSrc
Definition: recording.c:1662
static cUnbufferedFile * OpenVideoFile(const char *FileName, int Flags)
Definition: videodir.c:113
#define TIMERMACRO_TITLE
Definition: config.h:51
static cString PrefixVideoFileName(const char *FileName, char Prefix)
Definition: videodir.c:156
void Stop(void)
Definition: recording.c:1809
Definition: timers.h:27
bool ChangeName(const char *NewName)
Changes the name of this recording to the given value.
Definition: recording.c:1229
bool Read(FILE *f)
Definition: recording.c:452
bool Save(int Index)
Definition: recording.c:304
static const char * Name(void)
Definition: videodir.c:53
bool isPesRecording
Definition: recording.h:54
char * SortName(void) const
Definition: recording.c:975
#define MAXFILESPERRECORDINGPES
Definition: recording.c:2808
cMark * Last(void) const
Definition: tools.h:493
cMark * GetNextEnd(cMark *BeginMark)
Returns the next &quot;end&quot; mark after BeginMark, skipping any marks at the same position as BeginMark...
Definition: recording.c:2192
#define DATAFORMATPES
Definition: recording.c:47
int priority
Definition: recording.h:121
int Lifetime(void) const
Definition: timers.h:62
double framesPerSecond
Definition: recording.h:113
cMark(int Position=0, const char *Comment=NULL, double FramesPerSecond=DEFAULTFRAMESPERSECOND)
Definition: recording.c:2019
int PathIsInUse(const char *Path)
Checks whether any recording in the given Path is currently in use and therefore the whole Path shall...
Definition: recording.c:1601
void SetTitle(const char *Title)
Definition: epg.c:180
void Unlock(void)
Definition: thread.h:93
tCharExchange CharExchange[]
Definition: recording.c:563
double framesPerSecond
Definition: recording.h:71
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:397
cRecording * GetByName(const char *FileName)
Definition: recording.c:1510
const char * Name(void) const
Definition: channels.c:122
#define LIMIT_SECS_PER_MB_RADIO
Definition: recording.c:73
cMark * GetNext(int Position)
Definition: recording.c:2167
T * Next(const T *object) const
Definition: tools.h:495
void ReadInfo(void)
Definition: recording.c:1174
const char * Comment(void) const
Definition: recording.h:345
ssize_t safe_write(int filedes, const void *buffer, size_t size)
Definition: tools.c:65
void GetRecordingsSortMode(const char *Directory)
Definition: recording.c:3029
void SetFileName(const char *FileName)
Definition: recording.c:445
bool isPesRecording
Definition: recording.h:358
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:70
int reserved
Definition: recording.c:2435
time_t lastChange
Definition: recording.h:361
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:97
bool deleted
Definition: recording.h:222
static void RemoveEmptyVideoDirectories(const char *IgnoreFiles[]=NULL)
Definition: videodir.c:176
virtual ~cRecording()
Definition: recording.c:936
char * sortBufferTime
Definition: recording.h:104
bool record
Definition: recording.h:468
bool isPesRecording
Definition: recording.h:111
#define FOLDERDELIMCHAR
Definition: recording.h:21
bool Write(FILE *f, const char *Prefix="") const
Definition: recording.c:511
#define INDEXFILESUFFIX
Definition: recording.c:2420
void SetData(const char *Title, const char *ShortText, const char *Description)
Definition: recording.c:424
#define DATAFORMATTS
Definition: recording.c:49
int GetResume(void) const
Returns the index of the frame where replay of this recording shall be resumed, or -1 in case of an e...
Definition: recording.c:1006
int LengthInSeconds(void) const
Returns the length (in seconds) of this recording, or -1 in case of error.
Definition: recording.c:1345
bool blocking
Definition: recording.h:469
static cString EditedFileName(const char *FileName)
Returns the full path name of the edited version of the recording with the given FileName.
Definition: cutter.c:657
bool Active(bool &Error)
Definition: recording.c:1865
void RemoveDeletedRecordings(void)
Definition: recording.c:133
void swap(T &a, T &b)
Definition: tools.h:57
tIndexTs(off_t Offset, bool Independent, uint16_t Number)
Definition: recording.c:2438
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:110
void UpdateByName(const char *FileName)
Definition: recording.c:1559
int GetNumRecordingsInPath(const char *Path)
Returns the total number of recordings in the given Path, including all sub-folders of Path...
Definition: recording.c:1612
int HMSFToIndex(const char *HMSF, double FramesPerSecond)
Definition: recording.c:2988
char * channelName
Definition: recording.h:67
int position
Definition: recording.h:339
void bool Start(void)
Sets the description of this thread, which will be used when logging starting or stopping of the thre...
Definition: thread.c:273
int lifetime
Definition: recording.h:122
uint32_t offset
Definition: recording.c:2427
#define RECEXT
Definition: recording.c:36
void SetStartTime(time_t Start)
Sets the start time of this recording to the given value.
Definition: recording.c:1195
bool Finished(bool &Error)
Returns true if all operations in the list have been finished.
Definition: recording.c:2002
Definition: skins.h:24
int Usage(const char *FileName=NULL) const
Definition: recording.c:1853
bool NeedsConversion(const char *p)
Definition: recording.c:576
bool GenerateIndex(const char *FileName, bool Update)
Generates the index of the existing recording with the given FileName.
Definition: recording.c:2778
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:1353
bool Ok(void)
Definition: tools.h:379
bool Delete(void)
Changes the file name so that it will no longer be visible in the &quot;Recordings&quot; menu Returns false in ...
Definition: recording.c:1255
#define MAXLIFETIME
Definition: config.h:48
bool suspensionLogged
Definition: recording.c:1665
void ConvertToPes(tIndexTs *IndexTs, int Count)
Definition: recording.c:2560
cUnbufferedFile * Open(void)
Definition: recording.c:2889
#define MININDEXAGE
Definition: recording.c:68
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:2769
int Lifetime(void) const
Definition: recording.h:130
static int Utf8CharLen(const char *s)
Definition: si.c:417
tChannelID GetChannelID(void) const
Definition: channels.h:190
int isOnVideoDirectoryFileSystem
Definition: recording.h:112
void ConvertFromPes(tIndexTs *IndexTs, int Count)
Definition: recording.c:2548
char * pFileNumber
Definition: recording.h:467
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:2710
bool Write(void) const
Definition: recording.c:542
static bool HasKeys(void)
Definition: remote.c:175
uchar type
Definition: recording.c:2428
char * fileName
Definition: recording.h:467
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:221
bool HasRecordingsSortMode(const char *Directory)
Definition: recording.c:3024
bool Throttled(void)
Definition: recording.c:1689
Definition: thread.h:63
int InstanceId
Definition: recording.c:78
bool TimedWait(cMutex &Mutex, int TimeoutMs)
Definition: thread.c:117
bool Read(void)
Definition: recording.c:524
cRecordingsHandler RecordingsHandler
Definition: recording.c:1911
int SystemExec(const char *Command, bool Detached)
Definition: thread.c:559
int HierarchyLevels(void) const
Definition: recording.c:1133
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:1384
cIndexFileGenerator(const char *RecordingName, bool Update=false)
Definition: recording.c:2260
void TouchUpdate(void)
Touches the &#39;.update&#39; file in the video directory, so that other instances of VDR that access the sam...
Definition: recording.c:1483
bool MoveRecordings(const char *OldPath, const char *NewPath)
Moves all recordings in OldPath to NewPath.
Definition: recording.c:1623
int independent
Definition: recording.c:2436
const char * FileNameDst(void) const
Definition: recording.c:1834
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:2035
#define MAXFILESPERRECORDINGTS
Definition: recording.c:2810
const char * UpdateFileName(void)
Definition: recording.c:1389
const char * Title(void) const
Definition: epg.h:100
const char * FileNameSrc(void) const
Definition: recording.c:1833
cIndexFile(const char *FileName, bool Record, bool IsPesRecording=false, bool PauseLive=false, bool Update=false)
Definition: recording.c:2451
bool Valid(void) const
Definition: channels.h:62
#define ICON_NEW_UTF8
Definition: iconpatch.h:61
bool Parse(char *s)
Definition: epg.c:466
bool Lock(int WaitSeconds=0)
Definition: tools.c:1912
bool Remove(void)
Actually removes the file from the disk Returns false in case of error.
Definition: recording.c:1281
cString BaseName(void) const
Returns the base name of this recording (without the video directory and folder). ...
Definition: recording.c:1036
int GetUsage(const char *FileName)
Returns the usage type for the given FileName.
Definition: recording.c:1982
cString fileName
Definition: recording.h:356
cRecordings DeletedRecordings
Definition: cutter.h:18
cIndexFileGenerator * indexFileGenerator
Definition: recording.h:433
cString comment
Definition: recording.h:340
cRecordings(bool Deleted=false)
Definition: recording.c:1370
Definition: epg.h:42
Definition: skins.h:24
#define RECORDFILESUFFIXLEN
Definition: recording.c:2812
int DirectoryPathMax
Definition: recording.c:75
char * titleBuffer
Definition: recording.h:102
int GetNumSequences(void)
Returns the actual number of sequences to be cut from the recording.
Definition: recording.c:2210
static bool RemoveVideoFile(const char *FileName)
Definition: videodir.c:130
#define MAXDPIDS
Definition: channels.h:36
T * First(void) const
Definition: tools.h:492
#define MIN_TS_PACKETS_FOR_FRAME_DETECTOR
Definition: remux.h:473
void Del(cListObject *Object, bool DeleteObject=true)
Definition: tools.c:2046
cString ToString(void) const
Definition: channels.c:42
int channel
Definition: recording.h:109
void DelAll(void)
Deletes/terminates all operations.
Definition: recording.c:1975
#define MARKSFILESUFFIX
Definition: recording.c:57
static bool MoveVideoFile(const char *FromName, const char *ToName)
Definition: videodir.c:125
int WarEagleIcons
Definition: config.h:261
#define PATPID
Definition: remux.h:52
bool IsLangUtf8(void)
Definition: iconpatch.c:9
cMark * Get(int Position)
Definition: recording.c:2149
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:2814
cString fileName
Definition: recording.h:428
#define MAXPRIORITY
Definition: config.h:43
void Delete(void)
Definition: recording.c:2757
time_t lastFileTime
Definition: recording.h:360
bool NeedsUpdate(void)
Definition: recording.c:1491
off_t FileSize(const char *FileName)
returns the size of the given file, or -1 in case of an error (e.g. if the file doesn&#39;t exist) ...
Definition: tools.c:677
#define RESUME_NOT_INITIALIZED
Definition: recording.c:560
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:2913
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:920
void Delete(void)
Definition: recording.c:332
bool IsSingleEvent(void) const
Definition: timers.c:346
void ClearSortName(void)
Definition: recording.c:999
cRecordings Recordings
Any access to Recordings that loops through the list of recordings needs to hold a thread lock on thi...
Definition: recording.c:1366
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:1579
char * skipspace(const char *s)
Definition: tools.h:200
#define SECSINDAY
Definition: tools.h:41
static bool IsOnVideoDirectoryFileSystem(const char *FileName)
Definition: videodir.c:181
void Add(int Position)
If this cMarks object is used by multiple threads, the caller must Lock() it before calling Add() and...
Definition: recording.c:2142
cRecordingsHandlerEntry * Get(const char *FileName)
Definition: recording.c:1923
void IncRecordingsSortMode(const char *Directory)
Definition: recording.c:3048
int NumComponents(void) const
Definition: epg.h:59
const char * Name(void) const
Returns the full name of the recording (without the video directory.
Definition: recording.h:142
#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:150
double framesPerSecond
Definition: recording.h:357
cResumeFile resumeFile
Definition: recording.h:432
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
static cRecordControl * GetRecordControl(const char *FileName)
Definition: menu.c:5060
void DelByName(const char *FileName)
Definition: recording.c:1535
char * fileName
Definition: recording.h:53
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:85
cList< cRecordingsHandlerEntry > operations
Definition: recording.h:294
bool Update(void)
Definition: recording.c:2074
#define MAXSPIDS
Definition: channels.h:37
void SetVersion(uchar Version)
Definition: epg.c:168
void ClearSortNames(void)
Definition: recording.c:1651
int SecondsToFrames(int Seconds, double FramesPerSecond)
Definition: recording.c:2999
ssize_t safe_read(int filedes, void *buffer, size_t size)
Definition: tools.c:53
bool StateChanged(int &State)
Definition: recording.c:1475
const char * Slang(int i) const
Definition: channels.h:165
cMutex MutexMarkFramesPerSecond
Definition: recording.c:2017
const cComponents * Components(void) const
Definition: epg.h:103
cString Folder(void) const
Returns the name of the folder this recording is stored in (without the video directory).
Definition: recording.c:1029
int Read(void)
Definition: recording.c:259
int resume
Definition: recording.h:101
static const tChannelID InvalidID
Definition: channels.h:72
int Priority(void) const
Definition: timers.h:61
bool Load(const char *RecordingFileName, double FramesPerSecond=DEFAULTFRAMESPERSECOND, bool IsPesRecording=false)
Definition: recording.c:2061
cRecordingInfo(const cChannel *Channel=NULL, const cEvent *Event=NULL)
Definition: recording.c:344
#define TS_SIZE
Definition: remux.h:34
bool error
Definition: recording.c:1664
bool DeleteMarks(void)
Deletes the editing marks from this recording (if any).
Definition: recording.c:1163
static cUnbufferedFile * Create(const char *FileName, int Flags, mode_t Mode=DEFFILEMODE)
Definition: tools.c:1883
time_t nextUpdate
Definition: recording.h:359
bool Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent=NULL, int *Length=NULL)
Definition: recording.c:2646
void TouchFile(const char *FileName)
Definition: tools.c:663
bool Active(void)
Checks whether there is currently any operation running and starts the next one form the list if the ...
Definition: recording.c:1990
#define DISKCHECKDELTA
Definition: recording.c:65
#define MINDISKSPACE
Definition: recording.c:61
bool DoubleEqual(double a, double b)
Definition: tools.h:87
bool IsStillRecording(void)
Definition: recording.c:2752
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:2273
char * compactspace(char *s)
Definition: tools.c:213
cString strescape(const char *s, const char *chars)
Definition: tools.c:254
#define LOCK_THREAD
Definition: thread.h:165
void SetEventID(tEventID EventID)
Definition: epg.c:152
#define INDEXFILECHECKINTERVAL
Definition: recording.c:2448
char * ExchangeChars(char *s, bool ToFileSystem)
Definition: recording.c:583
bool isPesRecording
Definition: recording.h:470
uint16_t fileNumber
Definition: recording.h:466
cString recordingFileName
Definition: recording.h:355
cString dirNameDst
Definition: recording.c:1663
char * fileName
Definition: recording.h:105
const char * ShortText(void) const
Definition: epg.h:101
const char * FileName(void) const
Returns the full path name to the recording directory, including the video directory and the actual &#39;...
Definition: recording.c:1043
#define INDEXCATCHUPWAIT
Definition: recording.c:2424
time_t lastUpdate
Definition: recording.h:224
bool Error(void)
Returns true if an error occurred while cutting the recording.
Definition: cutter.c:722
#define NAMEFORMATPES
Definition: recording.c:48
virtual int Compare(const cListObject &ListObject) const
Must return 0 if this object is equal to ListObject, a positive value if it is &quot;greater&quot;, and a negative value if it is &quot;smaller&quot;.
Definition: recording.c:1015
void Cancel(int WaitSeconds=0)
Cancels the thread by first setting &#39;running&#39; to false, so that the Action() loop can finish in an or...
Definition: thread.c:323
const char * PrefixFileName(char Prefix)
Definition: recording.c:1122
cDirCopier(const char *DirNameSrc, const char *DirNameDst)
Definition: recording.c:1675
const char * Aux(void) const
Definition: timers.h:65
bool WriteInfo(const char *OtherFileName=NULL)
Writes in info file of this recording.
Definition: recording.c:1182
bool Save(void)
Definition: recording.c:2106
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:1705
cMark * Prev(const cMark *object) const
Definition: tools.h:494
static cRemoveDeletedRecordingsThread RemoveDeletedRecordingsThread
Definition: recording.c:129
cUnbufferedFile * file
Definition: recording.h:465
time_t LastModifiedTime(const char *FileName)
Definition: tools.c:669
void SetFile(const char *File)
Definition: timers.c:392
int numFrames
Definition: recording.h:108
char * strreplace(char *s, char c1, char c2)
Definition: tools.c:139
void Del(const char *FileName)
Deletes the given FileName from the list of operations.
Definition: recording.c:1966
cRecordingsHandlerEntry(int Usage, const char *FileNameSrc, const char *FileNameDst)
Definition: recording.c:1838
void AddByName(const char *FileName, bool TriggerUpdate=true)
Definition: recording.c:1522
bool IsPesRecording(void) const
Definition: recording.h:167
int IsInUse(void) const
Checks whether this recording is currently in use and therefore shall not be tampered with...
Definition: recording.c:1318
#define MAX_LINK_LEVEL
Definition: recording.c:71
virtual ~cRecordings()
Definition: recording.c:1379
bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset)
Definition: recording.c:2629
#define RUC_DELETERECORDING
Definition: recording.h:399
double framesPerSecond
Definition: recording.h:338
#define SUMMARYFILESUFFIX
Definition: recording.c:54
bool Add(int Usage, const char *FileNameSrc, const char *FileNameDst=NULL)
Adds the given FileNameSrc to the recordings handler for (later) processing.
Definition: recording.c:1934
int ResumeID
Definition: config.h:351
bool Undelete(void)
Changes the file name so that it will be visible in the &quot;Recordings&quot; menu again and not processed by ...
Definition: recording.c:1292
void writechar(int filedes, char c)
Definition: tools.c:85
Definition: tools.h:168
int Close(void)
Definition: tools.c:1731
static const char * NowReplaying(void)
Definition: menu.c:5245
cString ToText(void)
Definition: recording.c:2030
bool HasMarks(void)
Returns true if this recording has any editing marks.
Definition: recording.c:1158
void Lock(void)
Definition: thread.h:92
bool ScanVideoDir(const char *DirName, bool Foreground=false, int LinkLevel=0, int DirLevel=0)
Definition: recording.c:1409
cSkins Skins
Definition: skins.c:219
virtual int Available(void)
Definition: ringbuffer.c:211
#define MAXAPIDS
Definition: channels.h:35
uchar number
Definition: recording.c:2429