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