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