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