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.
|
|
|
|
|
*
|
2003-11-08 15:45:59 +01:00
|
|
|
|
* $Id: recording.c 1.84.1.2 2003/11/08 15:25:35 kls Exp $
|
2000-03-11 11:22:37 +01:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "recording.h"
|
|
|
|
|
#include <errno.h>
|
2000-07-24 16:43:04 +02:00
|
|
|
|
#include <fcntl.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"
|
2002-01-26 15:25:37 +01:00
|
|
|
|
#include "i18n.h"
|
2000-03-11 11:22:37 +01:00
|
|
|
|
#include "interface.h"
|
2002-06-16 12:57:31 +02:00
|
|
|
|
#include "remux.h" //XXX+ I_FRAME
|
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
|
|
|
|
|
|
|
|
|
#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.
|
|
|
|
|
Unfortunately, because of windows and its brain dead file system, we have
|
|
|
|
|
to use a more complicated approach, in order to allow users who have enabled
|
|
|
|
|
the VFAT compile time option to see their recordings even if they forget to
|
|
|
|
|
enable VFAT when compiling a new version of VDR... Gee, do I hate Windows.
|
|
|
|
|
(kls 2002-07-27)
|
|
|
|
|
#define DATAFORMAT "%4d-%02d-%02d.%02d:%02d.%02d.%02d" RECEXT
|
|
|
|
|
#define NAMEFORMAT "%s/%s/" DATAFORMAT
|
|
|
|
|
*/
|
|
|
|
|
// start of implementation for brain dead systems
|
|
|
|
|
#define DATAFORMAT "%4d-%02d-%02d.%02d%*c%02d.%02d.%02d" RECEXT
|
2001-02-18 16:21:05 +01:00
|
|
|
|
#ifdef VFAT
|
2002-07-27 12:55:14 +02:00
|
|
|
|
#define nameFORMAT "%4d-%02d-%02d.%02d.%02d.%02d.%02d" RECEXT
|
2001-02-18 16:21:05 +01:00
|
|
|
|
#else
|
2002-07-27 12:55:14 +02:00
|
|
|
|
#define nameFORMAT "%4d-%02d-%02d.%02d:%02d.%02d.%02d" RECEXT
|
2001-02-18 16:21:05 +01:00
|
|
|
|
#endif
|
2002-07-27 12:55:14 +02:00
|
|
|
|
#define NAMEFORMAT "%s/%s/" nameFORMAT
|
|
|
|
|
// end of implementation for brain dead systems
|
2000-03-11 11:22:37 +01:00
|
|
|
|
|
2003-04-12 10:06:21 +02:00
|
|
|
|
#define RESUMEFILESUFFIX "/resume%s%s.vdr"
|
2000-07-24 16:43:04 +02:00
|
|
|
|
#define SUMMARYFILESUFFIX "/summary.vdr"
|
2000-12-28 12:57:16 +01:00
|
|
|
|
#define MARKSFILESUFFIX "/marks.vdr"
|
2000-07-24 16:43:04 +02:00
|
|
|
|
|
2003-11-08 15:45:59 +01:00
|
|
|
|
#define FINDCMD "cd '%s' && find '%s' -follow -type d -name '%s' 2> /dev/null"
|
2002-01-27 15:14:45 +01:00
|
|
|
|
|
2000-03-11 11:22:37 +01:00
|
|
|
|
#define MINDISKSPACE 1024 // MB
|
|
|
|
|
|
2001-02-04 12:36:32 +01:00
|
|
|
|
#define DELETEDLIFETIME 1 // hours after which a deleted recording will be actually removed
|
|
|
|
|
#define REMOVECHECKDELTA 3600 // seconds between checks for removing deleted files
|
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
|
2001-02-04 12:36:32 +01:00
|
|
|
|
|
2002-02-03 15:55:04 +01:00
|
|
|
|
#define TIMERMACRO_TITLE "TITLE"
|
|
|
|
|
#define TIMERMACRO_EPISODE "EPISODE"
|
|
|
|
|
|
2003-01-06 15:37:33 +01:00
|
|
|
|
#define MAX_SUBTITLE_LENGTH 40
|
|
|
|
|
|
2001-02-04 12:36:32 +01:00
|
|
|
|
void RemoveDeletedRecordings(void)
|
|
|
|
|
{
|
|
|
|
|
static time_t LastRemoveCheck = 0;
|
|
|
|
|
if (time(NULL) - LastRemoveCheck > REMOVECHECKDELTA) {
|
2001-09-30 10:38:06 +02:00
|
|
|
|
// Make sure only one instance of VDR does this:
|
|
|
|
|
cLockFile LockFile(VideoDirectory);
|
|
|
|
|
if (!LockFile.Lock())
|
|
|
|
|
return;
|
2001-02-04 12:36:32 +01:00
|
|
|
|
// Remove the oldest file that has been "deleted":
|
|
|
|
|
cRecordings Recordings;
|
|
|
|
|
if (Recordings.Load(true)) {
|
|
|
|
|
cRecording *r = Recordings.First();
|
|
|
|
|
cRecording *r0 = r;
|
|
|
|
|
while (r) {
|
|
|
|
|
if (r->start < r0->start)
|
|
|
|
|
r0 = r;
|
|
|
|
|
r = Recordings.Next(r);
|
|
|
|
|
}
|
2003-04-27 15:56:04 +02:00
|
|
|
|
if (r0 && time(NULL) - r0->start > DELETEDLIFETIME * 3600) {
|
2001-02-04 12:36:32 +01:00
|
|
|
|
r0->Remove();
|
2001-02-11 14:53:44 +01:00
|
|
|
|
RemoveEmptyVideoDirectories();
|
2001-02-04 12:36:32 +01:00
|
|
|
|
LastRemoveCheck += REMOVELATENCY;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
LastRemoveCheck = time(NULL);
|
|
|
|
|
}
|
|
|
|
|
}
|
2000-03-11 11:22:37 +01:00
|
|
|
|
|
2001-06-02 10:47:40 +02:00
|
|
|
|
void AssertFreeDiskSpace(int Priority)
|
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;
|
|
|
|
|
if (time(NULL) - LastFreeDiskCheck > DISKCHECKDELTA / Factor) {
|
2000-07-29 15:21:42 +02:00
|
|
|
|
if (!VideoFileSpaceAvailable(MINDISKSPACE)) {
|
2001-09-30 10:38:06 +02:00
|
|
|
|
// Make sure only one instance of VDR does this:
|
|
|
|
|
cLockFile LockFile(VideoDirectory);
|
|
|
|
|
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...");
|
2000-04-15 17:38:11 +02:00
|
|
|
|
cRecordings Recordings;
|
|
|
|
|
if (Recordings.Load(true)) {
|
|
|
|
|
cRecording *r = Recordings.First();
|
|
|
|
|
cRecording *r0 = r;
|
|
|
|
|
while (r) {
|
|
|
|
|
if (r->start < r0->start)
|
|
|
|
|
r0 = r;
|
|
|
|
|
r = Recordings.Next(r);
|
|
|
|
|
}
|
2000-11-18 16:26:50 +01:00
|
|
|
|
if (r0 && r0->Remove()) {
|
2003-08-17 09:18:40 +02:00
|
|
|
|
LastFreeDiskCheck += REMOVELATENCY / Factor;
|
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
|
|
|
|
}
|
|
|
|
|
// No "deleted" files to remove, so let's see if we can delete a recording:
|
2002-05-13 16:35:49 +02:00
|
|
|
|
isyslog("...no deleted recording found, trying to delete an old recording...");
|
2000-04-15 17:38:11 +02:00
|
|
|
|
if (Recordings.Load(false)) {
|
|
|
|
|
cRecording *r = Recordings.First();
|
|
|
|
|
cRecording *r0 = NULL;
|
|
|
|
|
while (r) {
|
2003-10-17 14:40:37 +02:00
|
|
|
|
if (!r->IsEdited() && r->lifetime < MAXLIFETIME) { // edited recordings and recordings with MAXLIFETIME live forever
|
2002-03-07 17:07:04 +01:00
|
|
|
|
if ((r->lifetime == 0 && Priority > r->priority) || // the recording has no guaranteed lifetime and the new recording has higher priority
|
2001-06-12 15:32:47 +02:00
|
|
|
|
(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;
|
2000-04-15 17:38:11 +02:00
|
|
|
|
}
|
2000-03-11 11:22:37 +01:00
|
|
|
|
}
|
2000-04-15 17:38:11 +02:00
|
|
|
|
r = Recordings.Next(r);
|
2000-03-11 11:22:37 +01:00
|
|
|
|
}
|
2000-04-15 17:38:11 +02:00
|
|
|
|
if (r0 && r0->Delete())
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// Unable to free disk space, but there's nothing we can do about that...
|
2002-05-13 16:35:49 +02:00
|
|
|
|
isyslog("...no old recording found, giving up");
|
2002-02-15 22:24:30 +01:00
|
|
|
|
Interface->Confirm(tr("Low disk space!"), 30);
|
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
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2001-02-11 11:04:41 +01:00
|
|
|
|
// --- cResumeFile ------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
cResumeFile::cResumeFile(const char *FileName)
|
|
|
|
|
{
|
2002-08-11 13:32:23 +02:00
|
|
|
|
fileName = MALLOC(char, strlen(FileName) + strlen(RESUMEFILESUFFIX) + 1);
|
2001-02-11 11:04:41 +01:00
|
|
|
|
if (fileName) {
|
|
|
|
|
strcpy(fileName, FileName);
|
2003-04-12 10:06:21 +02:00
|
|
|
|
sprintf(fileName + strlen(fileName), RESUMEFILESUFFIX, 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;
|
|
|
|
|
}
|
2001-02-11 11:04:41 +01:00
|
|
|
|
int f = open(fileName, O_RDONLY);
|
|
|
|
|
if (f >= 0) {
|
2001-08-12 15:22:48 +02:00
|
|
|
|
if (safe_read(f, &resume, sizeof(resume)) != sizeof(resume)) {
|
2001-02-11 11:04:41 +01:00
|
|
|
|
resume = -1;
|
|
|
|
|
LOG_ERROR_STR(fileName);
|
|
|
|
|
}
|
|
|
|
|
close(f);
|
|
|
|
|
}
|
|
|
|
|
else if (errno != ENOENT)
|
|
|
|
|
LOG_ERROR_STR(fileName);
|
|
|
|
|
}
|
|
|
|
|
return resume;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool cResumeFile::Save(int Index)
|
|
|
|
|
{
|
|
|
|
|
if (fileName) {
|
2001-06-02 10:47:40 +02:00
|
|
|
|
int f = open(fileName, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
2001-02-11 11:04:41 +01:00
|
|
|
|
if (f >= 0) {
|
2002-03-23 16:17:39 +01:00
|
|
|
|
if (safe_write(f, &Index, sizeof(Index)) < 0)
|
2001-02-11 11:04:41 +01:00
|
|
|
|
LOG_ERROR_STR(fileName);
|
|
|
|
|
close(f);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void cResumeFile::Delete(void)
|
|
|
|
|
{
|
|
|
|
|
if (fileName) {
|
|
|
|
|
if (remove(fileName) < 0 && errno != ENOENT)
|
|
|
|
|
LOG_ERROR_STR(fileName);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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[] = {
|
2001-09-02 10:28:20 +02:00
|
|
|
|
{ '~', '/' },
|
2001-06-16 10:36:13 +02:00
|
|
|
|
{ ' ', '_' },
|
|
|
|
|
{ '\'', '\x01' },
|
|
|
|
|
{ '/', '\x02' },
|
|
|
|
|
{ 0, 0 }
|
|
|
|
|
};
|
|
|
|
|
|
2002-02-10 14:21:36 +01:00
|
|
|
|
static 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) {
|
2002-02-10 14:21:36 +01:00
|
|
|
|
#ifdef VFAT
|
|
|
|
|
// The VFAT file system can't handle all characters, so we
|
|
|
|
|
// have to take extra efforts to encode/decode them:
|
|
|
|
|
if (ToFileSystem) {
|
|
|
|
|
switch (*p) {
|
|
|
|
|
// characters that can be used "as is":
|
|
|
|
|
case '!':
|
|
|
|
|
case '@':
|
|
|
|
|
case '$':
|
|
|
|
|
case '%':
|
|
|
|
|
case '&':
|
|
|
|
|
case '(':
|
|
|
|
|
case ')':
|
|
|
|
|
case '+':
|
|
|
|
|
case ',':
|
|
|
|
|
case '-':
|
|
|
|
|
case ';':
|
|
|
|
|
case '=':
|
|
|
|
|
case '0' ... '9':
|
|
|
|
|
case 'a' ... 'z':
|
2002-02-24 11:22:30 +01:00
|
|
|
|
case 'A' ... 'Z':
|
|
|
|
|
case '<EFBFBD>': case '<EFBFBD>':
|
|
|
|
|
case '<EFBFBD>': case '<EFBFBD>':
|
|
|
|
|
case '<EFBFBD>': case '<EFBFBD>':
|
|
|
|
|
case '<EFBFBD>':
|
|
|
|
|
break;
|
2002-02-10 14:21:36 +01:00
|
|
|
|
// characters that can be mapped to other characters:
|
|
|
|
|
case ' ': *p = '_'; break;
|
|
|
|
|
case '~': *p = '/'; break;
|
|
|
|
|
// characters that have to be encoded:
|
2002-04-21 14:10:31 +02:00
|
|
|
|
default:
|
|
|
|
|
if (*p != '.' || !*(p + 1) || *(p + 1) == '~') { // Windows can't handle '.' at the end of directory names
|
2002-02-10 14:21:36 +01:00
|
|
|
|
int l = p - s;
|
|
|
|
|
s = (char *)realloc(s, strlen(s) + 10);
|
|
|
|
|
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 {
|
|
|
|
|
switch (*p) {
|
|
|
|
|
// mapped characters:
|
|
|
|
|
case '_': *p = ' '; break;
|
|
|
|
|
case '/': *p = '~'; break;
|
|
|
|
|
// encodes characters:
|
|
|
|
|
case '#': {
|
|
|
|
|
if (strlen(p) > 2) {
|
|
|
|
|
char buf[3];
|
|
|
|
|
sprintf(buf, "%c%c", *(p + 1), *(p + 2));
|
|
|
|
|
unsigned char c = strtol(buf, NULL, 16);
|
|
|
|
|
*p = c;
|
|
|
|
|
memmove(p + 1, p + 3, strlen(p) - 2);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
// backwards compatibility:
|
|
|
|
|
case '\x01': *p = '\''; break;
|
|
|
|
|
case '\x02': *p = '/'; break;
|
|
|
|
|
case '\x03': *p = ':'; break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#else
|
2001-09-02 10:28:20 +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;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2002-02-10 14:21:36 +01:00
|
|
|
|
#endif
|
2001-09-02 10:28:20 +02:00
|
|
|
|
p++;
|
|
|
|
|
}
|
2001-06-16 10:36:13 +02:00
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
|
2002-02-03 15:55:04 +01:00
|
|
|
|
cRecording::cRecording(cTimer *Timer, const char *Title, const char *Subtitle, const char *Summary)
|
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;
|
2001-10-07 11:00:35 +02:00
|
|
|
|
sortBuffer = NULL;
|
2000-03-11 11:22:37 +01:00
|
|
|
|
fileName = NULL;
|
2002-02-03 15:55:04 +01:00
|
|
|
|
name = NULL;
|
|
|
|
|
// set up the actual name:
|
2003-01-06 15:37:33 +01:00
|
|
|
|
const char *OriginalSubtitle = Subtitle;
|
|
|
|
|
char SubtitleBuffer[MAX_SUBTITLE_LENGTH];
|
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 = " ";
|
2003-01-06 15:37:33 +01:00
|
|
|
|
else if (strlen(Subtitle) > MAX_SUBTITLE_LENGTH) {
|
|
|
|
|
// let's make sure the Subtitle doesn't produce too long a file name:
|
|
|
|
|
strn0cpy(SubtitleBuffer, Subtitle, MAX_SUBTITLE_LENGTH);
|
|
|
|
|
Subtitle = SubtitleBuffer;
|
|
|
|
|
}
|
2002-10-20 12:28:55 +02:00
|
|
|
|
char *macroTITLE = strstr(Timer->File(), TIMERMACRO_TITLE);
|
|
|
|
|
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);
|
|
|
|
|
if (Timer->IsSingleEvent()) {
|
|
|
|
|
Timer->SetFile(name); // this was an instant recording, so let's set the actual data
|
|
|
|
|
Timers.Save();
|
|
|
|
|
}
|
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
|
2002-10-20 12:28:55 +02:00
|
|
|
|
asprintf(&name, "%s~%s", Timer->File(), 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();
|
2001-09-02 15:21:54 +02:00
|
|
|
|
// handle summary:
|
2002-10-20 12:28:55 +02:00
|
|
|
|
summary = !isempty(Timer->Summary()) ? strdup(Timer->Summary()) : NULL;
|
2001-09-02 15:21:54 +02:00
|
|
|
|
if (!summary) {
|
2003-01-06 15:37:33 +01:00
|
|
|
|
Subtitle = OriginalSubtitle;
|
2001-09-02 15:21:54 +02:00
|
|
|
|
if (isempty(Subtitle))
|
|
|
|
|
Subtitle = "";
|
|
|
|
|
if (isempty(Summary))
|
|
|
|
|
Summary = "";
|
|
|
|
|
if (*Subtitle || *Summary)
|
2002-03-23 11:37:28 +01:00
|
|
|
|
asprintf(&summary, "%s\n\n%s%s%s", Title, Subtitle, (*Subtitle && *Summary) ? "\n\n" : "", Summary);
|
2001-09-02 15:21:54 +02:00
|
|
|
|
}
|
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;
|
2000-04-24 09:35:29 +02:00
|
|
|
|
titleBuffer = NULL;
|
2001-10-07 11:00:35 +02:00
|
|
|
|
sortBuffer = NULL;
|
2000-03-11 11:22:37 +01:00
|
|
|
|
fileName = strdup(FileName);
|
2000-07-28 13:44:31 +02:00
|
|
|
|
FileName += strlen(VideoDirectory) + 1;
|
2000-03-11 11:22:37 +01:00
|
|
|
|
char *p = strrchr(FileName, '/');
|
|
|
|
|
|
|
|
|
|
name = NULL;
|
2000-07-24 16:43:04 +02:00
|
|
|
|
summary = NULL;
|
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
|
2000-04-15 17:38:11 +02:00
|
|
|
|
if (7 == sscanf(p + 1, DATAFORMAT, &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);
|
2000-03-11 11:22:37 +01:00
|
|
|
|
}
|
2000-07-24 16:43:04 +02:00
|
|
|
|
// read an optional summary file:
|
|
|
|
|
char *SummaryFileName = NULL;
|
|
|
|
|
asprintf(&SummaryFileName, "%s%s", fileName, SUMMARYFILESUFFIX);
|
|
|
|
|
int f = open(SummaryFileName, O_RDONLY);
|
|
|
|
|
if (f >= 0) {
|
|
|
|
|
struct stat buf;
|
|
|
|
|
if (fstat(f, &buf) == 0) {
|
|
|
|
|
int size = buf.st_size;
|
2002-08-11 13:32:23 +02:00
|
|
|
|
summary = MALLOC(char, size + 1); // +1 for terminating 0
|
2000-07-24 16:43:04 +02:00
|
|
|
|
if (summary) {
|
2001-08-12 15:22:48 +02:00
|
|
|
|
int rbytes = safe_read(f, summary, size);
|
2000-07-24 16:43:04 +02:00
|
|
|
|
if (rbytes >= 0) {
|
|
|
|
|
summary[rbytes] = 0;
|
|
|
|
|
if (rbytes != size)
|
2002-05-13 16:35:49 +02:00
|
|
|
|
esyslog("%s: expected %d bytes but read %d", SummaryFileName, size, rbytes);
|
2000-07-24 16:43:04 +02:00
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
LOG_ERROR_STR(SummaryFileName);
|
2002-08-11 13:32:23 +02:00
|
|
|
|
free(summary);
|
2000-07-24 16:43:04 +02:00
|
|
|
|
summary = NULL;
|
|
|
|
|
}
|
2001-06-02 10:47:40 +02:00
|
|
|
|
|
2000-07-24 16:43:04 +02:00
|
|
|
|
}
|
|
|
|
|
else
|
2002-05-13 16:35:49 +02:00
|
|
|
|
esyslog("can't allocate %d byte of memory for summary file '%s'", size + 1, SummaryFileName);
|
2000-07-24 16:43:04 +02:00
|
|
|
|
close(f);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
LOG_ERROR_STR(SummaryFileName);
|
|
|
|
|
}
|
|
|
|
|
else if (errno != ENOENT)
|
|
|
|
|
LOG_ERROR_STR(SummaryFileName);
|
2002-08-11 13:32:23 +02:00
|
|
|
|
free(SummaryFileName);
|
2000-03-11 11:22:37 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cRecording::~cRecording()
|
|
|
|
|
{
|
2002-08-11 13:32:23 +02:00
|
|
|
|
free(titleBuffer);
|
|
|
|
|
free(sortBuffer);
|
|
|
|
|
free(fileName);
|
|
|
|
|
free(name);
|
|
|
|
|
free(summary);
|
2000-03-11 11:22:37 +01:00
|
|
|
|
}
|
|
|
|
|
|
2001-10-07 11:00:35 +02:00
|
|
|
|
char *cRecording::StripEpisodeName(char *s)
|
|
|
|
|
{
|
|
|
|
|
char *t = s, *s1 = NULL, *s2 = NULL;
|
|
|
|
|
while (*t) {
|
|
|
|
|
if (*t == '/') {
|
|
|
|
|
if (s1) {
|
|
|
|
|
if (s2)
|
|
|
|
|
s1 = s2;
|
|
|
|
|
s2 = t;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
s1 = t;
|
|
|
|
|
}
|
|
|
|
|
t++;
|
|
|
|
|
}
|
|
|
|
|
if (s1 && s2)
|
|
|
|
|
memmove(s1 + 1, s2, t - s2 + 1);
|
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char *cRecording::SortName(void)
|
|
|
|
|
{
|
|
|
|
|
if (!sortBuffer) {
|
|
|
|
|
char *s = StripEpisodeName(strdup(FileName() + strlen(VideoDirectory) + 1));
|
|
|
|
|
int l = strxfrm(NULL, s, 0);
|
2002-08-11 13:32:23 +02:00
|
|
|
|
sortBuffer = MALLOC(char, l);
|
2001-10-07 11:00:35 +02:00
|
|
|
|
strxfrm(sortBuffer, s, l);
|
2002-08-11 13:32:23 +02:00
|
|
|
|
free(s);
|
2001-10-07 11:00:35 +02:00
|
|
|
|
}
|
|
|
|
|
return sortBuffer;
|
|
|
|
|
}
|
|
|
|
|
|
2002-01-20 14:05:28 +01:00
|
|
|
|
int cRecording::GetResume(void)
|
|
|
|
|
{
|
|
|
|
|
if (resume == RESUME_NOT_INITIALIZED) {
|
|
|
|
|
cResumeFile ResumeFile(FileName());
|
|
|
|
|
resume = ResumeFile.Read();
|
|
|
|
|
}
|
|
|
|
|
return resume;
|
|
|
|
|
}
|
|
|
|
|
|
2001-10-07 11:00:35 +02:00
|
|
|
|
bool cRecording::operator< (const cListObject &ListObject)
|
|
|
|
|
{
|
|
|
|
|
cRecording *r = (cRecording *)&ListObject;
|
|
|
|
|
return strcasecmp(SortName(), r->SortName()) < 0;
|
|
|
|
|
}
|
|
|
|
|
|
2000-03-11 11:22:37 +01:00
|
|
|
|
const char *cRecording::FileName(void)
|
|
|
|
|
{
|
|
|
|
|
if (!fileName) {
|
2001-10-19 13:22:24 +02:00
|
|
|
|
struct tm tm_r;
|
|
|
|
|
struct tm *t = localtime_r(&start, &tm_r);
|
2002-02-10 14:21:36 +01:00
|
|
|
|
name = ExchangeChars(name, true);
|
2000-07-28 13:44:31 +02:00
|
|
|
|
asprintf(&fileName, NAMEFORMAT, VideoDirectory, name, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, priority, lifetime);
|
2002-02-10 14:21:36 +01:00
|
|
|
|
name = ExchangeChars(name, false);
|
2000-03-11 11:22:37 +01:00
|
|
|
|
}
|
|
|
|
|
return fileName;
|
|
|
|
|
}
|
|
|
|
|
|
2002-01-20 14:05:28 +01:00
|
|
|
|
const char *cRecording::Title(char Delimiter, bool NewIndicator, int Level)
|
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;
|
2002-01-20 14:05:28 +01:00
|
|
|
|
if (Level > 0 && (s = strrchr(name, '~')) != NULL)
|
|
|
|
|
s++;
|
|
|
|
|
else
|
|
|
|
|
s = name;
|
|
|
|
|
asprintf(&titleBuffer, "%02d.%02d%c%02d:%02d%c%c%s",
|
|
|
|
|
t->tm_mday,
|
|
|
|
|
t->tm_mon + 1,
|
|
|
|
|
Delimiter,
|
|
|
|
|
t->tm_hour,
|
|
|
|
|
t->tm_min,
|
|
|
|
|
New,
|
|
|
|
|
Delimiter,
|
|
|
|
|
s);
|
2002-02-10 15:41:23 +01:00
|
|
|
|
// let's not display a trailing '~':
|
|
|
|
|
stripspace(titleBuffer);
|
|
|
|
|
s = &titleBuffer[strlen(titleBuffer) - 1];
|
|
|
|
|
if (*s == '~')
|
|
|
|
|
*s = 0;
|
2002-01-20 14:05:28 +01:00
|
|
|
|
}
|
|
|
|
|
else if (Level < HierarchyLevels()) {
|
|
|
|
|
const char *s = name;
|
|
|
|
|
const char *p = s;
|
|
|
|
|
while (*++s) {
|
|
|
|
|
if (*s == '~') {
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
const char *p = PrefixVideoFileName(FileName(), Prefix);
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2002-01-20 14:05:28 +01:00
|
|
|
|
int cRecording::HierarchyLevels(void)
|
|
|
|
|
{
|
|
|
|
|
const char *s = name;
|
|
|
|
|
int level = 0;
|
|
|
|
|
while (*++s) {
|
|
|
|
|
if (*s == '~')
|
|
|
|
|
level++;
|
|
|
|
|
}
|
|
|
|
|
return level;
|
|
|
|
|
}
|
|
|
|
|
|
2003-10-17 14:40:37 +02:00
|
|
|
|
bool cRecording::IsEdited(void)
|
|
|
|
|
{
|
|
|
|
|
const char *s = strrchr(name, '~');
|
|
|
|
|
s = !s ? name : s + 1;
|
|
|
|
|
return *s == '%';
|
|
|
|
|
}
|
|
|
|
|
|
2000-07-24 16:43:04 +02:00
|
|
|
|
bool cRecording::WriteSummary(void)
|
|
|
|
|
{
|
|
|
|
|
if (summary) {
|
|
|
|
|
char *SummaryFileName = NULL;
|
|
|
|
|
asprintf(&SummaryFileName, "%s%s", fileName, SUMMARYFILESUFFIX);
|
|
|
|
|
FILE *f = fopen(SummaryFileName, "w");
|
|
|
|
|
if (f) {
|
|
|
|
|
if (fputs(summary, f) < 0)
|
|
|
|
|
LOG_ERROR_STR(SummaryFileName);
|
|
|
|
|
fclose(f);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
LOG_ERROR_STR(SummaryFileName);
|
2002-08-11 13:32:23 +02:00
|
|
|
|
free(SummaryFileName);
|
2000-07-24 16:43:04 +02:00
|
|
|
|
}
|
|
|
|
|
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, '.');
|
|
|
|
|
if (strcmp(ext, RECEXT) == 0) {
|
|
|
|
|
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:
|
2002-05-13 16:35:49 +02:00
|
|
|
|
isyslog("removing recording %s", NewName);
|
2002-03-16 12:19:14 +01:00
|
|
|
|
RemoveVideoFile(NewName);
|
|
|
|
|
}
|
2002-05-13 16:35:49 +02:00
|
|
|
|
isyslog("deleting recording %s", FileName());
|
2000-07-29 15:21:42 +02:00
|
|
|
|
result = RenameVideoFile(FileName(), NewName);
|
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());
|
2000-07-29 15:21:42 +02:00
|
|
|
|
return RemoveVideoFile(FileName());
|
2000-03-11 11:22:37 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- cRecordings -----------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
bool cRecordings::Load(bool Deleted)
|
|
|
|
|
{
|
|
|
|
|
Clear();
|
2002-01-27 15:14:45 +01:00
|
|
|
|
bool result = false;
|
|
|
|
|
char *cmd = NULL;
|
2003-11-08 15:45:59 +01:00
|
|
|
|
asprintf(&cmd, FINDCMD, VideoDirectory, VideoDirectory, Deleted ? "*" DELEXT : "*" RECEXT);
|
2002-01-27 15:14:45 +01:00
|
|
|
|
FILE *p = popen(cmd, "r");
|
|
|
|
|
if (p) {
|
|
|
|
|
char *s;
|
|
|
|
|
while ((s = readline(p)) != NULL) {
|
|
|
|
|
cRecording *r = new cRecording(s);
|
|
|
|
|
if (r->Name())
|
|
|
|
|
Add(r);
|
|
|
|
|
else
|
|
|
|
|
delete r;
|
|
|
|
|
}
|
|
|
|
|
pclose(p);
|
|
|
|
|
Sort();
|
|
|
|
|
result = Count() > 0;
|
2000-03-11 11:22:37 +01:00
|
|
|
|
}
|
2002-01-27 15:14:45 +01:00
|
|
|
|
else
|
|
|
|
|
Interface->Error("Error while opening pipe!");
|
2002-08-11 13:32:23 +02:00
|
|
|
|
free(cmd);
|
2000-03-11 11:22:37 +01:00
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2002-01-20 14:05:28 +01:00
|
|
|
|
cRecording *cRecordings::GetByName(const char *FileName)
|
|
|
|
|
{
|
|
|
|
|
for (cRecording *recording = First(); recording; recording = Next(recording)) {
|
|
|
|
|
if (strcmp(recording->FileName(), FileName) == 0)
|
|
|
|
|
return recording;
|
|
|
|
|
}
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2000-12-28 12:57:16 +01:00
|
|
|
|
// --- cMark -----------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
char *cMark::buffer = NULL;
|
|
|
|
|
|
|
|
|
|
cMark::cMark(int Position, const char *Comment)
|
|
|
|
|
{
|
|
|
|
|
position = Position;
|
|
|
|
|
comment = Comment ? strdup(Comment) : NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cMark::~cMark()
|
|
|
|
|
{
|
2002-08-11 13:32:23 +02:00
|
|
|
|
free(comment);
|
2000-12-28 12:57:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const char *cMark::ToText(void)
|
|
|
|
|
{
|
2002-08-11 13:32:23 +02:00
|
|
|
|
free(buffer);
|
2000-12-28 12:57:16 +01:00
|
|
|
|
asprintf(&buffer, "%s%s%s\n", IndexToHMSF(position, true), comment ? " " : "", comment ? comment : "");
|
|
|
|
|
return buffer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool cMark::Parse(const char *s)
|
|
|
|
|
{
|
2002-08-11 13:32:23 +02:00
|
|
|
|
free(comment);
|
2000-12-28 12:57:16 +01:00
|
|
|
|
comment = NULL;
|
|
|
|
|
position = HMSFToIndex(s);
|
|
|
|
|
const char *p = strchr(s, ' ');
|
|
|
|
|
if (p) {
|
|
|
|
|
p = skipspace(p);
|
2003-10-24 15:49:00 +02:00
|
|
|
|
if (*p)
|
2000-12-28 12:57:16 +01:00
|
|
|
|
comment = strdup(p);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool cMark::Save(FILE *f)
|
|
|
|
|
{
|
|
|
|
|
return fprintf(f, ToText()) > 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- cMarks ----------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
bool cMarks::Load(const char *RecordingFileName)
|
|
|
|
|
{
|
|
|
|
|
const char *MarksFile = AddDirectory(RecordingFileName, MARKSFILESUFFIX);
|
2001-01-01 14:48:03 +01:00
|
|
|
|
if (cConfig<cMark>::Load(MarksFile)) {
|
2000-12-28 12:57:16 +01:00
|
|
|
|
Sort();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void cMarks::Sort(void)
|
|
|
|
|
{
|
|
|
|
|
for (cMark *m1 = First(); m1; m1 = Next(m1)) {
|
|
|
|
|
for (cMark *m2 = Next(m1); m2; m2 = Next(m2)) {
|
|
|
|
|
if (m2->position < m1->position) {
|
|
|
|
|
swap(m1->position, m2->position);
|
|
|
|
|
swap(m1->comment, m2->comment);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cMark *cMarks::Add(int Position)
|
|
|
|
|
{
|
|
|
|
|
cMark *m = Get(Position);
|
|
|
|
|
if (!m) {
|
2001-01-01 14:48:03 +01:00
|
|
|
|
cConfig<cMark>::Add(m = new cMark(Position));
|
2000-12-28 12:57:16 +01:00
|
|
|
|
Sort();
|
|
|
|
|
}
|
|
|
|
|
return m;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cMark *cMarks::Get(int Position)
|
|
|
|
|
{
|
|
|
|
|
for (cMark *mi = First(); mi; mi = Next(mi)) {
|
|
|
|
|
if (mi->position == Position)
|
|
|
|
|
return mi;
|
|
|
|
|
}
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cMark *cMarks::GetPrev(int Position)
|
|
|
|
|
{
|
|
|
|
|
for (cMark *mi = Last(); mi; mi = Prev(mi)) {
|
|
|
|
|
if (mi->position < Position)
|
|
|
|
|
return mi;
|
|
|
|
|
}
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cMark *cMarks::GetNext(int Position)
|
|
|
|
|
{
|
|
|
|
|
for (cMark *mi = First(); mi; mi = Next(mi)) {
|
|
|
|
|
if (mi->position > Position)
|
|
|
|
|
return mi;
|
|
|
|
|
}
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2001-09-23 14:02:11 +02:00
|
|
|
|
// --- cRecordingUserCommand -------------------------------------------------
|
|
|
|
|
|
|
|
|
|
const char *cRecordingUserCommand::command = NULL;
|
|
|
|
|
|
|
|
|
|
void cRecordingUserCommand::InvokeCommand(const char *State, const char *RecordingFileName)
|
|
|
|
|
{
|
|
|
|
|
if (command) {
|
|
|
|
|
char *cmd;
|
2002-01-26 12:04:32 +01:00
|
|
|
|
asprintf(&cmd, "%s %s \"%s\"", command, State, strescape(RecordingFileName, "\"$"));
|
2002-05-13 16:35:49 +02:00
|
|
|
|
isyslog("executing '%s'", cmd);
|
2001-10-20 10:39:27 +02:00
|
|
|
|
SystemExec(cmd);
|
2002-08-11 13:32:23 +02:00
|
|
|
|
free(cmd);
|
2001-09-23 14:02:11 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2002-06-16 12:57:31 +02:00
|
|
|
|
|
|
|
|
|
// --- XXX+
|
|
|
|
|
|
|
|
|
|
//XXX+ somewhere else???
|
|
|
|
|
// --- cIndexFile ------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
#define INDEXFILESUFFIX "/index.vdr"
|
|
|
|
|
|
2003-05-30 13:23:54 +02:00
|
|
|
|
// The number of frames to stay off the end in case of time shift:
|
2003-08-15 15:24:54 +02:00
|
|
|
|
#define INDEXSAFETYLIMIT 150 // frames
|
2003-05-30 13:23:54 +02:00
|
|
|
|
|
|
|
|
|
// The maximum time to wait before giving up while catching up on an index file:
|
|
|
|
|
#define MAXINDEXCATCHUP 8 // seconds
|
|
|
|
|
|
2002-08-24 15:08:25 +02:00
|
|
|
|
// The minimum age of an index file for considering it no longer to be written:
|
2003-05-11 13:09:28 +02:00
|
|
|
|
#define MININDEXAGE 3600 // seconds
|
2002-08-24 15:08:25 +02:00
|
|
|
|
|
2002-06-16 12:57:31 +02:00
|
|
|
|
cIndexFile::cIndexFile(const char *FileName, bool Record)
|
|
|
|
|
:resumeFile(FileName)
|
|
|
|
|
{
|
|
|
|
|
f = -1;
|
|
|
|
|
fileName = NULL;
|
|
|
|
|
size = 0;
|
|
|
|
|
last = -1;
|
|
|
|
|
index = NULL;
|
|
|
|
|
if (FileName) {
|
2002-10-13 09:11:16 +02:00
|
|
|
|
fileName = MALLOC(char, strlen(FileName) + strlen(INDEXFILESUFFIX) + 1);
|
2002-06-16 12:57:31 +02:00
|
|
|
|
if (fileName) {
|
|
|
|
|
strcpy(fileName, FileName);
|
|
|
|
|
char *pFileExt = fileName + strlen(fileName);
|
|
|
|
|
strcpy(pFileExt, INDEXFILESUFFIX);
|
|
|
|
|
int delta = 0;
|
|
|
|
|
if (access(fileName, R_OK) == 0) {
|
|
|
|
|
struct stat buf;
|
|
|
|
|
if (stat(fileName, &buf) == 0) {
|
|
|
|
|
delta = buf.st_size % sizeof(tIndex);
|
|
|
|
|
if (delta) {
|
|
|
|
|
delta = sizeof(tIndex) - delta;
|
|
|
|
|
esyslog("ERROR: invalid file size (%ld) in '%s'", buf.st_size, fileName);
|
|
|
|
|
}
|
|
|
|
|
last = (buf.st_size + delta) / sizeof(tIndex) - 1;
|
|
|
|
|
if (!Record && last >= 0) {
|
|
|
|
|
size = last + 1;
|
2002-08-11 13:32:23 +02:00
|
|
|
|
index = MALLOC(tIndex, size);
|
2002-06-16 12:57:31 +02:00
|
|
|
|
if (index) {
|
|
|
|
|
f = open(fileName, O_RDONLY);
|
|
|
|
|
if (f >= 0) {
|
|
|
|
|
if ((int)safe_read(f, index, buf.st_size) != buf.st_size) {
|
|
|
|
|
esyslog("ERROR: can't read from file '%s'", fileName);
|
2002-08-11 13:32:23 +02:00
|
|
|
|
free(index);
|
2002-06-16 12:57:31 +02:00
|
|
|
|
index = NULL;
|
|
|
|
|
close(f);
|
|
|
|
|
f = -1;
|
|
|
|
|
}
|
|
|
|
|
// we don't close f here, see CatchUp()!
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
LOG_ERROR_STR(fileName);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
esyslog("ERROR: can't allocate %d bytes for index '%s'", size * sizeof(tIndex), fileName);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
LOG_ERROR;
|
|
|
|
|
}
|
|
|
|
|
else if (!Record)
|
|
|
|
|
isyslog("missing index file %s", fileName);
|
|
|
|
|
if (Record) {
|
|
|
|
|
if ((f = open(fileName, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) >= 0) {
|
|
|
|
|
if (delta) {
|
|
|
|
|
esyslog("ERROR: padding index file with %d '0' bytes", delta);
|
|
|
|
|
while (delta--)
|
|
|
|
|
writechar(f, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
LOG_ERROR_STR(fileName);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
esyslog("ERROR: can't copy file name '%s'", FileName);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cIndexFile::~cIndexFile()
|
|
|
|
|
{
|
|
|
|
|
if (f >= 0)
|
|
|
|
|
close(f);
|
2002-08-11 13:32:23 +02:00
|
|
|
|
free(fileName);
|
|
|
|
|
free(index);
|
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);
|
2003-05-30 13:23:54 +02:00
|
|
|
|
for (int i = 0; i <= MAXINDEXCATCHUP && (Index < 0 || Index >= last); i++) {
|
|
|
|
|
struct stat buf;
|
|
|
|
|
if (fstat(f, &buf) == 0) {
|
|
|
|
|
if (time(NULL) - buf.st_mtime > MININDEXAGE) {
|
|
|
|
|
// apparently the index file is not being written any more
|
|
|
|
|
close(f);
|
|
|
|
|
f = -1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
int newLast = buf.st_size / sizeof(tIndex) - 1;
|
|
|
|
|
if (newLast > last) {
|
|
|
|
|
if (size <= newLast) {
|
|
|
|
|
size *= 2;
|
|
|
|
|
if (size <= newLast)
|
|
|
|
|
size = newLast + 1;
|
|
|
|
|
}
|
|
|
|
|
index = (tIndex *)realloc(index, size * sizeof(tIndex));
|
|
|
|
|
if (index) {
|
|
|
|
|
int offset = (last + 1) * sizeof(tIndex);
|
|
|
|
|
int delta = (newLast - last) * sizeof(tIndex);
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
last = newLast;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
LOG_ERROR_STR(fileName);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
esyslog("ERROR: can't realloc() index");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
LOG_ERROR_STR(fileName);
|
|
|
|
|
if (Index < last - (i ? 2 * INDEXSAFETYLIMIT : 0) || Index > 10 * INDEXSAFETYLIMIT) // keep off the end in case of "Pause live video"
|
|
|
|
|
break;
|
|
|
|
|
sleep(1);
|
|
|
|
|
}
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool cIndexFile::Write(uchar PictureType, uchar FileNumber, int FileOffset)
|
|
|
|
|
{
|
|
|
|
|
if (f >= 0) {
|
|
|
|
|
tIndex i = { FileOffset, PictureType, FileNumber, 0 };
|
|
|
|
|
if (safe_write(f, &i, sizeof(i)) < 0) {
|
|
|
|
|
LOG_ERROR_STR(fileName);
|
|
|
|
|
close(f);
|
|
|
|
|
f = -1;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
last++;
|
|
|
|
|
}
|
|
|
|
|
return f >= 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool cIndexFile::Get(int Index, uchar *FileNumber, int *FileOffset, uchar *PictureType, int *Length)
|
|
|
|
|
{
|
2003-03-30 13:31:32 +02:00
|
|
|
|
if (CatchUp(Index)) {
|
2002-08-24 15:08:25 +02:00
|
|
|
|
if (Index >= 0 && Index < last) {
|
2002-06-16 12:57:31 +02:00
|
|
|
|
*FileNumber = index[Index].number;
|
|
|
|
|
*FileOffset = index[Index].offset;
|
|
|
|
|
if (PictureType)
|
|
|
|
|
*PictureType = index[Index].type;
|
|
|
|
|
if (Length) {
|
|
|
|
|
int fn = index[Index + 1].number;
|
|
|
|
|
int fo = index[Index + 1].offset;
|
|
|
|
|
if (fn == *FileNumber)
|
|
|
|
|
*Length = fo - *FileOffset;
|
|
|
|
|
else
|
|
|
|
|
*Length = -1; // this means "everything up to EOF" (the buffer's Read function will act accordingly)
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int cIndexFile::GetNextIFrame(int Index, bool Forward, uchar *FileNumber, int *FileOffset, int *Length, bool StayOffEnd)
|
|
|
|
|
{
|
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;
|
2003-05-30 13:23:54 +02:00
|
|
|
|
if (Index >= 0 && Index < last - ((Forward && StayOffEnd) ? INDEXSAFETYLIMIT : 0)) {
|
2002-06-16 12:57:31 +02:00
|
|
|
|
if (index[Index].type == I_FRAME) {
|
|
|
|
|
if (FileNumber)
|
|
|
|
|
*FileNumber = index[Index].number;
|
|
|
|
|
else
|
|
|
|
|
FileNumber = &index[Index].number;
|
|
|
|
|
if (FileOffset)
|
|
|
|
|
*FileOffset = index[Index].offset;
|
|
|
|
|
else
|
|
|
|
|
FileOffset = &index[Index].offset;
|
|
|
|
|
if (Length) {
|
|
|
|
|
// all recordings end with a non-I_FRAME, so the following should be safe:
|
|
|
|
|
int fn = index[Index + 1].number;
|
|
|
|
|
int fo = index[Index + 1].offset;
|
|
|
|
|
if (fn == *FileNumber)
|
|
|
|
|
*Length = fo - *FileOffset;
|
|
|
|
|
else {
|
|
|
|
|
esyslog("ERROR: 'I' frame at end of file #%d", *FileNumber);
|
|
|
|
|
*Length = -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return Index;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int cIndexFile::Get(uchar FileNumber, int FileOffset)
|
|
|
|
|
{
|
2003-03-30 13:31:32 +02:00
|
|
|
|
if (CatchUp()) {
|
2002-06-16 12:57:31 +02:00
|
|
|
|
//TODO implement binary search!
|
|
|
|
|
int i;
|
|
|
|
|
for (i = 0; i < last; i++) {
|
|
|
|
|
if (index[i].number > FileNumber || (index[i].number == FileNumber) && index[i].offset >= FileOffset)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- cFileName -------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include "videodir.h"
|
|
|
|
|
|
|
|
|
|
#define MAXFILESPERRECORDING 255
|
|
|
|
|
#define RECORDFILESUFFIX "/%03d.vdr"
|
|
|
|
|
#define RECORDFILESUFFIXLEN 20 // some additional bytes for safety...
|
|
|
|
|
|
|
|
|
|
cFileName::cFileName(const char *FileName, bool Record, bool Blocking)
|
|
|
|
|
{
|
|
|
|
|
file = -1;
|
|
|
|
|
fileNumber = 0;
|
|
|
|
|
record = Record;
|
|
|
|
|
blocking = Blocking;
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int cFileName::Open(void)
|
|
|
|
|
{
|
|
|
|
|
if (file < 0) {
|
|
|
|
|
int BlockingFlag = blocking ? 0 : O_NONBLOCK;
|
|
|
|
|
if (record) {
|
|
|
|
|
dsyslog("recording to '%s'", fileName);
|
|
|
|
|
file = OpenVideoFile(fileName, O_RDWR | O_CREAT | BlockingFlag);
|
|
|
|
|
if (file < 0)
|
|
|
|
|
LOG_ERROR_STR(fileName);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
if (access(fileName, R_OK) == 0) {
|
|
|
|
|
dsyslog("playing '%s'", fileName);
|
|
|
|
|
file = open(fileName, O_RDONLY | BlockingFlag);
|
|
|
|
|
if (file < 0)
|
|
|
|
|
LOG_ERROR_STR(fileName);
|
|
|
|
|
}
|
|
|
|
|
else if (errno != ENOENT)
|
|
|
|
|
LOG_ERROR_STR(fileName);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return file;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void cFileName::Close(void)
|
|
|
|
|
{
|
|
|
|
|
if (file >= 0) {
|
|
|
|
|
if ((record && CloseVideoFile(file) < 0) || (!record && close(file) < 0))
|
|
|
|
|
LOG_ERROR_STR(fileName);
|
|
|
|
|
file = -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int cFileName::SetOffset(int Number, int Offset)
|
|
|
|
|
{
|
|
|
|
|
if (fileNumber != Number)
|
|
|
|
|
Close();
|
|
|
|
|
if (0 < Number && Number <= MAXFILESPERRECORDING) {
|
|
|
|
|
fileNumber = Number;
|
|
|
|
|
sprintf(pFileNumber, RECORDFILESUFFIX, fileNumber);
|
|
|
|
|
if (record) {
|
|
|
|
|
if (access(fileName, F_OK) == 0) // file exists, let's try next suffix
|
|
|
|
|
return SetOffset(Number + 1);
|
|
|
|
|
else if (errno != ENOENT) { // something serious has happened
|
|
|
|
|
LOG_ERROR_STR(fileName);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
// found a non existing file suffix
|
|
|
|
|
}
|
|
|
|
|
if (Open() >= 0) {
|
|
|
|
|
if (!record && Offset >= 0 && lseek(file, Offset, SEEK_SET) != Offset) {
|
|
|
|
|
LOG_ERROR_STR(fileName);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return file;
|
|
|
|
|
}
|
|
|
|
|
esyslog("ERROR: max number of files (%d) exceeded", MAXFILESPERRECORDING);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int cFileName::NextFile(void)
|
|
|
|
|
{
|
|
|
|
|
return SetOffset(fileNumber + 1);
|
|
|
|
|
}
|
|
|
|
|
|
2002-06-22 10:11:59 +02:00
|
|
|
|
// --- Index stuff -----------------------------------------------------------
|
|
|
|
|
|
2002-06-16 12:57:31 +02:00
|
|
|
|
const char *IndexToHMSF(int Index, bool WithFrame)
|
|
|
|
|
{
|
|
|
|
|
static char buffer[16];
|
|
|
|
|
int f = (Index % FRAMESPERSEC) + 1;
|
|
|
|
|
int s = (Index / FRAMESPERSEC);
|
|
|
|
|
int m = s / 60 % 60;
|
|
|
|
|
int h = s / 3600;
|
|
|
|
|
s %= 60;
|
|
|
|
|
snprintf(buffer, sizeof(buffer), WithFrame ? "%d:%02d:%02d.%02d" : "%d:%02d:%02d", h, m, s, f);
|
|
|
|
|
return buffer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int HMSFToIndex(const char *HMSF)
|
|
|
|
|
{
|
|
|
|
|
int h, m, s, f = 0;
|
|
|
|
|
if (3 <= sscanf(HMSF, "%d:%d:%d.%d", &h, &m, &s, &f))
|
|
|
|
|
return (h * 3600 + m * 60 + s) * FRAMESPERSEC + f - 1;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int SecondsToFrames(int Seconds)
|
|
|
|
|
{
|
|
|
|
|
return Seconds * FRAMESPERSEC;
|
|
|
|
|
}
|
2002-06-22 10:11:59 +02:00
|
|
|
|
|
|
|
|
|
// --- ReadFrame -------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
int ReadFrame(int f, uchar *b, int Length, int Max)
|
|
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
int r = safe_read(f, b, Length);
|
|
|
|
|
if (r < 0)
|
|
|
|
|
LOG_ERROR;
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|