diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 0201f80c..c24496f1 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -5,6 +5,7 @@ Carsten Koch 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 for fixing the frequency offset for Hotbird channels diff --git a/HISTORY b/HISTORY index f95ac51f..36bc8d71 100644 --- a/HISTORY +++ b/HISTORY @@ -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 ' 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. diff --git a/INSTALL b/INSTALL index 3261ef00..6bf3a9fd 100644 --- a/INSTALL +++ b/INSTALL @@ -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: -------------------- diff --git a/Makefile b/Makefile index 8fb678bc..f71236b7 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/dvbapi.c b/dvbapi.c index a9673a7a..2f726ed5 100644 --- a/dvbapi.c +++ b/dvbapi.c @@ -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 #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; diff --git a/recording.c b/recording.c index 277b9114..9f3b3885 100644 --- a/recording.c +++ b/recording.c @@ -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 #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 ----------------------------------------------------------- diff --git a/recording.h b/recording.h index ae17cd3c..dc3b3d74 100644 --- a/recording.h +++ b/recording.h @@ -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 { diff --git a/tools.c b/tools.c index 88acd333..83cb51ef 100644 --- a/tools.c +++ b/tools.c @@ -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; } diff --git a/tools.h b/tools.h index dc22033d..563f3d06 100644 --- a/tools.h +++ b/tools.h @@ -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); diff --git a/vdr.c b/vdr.c index 36ddcb42..51422234 100644 --- a/vdr.c +++ b/vdr.c @@ -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 @@ -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(); } diff --git a/videodir.c b/videodir.c new file mode 100644 index 00000000..7bd6299a --- /dev/null +++ b/videodir.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/videodir.h b/videodir.h new file mode 100644 index 00000000..7ce15314 --- /dev/null +++ b/videodir.h @@ -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