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