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