vdr/recording.c
Klaus Schmidinger 1d22145c42 Version 0.6
- Added support for LIRC remote control (thanks to Carsten Koch!).
  There are now three different remote control modes: KBD (PC-Keyboard), RCU
  and LIRC. See the INSTALL file for information on how to enable either of
  these modes. The default mode is now KBD, not RCU as before (to make it
  work immediately even if there is no actual remote control).
- Fixed small bug in dvbapi.c that was causing some channels (many on hotbird)
  not to be correctly tuned (thanks to Plamen Ganev!).
- Now clearing the replay buffer in search forward/back, which results in
  faster reaction.
- The 'Recordings' menu is now listed alphabetically (thanks to Carsten Koch!).
- The new 'epg2timers' tool (thanks to Carsten Koch!) can be used to convert
  an EPG "merkliste" page (http://www.tvtv.de) to vdr timer entries.
- The new 'xtvrc2vdr' tool (thanks to Plamen Ganev!) can be used to convert
  'xtvrc' channel files into 'vdr' format.
- When more than one timer matches at the same time, the first one in the list
  with the highest 'Priority' is selected.
- The MANUAL section on "Programming the Timer" has been filled in.
- The year in the "Recordings" menu as well as in the progress display during
  replay has been reduced to 2 digits to allow more space for the recording's
  title. In the internal file structure the year is still stored with 4 digits,
  so there will be no problem at the next turn of the century ;-)
- Channel names and timer filenames can now contain blanks. To avoid problems
  with file names that contain blanks, all blanks in recording file names are
  converted to underscores.
- The polarization can now be given in uppercase or lowercase characters in
  channels.conf.
- Fixed buffer initialization to work with DVB driver version 0.6.
- Implemented the "Simple Video Disk Recorder Protocol" (SVDRP) to control
  the VDR over a network connection.
- Implemented command line option handling.
- The program can now run in full background mode by using the --daemon option.
- Added a "summary" field to the timers (thanks to Carsten Koch!).
  This field can contain a descriptive text of the programme and will be
  displayed when the "Blue" key is pressed on a recording that was created by
  this timer. If the text contains the special character '|', a newline will
  be inserted at that place.  When pressing "Ok" on a timer that contains a
  summary field, the summary will be displayed. To edit such a timer the "Red"
  key must be pressed. Timers without a summary still go into Edit mode when
  pressing "Ok".  The summary field can only be filled in directly by editing
  the 'timers.conf' file with a text editor, or by defining/modifying the timer
  via the SVDRP interface.
2000-07-25 18:00:00 +02:00

284 lines
7.8 KiB
C

/*
* recording.h: Recording file handling
*
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: recording.c 1.12 2000/07/24 16:31:07 kls Exp $
*/
#define _GNU_SOURCE
#include "recording.h"
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "interface.h"
#include "tools.h"
#define RECEXT ".rec"
#define DELEXT ".del"
#define DATAFORMAT "%4d-%02d-%02d.%02d:%02d.%02d.%02d" RECEXT
#define NAMEFORMAT "%s/%s/" DATAFORMAT
#define SUMMARYFILESUFFIX "/summary.vdr"
#define FINDCMD "find %s -type d -name '%s' | sort -df"
#define DFCMD "df -m %s"
#define MINDISKSPACE 1024 // MB
#define DISKCHECKDELTA 300 // seconds between checks for free disk space
const char *BaseDir = "/video";
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.
static time_t LastFreeDiskCheck = 0;
if (time(NULL) - LastFreeDiskCheck > DISKCHECKDELTA) {
if (LowDiskSpace()) {
// 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
r0 = r;
}
r = Recordings.Next(r);
}
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");
}
LastFreeDiskCheck = time(NULL);
}
}
// --- cRecording ------------------------------------------------------------
cRecording::cRecording(cTimer *Timer)
{
titleBuffer = NULL;
fileName = NULL;
name = strdup(Timer->file);
summary = Timer->summary ? strdup(Timer->summary) : NULL;
if (summary)
strreplace(summary, '|', '\n');
start = Timer->StartTime();
priority = Timer->priority;
lifetime = Timer->lifetime;
}
cRecording::cRecording(const char *FileName)
{
titleBuffer = NULL;
fileName = strdup(FileName);
FileName += strlen(BaseDir) + 1;
char *p = strrchr(FileName, '/');
name = NULL;
summary = NULL;
if (p) {
time_t now = time(NULL);
struct tm t = *localtime(&now); // this initializes the time zone in 't'
if (7 == sscanf(p + 1, DATAFORMAT, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &priority, &lifetime)) {
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;
strreplace(name, '_', ' ');
}
// 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;
}
}
cRecording::~cRecording()
{
delete titleBuffer;
delete fileName;
delete name;
delete summary;
}
const char *cRecording::FileName(void)
{
if (!fileName) {
struct tm *t = localtime(&start);
asprintf(&fileName, NAMEFORMAT, BaseDir, name, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, priority, lifetime);
if (fileName)
strreplace(fileName, ' ', '_');
}
return fileName;
}
const char *cRecording::Title(char Delimiter)
{
delete titleBuffer;
titleBuffer = NULL;
struct tm *t = localtime(&start);
asprintf(&titleBuffer, "%02d.%02d.%02d%c%02d:%02d%c%s",
t->tm_mday,
t->tm_mon + 1,
t->tm_year % 100,
Delimiter,
t->tm_hour,
t->tm_min,
Delimiter,
name);
return titleBuffer;
}
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;
}
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) {
esyslog(LOG_ERR, "ERROR: %s: %s", FileName(), strerror(errno));
result = false;
}
}
delete NewName;
return result;
}
bool cRecording::Remove(void)
{
isyslog(LOG_INFO, "removing recording %s", FileName());
return RemoveFileOrDir(FileName());
}
// --- 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;
}