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