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