Support for more than one video directory

This commit is contained in:
Klaus Schmidinger 2000-07-29 15:21:42 +02:00
parent 92096e097a
commit 0f2099b4f2
12 changed files with 352 additions and 75 deletions

View File

@ -5,6 +5,7 @@ Carsten Koch <Carsten.Koch@icem.de>
for making the 'Recordings' menu be listed alphabetically
for implementing the 'Summary' feature
for adding the 'epg2timers' tool (see Tools/epg2timers)
for his idea of using multiple disks (and for testing this feature)
Plamen Ganev <pganev@com-it.net>
for fixing the frequency offset for Hotbird channels

View File

@ -100,9 +100,12 @@ Video Disk Recorder Revision History
the 'timers.conf' file with a text editor, or by defining/modifying the timer
via the SVDRP interface.
2000-07-28:
2000-07-29:
- When scrolling through a list it now moves a full page up or down when the
cursor reaches the top or bottom of the menu (thanks to Heino Goldenstein!).
- Added missing '#include <sys/stat.h>' to recording.c.
- The video directory can now be defined with the command line option -v.
- There can now be more than one video directory (in case you have several
disks).
- Fixed learning key codes for PC keyboard.

34
INSTALL
View File

@ -49,6 +49,9 @@ If the program shall run as a daemon, use the --daemon option. This
will completely detach it from the terminal and will continue as a
background process.
Command line options:
---------------------
Use "vdr --help" for a list of available command line options.
The video data directory:
@ -57,14 +60,41 @@ The video data directory:
All recordings are written into directories below "/video". Please
make sure this directory exists, and that the user who runs the 'vdr'
program has read and write access to that directory.
If you prefer a different location for your video files, you can change
the value of 'BaseDir' in recording.c.
If you prefer a different location for your video files, you can use
the '-v' option to change that.
Note that the file system need not be 64-bit proof, since the 'vdr'
program splits video files into chunks of about 1GB. You should use
a disk with several gigabytes of free space. One GB can store roughly
half an hour of video data.
If you have more than one disk and don't want to combine them to form
one large logical volume, you can set up several video directories as
mount points for these disks. All of these directories must have the
same basic name and must end with a numeric part, which starts at 0 for
the main directory and has increasing values for the rest of the
directories. For example
/video0
/video1
/video2
would be a setup with three directories. You can use more than one
numeric digit, and the directories need not be directly under '/':
/mnt/MyVideos/vdr.00
/mnt/MyVideos/vdr.01
/mnt/MyVideos/vdr.02
...
/mnt/MyVideos/vdr.11
would set up twelve disks (wow, what a machine that would be!).
To use such a multi directory setup, you need to add the '-v' option
with the name of the basic directory when running 'vdr':
vdr -v /video0
Configuration files:
--------------------

View File

@ -4,9 +4,9 @@
# See the main source file 'vdr.c' for copyright information and
# how to reach the author.
#
# $Id: Makefile 1.5 2000/07/23 11:57:14 kls Exp $
# $Id: Makefile 1.6 2000/07/28 14:37:44 kls Exp $
OBJS = config.o dvbapi.o interface.o menu.o osd.o recording.o remote.o svdrp.o tools.o vdr.o
OBJS = config.o dvbapi.o interface.o menu.o osd.o recording.o remote.o svdrp.o tools.o vdr.o videodir.o
ifndef REMOTE
REMOTE = KBD
@ -24,15 +24,16 @@ endif
all: vdr
config.o : config.c config.h dvbapi.h interface.h tools.h
dvbapi.o : dvbapi.c config.h dvbapi.h interface.h tools.h
dvbapi.o : dvbapi.c config.h dvbapi.h interface.h tools.h videodir.h
interface.o: interface.c config.h dvbapi.h interface.h remote.h tools.h
menu.o : menu.c config.h dvbapi.h interface.h menu.h osd.h recording.h tools.h
osd.o : osd.c config.h dvbapi.h interface.h osd.h tools.h
vdr.o : vdr.c config.h dvbapi.h interface.h menu.h osd.h recording.h svdrp.h tools.h
recording.o: recording.c config.h dvbapi.h interface.h recording.h tools.h
vdr.o : vdr.c config.h dvbapi.h interface.h menu.h osd.h recording.h svdrp.h tools.h videodir.h
recording.o: recording.c config.h dvbapi.h interface.h recording.h tools.h videodir.h
remote.o : remote.c remote.h tools.h
svdrp.o : svdrp.c svdrp.h config.h interface.h tools.h
tools.o : tools.c tools.h
videodir.o : videodir.c tools.h videodir.h
vdr: $(OBJS)
g++ -g -O2 $(OBJS) -lncurses -o vdr

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: dvbapi.c 1.15 2000/07/21 13:18:02 kls Exp $
* $Id: dvbapi.c 1.16 2000/07/29 14:49:46 kls Exp $
*/
#include "dvbapi.h"
@ -17,6 +17,7 @@
#include <unistd.h>
#include "interface.h"
#include "tools.h"
#include "videodir.h"
#define VIDEODEVICE "/dev/video"
@ -50,9 +51,12 @@
// 'signed'), so let's use 1GB for absolute safety (the actual file size
// may be slightly higher because we stop recording only before the next
// 'I' frame, to have a complete Group Of Pictures):
#define MAXVIDEOFILESIZE (1024*1024*1024)
#define MAXVIDEOFILESIZE (1024*1024*1024) // Byte
#define MAXFILESPERRECORDING 255
#define MINFREEDISKSPACE (512) // MB
#define DISKCHECKINTERVAL 100 // seconds
#define INDEXFILESUFFIX "/index.vdr"
#define RESUMEFILESUFFIX "/resume.vdr"
#define RECORDFILESUFFIX "/%03d.vdr"
@ -598,6 +602,8 @@ private:
int recordFile;
uchar tagAudio, tagVideo;
bool ok, synced;
time_t lastDiskSpaceCheck;
bool RunningLowOnDiskSpace(void);
int Synchronize(void);
bool NextFile(void);
virtual int Write(int Max = -1);
@ -615,6 +621,7 @@ cRecordBuffer::cRecordBuffer(int *InFile, const char *FileName)
recordFile = -1;
tagAudio = tagVideo = 0;
ok = synced = false;
lastDiskSpaceCheck = time(NULL);
if (!fileName)
return;//XXX find a better way???
// Find the highest existing file suffix:
@ -636,7 +643,20 @@ cRecordBuffer::cRecordBuffer(int *InFile, const char *FileName)
cRecordBuffer::~cRecordBuffer()
{
if (recordFile >= 0)
close(recordFile);
CloseVideoFile(recordFile);
}
bool cRecordBuffer::RunningLowOnDiskSpace(void)
{
if (time(NULL) > lastDiskSpaceCheck + DISKCHECKINTERVAL) {
uint Free = FreeDiskSpaceMB(fileName);
lastDiskSpaceCheck = time(NULL);
if (Free < MINFREEDISKSPACE) {
dsyslog(LOG_INFO, "low disk space (%d MB, limit is %d MB)", Free, MINFREEDISKSPACE);
return true;
}
}
return false;
}
int cRecordBuffer::Synchronize(void)
@ -714,20 +734,22 @@ int cRecordBuffer::Synchronize(void)
bool cRecordBuffer::NextFile(void)
{
if (recordFile >= 0 && fileSize > MAXVIDEOFILESIZE && pictureType == I_FRAME) {
if (close(recordFile) < 0)
LOG_ERROR;
// don't return 'false', maybe we can still record into the next file
recordFile = -1;
fileNumber++;
if (fileNumber == 0)
esyslog(LOG_ERR, "ERROR: max number of files (%d) exceeded", MAXFILESPERRECORDING);
fileSize = 0;
if (recordFile >= 0 && pictureType == I_FRAME) { // every file shall start with an I_FRAME
if (fileSize > MAXVIDEOFILESIZE || RunningLowOnDiskSpace()) {
if (CloseVideoFile(recordFile) < 0)
LOG_ERROR;
// don't return 'false', maybe we can still record into the next file
recordFile = -1;
fileNumber++;
if (fileNumber == 0)
esyslog(LOG_ERR, "ERROR: max number of files (%d) exceeded", MAXFILESPERRECORDING);
fileSize = 0;
}
}
if (recordFile < 0) {
sprintf(pFileNumber, RECORDFILESUFFIX, fileNumber);
dsyslog(LOG_INFO, "recording to '%s'", fileName);
recordFile = open(fileName, O_RDWR | O_CREAT | O_NONBLOCK, S_IRUSR | S_IWUSR);
recordFile = OpenVideoFile(fileName, O_RDWR | O_CREAT | O_NONBLOCK);
if (recordFile < 0) {
LOG_ERROR;
return false;

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: recording.c 1.14 2000/07/28 12:47:54 kls Exp $
* $Id: recording.c 1.15 2000/07/29 14:08:17 kls Exp $
*/
#define _GNU_SOURCE
@ -17,6 +17,7 @@
#include <unistd.h>
#include "interface.h"
#include "tools.h"
#include "videodir.h"
#define RECEXT ".rec"
#define DELEXT ".del"
@ -25,40 +26,12 @@
#define SUMMARYFILESUFFIX "/summary.vdr"
#define FINDCMD "find %s -type d -name '%s' | sort -df"
#define FINDCMD "find %s -follow -type d -name '%s' 2> /dev/null | sort -df"
#define DFCMD "df -m %s"
#define MINDISKSPACE 1024 // MB
#define DISKCHECKDELTA 300 // seconds between checks for free disk space
const char *VideoDirectory = "/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, VideoDirectory);
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
@ -66,7 +39,7 @@ void AssertFreeDiskSpace(void)
// it will get removed during the next call.
static time_t LastFreeDiskCheck = 0;
if (time(NULL) - LastFreeDiskCheck > DISKCHECKDELTA) {
if (LowDiskSpace()) {
if (!VideoFileSpaceAvailable(MINDISKSPACE)) {
// Remove the oldest file that has been "deleted":
cRecordings Recordings;
if (Recordings.Load(true)) {
@ -240,10 +213,7 @@ bool cRecording::Delete(void)
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;
}
result = RenameVideoFile(FileName(), NewName);
}
delete NewName;
return result;
@ -252,7 +222,7 @@ bool cRecording::Delete(void)
bool cRecording::Remove(void)
{
isyslog(LOG_INFO, "removing recording %s", FileName());
return RemoveFileOrDir(FileName());
return RemoveVideoFile(FileName());
}
// --- cRecordings -----------------------------------------------------------

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: recording.h 1.8 2000/07/28 12:48:06 kls Exp $
* $Id: recording.h 1.9 2000/07/28 13:53:54 kls Exp $
*/
#ifndef __RECORDING_H
@ -14,8 +14,6 @@
#include "config.h"
#include "tools.h"
extern const char *VideoDirectory;
void AssertFreeDiskSpace(void);
class cRecording : public cListObject {

67
tools.c
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: tools.c 1.11 2000/07/28 13:22:10 kls Exp $
* $Id: tools.c 1.12 2000/07/29 14:02:41 kls Exp $
*/
#define _GNU_SOURCE
@ -145,20 +145,47 @@ bool isnumber(const char *s)
return true;
}
bool DirectoryOk(const char *DirName)
#define DFCMD "df -m %s"
uint FreeDiskSpaceMB(const char *Directory)
{
//TODO Find a simpler way to determine the amount of free disk space!
uint Free = 0;
char *cmd = NULL;
asprintf(&cmd, DFCMD, Directory);
FILE *p = popen(cmd, "r");
if (p) {
char *s;
while ((s = readline(p)) != NULL) {
if (*s == '/') {
uint available;
sscanf(s, "%*s %*d %*d %u", &available);
Free = available;
break;
}
}
pclose(p);
}
else
esyslog(LOG_ERR, "ERROR: can't open pipe for cmd '%s'", cmd);
delete cmd;
return Free;
}
bool DirectoryOk(const char *DirName, bool LogErrors)
{
struct stat ds;
if (stat(DirName, &ds) == 0) {
if (S_ISDIR(ds.st_mode)) {
if (access(DirName, R_OK | W_OK | X_OK) == 0)
return true;
else
else if (LogErrors)
esyslog(LOG_ERR, "ERROR: can't access %s", DirName);
}
else
else if (LogErrors)
esyslog(LOG_ERR, "ERROR: %s is not a directory", DirName);
}
else
else if (LogErrors)
LOG_ERROR_STR(DirName);
return false;
}
@ -175,7 +202,7 @@ bool MakeDirs(const char *FileName, bool IsDirectory)
*p = 0;
struct stat fs;
if (stat(s, &fs) != 0 || !S_ISDIR(fs.st_mode)) {
isyslog(LOG_INFO, "creating directory %s", s);
dsyslog(LOG_INFO, "creating directory %s", s);
if (mkdir(s, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == -1) {
esyslog(LOG_ERR, "ERROR: %s: %s", s, strerror(errno));
result = false;
@ -191,7 +218,7 @@ bool MakeDirs(const char *FileName, bool IsDirectory)
return result;
}
bool RemoveFileOrDir(const char *FileName)
bool RemoveFileOrDir(const char *FileName, bool FollowSymlinks)
{
struct stat st;
if (stat(FileName, &st) == 0) {
@ -203,23 +230,43 @@ bool RemoveFileOrDir(const char *FileName)
if (strcmp(e->d_name, ".") && strcmp(e->d_name, "..")) {
char *buffer;
asprintf(&buffer, "%s/%s", FileName, e->d_name);
if (FollowSymlinks) {
int size = strlen(buffer) * 2; // should be large enough
char *l = new char[size];
int n = readlink(buffer, l, size);
if (n < 0) {
if (errno != EINVAL)
LOG_ERROR_STR(buffer);
}
else if (n < size) {
l[n] = 0;
dsyslog(LOG_INFO, "removing %s", l);
if (remove(l) < 0)
LOG_ERROR_STR(l);
}
else
esyslog(LOG_ERR, "symlink name length (%d) exceeded anticipated buffer size (%d)", n, size);
delete l;
}
dsyslog(LOG_INFO, "removing %s", buffer);
if (remove(buffer) < 0)
esyslog(LOG_ERR, "ERROR: %s: %s", buffer, strerror(errno));
LOG_ERROR_STR(buffer);
delete buffer;
}
}
closedir(d);
}
else {
esyslog(LOG_ERR, "ERROR: %s: %s", FileName, strerror(errno));
LOG_ERROR_STR(FileName);
return false;
}
}
dsyslog(LOG_INFO, "removing %s", FileName);
if (remove(FileName) == 0)
return true;
}
else
esyslog(LOG_ERR, "ERROR: %s: %s", FileName, strerror(errno));
LOG_ERROR_STR(FileName);
return false;
}

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: tools.h 1.11 2000/07/28 13:02:05 kls Exp $
* $Id: tools.h 1.12 2000/07/29 10:56:00 kls Exp $
*/
#ifndef __TOOLS_H
@ -43,9 +43,10 @@ char *skipspace(char *s);
int time_ms(void);
void delay_ms(int ms);
bool isnumber(const char *s);
bool DirectoryOk(const char *DirName);
uint FreeDiskSpaceMB(const char *Directory);
bool DirectoryOk(const char *DirName, bool LogErrors = false);
bool MakeDirs(const char *FileName, bool IsDirectory = false);
bool RemoveFileOrDir(const char *FileName);
bool RemoveFileOrDir(const char *FileName, bool FollowSymlinks = false);
bool CheckProcess(pid_t pid);
void KillProcess(pid_t pid, int Timeout = MAXPROCESSTIMEOUT);

5
vdr.c
View File

@ -22,7 +22,7 @@
*
* The project's page is at http://www.cadsoft.de/people/kls/vdr
*
* $Id: vdr.c 1.24 2000/07/28 13:14:19 kls Exp $
* $Id: vdr.c 1.25 2000/07/28 15:55:31 kls Exp $
*/
#include <getopt.h>
@ -36,6 +36,7 @@
#include "recording.h"
#include "svdrp.h"
#include "tools.h"
#include "videodir.h"
#ifdef REMOTE_KBD
#define KEYS_CONF "keys-pc.conf"
@ -105,7 +106,7 @@ int main(int argc, char *argv[])
// Check the video directory:
if (!DirectoryOk(VideoDirectory)) {
if (!DirectoryOk(VideoDirectory, true)) {
fprintf(stderr, "vdr: can't access video directory %s\n", VideoDirectory);
abort();
}

182
videodir.c Normal file
View File

@ -0,0 +1,182 @@
/*
* videodir.c: Functions to maintain a distributed video directory
*
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: videodir.c 1.1 2000/07/29 15:21:42 kls Exp $
*/
#include "videodir.h"
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "tools.h"
const char *VideoDirectory = "/video";
class cVideoDirectory {
private:
char *name, *stored, *adjusted;
int length, number, digits;
public:
cVideoDirectory(void);
~cVideoDirectory();
uint FreeMB(void);
const char *Name(void) { return name; }
const char *Stored(void) { return stored; }
int Length(void) { return length; }
bool IsDistributed(void) { return name != NULL; }
bool Next(void);
void Store(void);
const char *Adjust(const char *FileName);
};
cVideoDirectory::cVideoDirectory(void)
{
length = strlen(VideoDirectory);
name = (VideoDirectory[length - 1] == '0') ? strdup(VideoDirectory) : NULL;
stored = adjusted = NULL;
number = -1;
digits = 0;
}
cVideoDirectory::~cVideoDirectory()
{
delete name;
delete stored;
delete adjusted;
}
uint cVideoDirectory::FreeMB(void)
{
return FreeDiskSpaceMB(name ? name : VideoDirectory);
}
bool cVideoDirectory::Next(void)
{
if (name) {
if (number < 0) {
int l = length;
while (l-- > 0 && isdigit(name[l]))
;
l++;
digits = length - l;
int n = atoi(&name[l]);
if (n == 0)
number = n;
else
return false; // base video directory must end with zero
}
if (++number > 0) {
char buf[16];
if (sprintf(buf, "%0*d", digits, number) == digits) {
strcpy(&name[length - digits], buf);
return DirectoryOk(name);
}
}
}
return false;
}
void cVideoDirectory::Store(void)
{
if (name) {
delete stored;
stored = strdup(name);
}
}
const char *cVideoDirectory::Adjust(const char *FileName)
{
if (stored) {
delete adjusted;
adjusted = strdup(FileName);
return strncpy(adjusted, stored, length);
}
return NULL;
}
int OpenVideoFile(const char *FileName, int Flags)
{
const char *ActualFileName = FileName;
// Incoming name must be in base video directory:
if (strstr(FileName, VideoDirectory) != FileName) {
esyslog(LOG_ERR, "ERROR: %s not in %s", FileName, VideoDirectory);
errno = ENOENT; // must set 'errno' - any ideas for a better value?
return -1;
}
// Are we going to create a new file?
if ((Flags & O_CREAT) != 0) {
cVideoDirectory Dir;
if (Dir.IsDistributed()) {
// Find the directory with the most free space:
uint MaxFree = Dir.FreeMB();
while (Dir.Next()) {
uint Free = FreeDiskSpaceMB(Dir.Name());
if (Free > MaxFree) {
Dir.Store();
MaxFree = Free;
}
}
if (Dir.Stored()) {
ActualFileName = Dir.Adjust(FileName);
if (!MakeDirs(ActualFileName, false))
return -1; // errno has been set by MakeDirs()
if (symlink(ActualFileName, FileName) < 0) {
LOG_ERROR_STR(FileName);
return -1;
}
ActualFileName = strdup(ActualFileName); // must survive Dir!
}
}
}
int Result = open(ActualFileName, Flags, S_IRUSR | S_IWUSR);
if (ActualFileName != FileName)
delete ActualFileName;
return Result;
}
int CloseVideoFile(int FileHandle)
{
// just in case we ever decide to do something special when closing the file!
return close(FileHandle);
}
bool RenameVideoFile(const char *OldName, const char *NewName)
{
// Only the base video directory entry will be renamed, leaving the
// possible symlinks untouched. Going through all the symlinks and disks
// would be unnecessary work - maybe later...
if (rename(OldName, NewName) == -1) {
LOG_ERROR_STR(OldName);
return false;
}
return true;
}
bool RemoveVideoFile(const char *FileName)
{
return RemoveFileOrDir(FileName, true);
}
bool VideoFileSpaceAvailable(unsigned int SizeMB)
{
cVideoDirectory Dir;
if (Dir.IsDistributed()) {
if (Dir.FreeMB() >= SizeMB * 2) // base directory needs additional space
return true;
while (Dir.Next()) {
if (Dir.FreeMB() >= SizeMB)
return true;
}
return false;
}
return Dir.FreeMB() >= SizeMB;
}

21
videodir.h Normal file
View File

@ -0,0 +1,21 @@
/*
* videodir.h: Functions to maintain a distributed video directory
*
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: videodir.h 1.1 2000/07/29 14:08:27 kls Exp $
*/
#ifndef __VIDEODIR_H
#define __VIDEODIR_H
extern const char *VideoDirectory;
int OpenVideoFile(const char *FileName, int Flags);
int CloseVideoFile(int FileHandle);
bool RenameVideoFile(const char *OldName, const char *NewName);
bool RemoveVideoFile(const char *FileName);
bool VideoFileSpaceAvailable(unsigned int SizeMB);
#endif //__VIDEODIR_H