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