16 #define __STDC_FORMAT_MACROS // Required for format specifiers 35 #define SUMMARYFALLBACK 48 #define DATAFORMATPES "%4d-%02d-%02d.%02d%*c%02d.%02d.%02d" RECEXT 49 #define NAMEFORMATPES "%s/%s/" "%4d-%02d-%02d.%02d.%02d.%02d.%02d" RECEXT 50 #define DATAFORMATTS "%4d-%02d-%02d.%02d.%02d.%d-%d" RECEXT 51 #define NAMEFORMATTS "%s/%s/" DATAFORMATTS 53 #define RESUMEFILESUFFIX "/resume%s%s" 54 #ifdef SUMMARYFALLBACK 55 #define SUMMARYFILESUFFIX "/summary.vdr" 57 #define INFOFILESUFFIX "/info" 58 #define MARKSFILESUFFIX "/marks" 60 #define SORTMODEFILE ".sort" 61 #define TIMERRECFILE ".timer" 63 #define MINDISKSPACE 1024 // MB 65 #define REMOVECHECKDELTA 60 // seconds between checks for removing deleted files 66 #define DELETEDLIFETIME 300 // seconds after which a deleted recording will be actually removed 67 #define DISKCHECKDELTA 100 // seconds between checks for free disk space 68 #define REMOVELATENCY 10 // seconds to wait until next check after removing a file 69 #define MARKSUPDATEDELTA 10 // seconds between checks for updating editing marks 70 #define MININDEXAGE 3600 // seconds before an index file is considered no longer to be written 71 #define MAXREMOVETIME 10 // seconds after which to return from removing deleted recordings 73 #define MAX_LINK_LEVEL 6 75 #define LIMIT_SECS_PER_MB_RADIO 5 // radio recordings typically have more than this 92 :
cThread(
"remove deleted recordings", true)
100 if (LockFile.
Lock()) {
101 time_t StartTime = time(NULL);
102 bool deleted =
false;
104 for (
cRecording *r = DeletedRecordings->First(); r; ) {
114 DeletedRecordings->Del(r);
119 r = DeletedRecordings->
Next(r);
134 static time_t LastRemoveCheck = 0;
136 if (!RemoveDeletedRecordingsThread.
Active()) {
138 for (
const cRecording *r = DeletedRecordings->First(); r; r = DeletedRecordings->
Next(r)) {
140 RemoveDeletedRecordingsThread.
Start();
145 LastRemoveCheck = time(NULL);
156 static time_t LastFreeDiskCheck = 0;
157 int Factor = (Priority == -1) ? 10 : 1;
158 if (Force || time(NULL) - LastFreeDiskCheck >
DISKCHECKDELTA / Factor) {
162 if (!LockFile.
Lock())
165 isyslog(
"low disk space while recording, trying to remove a deleted recording...");
166 int NumDeletedRecordings = 0;
169 NumDeletedRecordings = DeletedRecordings->Count();
170 if (NumDeletedRecordings) {
178 r = DeletedRecordings->
Next(r);
183 DeletedRecordings->Del(r0);
188 if (NumDeletedRecordings == 0) {
193 if (DeletedRecordings->Count())
198 isyslog(
"...no deleted recording found, trying to delete an old recording...");
200 Recordings->SetExplicitModify();
201 if (Recordings->Count()) {
218 r = Recordings->
Next(r);
222 Recordings->SetModified();
227 isyslog(
"...no old recording found, giving up");
230 isyslog(
"...no deleted recording found, priority %d too low to trigger deleting an old recording", Priority);
233 LastFreeDiskCheck = time(NULL);
241 isPesRecording = IsPesRecording;
243 fileName =
MALLOC(
char, strlen(FileName) + strlen(Suffix) + 1);
245 strcpy(fileName, FileName);
249 esyslog(
"ERROR: can't allocate memory for resume file name");
262 if (stat(fileName, &st) == 0) {
263 if ((st.st_mode & S_IWUSR) == 0)
266 if (isPesRecording) {
267 int f = open(fileName, O_RDONLY);
269 if (
safe_read(f, &resume,
sizeof(resume)) !=
sizeof(resume)) {
275 else if (errno != ENOENT)
279 FILE *f = fopen(fileName,
"r");
284 while ((s = ReadLine.
Read(f)) != NULL) {
288 case 'I': resume = atoi(t);
295 else if (errno != ENOENT)
305 if (isPesRecording) {
306 int f = open(fileName, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE);
312 Recordings->ResetResume(fileName);
317 FILE *f = fopen(fileName,
"w");
319 fprintf(f,
"I %d\n", Index);
322 Recordings->ResetResume(fileName);
335 if (
remove(fileName) == 0) {
337 Recordings->ResetResume(fileName);
339 else if (errno != ENOENT)
349 channelName = Channel ? strdup(Channel->
Name()) : NULL;
350 ownEvent = Event ? NULL :
new cEvent(0);
351 event = ownEvent ? ownEvent : Event;
364 for (
int i = 0; i <
MAXAPIDS; i++) {
365 const char *s = Channel->
Alang(i);
370 else if (strlen(s) > strlen(Component->
language))
377 for (
int i = 0; i <
MAXDPIDS; i++) {
378 const char *s = Channel->
Dlang(i);
385 else if (strlen(s) > strlen(Component->
language))
390 for (
int i = 0; i <
MAXSPIDS; i++) {
391 const char *s = Channel->
Slang(i);
396 else if (strlen(s) > strlen(Component->
language))
400 if (Components != event->Components())
401 ((
cEvent *)event)->SetComponents(Components);
429 ((
cEvent *)event)->SetTitle(Title);
431 ((
cEvent *)event)->SetShortText(ShortText);
433 ((
cEvent *)event)->SetDescription(Description);
439 aux = Aux ? strdup(Aux) : NULL;
444 framesPerSecond = FramesPerSecond;
449 bool IsPesRecording = fileName &&
endswith(fileName,
".vdr");
460 while ((s = ReadLine.
Read(f)) != NULL) {
465 char *p = strchr(t,
' ');
476 unsigned int EventID;
479 unsigned int TableID = 0;
480 unsigned int Version = 0xFF;
481 int n = sscanf(t,
"%u %ld %d %X %X", &EventID, &StartTime, &Duration, &TableID, &Version);
482 if (n >= 3 && n <= 5) {
483 ownEvent->SetEventID(EventID);
484 ownEvent->SetStartTime(StartTime);
485 ownEvent->SetDuration(Duration);
486 ownEvent->SetTableID(
uchar(TableID));
487 ownEvent->SetVersion(
uchar(Version));
491 case 'F': framesPerSecond =
atod(t);
493 case 'L': lifetime = atoi(t);
495 case 'P': priority = atoi(t);
501 default:
if (!ownEvent->Parse(s)) {
502 esyslog(
"ERROR: EPG data problem in line %d", line);
515 if (channelID.Valid())
516 fprintf(f,
"%sC %s%s%s\n", Prefix, *channelID.ToString(), channelName ?
" " :
"", channelName ? channelName :
"");
517 event->Dump(f, Prefix,
true);
518 fprintf(f,
"%sF %s\n", Prefix, *
dtoa(framesPerSecond,
"%.10g"));
519 fprintf(f,
"%sP %d\n", Prefix, priority);
520 fprintf(f,
"%sL %d\n", Prefix, lifetime);
522 fprintf(f,
"%s@ %s\n", Prefix, aux);
530 FILE *f = fopen(fileName,
"r");
535 esyslog(
"ERROR: EPG data problem in file %s", fileName);
538 else if (errno != ENOENT)
562 #define RESUME_NOT_INITIALIZED (-2) 595 case ' ': *p =
'_';
break;
602 if (
char *NewBuffer = (
char *)realloc(s, strlen(s) + 10)) {
606 sprintf(buf,
"#%02X", (
unsigned char)*p);
607 memmove(p + 2, p, strlen(p) + 1);
612 esyslog(
"ERROR: out of memory");
619 case '_': *p =
' ';
break;
624 if (strlen(p) > 2 && isxdigit(*(p + 1)) && isxdigit(*(p + 2))) {
626 sprintf(buf,
"%c%c", *(p + 1), *(p + 2));
630 memmove(p + 1, p + 3, strlen(p) - 2);
636 case '\x01': *p =
'\'';
break;
637 case '\x02': *p =
'/';
break;
638 case '\x03': *p =
':';
break;
644 for (
struct tCharExchange *ce = CharExchange; ce->
a && ce->b; ce++) {
645 if (*p == (ToFileSystem ? ce->a : ce->b)) {
646 *p = ToFileSystem ? ce->b : ce->a;
668 int Length = strlen(s);
671 bool NameTooLong =
false;
675 for (
char *p = s; *p; p++) {
678 NameTooLong |= NameLength > NameMax;
699 NameTooLong |= NameLength > NameMax;
707 while (i-- > 0 && a[i] >= 0) {
712 if (NameLength > NameMax) {
715 while (i-- > 0 && a[i] >= 0) {
717 if (NameLength - l <= NameMax) {
718 memmove(s + i, s + n, Length - n + 1);
719 memmove(a + i, a + n, Length - n + 1);
732 while (PathLength > PathMax && n > 0) {
737 while (--i > 0 && a[i - 1] >= 0) {
741 if (PathLength - l <= PathMax)
747 memmove(s + b, s + n, Length - n + 1);
762 sortBufferName = sortBufferTime = NULL;
768 isPesRecording =
false;
769 isOnVideoDirectoryFileSystem = -1;
774 const char *Title = Event ? Event->
Title() : NULL;
775 const char *Subtitle = Event ? Event->
ShortText() : NULL;
782 if (macroTITLE || macroEPISODE) {
783 name = strdup(Timer->
File());
787 int l = strlen(name);
798 name = strdup(Timer->
File());
809 info->priority = priority;
810 info->lifetime = lifetime;
822 isPesRecording =
false;
823 isOnVideoDirectoryFileSystem = -1;
828 sortBufferName = sortBufferTime = NULL;
829 FileName = fileName = strdup(FileName);
830 if (*(fileName + strlen(fileName) - 1) ==
'/')
831 *(fileName + strlen(fileName) - 1) = 0;
834 const char *p = strrchr(FileName,
'/');
839 time_t now = time(NULL);
841 struct tm t = *localtime_r(&now, &tm_r);
843 if (7 == sscanf(p + 1,
DATAFORMATTS, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &channel, &instanceId)
844 || 7 == sscanf(p + 1,
DATAFORMATPES, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &priority, &lifetime)) {
849 name =
MALLOC(
char, p - FileName + 1);
850 strncpy(name, FileName, p - FileName);
851 name[p - FileName] = 0;
853 isPesRecording = instanceId < 0;
860 FILE *f = fopen(InfoFileName,
"r");
863 esyslog(
"ERROR: EPG data problem in file %s", *InfoFileName);
864 else if (!isPesRecording) {
865 priority = info->priority;
866 lifetime = info->lifetime;
867 framesPerSecond = info->framesPerSecond;
871 else if (errno == ENOENT)
872 info->ownEvent->SetTitle(name);
875 #ifdef SUMMARYFALLBACK 879 FILE *f = fopen(SummaryFileName,
"r");
882 char *data[3] = { NULL };
885 while ((s = ReadLine.
Read(f)) != NULL) {
886 if (*s || line > 1) {
889 len += strlen(data[line]) + 1;
890 if (
char *NewBuffer = (
char *)realloc(data[line], len + 1)) {
891 data[line] = NewBuffer;
892 strcat(data[line],
"\n");
893 strcat(data[line], s);
896 esyslog(
"ERROR: out of memory");
899 data[line] = strdup(s);
909 else if (data[1] && data[2]) {
913 int len = strlen(data[1]);
915 if (
char *NewBuffer = (
char *)realloc(data[1], len + 1 + strlen(data[2]) + 1)) {
917 strcat(data[1],
"\n");
918 strcat(data[1], data[2]);
924 esyslog(
"ERROR: out of memory");
927 info->SetData(data[0], data[1], data[2]);
928 for (
int i = 0; i < 3; i ++)
931 else if (errno != ENOENT)
941 free(sortBufferName);
942 free(sortBufferTime);
950 char *t = s, *s1 = NULL, *s2 = NULL;
971 memmove(s1, s2, t - s2 + 1);
984 strftime(buf,
sizeof(buf),
"%Y%m%d%H%I", localtime_r(&start, &tm_r));
992 int l = strxfrm(NULL, s, 0) + 1;
1003 free(sortBufferName);
1004 free(sortBufferTime);
1005 sortBufferName = sortBufferTime = NULL;
1016 cResumeFile ResumeFile(FileName(), isPesRecording);
1017 resume = ResumeFile.
Read();
1026 return strcasecmp(SortName(), r->
SortName());
1028 return strcasecmp(r->
SortName(), SortName());
1035 int l = strlen(Path);
1057 struct tm *t = localtime_r(&start, &tm_r);
1059 int ch = isPesRecording ? priority : channel;
1060 int ri = isPesRecording ? lifetime : instanceId;
1062 if (strcmp(Name, name) != 0)
1063 dsyslog(
"recording file name '%s' truncated to '%s'", name, Name);
1076 if (Level < 0 || Level == HierarchyLevels()) {
1078 struct tm *t = localtime_r(&start, &tm_r);
1086 int Minutes =
max(0, (LengthInSeconds() + 30) / 60);
1107 s = &titleBuffer[strlen(titleBuffer) - 1];
1111 else if (Level < HierarchyLevels()) {
1112 const char *s = name;
1122 titleBuffer =
MALLOC(
char, s - p + 3);
1123 *titleBuffer = Delimiter;
1124 *(titleBuffer + 1) = Delimiter;
1125 strn0cpy(titleBuffer + 2, p, s - p + 1);
1137 fileName = strdup(p);
1145 const char *s = name;
1157 s = !s ? name : s + 1;
1163 if (isOnVideoDirectoryFileSystem < 0)
1165 return isOnVideoDirectoryFileSystem;
1181 priority = info->priority;
1182 lifetime = info->lifetime;
1183 framesPerSecond = info->framesPerSecond;
1208 if (NewPriority != Priority() || NewLifetime != Lifetime()) {
1209 dsyslog(
"changing priority/lifetime of '%s' to %d/%d", Name(), NewPriority, NewLifetime);
1210 if (IsPesRecording()) {
1211 cString OldFileName = FileName();
1212 priority = NewPriority;
1213 lifetime = NewLifetime;
1216 cString NewFileName = FileName();
1219 info->SetFileName(NewFileName);
1222 priority = info->priority = NewPriority;
1223 lifetime = info->lifetime = NewLifetime;
1233 if (strcmp(NewName, Name())) {
1234 dsyslog(
"changing name of '%s' to '%s'", Name(), NewName);
1236 cString OldFileName = FileName();
1240 name = strdup(NewName);
1241 cString NewFileName = FileName();
1242 bool Exists = access(NewFileName, F_OK) == 0;
1244 esyslog(
"ERROR: recording '%s' already exists", NewName);
1247 name = strdup(OldName);
1249 fileName = strdup(OldFileName);
1252 isOnVideoDirectoryFileSystem = -1;
1261 char *NewName = strdup(FileName());
1262 char *ext = strrchr(NewName,
'.');
1263 if (ext && strcmp(ext,
RECEXT) == 0) {
1264 strncpy(ext,
DELEXT, strlen(ext));
1265 if (access(NewName, F_OK) == 0) {
1267 isyslog(
"removing recording '%s'", NewName);
1270 isyslog(
"deleting recording '%s'", FileName());
1271 if (access(FileName(), F_OK) == 0) {
1276 isyslog(
"recording '%s' vanished", FileName());
1288 esyslog(
"attempt to remove recording %s", FileName());
1291 isyslog(
"removing recording %s", FileName());
1298 char *NewName = strdup(FileName());
1299 char *ext = strrchr(NewName,
'.');
1300 if (ext && strcmp(ext,
DELEXT) == 0) {
1301 strncpy(ext,
RECEXT, strlen(ext));
1302 if (access(NewName, F_OK) == 0) {
1304 esyslog(
"ERROR: attempt to undelete '%s', while recording '%s' exists", FileName(), NewName);
1308 isyslog(
"undeleting recording '%s'", FileName());
1309 if (access(FileName(), F_OK) == 0)
1312 isyslog(
"deleted recording '%s' vanished", FileName());
1339 if (numFrames < 0) {
1350 int nf = NumFrames();
1352 return int(nf / FramesPerSecond());
1358 if (fileSizeMB < 0) {
1374 void ScanVideoDir(
const char *DirName,
int LinkLevel = 0,
int DirLevel = 0);
1376 virtual void Action(
void);
1383 :
cThread(
"video directory scanner", true)
1417 if (lstat(buffer, &st) == 0) {
1419 if (S_ISLNK(st.st_mode)) {
1421 isyslog(
"max link level exceeded - not scanning %s", *buffer);
1425 if (stat(buffer, &st) != 0)
1428 if (S_ISDIR(st.st_mode)) {
1436 Recordings->
Lock(StateKey,
true);
1458 if (!
initial && DirLevel == 0) {
1464 if (access(r->
FileName(), F_OK) != 0)
1510 if (lastModified > time(NULL))
1530 if (Recording->Id() == Id)
1540 if (strcmp(Recording->FileName(), FileName) == 0)
1567 Recording = dummy =
new cRecording(FileName);
1570 Del(Recording,
false);
1571 char *ext = strrchr(Recording->
fileName,
'.');
1573 strncpy(ext,
DELEXT, strlen(ext));
1574 if (access(Recording->
FileName(), F_OK) == 0) {
1576 DeletedRecordings->Add(Recording);
1587 Recording->ReadInfo();
1594 int FileSizeMB = Recording->FileSizeMB();
1595 if (FileSizeMB > 0 && Recording->IsOnVideoDirectoryFileSystem())
1606 if (Recording->IsOnVideoDirectoryFileSystem()) {
1607 int FileSizeMB = Recording->FileSizeMB();
1608 if (FileSizeMB > 0) {
1609 int LengthInSeconds = Recording->LengthInSeconds();
1610 if (LengthInSeconds > 0) {
1613 length += LengthInSeconds;
1619 return (size && length) ? double(size) * 60 / length : -1;
1626 if (Recording->IsInPath(Path))
1627 Use |= Recording->IsInUse();
1636 if (Recording->IsInPath(Path))
1644 if (OldPath && NewPath && strcmp(OldPath, NewPath)) {
1645 dsyslog(
"moving '%s' to '%s'", OldPath, NewPath);
1648 if (Recording->IsInPath(OldPath)) {
1649 const char *p = Recording->Name() + strlen(OldPath);
1651 if (!Recording->ChangeName(NewName))
1665 if (!ResumeFileName || strncmp(ResumeFileName, Recording->FileName(), strlen(Recording->FileName())) == 0)
1666 Recording->ResetResume();
1673 Recording->ClearSortName();
1684 bool Throttled(
void);
1685 virtual void Action(
void);
1687 cDirCopier(
const char *DirNameSrc,
const char *DirNameDst);
1710 dsyslog(
"suspending copy thread");
1716 dsyslog(
"resuming copy thread");
1733 size_t BufferSize = BUFSIZ;
1734 uchar *Buffer = NULL;
1748 size_t Read =
safe_read(From, Buffer, BufferSize);
1750 size_t Written =
safe_write(To, Buffer, Read);
1751 if (Written != Read) {
1752 esyslog(
"ERROR: can't write to destination file '%s': %m", *FileNameDst);
1756 else if (Read == 0) {
1758 if (fsync(To) < 0) {
1759 esyslog(
"ERROR: can't sync destination file '%s': %m", *FileNameDst);
1762 if (close(From) < 0) {
1763 esyslog(
"ERROR: can't close source file '%s': %m", *FileNameSrc);
1766 if (close(To) < 0) {
1767 esyslog(
"ERROR: can't close destination file '%s': %m", *FileNameDst);
1771 off_t FileSizeSrc =
FileSize(FileNameSrc);
1772 off_t FileSizeDst =
FileSize(FileNameDst);
1773 if (FileSizeSrc != FileSizeDst) {
1774 esyslog(
"ERROR: file size discrepancy: %" PRId64
" != %" PRId64, FileSizeSrc, FileSizeDst);
1779 esyslog(
"ERROR: can't read from source file '%s': %m", *FileNameSrc);
1783 else if ((e = d.
Next()) != NULL) {
1788 if (stat(FileNameSrc, &st) < 0) {
1789 esyslog(
"ERROR: can't access source file '%s': %m", *FileNameSrc);
1792 if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) {
1793 esyslog(
"ERROR: source file '%s' is neither a regular file nor a symbolic link", *FileNameSrc);
1796 dsyslog(
"copying file '%s' to '%s'", *FileNameSrc, *FileNameDst);
1798 BufferSize =
max(
size_t(st.st_blksize * 10),
size_t(BUFSIZ));
1801 esyslog(
"ERROR: out of memory");
1805 if (access(FileNameDst, F_OK) == 0) {
1806 esyslog(
"ERROR: destination file '%s' already exists", *FileNameDst);
1809 if ((From = open(FileNameSrc, O_RDONLY)) < 0) {
1810 esyslog(
"ERROR: can't open source file '%s': %m", *FileNameSrc);
1813 if ((To = open(FileNameDst, O_WRONLY | O_CREAT | O_EXCL, DEFFILEMODE)) < 0) {
1814 esyslog(
"ERROR: can't open destination file '%s': %m", *FileNameDst);
1853 int Usage(
const char *FileName = NULL)
const;
1865 fileNameSrc = FileNameSrc;
1866 fileNameDst = FileNameDst;
1881 if (FileName && *FileName) {
1882 if (strcmp(FileName, fileNameSrc) == 0)
1884 else if (strcmp(FileName, fileNameDst) == 0)
1896 if (cutter->Active())
1898 error = cutter->Error();
1903 if (copier->Active())
1905 error = copier->Error();
1911 if ((Usage() &
ruCut) != 0) {
1912 cutter =
new cCutter(FileNameSrc());
1914 Recordings->
AddByName(FileNameDst(),
false);
1917 copier =
new cDirCopier(FileNameSrc(), FileNameDst());
1937 if ((usage &
ruCut)) {
1954 if ((usage &
ruMove) != 0)
1965 :
cThread(
"recordings handler")
1982 Recordings->SetExplicitModify();
1985 if (!r->Active(Recordings)) {
1986 error |= r->Error();
1987 r->Cleanup(Recordings);
2003 if (FileName && *FileName) {
2007 if (strcmp(FileName, r->FileNameSrc()) == 0 || strcmp(FileName, r->FileNameDst()) == 0)
2016 dsyslog(
"recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2019 if (FileNameSrc && *FileNameSrc) {
2020 if (Usage ==
ruCut || FileNameDst && *FileNameDst) {
2022 if (Usage ==
ruCut && !FileNameDst)
2024 if (!
Get(FileNameSrc) && !
Get(FileNameDst)) {
2032 esyslog(
"ERROR: file name already present in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2035 esyslog(
"ERROR: missing dst file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2038 esyslog(
"ERROR: missing src file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2041 esyslog(
"ERROR: invalid usage in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2063 return r->Usage(FileName);
2086 position = Position;
2088 framesPerSecond = FramesPerSecond;
2097 return cString::sprintf(
"%s%s%s", *
IndexToHMSF(position,
true, framesPerSecond), Comment() ?
" " :
"", Comment() ? Comment() :
"");
2105 const char *p = strchr(s,
' ');
2109 comment = strdup(p);
2116 return fprintf(f,
"%s\n", *ToText()) > 0;
2129 if (errno != ENOENT) {
2137 bool cMarks::Load(
const char *RecordingFileName,
double FramesPerSecond,
bool IsPesRecording)
2139 recordingFileName = RecordingFileName;
2141 framesPerSecond = FramesPerSecond;
2142 isPesRecording = IsPesRecording;
2151 time_t t = time(NULL);
2152 if (t > nextUpdate && *fileName) {
2154 if (LastModified != lastFileTime)
2155 lastChange = LastModified > 0 ? LastModified : t;
2156 int d = t - lastChange;
2164 if (LastModified != lastFileTime) {
2165 lastFileTime = LastModified;
2166 if (lastFileTime == t)
2168 cMutexLock MutexLock(&MutexMarkFramesPerSecond);
2191 cIndexFile IndexFile(recordingFileName,
false, isPesRecording);
2192 for (
cMark *m = First(); m; m = Next(m)) {
2194 if (m->Position() - p) {
2203 for (
cMark *m1 = First(); m1; m1 = Next(m1)) {
2204 for (
cMark *m2 = Next(m1); m2; m2 = Next(m2)) {
2205 if (m2->Position() < m1->Position()) {
2206 swap(m1->position, m2->position);
2207 swap(m1->comment, m2->comment);
2221 for (
const cMark *mi = First(); mi; mi = Next(mi)) {
2222 if (mi->Position() == Position)
2230 for (
const cMark *mi = Last(); mi; mi = Prev(mi)) {
2231 if (mi->Position() < Position)
2239 for (
const cMark *mi = First(); mi; mi = Next(mi)) {
2240 if (mi->Position() > Position)
2248 const cMark *BeginMark = EndMark ? Next(EndMark) : First();
2249 if (BeginMark && EndMark && BeginMark->
Position() == EndMark->
Position()) {
2250 while (
const cMark *NextMark = Next(BeginMark)) {
2251 if (BeginMark->
Position() == NextMark->Position()) {
2252 if (!(BeginMark = Next(NextMark)))
2266 const cMark *EndMark = Next(BeginMark);
2267 if (EndMark && BeginMark && BeginMark->
Position() == EndMark->
Position()) {
2268 while (
const cMark *NextMark = Next(EndMark)) {
2269 if (EndMark->
Position() == NextMark->Position()) {
2270 if (!(EndMark = Next(NextMark)))
2282 int NumSequences = 0;
2283 if (
const cMark *BeginMark = GetNextBegin()) {
2284 while (
const cMark *EndMark = GetNextEnd(BeginMark)) {
2286 BeginMark = GetNextBegin(EndMark);
2290 if (NumSequences == 1 && BeginMark->Position() == 0)
2294 return NumSequences;
2309 isyslog(
"executing '%s'", *cmd);
2316 #define IFG_BUFFER_SIZE KILOBYTE(100) 2323 virtual void Action(
void);
2330 :
cThread(
"index file generator")
2331 ,recordingName(RecordingName)
2344 bool IndexFileComplete =
false;
2345 bool IndexFileWritten =
false;
2346 bool Rewind =
false;
2355 off_t FrameOffset = -1;
2356 uint16_t FileNumber = 1;
2357 off_t FileOffset = 0;
2363 Last = IndexFile.
Last();
2364 if (Last >= 0 && !IndexFile.
Get(Last, &FileNumber, &FileOffset, &Independent, &Length))
2368 isyslog(
"updating index file");
2371 isyslog(
"generating index file");
2374 bool Stuffed =
false;
2378 ReplayFile = FileName.
SetOffset(FileNumber, FileOffset);
2379 FileSize = FileOffset;
2387 if (FrameDetector.
Synced()) {
2390 FrameOffset = FileSize;
2391 int Processed = FrameDetector.
Analyze(Data, Length);
2392 if (Processed > 0) {
2394 if (IndexFileWritten || Last < 0)
2397 IndexFileWritten =
true;
2399 FileSize += Processed;
2400 Buffer.
Del(Processed);
2405 int Processed = FrameDetector.
Analyze(Data, Length);
2406 if (Processed > 0) {
2407 if (FrameDetector.
Synced()) {
2411 Buffer.
Del(Processed);
2417 while (Length >= TS_SIZE) {
2421 else if (PatPmtParser.
IsPmtPid(Pid))
2427 FrameDetector.
SetPid(PatPmtParser.
Vpid() ? PatPmtParser.
Vpid() : PatPmtParser.
Apid(0), PatPmtParser.
Vpid() ? PatPmtParser.
Vtype() : PatPmtParser.
Atype(0));
2433 Buffer.
Del(p - Data);
2437 else if (ReplayFile) {
2438 int Result = Buffer.
Read(ReplayFile, BufferChunks);
2440 if (Buffer.
Available() > 0 && !Stuffed) {
2449 Buffer.
Put(StuffingPacket,
sizeof(StuffingPacket));
2463 IndexFileComplete =
true;
2467 if (IndexFileComplete) {
2468 if (IndexFileWritten) {
2470 if (RecordingInfo.
Read()) {
2473 RecordingInfo.
Write();
2490 #define INDEXFILESUFFIX "/index" 2493 #define MAXINDEXCATCHUP 8 // number of retries 2494 #define INDEXCATCHUPWAIT 100 // milliseconds 2508 tIndexTs(off_t Offset,
bool Independent, uint16_t Number)
2512 independent = Independent;
2517 #define MAXWAITFORINDEXFILE 10 // max. time to wait for the regenerated index file (seconds) 2518 #define INDEXFILECHECKINTERVAL 500 // ms between checks for existence of the regenerated index file 2519 #define INDEXFILETESTINTERVAL 10 // ms between tests for the size of the index file in case of pausing live video 2522 :resumeFile(FileName, IsPesRecording)
2532 if (!Record && PauseLive) {
2539 if (!Record && access(
fileName, R_OK) != 0) {
2548 }
while (access(
fileName, R_OK) != 0 && time(NULL) < tmax);
2554 delta = int(buf.st_size %
sizeof(
tIndexTs));
2557 esyslog(
"ERROR: invalid file size (%" PRId64
") in '%s'", buf.st_size, *
fileName);
2559 last = int((buf.st_size + delta) /
sizeof(
tIndexTs) - 1);
2560 if ((!Record || Update) &&
last >= 0) {
2592 if ((
f = open(
fileName, O_WRONLY | O_CREAT | O_APPEND, DEFFILEMODE)) >= 0) {
2594 esyslog(
"ERROR: padding index file with %d '0' bytes", delta);
2621 while (Count-- > 0) {
2622 memcpy(&IndexPes, IndexTs,
sizeof(IndexPes));
2633 while (Count-- > 0) {
2638 memcpy(IndexTs, &IndexPes,
sizeof(*IndexTs));
2650 for (
int i = 0; i <= MAXINDEXCATCHUP && (Index < 0 || Index >=
last); i++) {
2652 if (fstat(
f, &buf) == 0) {
2653 int newLast = int(buf.st_size /
sizeof(
tIndexTs) - 1);
2654 if (newLast >
last) {
2656 if (NewSize <= newLast) {
2658 if (NewSize <= newLast)
2659 NewSize = newLast + 1;
2666 if (lseek(
f, offset, SEEK_SET) == offset) {
2668 esyslog(
"ERROR: can't read from index");
2683 esyslog(
"ERROR: can't realloc() index");
2696 return index != NULL;
2702 tIndexTs i(FileOffset, Independent, FileNumber);
2716 bool cIndexFile::Get(
int Index, uint16_t *FileNumber, off_t *FileOffset,
bool *Independent,
int *Length)
2719 if (Index >= 0 && Index <=
last) {
2728 if (fn == *FileNumber)
2729 *Length = int(fo - *FileOffset);
2745 int d = Forward ? 1 : -1;
2748 if (Index >= 0 && Index <=
last) {
2749 if (
index[Index].independent) {
2762 if (fn == *FileNumber)
2763 *Length = int(fo - *FileOffset);
2784 if (
index[Index].independent)
2790 if (
index[il].independent)
2797 if (
index[ih].independent)
2813 for (i = 0; i <=
last; i++) {
2814 if (
index[i].number > FileNumber || (
index[i].number == FileNumber) && off_t(
index[i].offset) >= FileOffset)
2843 if (*s && stat(s, &buf) == 0)
2852 if (Recording.
Name()) {
2856 unlink(IndexFileName);
2858 while (IndexFileGenerator->
Active())
2860 if (access(IndexFileName, R_OK) == 0)
2863 fprintf(stderr,
"cannot create '%s'\n", *IndexFileName);
2866 fprintf(stderr,
"'%s' is not a TS recording\n", FileName);
2869 fprintf(stderr,
"'%s' is not a recording\n", FileName);
2872 fprintf(stderr,
"'%s' is not a directory\n", FileName);
2878 #define MAXFILESPERRECORDINGPES 255 2879 #define RECORDFILESUFFIXPES "/%03d.vdr" 2880 #define MAXFILESPERRECORDINGTS 65535 2881 #define RECORDFILESUFFIXTS "/%05d.ts" 2882 #define RECORDFILESUFFIXLEN 20 // some additional bytes for safety... 2889 blocking = Blocking;
2920 for (; Number > 0; Number--) {
2924 int fd = open(
fileName, O_RDONLY | O_LARGEFILE, DEFFILEMODE);
2926 off_t pos = lseek(fd, -
TS_SIZE, SEEK_END);
2930 while (read(fd, buf,
sizeof(buf)) ==
sizeof(buf)) {
2932 int Pid =
TsPid(buf);
2934 PatPmtParser.
ParsePat(buf,
sizeof(buf));
2935 else if (PatPmtParser.
IsPmtPid(Pid)) {
2936 PatPmtParser.
ParsePmt(buf,
sizeof(buf));
2937 if (PatPmtParser.
GetVersions(PatVersion, PmtVersion)) {
2948 pos = lseek(fd, pos -
TS_SIZE, SEEK_SET);
2962 int BlockingFlag = blocking ? 0 : O_NONBLOCK;
2976 else if (errno != ENOENT)
2986 if (file->Close() < 0)
2995 if (fileNumber != Number)
2998 if (0 < Number && Number <= MaxFilesPerRecording) {
2999 fileNumber = uint16_t(Number);
3006 if (buf.st_size != 0)
3007 return SetOffset(Number + 1);
3010 dsyslog(
"cFileName::SetOffset: removing zero-sized file %s",
fileName);
3015 return SetOffset(Number + 1);
3017 else if (errno != ENOENT) {
3024 if (!record && Offset >= 0 && file && file->Seek(Offset, SEEK_SET) != Offset) {
3031 esyslog(
"ERROR: max number of files (%d) exceeded", MaxFilesPerRecording);
3037 return SetOffset(fileNumber + 1);
3044 const char *Sign =
"";
3050 int f = int(modf((Index + 0.5) / FramesPerSecond, &Seconds) * FramesPerSecond);
3051 int s = int(Seconds);
3052 int m = s / 60 % 60;
3055 return cString::sprintf(WithFrame ?
"%s%d:%02d:%02d.%02d" :
"%s%d:%02d:%02d", Sign, h, m, s, f);
3061 int n = sscanf(HMSF,
"%d:%d:%d.%d", &h, &m, &s, &f);
3065 return int(round((h * 3600 + m * 60 + s) * FramesPerSecond)) +
f;
3071 return int(round(Seconds * FramesPerSecond));
3080 else if (Length > Max) {
3081 esyslog(
"ERROR: frame larger than buffer (%d > %d)", Length, Max);
3084 int r = f->
Read(b, Length);
3104 if (fgets(buf,
sizeof(buf),
f))
3133 dsyslog(
"writing timer id '%s' to %s", TimerId, *FileName);
3134 if (FILE *
f = fopen(FileName,
"w")) {
3135 fprintf(
f,
"%s\n", TimerId);
3142 dsyslog(
"removing %s", *FileName);
3150 const char *Id = NULL;
3151 if (FILE *
f = fopen(FileName,
"r")) {
3152 char buf[HOST_NAME_MAX + 10];
3153 if (fgets(buf,
sizeof(buf),
f)) {
bool Lock(cStateKey &StateKey, bool Write=false, int TimeoutMs=0) const
Tries to get a lock on this list and returns true if successful.
struct dirent * Next(void)
static bool RenameVideoFile(const char *OldName, const char *NewName)
void ParsePat(const uchar *Data, int Length)
Parses the PAT data from the single TS packet in Data.
void SetFramesPerSecond(double FramesPerSecond)
virtual void Clear(void)
Immediately clears the ring buffer.
static cString MarksFileName(const cRecording *Recording)
Returns the marks file name for the given Recording (regardless whether such a file actually exists)...
tComponent * GetComponent(int Index, uchar Stream, uchar Type)
int NumFrames(void) const
Returns the number of frames in this recording.
static tChannelID FromString(const char *s)
void Cleanup(cRecordings *Recordings)
static char * StripEpisodeName(char *s, bool Strip)
void SetPid(int Pid, int Type)
Sets the Pid and stream Type to detect frames for.
void SetComponent(int Index, const char *s)
#define DEFAULTFRAMESPERSECOND
void ParsePmt(const uchar *Data, int Length)
Parses the PMT data from the single TS packet in Data.
const cMark * GetNext(int Position) const
const char * InvalidChars
bool IsPmtPid(int Pid) const
Returns true if Pid the one of the PMT pids as defined by the current PAT.
void SetRecordingsSortMode(const char *Directory, eRecordingsSortMode SortMode)
void ResetResume(const char *ResumeFileName=NULL)
void SetRecordingTimerId(const char *Directory, const char *TimerId)
void Add(cListObject *Object, cListObject *After=NULL)
bool CatchUp(int Index=-1)
cResumeFile(const char *FileName, bool IsPesRecording)
int GetNumSequences(void) const
Returns the actual number of sequences to be cut from the recording.
bool IsEdited(void) const
char * LimitNameLengths(char *s, int PathMax, int NameMax)
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
static void InvokeCommand(const char *State, const char *RecordingFileName, const char *SourceFileName=NULL)
eRecordingsSortMode RecordingsSortMode
ssize_t Read(void *Data, size_t Size)
char language[MAXLANGCODE2]
cUnbufferedFile * SetOffset(int Number, off_t Offset=0)
const char * Title(char Delimiter= ' ', bool NewIndicator=false, int Level=-1) const
static cRecordings deletedRecordings
#define TIMERMACRO_EPISODE
static cString sprintf(const char *fmt,...) __attribute__((format(printf
int Analyze(const uchar *Data, int Length)
Analyzes the TS packets pointed to by Data.
int GetNextIFrame(int Index, bool Forward, uint16_t *FileNumber=NULL, off_t *FileOffset=NULL, int *Length=NULL)
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.
int GetNumRecordingsInPath(const char *Path) const
Returns the total number of recordings in the given Path, including all sub-folders of Path...
void Remove(bool IncState=true)
Removes this key from the lock it was previously used with.
bool IsOnVideoDirectoryFileSystem(void) const
bool ChangePriorityLifetime(int NewPriority, int NewLifetime)
Changes the priority and lifetime of this recording to the given values.
const cRecording * GetByName(const char *FileName) const
static cRecordings recordings
cUnbufferedFile * NextFile(void)
#define RECORDFILESUFFIXTS
int AlwaysSortFoldersFirst
double MarkFramesPerSecond
int Vpid(void) const
Returns the video pid as defined by the current PMT, or 0 if no video pid has been detected...
virtual ~cRecordingsHandler()
bool HasMarks(void) const
Returns true if this recording has any editing marks.
int ReadFrame(cUnbufferedFile *f, uchar *b, int Length, int Max)
const cMark * GetNextEnd(const cMark *BeginMark) const
Returns the next "end" mark after BeginMark, skipping any marks at the same position as BeginMark...
double FramesPerSecond(void) const
#define MAXWAITFORINDEXFILE
void ResetResume(void) const
static bool VideoFileSpaceAvailable(int SizeMB)
time_t StartTime(void) const
int Put(const uchar *Data, int Count)
Puts at most Count bytes of Data into the ring buffer.
cRecording(const cRecording &)
const char * Dlang(int i) const
#define INDEXFILETESTINTERVAL
int Last(void)
Returns the index of the last entry in this file, or -1 if the file is empty.
void SetAux(const char *Aux)
~cVideoDirectoryScannerThread()
void ScanVideoDir(const char *DirName, int LinkLevel=0, int DirLevel=0)
#define RECORDFILESUFFIXPES
cUnbufferedFile is used for large files that are mainly written or read in a streaming manner...
static cString IndexFileName(const char *FileName, bool IsPesRecording)
bool GetLastPatPmtVersions(int &PatVersion, int &PmtVersion)
const char * Alang(int i) const
bool Synced(void)
Returns true if the frame detector has synced on the data stream.
static const char * command
const cRecording * GetById(int Id) const
const cMark * GetNextBegin(const cMark *EndMark=NULL) const
Returns the next "begin" mark after EndMark, skipping any marks at the same position as EndMark...
cString IndexToHMSF(int Index, bool WithFrame, double FramesPerSecond)
const cChannel * Channel(void) const
int TsPid(const uchar *p)
static cUnbufferedFile * OpenVideoFile(const char *FileName, int Flags)
static cString PrefixVideoFileName(const char *FileName, char Prefix)
bool ChangeName(const char *NewName)
Changes the name of this recording to the given value.
static const char * Name(void)
static int lastRecordingId
char * SortName(void) const
#define MAXFILESPERRECORDINGPES
cMark(int Position=0, const char *Comment=NULL, double FramesPerSecond=DEFAULTFRAMESPERSECOND)
int PathIsInUse(const char *Path) const
Checks whether any recording in the given Path is currently in use and therefore the whole Path shall...
tCharExchange CharExchange[]
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...
const char * Name(void) const
#define LIMIT_SECS_PER_MB_RADIO
void GetRecordingsSortMode(const char *Directory)
void SetFileName(const char *FileName)
int Read(int FileHandle, int Max=0)
Reads at most Max bytes from FileHandle and stores them in the ring buffer.
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
static void RemoveEmptyVideoDirectories(const char *IgnoreFiles[]=NULL)
cListObject * Next(void) const
bool Active(cRecordings *Recordings)
bool Write(FILE *f, const char *Prefix="") const
void SetData(const char *Title, const char *ShortText, const char *Description)
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...
int LengthInSeconds(void) const
Returns the length (in seconds) of this recording, or -1 in case of error.
#define LOCK_RECORDINGS_WRITE
static cString EditedFileName(const char *FileName)
Returns the full path name of the edited version of the recording with the given FileName.
void RemoveDeletedRecordings(void)
tIndexTs(off_t Offset, bool Independent, uint16_t Number)
static void SleepMs(int TimeoutMs)
Creates a cCondWait object and uses it to sleep for TimeoutMs milliseconds, immediately giving up the...
void UpdateByName(const char *FileName)
int HMSFToIndex(const char *HMSF, double FramesPerSecond)
void bool Start(void)
Sets the description of this thread, which will be used when logging starting or stopping of the thre...
void SetStartTime(time_t Start)
Sets the start time of this recording to the given value.
bool Finished(bool &Error)
Returns true if all operations in the list have been finished.
int Usage(const char *FileName=NULL) const
bool NeedsConversion(const char *p)
bool GenerateIndex(const char *FileName, bool Update)
Generates the index of the existing recording with the given FileName.
double MBperMinute(void) const
Returns the average data rate (in MB/min) of all recordings, or -1 if this value is unknown...
int FileSizeMB(void) const
Returns the total file size of this recording (in MB), or -1 if the file size is unknown.
bool Delete(void)
Changes the file name so that it will no longer be visible in the "Recordings" menu Returns false in ...
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
void ConvertToPes(tIndexTs *IndexTs, int Count)
cUnbufferedFile * Open(void)
static int GetLength(const char *FileName, bool IsPesRecording=false)
Calculates the recording length (number of frames) without actually reading the index file...
cVideoDirectoryScannerThread(cRecordings *Recordings, cRecordings *DeletedRecordings)
static int Utf8CharLen(const char *s)
tChannelID GetChannelID(void) const
const T * Next(const T *Object) const
< Returns the element immediately before Object in this list, or NULL if Object is the first element ...
void ConvertFromPes(tIndexTs *IndexTs, int Count)
int GetClosestIFrame(int Index)
Returns the index of the I-frame that is closest to the given Index (or Index itself, if it already points to an I-frame).
static bool HasKeys(void)
bool Running(void)
Returns false if a derived cThread object shall leave its Action() function.
static char * updateFileName
bool HasRecordingsSortMode(const char *Directory)
bool TimedWait(cMutex &Mutex, int TimeoutMs)
cRecordingsHandler RecordingsHandler
int SystemExec(const char *Command, bool Detached)
int HierarchyLevels(void) const
cIndexFileGenerator(const char *RecordingName, bool Update=false)
static void TouchUpdate(void)
Touches the '.update' file in the video directory, so that other instances of VDR that access the sam...
bool MoveRecordings(const char *OldPath, const char *NewPath)
Moves all recordings in OldPath to NewPath.
const char * FileNameDst(void) const
void Del(int Count)
Deletes at most Count bytes from the ring buffer.
bool Parse(const char *s)
#define MAXFILESPERRECORDINGTS
static const char * UpdateFileName(void)
bool Completed(void)
Returns true if the PMT has been completely parsed.
const char * Title(void) const
const char * FileNameSrc(void) const
cIndexFile(const char *FileName, bool Record, bool IsPesRecording=false, bool PauseLive=false, bool Update=false)
bool Lock(int WaitSeconds=0)
bool Remove(void)
Actually removes the file from the disk Returns false in case of error.
cString BaseName(void) const
Returns the base name of this recording (without the video directory and folder). ...
int GetUsage(const char *FileName)
Returns the usage type for the given FileName.
cIndexFileGenerator * indexFileGenerator
cRecordings(bool Deleted=false)
const cMark * Get(int Position) const
cString GetRecordingTimerId(const char *Directory)
bool IsInPath(const char *Path) const
Returns true if this recording is stored anywhere under the given Path.
#define RECORDFILESUFFIXLEN
static bool RemoveVideoFile(const char *FileName)
static bool DeleteMarksFile(const cRecording *Recording)
#define LOCK_DELETEDRECORDINGS_READ
#define MIN_TS_PACKETS_FOR_FRAME_DETECTOR
void Del(cListObject *Object, bool DeleteObject=true)
void DelAll(void)
Deletes/terminates all operations.
cRecordings * deletedRecordings
const cMark * GetPrev(int Position) const
static bool MoveVideoFile(const char *FromName, const char *ToName)
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...
bool Active(void)
Checks whether the thread is still alive.
cFileName(const char *FileName, bool Record, bool Blocking=false, bool IsPesRecording=false)
cRemoveDeletedRecordingsThread(void)
static bool NeedsUpdate(void)
#define RESUME_NOT_INITIALIZED
bool NewFrame(void)
Returns true if the data given to the last call to Analyze() started a new frame. ...
bool IndependentFrame(void)
Returns true if a new frame was detected and this is an independent frame (i.e.
const char * File(void) const
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...
bool IsSingleEvent(void) const
uchar * Get(int &Count)
Gets data from the ring buffer.
static cVideoDirectoryScannerThread * videoDirectoryScannerThread
static bool IsOnVideoDirectoryFileSystem(const char *FileName)
void Add(int Position)
If this cMarks object is used by multiple threads, the caller must Lock() it before calling Add() and...
cRecordingsHandlerEntry * Get(const char *FileName)
void IncRecordingsSortMode(const char *Directory)
int NumComponents(void) const
const char * Name(void) const
Returns the full name of the recording (without the video directory).
void AssertFreeDiskSpace(int Priority, bool Force)
The special Priority value -1 means that we shall get rid of any deleted recordings faster than norma...
double FramesPerSecond(void)
Returns the number of frames per second, or 0 if this information is not available.
static cRecordControl * GetRecordControl(const char *FileName)
void DelByName(const char *FileName)
static bool Engaged(void)
Returns true if any I/O throttling object is currently active.
cList< cRecordingsHandlerEntry > operations
void ClearSortNames(void)
int SecondsToFrames(int Seconds, double FramesPerSecond)
int TotalFileSizeMB(void) const
const char * Slang(int i) const
cMutex MutexMarkFramesPerSecond
cString Folder(void) const
Returns the name of the folder this recording is stored in (without the video directory).
static const tChannelID InvalidID
bool Load(const char *RecordingFileName, double FramesPerSecond=DEFAULTFRAMESPERSECOND, bool IsPesRecording=false)
cRecordingInfo(const cChannel *Channel=NULL, const cEvent *Event=NULL)
bool DeleteMarks(void)
Deletes the editing marks from this recording (if any).
static cUnbufferedFile * Create(const char *FileName, int Flags, mode_t Mode=DEFFILEMODE)
bool Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent=NULL, int *Length=NULL)
bool IsStillRecording(void)
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
#define LOCK_DELETEDRECORDINGS_WRITE
const T * First(void) const
Returns the first element in this list, or NULL if the list is empty.
#define INDEXFILECHECKINTERVAL
char * ExchangeChars(char *s, bool ToFileSystem)
const char * ShortText(void) const
const char * FileName(void) const
Returns the full path name to the recording directory, including the video directory and the actual '...
virtual int Compare(const cListObject &ListObject) const
Must return 0 if this object is equal to ListObject, a positive value if it is "greater", and a negative value if it is "smaller".
void Cancel(int WaitSeconds=0)
Cancels the thread by first setting 'running' to false, so that the Action() loop can finish in an or...
const char * PrefixFileName(char Prefix)
cDirCopier(const char *DirNameSrc, const char *DirNameDst)
const char * Aux(void) const
bool WriteInfo(const char *OtherFileName=NULL)
Writes in info file of this recording.
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
void Add(cRecording *Recording)
static cRemoveDeletedRecordingsThread RemoveDeletedRecordingsThread
void SetModified(void)
Unconditionally marks this list as modified.
void SetFile(const char *File)
void Del(const char *FileName)
Deletes the given FileName from the list of operations.
cRecordingsHandlerEntry(int Usage, const char *FileNameSrc, const char *FileNameDst)
void AddByName(const char *FileName, bool TriggerUpdate=true)
bool IsPesRecording(void) const
int IsInUse(void) const
Checks whether this recording is currently in use and therefore shall not be tampered with...
~cRecordingsHandlerEntry()
bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset)
#define RUC_DELETERECORDING
#define SUMMARYFILESUFFIX
bool Add(int Usage, const char *FileNameSrc, const char *FileNameDst=NULL)
Adds the given FileNameSrc to the recordings handler for (later) processing.
bool Undelete(void)
Changes the file name so that it will be visible in the "Recordings" menu again and not processed by ...
static const char * NowReplaying(void)
virtual int Available(void)