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