15 #define __STDC_FORMAT_MACROS // Required for format specifiers 33 #define SUMMARYFALLBACK 46 #define DATAFORMATPES "%4d-%02d-%02d.%02d%*c%02d.%02d.%02d" RECEXT 47 #define NAMEFORMATPES "%s/%s/" "%4d-%02d-%02d.%02d.%02d.%02d.%02d" RECEXT 48 #define DATAFORMATTS "%4d-%02d-%02d.%02d.%02d.%d-%d" RECEXT 49 #define NAMEFORMATTS "%s/%s/" DATAFORMATTS 51 #define RESUMEFILESUFFIX "/resume%s%s" 52 #ifdef SUMMARYFALLBACK 53 #define SUMMARYFILESUFFIX "/summary.vdr" 55 #define INFOFILESUFFIX "/info" 56 #define MARKSFILESUFFIX "/marks" 58 #define SORTMODEFILE ".sort" 60 #define MINDISKSPACE 1024 // MB 62 #define REMOVECHECKDELTA 60 // seconds between checks for removing deleted files 63 #define DELETEDLIFETIME 300 // seconds after which a deleted recording will be actually removed 64 #define DISKCHECKDELTA 100 // seconds between checks for free disk space 65 #define REMOVELATENCY 10 // seconds to wait until next check after removing a file 66 #define MARKSUPDATEDELTA 10 // seconds between checks for updating editing marks 67 #define MININDEXAGE 3600 // seconds before an index file is considered no longer to be written 68 #define MAXREMOVETIME 10 // seconds after which to return from removing deleted recordings 70 #define MAX_LINK_LEVEL 6 72 #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;
134 static time_t LastRemoveCheck = 0;
136 if (!RemoveDeletedRecordingsThread.
Active()) {
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...");
193 isyslog(
"...no deleted recording found, trying to delete an old recording...");
220 isyslog(
"...no old recording found, giving up");
223 isyslog(
"...no deleted recording found, priority %d too low to trigger deleting an old recording", Priority);
226 LastFreeDiskCheck = time(NULL);
235 VanishedRecordings.
Clear();
242 isPesRecording = IsPesRecording;
244 fileName =
MALLOC(
char, strlen(FileName) + strlen(Suffix) + 1);
246 strcpy(fileName, FileName);
250 esyslog(
"ERROR: can't allocate memory for resume file name");
263 if (stat(fileName, &st) == 0) {
264 if ((st.st_mode & S_IWUSR) == 0)
267 if (isPesRecording) {
268 int f = open(fileName, O_RDONLY);
270 if (
safe_read(f, &resume,
sizeof(resume)) !=
sizeof(resume)) {
276 else if (errno != ENOENT)
280 FILE *f = fopen(fileName,
"r");
285 while ((s = ReadLine.
Read(f)) != NULL) {
289 case 'I': resume = atoi(t);
296 else if (errno != ENOENT)
306 if (isPesRecording) {
307 int f = open(fileName, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE);
317 FILE *f = fopen(fileName,
"w");
319 fprintf(f,
"I %d\n", Index);
334 if (
remove(fileName) == 0)
336 else if (errno != ENOENT)
346 channelName = Channel ? strdup(Channel->
Name()) : NULL;
347 ownEvent = Event ? NULL :
new cEvent(0);
348 event = ownEvent ? ownEvent : Event;
361 for (
int i = 0; i <
MAXAPIDS; i++) {
362 const char *s = Channel->
Alang(i);
367 else if (strlen(s) > strlen(Component->
language))
374 for (
int i = 0; i <
MAXDPIDS; i++) {
375 const char *s = Channel->
Dlang(i);
382 else if (strlen(s) > strlen(Component->
language))
387 for (
int i = 0; i <
MAXSPIDS; i++) {
388 const char *s = Channel->
Slang(i);
393 else if (strlen(s) > strlen(Component->
language))
397 if (Components != event->Components())
398 ((
cEvent *)event)->SetComponents(Components);
426 ((
cEvent *)event)->SetTitle(Title);
428 ((
cEvent *)event)->SetShortText(ShortText);
430 ((
cEvent *)event)->SetDescription(Description);
436 aux = Aux ? strdup(Aux) : NULL;
441 framesPerSecond = FramesPerSecond;
446 bool IsPesRecording = fileName &&
endswith(fileName,
".vdr");
457 while ((s = ReadLine.
Read(f)) != NULL) {
462 char *p = strchr(t,
' ');
473 unsigned int EventID;
476 unsigned int TableID = 0;
477 unsigned int Version = 0xFF;
478 int n = sscanf(t,
"%u %ld %d %X %X", &EventID, &StartTime, &Duration, &TableID, &Version);
479 if (n >= 3 && n <= 5) {
480 ownEvent->SetEventID(EventID);
481 ownEvent->SetStartTime(StartTime);
482 ownEvent->SetDuration(Duration);
483 ownEvent->SetTableID(
uchar(TableID));
484 ownEvent->SetVersion(
uchar(Version));
488 case 'F': framesPerSecond =
atod(t);
490 case 'L': lifetime = atoi(t);
492 case 'P': priority = atoi(t);
498 default:
if (!ownEvent->Parse(s)) {
499 esyslog(
"ERROR: EPG data problem in line %d", line);
512 if (channelID.Valid())
513 fprintf(f,
"%sC %s%s%s\n", Prefix, *channelID.ToString(), channelName ?
" " :
"", channelName ? channelName :
"");
514 event->Dump(f, Prefix,
true);
515 fprintf(f,
"%sF %s\n", Prefix, *
dtoa(framesPerSecond,
"%.10g"));
516 fprintf(f,
"%sP %d\n", Prefix, priority);
517 fprintf(f,
"%sL %d\n", Prefix, lifetime);
519 fprintf(f,
"%s@ %s\n", Prefix, aux);
527 FILE *f = fopen(fileName,
"r");
532 esyslog(
"ERROR: EPG data problem in file %s", fileName);
535 else if (errno != ENOENT)
559 #define RESUME_NOT_INITIALIZED (-2) 592 case ' ': *p =
'_';
break;
599 if (
char *NewBuffer = (
char *)realloc(s, strlen(s) + 10)) {
603 sprintf(buf,
"#%02X", (
unsigned char)*p);
604 memmove(p + 2, p, strlen(p) + 1);
609 esyslog(
"ERROR: out of memory");
616 case '_': *p =
' ';
break;
621 if (strlen(p) > 2 && isxdigit(*(p + 1)) && isxdigit(*(p + 2))) {
623 sprintf(buf,
"%c%c", *(p + 1), *(p + 2));
627 memmove(p + 1, p + 3, strlen(p) - 2);
633 case '\x01': *p =
'\'';
break;
634 case '\x02': *p =
'/';
break;
635 case '\x03': *p =
':';
break;
641 for (
struct tCharExchange *ce = CharExchange; ce->
a && ce->b; ce++) {
642 if (*p == (ToFileSystem ? ce->a : ce->b)) {
643 *p = ToFileSystem ? ce->b : ce->a;
665 int Length = strlen(s);
668 bool NameTooLong =
false;
672 for (
char *p = s; *p; p++) {
675 NameTooLong |= NameLength > NameMax;
696 NameTooLong |= NameLength > NameMax;
704 while (i-- > 0 && a[i] >= 0) {
709 if (NameLength > NameMax) {
712 while (i-- > 0 && a[i] >= 0) {
714 if (NameLength - l <= NameMax) {
715 memmove(s + i, s + n, Length - n + 1);
716 memmove(a + i, a + n, Length - n + 1);
729 while (PathLength > PathMax && n > 0) {
734 while (--i > 0 && a[i - 1] >= 0) {
738 if (PathLength - l <= PathMax)
744 memmove(s + b, s + n, Length - n + 1);
758 sortBufferName = sortBufferTime = NULL;
764 isPesRecording =
false;
765 isOnVideoDirectoryFileSystem = -1;
770 const char *Title = Event ? Event->
Title() : NULL;
771 const char *Subtitle = Event ? Event->
ShortText() : NULL;
778 if (macroTITLE || macroEPISODE) {
779 name = strdup(Timer->
File());
783 int l = strlen(name);
796 name = strdup(Timer->
File());
807 info->priority = priority;
808 info->lifetime = lifetime;
819 isPesRecording =
false;
820 isOnVideoDirectoryFileSystem = -1;
825 sortBufferName = sortBufferTime = NULL;
826 FileName = fileName = strdup(FileName);
827 if (*(fileName + strlen(fileName) - 1) ==
'/')
828 *(fileName + strlen(fileName) - 1) = 0;
831 const char *p = strrchr(FileName,
'/');
836 time_t now = time(NULL);
838 struct tm t = *localtime_r(&now, &tm_r);
840 if (7 == sscanf(p + 1,
DATAFORMATTS, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &channel, &instanceId)
841 || 7 == sscanf(p + 1,
DATAFORMATPES, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &priority, &lifetime)) {
846 name =
MALLOC(
char, p - FileName + 1);
847 strncpy(name, FileName, p - FileName);
848 name[p - FileName] = 0;
850 isPesRecording = instanceId < 0;
857 FILE *f = fopen(InfoFileName,
"r");
860 esyslog(
"ERROR: EPG data problem in file %s", *InfoFileName);
861 else if (!isPesRecording) {
862 priority = info->priority;
863 lifetime = info->lifetime;
864 framesPerSecond = info->framesPerSecond;
868 else if (errno == ENOENT)
869 info->ownEvent->SetTitle(name);
872 #ifdef SUMMARYFALLBACK 876 FILE *f = fopen(SummaryFileName,
"r");
879 char *data[3] = { NULL };
882 while ((s = ReadLine.
Read(f)) != NULL) {
883 if (*s || line > 1) {
886 len += strlen(data[line]) + 1;
887 if (
char *NewBuffer = (
char *)realloc(data[line], len + 1)) {
888 data[line] = NewBuffer;
889 strcat(data[line],
"\n");
890 strcat(data[line], s);
893 esyslog(
"ERROR: out of memory");
896 data[line] = strdup(s);
906 else if (data[1] && data[2]) {
910 int len = strlen(data[1]);
912 if (
char *NewBuffer = (
char *)realloc(data[1], len + 1 + strlen(data[2]) + 1)) {
914 strcat(data[1],
"\n");
915 strcat(data[1], data[2]);
921 esyslog(
"ERROR: out of memory");
924 info->SetData(data[0], data[1], data[2]);
925 for (
int i = 0; i < 3; i ++)
928 else if (errno != ENOENT)
938 free(sortBufferName);
939 free(sortBufferTime);
947 char *t = s, *s1 = NULL, *s2 = NULL;
968 memmove(s1, s2, t - s2 + 1);
981 strftime(buf,
sizeof(buf),
"%Y%m%d%H%I", localtime_r(&start, &tm_r));
989 int l = strxfrm(NULL, s, 0) + 1;
1000 free(sortBufferName);
1001 free(sortBufferTime);
1002 sortBufferName = sortBufferTime = NULL;
1008 cResumeFile ResumeFile(FileName(), isPesRecording);
1009 resume = ResumeFile.
Read();
1017 return strcasecmp(SortName(), r->
SortName());
1024 int l = strlen(Path);
1046 struct tm *t = localtime_r(&start, &tm_r);
1048 int ch = isPesRecording ? priority : channel;
1049 int ri = isPesRecording ? lifetime : instanceId;
1051 if (strcmp(Name, name) != 0)
1052 dsyslog(
"recording file name '%s' truncated to '%s'", name, Name);
1062 char New = NewIndicator && IsNew() ?
'*' :
' ';
1065 if (Level < 0 || Level == HierarchyLevels()) {
1067 struct tm *t = localtime_r(&start, &tm_r);
1075 int Minutes =
max(0, (LengthInSeconds() + 30) / 60);
1096 s = &titleBuffer[strlen(titleBuffer) - 1];
1100 else if (Level < HierarchyLevels()) {
1101 const char *s = name;
1111 titleBuffer =
MALLOC(
char, s - p + 3);
1112 *titleBuffer = Delimiter;
1113 *(titleBuffer + 1) = Delimiter;
1114 strn0cpy(titleBuffer + 2, p, s - p + 1);
1126 fileName = strdup(p);
1134 const char *s = name;
1146 s = !s ? name : s + 1;
1152 if (isOnVideoDirectoryFileSystem < 0)
1154 return isOnVideoDirectoryFileSystem;
1165 if (errno != ENOENT) {
1176 priority = info->priority;
1177 lifetime = info->lifetime;
1178 framesPerSecond = info->framesPerSecond;
1203 if (NewPriority != Priority() || NewLifetime != Lifetime()) {
1204 dsyslog(
"changing priority/lifetime of '%s' to %d/%d", Name(), NewPriority, NewLifetime);
1205 if (IsPesRecording()) {
1206 cString OldFileName = FileName();
1207 priority = NewPriority;
1208 lifetime = NewLifetime;
1211 cString NewFileName = FileName();
1214 info->SetFileName(NewFileName);
1217 priority = info->priority = NewPriority;
1218 lifetime = info->lifetime = NewLifetime;
1230 if (strcmp(NewName, Name())) {
1231 dsyslog(
"changing name of '%s' to '%s'", Name(), NewName);
1233 cString OldFileName = FileName();
1237 name = strdup(NewName);
1238 cString NewFileName = FileName();
1241 name = strdup(OldName);
1243 fileName = strdup(OldFileName);
1246 isOnVideoDirectoryFileSystem = -1;
1257 char *NewName = strdup(FileName());
1258 char *ext = strrchr(NewName,
'.');
1259 if (ext && strcmp(ext,
RECEXT) == 0) {
1260 strncpy(ext,
DELEXT, strlen(ext));
1261 if (access(NewName, F_OK) == 0) {
1263 isyslog(
"removing recording '%s'", NewName);
1266 isyslog(
"deleting recording '%s'", FileName());
1267 if (access(FileName(), F_OK) == 0) {
1272 isyslog(
"recording '%s' vanished", FileName());
1284 esyslog(
"attempt to remove recording %s", FileName());
1287 isyslog(
"removing recording %s", FileName());
1294 char *NewName = strdup(FileName());
1295 char *ext = strrchr(NewName,
'.');
1296 if (ext && strcmp(ext,
DELEXT) == 0) {
1297 strncpy(ext,
RECEXT, strlen(ext));
1298 if (access(NewName, F_OK) == 0) {
1300 esyslog(
"ERROR: attempt to undelete '%s', while recording '%s' exists", FileName(), NewName);
1304 isyslog(
"undeleting recording '%s'", FileName());
1305 if (access(FileName(), F_OK) == 0)
1308 isyslog(
"deleted recording '%s' vanished", FileName());
1335 if (numFrames < 0) {
1346 int nf = NumFrames();
1348 return int(nf / FramesPerSecond());
1354 if (fileSizeMB < 0) {
1370 :
cThread(
"video directory scanner")
1410 bool DoChangeState =
false;
1414 while ((Foreground ||
Running()) && (e = d.
Next()) != NULL) {
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)) {
1443 DoChangeState =
true;
1451 DoChangeState |=
ScanVideoDir(buffer, Foreground, LinkLevel + Link, DirLevel + 1);
1459 recording =
Next(recording);
1460 if (access(r->
FileName(), F_OK) != 0) {
1463 VanishedRecordings.
Add(r);
1464 DoChangeState =
true;
1469 if (DoChangeState && DirLevel == 0)
1471 return DoChangeState;
1476 int NewState =
state;
1477 bool Result = State != NewState;
1493 if (lastModified > time(NULL))
1514 if (strcmp(recording->FileName(), FileName) == 0)
1540 recording = dummy =
new cRecording(FileName);
1543 Del(recording,
false);
1544 char *ext = strrchr(recording->
fileName,
'.');
1546 strncpy(ext,
DELEXT, strlen(ext));
1547 if (access(recording->
FileName(), F_OK) == 0) {
1548 recording->
deleted = time(NULL);
1571 int FileSizeMB = recording->FileSizeMB();
1572 if (FileSizeMB > 0 && recording->IsOnVideoDirectoryFileSystem())
1584 if (recording->IsOnVideoDirectoryFileSystem()) {
1585 int FileSizeMB = recording->FileSizeMB();
1586 if (FileSizeMB > 0) {
1587 int LengthInSeconds = recording->LengthInSeconds();
1588 if (LengthInSeconds > 0) {
1591 length += LengthInSeconds;
1597 return (size && length) ? double(size) * 60 / length : -1;
1605 if (recording->IsInPath(Path))
1606 Use |= recording->IsInUse();
1616 if (recording->IsInPath(Path))
1624 if (OldPath && NewPath && strcmp(OldPath, NewPath)) {
1626 dsyslog(
"moving '%s' to '%s'", OldPath, NewPath);
1628 if (recording->IsInPath(OldPath)) {
1629 const char *p = recording->Name() + strlen(OldPath);
1631 if (!recording->ChangeName(NewName))
1644 if (!ResumeFileName || strncmp(ResumeFileName, recording->FileName(), strlen(recording->FileName())) == 0)
1645 recording->ResetResume();
1654 recording->ClearSortName();
1665 bool Throttled(
void);
1666 virtual void Action(
void);
1668 cDirCopier(
const char *DirNameSrc,
const char *DirNameDst);
1692 dsyslog(
"suspending copy thread");
1698 dsyslog(
"resuming copy thread");
1715 size_t BufferSize = BUFSIZ;
1725 uchar Buffer[BufferSize];
1726 size_t Read =
safe_read(From, Buffer,
sizeof(Buffer));
1728 size_t Written =
safe_write(To, Buffer, Read);
1729 if (Written != Read) {
1730 esyslog(
"ERROR: can't write to destination file '%s': %m", *FileNameDst);
1734 else if (Read == 0) {
1736 if (fsync(To) < 0) {
1737 esyslog(
"ERROR: can't sync destination file '%s': %m", *FileNameDst);
1740 if (close(From) < 0) {
1741 esyslog(
"ERROR: can't close source file '%s': %m", *FileNameSrc);
1744 if (close(To) < 0) {
1745 esyslog(
"ERROR: can't close destination file '%s': %m", *FileNameDst);
1749 off_t FileSizeSrc =
FileSize(FileNameSrc);
1750 off_t FileSizeDst =
FileSize(FileNameDst);
1751 if (FileSizeSrc != FileSizeDst) {
1752 esyslog(
"ERROR: file size discrepancy: %" PRId64
" != %" PRId64, FileSizeSrc, FileSizeDst);
1757 esyslog(
"ERROR: can't read from source file '%s': %m", *FileNameSrc);
1761 else if ((e = d.
Next()) != NULL) {
1766 if (stat(FileNameSrc, &st) < 0) {
1767 esyslog(
"ERROR: can't access source file '%s': %m", *FileNameSrc);
1770 if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) {
1771 esyslog(
"ERROR: source file '%s' is neither a regular file nor a symbolic link", *FileNameSrc);
1774 dsyslog(
"copying file '%s' to '%s'", *FileNameSrc, *FileNameDst);
1775 BufferSize =
max(
size_t(st.st_blksize * 10),
size_t(BUFSIZ));
1776 if (access(FileNameDst, F_OK) == 0) {
1777 esyslog(
"ERROR: destination file '%s' already exists", *FileNameDst);
1780 if ((From = open(FileNameSrc, O_RDONLY)) < 0) {
1781 esyslog(
"ERROR: can't open source file '%s': %m", *FileNameSrc);
1784 if ((To = open(FileNameDst, O_WRONLY | O_CREAT | O_EXCL, DEFFILEMODE)) < 0) {
1785 esyslog(
"ERROR: can't open destination file '%s': %m", *FileNameDst);
1831 int Usage(
const char *FileName = NULL)
const;
1840 fileNameSrc = FileNameSrc;
1841 fileNameDst = FileNameDst;
1855 if (FileName && *FileName) {
1856 if (strcmp(FileName, fileNameSrc) == 0)
1858 else if (strcmp(FileName, fileNameDst) == 0)
1866 bool CopierFinishedOk =
false;
1869 if (cutter->Active())
1871 Error |= cutter->Error();
1876 if (copier->Active())
1878 Error |= copier->Error();
1879 CopierFinishedOk = !copier->Error();
1885 if ((Usage() &
ruCut) != 0) {
1886 cutter =
new cCutter(FileNameSrc());
1890 copier =
new cDirCopier(FileNameSrc(), FileNameDst());
1898 if (CopierFinishedOk && (Usage() &
ruMove) != 0) {
1924 if (FileName && *FileName) {
1926 if (strcmp(FileName, r->FileNameSrc()) == 0 || strcmp(FileName, r->FileNameDst()) == 0)
1935 dsyslog(
"recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
1938 if (FileNameSrc && *FileNameSrc) {
1939 if (Usage ==
ruCut || FileNameDst && *FileNameDst) {
1941 if (Usage ==
ruCut && !FileNameDst)
1943 if (!Get(FileNameSrc) && !Get(FileNameDst)) {
1952 esyslog(
"ERROR: file name already present in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
1955 esyslog(
"ERROR: missing dst file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
1958 esyslog(
"ERROR: missing src file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
1961 esyslog(
"ERROR: invalid usage in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
1985 return r->Usage(FileName);
1993 if (r->Active(
error))
2004 if (!finished && operations.Count() == 0) {
2020 position = Position;
2022 framesPerSecond = FramesPerSecond;
2031 return cString::sprintf(
"%s%s%s\n", *
IndexToHMSF(position,
true, framesPerSecond), Comment() ?
" " :
"", Comment() ? Comment() :
"");
2039 const char *p = strchr(s,
' ');
2043 comment = strdup(p);
2050 return fprintf(f,
"%s", *ToText()) > 0;
2060 bool cMarks::Load(
const char *RecordingFileName,
double FramesPerSecond,
bool IsPesRecording)
2063 recordingFileName = RecordingFileName;
2065 framesPerSecond = FramesPerSecond;
2066 isPesRecording = IsPesRecording;
2076 time_t t = time(NULL);
2077 if (t > nextUpdate && *fileName) {
2079 if (LastModified != lastFileTime)
2080 lastChange = LastModified > 0 ? LastModified : t;
2081 int d = t - lastChange;
2089 if (LastModified != lastFileTime) {
2090 lastFileTime = LastModified;
2091 if (lastFileTime == t)
2093 cMutexLock MutexLock(&MutexMarkFramesPerSecond);
2118 cIndexFile IndexFile(recordingFileName,
false, isPesRecording);
2119 for (
cMark *m = First(); m; m = Next(m)) {
2121 if (
int d = m->Position() - p) {
2122 isyslog(
"aligned editing mark %s to %s (off by %d frame%s)", *
IndexToHMSF(m->Position(),
true, framesPerSecond), *
IndexToHMSF(p,
true, framesPerSecond), d, abs(d) > 1 ?
"s" :
"");
2131 for (
cMark *m1 = First(); m1; m1 = Next(m1)) {
2132 for (
cMark *m2 = Next(m1); m2; m2 = Next(m2)) {
2133 if (m2->Position() < m1->Position()) {
2134 swap(m1->position, m2->position);
2135 swap(m1->comment, m2->comment);
2150 for (
cMark *mi = First(); mi; mi = Next(mi)) {
2151 if (mi->Position() == Position)
2159 for (
cMark *mi = Last(); mi; mi = Prev(mi)) {
2160 if (mi->Position() < Position)
2168 for (
cMark *mi = First(); mi; mi = Next(mi)) {
2169 if (mi->Position() > Position)
2177 cMark *BeginMark = EndMark ? Next(EndMark) : First();
2178 if (BeginMark && EndMark && BeginMark->
Position() == EndMark->
Position()) {
2179 while (
cMark *NextMark = Next(BeginMark)) {
2180 if (BeginMark->
Position() == NextMark->Position()) {
2181 if (!(BeginMark = Next(NextMark)))
2195 cMark *EndMark = Next(BeginMark);
2196 if (EndMark && BeginMark && BeginMark->
Position() == EndMark->
Position()) {
2197 while (
cMark *NextMark = Next(EndMark)) {
2198 if (EndMark->
Position() == NextMark->Position()) {
2199 if (!(EndMark = Next(NextMark)))
2212 int NumSequences = 0;
2213 if (
cMark *BeginMark = GetNextBegin()) {
2214 while (
cMark *EndMark = GetNextEnd(BeginMark)) {
2216 BeginMark = GetNextBegin(EndMark);
2220 if (NumSequences == 1 && BeginMark->Position() == 0)
2224 return NumSequences;
2239 isyslog(
"executing '%s'", *cmd);
2246 #define IFG_BUFFER_SIZE KILOBYTE(100) 2253 virtual void Action(
void);
2260 :
cThread(
"index file generator")
2261 ,recordingName(RecordingName)
2274 bool IndexFileComplete =
false;
2275 bool IndexFileWritten =
false;
2276 bool Rewind =
false;
2285 off_t FrameOffset = -1;
2286 uint16_t FileNumber = 1;
2287 off_t FileOffset = 0;
2293 Last = IndexFile.
Last();
2294 if (Last >= 0 && !IndexFile.
Get(Last, &FileNumber, &FileOffset, &Independent, &Length))
2298 isyslog(
"updating index file");
2301 isyslog(
"generating index file");
2304 bool Stuffed =
false;
2308 ReplayFile = FileName.
SetOffset(FileNumber, FileOffset);
2309 FileSize = FileOffset;
2317 if (FrameDetector.
Synced()) {
2320 FrameOffset = FileSize;
2321 int Processed = FrameDetector.
Analyze(Data, Length);
2322 if (Processed > 0) {
2324 if (IndexFileWritten || Last < 0)
2327 IndexFileWritten =
true;
2329 FileSize += Processed;
2330 Buffer.
Del(Processed);
2333 else if (PatPmtParser.
Vpid()) {
2335 int Processed = FrameDetector.
Analyze(Data, Length);
2336 if (Processed > 0) {
2337 if (FrameDetector.
Synced()) {
2341 Buffer.
Del(Processed);
2347 while (Length >= TS_SIZE) {
2351 else if (PatPmtParser.
IsPmtPid(Pid))
2355 if (PatPmtParser.
Vpid()) {
2363 Buffer.
Del(p - Data);
2367 else if (ReplayFile) {
2368 int Result = Buffer.
Read(ReplayFile, BufferChunks);
2370 if (Buffer.
Available() > 0 && !Stuffed) {
2379 Buffer.
Put(StuffingPacket,
sizeof(StuffingPacket));
2393 IndexFileComplete =
true;
2397 if (IndexFileComplete) {
2398 if (IndexFileWritten) {
2400 if (RecordingInfo.
Read()) {
2403 RecordingInfo.
Write();
2419 #define INDEXFILESUFFIX "/index" 2422 #define MAXINDEXCATCHUP 8 // number of retries 2423 #define INDEXCATCHUPWAIT 100 // milliseconds 2437 tIndexTs(off_t Offset,
bool Independent, uint16_t Number)
2441 independent = Independent;
2446 #define MAXWAITFORINDEXFILE 10 // max. time to wait for the regenerated index file (seconds) 2447 #define INDEXFILECHECKINTERVAL 500 // ms between checks for existence of the regenerated index file 2448 #define INDEXFILETESTINTERVAL 10 // ms between tests for the size of the index file in case of pausing live video 2451 :resumeFile(FileName, IsPesRecording)
2461 if (!Record && PauseLive) {
2468 if (!Record && access(
fileName, R_OK) != 0) {
2477 }
while (access(
fileName, R_OK) != 0 && time(NULL) < tmax);
2483 delta = int(buf.st_size %
sizeof(
tIndexTs));
2486 esyslog(
"ERROR: invalid file size (%" PRId64
") in '%s'", buf.st_size, *
fileName);
2488 last = int((buf.st_size + delta) /
sizeof(
tIndexTs) - 1);
2489 if ((!Record || Update) &&
last >= 0) {
2521 if ((
f = open(
fileName, O_WRONLY | O_CREAT | O_APPEND, DEFFILEMODE)) >= 0) {
2523 esyslog(
"ERROR: padding index file with %d '0' bytes", delta);
2550 while (Count-- > 0) {
2551 memcpy(&IndexPes, IndexTs,
sizeof(IndexPes));
2562 while (Count-- > 0) {
2567 memcpy(IndexTs, &IndexPes,
sizeof(*IndexTs));
2579 for (
int i = 0; i <= MAXINDEXCATCHUP && (Index < 0 || Index >=
last); i++) {
2581 if (fstat(
f, &buf) == 0) {
2582 int newLast = int(buf.st_size /
sizeof(
tIndexTs) - 1);
2583 if (newLast >
last) {
2585 if (NewSize <= newLast) {
2587 if (NewSize <= newLast)
2588 NewSize = newLast + 1;
2595 if (lseek(
f, offset, SEEK_SET) == offset) {
2597 esyslog(
"ERROR: can't read from index");
2612 esyslog(
"ERROR: can't realloc() index");
2625 return index != NULL;
2631 tIndexTs i(FileOffset, Independent, FileNumber);
2645 bool cIndexFile::Get(
int Index, uint16_t *FileNumber, off_t *FileOffset,
bool *Independent,
int *Length)
2648 if (Index >= 0 && Index <=
last) {
2657 if (fn == *FileNumber)
2658 *Length = int(fo - *FileOffset);
2674 int d = Forward ? 1 : -1;
2677 if (Index >= 0 && Index <=
last) {
2678 if (
index[Index].independent) {
2691 if (fn == *FileNumber)
2692 *Length = int(fo - *FileOffset);
2713 if (
index[Index].independent)
2719 if (
index[il].independent)
2726 if (
index[ih].independent)
2742 for (i = 0; i <=
last; i++) {
2743 if (
index[i].number > FileNumber || (
index[i].number == FileNumber) && off_t(
index[i].offset) >= FileOffset)
2772 if (*s && stat(s, &buf) == 0)
2781 if (Recording.
Name()) {
2785 unlink(IndexFileName);
2787 while (IndexFileGenerator->
Active())
2789 if (access(IndexFileName, R_OK) == 0)
2792 fprintf(stderr,
"cannot create '%s'\n", *IndexFileName);
2795 fprintf(stderr,
"'%s' is not a TS recording\n", FileName);
2798 fprintf(stderr,
"'%s' is not a recording\n", FileName);
2801 fprintf(stderr,
"'%s' is not a directory\n", FileName);
2807 #define MAXFILESPERRECORDINGPES 255 2808 #define RECORDFILESUFFIXPES "/%03d.vdr" 2809 #define MAXFILESPERRECORDINGTS 65535 2810 #define RECORDFILESUFFIXTS "/%05d.ts" 2811 #define RECORDFILESUFFIXLEN 20 // some additional bytes for safety... 2818 blocking = Blocking;
2849 for (; Number > 0; Number--) {
2853 int fd = open(
fileName, O_RDONLY | O_LARGEFILE, DEFFILEMODE);
2855 off_t pos = lseek(fd, -
TS_SIZE, SEEK_END);
2859 while (read(fd, buf,
sizeof(buf)) ==
sizeof(buf)) {
2861 int Pid =
TsPid(buf);
2863 PatPmtParser.
ParsePat(buf,
sizeof(buf));
2864 else if (PatPmtParser.
IsPmtPid(Pid)) {
2865 PatPmtParser.
ParsePmt(buf,
sizeof(buf));
2866 if (PatPmtParser.
GetVersions(PatVersion, PmtVersion)) {
2877 pos = lseek(fd, pos -
TS_SIZE, SEEK_SET);
2891 int BlockingFlag = blocking ? 0 : O_NONBLOCK;
2905 else if (errno != ENOENT)
2915 if (file->Close() < 0)
2924 if (fileNumber != Number)
2927 if (0 < Number && Number <= MaxFilesPerRecording) {
2928 fileNumber = uint16_t(Number);
2935 if (buf.st_size != 0)
2936 return SetOffset(Number + 1);
2939 dsyslog(
"cFileName::SetOffset: removing zero-sized file %s",
fileName);
2944 return SetOffset(Number + 1);
2946 else if (errno != ENOENT) {
2953 if (!record && Offset >= 0 && file && file->Seek(Offset, SEEK_SET) != Offset) {
2960 esyslog(
"ERROR: max number of files (%d) exceeded", MaxFilesPerRecording);
2966 return SetOffset(fileNumber + 1);
2973 const char *Sign =
"";
2979 int f = int(modf((Index + 0.5) / FramesPerSecond, &Seconds) * FramesPerSecond);
2980 int s = int(Seconds);
2981 int m = s / 60 % 60;
2984 return cString::sprintf(WithFrame ?
"%s%d:%02d:%02d.%02d" :
"%s%d:%02d:%02d", Sign, h, m, s, f);
2990 int n = sscanf(HMSF,
"%d:%d:%d.%d", &h, &m, &s, &f);
2994 return int(round((h * 3600 + m * 60 + s) * FramesPerSecond)) +
f;
3000 return int(round(Seconds * FramesPerSecond));
3009 else if (Length > Max) {
3010 esyslog(
"ERROR: frame larger than buffer (%d > %d)", Length, Max);
3013 int r = f->
Read(b, Length);
3033 if (fgets(buf,
sizeof(buf),
f))
struct dirent * Next(void)
static bool RenameVideoFile(const char *OldName, const char *NewName)
void ClearVanishedRecordings(void)
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.
bool Update(bool Wait=false)
Triggers an update of the list of recordings, which will run as a separate thread if Wait is false...
static cString MarksFileName(const cRecording *Recording)
Returns the marks file name for the given Recording (regardless whether such a file actually exists)...
int TotalFileSizeMB(void)
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 Refresh(bool Foreground=false)
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 char * InvalidChars
cMark * GetPrev(int Position)
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 Add(cListObject *Object, cListObject *After=NULL)
bool CatchUp(int Index=-1)
cResumeFile(const char *FileName, bool IsPesRecording)
bool IsEdited(void) const
char * LimitNameLengths(char *s, int PathMax, int NameMax)
bool IsInPath(const char *Path)
Returns true if this recording is stored anywhere under the given Path.
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]
cMark * GetNextBegin(cMark *EndMark=NULL)
Returns the next "begin" mark after EndMark, skipping any marks at the same position as EndMark...
cUnbufferedFile * SetOffset(int Number, off_t Offset=0)
const char * Title(char Delimiter= ' ', bool NewIndicator=false, int Level=-1) const
#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.
bool IsOnVideoDirectoryFileSystem(void) const
bool ChangePriorityLifetime(int NewPriority, int NewLifetime)
Changes the priority and lifetime of this recording to the given values.
cUnbufferedFile * NextFile(void)
static cRecordings VanishedRecordings
#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...
int ReadFrame(cUnbufferedFile *f, uchar *b, int Length, int Max)
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)
#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
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)
char * SortName(void) const
#define MAXFILESPERRECORDINGPES
cMark * GetNextEnd(cMark *BeginMark)
Returns the next "end" mark after BeginMark, skipping any marks at the same position as BeginMark...
cMark(int Position=0, const char *Comment=NULL, double FramesPerSecond=DEFAULTFRAMESPERSECOND)
int PathIsInUse(const char *Path)
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...
cRecording * GetByName(const char *FileName)
const char * Name(void) const
#define LIMIT_SECS_PER_MB_RADIO
cMark * GetNext(int Position)
T * Next(const T *object) const
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 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.
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 GetNumRecordingsInPath(const char *Path)
Returns the total number of recordings in the given Path, including all sub-folders of Path...
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.
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 ...
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...
static int Utf8CharLen(const char *s)
tChannelID GetChannelID(void) const
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
void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
cIndexFileGenerator(const char *RecordingName, bool Update=false)
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
const char * UpdateFileName(void)
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)
#define RECORDFILESUFFIXLEN
int GetNumSequences(void)
Returns the actual number of sequences to be cut from the recording.
static bool RemoveVideoFile(const char *FileName)
#define MIN_TS_PACKETS_FOR_FRAME_DETECTOR
void Del(cListObject *Object, bool DeleteObject=true)
void DelAll(void)
Deletes/terminates all operations.
static bool MoveVideoFile(const char *FromName, const char *ToName)
cMark * Get(int Position)
bool Active(void)
Checks whether the thread is still alive.
cFileName(const char *FileName, bool Record, bool Blocking=false, bool IsPesRecording=false)
cRemoveDeletedRecordingsThread(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
cRecordings Recordings
Any access to Recordings that loops through the list of recordings needs to hold a thread lock on thi...
uchar * Get(int &Count)
Gets data from the ring buffer.
double MBperMinute(void)
Returns the average data rate (in MB/min) of all recordings, or -1 if this value is unknown...
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.
void ClearSortNames(void)
int SecondsToFrames(int Seconds, double FramesPerSecond)
bool StateChanged(int &State)
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 Active(void)
Checks whether there is currently any operation running and starts the next one form the list if the ...
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 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...
static cRemoveDeletedRecordingsThread RemoveDeletedRecordingsThread
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
cRecordings DeletedRecordings(true)
#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)
bool HasMarks(void)
Returns true if this recording has any editing marks.
bool ScanVideoDir(const char *DirName, bool Foreground=false, int LinkLevel=0, int DirLevel=0)
virtual int Available(void)