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