Bug Summary

File:recording.c
Location:line 1628, column 30
Description:Null pointer passed as an argument to a 'nonnull' parameter

Annotated Source Code

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