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