vdr
1.7.27
|
00001 /* 00002 * recording.c: Recording file handling 00003 * 00004 * See the main source file 'vdr.c' for copyright information and 00005 * how to reach the author. 00006 * 00007 * $Id: recording.c 2.53 2012/03/13 13:17:57 kls Exp $ 00008 */ 00009 00010 #include "recording.h" 00011 #include <ctype.h> 00012 #include <dirent.h> 00013 #include <errno.h> 00014 #include <fcntl.h> 00015 #define __STDC_FORMAT_MACROS // Required for format specifiers 00016 #include <inttypes.h> 00017 #include <math.h> 00018 #include <stdio.h> 00019 #include <string.h> 00020 #include <sys/stat.h> 00021 #include <unistd.h> 00022 #include "channels.h" 00023 #include "i18n.h" 00024 #include "interface.h" 00025 #include "remux.h" 00026 #include "ringbuffer.h" 00027 #include "skins.h" 00028 #include "tools.h" 00029 #include "videodir.h" 00030 00031 #define SUMMARYFALLBACK 00032 00033 #define RECEXT ".rec" 00034 #define DELEXT ".del" 00035 /* This was the original code, which works fine in a Linux only environment. 00036 Unfortunately, because of Windows and its brain dead file system, we have 00037 to use a more complicated approach, in order to allow users who have enabled 00038 the --vfat command line option to see their recordings even if they forget to 00039 enable --vfat when restarting VDR... Gee, do I hate Windows. 00040 (kls 2002-07-27) 00041 #define DATAFORMAT "%4d-%02d-%02d.%02d:%02d.%02d.%02d" RECEXT 00042 #define NAMEFORMAT "%s/%s/" DATAFORMAT 00043 */ 00044 #define DATAFORMATPES "%4d-%02d-%02d.%02d%*c%02d.%02d.%02d" RECEXT 00045 #define NAMEFORMATPES "%s/%s/" "%4d-%02d-%02d.%02d.%02d.%02d.%02d" RECEXT 00046 #define DATAFORMATTS "%4d-%02d-%02d.%02d.%02d.%d-%d" RECEXT 00047 #define NAMEFORMATTS "%s/%s/" DATAFORMATTS 00048 00049 #define RESUMEFILESUFFIX "/resume%s%s" 00050 #ifdef SUMMARYFALLBACK 00051 #define SUMMARYFILESUFFIX "/summary.vdr" 00052 #endif 00053 #define INFOFILESUFFIX "/info" 00054 #define MARKSFILESUFFIX "/marks" 00055 00056 #define MINDISKSPACE 1024 // MB 00057 00058 #define REMOVECHECKDELTA 60 // seconds between checks for removing deleted files 00059 #define DELETEDLIFETIME 300 // seconds after which a deleted recording will be actually removed 00060 #define DISKCHECKDELTA 100 // seconds between checks for free disk space 00061 #define REMOVELATENCY 10 // seconds to wait until next check after removing a file 00062 #define MARKSUPDATEDELTA 10 // seconds between checks for updating editing marks 00063 #define MININDEXAGE 3600 // seconds before an index file is considered no longer to be written 00064 00065 #define MAX_SUBTITLE_LENGTH 40 00066 00067 #define MAX_LINK_LEVEL 6 00068 00069 bool VfatFileSystem = false; 00070 int InstanceId = 0; 00071 00072 cRecordings DeletedRecordings(true); 00073 00074 // --- cRemoveDeletedRecordingsThread ---------------------------------------- 00075 00076 class cRemoveDeletedRecordingsThread : public cThread { 00077 protected: 00078 virtual void Action(void); 00079 public: 00080 cRemoveDeletedRecordingsThread(void); 00081 }; 00082 00083 cRemoveDeletedRecordingsThread::cRemoveDeletedRecordingsThread(void) 00084 :cThread("remove deleted recordings") 00085 { 00086 } 00087 00088 void cRemoveDeletedRecordingsThread::Action(void) 00089 { 00090 SetPriority(19); 00091 SetIOPriority(7); 00092 // Make sure only one instance of VDR does this: 00093 cLockFile LockFile(VideoDirectory); 00094 if (LockFile.Lock()) { 00095 bool deleted = false; 00096 cThreadLock DeletedRecordingsLock(&DeletedRecordings); 00097 for (cRecording *r = DeletedRecordings.First(); r; ) { 00098 if (r->Deleted() && time(NULL) - r->Deleted() > DELETEDLIFETIME) { 00099 cRecording *next = DeletedRecordings.Next(r); 00100 r->Remove(); 00101 DeletedRecordings.Del(r); 00102 r = next; 00103 deleted = true; 00104 continue; 00105 } 00106 r = DeletedRecordings.Next(r); 00107 } 00108 if (deleted) 00109 RemoveEmptyVideoDirectories(); 00110 } 00111 } 00112 00113 static cRemoveDeletedRecordingsThread RemoveDeletedRecordingsThread; 00114 00115 // --- 00116 00117 void RemoveDeletedRecordings(void) 00118 { 00119 static time_t LastRemoveCheck = 0; 00120 if (time(NULL) - LastRemoveCheck > REMOVECHECKDELTA) { 00121 if (!RemoveDeletedRecordingsThread.Active()) { 00122 cThreadLock DeletedRecordingsLock(&DeletedRecordings); 00123 for (cRecording *r = DeletedRecordings.First(); r; r = DeletedRecordings.Next(r)) { 00124 if (r->Deleted() && time(NULL) - r->Deleted() > DELETEDLIFETIME) { 00125 RemoveDeletedRecordingsThread.Start(); 00126 break; 00127 } 00128 } 00129 } 00130 LastRemoveCheck = time(NULL); 00131 } 00132 } 00133 00134 void AssertFreeDiskSpace(int Priority, bool Force) 00135 { 00136 static cMutex Mutex; 00137 cMutexLock MutexLock(&Mutex); 00138 // With every call to this function we try to actually remove 00139 // a file, or mark a file for removal ("delete" it), so that 00140 // it will get removed during the next call. 00141 static time_t LastFreeDiskCheck = 0; 00142 int Factor = (Priority == -1) ? 10 : 1; 00143 if (Force || time(NULL) - LastFreeDiskCheck > DISKCHECKDELTA / Factor) { 00144 if (!VideoFileSpaceAvailable(MINDISKSPACE)) { 00145 // Make sure only one instance of VDR does this: 00146 cLockFile LockFile(VideoDirectory); 00147 if (!LockFile.Lock()) 00148 return; 00149 // Remove the oldest file that has been "deleted": 00150 isyslog("low disk space while recording, trying to remove a deleted recording..."); 00151 cThreadLock DeletedRecordingsLock(&DeletedRecordings); 00152 if (DeletedRecordings.Count()) { 00153 cRecording *r = DeletedRecordings.First(); 00154 cRecording *r0 = NULL; 00155 while (r) { 00156 if (IsOnVideoDirectoryFileSystem(r->FileName())) { // only remove recordings that will actually increase the free video disk space 00157 if (!r0 || r->Start() < r0->Start()) 00158 r0 = r; 00159 } 00160 r = DeletedRecordings.Next(r); 00161 } 00162 if (r0) { 00163 if (r0->Remove()) 00164 LastFreeDiskCheck += REMOVELATENCY / Factor; 00165 DeletedRecordings.Del(r0); 00166 return; 00167 } 00168 } 00169 else { 00170 // DeletedRecordings was empty, so to be absolutely sure there are no 00171 // deleted recordings we need to double check: 00172 DeletedRecordings.Update(true); 00173 if (DeletedRecordings.Count()) 00174 return; // the next call will actually remove it 00175 } 00176 // No "deleted" files to remove, so let's see if we can delete a recording: 00177 isyslog("...no deleted recording found, trying to delete an old recording..."); 00178 cThreadLock RecordingsLock(&Recordings); 00179 if (Recordings.Count()) { 00180 cRecording *r = Recordings.First(); 00181 cRecording *r0 = NULL; 00182 while (r) { 00183 if (IsOnVideoDirectoryFileSystem(r->FileName())) { // only delete recordings that will actually increase the free video disk space 00184 if (!r->IsEdited() && r->Lifetime() < MAXLIFETIME) { // edited recordings and recordings with MAXLIFETIME live forever 00185 if ((r->Lifetime() == 0 && Priority > r->Priority()) || // the recording has no guaranteed lifetime and the new recording has higher priority 00186 (r->Lifetime() > 0 && (time(NULL) - r->Start()) / SECSINDAY >= r->Lifetime())) { // the recording's guaranteed lifetime has expired 00187 if (r0) { 00188 if (r->Priority() < r0->Priority() || (r->Priority() == r0->Priority() && r->Start() < r0->Start())) 00189 r0 = r; // in any case we delete the one with the lowest priority (or the older one in case of equal priorities) 00190 } 00191 else 00192 r0 = r; 00193 } 00194 } 00195 } 00196 r = Recordings.Next(r); 00197 } 00198 if (r0 && r0->Delete()) { 00199 Recordings.Del(r0); 00200 return; 00201 } 00202 } 00203 // Unable to free disk space, but there's nothing we can do about that... 00204 isyslog("...no old recording found, giving up"); 00205 Skins.QueueMessage(mtWarning, tr("Low disk space!"), 5, -1); 00206 } 00207 LastFreeDiskCheck = time(NULL); 00208 } 00209 } 00210 00211 // --- cResumeFile ----------------------------------------------------------- 00212 00213 cResumeFile::cResumeFile(const char *FileName, bool IsPesRecording) 00214 { 00215 isPesRecording = IsPesRecording; 00216 const char *Suffix = isPesRecording ? RESUMEFILESUFFIX ".vdr" : RESUMEFILESUFFIX; 00217 fileName = MALLOC(char, strlen(FileName) + strlen(Suffix) + 1); 00218 if (fileName) { 00219 strcpy(fileName, FileName); 00220 sprintf(fileName + strlen(fileName), Suffix, Setup.ResumeID ? "." : "", Setup.ResumeID ? *itoa(Setup.ResumeID) : ""); 00221 } 00222 else 00223 esyslog("ERROR: can't allocate memory for resume file name"); 00224 } 00225 00226 cResumeFile::~cResumeFile() 00227 { 00228 free(fileName); 00229 } 00230 00231 int cResumeFile::Read(void) 00232 { 00233 int resume = -1; 00234 if (fileName) { 00235 struct stat st; 00236 if (stat(fileName, &st) == 0) { 00237 if ((st.st_mode & S_IWUSR) == 0) // no write access, assume no resume 00238 return -1; 00239 } 00240 if (isPesRecording) { 00241 int f = open(fileName, O_RDONLY); 00242 if (f >= 0) { 00243 if (safe_read(f, &resume, sizeof(resume)) != sizeof(resume)) { 00244 resume = -1; 00245 LOG_ERROR_STR(fileName); 00246 } 00247 close(f); 00248 } 00249 else if (errno != ENOENT) 00250 LOG_ERROR_STR(fileName); 00251 } 00252 else { 00253 FILE *f = fopen(fileName, "r"); 00254 if (f) { 00255 cReadLine ReadLine; 00256 char *s; 00257 int line = 0; 00258 while ((s = ReadLine.Read(f)) != NULL) { 00259 ++line; 00260 char *t = skipspace(s + 1); 00261 switch (*s) { 00262 case 'I': resume = atoi(t); 00263 break; 00264 default: ; 00265 } 00266 } 00267 fclose(f); 00268 } 00269 else if (errno != ENOENT) 00270 LOG_ERROR_STR(fileName); 00271 } 00272 } 00273 return resume; 00274 } 00275 00276 bool cResumeFile::Save(int Index) 00277 { 00278 if (fileName) { 00279 if (isPesRecording) { 00280 int f = open(fileName, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE); 00281 if (f >= 0) { 00282 if (safe_write(f, &Index, sizeof(Index)) < 0) 00283 LOG_ERROR_STR(fileName); 00284 close(f); 00285 Recordings.ResetResume(fileName); 00286 return true; 00287 } 00288 } 00289 else { 00290 FILE *f = fopen(fileName, "w"); 00291 if (f) { 00292 fprintf(f, "I %d\n", Index); 00293 fclose(f); 00294 Recordings.ResetResume(fileName); 00295 } 00296 else 00297 LOG_ERROR_STR(fileName); 00298 return true; 00299 } 00300 } 00301 return false; 00302 } 00303 00304 void cResumeFile::Delete(void) 00305 { 00306 if (fileName) { 00307 if (remove(fileName) == 0) 00308 Recordings.ResetResume(fileName); 00309 else if (errno != ENOENT) 00310 LOG_ERROR_STR(fileName); 00311 } 00312 } 00313 00314 // --- cRecordingInfo -------------------------------------------------------- 00315 00316 cRecordingInfo::cRecordingInfo(const cChannel *Channel, const cEvent *Event) 00317 { 00318 channelID = Channel ? Channel->GetChannelID() : tChannelID::InvalidID; 00319 channelName = Channel ? strdup(Channel->Name()) : NULL; 00320 ownEvent = Event ? NULL : new cEvent(0); 00321 event = ownEvent ? ownEvent : Event; 00322 aux = NULL; 00323 framesPerSecond = DEFAULTFRAMESPERSECOND; 00324 priority = MAXPRIORITY; 00325 lifetime = MAXLIFETIME; 00326 fileName = NULL; 00327 if (Channel) { 00328 // Since the EPG data's component records can carry only a single 00329 // language code, let's see whether the channel's PID data has 00330 // more information: 00331 cComponents *Components = (cComponents *)event->Components(); 00332 if (!Components) 00333 Components = new cComponents; 00334 for (int i = 0; i < MAXAPIDS; i++) { 00335 const char *s = Channel->Alang(i); 00336 if (*s) { 00337 tComponent *Component = Components->GetComponent(i, 2, 3); 00338 if (!Component) 00339 Components->SetComponent(Components->NumComponents(), 2, 3, s, NULL); 00340 else if (strlen(s) > strlen(Component->language)) 00341 strn0cpy(Component->language, s, sizeof(Component->language)); 00342 } 00343 } 00344 // There's no "multiple languages" for Dolby Digital tracks, but 00345 // we do the same procedure here, too, in case there is no component 00346 // information at all: 00347 for (int i = 0; i < MAXDPIDS; i++) { 00348 const char *s = Channel->Dlang(i); 00349 if (*s) { 00350 tComponent *Component = Components->GetComponent(i, 4, 0); // AC3 component according to the DVB standard 00351 if (!Component) 00352 Component = Components->GetComponent(i, 2, 5); // fallback "Dolby" component according to the "Premiere pseudo standard" 00353 if (!Component) 00354 Components->SetComponent(Components->NumComponents(), 2, 5, s, NULL); 00355 else if (strlen(s) > strlen(Component->language)) 00356 strn0cpy(Component->language, s, sizeof(Component->language)); 00357 } 00358 } 00359 // The same applies to subtitles: 00360 for (int i = 0; i < MAXSPIDS; i++) { 00361 const char *s = Channel->Slang(i); 00362 if (*s) { 00363 tComponent *Component = Components->GetComponent(i, 3, 3); 00364 if (!Component) 00365 Components->SetComponent(Components->NumComponents(), 3, 3, s, NULL); 00366 else if (strlen(s) > strlen(Component->language)) 00367 strn0cpy(Component->language, s, sizeof(Component->language)); 00368 } 00369 } 00370 if (Components != event->Components()) 00371 ((cEvent *)event)->SetComponents(Components); 00372 } 00373 } 00374 00375 cRecordingInfo::cRecordingInfo(const char *FileName) 00376 { 00377 channelID = tChannelID::InvalidID; 00378 channelName = NULL; 00379 ownEvent = new cEvent(0); 00380 event = ownEvent; 00381 aux = NULL; 00382 framesPerSecond = DEFAULTFRAMESPERSECOND; 00383 priority = MAXPRIORITY; 00384 lifetime = MAXLIFETIME; 00385 fileName = strdup(cString::sprintf("%s%s", FileName, INFOFILESUFFIX)); 00386 } 00387 00388 cRecordingInfo::~cRecordingInfo() 00389 { 00390 delete ownEvent; 00391 free(aux); 00392 free(channelName); 00393 free(fileName); 00394 } 00395 00396 void cRecordingInfo::SetData(const char *Title, const char *ShortText, const char *Description) 00397 { 00398 if (!isempty(Title)) 00399 ((cEvent *)event)->SetTitle(Title); 00400 if (!isempty(ShortText)) 00401 ((cEvent *)event)->SetShortText(ShortText); 00402 if (!isempty(Description)) 00403 ((cEvent *)event)->SetDescription(Description); 00404 } 00405 00406 void cRecordingInfo::SetAux(const char *Aux) 00407 { 00408 free(aux); 00409 aux = Aux ? strdup(Aux) : NULL; 00410 } 00411 00412 void cRecordingInfo::SetFramesPerSecond(double FramesPerSecond) 00413 { 00414 framesPerSecond = FramesPerSecond; 00415 } 00416 00417 bool cRecordingInfo::Read(FILE *f) 00418 { 00419 if (ownEvent) { 00420 cReadLine ReadLine; 00421 char *s; 00422 int line = 0; 00423 while ((s = ReadLine.Read(f)) != NULL) { 00424 ++line; 00425 char *t = skipspace(s + 1); 00426 switch (*s) { 00427 case 'C': { 00428 char *p = strchr(t, ' '); 00429 if (p) { 00430 free(channelName); 00431 channelName = strdup(compactspace(p)); 00432 *p = 0; // strips optional channel name 00433 } 00434 if (*t) 00435 channelID = tChannelID::FromString(t); 00436 } 00437 break; 00438 case 'E': { 00439 unsigned int EventID; 00440 time_t StartTime; 00441 int Duration; 00442 unsigned int TableID = 0; 00443 unsigned int Version = 0xFF; 00444 int n = sscanf(t, "%u %ld %d %X %X", &EventID, &StartTime, &Duration, &TableID, &Version); 00445 if (n >= 3 && n <= 5) { 00446 ownEvent->SetEventID(EventID); 00447 ownEvent->SetStartTime(StartTime); 00448 ownEvent->SetDuration(Duration); 00449 ownEvent->SetTableID(uchar(TableID)); 00450 ownEvent->SetVersion(uchar(Version)); 00451 } 00452 } 00453 break; 00454 case 'F': framesPerSecond = atof(t); 00455 break; 00456 case 'L': lifetime = atoi(t); 00457 break; 00458 case 'P': priority = atoi(t); 00459 break; 00460 case '@': free(aux); 00461 aux = strdup(t); 00462 break; 00463 case '#': break; // comments are ignored 00464 default: if (!ownEvent->Parse(s)) { 00465 esyslog("ERROR: EPG data problem in line %d", line); 00466 return false; 00467 } 00468 break; 00469 } 00470 } 00471 return true; 00472 } 00473 return false; 00474 } 00475 00476 bool cRecordingInfo::Write(FILE *f, const char *Prefix) const 00477 { 00478 if (channelID.Valid()) 00479 fprintf(f, "%sC %s%s%s\n", Prefix, *channelID.ToString(), channelName ? " " : "", channelName ? channelName : ""); 00480 event->Dump(f, Prefix, true); 00481 fprintf(f, "%sF %.10g\n", Prefix, framesPerSecond); 00482 fprintf(f, "%sP %d\n", Prefix, priority); 00483 fprintf(f, "%sL %d\n", Prefix, lifetime); 00484 if (aux) 00485 fprintf(f, "%s@ %s\n", Prefix, aux); 00486 return true; 00487 } 00488 00489 bool cRecordingInfo::Read(void) 00490 { 00491 bool Result = false; 00492 if (fileName) { 00493 FILE *f = fopen(fileName, "r"); 00494 if (f) { 00495 if (Read(f)) 00496 Result = true; 00497 else 00498 esyslog("ERROR: EPG data problem in file %s", fileName); 00499 fclose(f); 00500 } 00501 else if (errno != ENOENT) 00502 LOG_ERROR_STR(fileName); 00503 } 00504 return Result; 00505 } 00506 00507 bool cRecordingInfo::Write(void) const 00508 { 00509 bool Result = false; 00510 if (fileName) { 00511 cSafeFile f(fileName); 00512 if (f.Open()) { 00513 if (Write(f)) 00514 Result = true; 00515 f.Close(); 00516 } 00517 else 00518 LOG_ERROR_STR(fileName); 00519 } 00520 return Result; 00521 } 00522 00523 // --- cRecording ------------------------------------------------------------ 00524 00525 #define RESUME_NOT_INITIALIZED (-2) 00526 00527 struct tCharExchange { char a; char b; }; 00528 tCharExchange CharExchange[] = { 00529 { FOLDERDELIMCHAR, '/' }, 00530 { '/', FOLDERDELIMCHAR }, 00531 { ' ', '_' }, 00532 // backwards compatibility: 00533 { '\'', '\'' }, 00534 { '\'', '\x01' }, 00535 { '/', '\x02' }, 00536 { 0, 0 } 00537 }; 00538 00539 char *ExchangeChars(char *s, bool ToFileSystem) 00540 { 00541 char *p = s; 00542 while (*p) { 00543 if (VfatFileSystem) { 00544 // The VFAT file system can't handle all characters, so we 00545 // have to take extra efforts to encode/decode them: 00546 if (ToFileSystem) { 00547 const char *InvalidChars = "\"\\/:*?|<>#"; 00548 switch (*p) { 00549 // characters that can be mapped to other characters: 00550 case ' ': *p = '_'; break; 00551 case FOLDERDELIMCHAR: *p = '/'; break; 00552 // characters that have to be encoded: 00553 default: 00554 if (strchr(InvalidChars, *p) || *p == '.' && (!*(p + 1) || *(p + 1) == FOLDERDELIMCHAR)) { // Windows can't handle '.' at the end of file/directory names 00555 int l = p - s; 00556 if (char *NewBuffer = (char *)realloc(s, strlen(s) + 10)) { 00557 s = NewBuffer; 00558 p = s + l; 00559 char buf[4]; 00560 sprintf(buf, "#%02X", (unsigned char)*p); 00561 memmove(p + 2, p, strlen(p) + 1); 00562 strncpy(p, buf, 3); 00563 p += 2; 00564 } 00565 else 00566 esyslog("ERROR: out of memory"); 00567 } 00568 } 00569 } 00570 else { 00571 switch (*p) { 00572 // mapped characters: 00573 case '_': *p = ' '; break; 00574 case '/': *p = FOLDERDELIMCHAR; break; 00575 // encoded characters: 00576 case '#': { 00577 if (strlen(p) > 2 && isxdigit(*(p + 1)) && isxdigit(*(p + 2))) { 00578 char buf[3]; 00579 sprintf(buf, "%c%c", *(p + 1), *(p + 2)); 00580 uchar c = uchar(strtol(buf, NULL, 16)); 00581 if (c) { 00582 *p = c; 00583 memmove(p + 1, p + 3, strlen(p) - 2); 00584 } 00585 } 00586 } 00587 break; 00588 // backwards compatibility: 00589 case '\x01': *p = '\''; break; 00590 case '\x02': *p = '/'; break; 00591 case '\x03': *p = ':'; break; 00592 default: ; 00593 } 00594 } 00595 } 00596 else { 00597 for (struct tCharExchange *ce = CharExchange; ce->a && ce->b; ce++) { 00598 if (*p == (ToFileSystem ? ce->a : ce->b)) { 00599 *p = ToFileSystem ? ce->b : ce->a; 00600 break; 00601 } 00602 } 00603 } 00604 p++; 00605 } 00606 return s; 00607 } 00608 00609 cRecording::cRecording(cTimer *Timer, const cEvent *Event) 00610 { 00611 resume = RESUME_NOT_INITIALIZED; 00612 titleBuffer = NULL; 00613 sortBuffer = NULL; 00614 fileName = NULL; 00615 name = NULL; 00616 fileSizeMB = -1; // unknown 00617 channel = Timer->Channel()->Number(); 00618 instanceId = InstanceId; 00619 isPesRecording = false; 00620 framesPerSecond = DEFAULTFRAMESPERSECOND; 00621 numFrames = -1; 00622 deleted = 0; 00623 // set up the actual name: 00624 const char *Title = Event ? Event->Title() : NULL; 00625 const char *Subtitle = Event ? Event->ShortText() : NULL; 00626 char SubtitleBuffer[MAX_SUBTITLE_LENGTH]; 00627 if (isempty(Title)) 00628 Title = Timer->Channel()->Name(); 00629 if (isempty(Subtitle)) 00630 Subtitle = " "; 00631 else if (strlen(Subtitle) > MAX_SUBTITLE_LENGTH) { 00632 // let's make sure the Subtitle doesn't produce too long a file name: 00633 Utf8Strn0Cpy(SubtitleBuffer, Subtitle, MAX_SUBTITLE_LENGTH); 00634 Subtitle = SubtitleBuffer; 00635 } 00636 const char *macroTITLE = strstr(Timer->File(), TIMERMACRO_TITLE); 00637 const char *macroEPISODE = strstr(Timer->File(), TIMERMACRO_EPISODE); 00638 if (macroTITLE || macroEPISODE) { 00639 name = strdup(Timer->File()); 00640 name = strreplace(name, TIMERMACRO_TITLE, Title); 00641 name = strreplace(name, TIMERMACRO_EPISODE, Subtitle); 00642 // avoid blanks at the end: 00643 int l = strlen(name); 00644 while (l-- > 2) { 00645 if (name[l] == ' ' && name[l - 1] != FOLDERDELIMCHAR) 00646 name[l] = 0; 00647 else 00648 break; 00649 } 00650 if (Timer->IsSingleEvent()) { 00651 Timer->SetFile(name); // this was an instant recording, so let's set the actual data 00652 Timers.SetModified(); 00653 } 00654 } 00655 else if (Timer->IsSingleEvent() || !Setup.UseSubtitle) 00656 name = strdup(Timer->File()); 00657 else 00658 name = strdup(cString::sprintf("%s~%s", Timer->File(), Subtitle)); 00659 // substitute characters that would cause problems in file names: 00660 strreplace(name, '\n', ' '); 00661 start = Timer->StartTime(); 00662 priority = Timer->Priority(); 00663 lifetime = Timer->Lifetime(); 00664 // handle info: 00665 info = new cRecordingInfo(Timer->Channel(), Event); 00666 info->SetAux(Timer->Aux()); 00667 info->priority = priority; 00668 info->lifetime = lifetime; 00669 } 00670 00671 cRecording::cRecording(const char *FileName) 00672 { 00673 resume = RESUME_NOT_INITIALIZED; 00674 fileSizeMB = -1; // unknown 00675 channel = -1; 00676 instanceId = -1; 00677 priority = MAXPRIORITY; // assume maximum in case there is no info file 00678 lifetime = MAXLIFETIME; 00679 isPesRecording = false; 00680 framesPerSecond = DEFAULTFRAMESPERSECOND; 00681 numFrames = -1; 00682 deleted = 0; 00683 titleBuffer = NULL; 00684 sortBuffer = NULL; 00685 FileName = fileName = strdup(FileName); 00686 if (*(fileName + strlen(fileName) - 1) == '/') 00687 *(fileName + strlen(fileName) - 1) = 0; 00688 FileName += strlen(VideoDirectory) + 1; 00689 const char *p = strrchr(FileName, '/'); 00690 00691 name = NULL; 00692 info = new cRecordingInfo(fileName); 00693 if (p) { 00694 time_t now = time(NULL); 00695 struct tm tm_r; 00696 struct tm t = *localtime_r(&now, &tm_r); // this initializes the time zone in 't' 00697 t.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting 00698 if (7 == sscanf(p + 1, DATAFORMATTS, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &channel, &instanceId) 00699 || 7 == sscanf(p + 1, DATAFORMATPES, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &priority, &lifetime)) { 00700 t.tm_year -= 1900; 00701 t.tm_mon--; 00702 t.tm_sec = 0; 00703 start = mktime(&t); 00704 name = MALLOC(char, p - FileName + 1); 00705 strncpy(name, FileName, p - FileName); 00706 name[p - FileName] = 0; 00707 name = ExchangeChars(name, false); 00708 isPesRecording = instanceId < 0; 00709 } 00710 else 00711 return; 00712 GetResume(); 00713 // read an optional info file: 00714 cString InfoFileName = cString::sprintf("%s%s", fileName, isPesRecording ? INFOFILESUFFIX ".vdr" : INFOFILESUFFIX); 00715 FILE *f = fopen(InfoFileName, "r"); 00716 if (f) { 00717 if (!info->Read(f)) 00718 esyslog("ERROR: EPG data problem in file %s", *InfoFileName); 00719 else if (!isPesRecording) { 00720 priority = info->priority; 00721 lifetime = info->lifetime; 00722 framesPerSecond = info->framesPerSecond; 00723 } 00724 fclose(f); 00725 } 00726 else if (errno != ENOENT) 00727 LOG_ERROR_STR(*InfoFileName); 00728 #ifdef SUMMARYFALLBACK 00729 // fall back to the old 'summary.vdr' if there was no 'info.vdr': 00730 if (isempty(info->Title())) { 00731 cString SummaryFileName = cString::sprintf("%s%s", fileName, SUMMARYFILESUFFIX); 00732 FILE *f = fopen(SummaryFileName, "r"); 00733 if (f) { 00734 int line = 0; 00735 char *data[3] = { NULL }; 00736 cReadLine ReadLine; 00737 char *s; 00738 while ((s = ReadLine.Read(f)) != NULL) { 00739 if (*s || line > 1) { 00740 if (data[line]) { 00741 int len = strlen(s); 00742 len += strlen(data[line]) + 1; 00743 if (char *NewBuffer = (char *)realloc(data[line], len + 1)) { 00744 data[line] = NewBuffer; 00745 strcat(data[line], "\n"); 00746 strcat(data[line], s); 00747 } 00748 else 00749 esyslog("ERROR: out of memory"); 00750 } 00751 else 00752 data[line] = strdup(s); 00753 } 00754 else 00755 line++; 00756 } 00757 fclose(f); 00758 if (!data[2]) { 00759 data[2] = data[1]; 00760 data[1] = NULL; 00761 } 00762 else if (data[1] && data[2]) { 00763 // if line 1 is too long, it can't be the short text, 00764 // so assume the short text is missing and concatenate 00765 // line 1 and line 2 to be the long text: 00766 int len = strlen(data[1]); 00767 if (len > 80) { 00768 if (char *NewBuffer = (char *)realloc(data[1], len + 1 + strlen(data[2]) + 1)) { 00769 data[1] = NewBuffer; 00770 strcat(data[1], "\n"); 00771 strcat(data[1], data[2]); 00772 free(data[2]); 00773 data[2] = data[1]; 00774 data[1] = NULL; 00775 } 00776 else 00777 esyslog("ERROR: out of memory"); 00778 } 00779 } 00780 info->SetData(data[0], data[1], data[2]); 00781 for (int i = 0; i < 3; i ++) 00782 free(data[i]); 00783 } 00784 else if (errno != ENOENT) 00785 LOG_ERROR_STR(*SummaryFileName); 00786 } 00787 #endif 00788 } 00789 } 00790 00791 cRecording::~cRecording() 00792 { 00793 free(titleBuffer); 00794 free(sortBuffer); 00795 free(fileName); 00796 free(name); 00797 delete info; 00798 } 00799 00800 char *cRecording::StripEpisodeName(char *s) 00801 { 00802 char *t = s, *s1 = NULL, *s2 = NULL; 00803 while (*t) { 00804 if (*t == '/') { 00805 if (s1) { 00806 if (s2) 00807 s1 = s2; 00808 s2 = t; 00809 } 00810 else 00811 s1 = t; 00812 } 00813 t++; 00814 } 00815 if (s1 && s2) 00816 memmove(s1 + 1, s2, t - s2 + 1); 00817 return s; 00818 } 00819 00820 char *cRecording::SortName(void) const 00821 { 00822 if (!sortBuffer) { 00823 char *s = StripEpisodeName(strdup(FileName() + strlen(VideoDirectory) + 1)); 00824 strreplace(s, '/', 'a'); // some locales ignore '/' when sorting 00825 int l = strxfrm(NULL, s, 0) + 1; 00826 sortBuffer = MALLOC(char, l); 00827 strxfrm(sortBuffer, s, l); 00828 free(s); 00829 } 00830 return sortBuffer; 00831 } 00832 00833 int cRecording::GetResume(void) const 00834 { 00835 if (resume == RESUME_NOT_INITIALIZED) { 00836 cResumeFile ResumeFile(FileName(), isPesRecording); 00837 resume = ResumeFile.Read(); 00838 } 00839 return resume; 00840 } 00841 00842 int cRecording::Compare(const cListObject &ListObject) const 00843 { 00844 cRecording *r = (cRecording *)&ListObject; 00845 return strcasecmp(SortName(), r->SortName()); 00846 } 00847 00848 const char *cRecording::FileName(void) const 00849 { 00850 if (!fileName) { 00851 struct tm tm_r; 00852 struct tm *t = localtime_r(&start, &tm_r); 00853 const char *fmt = isPesRecording ? NAMEFORMATPES : NAMEFORMATTS; 00854 int ch = isPesRecording ? priority : channel; 00855 int ri = isPesRecording ? lifetime : instanceId; 00856 name = ExchangeChars(name, true); 00857 fileName = strdup(cString::sprintf(fmt, VideoDirectory, name, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, ch, ri)); 00858 name = ExchangeChars(name, false); 00859 } 00860 return fileName; 00861 } 00862 00863 const char *cRecording::Title(char Delimiter, bool NewIndicator, int Level) const 00864 { 00865 char New = NewIndicator && IsNew() ? '*' : ' '; 00866 free(titleBuffer); 00867 titleBuffer = NULL; 00868 if (Level < 0 || Level == HierarchyLevels()) { 00869 struct tm tm_r; 00870 struct tm *t = localtime_r(&start, &tm_r); 00871 char *s; 00872 if (Level > 0 && (s = strrchr(name, FOLDERDELIMCHAR)) != NULL) 00873 s++; 00874 else 00875 s = name; 00876 cString Length(""); 00877 if (NewIndicator) { 00878 int Minutes = max(0, (LengthInSeconds() + 30) / 60); 00879 Length = cString::sprintf("%c%d:%02d", 00880 Delimiter, 00881 Minutes / 60, 00882 Minutes % 60 00883 ); 00884 } 00885 titleBuffer = strdup(cString::sprintf("%02d.%02d.%02d%c%02d:%02d%s%c%c%s", 00886 t->tm_mday, 00887 t->tm_mon + 1, 00888 t->tm_year % 100, 00889 Delimiter, 00890 t->tm_hour, 00891 t->tm_min, 00892 *Length, 00893 New, 00894 Delimiter, 00895 s)); 00896 // let's not display a trailing FOLDERDELIMCHAR: 00897 if (!NewIndicator) 00898 stripspace(titleBuffer); 00899 s = &titleBuffer[strlen(titleBuffer) - 1]; 00900 if (*s == FOLDERDELIMCHAR) 00901 *s = 0; 00902 } 00903 else if (Level < HierarchyLevels()) { 00904 const char *s = name; 00905 const char *p = s; 00906 while (*++s) { 00907 if (*s == FOLDERDELIMCHAR) { 00908 if (Level--) 00909 p = s + 1; 00910 else 00911 break; 00912 } 00913 } 00914 titleBuffer = MALLOC(char, s - p + 3); 00915 *titleBuffer = Delimiter; 00916 *(titleBuffer + 1) = Delimiter; 00917 strn0cpy(titleBuffer + 2, p, s - p + 1); 00918 } 00919 else 00920 return ""; 00921 return titleBuffer; 00922 } 00923 00924 const char *cRecording::PrefixFileName(char Prefix) 00925 { 00926 cString p = PrefixVideoFileName(FileName(), Prefix); 00927 if (*p) { 00928 free(fileName); 00929 fileName = strdup(p); 00930 return fileName; 00931 } 00932 return NULL; 00933 } 00934 00935 const char *cRecording::UpdateFileName(const char *FileName) 00936 { 00937 if (FileName && *FileName) { 00938 free(fileName); 00939 fileName = strdup(FileName); 00940 return fileName; 00941 } 00942 return NULL; 00943 } 00944 00945 int cRecording::HierarchyLevels(void) const 00946 { 00947 const char *s = name; 00948 int level = 0; 00949 while (*++s) { 00950 if (*s == FOLDERDELIMCHAR) 00951 level++; 00952 } 00953 return level; 00954 } 00955 00956 bool cRecording::IsEdited(void) const 00957 { 00958 const char *s = strrchr(name, FOLDERDELIMCHAR); 00959 s = !s ? name : s + 1; 00960 return *s == '%'; 00961 } 00962 00963 void cRecording::ReadInfo(void) 00964 { 00965 info->Read(); 00966 priority = info->priority; 00967 lifetime = info->lifetime; 00968 framesPerSecond = info->framesPerSecond; 00969 } 00970 00971 bool cRecording::WriteInfo(void) 00972 { 00973 cString InfoFileName = cString::sprintf("%s%s", fileName, isPesRecording ? INFOFILESUFFIX ".vdr" : INFOFILESUFFIX); 00974 FILE *f = fopen(InfoFileName, "w"); 00975 if (f) { 00976 info->Write(f); 00977 fclose(f); 00978 } 00979 else 00980 LOG_ERROR_STR(*InfoFileName); 00981 return true; 00982 } 00983 00984 void cRecording::SetStartTime(time_t Start) 00985 { 00986 start = Start; 00987 free(fileName); 00988 fileName = NULL; 00989 } 00990 00991 bool cRecording::Delete(void) 00992 { 00993 bool result = true; 00994 char *NewName = strdup(FileName()); 00995 char *ext = strrchr(NewName, '.'); 00996 if (ext && strcmp(ext, RECEXT) == 0) { 00997 strncpy(ext, DELEXT, strlen(ext)); 00998 if (access(NewName, F_OK) == 0) { 00999 // the new name already exists, so let's remove that one first: 01000 isyslog("removing recording '%s'", NewName); 01001 RemoveVideoFile(NewName); 01002 } 01003 isyslog("deleting recording '%s'", FileName()); 01004 if (access(FileName(), F_OK) == 0) 01005 result = RenameVideoFile(FileName(), NewName); 01006 else { 01007 isyslog("recording '%s' vanished", FileName()); 01008 result = true; // well, we were going to delete it, anyway 01009 } 01010 } 01011 free(NewName); 01012 return result; 01013 } 01014 01015 bool cRecording::Remove(void) 01016 { 01017 // let's do a final safety check here: 01018 if (!endswith(FileName(), DELEXT)) { 01019 esyslog("attempt to remove recording %s", FileName()); 01020 return false; 01021 } 01022 isyslog("removing recording %s", FileName()); 01023 return RemoveVideoFile(FileName()); 01024 } 01025 01026 bool cRecording::Undelete(void) 01027 { 01028 bool result = true; 01029 char *NewName = strdup(FileName()); 01030 char *ext = strrchr(NewName, '.'); 01031 if (ext && strcmp(ext, DELEXT) == 0) { 01032 strncpy(ext, RECEXT, strlen(ext)); 01033 if (access(NewName, F_OK) == 0) { 01034 // the new name already exists, so let's not remove that one: 01035 esyslog("ERROR: attempt to undelete '%s', while recording '%s' exists", FileName(), NewName); 01036 result = false; 01037 } 01038 else { 01039 isyslog("undeleting recording '%s'", FileName()); 01040 if (access(FileName(), F_OK) == 0) 01041 result = RenameVideoFile(FileName(), NewName); 01042 else { 01043 isyslog("deleted recording '%s' vanished", FileName()); 01044 result = false; 01045 } 01046 } 01047 } 01048 free(NewName); 01049 return result; 01050 } 01051 01052 void cRecording::ResetResume(void) const 01053 { 01054 resume = RESUME_NOT_INITIALIZED; 01055 } 01056 01057 int cRecording::NumFrames(void) const 01058 { 01059 if (numFrames < 0) { 01060 int nf = cIndexFile::GetLength(FileName(), IsPesRecording()); 01061 if (time(NULL) - LastModifiedTime(FileName()) < MININDEXAGE) 01062 return nf; // check again later for ongoing recordings 01063 numFrames = nf; 01064 } 01065 return numFrames; 01066 } 01067 01068 int cRecording::LengthInSeconds(void) const 01069 { 01070 int nf = NumFrames(); 01071 if (nf >= 0) 01072 return int(nf / FramesPerSecond()); 01073 return -1; 01074 } 01075 01076 int cRecording::FileSizeMB(void) const 01077 { 01078 if (fileSizeMB < 0) { 01079 int fs = DirSizeMB(FileName()); 01080 if (time(NULL) - LastModifiedTime(FileName()) < MININDEXAGE) 01081 return fs; // check again later for ongoing recordings 01082 fileSizeMB = fs; 01083 } 01084 return fileSizeMB; 01085 } 01086 01087 // --- cRecordings ----------------------------------------------------------- 01088 01089 cRecordings Recordings; 01090 01091 char *cRecordings::updateFileName = NULL; 01092 01093 cRecordings::cRecordings(bool Deleted) 01094 :cThread("video directory scanner") 01095 { 01096 deleted = Deleted; 01097 lastUpdate = 0; 01098 state = 0; 01099 } 01100 01101 cRecordings::~cRecordings() 01102 { 01103 Cancel(3); 01104 } 01105 01106 void cRecordings::Action(void) 01107 { 01108 Refresh(); 01109 } 01110 01111 const char *cRecordings::UpdateFileName(void) 01112 { 01113 if (!updateFileName) 01114 updateFileName = strdup(AddDirectory(VideoDirectory, ".update")); 01115 return updateFileName; 01116 } 01117 01118 void cRecordings::Refresh(bool Foreground) 01119 { 01120 lastUpdate = time(NULL); // doing this first to make sure we don't miss anything 01121 Lock(); 01122 Clear(); 01123 ChangeState(); 01124 Unlock(); 01125 ScanVideoDir(VideoDirectory, Foreground); 01126 } 01127 01128 void cRecordings::ScanVideoDir(const char *DirName, bool Foreground, int LinkLevel) 01129 { 01130 cReadDir d(DirName); 01131 struct dirent *e; 01132 while ((Foreground || Running()) && (e = d.Next()) != NULL) { 01133 cString buffer = AddDirectory(DirName, e->d_name); 01134 struct stat st; 01135 if (lstat(buffer, &st) == 0) { 01136 int Link = 0; 01137 if (S_ISLNK(st.st_mode)) { 01138 if (LinkLevel > MAX_LINK_LEVEL) { 01139 isyslog("max link level exceeded - not scanning %s", *buffer); 01140 continue; 01141 } 01142 Link = 1; 01143 if (stat(buffer, &st) != 0) 01144 continue; 01145 } 01146 if (S_ISDIR(st.st_mode)) { 01147 if (endswith(buffer, deleted ? DELEXT : RECEXT)) { 01148 cRecording *r = new cRecording(buffer); 01149 if (r->Name()) { 01150 r->NumFrames(); // initializes the numFrames member 01151 r->FileSizeMB(); // initializes the fileSizeMB member 01152 if (deleted) 01153 r->deleted = time(NULL); 01154 Lock(); 01155 Add(r); 01156 ChangeState(); 01157 Unlock(); 01158 } 01159 else 01160 delete r; 01161 } 01162 else 01163 ScanVideoDir(buffer, Foreground, LinkLevel + Link); 01164 } 01165 } 01166 } 01167 } 01168 01169 bool cRecordings::StateChanged(int &State) 01170 { 01171 int NewState = state; 01172 bool Result = State != NewState; 01173 State = state; 01174 return Result; 01175 } 01176 01177 void cRecordings::TouchUpdate(void) 01178 { 01179 bool needsUpdate = NeedsUpdate(); 01180 TouchFile(UpdateFileName()); 01181 if (!needsUpdate) 01182 lastUpdate = time(NULL); // make sure we don't trigger ourselves 01183 } 01184 01185 bool cRecordings::NeedsUpdate(void) 01186 { 01187 time_t lastModified = LastModifiedTime(UpdateFileName()); 01188 if (lastModified > time(NULL)) 01189 return false; // somebody's clock isn't running correctly 01190 return lastUpdate < lastModified; 01191 } 01192 01193 bool cRecordings::Update(bool Wait) 01194 { 01195 if (Wait) { 01196 Refresh(true); 01197 return Count() > 0; 01198 } 01199 else 01200 Start(); 01201 return false; 01202 } 01203 01204 cRecording *cRecordings::GetByName(const char *FileName) 01205 { 01206 if (FileName) { 01207 for (cRecording *recording = First(); recording; recording = Next(recording)) { 01208 if (strcmp(recording->FileName(), FileName) == 0) 01209 return recording; 01210 } 01211 } 01212 return NULL; 01213 } 01214 01215 void cRecordings::AddByName(const char *FileName, bool TriggerUpdate) 01216 { 01217 LOCK_THREAD; 01218 cRecording *recording = GetByName(FileName); 01219 if (!recording) { 01220 recording = new cRecording(FileName); 01221 Add(recording); 01222 ChangeState(); 01223 if (TriggerUpdate) 01224 TouchUpdate(); 01225 } 01226 } 01227 01228 void cRecordings::DelByName(const char *FileName, bool RemoveRecording) 01229 { 01230 LOCK_THREAD; 01231 cRecording *recording = GetByName(FileName); 01232 if (recording) { 01233 cThreadLock DeletedRecordingsLock(&DeletedRecordings); 01234 Del(recording, false); 01235 char *ext = strrchr(recording->fileName, '.'); 01236 if (ext && RemoveRecording) { 01237 strncpy(ext, DELEXT, strlen(ext)); 01238 if (access(recording->FileName(), F_OK) == 0) { 01239 recording->deleted = time(NULL); 01240 DeletedRecordings.Add(recording); 01241 recording = NULL; // to prevent it from being deleted below 01242 } 01243 } 01244 delete recording; 01245 ChangeState(); 01246 TouchUpdate(); 01247 } 01248 } 01249 01250 void cRecordings::UpdateByName(const char *FileName) 01251 { 01252 LOCK_THREAD; 01253 cRecording *recording = GetByName(FileName); 01254 if (recording) 01255 recording->ReadInfo(); 01256 } 01257 01258 int cRecordings::TotalFileSizeMB(void) 01259 { 01260 int size = 0; 01261 LOCK_THREAD; 01262 for (cRecording *recording = First(); recording; recording = Next(recording)) { 01263 int FileSizeMB = recording->FileSizeMB(); 01264 if (FileSizeMB > 0 && IsOnVideoDirectoryFileSystem(recording->FileName())) 01265 size += FileSizeMB; 01266 } 01267 return size; 01268 } 01269 01270 double cRecordings::MBperMinute(void) 01271 { 01272 int size = 0; 01273 int length = 0; 01274 LOCK_THREAD; 01275 for (cRecording *recording = First(); recording; recording = Next(recording)) { 01276 if (IsOnVideoDirectoryFileSystem(recording->FileName())) { 01277 int FileSizeMB = recording->FileSizeMB(); 01278 if (FileSizeMB > 0) { 01279 int LengthInSeconds = recording->LengthInSeconds(); 01280 if (LengthInSeconds > 0) { 01281 size += FileSizeMB; 01282 length += LengthInSeconds; 01283 } 01284 } 01285 } 01286 } 01287 return (size && length) ? double(size) * 60 / length : -1; 01288 } 01289 01290 void cRecordings::ResetResume(const char *ResumeFileName) 01291 { 01292 LOCK_THREAD; 01293 for (cRecording *recording = First(); recording; recording = Next(recording)) { 01294 if (!ResumeFileName || strncmp(ResumeFileName, recording->FileName(), strlen(recording->FileName())) == 0) 01295 recording->ResetResume(); 01296 } 01297 ChangeState(); 01298 } 01299 01300 // --- cMark ----------------------------------------------------------------- 01301 01302 double MarkFramesPerSecond = DEFAULTFRAMESPERSECOND; 01303 cMutex MutexMarkFramesPerSecond; 01304 01305 cMark::cMark(int Position, const char *Comment, double FramesPerSecond) 01306 { 01307 position = Position; 01308 comment = Comment; 01309 framesPerSecond = FramesPerSecond; 01310 } 01311 01312 cMark::~cMark() 01313 { 01314 } 01315 01316 cString cMark::ToText(void) 01317 { 01318 return cString::sprintf("%s%s%s\n", *IndexToHMSF(position, true, framesPerSecond), Comment() ? " " : "", Comment() ? Comment() : ""); 01319 } 01320 01321 bool cMark::Parse(const char *s) 01322 { 01323 comment = NULL; 01324 framesPerSecond = MarkFramesPerSecond; 01325 position = HMSFToIndex(s, framesPerSecond); 01326 const char *p = strchr(s, ' '); 01327 if (p) { 01328 p = skipspace(p); 01329 if (*p) 01330 comment = strdup(p); 01331 } 01332 return true; 01333 } 01334 01335 bool cMark::Save(FILE *f) 01336 { 01337 return fprintf(f, "%s", *ToText()) > 0; 01338 } 01339 01340 // --- cMarks ---------------------------------------------------------------- 01341 01342 bool cMarks::Load(const char *RecordingFileName, double FramesPerSecond, bool IsPesRecording) 01343 { 01344 fileName = AddDirectory(RecordingFileName, IsPesRecording ? MARKSFILESUFFIX ".vdr" : MARKSFILESUFFIX); 01345 framesPerSecond = FramesPerSecond; 01346 nextUpdate = 0; 01347 lastFileTime = -1; // the first call to Load() must take place! 01348 lastChange = 0; 01349 return Update(); 01350 } 01351 01352 bool cMarks::Update(void) 01353 { 01354 time_t t = time(NULL); 01355 if (t > nextUpdate) { 01356 time_t LastModified = LastModifiedTime(fileName); 01357 if (LastModified != lastFileTime) // change detected, or first run 01358 lastChange = LastModified > 0 ? LastModified : t; 01359 int d = t - lastChange; 01360 if (d < 60) 01361 d = 1; // check frequently if the file has just been modified 01362 else if (d < 3600) 01363 d = 10; // older files are checked less frequently 01364 else 01365 d /= 360; // phase out checking for very old files 01366 nextUpdate = t + d; 01367 if (LastModified != lastFileTime) { // change detected, or first run 01368 lastFileTime = LastModified; 01369 if (lastFileTime == t) 01370 lastFileTime--; // make sure we don't miss updates in the remaining second 01371 cMutexLock MutexLock(&MutexMarkFramesPerSecond); 01372 MarkFramesPerSecond = framesPerSecond; 01373 if (cConfig<cMark>::Load(fileName)) { 01374 Sort(); 01375 return true; 01376 } 01377 } 01378 } 01379 return false; 01380 } 01381 01382 void cMarks::Sort(void) 01383 { 01384 for (cMark *m1 = First(); m1; m1 = Next(m1)) { 01385 for (cMark *m2 = Next(m1); m2; m2 = Next(m2)) { 01386 if (m2->Position() < m1->Position()) { 01387 swap(m1->position, m2->position); 01388 swap(m1->comment, m2->comment); 01389 } 01390 } 01391 } 01392 } 01393 01394 cMark *cMarks::Add(int Position) 01395 { 01396 cMark *m = Get(Position); 01397 if (!m) { 01398 cConfig<cMark>::Add(m = new cMark(Position, NULL, framesPerSecond)); 01399 Sort(); 01400 } 01401 return m; 01402 } 01403 01404 cMark *cMarks::Get(int Position) 01405 { 01406 for (cMark *mi = First(); mi; mi = Next(mi)) { 01407 if (mi->Position() == Position) 01408 return mi; 01409 } 01410 return NULL; 01411 } 01412 01413 cMark *cMarks::GetPrev(int Position) 01414 { 01415 for (cMark *mi = Last(); mi; mi = Prev(mi)) { 01416 if (mi->Position() < Position) 01417 return mi; 01418 } 01419 return NULL; 01420 } 01421 01422 cMark *cMarks::GetNext(int Position) 01423 { 01424 for (cMark *mi = First(); mi; mi = Next(mi)) { 01425 if (mi->Position() > Position) 01426 return mi; 01427 } 01428 return NULL; 01429 } 01430 01431 // --- cRecordingUserCommand ------------------------------------------------- 01432 01433 const char *cRecordingUserCommand::command = NULL; 01434 01435 void cRecordingUserCommand::InvokeCommand(const char *State, const char *RecordingFileName) 01436 { 01437 if (command) { 01438 cString cmd = cString::sprintf("%s %s \"%s\"", command, State, *strescape(RecordingFileName, "\\\"$")); 01439 isyslog("executing '%s'", *cmd); 01440 SystemExec(cmd); 01441 } 01442 } 01443 01444 // --- cIndexFileGenerator --------------------------------------------------- 01445 01446 #define IFG_BUFFER_SIZE KILOBYTE(100) 01447 01448 class cIndexFileGenerator : public cThread { 01449 private: 01450 cString recordingName; 01451 protected: 01452 virtual void Action(void); 01453 public: 01454 cIndexFileGenerator(const char *RecordingName); 01455 ~cIndexFileGenerator(); 01456 }; 01457 01458 cIndexFileGenerator::cIndexFileGenerator(const char *RecordingName) 01459 :cThread("index file generator") 01460 ,recordingName(RecordingName) 01461 { 01462 Start(); 01463 } 01464 01465 cIndexFileGenerator::~cIndexFileGenerator() 01466 { 01467 Cancel(3); 01468 } 01469 01470 void cIndexFileGenerator::Action(void) 01471 { 01472 bool IndexFileComplete = false; 01473 bool Rewind = false; 01474 cFileName FileName(recordingName, false); 01475 cUnbufferedFile *ReplayFile = FileName.Open(); 01476 cRingBufferLinear Buffer(IFG_BUFFER_SIZE, MIN_TS_PACKETS_FOR_FRAME_DETECTOR * TS_SIZE); 01477 cPatPmtParser PatPmtParser; 01478 cFrameDetector FrameDetector; 01479 cIndexFile IndexFile(recordingName, true); 01480 int BufferChunks = KILOBYTE(1); // no need to read a lot at the beginning when parsing PAT/PMT 01481 off_t FileSize = 0; 01482 off_t FrameOffset = -1; 01483 Skins.QueueMessage(mtInfo, tr("Regenerating index file")); 01484 while (Running()) { 01485 // Rewind input file: 01486 if (Rewind) { 01487 ReplayFile = FileName.SetOffset(1); 01488 Buffer.Clear(); 01489 Rewind = false; 01490 } 01491 // Process data: 01492 int Length; 01493 uchar *Data = Buffer.Get(Length); 01494 if (Data) { 01495 if (FrameDetector.Synced()) { 01496 // Step 3 - generate the index: 01497 if (TsPid(Data) == PATPID) 01498 FrameOffset = FileSize; // the PAT/PMT is at the beginning of an I-frame 01499 int Processed = FrameDetector.Analyze(Data, Length); 01500 if (Processed > 0) { 01501 if (FrameDetector.NewFrame()) { 01502 IndexFile.Write(FrameDetector.IndependentFrame(), FileName.Number(), FrameOffset >= 0 ? FrameOffset : FileSize); 01503 FrameOffset = -1; 01504 } 01505 FileSize += Processed; 01506 Buffer.Del(Processed); 01507 } 01508 } 01509 else if (PatPmtParser.Vpid()) { 01510 // Step 2 - sync FrameDetector: 01511 int Processed = FrameDetector.Analyze(Data, Length); 01512 if (Processed > 0) { 01513 if (FrameDetector.Synced()) { 01514 // Synced FrameDetector, so rewind for actual processing: 01515 FrameDetector.Reset(); 01516 Rewind = true; 01517 } 01518 Buffer.Del(Processed); 01519 } 01520 } 01521 else { 01522 // Step 1 - parse PAT/PMT: 01523 uchar *p = Data; 01524 while (Length >= TS_SIZE) { 01525 int Pid = TsPid(p); 01526 if (Pid == 0) 01527 PatPmtParser.ParsePat(p, TS_SIZE); 01528 else if (Pid == PatPmtParser.PmtPid()) 01529 PatPmtParser.ParsePmt(p, TS_SIZE); 01530 Length -= TS_SIZE; 01531 p += TS_SIZE; 01532 if (PatPmtParser.Vpid()) { 01533 // Found Vpid, so rewind to sync FrameDetector: 01534 FrameDetector.SetPid(PatPmtParser.Vpid(), PatPmtParser.Vtype()); 01535 BufferChunks = IFG_BUFFER_SIZE; 01536 Rewind = true; 01537 break; 01538 } 01539 } 01540 Buffer.Del(p - Data); 01541 } 01542 } 01543 // Read data: 01544 else if (ReplayFile) { 01545 int Result = Buffer.Read(ReplayFile, BufferChunks); 01546 if (Result == 0) { // EOF 01547 ReplayFile = FileName.NextFile(); 01548 FileSize = 0; 01549 FrameOffset = -1; 01550 } 01551 } 01552 // Recording has been processed: 01553 else { 01554 IndexFileComplete = true; 01555 break; 01556 } 01557 } 01558 // Delete the index file if the recording has not been processed entirely: 01559 if (IndexFileComplete) 01560 Skins.QueueMessage(mtInfo, tr("Index file regeneration complete")); 01561 else 01562 IndexFile.Delete(); 01563 } 01564 01565 // --- cIndexFile ------------------------------------------------------------ 01566 01567 #define INDEXFILESUFFIX "/index" 01568 01569 // The maximum time to wait before giving up while catching up on an index file: 01570 #define MAXINDEXCATCHUP 8 // seconds 01571 01572 struct tIndexPes { 01573 uint32_t offset; 01574 uchar type; 01575 uchar number; 01576 uint16_t reserved; 01577 }; 01578 01579 struct tIndexTs { 01580 uint64_t offset:40; // up to 1TB per file (not using off_t here - must definitely be exactly 64 bit!) 01581 int reserved:7; // reserved for future use 01582 int independent:1; // marks frames that can be displayed by themselves (for trick modes) 01583 uint16_t number:16; // up to 64K files per recording 01584 tIndexTs(off_t Offset, bool Independent, uint16_t Number) 01585 { 01586 offset = Offset; 01587 reserved = 0; 01588 independent = Independent; 01589 number = Number; 01590 } 01591 }; 01592 01593 #define MAXWAITFORINDEXFILE 10 // max. time to wait for the regenerated index file (seconds) 01594 #define INDEXFILECHECKINTERVAL 500 // ms between checks for existence of the regenerated index file 01595 #define INDEXFILETESTINTERVAL 10 // ms between tests for the size of the index file in case of pausing live video 01596 01597 cIndexFile::cIndexFile(const char *FileName, bool Record, bool IsPesRecording, bool PauseLive) 01598 :resumeFile(FileName, IsPesRecording) 01599 { 01600 f = -1; 01601 size = 0; 01602 last = -1; 01603 index = NULL; 01604 isPesRecording = IsPesRecording; 01605 indexFileGenerator = NULL; 01606 if (FileName) { 01607 fileName = IndexFileName(FileName, isPesRecording); 01608 if (!Record && PauseLive) { 01609 // Wait until the index file contains at least two frames: 01610 time_t tmax = time(NULL) + MAXWAITFORINDEXFILE; 01611 while (time(NULL) < tmax && FileSize(fileName) < off_t(2 * sizeof(tIndexTs))) 01612 cCondWait::SleepMs(INDEXFILETESTINTERVAL); 01613 } 01614 int delta = 0; 01615 if (!Record && access(fileName, R_OK) != 0) { 01616 // Index file doesn't exist, so try to regenerate it: 01617 if (!isPesRecording) { // sorry, can only do this for TS recordings 01618 resumeFile.Delete(); // just in case 01619 indexFileGenerator = new cIndexFileGenerator(FileName); 01620 // Wait until the index file exists: 01621 time_t tmax = time(NULL) + MAXWAITFORINDEXFILE; 01622 do { 01623 cCondWait::SleepMs(INDEXFILECHECKINTERVAL); // start with a sleep, to give it a head start 01624 } while (access(fileName, R_OK) != 0 && time(NULL) < tmax); 01625 } 01626 } 01627 if (access(fileName, R_OK) == 0) { 01628 struct stat buf; 01629 if (stat(fileName, &buf) == 0) { 01630 delta = int(buf.st_size % sizeof(tIndexTs)); 01631 if (delta) { 01632 delta = sizeof(tIndexTs) - delta; 01633 esyslog("ERROR: invalid file size (%"PRId64") in '%s'", buf.st_size, *fileName); 01634 } 01635 last = int((buf.st_size + delta) / sizeof(tIndexTs) - 1); 01636 if (!Record && last >= 0) { 01637 size = last + 1; 01638 index = MALLOC(tIndexTs, size); 01639 if (index) { 01640 f = open(fileName, O_RDONLY); 01641 if (f >= 0) { 01642 if (safe_read(f, index, size_t(buf.st_size)) != buf.st_size) { 01643 esyslog("ERROR: can't read from file '%s'", *fileName); 01644 free(index); 01645 index = NULL; 01646 close(f); 01647 f = -1; 01648 } 01649 // we don't close f here, see CatchUp()! 01650 else if (isPesRecording) 01651 ConvertFromPes(index, size); 01652 } 01653 else 01654 LOG_ERROR_STR(*fileName); 01655 } 01656 else 01657 esyslog("ERROR: can't allocate %zd bytes for index '%s'", size * sizeof(tIndexTs), *fileName); 01658 } 01659 } 01660 else 01661 LOG_ERROR; 01662 } 01663 else if (!Record) 01664 isyslog("missing index file %s", *fileName); 01665 if (Record) { 01666 if ((f = open(fileName, O_WRONLY | O_CREAT | O_APPEND, DEFFILEMODE)) >= 0) { 01667 if (delta) { 01668 esyslog("ERROR: padding index file with %d '0' bytes", delta); 01669 while (delta--) 01670 writechar(f, 0); 01671 } 01672 } 01673 else 01674 LOG_ERROR_STR(*fileName); 01675 } 01676 } 01677 } 01678 01679 cIndexFile::~cIndexFile() 01680 { 01681 if (f >= 0) 01682 close(f); 01683 free(index); 01684 delete indexFileGenerator; 01685 } 01686 01687 cString cIndexFile::IndexFileName(const char *FileName, bool IsPesRecording) 01688 { 01689 return cString::sprintf("%s%s", FileName, IsPesRecording ? INDEXFILESUFFIX ".vdr" : INDEXFILESUFFIX); 01690 } 01691 01692 void cIndexFile::ConvertFromPes(tIndexTs *IndexTs, int Count) 01693 { 01694 tIndexPes IndexPes; 01695 while (Count-- > 0) { 01696 memcpy(&IndexPes, IndexTs, sizeof(IndexPes)); 01697 IndexTs->offset = IndexPes.offset; 01698 IndexTs->independent = IndexPes.type == 1; // I_FRAME 01699 IndexTs->number = IndexPes.number; 01700 IndexTs++; 01701 } 01702 } 01703 01704 void cIndexFile::ConvertToPes(tIndexTs *IndexTs, int Count) 01705 { 01706 tIndexPes IndexPes; 01707 while (Count-- > 0) { 01708 IndexPes.offset = uint32_t(IndexTs->offset); 01709 IndexPes.type = uchar(IndexTs->independent ? 1 : 2); // I_FRAME : "not I_FRAME" (exact frame type doesn't matter) 01710 IndexPes.number = uchar(IndexTs->number); 01711 IndexPes.reserved = 0; 01712 memcpy(IndexTs, &IndexPes, sizeof(*IndexTs)); 01713 IndexTs++; 01714 } 01715 } 01716 01717 bool cIndexFile::CatchUp(int Index) 01718 { 01719 // returns true unless something really goes wrong, so that 'index' becomes NULL 01720 if (index && f >= 0) { 01721 cMutexLock MutexLock(&mutex); 01722 for (int i = 0; i <= MAXINDEXCATCHUP && (Index < 0 || Index >= last); i++) { 01723 struct stat buf; 01724 if (fstat(f, &buf) == 0) { 01725 if (time(NULL) - buf.st_mtime > MININDEXAGE) { 01726 // apparently the index file is not being written any more 01727 close(f); 01728 f = -1; 01729 break; 01730 } 01731 int newLast = int(buf.st_size / sizeof(tIndexTs) - 1); 01732 if (newLast > last) { 01733 int NewSize = size; 01734 if (NewSize <= newLast) { 01735 NewSize *= 2; 01736 if (NewSize <= newLast) 01737 NewSize = newLast + 1; 01738 } 01739 if (tIndexTs *NewBuffer = (tIndexTs *)realloc(index, NewSize * sizeof(tIndexTs))) { 01740 size = NewSize; 01741 index = NewBuffer; 01742 int offset = (last + 1) * sizeof(tIndexTs); 01743 int delta = (newLast - last) * sizeof(tIndexTs); 01744 if (lseek(f, offset, SEEK_SET) == offset) { 01745 if (safe_read(f, &index[last + 1], delta) != delta) { 01746 esyslog("ERROR: can't read from index"); 01747 free(index); 01748 index = NULL; 01749 close(f); 01750 f = -1; 01751 break; 01752 } 01753 if (isPesRecording) 01754 ConvertFromPes(&index[last + 1], newLast - last); 01755 last = newLast; 01756 } 01757 else 01758 LOG_ERROR_STR(*fileName); 01759 } 01760 else { 01761 esyslog("ERROR: can't realloc() index"); 01762 break; 01763 } 01764 } 01765 } 01766 else 01767 LOG_ERROR_STR(*fileName); 01768 if (Index < last) 01769 break; 01770 cCondWait::SleepMs(1000); 01771 } 01772 } 01773 return index != NULL; 01774 } 01775 01776 bool cIndexFile::Write(bool Independent, uint16_t FileNumber, off_t FileOffset) 01777 { 01778 if (f >= 0) { 01779 tIndexTs i(FileOffset, Independent, FileNumber); 01780 if (isPesRecording) 01781 ConvertToPes(&i, 1); 01782 if (safe_write(f, &i, sizeof(i)) < 0) { 01783 LOG_ERROR_STR(*fileName); 01784 close(f); 01785 f = -1; 01786 return false; 01787 } 01788 last++; 01789 } 01790 return f >= 0; 01791 } 01792 01793 bool cIndexFile::Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent, int *Length) 01794 { 01795 if (CatchUp(Index)) { 01796 if (Index >= 0 && Index < last) { 01797 *FileNumber = index[Index].number; 01798 *FileOffset = index[Index].offset; 01799 if (Independent) 01800 *Independent = index[Index].independent; 01801 if (Length) { 01802 uint16_t fn = index[Index + 1].number; 01803 off_t fo = index[Index + 1].offset; 01804 if (fn == *FileNumber) 01805 *Length = int(fo - *FileOffset); 01806 else 01807 *Length = -1; // this means "everything up to EOF" (the buffer's Read function will act accordingly) 01808 } 01809 return true; 01810 } 01811 } 01812 return false; 01813 } 01814 01815 int cIndexFile::GetNextIFrame(int Index, bool Forward, uint16_t *FileNumber, off_t *FileOffset, int *Length) 01816 { 01817 if (CatchUp()) { 01818 int d = Forward ? 1 : -1; 01819 for (;;) { 01820 Index += d; 01821 if (Index >= 0 && Index < last) { 01822 if (index[Index].independent) { 01823 uint16_t fn; 01824 if (!FileNumber) 01825 FileNumber = &fn; 01826 off_t fo; 01827 if (!FileOffset) 01828 FileOffset = &fo; 01829 *FileNumber = index[Index].number; 01830 *FileOffset = index[Index].offset; 01831 if (Length) { 01832 // all recordings end with a non-independent frame, so the following should be safe: 01833 uint16_t fn = index[Index + 1].number; 01834 off_t fo = index[Index + 1].offset; 01835 if (fn == *FileNumber) 01836 *Length = int(fo - *FileOffset); 01837 else { 01838 esyslog("ERROR: 'I' frame at end of file #%d", *FileNumber); 01839 *Length = -1; 01840 } 01841 } 01842 return Index; 01843 } 01844 } 01845 else 01846 break; 01847 } 01848 } 01849 return -1; 01850 } 01851 01852 int cIndexFile::Get(uint16_t FileNumber, off_t FileOffset) 01853 { 01854 if (CatchUp()) { 01855 //TODO implement binary search! 01856 int i; 01857 for (i = 0; i < last; i++) { 01858 if (index[i].number > FileNumber || (index[i].number == FileNumber) && off_t(index[i].offset) >= FileOffset) 01859 break; 01860 } 01861 return i; 01862 } 01863 return -1; 01864 } 01865 01866 bool cIndexFile::IsStillRecording() 01867 { 01868 return f >= 0; 01869 } 01870 01871 void cIndexFile::Delete(void) 01872 { 01873 if (*fileName) { 01874 dsyslog("deleting index file '%s'", *fileName); 01875 if (f >= 0) { 01876 close(f); 01877 f = -1; 01878 } 01879 unlink(fileName); 01880 } 01881 } 01882 01883 int cIndexFile::GetLength(const char *FileName, bool IsPesRecording) 01884 { 01885 struct stat buf; 01886 cString s = IndexFileName(FileName, IsPesRecording); 01887 if (*s && stat(s, &buf) == 0) 01888 return buf.st_size / (IsPesRecording ? sizeof(tIndexTs) : sizeof(tIndexPes)); 01889 return -1; 01890 } 01891 01892 bool GenerateIndex(const char *FileName) 01893 { 01894 if (DirectoryOk(FileName)) { 01895 cRecording Recording(FileName); 01896 if (Recording.Name()) { 01897 if (!Recording.IsPesRecording()) { 01898 cString IndexFileName = AddDirectory(FileName, INDEXFILESUFFIX); 01899 unlink(IndexFileName); 01900 cIndexFileGenerator *IndexFileGenerator = new cIndexFileGenerator(FileName); 01901 while (IndexFileGenerator->Active()) 01902 cCondWait::SleepMs(INDEXFILECHECKINTERVAL); 01903 if (access(IndexFileName, R_OK) == 0) 01904 return true; 01905 else 01906 fprintf(stderr, "cannot create '%s'\n", *IndexFileName); 01907 } 01908 else 01909 fprintf(stderr, "'%s' is not a TS recording\n", FileName); 01910 } 01911 else 01912 fprintf(stderr, "'%s' is not a recording\n", FileName); 01913 } 01914 else 01915 fprintf(stderr, "'%s' is not a directory\n", FileName); 01916 return false; 01917 } 01918 01919 // --- cFileName ------------------------------------------------------------- 01920 01921 #define MAXFILESPERRECORDINGPES 255 01922 #define RECORDFILESUFFIXPES "/%03d.vdr" 01923 #define MAXFILESPERRECORDINGTS 65535 01924 #define RECORDFILESUFFIXTS "/%05d.ts" 01925 #define RECORDFILESUFFIXLEN 20 // some additional bytes for safety... 01926 01927 cFileName::cFileName(const char *FileName, bool Record, bool Blocking, bool IsPesRecording) 01928 { 01929 file = NULL; 01930 fileNumber = 0; 01931 record = Record; 01932 blocking = Blocking; 01933 isPesRecording = IsPesRecording; 01934 // Prepare the file name: 01935 fileName = MALLOC(char, strlen(FileName) + RECORDFILESUFFIXLEN); 01936 if (!fileName) { 01937 esyslog("ERROR: can't copy file name '%s'", fileName); 01938 return; 01939 } 01940 strcpy(fileName, FileName); 01941 pFileNumber = fileName + strlen(fileName); 01942 SetOffset(1); 01943 } 01944 01945 cFileName::~cFileName() 01946 { 01947 Close(); 01948 free(fileName); 01949 } 01950 01951 bool cFileName::GetLastPatPmtVersions(int &PatVersion, int &PmtVersion) 01952 { 01953 if (fileName && !isPesRecording) { 01954 // Find the last recording file: 01955 int Number = 1; 01956 for (; Number <= MAXFILESPERRECORDINGTS + 1; Number++) { // +1 to correctly set Number in case there actually are that many files 01957 sprintf(pFileNumber, RECORDFILESUFFIXTS, Number); 01958 if (access(fileName, F_OK) != 0) { // file doesn't exist 01959 Number--; 01960 break; 01961 } 01962 } 01963 for (; Number > 0; Number--) { 01964 // Search for a PAT packet from the end of the file: 01965 cPatPmtParser PatPmtParser; 01966 sprintf(pFileNumber, RECORDFILESUFFIXTS, Number); 01967 int fd = open(fileName, O_RDONLY | O_LARGEFILE, DEFFILEMODE); 01968 if (fd >= 0) { 01969 off_t pos = lseek(fd, -TS_SIZE, SEEK_END); 01970 while (pos >= 0) { 01971 // Read and parse the PAT/PMT: 01972 uchar buf[TS_SIZE]; 01973 while (read(fd, buf, sizeof(buf)) == sizeof(buf)) { 01974 if (buf[0] == TS_SYNC_BYTE) { 01975 int Pid = TsPid(buf); 01976 if (Pid == 0) 01977 PatPmtParser.ParsePat(buf, sizeof(buf)); 01978 else if (Pid == PatPmtParser.PmtPid()) { 01979 PatPmtParser.ParsePmt(buf, sizeof(buf)); 01980 if (PatPmtParser.GetVersions(PatVersion, PmtVersion)) { 01981 close(fd); 01982 return true; 01983 } 01984 } 01985 else 01986 break; // PAT/PMT is always in one sequence 01987 } 01988 else 01989 return false; 01990 } 01991 pos = lseek(fd, pos - TS_SIZE, SEEK_SET); 01992 } 01993 close(fd); 01994 } 01995 else 01996 break; 01997 } 01998 } 01999 return false; 02000 } 02001 02002 cUnbufferedFile *cFileName::Open(void) 02003 { 02004 if (!file) { 02005 int BlockingFlag = blocking ? 0 : O_NONBLOCK; 02006 if (record) { 02007 dsyslog("recording to '%s'", fileName); 02008 file = OpenVideoFile(fileName, O_RDWR | O_CREAT | O_LARGEFILE | BlockingFlag); 02009 if (!file) 02010 LOG_ERROR_STR(fileName); 02011 } 02012 else { 02013 if (access(fileName, R_OK) == 0) { 02014 dsyslog("playing '%s'", fileName); 02015 file = cUnbufferedFile::Create(fileName, O_RDONLY | O_LARGEFILE | BlockingFlag); 02016 if (!file) 02017 LOG_ERROR_STR(fileName); 02018 } 02019 else if (errno != ENOENT) 02020 LOG_ERROR_STR(fileName); 02021 } 02022 } 02023 return file; 02024 } 02025 02026 void cFileName::Close(void) 02027 { 02028 if (file) { 02029 if (CloseVideoFile(file) < 0) 02030 LOG_ERROR_STR(fileName); 02031 file = NULL; 02032 } 02033 } 02034 02035 cUnbufferedFile *cFileName::SetOffset(int Number, off_t Offset) 02036 { 02037 if (fileNumber != Number) 02038 Close(); 02039 int MaxFilesPerRecording = isPesRecording ? MAXFILESPERRECORDINGPES : MAXFILESPERRECORDINGTS; 02040 if (0 < Number && Number <= MaxFilesPerRecording) { 02041 fileNumber = uint16_t(Number); 02042 sprintf(pFileNumber, isPesRecording ? RECORDFILESUFFIXPES : RECORDFILESUFFIXTS, fileNumber); 02043 if (record) { 02044 if (access(fileName, F_OK) == 0) { 02045 // file exists, check if it has non-zero size 02046 struct stat buf; 02047 if (stat(fileName, &buf) == 0) { 02048 if (buf.st_size != 0) 02049 return SetOffset(Number + 1); // file exists and has non zero size, let's try next suffix 02050 else { 02051 // zero size file, remove it 02052 dsyslog("cFileName::SetOffset: removing zero-sized file %s", fileName); 02053 unlink(fileName); 02054 } 02055 } 02056 else 02057 return SetOffset(Number + 1); // error with fstat - should not happen, just to be on the safe side 02058 } 02059 else if (errno != ENOENT) { // something serious has happened 02060 LOG_ERROR_STR(fileName); 02061 return NULL; 02062 } 02063 // found a non existing file suffix 02064 } 02065 if (Open() >= 0) { 02066 if (!record && Offset >= 0 && file && file->Seek(Offset, SEEK_SET) != Offset) { 02067 LOG_ERROR_STR(fileName); 02068 return NULL; 02069 } 02070 } 02071 return file; 02072 } 02073 esyslog("ERROR: max number of files (%d) exceeded", MaxFilesPerRecording); 02074 return NULL; 02075 } 02076 02077 off_t cFileName::MaxFileSize() { 02078 const int maxVideoFileSize = isPesRecording ? MAXVIDEOFILESIZEPES : MAXVIDEOFILESIZETS; 02079 const int setupMaxVideoFileSize = min(maxVideoFileSize, Setup.MaxVideoFileSize); 02080 const int maxFileNumber = isPesRecording ? 255 : 65535; 02081 02082 const off_t smallFiles = (maxFileNumber * off_t(maxVideoFileSize) - 1024 * Setup.MaxRecordingSize) 02083 / max(maxVideoFileSize - setupMaxVideoFileSize, 1); 02084 02085 if (fileNumber <= smallFiles) 02086 return MEGABYTE(off_t(setupMaxVideoFileSize)); 02087 02088 return MEGABYTE(off_t(maxVideoFileSize)); 02089 } 02090 02091 cUnbufferedFile *cFileName::NextFile(void) 02092 { 02093 return SetOffset(fileNumber + 1); 02094 } 02095 02096 // --- Index stuff ----------------------------------------------------------- 02097 02098 cString IndexToHMSF(int Index, bool WithFrame, double FramesPerSecond) 02099 { 02100 const char *Sign = ""; 02101 if (Index < 0) { 02102 Index = -Index; 02103 Sign = "-"; 02104 } 02105 double Seconds; 02106 int f = int(modf((Index + 0.5) / FramesPerSecond, &Seconds) * FramesPerSecond + 1); 02107 int s = int(Seconds); 02108 int m = s / 60 % 60; 02109 int h = s / 3600; 02110 s %= 60; 02111 return cString::sprintf(WithFrame ? "%s%d:%02d:%02d.%02d" : "%s%d:%02d:%02d", Sign, h, m, s, f); 02112 } 02113 02114 int HMSFToIndex(const char *HMSF, double FramesPerSecond) 02115 { 02116 int h, m, s, f = 1; 02117 int n = sscanf(HMSF, "%d:%d:%d.%d", &h, &m, &s, &f); 02118 if (n == 1) 02119 return h - 1; // plain frame number 02120 if (n >= 3) 02121 return int(round((h * 3600 + m * 60 + s) * FramesPerSecond)) + f - 1; 02122 return 0; 02123 } 02124 02125 int SecondsToFrames(int Seconds, double FramesPerSecond) 02126 { 02127 return int(round(Seconds * FramesPerSecond)); 02128 } 02129 02130 // --- ReadFrame ------------------------------------------------------------- 02131 02132 int ReadFrame(cUnbufferedFile *f, uchar *b, int Length, int Max) 02133 { 02134 if (Length == -1) 02135 Length = Max; // this means we read up to EOF (see cIndex) 02136 else if (Length > Max) { 02137 esyslog("ERROR: frame larger than buffer (%d > %d)", Length, Max); 02138 Length = Max; 02139 } 02140 int r = f->Read(b, Length); 02141 if (r < 0) 02142 LOG_ERROR; 02143 return r; 02144 }