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