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