2000-03-11 11:22:37 +01:00
/*
2000-10-03 10:34:48 +02:00
* recording . c : Recording file handling
2000-03-11 11:22:37 +01:00
*
2000-04-24 09:46:05 +02:00
* See the main source file ' vdr . c ' for copyright information and
2000-03-11 11:22:37 +01:00
* how to reach the author .
*
2014-03-16 11:11:04 +01:00
* $ Id : recording . c 3.18 2014 / 03 / 16 11 : 09 : 17 kls Exp $
2000-03-11 11:22:37 +01:00
*/
# include "recording.h"
2007-11-04 11:24:07 +01:00
# include <ctype.h>
2004-06-13 20:26:51 +02:00
# include <dirent.h>
2000-03-11 11:22:37 +01:00
# include <errno.h>
2000-07-24 16:43:04 +02:00
# include <fcntl.h>
2011-03-27 15:12:20 +02:00
# define __STDC_FORMAT_MACROS // Required for format specifiers
# include <inttypes.h>
2009-01-06 14:41:11 +01:00
# include <math.h>
2000-03-11 11:22:37 +01:00
# include <stdio.h>
# include <string.h>
2000-07-28 12:45:18 +02:00
# include <sys/stat.h>
2000-07-24 16:43:04 +02:00
# include <unistd.h>
2002-10-06 10:25:42 +02:00
# include "channels.h"
2013-10-10 13:13:30 +02:00
# include "cutter.h"
2002-01-26 15:25:37 +01:00
# include "i18n.h"
2000-03-11 11:22:37 +01:00
# include "interface.h"
2013-10-10 13:13:30 +02:00
# include "menu.h"
2009-05-24 15:11:28 +02:00
# include "remux.h"
2009-11-22 11:30:27 +01:00
# include "ringbuffer.h"
2004-05-16 10:35:36 +02:00
# include "skins.h"
2000-03-11 11:22:37 +01:00
# include "tools.h"
2000-07-29 15:21:42 +02:00
# include "videodir.h"
2000-03-11 11:22:37 +01:00
2005-05-22 10:43:10 +02:00
# define SUMMARYFALLBACK
2000-03-11 11:22:37 +01:00
# define RECEXT ".rec"
# define DELEXT ".del"
2002-07-27 12:55:14 +02:00
/* This was the original code, which works fine in a Linux only environment.
2004-06-13 20:26:51 +02:00
Unfortunately , because of Windows and its brain dead file system , we have
2002-07-27 12:55:14 +02:00
to use a more complicated approach , in order to allow users who have enabled
2008-05-03 10:19:31 +02:00
the - - vfat command line option to see their recordings even if they forget to
enable - - vfat when restarting VDR . . . Gee , do I hate Windows .
2002-07-27 12:55:14 +02:00
( kls 2002 - 07 - 27 )
# define DATAFORMAT "%4d-%02d-%02d.%02d:%02d.%02d.%02d" RECEXT
# define NAMEFORMAT "%s / %s / " DATAFORMAT
*/
2009-01-06 14:41:11 +01:00
# define DATAFORMATPES "%4d-%02d-%02d.%02d%*c%02d.%02d.%02d" RECEXT
# define NAMEFORMATPES "%s / %s / " "%4d-%02d-%02d.%02d.%02d.%02d.%02d" RECEXT
# define DATAFORMATTS "%4d-%02d-%02d.%02d.%02d.%d-%d" RECEXT
# define NAMEFORMATTS "%s / %s / " DATAFORMATTS
2000-03-11 11:22:37 +01:00
2009-01-06 14:41:11 +01:00
# define RESUMEFILESUFFIX " / resume%s%s"
2005-05-22 10:43:10 +02:00
# ifdef SUMMARYFALLBACK
# define SUMMARYFILESUFFIX " / summary.vdr"
# endif
2009-01-06 14:41:11 +01:00
# define INFOFILESUFFIX " / info"
# define MARKSFILESUFFIX " / marks"
2000-07-24 16:43:04 +02:00
2012-06-09 14:32:29 +02:00
# define SORTMODEFILE ".sort"
2000-03-11 11:22:37 +01:00
# define MINDISKSPACE 1024 // MB
2005-12-18 12:14:11 +01:00
# define REMOVECHECKDELTA 60 // seconds between checks for removing deleted files
2006-01-03 11:39:48 +01:00
# define DELETEDLIFETIME 300 // seconds after which a deleted recording will be actually removed
2002-01-26 15:25:37 +01:00
# define DISKCHECKDELTA 100 // seconds between checks for free disk space
2001-06-02 10:47:40 +02:00
# define REMOVELATENCY 10 // seconds to wait until next check after removing a file
2011-02-27 13:40:43 +01:00
# define MARKSUPDATEDELTA 10 // seconds between checks for updating editing marks
2011-08-21 13:47:07 +02:00
# define MININDEXAGE 3600 // seconds before an index file is considered no longer to be written
2001-02-04 12:36:32 +01:00
2006-02-12 11:46:44 +01:00
# define MAX_LINK_LEVEL 6
2013-10-12 13:48:32 +02:00
# define LIMIT_SECS_PER_MB_RADIO 5 // radio recordings typically have more than this
2013-03-11 11:07:59 +01:00
int DirectoryPathMax = PATH_MAX - 1 ;
2013-02-08 09:24:55 +01:00
int DirectoryNameMax = NAME_MAX ;
bool DirectoryEncoding = false ;
2009-01-18 11:10:29 +01:00
int InstanceId = 0 ;
2005-09-03 13:35:55 +02:00
2005-12-18 10:41:26 +01:00
cRecordings DeletedRecordings ( true ) ;
2013-12-24 14:41:09 +01:00
static cRecordings VanishedRecordings ;
2005-09-25 11:00:57 +02:00
2005-12-28 12:21:57 +01:00
// --- cRemoveDeletedRecordingsThread ----------------------------------------
class cRemoveDeletedRecordingsThread : public cThread {
protected :
virtual void Action ( void ) ;
public :
cRemoveDeletedRecordingsThread ( void ) ;
} ;
cRemoveDeletedRecordingsThread : : cRemoveDeletedRecordingsThread ( void )
2012-10-04 12:32:31 +02:00
: cThread ( " remove deleted recordings " , true )
2001-02-04 12:36:32 +01:00
{
2005-12-28 12:21:57 +01:00
}
void cRemoveDeletedRecordingsThread : : Action ( void )
{
// Make sure only one instance of VDR does this:
2013-09-11 12:20:37 +02:00
cLockFile LockFile ( cVideoDirectory : : Name ( ) ) ;
2005-12-28 12:21:57 +01:00
if ( LockFile . Lock ( ) ) {
2006-03-19 14:12:57 +01:00
bool deleted = false ;
2005-09-25 11:00:57 +02:00
cThreadLock DeletedRecordingsLock ( & DeletedRecordings ) ;
2005-12-18 13:38:30 +01:00
for ( cRecording * r = DeletedRecordings . First ( ) ; r ; ) {
2012-10-03 12:58:02 +02:00
if ( cIoThrottle : : Engaged ( ) )
return ;
2011-08-21 11:34:30 +02:00
if ( r - > Deleted ( ) & & time ( NULL ) - r - > Deleted ( ) > DELETEDLIFETIME ) {
2005-12-28 12:21:57 +01:00
cRecording * next = DeletedRecordings . Next ( r ) ;
r - > Remove ( ) ;
DeletedRecordings . Del ( r ) ;
r = next ;
2006-03-19 14:12:57 +01:00
deleted = true ;
2005-12-28 12:21:57 +01:00
continue ;
2005-12-18 12:14:11 +01:00
}
2005-12-18 13:38:30 +01:00
r = DeletedRecordings . Next ( r ) ;
2005-12-18 12:14:11 +01:00
}
2012-09-30 13:05:14 +02:00
if ( deleted ) {
const char * IgnoreFiles [ ] = { SORTMODEFILE , NULL } ;
2013-09-11 12:20:37 +02:00
cVideoDirectory : : RemoveEmptyVideoDirectories ( IgnoreFiles ) ;
2012-09-30 13:05:14 +02:00
}
2005-12-28 12:21:57 +01:00
}
}
static cRemoveDeletedRecordingsThread RemoveDeletedRecordingsThread ;
// ---
void RemoveDeletedRecordings ( void )
{
static time_t LastRemoveCheck = 0 ;
if ( time ( NULL ) - LastRemoveCheck > REMOVECHECKDELTA ) {
2009-04-13 12:29:10 +02:00
if ( ! RemoveDeletedRecordingsThread . Active ( ) ) {
cThreadLock DeletedRecordingsLock ( & DeletedRecordings ) ;
for ( cRecording * r = DeletedRecordings . First ( ) ; r ; r = DeletedRecordings . Next ( r ) ) {
2011-08-21 11:34:30 +02:00
if ( r - > Deleted ( ) & & time ( NULL ) - r - > Deleted ( ) > DELETEDLIFETIME ) {
2009-04-13 12:29:10 +02:00
RemoveDeletedRecordingsThread . Start ( ) ;
break ;
}
}
}
2001-02-04 12:36:32 +01:00
LastRemoveCheck = time ( NULL ) ;
}
}
2000-03-11 11:22:37 +01:00
2006-01-20 17:19:46 +01:00
void AssertFreeDiskSpace ( int Priority , bool Force )
2000-03-11 11:22:37 +01:00
{
2005-12-18 13:10:52 +01:00
static cMutex Mutex ;
cMutexLock MutexLock ( & Mutex ) ;
2000-03-11 11:22:37 +01:00
// With every call to this function we try to actually remove
// a file, or mark a file for removal ("delete" it), so that
// it will get removed during the next call.
2000-04-15 17:38:11 +02:00
static time_t LastFreeDiskCheck = 0 ;
2003-08-17 09:18:40 +02:00
int Factor = ( Priority = = - 1 ) ? 10 : 1 ;
2006-01-20 17:19:46 +01:00
if ( Force | | time ( NULL ) - LastFreeDiskCheck > DISKCHECKDELTA / Factor ) {
2013-09-11 12:20:37 +02:00
if ( ! cVideoDirectory : : VideoFileSpaceAvailable ( MINDISKSPACE ) ) {
2001-09-30 10:38:06 +02:00
// Make sure only one instance of VDR does this:
2013-09-11 12:20:37 +02:00
cLockFile LockFile ( cVideoDirectory : : Name ( ) ) ;
2001-09-30 10:38:06 +02:00
if ( ! LockFile . Lock ( ) )
return ;
2000-04-15 17:38:11 +02:00
// Remove the oldest file that has been "deleted":
2002-05-13 16:35:49 +02:00
isyslog ( " low disk space while recording, trying to remove a deleted recording... " ) ;
2005-09-25 11:00:57 +02:00
cThreadLock DeletedRecordingsLock ( & DeletedRecordings ) ;
if ( DeletedRecordings . Count ( ) ) {
2004-06-13 20:26:51 +02:00
cRecording * r = DeletedRecordings . First ( ) ;
2008-02-16 13:38:22 +01:00
cRecording * r0 = NULL ;
2000-04-15 17:38:11 +02:00
while ( r ) {
2012-06-03 10:03:55 +02:00
if ( r - > IsOnVideoDirectoryFileSystem ( ) ) { // only remove recordings that will actually increase the free video disk space
2011-08-21 11:34:30 +02:00
if ( ! r0 | | r - > Start ( ) < r0 - > Start ( ) )
2008-02-16 13:38:22 +01:00
r0 = r ;
}
2004-06-13 20:26:51 +02:00
r = DeletedRecordings . Next ( r ) ;
2000-04-15 17:38:11 +02:00
}
2009-01-16 15:59:47 +01:00
if ( r0 ) {
if ( r0 - > Remove ( ) )
LastFreeDiskCheck + = REMOVELATENCY / Factor ;
2005-09-25 11:00:57 +02:00
DeletedRecordings . Del ( r0 ) ;
2000-04-15 17:38:11 +02:00
return ;
2000-11-18 16:26:50 +01:00
}
2000-04-15 17:38:11 +02:00
}
2008-02-16 13:38:22 +01:00
else {
// DeletedRecordings was empty, so to be absolutely sure there are no
// deleted recordings we need to double check:
DeletedRecordings . Update ( true ) ;
if ( DeletedRecordings . Count ( ) )
return ; // the next call will actually remove it
}
2000-04-15 17:38:11 +02:00
// No "deleted" files to remove, so let's see if we can delete a recording:
2013-08-21 13:56:33 +02:00
if ( Priority > 0 ) {
isyslog ( " ...no deleted recording found, trying to delete an old recording... " ) ;
cThreadLock RecordingsLock ( & Recordings ) ;
if ( Recordings . Count ( ) ) {
cRecording * r = Recordings . First ( ) ;
cRecording * r0 = NULL ;
while ( r ) {
if ( r - > IsOnVideoDirectoryFileSystem ( ) ) { // only delete recordings that will actually increase the free video disk space
if ( ! r - > IsEdited ( ) & & r - > Lifetime ( ) < MAXLIFETIME ) { // edited recordings and recordings with MAXLIFETIME live forever
if ( ( r - > Lifetime ( ) = = 0 & & Priority > r - > Priority ( ) ) | | // the recording has no guaranteed lifetime and the new recording has higher priority
( r - > Lifetime ( ) > 0 & & ( time ( NULL ) - r - > Start ( ) ) / SECSINDAY > = r - > Lifetime ( ) ) ) { // the recording's guaranteed lifetime has expired
if ( r0 ) {
if ( r - > Priority ( ) < r0 - > Priority ( ) | | ( r - > Priority ( ) = = r0 - > Priority ( ) & & r - > Start ( ) < r0 - > Start ( ) ) )
r0 = r ; // in any case we delete the one with the lowest priority (or the older one in case of equal priorities)
}
else
r0 = r ;
2008-02-16 13:38:22 +01:00
}
2001-06-12 15:32:47 +02:00
}
2000-04-15 17:38:11 +02:00
}
2013-08-21 13:56:33 +02:00
r = Recordings . Next ( r ) ;
2000-03-11 11:22:37 +01:00
}
2013-08-21 13:56:33 +02:00
if ( r0 & & r0 - > Delete ( ) ) {
Recordings . Del ( r0 ) ;
return ;
2000-03-11 11:22:37 +01:00
}
2004-06-13 20:26:51 +02:00
}
2013-08-21 13:56:33 +02:00
// Unable to free disk space, but there's nothing we can do about that...
isyslog ( " ...no old recording found, giving up " ) ;
2000-04-15 17:38:11 +02:00
}
2013-08-21 13:56:33 +02:00
else
isyslog ( " ...no deleted recording found, priority %d too low to trigger deleting an old recording " , Priority ) ;
2005-12-18 12:43:17 +01:00
Skins . QueueMessage ( mtWarning , tr ( " Low disk space! " ) , 5 , - 1 ) ;
2000-03-11 11:22:37 +01:00
}
2000-05-13 16:16:56 +02:00
LastFreeDiskCheck = time ( NULL ) ;
2000-03-11 11:22:37 +01:00
}
}
2013-12-24 14:41:09 +01:00
// --- Clear vanished recordings ---------------------------------------------
void ClearVanishedRecordings ( void )
{
cThreadLock RecordingsLock ( & Recordings ) ; // yes, it *is* Recordings!
VanishedRecordings . Clear ( ) ;
}
2006-04-17 11:00:00 +02:00
// --- cResumeFile -----------------------------------------------------------
2001-02-11 11:04:41 +01:00
2009-01-06 14:41:11 +01:00
cResumeFile : : cResumeFile ( const char * FileName , bool IsPesRecording )
2001-02-11 11:04:41 +01:00
{
2009-01-06 14:41:11 +01:00
isPesRecording = IsPesRecording ;
const char * Suffix = isPesRecording ? RESUMEFILESUFFIX " .vdr " : RESUMEFILESUFFIX ;
fileName = MALLOC ( char , strlen ( FileName ) + strlen ( Suffix ) + 1 ) ;
2001-02-11 11:04:41 +01:00
if ( fileName ) {
strcpy ( fileName , FileName ) ;
2009-01-06 14:41:11 +01:00
sprintf ( fileName + strlen ( fileName ) , Suffix , Setup . ResumeID ? " . " : " " , Setup . ResumeID ? * itoa ( Setup . ResumeID ) : " " ) ;
2001-02-11 11:04:41 +01:00
}
else
2002-05-13 16:35:49 +02:00
esyslog ( " ERROR: can't allocate memory for resume file name " ) ;
2001-02-11 11:04:41 +01:00
}
cResumeFile : : ~ cResumeFile ( )
{
2002-08-11 13:32:23 +02:00
free ( fileName ) ;
2001-02-11 11:04:41 +01:00
}
int cResumeFile : : Read ( void )
{
int resume = - 1 ;
if ( fileName ) {
2003-05-24 11:22:34 +02:00
struct stat st ;
if ( stat ( fileName , & st ) = = 0 ) {
if ( ( st . st_mode & S_IWUSR ) = = 0 ) // no write access, assume no resume
return - 1 ;
}
2009-01-06 14:41:11 +01:00
if ( isPesRecording ) {
int f = open ( fileName , O_RDONLY ) ;
if ( f > = 0 ) {
if ( safe_read ( f , & resume , sizeof ( resume ) ) ! = sizeof ( resume ) ) {
resume = - 1 ;
LOG_ERROR_STR ( fileName ) ;
}
close ( f ) ;
}
else if ( errno ! = ENOENT )
2001-02-11 11:04:41 +01:00
LOG_ERROR_STR ( fileName ) ;
2009-01-06 14:41:11 +01:00
}
else {
FILE * f = fopen ( fileName , " r " ) ;
if ( f ) {
cReadLine ReadLine ;
char * s ;
int line = 0 ;
while ( ( s = ReadLine . Read ( f ) ) ! = NULL ) {
+ + line ;
char * t = skipspace ( s + 1 ) ;
switch ( * s ) {
case ' I ' : resume = atoi ( t ) ;
break ;
2009-12-06 12:57:45 +01:00
default : ;
2009-01-06 14:41:11 +01:00
}
}
fclose ( f ) ;
2001-02-11 11:04:41 +01:00
}
2009-01-06 14:41:11 +01:00
else if ( errno ! = ENOENT )
LOG_ERROR_STR ( fileName ) ;
2001-02-11 11:04:41 +01:00
}
}
return resume ;
}
bool cResumeFile : : Save ( int Index )
{
if ( fileName ) {
2009-01-06 14:41:11 +01:00
if ( isPesRecording ) {
int f = open ( fileName , O_WRONLY | O_CREAT | O_TRUNC , DEFFILEMODE ) ;
if ( f > = 0 ) {
if ( safe_write ( f , & Index , sizeof ( Index ) ) < 0 )
LOG_ERROR_STR ( fileName ) ;
close ( f ) ;
Recordings . ResetResume ( fileName ) ;
return true ;
}
}
else {
FILE * f = fopen ( fileName , " w " ) ;
if ( f ) {
fprintf ( f , " I %d \n " , Index ) ;
fclose ( f ) ;
2009-01-30 16:48:40 +01:00
Recordings . ResetResume ( fileName ) ;
2009-01-06 14:41:11 +01:00
}
else
2001-02-11 11:04:41 +01:00
LOG_ERROR_STR ( fileName ) ;
return true ;
}
}
return false ;
}
void cResumeFile : : Delete ( void )
{
if ( fileName ) {
2011-04-17 13:58:29 +02:00
if ( remove ( fileName ) = = 0 )
Recordings . ResetResume ( fileName ) ;
else if ( errno ! = ENOENT )
2001-02-11 11:04:41 +01:00
LOG_ERROR_STR ( fileName ) ;
}
}
2005-05-16 14:45:11 +02:00
// --- cRecordingInfo --------------------------------------------------------
2006-02-18 16:03:40 +01:00
cRecordingInfo : : cRecordingInfo ( const cChannel * Channel , const cEvent * Event )
2005-05-16 14:45:11 +02:00
{
2006-02-18 16:03:40 +01:00
channelID = Channel ? Channel - > GetChannelID ( ) : tChannelID : : InvalidID ;
2007-06-17 13:13:47 +02:00
channelName = Channel ? strdup ( Channel - > Name ( ) ) : NULL ;
2006-02-19 13:27:51 +01:00
ownEvent = Event ? NULL : new cEvent ( 0 ) ;
event = ownEvent ? ownEvent : Event ;
2006-02-25 12:09:22 +01:00
aux = NULL ;
2009-01-06 14:41:11 +01:00
framesPerSecond = DEFAULTFRAMESPERSECOND ;
priority = MAXPRIORITY ;
lifetime = MAXLIFETIME ;
fileName = NULL ;
2006-02-19 13:27:51 +01:00
if ( Channel ) {
// Since the EPG data's component records can carry only a single
// language code, let's see whether the channel's PID data has
// more information:
cComponents * Components = ( cComponents * ) event - > Components ( ) ;
if ( ! Components )
Components = new cComponents ;
for ( int i = 0 ; i < MAXAPIDS ; i + + ) {
const char * s = Channel - > Alang ( i ) ;
if ( * s ) {
tComponent * Component = Components - > GetComponent ( i , 2 , 3 ) ;
if ( ! Component )
Components - > SetComponent ( Components - > NumComponents ( ) , 2 , 3 , s , NULL ) ;
else if ( strlen ( s ) > strlen ( Component - > language ) )
strn0cpy ( Component - > language , s , sizeof ( Component - > language ) ) ;
2006-02-18 16:03:40 +01:00
}
2006-02-19 13:27:51 +01:00
}
// There's no "multiple languages" for Dolby Digital tracks, but
// we do the same procedure here, too, in case there is no component
// information at all:
for ( int i = 0 ; i < MAXDPIDS ; i + + ) {
const char * s = Channel - > Dlang ( i ) ;
if ( * s ) {
2008-05-01 15:41:04 +02:00
tComponent * Component = Components - > GetComponent ( i , 4 , 0 ) ; // AC3 component according to the DVB standard
if ( ! Component )
Component = Components - > GetComponent ( i , 2 , 5 ) ; // fallback "Dolby" component according to the "Premiere pseudo standard"
2006-02-19 13:27:51 +01:00
if ( ! Component )
Components - > SetComponent ( Components - > NumComponents ( ) , 2 , 5 , s , NULL ) ;
else if ( strlen ( s ) > strlen ( Component - > language ) )
strn0cpy ( Component - > language , s , sizeof ( Component - > language ) ) ;
2006-02-18 16:03:40 +01:00
}
2006-02-19 13:27:51 +01:00
}
2007-10-12 14:52:30 +02:00
// The same applies to subtitles:
for ( int i = 0 ; i < MAXSPIDS ; i + + ) {
const char * s = Channel - > Slang ( i ) ;
if ( * s ) {
tComponent * Component = Components - > GetComponent ( i , 3 , 3 ) ;
if ( ! Component )
Components - > SetComponent ( Components - > NumComponents ( ) , 3 , 3 , s , NULL ) ;
else if ( strlen ( s ) > strlen ( Component - > language ) )
strn0cpy ( Component - > language , s , sizeof ( Component - > language ) ) ;
}
}
2006-02-19 13:27:51 +01:00
if ( Components ! = event - > Components ( ) )
( ( cEvent * ) event ) - > SetComponents ( Components ) ;
2006-02-18 16:03:40 +01:00
}
2005-05-16 14:45:11 +02:00
}
2009-01-06 14:41:11 +01:00
cRecordingInfo : : cRecordingInfo ( const char * FileName )
{
channelID = tChannelID : : InvalidID ;
channelName = NULL ;
ownEvent = new cEvent ( 0 ) ;
event = ownEvent ;
aux = NULL ;
framesPerSecond = DEFAULTFRAMESPERSECOND ;
priority = MAXPRIORITY ;
lifetime = MAXLIFETIME ;
fileName = strdup ( cString : : sprintf ( " %s%s " , FileName , INFOFILESUFFIX ) ) ;
}
2005-05-16 14:45:11 +02:00
cRecordingInfo : : ~ cRecordingInfo ( )
{
delete ownEvent ;
2006-02-25 12:09:22 +01:00
free ( aux ) ;
2007-06-17 13:13:47 +02:00
free ( channelName ) ;
2009-01-06 14:41:11 +01:00
free ( fileName ) ;
2005-05-16 14:45:11 +02:00
}
2005-05-22 10:43:10 +02:00
void cRecordingInfo : : SetData ( const char * Title , const char * ShortText , const char * Description )
2005-05-22 09:13:26 +02:00
{
2005-05-22 10:43:10 +02:00
if ( ! isempty ( Title ) )
2005-05-22 09:13:26 +02:00
( ( cEvent * ) event ) - > SetTitle ( Title ) ;
2005-05-22 10:43:10 +02:00
if ( ! isempty ( ShortText ) )
( ( cEvent * ) event ) - > SetShortText ( ShortText ) ;
2005-05-22 09:13:26 +02:00
if ( ! isempty ( Description ) )
( ( cEvent * ) event ) - > SetDescription ( Description ) ;
}
2006-02-25 12:09:22 +01:00
void cRecordingInfo : : SetAux ( const char * Aux )
{
free ( aux ) ;
aux = Aux ? strdup ( Aux ) : NULL ;
}
2009-01-06 14:41:11 +01:00
void cRecordingInfo : : SetFramesPerSecond ( double FramesPerSecond )
{
framesPerSecond = FramesPerSecond ;
}
2013-10-10 13:13:30 +02:00
void cRecordingInfo : : SetFileName ( const char * FileName )
{
bool IsPesRecording = fileName & & endswith ( fileName , " .vdr " ) ;
free ( fileName ) ;
fileName = strdup ( cString : : sprintf ( " %s%s " , FileName , IsPesRecording ? INFOFILESUFFIX " .vdr " : INFOFILESUFFIX ) ) ;
}
2005-05-16 14:45:11 +02:00
bool cRecordingInfo : : Read ( FILE * f )
{
if ( ownEvent ) {
cReadLine ReadLine ;
char * s ;
2005-10-09 13:13:36 +02:00
int line = 0 ;
2005-05-16 14:45:11 +02:00
while ( ( s = ReadLine . Read ( f ) ) ! = NULL ) {
2005-10-09 13:13:36 +02:00
+ + line ;
2005-05-16 14:45:11 +02:00
char * t = skipspace ( s + 1 ) ;
switch ( * s ) {
case ' C ' : {
char * p = strchr ( t , ' ' ) ;
2007-06-17 13:13:47 +02:00
if ( p ) {
free ( channelName ) ;
2008-02-15 14:57:48 +01:00
channelName = strdup ( compactspace ( p ) ) ;
2005-05-16 14:45:11 +02:00
* p = 0 ; // strips optional channel name
2007-06-17 13:13:47 +02:00
}
2005-05-16 14:45:11 +02:00
if ( * t )
2005-05-28 09:53:54 +02:00
channelID = tChannelID : : FromString ( t ) ;
2005-05-16 14:45:11 +02:00
}
break ;
2006-02-25 17:05:48 +01:00
case ' E ' : {
unsigned int EventID ;
time_t StartTime ;
int Duration ;
unsigned int TableID = 0 ;
unsigned int Version = 0xFF ;
int n = sscanf ( t , " %u %ld %d %X %X " , & EventID , & StartTime , & Duration , & TableID , & Version ) ;
if ( n > = 3 & & n < = 5 ) {
ownEvent - > SetEventID ( EventID ) ;
ownEvent - > SetStartTime ( StartTime ) ;
ownEvent - > SetDuration ( Duration ) ;
2009-12-06 12:57:45 +01:00
ownEvent - > SetTableID ( uchar ( TableID ) ) ;
ownEvent - > SetVersion ( uchar ( Version ) ) ;
2006-02-25 17:05:48 +01:00
}
}
break ;
2012-12-06 10:29:23 +01:00
case ' F ' : framesPerSecond = atod ( t ) ;
2009-01-06 14:41:11 +01:00
break ;
case ' L ' : lifetime = atoi ( t ) ;
break ;
case ' P ' : priority = atoi ( t ) ;
break ;
2006-02-25 12:09:22 +01:00
case ' @ ' : free ( aux ) ;
aux = strdup ( t ) ;
break ;
2006-02-26 12:03:28 +01:00
case ' # ' : break ; // comments are ignored
2005-10-09 13:13:36 +02:00
default : if ( ! ownEvent - > Parse ( s ) ) {
esyslog ( " ERROR: EPG data problem in line %d " , line ) ;
2005-05-16 14:45:11 +02:00
return false ;
2005-10-09 13:13:36 +02:00
}
2005-05-16 14:45:11 +02:00
break ;
}
}
return true ;
}
return false ;
}
bool cRecordingInfo : : Write ( FILE * f , const char * Prefix ) const
{
2005-05-28 09:53:54 +02:00
if ( channelID . Valid ( ) )
2007-06-17 13:13:47 +02:00
fprintf ( f , " %sC %s%s%s \n " , Prefix , * channelID . ToString ( ) , channelName ? " " : " " , channelName ? channelName : " " ) ;
2005-05-16 14:45:11 +02:00
event - > Dump ( f , Prefix , true ) ;
2012-12-06 10:29:23 +01:00
fprintf ( f , " %sF %s \n " , Prefix , * dtoa ( framesPerSecond , " %.10g " ) ) ;
2009-01-06 14:41:11 +01:00
fprintf ( f , " %sP %d \n " , Prefix , priority ) ;
fprintf ( f , " %sL %d \n " , Prefix , lifetime ) ;
2006-02-25 12:09:22 +01:00
if ( aux )
fprintf ( f , " %s@ %s \n " , Prefix , aux ) ;
2005-05-16 14:45:11 +02:00
return true ;
}
2009-01-06 14:41:11 +01:00
bool cRecordingInfo : : Read ( void )
{
bool Result = false ;
if ( fileName ) {
FILE * f = fopen ( fileName , " r " ) ;
if ( f ) {
if ( Read ( f ) )
Result = true ;
else
esyslog ( " ERROR: EPG data problem in file %s " , fileName ) ;
fclose ( f ) ;
}
else if ( errno ! = ENOENT )
LOG_ERROR_STR ( fileName ) ;
}
return Result ;
}
bool cRecordingInfo : : Write ( void ) const
{
bool Result = false ;
if ( fileName ) {
cSafeFile f ( fileName ) ;
if ( f . Open ( ) ) {
if ( Write ( f ) )
Result = true ;
f . Close ( ) ;
}
else
LOG_ERROR_STR ( fileName ) ;
}
return Result ;
}
2000-03-11 11:22:37 +01:00
// --- cRecording ------------------------------------------------------------
2002-01-20 14:05:28 +01:00
# define RESUME_NOT_INITIALIZED (-2)
2001-06-16 10:36:13 +02:00
struct tCharExchange { char a ; char b ; } ;
tCharExchange CharExchange [ ] = {
2010-01-17 12:08:03 +01:00
{ FOLDERDELIMCHAR , ' / ' } ,
{ ' / ' , FOLDERDELIMCHAR } ,
2001-06-16 10:36:13 +02:00
{ ' ' , ' _ ' } ,
2006-04-16 12:55:28 +02:00
// backwards compatibility:
{ ' \' ' , ' \' ' } ,
2001-06-16 10:36:13 +02:00
{ ' \' ' , ' \x01 ' } ,
{ ' / ' , ' \x02 ' } ,
{ 0 , 0 }
} ;
2013-02-08 09:24:55 +01:00
const char * InvalidChars = " \" \\ /:*?|<># " ;
bool NeedsConversion ( const char * p )
{
return DirectoryEncoding & &
( strchr ( InvalidChars , * p ) // characters that can't be part of a Windows file/directory name
| | * p = = ' . ' & & ( ! * ( p + 1 ) | | * ( p + 1 ) = = FOLDERDELIMCHAR ) ) ; // Windows can't handle '.' at the end of file/directory names
}
2005-09-25 14:31:23 +02:00
char * ExchangeChars ( char * s , bool ToFileSystem )
2001-06-16 10:36:13 +02:00
{
2001-09-02 10:28:20 +02:00
char * p = s ;
while ( * p ) {
2013-02-08 09:24:55 +01:00
if ( DirectoryEncoding ) {
// Some file systems can't handle all characters, so we
2005-09-03 13:35:55 +02:00
// have to take extra efforts to encode/decode them:
if ( ToFileSystem ) {
switch ( * p ) {
// characters that can be mapped to other characters:
case ' ' : * p = ' _ ' ; break ;
2010-01-17 12:08:03 +01:00
case FOLDERDELIMCHAR : * p = ' / ' ; break ;
2013-04-11 08:17:17 +02:00
case ' / ' : * p = FOLDERDELIMCHAR ; break ;
2005-09-03 13:35:55 +02:00
// characters that have to be encoded:
default :
2013-02-08 09:24:55 +01:00
if ( NeedsConversion ( p ) ) {
2005-09-03 13:35:55 +02:00
int l = p - s ;
2011-02-25 15:25:42 +01:00
if ( char * NewBuffer = ( char * ) realloc ( s , strlen ( s ) + 10 ) ) {
s = NewBuffer ;
p = s + l ;
char buf [ 4 ] ;
sprintf ( buf , " #%02X " , ( unsigned char ) * p ) ;
memmove ( p + 2 , p , strlen ( p ) + 1 ) ;
strncpy ( p , buf , 3 ) ;
p + = 2 ;
}
else
esyslog ( " ERROR: out of memory " ) ;
2005-09-03 13:35:55 +02:00
}
}
}
else {
switch ( * p ) {
// mapped characters:
case ' _ ' : * p = ' ' ; break ;
2013-04-11 08:17:17 +02:00
case FOLDERDELIMCHAR : * p = ' / ' ; break ;
2010-01-17 12:08:03 +01:00
case ' / ' : * p = FOLDERDELIMCHAR ; break ;
2007-06-16 08:59:39 +02:00
// encoded characters:
2005-09-03 13:35:55 +02:00
case ' # ' : {
2007-11-04 11:24:07 +01:00
if ( strlen ( p ) > 2 & & isxdigit ( * ( p + 1 ) ) & & isxdigit ( * ( p + 2 ) ) ) {
2005-09-03 13:35:55 +02:00
char buf [ 3 ] ;
sprintf ( buf , " %c%c " , * ( p + 1 ) , * ( p + 2 ) ) ;
2009-12-06 12:57:45 +01:00
uchar c = uchar ( strtol ( buf , NULL , 16 ) ) ;
2007-11-04 11:24:07 +01:00
if ( c ) {
* p = c ;
memmove ( p + 1 , p + 3 , strlen ( p ) - 2 ) ;
}
2005-09-03 13:35:55 +02:00
}
}
break ;
// backwards compatibility:
case ' \x01 ' : * p = ' \' ' ; break ;
case ' \x02 ' : * p = ' / ' ; break ;
case ' \x03 ' : * p = ' : ' ; break ;
2009-12-06 12:57:45 +01:00
default : ;
2005-09-03 13:35:55 +02:00
}
}
2002-02-10 14:21:36 +01:00
}
else {
2005-09-03 13:35:55 +02:00
for ( struct tCharExchange * ce = CharExchange ; ce - > a & & ce - > b ; ce + + ) {
if ( * p = = ( ToFileSystem ? ce - > a : ce - > b ) ) {
* p = ToFileSystem ? ce - > b : ce - > a ;
2002-02-10 14:21:36 +01:00
break ;
2005-09-03 13:35:55 +02:00
}
2001-09-02 10:28:20 +02:00
}
2005-09-03 13:35:55 +02:00
}
2001-09-02 10:28:20 +02:00
p + + ;
}
2001-06-16 10:36:13 +02:00
return s ;
}
2013-02-08 09:24:55 +01:00
char * LimitNameLengths ( char * s , int PathMax , int NameMax )
{
// Limits the total length of the directory path in 's' to PathMax, and each
// individual directory name to NameMax. The lengths of characters that need
// conversion when using 's' as a file name are taken into account accordingly.
// If a directory name exceeds NameMax, it will be truncated. If the whole
// directory path exceeds PathMax, individual directory names will be shortened
// (from right to left) until the limit is met, or until the currently handled
// directory name consists of only a single character. All operations are performed
// directly on the given 's', which may become shorter (but never longer) than
// the original value.
// Returns a pointer to 's'.
int Length = strlen ( s ) ;
int PathLength = 0 ;
// Collect the resulting lengths of each character:
bool NameTooLong = false ;
int8_t a [ Length ] ;
int n = 0 ;
int NameLength = 0 ;
for ( char * p = s ; * p ; p + + ) {
if ( * p = = FOLDERDELIMCHAR ) {
a [ n ] = - 1 ; // FOLDERDELIMCHAR is a single character, neg. sign marks it
NameTooLong | = NameLength > NameMax ;
NameLength = 0 ;
PathLength + = 1 ;
}
else if ( NeedsConversion ( p ) ) {
a [ n ] = 3 ; // "#xx"
NameLength + = 3 ;
PathLength + = 3 ;
}
else {
int8_t l = Utf8CharLen ( p ) ;
a [ n ] = l ;
NameLength + = l ;
PathLength + = l ;
while ( l - - > 1 ) {
a [ + + n ] = 0 ;
p + + ;
}
}
n + + ;
}
NameTooLong | = NameLength > NameMax ;
// Limit names to NameMax:
if ( NameTooLong ) {
while ( n > 0 ) {
// Calculate the length of the current name:
int NameLength = 0 ;
int i = n ;
int b = i ;
while ( i - - > 0 & & a [ i ] > = 0 ) {
NameLength + = a [ i ] ;
b = i ;
}
// Shorten the name if necessary:
if ( NameLength > NameMax ) {
int l = 0 ;
i = n ;
while ( i - - > 0 & & a [ i ] > = 0 ) {
l + = a [ i ] ;
if ( NameLength - l < = NameMax ) {
memmove ( s + i , s + n , Length - n + 1 ) ;
memmove ( a + i , a + n , Length - n + 1 ) ;
Length - = n - i ;
PathLength - = l ;
break ;
}
}
}
// Switch to the next name:
n = b - 1 ;
}
}
// Limit path to PathMax:
n = Length ;
while ( PathLength > PathMax & & n > 0 ) {
// Calculate how much to cut off the current name:
int i = n ;
int b = i ;
int l = 0 ;
while ( - - i > 0 & & a [ i - 1 ] > = 0 ) {
if ( a [ i ] > 0 ) {
l + = a [ i ] ;
b = i ;
if ( PathLength - l < = PathMax )
break ;
}
}
// Shorten the name if necessary:
if ( l > 0 ) {
memmove ( s + b , s + n , Length - n + 1 ) ;
Length - = n - b ;
PathLength - = l ;
}
// Switch to the next name:
n = i - 1 ;
}
return s ;
}
2005-05-16 14:45:11 +02:00
cRecording : : cRecording ( cTimer * Timer , const cEvent * Event )
2000-03-11 11:22:37 +01:00
{
2002-01-20 14:05:28 +01:00
resume = RESUME_NOT_INITIALIZED ;
2000-04-24 09:35:29 +02:00
titleBuffer = NULL ;
2012-06-09 14:32:29 +02:00
sortBufferName = sortBufferTime = NULL ;
2000-03-11 11:22:37 +01:00
fileName = NULL ;
2002-02-03 15:55:04 +01:00
name = NULL ;
2005-12-18 10:41:26 +01:00
fileSizeMB = - 1 ; // unknown
2009-01-06 14:41:11 +01:00
channel = Timer - > Channel ( ) - > Number ( ) ;
2009-01-18 11:10:29 +01:00
instanceId = InstanceId ;
2009-01-06 14:41:11 +01:00
isPesRecording = false ;
2012-06-03 10:03:55 +02:00
isOnVideoDirectoryFileSystem = - 1 ; // unknown
2009-01-06 14:41:11 +01:00
framesPerSecond = DEFAULTFRAMESPERSECOND ;
2011-08-21 13:47:07 +02:00
numFrames = - 1 ;
2005-12-18 12:14:11 +01:00
deleted = 0 ;
2002-02-03 15:55:04 +01:00
// set up the actual name:
2005-05-16 14:45:11 +02:00
const char * Title = Event ? Event - > Title ( ) : NULL ;
const char * Subtitle = Event ? Event - > ShortText ( ) : NULL ;
2002-02-03 15:55:04 +01:00
if ( isempty ( Title ) )
2002-10-20 12:28:55 +02:00
Title = Timer - > Channel ( ) - > Name ( ) ;
2002-02-03 15:55:04 +01:00
if ( isempty ( Subtitle ) )
Subtitle = " " ;
2009-06-06 13:48:41 +02:00
const char * macroTITLE = strstr ( Timer - > File ( ) , TIMERMACRO_TITLE ) ;
const char * macroEPISODE = strstr ( Timer - > File ( ) , TIMERMACRO_EPISODE ) ;
2002-02-03 15:55:04 +01:00
if ( macroTITLE | | macroEPISODE ) {
2002-10-20 12:28:55 +02:00
name = strdup ( Timer - > File ( ) ) ;
2002-02-03 15:55:04 +01:00
name = strreplace ( name , TIMERMACRO_TITLE , Title ) ;
name = strreplace ( name , TIMERMACRO_EPISODE , Subtitle ) ;
2005-05-16 15:19:54 +02:00
// avoid blanks at the end:
int l = strlen ( name ) ;
while ( l - - > 2 ) {
2010-01-17 12:08:03 +01:00
if ( name [ l ] = = ' ' & & name [ l - 1 ] ! = FOLDERDELIMCHAR )
2005-05-16 15:19:54 +02:00
name [ l ] = 0 ;
else
break ;
}
2002-02-03 15:55:04 +01:00
if ( Timer - > IsSingleEvent ( ) ) {
Timer - > SetFile ( name ) ; // this was an instant recording, so let's set the actual data
2004-10-31 10:22:32 +01:00
Timers . SetModified ( ) ;
2002-02-03 15:55:04 +01:00
}
2001-09-02 15:21:54 +02:00
}
2002-02-03 15:55:04 +01:00
else if ( Timer - > IsSingleEvent ( ) | | ! Setup . UseSubtitle )
2002-10-20 12:28:55 +02:00
name = strdup ( Timer - > File ( ) ) ;
2002-02-03 15:55:04 +01:00
else
2013-10-10 13:13:30 +02:00
name = strdup ( cString : : sprintf ( " %s%c%s " , Timer - > File ( ) , FOLDERDELIMCHAR , Subtitle ) ) ;
2000-11-01 16:04:57 +01:00
// substitute characters that would cause problems in file names:
2001-06-16 10:36:13 +02:00
strreplace ( name , ' \n ' , ' ' ) ;
2000-03-11 11:22:37 +01:00
start = Timer - > StartTime ( ) ;
2002-10-20 12:28:55 +02:00
priority = Timer - > Priority ( ) ;
lifetime = Timer - > Lifetime ( ) ;
2005-05-16 14:45:11 +02:00
// handle info:
2006-02-18 16:03:40 +01:00
info = new cRecordingInfo ( Timer - > Channel ( ) , Event ) ;
2006-02-25 12:09:22 +01:00
info - > SetAux ( Timer - > Aux ( ) ) ;
2009-01-06 14:41:11 +01:00
info - > priority = priority ;
info - > lifetime = lifetime ;
2000-03-11 11:22:37 +01:00
}
cRecording : : cRecording ( const char * FileName )
{
2002-01-20 14:05:28 +01:00
resume = RESUME_NOT_INITIALIZED ;
2005-12-18 10:41:26 +01:00
fileSizeMB = - 1 ; // unknown
2009-01-06 14:41:11 +01:00
channel = - 1 ;
2009-01-18 11:10:29 +01:00
instanceId = - 1 ;
2009-01-06 14:41:11 +01:00
priority = MAXPRIORITY ; // assume maximum in case there is no info file
lifetime = MAXLIFETIME ;
isPesRecording = false ;
2012-06-03 10:03:55 +02:00
isOnVideoDirectoryFileSystem = - 1 ; // unknown
2009-01-06 14:41:11 +01:00
framesPerSecond = DEFAULTFRAMESPERSECOND ;
2011-08-21 13:47:07 +02:00
numFrames = - 1 ;
2005-12-18 12:14:11 +01:00
deleted = 0 ;
2000-04-24 09:35:29 +02:00
titleBuffer = NULL ;
2012-06-09 14:32:29 +02:00
sortBufferName = sortBufferTime = NULL ;
2010-01-02 14:02:48 +01:00
FileName = fileName = strdup ( FileName ) ;
if ( * ( fileName + strlen ( fileName ) - 1 ) = = ' / ' )
* ( fileName + strlen ( fileName ) - 1 ) = 0 ;
2013-09-11 12:20:37 +02:00
if ( strstr ( FileName , cVideoDirectory : : Name ( ) ) = = FileName )
FileName + = strlen ( cVideoDirectory : : Name ( ) ) + 1 ;
2009-06-06 13:48:41 +02:00
const char * p = strrchr ( FileName , ' / ' ) ;
2000-03-11 11:22:37 +01:00
name = NULL ;
2010-12-27 12:25:19 +01:00
info = new cRecordingInfo ( fileName ) ;
2000-03-11 11:22:37 +01:00
if ( p ) {
time_t now = time ( NULL ) ;
2001-10-19 13:22:24 +02:00
struct tm tm_r ;
struct tm t = * localtime_r ( & now , & tm_r ) ; // this initializes the time zone in 't'
2002-04-01 11:04:47 +02:00
t . tm_isdst = - 1 ; // makes sure mktime() will determine the correct DST setting
2009-01-18 11:10:29 +01:00
if ( 7 = = sscanf ( p + 1 , DATAFORMATTS , & t . tm_year , & t . tm_mon , & t . tm_mday , & t . tm_hour , & t . tm_min , & channel , & instanceId )
2009-01-06 14:41:11 +01:00
| | 7 = = sscanf ( p + 1 , DATAFORMATPES , & t . tm_year , & t . tm_mon , & t . tm_mday , & t . tm_hour , & t . tm_min , & priority , & lifetime ) ) {
2000-03-11 11:22:37 +01:00
t . tm_year - = 1900 ;
t . tm_mon - - ;
t . tm_sec = 0 ;
start = mktime ( & t ) ;
2002-10-13 09:11:16 +02:00
name = MALLOC ( char , p - FileName + 1 ) ;
2000-03-11 11:22:37 +01:00
strncpy ( name , FileName , p - FileName ) ;
name [ p - FileName ] = 0 ;
2002-02-10 14:21:36 +01:00
name = ExchangeChars ( name , false ) ;
2009-01-18 11:10:29 +01:00
isPesRecording = instanceId < 0 ;
2000-03-11 11:22:37 +01:00
}
2009-01-06 14:41:11 +01:00
else
return ;
2005-09-25 11:00:57 +02:00
GetResume ( ) ;
2005-05-16 14:45:11 +02:00
// read an optional info file:
2009-01-06 14:41:11 +01:00
cString InfoFileName = cString : : sprintf ( " %s%s " , fileName , isPesRecording ? INFOFILESUFFIX " .vdr " : INFOFILESUFFIX ) ;
2005-05-16 14:45:11 +02:00
FILE * f = fopen ( InfoFileName , " r " ) ;
if ( f ) {
2005-10-09 13:13:36 +02:00
if ( ! info - > Read ( f ) )
2008-02-15 14:57:48 +01:00
esyslog ( " ERROR: EPG data problem in file %s " , * InfoFileName ) ;
2009-01-06 14:41:11 +01:00
else if ( ! isPesRecording ) {
priority = info - > priority ;
lifetime = info - > lifetime ;
framesPerSecond = info - > framesPerSecond ;
}
2005-05-16 14:45:11 +02:00
fclose ( f ) ;
2000-07-24 16:43:04 +02:00
}
2012-05-20 13:55:34 +02:00
else if ( errno = = ENOENT )
info - > ownEvent - > SetTitle ( name ) ;
else
2008-02-15 14:57:48 +01:00
LOG_ERROR_STR ( * InfoFileName ) ;
2005-05-22 10:43:10 +02:00
# ifdef SUMMARYFALLBACK
// fall back to the old 'summary.vdr' if there was no 'info.vdr':
if ( isempty ( info - > Title ( ) ) ) {
2008-02-15 14:57:48 +01:00
cString SummaryFileName = cString : : sprintf ( " %s%s " , fileName , SUMMARYFILESUFFIX ) ;
2005-05-22 10:43:10 +02:00
FILE * f = fopen ( SummaryFileName , " r " ) ;
if ( f ) {
int line = 0 ;
char * data [ 3 ] = { NULL } ;
cReadLine ReadLine ;
char * s ;
2005-06-04 11:33:09 +02:00
while ( ( s = ReadLine . Read ( f ) ) ! = NULL ) {
if ( * s | | line > 1 ) {
2005-05-22 10:43:10 +02:00
if ( data [ line ] ) {
int len = strlen ( s ) ;
len + = strlen ( data [ line ] ) + 1 ;
2011-02-25 15:25:42 +01:00
if ( char * NewBuffer = ( char * ) realloc ( data [ line ] , len + 1 ) ) {
data [ line ] = NewBuffer ;
strcat ( data [ line ] , " \n " ) ;
strcat ( data [ line ] , s ) ;
}
else
esyslog ( " ERROR: out of memory " ) ;
2005-05-22 10:43:10 +02:00
}
else
data [ line ] = strdup ( s ) ;
}
else
line + + ;
}
fclose ( f ) ;
2005-09-17 09:20:31 +02:00
if ( ! data [ 2 ] ) {
2005-05-22 10:43:10 +02:00
data [ 2 ] = data [ 1 ] ;
data [ 1 ] = NULL ;
}
2005-09-17 09:20:31 +02:00
else if ( data [ 1 ] & & data [ 2 ] ) {
2005-09-10 12:46:01 +02:00
// if line 1 is too long, it can't be the short text,
// so assume the short text is missing and concatenate
// line 1 and line 2 to be the long text:
int len = strlen ( data [ 1 ] ) ;
2006-01-08 11:44:37 +01:00
if ( len > 80 ) {
2011-02-25 15:25:42 +01:00
if ( char * NewBuffer = ( char * ) realloc ( data [ 1 ] , len + 1 + strlen ( data [ 2 ] ) + 1 ) ) {
data [ 1 ] = NewBuffer ;
strcat ( data [ 1 ] , " \n " ) ;
strcat ( data [ 1 ] , data [ 2 ] ) ;
free ( data [ 2 ] ) ;
data [ 2 ] = data [ 1 ] ;
data [ 1 ] = NULL ;
}
else
esyslog ( " ERROR: out of memory " ) ;
2005-09-10 12:46:01 +02:00
}
}
2005-05-22 10:43:10 +02:00
info - > SetData ( data [ 0 ] , data [ 1 ] , data [ 2 ] ) ;
for ( int i = 0 ; i < 3 ; i + + )
free ( data [ i ] ) ;
}
else if ( errno ! = ENOENT )
2008-02-15 14:57:48 +01:00
LOG_ERROR_STR ( * SummaryFileName ) ;
2005-05-22 10:43:10 +02:00
}
# endif
2000-03-11 11:22:37 +01:00
}
}
cRecording : : ~ cRecording ( )
{
2002-08-11 13:32:23 +02:00
free ( titleBuffer ) ;
2012-06-09 14:32:29 +02:00
free ( sortBufferName ) ;
free ( sortBufferTime ) ;
2002-08-11 13:32:23 +02:00
free ( fileName ) ;
free ( name ) ;
2005-05-16 14:45:11 +02:00
delete info ;
2000-03-11 11:22:37 +01:00
}
2013-03-03 11:04:22 +01:00
char * cRecording : : StripEpisodeName ( char * s , bool Strip )
2001-10-07 11:00:35 +02:00
{
char * t = s , * s1 = NULL , * s2 = NULL ;
while ( * t ) {
if ( * t = = ' / ' ) {
if ( s1 ) {
if ( s2 )
s1 = s2 ;
s2 = t ;
}
else
s1 = t ;
}
t + + ;
}
2012-06-09 14:32:29 +02:00
if ( s1 & & s2 ) {
2012-09-29 10:05:49 +02:00
// To have folders sorted before plain recordings, the '/' s1 points to
2013-01-13 11:57:50 +01:00
// is replaced by the character '1'. All other slashes will be replaced
// by '0' in SortName() (see below), which will result in the desired
2012-09-29 10:05:49 +02:00
// sequence:
2013-01-13 11:57:50 +01:00
* s1 = ' 1 ' ;
2013-03-03 11:04:22 +01:00
if ( Strip ) {
s1 + + ;
memmove ( s1 , s2 , t - s2 + 1 ) ;
}
2012-06-09 14:32:29 +02:00
}
2001-10-07 11:00:35 +02:00
return s ;
}
2004-11-01 10:40:38 +01:00
char * cRecording : : SortName ( void ) const
2001-10-07 11:00:35 +02:00
{
2012-06-09 14:32:29 +02:00
char * * sb = ( RecordingsSortMode = = rsmName ) ? & sortBufferName : & sortBufferTime ;
if ( ! * sb ) {
2014-01-29 10:45:28 +01:00
if ( RecordingsSortMode = = rsmTime & & ! Setup . RecordingDirs ) {
char buf [ 32 ] ;
struct tm tm_r ;
strftime ( buf , sizeof ( buf ) , " %Y%m%d%H%I " , localtime_r ( & start , & tm_r ) ) ;
* sb = strdup ( buf ) ;
}
else {
char * s = strdup ( FileName ( ) + strlen ( cVideoDirectory : : Name ( ) ) ) ;
if ( RecordingsSortMode ! = rsmName | | Setup . AlwaysSortFoldersFirst )
s = StripEpisodeName ( s , RecordingsSortMode ! = rsmName ) ;
strreplace ( s , ' / ' , ' 0 ' ) ; // some locales ignore '/' when sorting
int l = strxfrm ( NULL , s , 0 ) + 1 ;
* sb = MALLOC ( char , l ) ;
strxfrm ( * sb , s , l ) ;
free ( s ) ;
}
2001-10-07 11:00:35 +02:00
}
2012-06-09 14:32:29 +02:00
return * sb ;
2001-10-07 11:00:35 +02:00
}
2013-03-04 14:11:47 +01:00
void cRecording : : ClearSortName ( void )
{
2013-10-10 13:13:30 +02:00
free ( sortBufferName ) ;
free ( sortBufferTime ) ;
sortBufferName = sortBufferTime = NULL ;
2013-03-04 14:11:47 +01:00
}
2004-05-16 10:35:36 +02:00
int cRecording : : GetResume ( void ) const
2002-01-20 14:05:28 +01:00
{
if ( resume = = RESUME_NOT_INITIALIZED ) {
2009-01-06 14:41:11 +01:00
cResumeFile ResumeFile ( FileName ( ) , isPesRecording ) ;
2002-01-20 14:05:28 +01:00
resume = ResumeFile . Read ( ) ;
}
return resume ;
}
2004-11-01 10:40:38 +01:00
int cRecording : : Compare ( const cListObject & ListObject ) const
2001-10-07 11:00:35 +02:00
{
cRecording * r = ( cRecording * ) & ListObject ;
2004-11-01 10:40:38 +01:00
return strcasecmp ( SortName ( ) , r - > SortName ( ) ) ;
2001-10-07 11:00:35 +02:00
}
2013-10-10 13:13:30 +02:00
bool cRecording : : IsInPath ( const char * Path )
{
if ( isempty ( Path ) )
return true ;
int l = strlen ( Path ) ;
return strncmp ( Path , name , l ) = = 0 & & ( name [ l ] = = FOLDERDELIMCHAR ) ;
}
cString cRecording : : Folder ( void ) const
{
if ( char * s = strrchr ( name , FOLDERDELIMCHAR ) )
return cString ( name , s ) ;
return " " ;
}
cString cRecording : : BaseName ( void ) const
{
if ( char * s = strrchr ( name , FOLDERDELIMCHAR ) )
return cString ( s + 1 ) ;
return name ;
}
2004-05-16 10:35:36 +02:00
const char * cRecording : : FileName ( void ) const
2000-03-11 11:22:37 +01:00
{
if ( ! fileName ) {
2001-10-19 13:22:24 +02:00
struct tm tm_r ;
struct tm * t = localtime_r ( & start , & tm_r ) ;
2009-01-06 14:41:11 +01:00
const char * fmt = isPesRecording ? NAMEFORMATPES : NAMEFORMATTS ;
int ch = isPesRecording ? priority : channel ;
2009-01-18 11:10:29 +01:00
int ri = isPesRecording ? lifetime : instanceId ;
2013-09-11 12:20:37 +02:00
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
2013-02-08 09:24:55 +01:00
if ( strcmp ( Name , name ) ! = 0 )
dsyslog ( " recording file name '%s' truncated to '%s' " , name , Name ) ;
Name = ExchangeChars ( Name , true ) ;
2013-09-11 12:20:37 +02:00
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 ) ) ;
2013-02-08 09:24:55 +01:00
free ( Name ) ;
2000-03-11 11:22:37 +01:00
}
return fileName ;
}
2004-05-16 10:35:36 +02:00
const char * cRecording : : Title ( char Delimiter , bool NewIndicator , int Level ) const
2000-04-23 15:38:16 +02:00
{
2002-01-20 14:05:28 +01:00
char New = NewIndicator & & IsNew ( ) ? ' * ' : ' ' ;
2002-08-11 13:32:23 +02:00
free ( titleBuffer ) ;
2000-04-23 15:38:16 +02:00
titleBuffer = NULL ;
2002-01-20 14:05:28 +01:00
if ( Level < 0 | | Level = = HierarchyLevels ( ) ) {
struct tm tm_r ;
struct tm * t = localtime_r ( & start , & tm_r ) ;
2002-02-10 15:41:23 +01:00
char * s ;
2010-01-17 12:08:03 +01:00
if ( Level > 0 & & ( s = strrchr ( name , FOLDERDELIMCHAR ) ) ! = NULL )
2002-01-20 14:05:28 +01:00
s + + ;
else
s = name ;
2012-01-15 11:12:58 +01:00
cString Length ( " " ) ;
if ( NewIndicator ) {
2012-01-25 09:34:24 +01:00
int Minutes = max ( 0 , ( LengthInSeconds ( ) + 30 ) / 60 ) ;
2012-01-15 11:12:58 +01:00
Length = cString : : sprintf ( " %c%d:%02d " ,
Delimiter ,
2012-01-25 09:34:24 +01:00
Minutes / 60 ,
Minutes % 60
2012-01-15 11:12:58 +01:00
) ;
}
titleBuffer = strdup ( cString : : sprintf ( " %02d.%02d.%02d%c%02d:%02d%s%c%c%s " ,
2002-01-20 14:05:28 +01:00
t - > tm_mday ,
t - > tm_mon + 1 ,
2005-06-05 14:23:23 +02:00
t - > tm_year % 100 ,
2002-01-20 14:05:28 +01:00
Delimiter ,
t - > tm_hour ,
t - > tm_min ,
2012-01-15 11:12:58 +01:00
* Length ,
2002-01-20 14:05:28 +01:00
New ,
Delimiter ,
2008-02-15 14:57:48 +01:00
s ) ) ;
2010-01-17 12:08:03 +01:00
// let's not display a trailing FOLDERDELIMCHAR:
2005-05-29 11:22:39 +02:00
if ( ! NewIndicator )
stripspace ( titleBuffer ) ;
2002-02-10 15:41:23 +01:00
s = & titleBuffer [ strlen ( titleBuffer ) - 1 ] ;
2010-01-17 12:08:03 +01:00
if ( * s = = FOLDERDELIMCHAR )
2002-02-10 15:41:23 +01:00
* s = 0 ;
2002-01-20 14:05:28 +01:00
}
else if ( Level < HierarchyLevels ( ) ) {
const char * s = name ;
const char * p = s ;
while ( * + + s ) {
2010-01-17 12:08:03 +01:00
if ( * s = = FOLDERDELIMCHAR ) {
2002-01-20 14:05:28 +01:00
if ( Level - - )
p = s + 1 ;
else
break ;
}
}
2002-10-13 09:11:16 +02:00
titleBuffer = MALLOC ( char , s - p + 3 ) ;
2002-01-20 14:05:28 +01:00
* titleBuffer = Delimiter ;
* ( titleBuffer + 1 ) = Delimiter ;
strn0cpy ( titleBuffer + 2 , p , s - p + 1 ) ;
}
else
return " " ;
2000-04-23 15:38:16 +02:00
return titleBuffer ;
}
2001-09-01 13:38:09 +02:00
const char * cRecording : : PrefixFileName ( char Prefix )
{
2013-09-11 12:20:37 +02:00
cString p = cVideoDirectory : : PrefixVideoFileName ( FileName ( ) , Prefix ) ;
2004-12-26 12:45:22 +01:00
if ( * p ) {
2002-08-11 13:32:23 +02:00
free ( fileName ) ;
2001-09-01 13:38:09 +02:00
fileName = strdup ( p ) ;
return fileName ;
}
return NULL ;
}
2004-05-16 10:35:36 +02:00
int cRecording : : HierarchyLevels ( void ) const
2002-01-20 14:05:28 +01:00
{
const char * s = name ;
int level = 0 ;
while ( * + + s ) {
2010-01-17 12:08:03 +01:00
if ( * s = = FOLDERDELIMCHAR )
2002-01-20 14:05:28 +01:00
level + + ;
}
return level ;
}
2004-05-16 10:35:36 +02:00
bool cRecording : : IsEdited ( void ) const
2003-10-17 14:40:37 +02:00
{
2010-01-17 12:08:03 +01:00
const char * s = strrchr ( name , FOLDERDELIMCHAR ) ;
2003-10-17 14:40:37 +02:00
s = ! s ? name : s + 1 ;
return * s = = ' % ' ;
}
2012-06-03 10:03:55 +02:00
bool cRecording : : IsOnVideoDirectoryFileSystem ( void ) const
{
if ( isOnVideoDirectoryFileSystem < 0 )
2013-09-11 12:20:37 +02:00
isOnVideoDirectoryFileSystem = cVideoDirectory : : IsOnVideoDirectoryFileSystem ( FileName ( ) ) ;
2012-06-03 10:03:55 +02:00
return isOnVideoDirectoryFileSystem ;
}
2013-10-10 13:13:30 +02:00
bool cRecording : : HasMarks ( void )
{
return access ( cMarks : : MarksFileName ( this ) , F_OK ) = = 0 ;
}
bool cRecording : : DeleteMarks ( void )
{
if ( remove ( cMarks : : MarksFileName ( this ) ) < 0 ) {
if ( errno ! = ENOENT ) {
LOG_ERROR_STR ( fileName ) ;
return false ;
}
}
return true ;
}
2010-12-27 12:25:19 +01:00
void cRecording : : ReadInfo ( void )
{
info - > Read ( ) ;
priority = info - > priority ;
lifetime = info - > lifetime ;
framesPerSecond = info - > framesPerSecond ;
}
2013-10-10 13:13:30 +02:00
bool cRecording : : WriteInfo ( const char * OtherFileName )
2000-07-24 16:43:04 +02:00
{
2013-10-10 13:13:30 +02:00
cString InfoFileName = cString : : sprintf ( " %s%s " , OtherFileName ? OtherFileName : FileName ( ) , isPesRecording ? INFOFILESUFFIX " .vdr " : INFOFILESUFFIX ) ;
cSafeFile f ( InfoFileName ) ;
if ( f . Open ( ) ) {
2005-05-16 14:45:11 +02:00
info - > Write ( f ) ;
2013-10-10 13:13:30 +02:00
f . Close ( ) ;
2000-07-24 16:43:04 +02:00
}
2005-05-16 14:45:11 +02:00
else
2008-02-15 14:57:48 +01:00
LOG_ERROR_STR ( * InfoFileName ) ;
2000-07-24 16:43:04 +02:00
return true ;
}
2013-02-17 13:19:36 +01:00
void cRecording : : SetStartTime ( time_t Start )
2011-08-20 10:09:05 +02:00
{
start = Start ;
free ( fileName ) ;
fileName = NULL ;
}
2013-10-10 13:13:30 +02:00
bool cRecording : : ChangePriorityLifetime ( int NewPriority , int NewLifetime )
{
if ( NewPriority ! = Priority ( ) | | NewLifetime ! = Lifetime ( ) ) {
dsyslog ( " changing priority/lifetime of '%s' to %d/%d " , Name ( ) , NewPriority , NewLifetime ) ;
if ( IsPesRecording ( ) ) {
cString OldFileName = FileName ( ) ;
priority = NewPriority ;
lifetime = NewLifetime ;
free ( fileName ) ;
fileName = NULL ;
cString NewFileName = FileName ( ) ;
if ( ! cVideoDirectory : : RenameVideoFile ( OldFileName , NewFileName ) )
return false ;
info - > SetFileName ( NewFileName ) ;
}
else {
priority = info - > priority = NewPriority ;
lifetime = info - > lifetime = NewLifetime ;
if ( ! WriteInfo ( ) )
return false ;
}
Recordings . ChangeState ( ) ;
Recordings . TouchUpdate ( ) ;
}
return true ;
}
bool cRecording : : ChangeName ( const char * NewName )
{
if ( strcmp ( NewName , Name ( ) ) ) {
dsyslog ( " changing name of '%s' to '%s' " , Name ( ) , NewName ) ;
cString OldName = Name ( ) ;
cString OldFileName = FileName ( ) ;
free ( fileName ) ;
fileName = NULL ;
free ( name ) ;
name = strdup ( NewName ) ;
cString NewFileName = FileName ( ) ;
if ( ! ( MakeDirs ( NewFileName , true ) & & cVideoDirectory : : MoveVideoFile ( OldFileName , NewFileName ) ) ) {
free ( name ) ;
name = strdup ( OldName ) ;
free ( fileName ) ;
fileName = strdup ( OldFileName ) ;
return false ;
}
2014-03-16 11:11:04 +01:00
isOnVideoDirectoryFileSystem = - 1 ; // it might have been moved to a different file system
2013-10-10 13:13:30 +02:00
ClearSortName ( ) ;
Recordings . ChangeState ( ) ;
Recordings . TouchUpdate ( ) ;
}
return true ;
}
2000-03-11 11:22:37 +01:00
bool cRecording : : Delete ( void )
{
bool result = true ;
char * NewName = strdup ( FileName ( ) ) ;
char * ext = strrchr ( NewName , ' . ' ) ;
2005-12-18 10:41:26 +01:00
if ( ext & & strcmp ( ext , RECEXT ) = = 0 ) {
2000-03-11 11:22:37 +01:00
strncpy ( ext , DELEXT , strlen ( ext ) ) ;
2002-03-16 12:19:14 +01:00
if ( access ( NewName , F_OK ) = = 0 ) {
// the new name already exists, so let's remove that one first:
2006-03-19 14:36:43 +01:00
isyslog ( " removing recording '%s' " , NewName ) ;
2013-09-11 12:20:37 +02:00
cVideoDirectory : : RemoveVideoFile ( NewName ) ;
2002-03-16 12:19:14 +01:00
}
2006-03-19 14:36:43 +01:00
isyslog ( " deleting recording '%s' " , FileName ( ) ) ;
2012-09-17 09:03:01 +02:00
if ( access ( FileName ( ) , F_OK ) = = 0 ) {
2013-09-11 12:20:37 +02:00
result = cVideoDirectory : : RenameVideoFile ( FileName ( ) , NewName ) ;
2012-09-17 09:03:01 +02:00
cRecordingUserCommand : : InvokeCommand ( RUC_DELETERECORDING , NewName ) ;
}
2006-03-19 14:36:43 +01:00
else {
isyslog ( " recording '%s' vanished " , FileName ( ) ) ;
result = true ; // well, we were going to delete it, anyway
}
2000-03-11 11:22:37 +01:00
}
2002-08-11 13:32:23 +02:00
free ( NewName ) ;
2000-03-11 11:22:37 +01:00
return result ;
}
bool cRecording : : Remove ( void )
{
2002-01-20 16:47:09 +01:00
// let's do a final safety check here:
if ( ! endswith ( FileName ( ) , DELEXT ) ) {
2002-05-13 16:35:49 +02:00
esyslog ( " attempt to remove recording %s " , FileName ( ) ) ;
2002-01-20 16:47:09 +01:00
return false ;
}
2002-05-13 16:35:49 +02:00
isyslog ( " removing recording %s " , FileName ( ) ) ;
2013-09-11 12:20:37 +02:00
return cVideoDirectory : : RemoveVideoFile ( FileName ( ) ) ;
2000-03-11 11:22:37 +01:00
}
2007-10-14 10:23:19 +02:00
bool cRecording : : Undelete ( void )
{
bool result = true ;
char * NewName = strdup ( FileName ( ) ) ;
char * ext = strrchr ( NewName , ' . ' ) ;
if ( ext & & strcmp ( ext , DELEXT ) = = 0 ) {
strncpy ( ext , RECEXT , strlen ( ext ) ) ;
if ( access ( NewName , F_OK ) = = 0 ) {
// the new name already exists, so let's not remove that one:
esyslog ( " ERROR: attempt to undelete '%s', while recording '%s' exists " , FileName ( ) , NewName ) ;
result = false ;
}
else {
isyslog ( " undeleting recording '%s' " , FileName ( ) ) ;
if ( access ( FileName ( ) , F_OK ) = = 0 )
2013-09-11 12:20:37 +02:00
result = cVideoDirectory : : RenameVideoFile ( FileName ( ) , NewName ) ;
2007-10-14 10:23:19 +02:00
else {
isyslog ( " deleted recording '%s' vanished " , FileName ( ) ) ;
result = false ;
}
}
}
free ( NewName ) ;
return result ;
}
2013-10-10 13:13:30 +02:00
int cRecording : : IsInUse ( void ) const
{
int Use = ruNone ;
if ( cRecordControls : : GetRecordControl ( FileName ( ) ) )
Use | = ruTimer ;
if ( cReplayControl : : NowReplaying ( ) & & strcmp ( cReplayControl : : NowReplaying ( ) , FileName ( ) ) = = 0 )
Use | = ruReplay ;
Use | = RecordingsHandler . GetUsage ( FileName ( ) ) ;
return Use ;
}
2005-09-25 11:35:56 +02:00
void cRecording : : ResetResume ( void ) const
{
resume = RESUME_NOT_INITIALIZED ;
}
2011-08-21 13:47:07 +02:00
int cRecording : : NumFrames ( void ) const
{
if ( numFrames < 0 ) {
int nf = cIndexFile : : GetLength ( FileName ( ) , IsPesRecording ( ) ) ;
2012-09-06 10:07:25 +02:00
if ( time ( NULL ) - LastModifiedTime ( cIndexFile : : IndexFileName ( FileName ( ) , IsPesRecording ( ) ) ) < MININDEXAGE )
2011-08-21 13:47:07 +02:00
return nf ; // check again later for ongoing recordings
numFrames = nf ;
}
return numFrames ;
}
int cRecording : : LengthInSeconds ( void ) const
{
int nf = NumFrames ( ) ;
if ( nf > = 0 )
2012-01-25 09:34:24 +01:00
return int ( nf / FramesPerSecond ( ) ) ;
2011-08-21 13:47:07 +02:00
return - 1 ;
}
2012-03-13 13:22:06 +01:00
int cRecording : : FileSizeMB ( void ) const
{
if ( fileSizeMB < 0 ) {
int fs = DirSizeMB ( FileName ( ) ) ;
2012-09-06 10:07:25 +02:00
if ( time ( NULL ) - LastModifiedTime ( cIndexFile : : IndexFileName ( FileName ( ) , IsPesRecording ( ) ) ) < MININDEXAGE )
2012-03-13 13:22:06 +01:00
return fs ; // check again later for ongoing recordings
fileSizeMB = fs ;
}
return fileSizeMB ;
}
2000-03-11 11:22:37 +01:00
// --- cRecordings -----------------------------------------------------------
2004-06-13 20:26:51 +02:00
cRecordings Recordings ;
2005-10-01 10:33:38 +02:00
char * cRecordings : : updateFileName = NULL ;
2004-06-13 20:26:51 +02:00
cRecordings : : cRecordings ( bool Deleted )
2005-09-25 11:00:57 +02:00
: cThread ( " video directory scanner " )
2000-03-11 11:22:37 +01:00
{
2004-06-13 20:26:51 +02:00
deleted = Deleted ;
2013-12-24 14:41:09 +01:00
initial = true ;
2004-06-13 20:26:51 +02:00
lastUpdate = 0 ;
2005-09-25 11:00:57 +02:00
state = 0 ;
}
cRecordings : : ~ cRecordings ( )
{
Cancel ( 3 ) ;
}
void cRecordings : : Action ( void )
{
Refresh ( ) ;
}
2005-10-01 10:33:38 +02:00
const char * cRecordings : : UpdateFileName ( void )
{
if ( ! updateFileName )
2013-09-11 12:20:37 +02:00
updateFileName = strdup ( AddDirectory ( cVideoDirectory : : Name ( ) , " .update " ) ) ;
2005-10-01 10:33:38 +02:00
return updateFileName ;
}
2005-09-25 11:00:57 +02:00
void cRecordings : : Refresh ( bool Foreground )
{
lastUpdate = time ( NULL ) ; // doing this first to make sure we don't miss anything
2013-12-24 14:41:09 +01:00
initial = Count ( ) = = 0 ; // no name checking if the list is initially empty
if ( deleted ) {
Lock ( ) ;
Clear ( ) ;
ChangeState ( ) ;
Unlock ( ) ;
}
2013-09-11 12:20:37 +02:00
ScanVideoDir ( cVideoDirectory : : Name ( ) , Foreground ) ;
2004-06-13 20:26:51 +02:00
}
2013-12-27 11:06:01 +01:00
bool cRecordings : : ScanVideoDir ( const char * DirName , bool Foreground , int LinkLevel , int DirLevel )
2004-06-13 20:26:51 +02:00
{
2013-12-27 08:46:17 +01:00
bool DoChangeState = false ;
2013-12-24 14:41:09 +01:00
// Find any new recordings:
2004-12-19 16:33:34 +01:00
cReadDir d ( DirName ) ;
struct dirent * e ;
2005-09-25 11:00:57 +02:00
while ( ( Foreground | | Running ( ) ) & & ( e = d . Next ( ) ) ! = NULL ) {
2012-02-17 14:02:13 +01:00
cString buffer = AddDirectory ( DirName , e - > d_name ) ;
struct stat st ;
if ( lstat ( buffer , & st ) = = 0 ) {
int Link = 0 ;
if ( S_ISLNK ( st . st_mode ) ) {
if ( LinkLevel > MAX_LINK_LEVEL ) {
isyslog ( " max link level exceeded - not scanning %s " , * buffer ) ;
continue ;
2004-12-19 16:33:34 +01:00
}
2012-02-17 14:02:13 +01:00
Link = 1 ;
if ( stat ( buffer , & st ) ! = 0 )
continue ;
}
if ( S_ISDIR ( st . st_mode ) ) {
if ( endswith ( buffer , deleted ? DELEXT : RECEXT ) ) {
2013-12-24 14:41:09 +01:00
if ( deleted | | initial | | ! GetByName ( buffer ) ) {
cRecording * r = new cRecording ( buffer ) ;
if ( r - > Name ( ) ) {
r - > NumFrames ( ) ; // initializes the numFrames member
r - > FileSizeMB ( ) ; // initializes the fileSizeMB member
2014-03-16 11:01:46 +01:00
r - > IsOnVideoDirectoryFileSystem ( ) ; // initializes the isOnVideoDirectoryFileSystem member
2013-12-24 14:41:09 +01:00
if ( deleted )
r - > deleted = time ( NULL ) ;
Lock ( ) ;
Add ( r ) ;
2013-12-27 08:46:17 +01:00
if ( initial )
ChangeState ( ) ;
else
DoChangeState = true ;
2013-12-24 14:41:09 +01:00
Unlock ( ) ;
}
else
delete r ;
2004-06-13 20:26:51 +02:00
}
}
2012-02-17 14:02:13 +01:00
else
2013-12-27 11:06:01 +01:00
DoChangeState | = ScanVideoDir ( buffer , Foreground , LinkLevel + Link , DirLevel + 1 ) ;
2004-06-13 20:26:51 +02:00
}
2002-01-27 15:14:45 +01:00
}
2004-12-19 16:33:34 +01:00
}
2013-12-24 14:41:09 +01:00
// Handle any vanished recordings:
if ( ! deleted & & ! initial & & DirLevel = = 0 ) {
for ( cRecording * recording = First ( ) ; recording ; ) {
cRecording * r = recording ;
recording = Next ( recording ) ;
if ( access ( r - > FileName ( ) , F_OK ) ! = 0 ) {
Lock ( ) ;
Del ( r , false ) ;
VanishedRecordings . Add ( r ) ;
2013-12-27 08:46:17 +01:00
DoChangeState = true ;
2013-12-24 14:41:09 +01:00
Unlock ( ) ;
}
}
}
2013-12-27 11:06:01 +01:00
if ( DoChangeState & & DirLevel = = 0 )
2013-12-27 08:46:17 +01:00
ChangeState ( ) ;
2013-12-27 11:06:01 +01:00
return DoChangeState ;
2004-06-13 20:26:51 +02:00
}
2005-09-25 11:00:57 +02:00
bool cRecordings : : StateChanged ( int & State )
{
int NewState = state ;
bool Result = State ! = NewState ;
State = state ;
return Result ;
}
2005-09-25 13:49:31 +02:00
void cRecordings : : TouchUpdate ( void )
{
2006-10-07 12:50:24 +02:00
bool needsUpdate = NeedsUpdate ( ) ;
2005-10-01 10:33:38 +02:00
TouchFile ( UpdateFileName ( ) ) ;
2006-10-07 12:50:24 +02:00
if ( ! needsUpdate )
2011-12-04 13:58:33 +01:00
lastUpdate = time ( NULL ) ; // make sure we don't trigger ourselves
2005-09-25 13:49:31 +02:00
}
2004-06-13 20:26:51 +02:00
bool cRecordings : : NeedsUpdate ( void )
{
2006-09-16 12:14:06 +02:00
time_t lastModified = LastModifiedTime ( UpdateFileName ( ) ) ;
if ( lastModified > time ( NULL ) )
return false ; // somebody's clock isn't running correctly
return lastUpdate < lastModified ;
2004-06-13 20:26:51 +02:00
}
2005-09-25 11:00:57 +02:00
bool cRecordings : : Update ( bool Wait )
2004-06-13 20:26:51 +02:00
{
2005-09-25 11:00:57 +02:00
if ( Wait ) {
Refresh ( true ) ;
return Count ( ) > 0 ;
}
else
Start ( ) ;
return false ;
2000-03-11 11:22:37 +01:00
}
2002-01-20 14:05:28 +01:00
cRecording * cRecordings : : GetByName ( const char * FileName )
{
2008-02-15 16:13:10 +01:00
if ( FileName ) {
2013-12-24 14:41:09 +01:00
LOCK_THREAD ;
2008-02-15 16:13:10 +01:00
for ( cRecording * recording = First ( ) ; recording ; recording = Next ( recording ) ) {
if ( strcmp ( recording - > FileName ( ) , FileName ) = = 0 )
return recording ;
}
}
2002-01-20 14:05:28 +01:00
return NULL ;
}
2006-07-30 10:29:24 +02:00
void cRecordings : : AddByName ( const char * FileName , bool TriggerUpdate )
2004-06-13 20:26:51 +02:00
{
2005-09-25 11:00:57 +02:00
LOCK_THREAD ;
2004-06-13 20:26:51 +02:00
cRecording * recording = GetByName ( FileName ) ;
if ( ! recording ) {
recording = new cRecording ( FileName ) ;
Add ( recording ) ;
2005-09-25 11:00:57 +02:00
ChangeState ( ) ;
2006-07-30 10:29:24 +02:00
if ( TriggerUpdate )
TouchUpdate ( ) ;
2004-06-13 20:26:51 +02:00
}
}
void cRecordings : : DelByName ( const char * FileName )
{
2005-09-25 11:00:57 +02:00
LOCK_THREAD ;
2004-06-13 20:26:51 +02:00
cRecording * recording = GetByName ( FileName ) ;
2014-01-18 12:54:56 +01:00
cRecording * dummy = NULL ;
if ( ! recording )
recording = dummy = new cRecording ( FileName ) ; // allows us to use a FileName that is not in the Recordings list
cThreadLock DeletedRecordingsLock ( & DeletedRecordings ) ;
if ( ! dummy )
2005-12-18 10:41:26 +01:00
Del ( recording , false ) ;
2014-01-18 12:54:56 +01:00
char * ext = strrchr ( recording - > fileName , ' . ' ) ;
if ( ext ) {
strncpy ( ext , DELEXT , strlen ( ext ) ) ;
if ( access ( recording - > FileName ( ) , F_OK ) = = 0 ) {
recording - > deleted = time ( NULL ) ;
DeletedRecordings . Add ( recording ) ;
recording = NULL ; // to prevent it from being deleted below
2005-12-18 10:41:26 +01:00
}
2005-09-25 11:00:57 +02:00
}
2014-01-18 12:54:56 +01:00
delete recording ;
ChangeState ( ) ;
TouchUpdate ( ) ;
2004-06-13 20:26:51 +02:00
}
2010-12-27 12:25:19 +01:00
void cRecordings : : UpdateByName ( const char * FileName )
{
LOCK_THREAD ;
cRecording * recording = GetByName ( FileName ) ;
if ( recording )
recording - > ReadInfo ( ) ;
}
2005-12-18 10:41:26 +01:00
int cRecordings : : TotalFileSizeMB ( void )
{
int size = 0 ;
LOCK_THREAD ;
for ( cRecording * recording = First ( ) ; recording ; recording = Next ( recording ) ) {
2012-03-13 13:22:06 +01:00
int FileSizeMB = recording - > FileSizeMB ( ) ;
2012-06-03 10:03:55 +02:00
if ( FileSizeMB > 0 & & recording - > IsOnVideoDirectoryFileSystem ( ) )
2012-03-13 13:22:06 +01:00
size + = FileSizeMB ;
2005-12-18 10:41:26 +01:00
}
return size ;
}
2012-03-13 13:22:06 +01:00
double cRecordings : : MBperMinute ( void )
{
int size = 0 ;
int length = 0 ;
LOCK_THREAD ;
for ( cRecording * recording = First ( ) ; recording ; recording = Next ( recording ) ) {
2012-06-03 10:03:55 +02:00
if ( recording - > IsOnVideoDirectoryFileSystem ( ) ) {
2012-03-13 13:22:06 +01:00
int FileSizeMB = recording - > FileSizeMB ( ) ;
if ( FileSizeMB > 0 ) {
int LengthInSeconds = recording - > LengthInSeconds ( ) ;
if ( LengthInSeconds > 0 ) {
2013-10-12 13:48:32 +02:00
if ( LengthInSeconds / FileSizeMB < LIMIT_SECS_PER_MB_RADIO ) { // don't count radio recordings
size + = FileSizeMB ;
length + = LengthInSeconds ;
}
2012-03-13 13:22:06 +01:00
}
}
}
}
return ( size & & length ) ? double ( size ) * 60 / length : - 1 ;
}
2013-10-10 13:13:30 +02:00
int cRecordings : : PathIsInUse ( const char * Path )
{
LOCK_THREAD ;
int Use = ruNone ;
for ( cRecording * recording = First ( ) ; recording ; recording = Next ( recording ) ) {
if ( recording - > IsInPath ( Path ) )
Use | = recording - > IsInUse ( ) ;
}
return Use ;
}
int cRecordings : : GetNumRecordingsInPath ( const char * Path )
{
LOCK_THREAD ;
int n = 0 ;
for ( cRecording * recording = First ( ) ; recording ; recording = Next ( recording ) ) {
if ( recording - > IsInPath ( Path ) )
n + + ;
}
return n ;
}
bool cRecordings : : MoveRecordings ( const char * OldPath , const char * NewPath )
{
if ( OldPath & & NewPath & & strcmp ( OldPath , NewPath ) ) {
LOCK_THREAD ;
dsyslog ( " moving '%s' to '%s' " , OldPath , NewPath ) ;
for ( cRecording * recording = First ( ) ; recording ; recording = Next ( recording ) ) {
if ( recording - > IsInPath ( OldPath ) ) {
const char * p = recording - > Name ( ) + strlen ( OldPath ) ;
cString NewName = cString : : sprintf ( " %s%s " , NewPath , p ) ;
if ( ! recording - > ChangeName ( NewName ) )
return false ;
ChangeState ( ) ;
}
}
}
return true ;
}
2005-09-25 11:35:56 +02:00
void cRecordings : : ResetResume ( const char * ResumeFileName )
{
2005-09-25 12:28:40 +02:00
LOCK_THREAD ;
2005-09-25 11:35:56 +02:00
for ( cRecording * recording = First ( ) ; recording ; recording = Next ( recording ) ) {
if ( ! ResumeFileName | | strncmp ( ResumeFileName , recording - > FileName ( ) , strlen ( recording - > FileName ( ) ) ) = = 0 )
recording - > ResetResume ( ) ;
}
2005-09-25 12:28:40 +02:00
ChangeState ( ) ;
2005-09-25 11:35:56 +02:00
}
2013-03-04 14:11:47 +01:00
void cRecordings : : ClearSortNames ( void )
{
LOCK_THREAD ;
for ( cRecording * recording = First ( ) ; recording ; recording = Next ( recording ) )
recording - > ClearSortName ( ) ;
}
2013-10-10 13:13:30 +02:00
// --- cDirCopier ------------------------------------------------------------
class cDirCopier : public cThread {
private :
cString dirNameSrc ;
cString dirNameDst ;
bool error ;
bool suspensionLogged ;
bool Throttled ( void ) ;
virtual void Action ( void ) ;
public :
cDirCopier ( const char * DirNameSrc , const char * DirNameDst ) ;
virtual ~ cDirCopier ( ) ;
void Stop ( void ) ;
bool Error ( void ) { return error ; }
} ;
cDirCopier : : cDirCopier ( const char * DirNameSrc , const char * DirNameDst )
: cThread ( " file copier " , true )
{
dirNameSrc = DirNameSrc ;
dirNameDst = DirNameDst ;
error = false ;
suspensionLogged = false ;
}
cDirCopier : : ~ cDirCopier ( )
{
Stop ( ) ;
}
bool cDirCopier : : Throttled ( void )
{
if ( cIoThrottle : : Engaged ( ) ) {
if ( ! suspensionLogged ) {
dsyslog ( " suspending copy thread " ) ;
suspensionLogged = true ;
}
return true ;
}
else if ( suspensionLogged ) {
dsyslog ( " resuming copy thread " ) ;
suspensionLogged = false ;
}
return false ;
}
void cDirCopier : : Action ( void )
{
if ( DirectoryOk ( dirNameDst , true ) ) {
cReadDir d ( dirNameSrc ) ;
if ( d . Ok ( ) ) {
dsyslog ( " copying directory '%s' to '%s' " , * dirNameSrc , * dirNameDst ) ;
dirent * e = NULL ;
cString FileNameSrc ;
cString FileNameDst ;
int From = - 1 ;
int To = - 1 ;
size_t BufferSize = BUFSIZ ;
while ( Running ( ) ) {
// Suspend cutting if we have severe throughput problems:
if ( Throttled ( ) ) {
cCondWait : : SleepMs ( 100 ) ;
continue ;
}
// Copy all files in the source directory to the destination directory:
if ( e ) {
// We're currently copying a file:
uchar Buffer [ BufferSize ] ;
size_t Read = safe_read ( From , Buffer , sizeof ( Buffer ) ) ;
if ( Read > 0 ) {
size_t Written = safe_write ( To , Buffer , Read ) ;
if ( Written ! = Read ) {
esyslog ( " ERROR: can't write to destination file '%s': %m " , * FileNameDst ) ;
break ;
}
}
else if ( Read = = 0 ) { // EOF on From
e = NULL ; // triggers switch to next entry
if ( fsync ( To ) < 0 ) {
esyslog ( " ERROR: can't sync destination file '%s': %m " , * FileNameDst ) ;
break ;
}
if ( close ( From ) < 0 ) {
esyslog ( " ERROR: can't close source file '%s': %m " , * FileNameSrc ) ;
break ;
}
if ( close ( To ) < 0 ) {
esyslog ( " ERROR: can't close destination file '%s': %m " , * FileNameDst ) ;
break ;
}
// Plausibility check:
off_t FileSizeSrc = FileSize ( FileNameSrc ) ;
off_t FileSizeDst = FileSize ( FileNameDst ) ;
if ( FileSizeSrc ! = FileSizeDst ) {
2013-10-20 09:57:55 +02:00
esyslog ( " ERROR: file size discrepancy: % " PRId64 " != % " PRId64 , FileSizeSrc , FileSizeDst ) ;
2013-10-10 13:13:30 +02:00
break ;
}
}
else {
esyslog ( " ERROR: can't read from source file '%s': %m " , * FileNameSrc ) ;
break ;
}
}
else if ( ( e = d . Next ( ) ) ! = NULL ) {
// We're switching to the next directory entry:
FileNameSrc = AddDirectory ( dirNameSrc , e - > d_name ) ;
FileNameDst = AddDirectory ( dirNameDst , e - > d_name ) ;
struct stat st ;
if ( stat ( FileNameSrc , & st ) < 0 ) {
esyslog ( " ERROR: can't access source file '%s': %m " , * FileNameSrc ) ;
break ;
}
if ( ! ( S_ISREG ( st . st_mode ) | | S_ISLNK ( st . st_mode ) ) ) {
esyslog ( " ERROR: source file '%s' is neither a regular file nor a symbolic link " , * FileNameSrc ) ;
break ;
}
dsyslog ( " copying file '%s' to '%s' " , * FileNameSrc , * FileNameDst ) ;
BufferSize = max ( size_t ( st . st_blksize * 10 ) , size_t ( BUFSIZ ) ) ;
if ( access ( FileNameDst , F_OK ) = = 0 ) {
esyslog ( " ERROR: destination file '%s' already exists " , * FileNameDst ) ;
break ;
}
if ( ( From = open ( FileNameSrc , O_RDONLY ) ) < 0 ) {
esyslog ( " ERROR: can't open source file '%s': %m " , * FileNameSrc ) ;
break ;
}
if ( ( To = open ( FileNameDst , O_WRONLY | O_CREAT | O_EXCL , DEFFILEMODE ) ) < 0 ) {
esyslog ( " ERROR: can't open destination file '%s': %m " , * FileNameDst ) ;
close ( From ) ;
break ;
}
}
else {
// We're done:
dsyslog ( " done copying directory '%s' to '%s' " , * dirNameSrc , * dirNameDst ) ;
return ;
}
}
close ( From ) ; // just to be absolutely sure
close ( To ) ;
esyslog ( " ERROR: copying directory '%s' to '%s' ended prematurely " , * dirNameSrc , * dirNameDst ) ;
}
else
esyslog ( " ERROR: can't open '%s' " , * dirNameSrc ) ;
}
else
esyslog ( " ERROR: can't access '%s' " , * dirNameDst ) ;
error = true ;
}
void cDirCopier : : Stop ( void )
{
Cancel ( 3 ) ;
if ( error ) {
cVideoDirectory : : RemoveVideoFile ( dirNameDst ) ;
2013-10-16 10:24:28 +02:00
Recordings . AddByName ( dirNameSrc ) ;
2013-10-10 13:13:30 +02:00
Recordings . DelByName ( dirNameDst ) ;
}
}
// --- cRecordingsHandlerEntry -----------------------------------------------
class cRecordingsHandlerEntry : public cListObject {
private :
int usage ;
cString fileNameSrc ;
cString fileNameDst ;
cCutter * cutter ;
cDirCopier * copier ;
void ClearPending ( void ) { usage & = ~ ruPending ; }
public :
cRecordingsHandlerEntry ( int Usage , const char * FileNameSrc , const char * FileNameDst ) ;
~ cRecordingsHandlerEntry ( ) ;
int Usage ( const char * FileName = NULL ) const ;
const char * FileNameSrc ( void ) const { return fileNameSrc ; }
const char * FileNameDst ( void ) const { return fileNameDst ; }
bool Active ( bool & Error ) ;
} ;
cRecordingsHandlerEntry : : cRecordingsHandlerEntry ( int Usage , const char * FileNameSrc , const char * FileNameDst )
{
usage = Usage ;
fileNameSrc = FileNameSrc ;
fileNameDst = FileNameDst ;
cutter = NULL ;
copier = NULL ;
}
cRecordingsHandlerEntry : : ~ cRecordingsHandlerEntry ( )
{
delete cutter ;
delete copier ;
}
int cRecordingsHandlerEntry : : Usage ( const char * FileName ) const
{
int u = usage ;
if ( FileName & & * FileName ) {
if ( strcmp ( FileName , fileNameSrc ) = = 0 )
u | = ruSrc ;
else if ( strcmp ( FileName , fileNameDst ) = = 0 )
u | = ruDst ;
}
return u ;
}
bool cRecordingsHandlerEntry : : Active ( bool & Error )
{
bool CopierFinishedOk = false ;
// First test whether there is an ongoing operation:
if ( cutter ) {
if ( cutter - > Active ( ) )
return true ;
Error | = cutter - > Error ( ) ;
delete cutter ;
cutter = NULL ;
}
else if ( copier ) {
if ( copier - > Active ( ) )
return true ;
Error | = copier - > Error ( ) ;
CopierFinishedOk = ! copier - > Error ( ) ;
delete copier ;
copier = NULL ;
}
// Now check if there is something to start:
if ( ( Usage ( ) & ruPending ) ! = 0 ) {
if ( ( Usage ( ) & ruCut ) ! = 0 ) {
cutter = new cCutter ( FileNameSrc ( ) ) ;
cutter - > Start ( ) ;
}
else if ( ( Usage ( ) & ( ruMove | ruCopy ) ) ! = 0 ) {
copier = new cDirCopier ( FileNameSrc ( ) , FileNameDst ( ) ) ;
copier - > Start ( ) ;
}
ClearPending ( ) ;
2013-10-14 09:59:04 +02:00
Recordings . ChangeState ( ) ;
2013-10-10 13:13:30 +02:00
return true ;
}
2013-10-14 09:59:04 +02:00
// Clean up:
if ( CopierFinishedOk & & ( Usage ( ) & ruMove ) ! = 0 ) {
cRecording Recording ( FileNameSrc ( ) ) ;
2014-01-16 11:09:03 +01:00
if ( Recording . Delete ( ) )
2014-01-18 12:54:56 +01:00
Recordings . DelByName ( Recording . FileName ( ) ) ;
2013-10-10 13:13:30 +02:00
}
2013-10-14 09:59:04 +02:00
Recordings . ChangeState ( ) ;
Recordings . TouchUpdate ( ) ;
2013-10-10 13:13:30 +02:00
return false ;
}
// --- cRecordingsHandler ----------------------------------------------------
cRecordingsHandler RecordingsHandler ;
cRecordingsHandler : : cRecordingsHandler ( void )
{
finished = true ;
error = false ;
}
cRecordingsHandler : : ~ cRecordingsHandler ( )
{
}
cRecordingsHandlerEntry * cRecordingsHandler : : Get ( const char * FileName )
{
if ( FileName & & * FileName ) {
for ( cRecordingsHandlerEntry * r = operations . First ( ) ; r ; r = operations . Next ( r ) ) {
if ( strcmp ( FileName , r - > FileNameSrc ( ) ) = = 0 | | strcmp ( FileName , r - > FileNameDst ( ) ) = = 0 )
return r ;
}
}
return NULL ;
}
bool cRecordingsHandler : : Add ( int Usage , const char * FileNameSrc , const char * FileNameDst )
{
dsyslog ( " recordings handler add %d '%s' '%s' " , Usage , FileNameSrc , FileNameDst ) ;
cMutexLock MutexLock ( & mutex ) ;
if ( Usage = = ruCut | | Usage = = ruMove | | Usage = = ruCopy ) {
if ( FileNameSrc & & * FileNameSrc ) {
if ( Usage = = ruCut | | FileNameDst & & * FileNameDst ) {
cString fnd ;
if ( Usage = = ruCut & & ! FileNameDst )
FileNameDst = fnd = cCutter : : EditedFileName ( FileNameSrc ) ;
if ( ! Get ( FileNameSrc ) & & ! Get ( FileNameDst ) ) {
Usage | = ruPending ;
operations . Add ( new cRecordingsHandlerEntry ( Usage , FileNameSrc , FileNameDst ) ) ;
finished = false ;
Active ( ) ; // start it right away if possible
2013-10-14 09:59:04 +02:00
Recordings . ChangeState ( ) ;
2013-10-10 13:13:30 +02:00
return true ;
}
else
esyslog ( " ERROR: file name already present in recordings handler add %d '%s' '%s' " , Usage , FileNameSrc , FileNameDst ) ;
}
else
esyslog ( " ERROR: missing dst file name in recordings handler add %d '%s' '%s' " , Usage , FileNameSrc , FileNameDst ) ;
}
else
esyslog ( " ERROR: missing src file name in recordings handler add %d '%s' '%s' " , Usage , FileNameSrc , FileNameDst ) ;
}
else
esyslog ( " ERROR: invalid usage in recordings handler add %d '%s' '%s' " , Usage , FileNameSrc , FileNameDst ) ;
return false ;
}
void cRecordingsHandler : : Del ( const char * FileName )
{
cMutexLock MutexLock ( & mutex ) ;
2013-10-14 09:59:04 +02:00
if ( cRecordingsHandlerEntry * r = Get ( FileName ) ) {
2013-10-10 13:13:30 +02:00
operations . Del ( r ) ;
2013-10-14 09:59:04 +02:00
Recordings . ChangeState ( ) ;
}
2013-10-10 13:13:30 +02:00
}
void cRecordingsHandler : : DelAll ( void )
{
cMutexLock MutexLock ( & mutex ) ;
operations . Clear ( ) ;
2013-10-14 09:59:04 +02:00
Recordings . ChangeState ( ) ;
2013-10-10 13:13:30 +02:00
}
int cRecordingsHandler : : GetUsage ( const char * FileName )
{
cMutexLock MutexLock ( & mutex ) ;
if ( cRecordingsHandlerEntry * r = Get ( FileName ) )
return r - > Usage ( FileName ) ;
return ruNone ;
}
bool cRecordingsHandler : : Active ( void )
{
cMutexLock MutexLock ( & mutex ) ;
while ( cRecordingsHandlerEntry * r = operations . First ( ) ) {
if ( r - > Active ( error ) )
return true ;
else
operations . Del ( r ) ;
}
return false ;
}
bool cRecordingsHandler : : Finished ( bool & Error )
{
cMutexLock MutexLock ( & mutex ) ;
if ( ! finished & & operations . Count ( ) = = 0 ) {
finished = true ;
Error = error ;
error = false ;
return true ;
}
return false ;
}
2000-12-28 12:57:16 +01:00
// --- cMark -----------------------------------------------------------------
2009-01-06 14:41:11 +01:00
double MarkFramesPerSecond = DEFAULTFRAMESPERSECOND ;
cMutex MutexMarkFramesPerSecond ;
cMark : : cMark ( int Position , const char * Comment , double FramesPerSecond )
2000-12-28 12:57:16 +01:00
{
position = Position ;
2011-08-21 11:34:30 +02:00
comment = Comment ;
2009-01-06 14:41:11 +01:00
framesPerSecond = FramesPerSecond ;
2000-12-28 12:57:16 +01:00
}
cMark : : ~ cMark ( )
{
}
2004-12-26 12:45:22 +01:00
cString cMark : : ToText ( void )
2000-12-28 12:57:16 +01:00
{
2011-08-21 11:34:30 +02:00
return cString : : sprintf ( " %s%s%s \n " , * IndexToHMSF ( position , true , framesPerSecond ) , Comment ( ) ? " " : " " , Comment ( ) ? Comment ( ) : " " ) ;
2000-12-28 12:57:16 +01:00
}
bool cMark : : Parse ( const char * s )
{
comment = NULL ;
2009-01-06 14:41:11 +01:00
framesPerSecond = MarkFramesPerSecond ;
position = HMSFToIndex ( s , framesPerSecond ) ;
2000-12-28 12:57:16 +01:00
const char * p = strchr ( s , ' ' ) ;
if ( p ) {
p = skipspace ( p ) ;
2003-10-24 15:49:30 +02:00
if ( * p )
2000-12-28 12:57:16 +01:00
comment = strdup ( p ) ;
}
return true ;
}
bool cMark : : Save ( FILE * f )
{
2006-03-26 09:27:30 +02:00
return fprintf ( f , " %s " , * ToText ( ) ) > 0 ;
2000-12-28 12:57:16 +01:00
}
// --- cMarks ----------------------------------------------------------------
2013-10-10 13:13:30 +02:00
cString cMarks : : MarksFileName ( const cRecording * Recording )
{
return AddDirectory ( Recording - > FileName ( ) , Recording - > IsPesRecording ( ) ? MARKSFILESUFFIX " .vdr " : MARKSFILESUFFIX ) ;
}
2009-01-06 14:41:11 +01:00
bool cMarks : : Load ( const char * RecordingFileName , double FramesPerSecond , bool IsPesRecording )
2000-12-28 12:57:16 +01:00
{
2012-10-15 11:23:59 +02:00
recordingFileName = RecordingFileName ;
2011-02-27 13:40:43 +01:00
fileName = AddDirectory ( RecordingFileName , IsPesRecording ? MARKSFILESUFFIX " .vdr " : MARKSFILESUFFIX ) ;
2009-01-06 14:41:11 +01:00
framesPerSecond = FramesPerSecond ;
2012-10-15 11:23:59 +02:00
isPesRecording = IsPesRecording ;
2011-03-20 11:46:58 +01:00
nextUpdate = 0 ;
2011-02-27 13:40:43 +01:00
lastFileTime = - 1 ; // the first call to Load() must take place!
2011-04-17 13:22:44 +02:00
lastChange = 0 ;
2011-02-27 13:40:43 +01:00
return Update ( ) ;
}
bool cMarks : : Update ( void )
{
time_t t = time ( NULL ) ;
2011-03-20 11:46:58 +01:00
if ( t > nextUpdate ) {
time_t LastModified = LastModifiedTime ( fileName ) ;
2011-04-17 13:22:44 +02:00
if ( LastModified ! = lastFileTime ) // change detected, or first run
lastChange = LastModified > 0 ? LastModified : t ;
int d = t - lastChange ;
2011-03-20 11:46:58 +01:00
if ( d < 60 )
d = 1 ; // check frequently if the file has just been modified
else if ( d < 3600 )
d = 10 ; // older files are checked less frequently
else
d / = 360 ; // phase out checking for very old files
nextUpdate = t + d ;
2011-04-17 13:22:44 +02:00
if ( LastModified ! = lastFileTime ) { // change detected, or first run
2011-03-20 11:46:58 +01:00
lastFileTime = LastModified ;
2011-04-17 13:22:44 +02:00
if ( lastFileTime = = t )
lastFileTime - - ; // make sure we don't miss updates in the remaining second
2011-02-27 13:40:43 +01:00
cMutexLock MutexLock ( & MutexMarkFramesPerSecond ) ;
MarkFramesPerSecond = framesPerSecond ;
if ( cConfig < cMark > : : Load ( fileName ) ) {
2012-10-15 11:23:59 +02:00
Align ( ) ;
2011-02-27 13:40:43 +01:00
Sort ( ) ;
return true ;
}
}
2000-12-28 12:57:16 +01:00
}
return false ;
}
2013-02-11 11:27:34 +01:00
bool cMarks : : Save ( void )
{
if ( cConfig < cMark > : : Save ( ) ) {
lastFileTime = LastModifiedTime ( fileName ) ;
return true ;
}
return false ;
}
2012-10-15 11:23:59 +02:00
void cMarks : : Align ( void )
{
2012-11-03 11:25:13 +01:00
cIndexFile IndexFile ( recordingFileName , false , isPesRecording ) ;
2012-10-15 11:23:59 +02:00
for ( cMark * m = First ( ) ; m ; m = Next ( m ) ) {
int p = IndexFile . GetClosestIFrame ( m - > Position ( ) ) ;
if ( int d = m - > Position ( ) - p ) {
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 " : " " ) ;
m - > SetPosition ( p ) ;
}
}
}
2000-12-28 12:57:16 +01:00
void cMarks : : Sort ( void )
{
for ( cMark * m1 = First ( ) ; m1 ; m1 = Next ( m1 ) ) {
for ( cMark * m2 = Next ( m1 ) ; m2 ; m2 = Next ( m2 ) ) {
2011-08-21 11:34:30 +02:00
if ( m2 - > Position ( ) < m1 - > Position ( ) ) {
2000-12-28 12:57:16 +01:00
swap ( m1 - > position , m2 - > position ) ;
swap ( m1 - > comment , m2 - > comment ) ;
}
}
}
}
2012-11-12 14:51:18 +01:00
void cMarks : : Add ( int Position )
2000-12-28 12:57:16 +01:00
{
2012-11-12 14:51:18 +01:00
cConfig < cMark > : : Add ( new cMark ( Position , NULL , framesPerSecond ) ) ;
Sort ( ) ;
2000-12-28 12:57:16 +01:00
}
cMark * cMarks : : Get ( int Position )
{
for ( cMark * mi = First ( ) ; mi ; mi = Next ( mi ) ) {
2011-08-21 11:34:30 +02:00
if ( mi - > Position ( ) = = Position )
2000-12-28 12:57:16 +01:00
return mi ;
}
return NULL ;
}
cMark * cMarks : : GetPrev ( int Position )
{
for ( cMark * mi = Last ( ) ; mi ; mi = Prev ( mi ) ) {
2011-08-21 11:34:30 +02:00
if ( mi - > Position ( ) < Position )
2000-12-28 12:57:16 +01:00
return mi ;
}
return NULL ;
}
cMark * cMarks : : GetNext ( int Position )
{
for ( cMark * mi = First ( ) ; mi ; mi = Next ( mi ) ) {
2011-08-21 11:34:30 +02:00
if ( mi - > Position ( ) > Position )
2000-12-28 12:57:16 +01:00
return mi ;
}
return NULL ;
}
2012-11-18 12:19:51 +01:00
cMark * cMarks : : GetNextBegin ( cMark * EndMark )
{
cMark * BeginMark = EndMark ? Next ( EndMark ) : First ( ) ;
if ( BeginMark ) {
while ( cMark * NextMark = Next ( BeginMark ) ) {
if ( BeginMark - > Position ( ) = = NextMark - > Position ( ) ) { // skip Begin/End at the same position
if ( ! ( BeginMark = Next ( NextMark ) ) )
break ;
}
else
break ;
}
}
return BeginMark ;
}
cMark * cMarks : : GetNextEnd ( cMark * BeginMark )
{
if ( ! BeginMark )
return NULL ;
cMark * EndMark = Next ( BeginMark ) ;
if ( EndMark ) {
while ( cMark * NextMark = Next ( EndMark ) ) {
if ( EndMark - > Position ( ) = = NextMark - > Position ( ) ) { // skip End/Begin at the same position
if ( ! ( EndMark = Next ( NextMark ) ) )
break ;
}
else
break ;
}
}
return EndMark ;
}
int cMarks : : GetNumSequences ( void )
{
int NumSequences = 0 ;
if ( cMark * BeginMark = GetNextBegin ( ) ) {
while ( cMark * EndMark = GetNextEnd ( BeginMark ) ) {
NumSequences + + ;
BeginMark = GetNextBegin ( EndMark ) ;
}
2012-11-26 09:39:59 +01:00
if ( BeginMark ) {
NumSequences + + ; // the last sequence had no actual "end" mark
if ( NumSequences = = 1 & & BeginMark - > Position ( ) = = 0 )
NumSequences = 0 ; // there is only one actual "begin" mark at offset zero, and no actual "end" mark
}
2012-11-18 12:19:51 +01:00
}
return NumSequences ;
}
2001-09-23 14:02:11 +02:00
// --- cRecordingUserCommand -------------------------------------------------
const char * cRecordingUserCommand : : command = NULL ;
2012-06-02 13:57:41 +02:00
void cRecordingUserCommand : : InvokeCommand ( const char * State , const char * RecordingFileName , const char * SourceFileName )
2001-09-23 14:02:11 +02:00
{
if ( command ) {
2012-06-02 13:57:41 +02:00
cString cmd ;
if ( SourceFileName )
cmd = cString : : sprintf ( " %s %s \" %s \" \" %s \" " , command , State , * strescape ( RecordingFileName , " \\ \" $ " ) , * strescape ( SourceFileName , " \\ \" $ " ) ) ;
else
cmd = cString : : sprintf ( " %s %s \" %s \" " , command , State , * strescape ( RecordingFileName , " \\ \" $ " ) ) ;
isyslog ( " executing '%s' " , * cmd ) ;
SystemExec ( cmd ) ;
}
2001-09-23 14:02:11 +02:00
}
2002-06-16 12:57:31 +02:00
2009-11-22 11:30:27 +01:00
// --- cIndexFileGenerator ---------------------------------------------------
# define IFG_BUFFER_SIZE KILOBYTE(100)
class cIndexFileGenerator : public cThread {
private :
cString recordingName ;
protected :
virtual void Action ( void ) ;
public :
cIndexFileGenerator ( const char * RecordingName ) ;
~ cIndexFileGenerator ( ) ;
} ;
cIndexFileGenerator : : cIndexFileGenerator ( const char * RecordingName )
: cThread ( " index file generator " )
, recordingName ( RecordingName )
{
Start ( ) ;
}
cIndexFileGenerator : : ~ cIndexFileGenerator ( )
{
Cancel ( 3 ) ;
}
void cIndexFileGenerator : : Action ( void )
{
bool IndexFileComplete = false ;
2012-09-13 11:18:53 +02:00
bool IndexFileWritten = false ;
2009-11-22 11:30:27 +01:00
bool Rewind = false ;
cFileName FileName ( recordingName , false ) ;
cUnbufferedFile * ReplayFile = FileName . Open ( ) ;
2011-09-04 10:13:14 +02:00
cRingBufferLinear Buffer ( IFG_BUFFER_SIZE , MIN_TS_PACKETS_FOR_FRAME_DETECTOR * TS_SIZE ) ;
2009-11-22 11:30:27 +01:00
cPatPmtParser PatPmtParser ;
cFrameDetector FrameDetector ;
cIndexFile IndexFile ( recordingName , true ) ;
int BufferChunks = KILOBYTE ( 1 ) ; // no need to read a lot at the beginning when parsing PAT/PMT
off_t FileSize = 0 ;
off_t FrameOffset = - 1 ;
Skins . QueueMessage ( mtInfo , tr ( " Regenerating index file " ) ) ;
while ( Running ( ) ) {
// Rewind input file:
if ( Rewind ) {
ReplayFile = FileName . SetOffset ( 1 ) ;
Buffer . Clear ( ) ;
Rewind = false ;
}
// Process data:
int Length ;
uchar * Data = Buffer . Get ( Length ) ;
if ( Data ) {
if ( FrameDetector . Synced ( ) ) {
// Step 3 - generate the index:
2011-09-04 10:13:14 +02:00
if ( TsPid ( Data ) = = PATPID )
2009-11-22 11:30:27 +01:00
FrameOffset = FileSize ; // the PAT/PMT is at the beginning of an I-frame
int Processed = FrameDetector . Analyze ( Data , Length ) ;
if ( Processed > 0 ) {
if ( FrameDetector . NewFrame ( ) ) {
2011-09-04 10:13:14 +02:00
IndexFile . Write ( FrameDetector . IndependentFrame ( ) , FileName . Number ( ) , FrameOffset > = 0 ? FrameOffset : FileSize ) ;
2009-11-22 11:30:27 +01:00
FrameOffset = - 1 ;
2012-09-13 11:18:53 +02:00
IndexFileWritten = true ;
2009-11-22 11:30:27 +01:00
}
FileSize + = Processed ;
Buffer . Del ( Processed ) ;
}
}
else if ( PatPmtParser . Vpid ( ) ) {
// Step 2 - sync FrameDetector:
int Processed = FrameDetector . Analyze ( Data , Length ) ;
if ( Processed > 0 ) {
if ( FrameDetector . Synced ( ) ) {
// Synced FrameDetector, so rewind for actual processing:
Rewind = true ;
}
Buffer . Del ( Processed ) ;
}
}
else {
// Step 1 - parse PAT/PMT:
uchar * p = Data ;
while ( Length > = TS_SIZE ) {
int Pid = TsPid ( p ) ;
2012-11-12 14:50:02 +01:00
if ( Pid = = PATPID )
2009-11-22 11:30:27 +01:00
PatPmtParser . ParsePat ( p , TS_SIZE ) ;
2012-11-19 10:32:31 +01:00
else if ( PatPmtParser . IsPmtPid ( Pid ) )
2009-11-22 11:30:27 +01:00
PatPmtParser . ParsePmt ( p , TS_SIZE ) ;
Length - = TS_SIZE ;
p + = TS_SIZE ;
if ( PatPmtParser . Vpid ( ) ) {
// Found Vpid, so rewind to sync FrameDetector:
FrameDetector . SetPid ( PatPmtParser . Vpid ( ) , PatPmtParser . Vtype ( ) ) ;
BufferChunks = IFG_BUFFER_SIZE ;
Rewind = true ;
break ;
}
}
Buffer . Del ( p - Data ) ;
}
}
// Read data:
else if ( ReplayFile ) {
int Result = Buffer . Read ( ReplayFile , BufferChunks ) ;
2009-11-22 19:55:45 +01:00
if ( Result = = 0 ) { // EOF
2009-11-22 11:30:27 +01:00
ReplayFile = FileName . NextFile ( ) ;
2009-11-22 19:55:45 +01:00
FileSize = 0 ;
FrameOffset = - 1 ;
2012-11-04 15:27:44 +01:00
Buffer . Clear ( ) ;
2009-11-22 19:55:45 +01:00
}
2009-11-22 11:30:27 +01:00
}
// Recording has been processed:
else {
IndexFileComplete = true ;
break ;
}
}
2012-09-13 11:18:53 +02:00
if ( IndexFileComplete ) {
if ( IndexFileWritten ) {
2012-12-05 10:37:41 +01:00
cRecordingInfo RecordingInfo ( recordingName ) ;
if ( RecordingInfo . Read ( ) ) {
if ( FrameDetector . FramesPerSecond ( ) > 0 & & ! DoubleEqual ( RecordingInfo . FramesPerSecond ( ) , FrameDetector . FramesPerSecond ( ) ) ) {
RecordingInfo . SetFramesPerSecond ( FrameDetector . FramesPerSecond ( ) ) ;
RecordingInfo . Write ( ) ;
Recordings . UpdateByName ( recordingName ) ;
}
}
2012-09-13 11:18:53 +02:00
Skins . QueueMessage ( mtInfo , tr ( " Index file regeneration complete " ) ) ;
return ;
}
else
Skins . QueueMessage ( mtError , tr ( " Index file regeneration failed! " ) ) ;
}
2009-11-22 11:30:27 +01:00
// Delete the index file if the recording has not been processed entirely:
2012-09-13 11:18:53 +02:00
IndexFile . Delete ( ) ;
2009-11-22 11:30:27 +01:00
}
2002-06-16 12:57:31 +02:00
// --- cIndexFile ------------------------------------------------------------
2009-01-06 14:41:11 +01:00
# define INDEXFILESUFFIX " / index"
2002-06-16 12:57:31 +02:00
2003-05-30 13:23:54 +02:00
// The maximum time to wait before giving up while catching up on an index file:
2012-12-24 09:00:55 +01:00
# define MAXINDEXCATCHUP 8 // number of retries
# define INDEXCATCHUPWAIT 100 // milliseconds
2003-05-30 13:23:54 +02:00
2009-01-06 14:41:11 +01:00
struct tIndexPes {
uint32_t offset ;
uchar type ;
uchar number ;
uint16_t reserved ;
} ;
struct tIndexTs {
uint64_t offset : 40 ; // up to 1TB per file (not using off_t here - must definitely be exactly 64 bit!)
int reserved : 7 ; // reserved for future use
int independent : 1 ; // marks frames that can be displayed by themselves (for trick modes)
uint16_t number : 16 ; // up to 64K files per recording
tIndexTs ( off_t Offset , bool Independent , uint16_t Number )
{
offset = Offset ;
reserved = 0 ;
independent = Independent ;
number = Number ;
}
} ;
2009-11-22 11:30:27 +01:00
# define MAXWAITFORINDEXFILE 10 // max. time to wait for the regenerated index file (seconds)
# define INDEXFILECHECKINTERVAL 500 // ms between checks for existence of the regenerated index file
2012-02-19 11:50:20 +01:00
# define INDEXFILETESTINTERVAL 10 // ms between tests for the size of the index file in case of pausing live video
2009-11-22 11:30:27 +01:00
2012-02-19 11:50:20 +01:00
cIndexFile : : cIndexFile ( const char * FileName , bool Record , bool IsPesRecording , bool PauseLive )
2009-01-06 14:41:11 +01:00
: resumeFile ( FileName , IsPesRecording )
2002-06-16 12:57:31 +02:00
{
f = - 1 ;
size = 0 ;
last = - 1 ;
index = NULL ;
2009-01-06 14:41:11 +01:00
isPesRecording = IsPesRecording ;
2009-11-22 11:30:27 +01:00
indexFileGenerator = NULL ;
2002-06-16 12:57:31 +02:00
if ( FileName ) {
2011-08-13 11:16:41 +02:00
fileName = IndexFileName ( FileName , isPesRecording ) ;
2012-02-19 11:50:20 +01:00
if ( ! Record & & PauseLive ) {
// Wait until the index file contains at least two frames:
time_t tmax = time ( NULL ) + MAXWAITFORINDEXFILE ;
2012-02-26 14:02:17 +01:00
while ( time ( NULL ) < tmax & & FileSize ( fileName ) < off_t ( 2 * sizeof ( tIndexTs ) ) )
2012-02-19 11:50:20 +01:00
cCondWait : : SleepMs ( INDEXFILETESTINTERVAL ) ;
}
2011-08-13 11:16:41 +02:00
int delta = 0 ;
if ( ! Record & & access ( fileName , R_OK ) ! = 0 ) {
// Index file doesn't exist, so try to regenerate it:
if ( ! isPesRecording ) { // sorry, can only do this for TS recordings
resumeFile . Delete ( ) ; // just in case
indexFileGenerator = new cIndexFileGenerator ( FileName ) ;
// Wait until the index file exists:
time_t tmax = time ( NULL ) + MAXWAITFORINDEXFILE ;
do {
cCondWait : : SleepMs ( INDEXFILECHECKINTERVAL ) ; // start with a sleep, to give it a head start
} while ( access ( fileName , R_OK ) ! = 0 & & time ( NULL ) < tmax ) ;
2009-11-22 11:30:27 +01:00
}
2011-08-13 11:16:41 +02:00
}
if ( access ( fileName , R_OK ) = = 0 ) {
struct stat buf ;
if ( stat ( fileName , & buf ) = = 0 ) {
delta = int ( buf . st_size % sizeof ( tIndexTs ) ) ;
if ( delta ) {
delta = sizeof ( tIndexTs ) - delta ;
esyslog ( " ERROR: invalid file size (% " PRId64 " ) in '%s' " , buf . st_size , * fileName ) ;
}
last = int ( ( buf . st_size + delta ) / sizeof ( tIndexTs ) - 1 ) ;
if ( ! Record & & last > = 0 ) {
size = last + 1 ;
index = MALLOC ( tIndexTs , size ) ;
if ( index ) {
f = open ( fileName , O_RDONLY ) ;
if ( f > = 0 ) {
if ( safe_read ( f , index , size_t ( buf . st_size ) ) ! = buf . st_size ) {
esyslog ( " ERROR: can't read from file '%s' " , * fileName ) ;
free ( index ) ;
index = NULL ;
2002-06-16 12:57:31 +02:00
}
2011-08-13 11:16:41 +02:00
else if ( isPesRecording )
ConvertFromPes ( index , size ) ;
2013-01-25 14:32:11 +01:00
if ( ! index | | time ( NULL ) - buf . st_mtime > = MININDEXAGE ) {
close ( f ) ;
f = - 1 ;
}
// otherwise we don't close f here, see CatchUp()!
2002-06-16 12:57:31 +02:00
}
else
2011-08-13 11:16:41 +02:00
LOG_ERROR_STR ( * fileName ) ;
2002-06-16 12:57:31 +02:00
}
2011-08-13 11:16:41 +02:00
else
esyslog ( " ERROR: can't allocate %zd bytes for index '%s' " , size * sizeof ( tIndexTs ) , * fileName ) ;
2002-06-16 12:57:31 +02:00
}
}
2011-08-13 11:16:41 +02:00
else
LOG_ERROR ;
}
else if ( ! Record )
isyslog ( " missing index file %s " , * fileName ) ;
if ( Record ) {
if ( ( f = open ( fileName , O_WRONLY | O_CREAT | O_APPEND , DEFFILEMODE ) ) > = 0 ) {
if ( delta ) {
esyslog ( " ERROR: padding index file with %d '0' bytes " , delta ) ;
while ( delta - - )
writechar ( f , 0 ) ;
2002-06-16 12:57:31 +02:00
}
}
2011-08-13 11:16:41 +02:00
else
LOG_ERROR_STR ( * fileName ) ;
2002-06-16 12:57:31 +02:00
}
}
}
cIndexFile : : ~ cIndexFile ( )
{
if ( f > = 0 )
close ( f ) ;
2002-08-11 13:32:23 +02:00
free ( index ) ;
2009-11-22 11:30:27 +01:00
delete indexFileGenerator ;
2002-06-16 12:57:31 +02:00
}
2011-08-13 11:16:41 +02:00
cString cIndexFile : : IndexFileName ( const char * FileName , bool IsPesRecording )
{
return cString : : sprintf ( " %s%s " , FileName , IsPesRecording ? INDEXFILESUFFIX " .vdr " : INDEXFILESUFFIX ) ;
}
2009-01-06 14:41:11 +01:00
void cIndexFile : : ConvertFromPes ( tIndexTs * IndexTs , int Count )
{
tIndexPes IndexPes ;
while ( Count - - > 0 ) {
memcpy ( & IndexPes , IndexTs , sizeof ( IndexPes ) ) ;
IndexTs - > offset = IndexPes . offset ;
IndexTs - > independent = IndexPes . type = = 1 ; // I_FRAME
IndexTs - > number = IndexPes . number ;
IndexTs + + ;
}
}
2009-01-24 13:16:43 +01:00
void cIndexFile : : ConvertToPes ( tIndexTs * IndexTs , int Count )
{
tIndexPes IndexPes ;
while ( Count - - > 0 ) {
IndexPes . offset = uint32_t ( IndexTs - > offset ) ;
2009-12-06 12:57:45 +01:00
IndexPes . type = uchar ( IndexTs - > independent ? 1 : 2 ) ; // I_FRAME : "not I_FRAME" (exact frame type doesn't matter)
IndexPes . number = uchar ( IndexTs - > number ) ;
2009-01-24 13:16:43 +01:00
IndexPes . reserved = 0 ;
memcpy ( IndexTs , & IndexPes , sizeof ( * IndexTs ) ) ;
IndexTs + + ;
}
}
2002-06-16 12:57:31 +02:00
bool cIndexFile : : CatchUp ( int Index )
{
2003-03-30 13:31:32 +02:00
// returns true unless something really goes wrong, so that 'index' becomes NULL
2002-06-16 12:57:31 +02:00
if ( index & & f > = 0 ) {
2003-09-09 16:09:05 +02:00
cMutexLock MutexLock ( & mutex ) ;
2012-12-23 13:32:26 +01:00
// Note that CatchUp() is triggered even if Index is 'last' (and thus valid).
// This is done to make absolutely sure we don't miss any data at the very end.
for ( int i = 0 ; i < = MAXINDEXCATCHUP & & ( Index < 0 | | Index > = last ) ; i + + ) {
2003-05-30 13:23:54 +02:00
struct stat buf ;
if ( fstat ( f , & buf ) = = 0 ) {
2009-12-06 12:57:45 +01:00
int newLast = int ( buf . st_size / sizeof ( tIndexTs ) - 1 ) ;
2003-05-30 13:23:54 +02:00
if ( newLast > last ) {
2011-02-25 15:25:42 +01:00
int NewSize = size ;
if ( NewSize < = newLast ) {
NewSize * = 2 ;
if ( NewSize < = newLast )
NewSize = newLast + 1 ;
2003-05-30 13:23:54 +02:00
}
2011-02-25 15:25:42 +01:00
if ( tIndexTs * NewBuffer = ( tIndexTs * ) realloc ( index , NewSize * sizeof ( tIndexTs ) ) ) {
size = NewSize ;
index = NewBuffer ;
2009-01-06 14:41:11 +01:00
int offset = ( last + 1 ) * sizeof ( tIndexTs ) ;
int delta = ( newLast - last ) * sizeof ( tIndexTs ) ;
2003-05-30 13:23:54 +02:00
if ( lseek ( f , offset , SEEK_SET ) = = offset ) {
if ( safe_read ( f , & index [ last + 1 ] , delta ) ! = delta ) {
esyslog ( " ERROR: can't read from index " ) ;
free ( index ) ;
index = NULL ;
close ( f ) ;
f = - 1 ;
break ;
}
2009-01-06 14:41:11 +01:00
if ( isPesRecording )
ConvertFromPes ( & index [ last + 1 ] , newLast - last ) ;
2003-05-30 13:23:54 +02:00
last = newLast ;
}
else
2011-08-13 11:16:41 +02:00
LOG_ERROR_STR ( * fileName ) ;
2003-05-30 13:23:54 +02:00
}
2011-02-25 15:25:42 +01:00
else {
2003-05-30 13:23:54 +02:00
esyslog ( " ERROR: can't realloc() index " ) ;
2011-02-25 15:25:42 +01:00
break ;
}
2003-05-30 13:23:54 +02:00
}
}
else
2011-08-13 11:16:41 +02:00
LOG_ERROR_STR ( * fileName ) ;
2012-12-23 13:32:26 +01:00
if ( Index < last )
2003-05-30 13:23:54 +02:00
break ;
2013-01-25 14:34:08 +01:00
cCondVar CondVar ;
CondVar . TimedWait ( mutex , INDEXCATCHUPWAIT ) ;
2003-05-30 13:23:54 +02:00
}
2002-06-16 12:57:31 +02:00
}
2003-03-30 13:31:32 +02:00
return index ! = NULL ;
2002-06-16 12:57:31 +02:00
}
2009-01-06 14:41:11 +01:00
bool cIndexFile : : Write ( bool Independent , uint16_t FileNumber , off_t FileOffset )
2002-06-16 12:57:31 +02:00
{
if ( f > = 0 ) {
2009-01-06 14:41:11 +01:00
tIndexTs i ( FileOffset , Independent , FileNumber ) ;
2009-01-24 13:16:43 +01:00
if ( isPesRecording )
ConvertToPes ( & i , 1 ) ;
2002-06-16 12:57:31 +02:00
if ( safe_write ( f , & i , sizeof ( i ) ) < 0 ) {
2011-08-13 11:16:41 +02:00
LOG_ERROR_STR ( * fileName ) ;
2002-06-16 12:57:31 +02:00
close ( f ) ;
f = - 1 ;
return false ;
}
last + + ;
}
return f > = 0 ;
}
2009-01-06 14:41:11 +01:00
bool cIndexFile : : Get ( int Index , uint16_t * FileNumber , off_t * FileOffset , bool * Independent , int * Length )
2002-06-16 12:57:31 +02:00
{
2003-03-30 13:31:32 +02:00
if ( CatchUp ( Index ) ) {
2012-11-12 14:50:02 +01:00
if ( Index > = 0 & & Index < = last ) {
2002-06-16 12:57:31 +02:00
* FileNumber = index [ Index ] . number ;
* FileOffset = index [ Index ] . offset ;
2009-01-06 14:41:11 +01:00
if ( Independent )
* Independent = index [ Index ] . independent ;
2002-06-16 12:57:31 +02:00
if ( Length ) {
2012-11-12 14:50:02 +01:00
if ( Index < last ) {
uint16_t fn = index [ Index + 1 ] . number ;
off_t fo = index [ Index + 1 ] . offset ;
if ( fn = = * FileNumber )
* Length = int ( fo - * FileOffset ) ;
else
* Length = - 1 ; // this means "everything up to EOF" (the buffer's Read function will act accordingly)
}
2002-06-16 12:57:31 +02:00
else
2012-11-12 14:50:02 +01:00
* Length = - 1 ;
2002-06-16 12:57:31 +02:00
}
return true ;
}
}
return false ;
}
2012-03-12 14:53:59 +01:00
int cIndexFile : : GetNextIFrame ( int Index , bool Forward , uint16_t * FileNumber , off_t * FileOffset , int * Length )
2002-06-16 12:57:31 +02:00
{
2003-03-30 13:31:32 +02:00
if ( CatchUp ( ) ) {
2002-06-16 12:57:31 +02:00
int d = Forward ? 1 : - 1 ;
for ( ; ; ) {
Index + = d ;
2012-11-12 14:50:02 +01:00
if ( Index > = 0 & & Index < = last ) {
2009-01-06 14:41:11 +01:00
if ( index [ Index ] . independent ) {
uint16_t fn ;
if ( ! FileNumber )
FileNumber = & fn ;
off_t fo ;
if ( ! FileOffset )
FileOffset = & fo ;
* FileNumber = index [ Index ] . number ;
* FileOffset = index [ Index ] . offset ;
2002-06-16 12:57:31 +02:00
if ( Length ) {
2012-12-23 13:37:13 +01:00
if ( Index < last ) {
uint16_t fn = index [ Index + 1 ] . number ;
off_t fo = index [ Index + 1 ] . offset ;
if ( fn = = * FileNumber )
* Length = int ( fo - * FileOffset ) ;
else
* Length = - 1 ; // this means "everything up to EOF" (the buffer's Read function will act accordingly)
2002-06-16 12:57:31 +02:00
}
2012-12-23 13:37:13 +01:00
else
* Length = - 1 ;
2002-06-16 12:57:31 +02:00
}
return Index ;
}
}
else
break ;
}
}
return - 1 ;
}
2012-10-15 11:23:59 +02:00
int cIndexFile : : GetClosestIFrame ( int Index )
{
if ( last > 0 ) {
2012-11-12 14:50:02 +01:00
Index = constrain ( Index , 0 , last ) ;
2012-10-15 11:23:59 +02:00
if ( index [ Index ] . independent )
return Index ;
int il = Index - 1 ;
int ih = Index + 1 ;
for ( ; ; ) {
if ( il > = 0 ) {
if ( index [ il ] . independent )
return il ;
il - - ;
}
2012-11-12 14:50:02 +01:00
else if ( ih > last )
2012-10-15 11:23:59 +02:00
break ;
2012-11-12 14:50:02 +01:00
if ( ih < = last ) {
2012-10-15 11:23:59 +02:00
if ( index [ ih ] . independent )
return ih ;
ih + + ;
}
else if ( il < 0 )
break ;
}
}
return 0 ;
}
2009-01-06 14:41:11 +01:00
int cIndexFile : : Get ( uint16_t FileNumber , off_t FileOffset )
2002-06-16 12:57:31 +02:00
{
2003-03-30 13:31:32 +02:00
if ( CatchUp ( ) ) {
2002-06-16 12:57:31 +02:00
//TODO implement binary search!
int i ;
2012-11-12 14:50:02 +01:00
for ( i = 0 ; i < = last ; i + + ) {
2009-01-06 14:41:11 +01:00
if ( index [ i ] . number > FileNumber | | ( index [ i ] . number = = FileNumber ) & & off_t ( index [ i ] . offset ) > = FileOffset )
2002-06-16 12:57:31 +02:00
break ;
}
return i ;
}
return - 1 ;
}
2006-04-09 13:57:39 +02:00
bool cIndexFile : : IsStillRecording ( )
{
return f > = 0 ;
}
2009-11-22 11:30:27 +01:00
void cIndexFile : : Delete ( void )
{
2011-08-13 11:16:41 +02:00
if ( * fileName ) {
dsyslog ( " deleting index file '%s' " , * fileName ) ;
2009-11-22 11:30:27 +01:00
if ( f > = 0 ) {
close ( f ) ;
f = - 1 ;
}
unlink ( fileName ) ;
}
}
2011-08-13 12:45:42 +02:00
int cIndexFile : : GetLength ( const char * FileName , bool IsPesRecording )
{
struct stat buf ;
cString s = IndexFileName ( FileName , IsPesRecording ) ;
if ( * s & & stat ( s , & buf ) = = 0 )
return buf . st_size / ( IsPesRecording ? sizeof ( tIndexTs ) : sizeof ( tIndexPes ) ) ;
return - 1 ;
}
2013-02-17 13:19:36 +01:00
bool GenerateIndex ( const char * FileName )
2010-01-02 14:02:48 +01:00
{
if ( DirectoryOk ( FileName ) ) {
cRecording Recording ( FileName ) ;
if ( Recording . Name ( ) ) {
if ( ! Recording . IsPesRecording ( ) ) {
cString IndexFileName = AddDirectory ( FileName , INDEXFILESUFFIX ) ;
unlink ( IndexFileName ) ;
cIndexFileGenerator * IndexFileGenerator = new cIndexFileGenerator ( FileName ) ;
while ( IndexFileGenerator - > Active ( ) )
cCondWait : : SleepMs ( INDEXFILECHECKINTERVAL ) ;
if ( access ( IndexFileName , R_OK ) = = 0 )
return true ;
else
fprintf ( stderr , " cannot create '%s' \n " , * IndexFileName ) ;
}
else
fprintf ( stderr , " '%s' is not a TS recording \n " , FileName ) ;
}
else
fprintf ( stderr , " '%s' is not a recording \n " , FileName ) ;
}
else
fprintf ( stderr , " '%s' is not a directory \n " , FileName ) ;
return false ;
}
2002-06-16 12:57:31 +02:00
// --- cFileName -------------------------------------------------------------
2009-01-06 14:41:11 +01:00
# define MAXFILESPERRECORDINGPES 255
# define RECORDFILESUFFIXPES " / %03d.vdr"
# define MAXFILESPERRECORDINGTS 65535
# define RECORDFILESUFFIXTS " / %05d.ts"
2002-06-16 12:57:31 +02:00
# define RECORDFILESUFFIXLEN 20 // some additional bytes for safety...
2009-01-06 14:41:11 +01:00
cFileName : : cFileName ( const char * FileName , bool Record , bool Blocking , bool IsPesRecording )
2002-06-16 12:57:31 +02:00
{
2005-10-31 13:14:26 +01:00
file = NULL ;
2002-06-16 12:57:31 +02:00
fileNumber = 0 ;
record = Record ;
blocking = Blocking ;
2009-01-06 14:41:11 +01:00
isPesRecording = IsPesRecording ;
2002-06-16 12:57:31 +02:00
// Prepare the file name:
2002-10-13 09:11:16 +02:00
fileName = MALLOC ( char , strlen ( FileName ) + RECORDFILESUFFIXLEN ) ;
2002-06-16 12:57:31 +02:00
if ( ! fileName ) {
esyslog ( " ERROR: can't copy file name '%s' " , fileName ) ;
return ;
}
strcpy ( fileName , FileName ) ;
pFileNumber = fileName + strlen ( fileName ) ;
SetOffset ( 1 ) ;
}
cFileName : : ~ cFileName ( )
{
Close ( ) ;
2002-08-11 13:32:23 +02:00
free ( fileName ) ;
2002-06-16 12:57:31 +02:00
}
2009-05-24 15:11:28 +02:00
bool cFileName : : GetLastPatPmtVersions ( int & PatVersion , int & PmtVersion )
{
if ( fileName & & ! isPesRecording ) {
// Find the last recording file:
int Number = 1 ;
for ( ; Number < = MAXFILESPERRECORDINGTS + 1 ; Number + + ) { // +1 to correctly set Number in case there actually are that many files
sprintf ( pFileNumber , RECORDFILESUFFIXTS , Number ) ;
if ( access ( fileName , F_OK ) ! = 0 ) { // file doesn't exist
Number - - ;
break ;
}
}
for ( ; Number > 0 ; Number - - ) {
// Search for a PAT packet from the end of the file:
cPatPmtParser PatPmtParser ;
sprintf ( pFileNumber , RECORDFILESUFFIXTS , Number ) ;
int fd = open ( fileName , O_RDONLY | O_LARGEFILE , DEFFILEMODE ) ;
if ( fd > = 0 ) {
off_t pos = lseek ( fd , - TS_SIZE , SEEK_END ) ;
while ( pos > = 0 ) {
// Read and parse the PAT/PMT:
uchar buf [ TS_SIZE ] ;
while ( read ( fd , buf , sizeof ( buf ) ) = = sizeof ( buf ) ) {
if ( buf [ 0 ] = = TS_SYNC_BYTE ) {
int Pid = TsPid ( buf ) ;
2012-11-12 14:50:02 +01:00
if ( Pid = = PATPID )
2009-05-24 15:11:28 +02:00
PatPmtParser . ParsePat ( buf , sizeof ( buf ) ) ;
2012-11-19 10:32:31 +01:00
else if ( PatPmtParser . IsPmtPid ( Pid ) ) {
2009-05-24 15:11:28 +02:00
PatPmtParser . ParsePmt ( buf , sizeof ( buf ) ) ;
if ( PatPmtParser . GetVersions ( PatVersion , PmtVersion ) ) {
close ( fd ) ;
return true ;
}
}
else
break ; // PAT/PMT is always in one sequence
}
else
return false ;
}
pos = lseek ( fd , pos - TS_SIZE , SEEK_SET ) ;
}
close ( fd ) ;
}
else
break ;
}
}
return false ;
}
2005-10-31 13:14:26 +01:00
cUnbufferedFile * cFileName : : Open ( void )
2002-06-16 12:57:31 +02:00
{
2005-10-31 13:14:26 +01:00
if ( ! file ) {
2002-06-16 12:57:31 +02:00
int BlockingFlag = blocking ? 0 : O_NONBLOCK ;
if ( record ) {
dsyslog ( " recording to '%s' " , fileName ) ;
2013-09-11 12:20:37 +02:00
file = cVideoDirectory : : OpenVideoFile ( fileName , O_RDWR | O_CREAT | O_LARGEFILE | BlockingFlag ) ;
2005-10-31 13:14:26 +01:00
if ( ! file )
2002-06-16 12:57:31 +02:00
LOG_ERROR_STR ( fileName ) ;
}
else {
if ( access ( fileName , R_OK ) = = 0 ) {
dsyslog ( " playing '%s' " , fileName ) ;
2009-01-06 14:41:11 +01:00
file = cUnbufferedFile : : Create ( fileName , O_RDONLY | O_LARGEFILE | BlockingFlag ) ;
2005-10-31 13:14:26 +01:00
if ( ! file )
2002-06-16 12:57:31 +02:00
LOG_ERROR_STR ( fileName ) ;
}
else if ( errno ! = ENOENT )
LOG_ERROR_STR ( fileName ) ;
}
}
return file ;
}
void cFileName : : Close ( void )
{
2005-10-31 13:14:26 +01:00
if ( file ) {
2013-09-11 12:20:37 +02:00
if ( file - > Close ( ) < 0 )
2002-06-16 12:57:31 +02:00
LOG_ERROR_STR ( fileName ) ;
2013-09-11 12:20:37 +02:00
delete file ;
2005-10-31 13:14:26 +01:00
file = NULL ;
2002-06-16 12:57:31 +02:00
}
}
2009-01-06 14:41:11 +01:00
cUnbufferedFile * cFileName : : SetOffset ( int Number , off_t Offset )
2002-06-16 12:57:31 +02:00
{
if ( fileNumber ! = Number )
Close ( ) ;
2009-01-06 14:41:11 +01:00
int MaxFilesPerRecording = isPesRecording ? MAXFILESPERRECORDINGPES : MAXFILESPERRECORDINGTS ;
if ( 0 < Number & & Number < = MaxFilesPerRecording ) {
2009-12-06 12:57:45 +01:00
fileNumber = uint16_t ( Number ) ;
2009-01-06 14:41:11 +01:00
sprintf ( pFileNumber , isPesRecording ? RECORDFILESUFFIXPES : RECORDFILESUFFIXTS , fileNumber ) ;
2002-06-16 12:57:31 +02:00
if ( record ) {
2005-05-07 15:29:23 +02:00
if ( access ( fileName , F_OK ) = = 0 ) {
2009-12-06 12:57:45 +01:00
// file exists, check if it has non-zero size
2005-05-07 15:29:23 +02:00
struct stat buf ;
if ( stat ( fileName , & buf ) = = 0 ) {
if ( buf . st_size ! = 0 )
return SetOffset ( Number + 1 ) ; // file exists and has non zero size, let's try next suffix
else {
// zero size file, remove it
2008-05-22 10:40:08 +02:00
dsyslog ( " cFileName::SetOffset: removing zero-sized file %s " , fileName ) ;
unlink ( fileName ) ;
2005-05-07 15:29:23 +02:00
}
}
else
return SetOffset ( Number + 1 ) ; // error with fstat - should not happen, just to be on the safe side
}
2002-06-16 12:57:31 +02:00
else if ( errno ! = ENOENT ) { // something serious has happened
LOG_ERROR_STR ( fileName ) ;
2005-10-31 13:14:26 +01:00
return NULL ;
2002-06-16 12:57:31 +02:00
}
// found a non existing file suffix
}
if ( Open ( ) > = 0 ) {
2005-11-04 13:19:49 +01:00
if ( ! record & & Offset > = 0 & & file & & file - > Seek ( Offset , SEEK_SET ) ! = Offset ) {
2002-06-16 12:57:31 +02:00
LOG_ERROR_STR ( fileName ) ;
2005-10-31 13:14:26 +01:00
return NULL ;
2002-06-16 12:57:31 +02:00
}
}
return file ;
}
2009-01-06 14:41:11 +01:00
esyslog ( " ERROR: max number of files (%d) exceeded " , MaxFilesPerRecording ) ;
2005-10-31 13:14:26 +01:00
return NULL ;
2002-06-16 12:57:31 +02:00
}
2005-10-31 13:14:26 +01:00
cUnbufferedFile * cFileName : : NextFile ( void )
2002-06-16 12:57:31 +02:00
{
return SetOffset ( fileNumber + 1 ) ;
}
2002-06-22 10:11:59 +02:00
// --- Index stuff -----------------------------------------------------------
2009-01-06 14:41:11 +01:00
cString IndexToHMSF ( int Index , bool WithFrame , double FramesPerSecond )
2002-06-16 12:57:31 +02:00
{
2012-01-14 13:00:47 +01:00
const char * Sign = " " ;
if ( Index < 0 ) {
Index = - Index ;
Sign = " - " ;
}
2009-01-06 14:41:11 +01:00
double Seconds ;
2014-02-06 10:57:45 +01:00
int f = int ( modf ( ( Index + 0.5 ) / FramesPerSecond , & Seconds ) * FramesPerSecond ) ;
2009-01-06 14:41:11 +01:00
int s = int ( Seconds ) ;
2002-06-16 12:57:31 +02:00
int m = s / 60 % 60 ;
int h = s / 3600 ;
s % = 60 ;
2012-01-14 13:00:47 +01:00
return cString : : sprintf ( WithFrame ? " %s%d:%02d:%02d.%02d " : " %s%d:%02d:%02d " , Sign , h , m , s , f ) ;
2002-06-16 12:57:31 +02:00
}
2009-01-06 14:41:11 +01:00
int HMSFToIndex ( const char * HMSF , double FramesPerSecond )
2002-06-16 12:57:31 +02:00
{
2014-02-08 11:16:02 +01:00
int h , m , s , f = 0 ;
2009-01-06 14:41:11 +01:00
int n = sscanf ( HMSF , " %d:%d:%d.%d " , & h , & m , & s , & f ) ;
if ( n = = 1 )
2014-02-06 10:57:45 +01:00
return h ; // plain frame number
2009-01-06 14:41:11 +01:00
if ( n > = 3 )
2014-02-06 10:57:45 +01:00
return int ( round ( ( h * 3600 + m * 60 + s ) * FramesPerSecond ) ) + f ;
2002-06-16 12:57:31 +02:00
return 0 ;
}
2009-01-06 14:41:11 +01:00
int SecondsToFrames ( int Seconds , double FramesPerSecond )
2002-06-16 12:57:31 +02:00
{
2009-01-24 11:42:24 +01:00
return int ( round ( Seconds * FramesPerSecond ) ) ;
2002-06-16 12:57:31 +02:00
}
2002-06-22 10:11:59 +02:00
// --- ReadFrame -------------------------------------------------------------
2005-10-31 13:14:26 +01:00
int ReadFrame ( cUnbufferedFile * f , uchar * b , int Length , int Max )
2002-06-22 10:11:59 +02:00
{
if ( Length = = - 1 )
Length = Max ; // this means we read up to EOF (see cIndex)
else if ( Length > Max ) {
esyslog ( " ERROR: frame larger than buffer (%d > %d) " , Length , Max ) ;
Length = Max ;
}
2005-10-31 13:14:26 +01:00
int r = f - > Read ( b , Length ) ;
2002-06-22 10:11:59 +02:00
if ( r < 0 )
LOG_ERROR ;
return r ;
}
2012-06-09 14:32:29 +02:00
// --- Recordings Sort Mode --------------------------------------------------
eRecordingsSortMode RecordingsSortMode = rsmName ;
bool HasRecordingsSortMode ( const char * Directory )
{
return access ( AddDirectory ( Directory , SORTMODEFILE ) , R_OK ) = = 0 ;
}
void GetRecordingsSortMode ( const char * Directory )
{
if ( FILE * f = fopen ( AddDirectory ( Directory , SORTMODEFILE ) , " r " ) ) {
char buf [ 8 ] ;
if ( fgets ( buf , sizeof ( buf ) , f ) )
RecordingsSortMode = eRecordingsSortMode ( constrain ( atoi ( buf ) , 0 , int ( rsmTime ) ) ) ;
fclose ( f ) ;
}
}
void SetRecordingsSortMode ( const char * Directory , eRecordingsSortMode SortMode )
{
if ( FILE * f = fopen ( AddDirectory ( Directory , SORTMODEFILE ) , " w " ) ) {
fputs ( cString : : sprintf ( " %d \n " , SortMode ) , f ) ;
fclose ( f ) ;
}
}
void IncRecordingsSortMode ( const char * Directory )
{
GetRecordingsSortMode ( Directory ) ;
RecordingsSortMode = eRecordingsSortMode ( int ( RecordingsSortMode ) + 1 ) ;
if ( RecordingsSortMode > rsmTime )
RecordingsSortMode = eRecordingsSortMode ( 0 ) ;
SetRecordingsSortMode ( Directory , RecordingsSortMode ) ;
}