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