2000-03-11 11:22:37 +01:00
|
|
|
/*
|
|
|
|
* recording.h: Recording file handling
|
|
|
|
*
|
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.
|
|
|
|
*
|
2000-07-24 16:43:04 +02:00
|
|
|
* $Id: recording.c 1.12 2000/07/24 16:31:07 kls Exp $
|
2000-03-11 11:22:37 +01:00
|
|
|
*/
|
|
|
|
|
2000-04-15 17:38:11 +02:00
|
|
|
#define _GNU_SOURCE
|
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-24 16:43:04 +02:00
|
|
|
#include <unistd.h>
|
2000-03-11 11:22:37 +01:00
|
|
|
#include "interface.h"
|
|
|
|
#include "tools.h"
|
|
|
|
|
|
|
|
#define RECEXT ".rec"
|
|
|
|
#define DELEXT ".del"
|
2000-04-15 17:38:11 +02:00
|
|
|
#define DATAFORMAT "%4d-%02d-%02d.%02d:%02d.%02d.%02d" RECEXT
|
2000-03-11 11:22:37 +01:00
|
|
|
#define NAMEFORMAT "%s/%s/" DATAFORMAT
|
|
|
|
|
2000-07-24 16:43:04 +02:00
|
|
|
#define SUMMARYFILESUFFIX "/summary.vdr"
|
|
|
|
|
2000-07-16 10:00:04 +02:00
|
|
|
#define FINDCMD "find %s -type d -name '%s' | sort -df"
|
2000-03-11 11:22:37 +01:00
|
|
|
|
|
|
|
#define DFCMD "df -m %s"
|
|
|
|
#define MINDISKSPACE 1024 // MB
|
|
|
|
|
2000-04-15 17:38:11 +02:00
|
|
|
#define DISKCHECKDELTA 300 // seconds between checks for free disk space
|
2000-03-11 11:22:37 +01:00
|
|
|
|
2000-04-15 17:38:11 +02:00
|
|
|
const char *BaseDir = "/video";
|
2000-03-11 11:22:37 +01:00
|
|
|
|
|
|
|
static bool LowDiskSpace(void)
|
|
|
|
{
|
|
|
|
//TODO Find a simpler way to determine the amount of free disk space!
|
|
|
|
bool result = true;
|
|
|
|
char *cmd = NULL;
|
|
|
|
asprintf(&cmd, DFCMD, BaseDir);
|
|
|
|
FILE *p = popen(cmd, "r");
|
|
|
|
if (p) {
|
|
|
|
char *s;
|
|
|
|
while ((s = readline(p)) != NULL) {
|
|
|
|
if (*s == '/') {
|
|
|
|
int available;
|
|
|
|
sscanf(s, "%*s %*d %*d %d", &available);
|
|
|
|
result = available < MINDISKSPACE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pclose(p);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
esyslog(LOG_ERR, "ERROR: can't open pipe for cmd '%s'", cmd);
|
|
|
|
delete cmd;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AssertFreeDiskSpace(void)
|
|
|
|
{
|
|
|
|
// 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;
|
|
|
|
if (time(NULL) - LastFreeDiskCheck > DISKCHECKDELTA) {
|
2000-04-30 10:22:13 +02:00
|
|
|
if (LowDiskSpace()) {
|
2000-04-15 17:38:11 +02: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);
|
|
|
|
}
|
|
|
|
if (r0 && r0->Remove())
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// No "deleted" files to remove, so let's see if we can delete a recording:
|
|
|
|
if (Recordings.Load(false)) {
|
|
|
|
cRecording *r = Recordings.First();
|
|
|
|
cRecording *r0 = NULL;
|
|
|
|
while (r) {
|
|
|
|
if ((time(NULL) - r->start) / SECSINDAY > r->lifetime) {
|
|
|
|
if (r0) {
|
|
|
|
if (r->priority < r0->priority)
|
|
|
|
r0 = r;
|
|
|
|
}
|
|
|
|
else
|
2000-03-11 11:22:37 +01:00
|
|
|
r0 = r;
|
|
|
|
}
|
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...
|
|
|
|
esyslog(LOG_ERR, "low disk space, but no recordings to delete");
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// --- cRecording ------------------------------------------------------------
|
|
|
|
|
|
|
|
cRecording::cRecording(cTimer *Timer)
|
|
|
|
{
|
2000-04-24 09:35:29 +02:00
|
|
|
titleBuffer = NULL;
|
2000-03-11 11:22:37 +01:00
|
|
|
fileName = NULL;
|
|
|
|
name = strdup(Timer->file);
|
2000-07-24 16:43:04 +02:00
|
|
|
summary = Timer->summary ? strdup(Timer->summary) : NULL;
|
|
|
|
if (summary)
|
|
|
|
strreplace(summary, '|', '\n');
|
2000-03-11 11:22:37 +01:00
|
|
|
start = Timer->StartTime();
|
|
|
|
priority = Timer->priority;
|
|
|
|
lifetime = Timer->lifetime;
|
|
|
|
}
|
|
|
|
|
|
|
|
cRecording::cRecording(const char *FileName)
|
|
|
|
{
|
2000-04-24 09:35:29 +02:00
|
|
|
titleBuffer = NULL;
|
2000-03-11 11:22:37 +01:00
|
|
|
fileName = strdup(FileName);
|
|
|
|
FileName += strlen(BaseDir) + 1;
|
|
|
|
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);
|
|
|
|
struct tm t = *localtime(&now); // this initializes the time zone in 't'
|
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);
|
|
|
|
name = new char[p - FileName + 1];
|
|
|
|
strncpy(name, FileName, p - FileName);
|
|
|
|
name[p - FileName] = 0;
|
2000-07-16 15:02:33 +02:00
|
|
|
strreplace(name, '_', ' ');
|
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;
|
|
|
|
summary = new char[size + 1]; // +1 for terminating 0
|
|
|
|
if (summary) {
|
|
|
|
int rbytes = read(f, summary, size);
|
|
|
|
if (rbytes >= 0) {
|
|
|
|
summary[rbytes] = 0;
|
|
|
|
if (rbytes != size)
|
|
|
|
esyslog(LOG_ERR, "%s: expected %d bytes but read %d", SummaryFileName, size, rbytes);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
LOG_ERROR_STR(SummaryFileName);
|
|
|
|
delete summary;
|
|
|
|
summary = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
else
|
|
|
|
esyslog(LOG_ERR, "can't allocate %d byte of memory for summary file '%s'", size + 1, SummaryFileName);
|
|
|
|
close(f);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
LOG_ERROR_STR(SummaryFileName);
|
|
|
|
}
|
|
|
|
else if (errno != ENOENT)
|
|
|
|
LOG_ERROR_STR(SummaryFileName);
|
|
|
|
delete SummaryFileName;
|
2000-03-11 11:22:37 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cRecording::~cRecording()
|
|
|
|
{
|
2000-04-24 09:35:29 +02:00
|
|
|
delete titleBuffer;
|
2000-03-11 11:22:37 +01:00
|
|
|
delete fileName;
|
|
|
|
delete name;
|
2000-07-24 16:43:04 +02:00
|
|
|
delete summary;
|
2000-03-11 11:22:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const char *cRecording::FileName(void)
|
|
|
|
{
|
|
|
|
if (!fileName) {
|
|
|
|
struct tm *t = localtime(&start);
|
2000-04-15 17:38:11 +02:00
|
|
|
asprintf(&fileName, NAMEFORMAT, BaseDir, name, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, priority, lifetime);
|
2000-07-16 15:02:33 +02:00
|
|
|
if (fileName)
|
|
|
|
strreplace(fileName, ' ', '_');
|
2000-03-11 11:22:37 +01:00
|
|
|
}
|
|
|
|
return fileName;
|
|
|
|
}
|
|
|
|
|
2000-04-23 15:38:16 +02:00
|
|
|
const char *cRecording::Title(char Delimiter)
|
|
|
|
{
|
|
|
|
delete titleBuffer;
|
|
|
|
titleBuffer = NULL;
|
|
|
|
struct tm *t = localtime(&start);
|
2000-07-16 13:46:05 +02:00
|
|
|
asprintf(&titleBuffer, "%02d.%02d.%02d%c%02d:%02d%c%s",
|
2000-04-23 15:38:16 +02:00
|
|
|
t->tm_mday,
|
|
|
|
t->tm_mon + 1,
|
2000-07-16 13:46:05 +02:00
|
|
|
t->tm_year % 100,
|
2000-04-23 15:38:16 +02:00
|
|
|
Delimiter,
|
|
|
|
t->tm_hour,
|
|
|
|
t->tm_min,
|
|
|
|
Delimiter,
|
|
|
|
name);
|
|
|
|
return titleBuffer;
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
delete SummaryFileName;
|
|
|
|
}
|
|
|
|
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));
|
|
|
|
isyslog(LOG_INFO, "deleting recording %s", FileName());
|
|
|
|
if (rename(FileName(), NewName) == -1) {
|
2000-04-15 17:38:11 +02:00
|
|
|
esyslog(LOG_ERR, "ERROR: %s: %s", FileName(), strerror(errno));
|
2000-03-11 11:22:37 +01:00
|
|
|
result = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
delete NewName;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool cRecording::Remove(void)
|
|
|
|
{
|
|
|
|
isyslog(LOG_INFO, "removing recording %s", FileName());
|
2000-04-15 17:38:11 +02:00
|
|
|
return RemoveFileOrDir(FileName());
|
2000-03-11 11:22:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// --- cRecordings -----------------------------------------------------------
|
|
|
|
|
|
|
|
bool cRecordings::Load(bool Deleted)
|
|
|
|
{
|
|
|
|
Clear();
|
|
|
|
bool result = false;
|
|
|
|
char *cmd = NULL;
|
|
|
|
asprintf(&cmd, FINDCMD, BaseDir, Deleted ? "*" DELEXT : "*" RECEXT);
|
|
|
|
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);
|
|
|
|
result = Count() > 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
Interface.Error("Error while opening pipe!");
|
|
|
|
delete cmd;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|