Removed the code for distributing recordings over several video directories; added the cVideoDirectory plugin API

This commit is contained in:
Klaus Schmidinger 2013-09-11 12:20:37 +02:00
parent 209b850067
commit 3971cc6e88
9 changed files with 266 additions and 254 deletions

23
HISTORY
View File

@ -7943,3 +7943,26 @@ Video Disk Recorder Revision History
respectively, during replay (reported by Thomas Maass). respectively, during replay (reported by Thomas Maass).
- The Yellow button in the main menu no longer acts as "Pause" if "Pause key handling" - The Yellow button in the main menu no longer acts as "Pause" if "Pause key handling"
is set to "do not pause live video" (suggested by Ulf Kiener). is set to "do not pause live video" (suggested by Ulf Kiener).
- The code for distributing recordings over several video directories has been
removed. VDR now by default assumes that the video directory is one big disk.
If you absolutely need to use several separate disks to store recordings, you can
write a plugin that uses the new cVideoDirectory API to implement the necessary
functionality (see PLUGINS.html, section "The video directory"). You can copy the
respective code from previous versions of videodir.c.
IMPORTANT NOTE: If you write a plugin that implements a distributed video directory,
=============== be sure to make cVideoDirectory::Rename() follow symbolic links!
This functionality was never implemented in VDR and it therefore
used a workaround in cutter.c. See the section marked with
// XXX this can be removed once RenameVideoFile() follows symlinks
in previous versions of cutter.c.
+ CloseVideoFile() is obsolete and has been removed.
+ The functions OpenVideoFile(), RenameVideoFile(), RemoveVideoFile(), VideoFileSpaceAvailable(),
VideoDiskSpace(), RemoveEmptyVideoDirectories(), IsOnVideoDirectoryFileSystem() and
PrefixVideoFileName() are now static members of cVideoDirectory and need to be called
with the proper prefix.
+ The name of the video directory is now available through cVideoDirectory::Name().
The former global variable VideoDirectory is still there for backwards compatibility,
but will be removed in a future version. Comment out the line
#define DEPRECATED_VIDEODIR
in videodir.h and recompile your plugins to see whether your code will work without
this variable. If you get a compile error, replace it with cVideoDirectory::Name().

View File

@ -104,6 +104,7 @@ structures and allows it to hook itself into specific areas to perform special a
<li><a href="#Remote Control">Remote Control</a> <li><a href="#Remote Control">Remote Control</a>
<li><a href="#Conditional Access">Conditional Access</a> <li><a href="#Conditional Access">Conditional Access</a>
<li><a href="#Electronic Program Guide">Electronic Program Guide</a> <li><a href="#Electronic Program Guide">Electronic Program Guide</a>
<li><modified><a href="#The video directory">The video directory</a></modified>
</ul> </ul>
</ul> </ul>
@ -2300,5 +2301,41 @@ to signal VDR that no other EPG handlers shall be queried after this one.
<p> <p>
See <tt>VDR/epg.h</tt> for details. See <tt>VDR/epg.h</tt> for details.
<div class="modified">
<hr><h2><a name="The video directory">The video directory</a></h2>
<div class="blurb">Bits and pieces...</div><p>
By default VDR assumes that the video directory consists of one large
volume, on which it can store its recordings. If you want to distribute your
recordings over several physical drives, you can derive from <tt>cVideoDirectory</tt>,
as in
<p><table><tr><td class="code"><pre>
#include &lt;vdr/videodir.h&gt;
class cMyVideoDirectory : public cVideoDirectory {
public:
cMyVideoDirectory(void);
virtual ~cMyVideoDirectory();
virtual int FreeMB(int *UsedMB = NULL);
virtual bool Register(const char *FileName);
virtual bool Rename(const char *OldName, const char *NewName);
virtual bool Move(const char *FromName, const char *ToName);
virtual bool Remove(const char *Name);
virtual void Cleanup(const char *IgnoreFiles[] = NULL);
virtual bool Contains(const char *Name);
};
</pre></td></tr></table><p>
See the description in <tt>videodir.h</tt> for details.
<p>
You should create your derived video directory object in the
<a href="#Getting started"><tt>Start()</tt></a> function of your plugin.
Note that the object has to be created on the heap (using <tt>new</tt>),
and you shall not delete it at any point (it will be deleted automatically
when the program ends).
</div modified>
</body> </body>
</html> </html>

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * how to reach the author.
* *
* $Id: cutter.c 3.2 2013/08/21 13:15:24 kls Exp $ * $Id: cutter.c 3.3 2013/09/10 14:51:45 kls Exp $
*/ */
#include "cutter.h" #include "cutter.h"
@ -664,19 +664,7 @@ bool cCutter::Start(const char *FileName)
Recording.SetStartTime(Recording.Start() + (int(First->Position() / Recording.FramesPerSecond() + 30) / 60) * 60); Recording.SetStartTime(Recording.Start() + (int(First->Position() / Recording.FramesPerSecond() + 30) / 60) * 60);
const char *evn = Recording.PrefixFileName('%'); const char *evn = Recording.PrefixFileName('%');
if (evn && RemoveVideoFile(evn) && MakeDirs(evn, true)) { if (evn && cVideoDirectory::RemoveVideoFile(evn) && MakeDirs(evn, true)) {
// XXX this can be removed once RenameVideoFile() follows symlinks (see videodir.c)
// remove a possible deleted recording with the same name to avoid symlink mixups:
char *s = strdup(evn);
char *e = strrchr(s, '.');
if (e) {
if (strcmp(e, ".rec") == 0) {
strcpy(e, ".del");
RemoveVideoFile(s);
}
}
free(s);
// XXX
editedVersionName = evn; editedVersionName = evn;
Recording.WriteInfo(); Recording.WriteInfo();
Recordings.AddByName(editedVersionName, false); Recordings.AddByName(editedVersionName, false);
@ -701,7 +689,7 @@ void cCutter::Stop(void)
esyslog("ERROR: '%s' during editing process", Error); esyslog("ERROR: '%s' during editing process", Error);
if (cReplayControl::NowReplaying() && strcmp(cReplayControl::NowReplaying(), editedVersionName) == 0) if (cReplayControl::NowReplaying() && strcmp(cReplayControl::NowReplaying(), editedVersionName) == 0)
cControl::Shutdown(); cControl::Shutdown();
RemoveVideoFile(editedVersionName); cVideoDirectory::RemoveVideoFile(editedVersionName);
Recordings.DelByName(editedVersionName); Recordings.DelByName(editedVersionName);
} }
} }

6
menu.c
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * how to reach the author.
* *
* $Id: menu.c 3.5 2013/09/07 12:43:26 kls Exp $ * $Id: menu.c 3.6 2013/09/10 13:16:40 kls Exp $
*/ */
#include "menu.h" #include "menu.h"
@ -2316,7 +2316,7 @@ void cMenuRecordings::Set(bool Refresh)
cString cMenuRecordings::DirectoryName(void) cString cMenuRecordings::DirectoryName(void)
{ {
cString d(VideoDirectory); cString d(cVideoDirectory::Name());
if (base) { if (base) {
char *s = ExchangeChars(strdup(base), true); char *s = ExchangeChars(strdup(base), true);
d = AddDirectory(d, s); d = AddDirectory(d, s);
@ -4342,7 +4342,7 @@ bool cRecordControls::Start(cTimer *Timer, bool Pause)
AssertFreeDiskSpace(Timer->Priority(), !Timer->Pending()); AssertFreeDiskSpace(Timer->Priority(), !Timer->Pending());
Timer->SetPending(true); Timer->SetPending(true);
} }
VideoDiskSpace(&FreeMB); cVideoDirectory::VideoDiskSpace(&FreeMB);
if (FreeMB < MINFREEDISK) { if (FreeMB < MINFREEDISK) {
if (!Timer || time(NULL) - LastNoDiskSpaceMessage > NODISKSPACEDELTA) { if (!Timer || time(NULL) - LastNoDiskSpaceMessage > NODISKSPACEDELTA) {
isyslog("not enough disk space to start recording%s%s", Timer ? " timer " : "", Timer ? *Timer->ToDescr() : ""); isyslog("not enough disk space to start recording%s%s", Timer ? " timer " : "", Timer ? *Timer->ToDescr() : "");

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * how to reach the author.
* *
* $Id: recording.c 3.2 2013/08/21 13:56:33 kls Exp $ * $Id: recording.c 3.3 2013/09/11 08:28:27 kls Exp $
*/ */
#include "recording.h" #include "recording.h"
@ -90,7 +90,7 @@ cRemoveDeletedRecordingsThread::cRemoveDeletedRecordingsThread(void)
void cRemoveDeletedRecordingsThread::Action(void) void cRemoveDeletedRecordingsThread::Action(void)
{ {
// Make sure only one instance of VDR does this: // Make sure only one instance of VDR does this:
cLockFile LockFile(VideoDirectory); cLockFile LockFile(cVideoDirectory::Name());
if (LockFile.Lock()) { if (LockFile.Lock()) {
bool deleted = false; bool deleted = false;
cThreadLock DeletedRecordingsLock(&DeletedRecordings); cThreadLock DeletedRecordingsLock(&DeletedRecordings);
@ -109,7 +109,7 @@ void cRemoveDeletedRecordingsThread::Action(void)
} }
if (deleted) { if (deleted) {
const char *IgnoreFiles[] = { SORTMODEFILE, NULL }; const char *IgnoreFiles[] = { SORTMODEFILE, NULL };
RemoveEmptyVideoDirectories(IgnoreFiles); cVideoDirectory::RemoveEmptyVideoDirectories(IgnoreFiles);
} }
} }
} }
@ -145,9 +145,9 @@ void AssertFreeDiskSpace(int Priority, bool Force)
static time_t LastFreeDiskCheck = 0; static time_t LastFreeDiskCheck = 0;
int Factor = (Priority == -1) ? 10 : 1; int Factor = (Priority == -1) ? 10 : 1;
if (Force || time(NULL) - LastFreeDiskCheck > DISKCHECKDELTA / Factor) { if (Force || time(NULL) - LastFreeDiskCheck > DISKCHECKDELTA / Factor) {
if (!VideoFileSpaceAvailable(MINDISKSPACE)) { if (!cVideoDirectory::VideoFileSpaceAvailable(MINDISKSPACE)) {
// Make sure only one instance of VDR does this: // Make sure only one instance of VDR does this:
cLockFile LockFile(VideoDirectory); cLockFile LockFile(cVideoDirectory::Name());
if (!LockFile.Lock()) if (!LockFile.Lock())
return; return;
// Remove the oldest file that has been "deleted": // Remove the oldest file that has been "deleted":
@ -800,8 +800,8 @@ cRecording::cRecording(const char *FileName)
FileName = fileName = strdup(FileName); FileName = fileName = strdup(FileName);
if (*(fileName + strlen(fileName) - 1) == '/') if (*(fileName + strlen(fileName) - 1) == '/')
*(fileName + strlen(fileName) - 1) = 0; *(fileName + strlen(fileName) - 1) = 0;
if (strstr(FileName, VideoDirectory) == FileName) if (strstr(FileName, cVideoDirectory::Name()) == FileName)
FileName += strlen(VideoDirectory) + 1; FileName += strlen(cVideoDirectory::Name()) + 1;
const char *p = strrchr(FileName, '/'); const char *p = strrchr(FileName, '/');
name = NULL; name = NULL;
@ -949,7 +949,7 @@ char *cRecording::SortName(void) const
{ {
char **sb = (RecordingsSortMode == rsmName) ? &sortBufferName : &sortBufferTime; char **sb = (RecordingsSortMode == rsmName) ? &sortBufferName : &sortBufferTime;
if (!*sb) { if (!*sb) {
char *s = strdup(FileName() + strlen(VideoDirectory)); char *s = strdup(FileName() + strlen(cVideoDirectory::Name()));
if (RecordingsSortMode != rsmName || Setup.AlwaysSortFoldersFirst) if (RecordingsSortMode != rsmName || Setup.AlwaysSortFoldersFirst)
s = StripEpisodeName(s, RecordingsSortMode != rsmName); s = StripEpisodeName(s, RecordingsSortMode != rsmName);
strreplace(s, '/', '0'); // some locales ignore '/' when sorting strreplace(s, '/', '0'); // some locales ignore '/' when sorting
@ -990,11 +990,11 @@ const char *cRecording::FileName(void) const
const char *fmt = isPesRecording ? NAMEFORMATPES : NAMEFORMATTS; const char *fmt = isPesRecording ? NAMEFORMATPES : NAMEFORMATTS;
int ch = isPesRecording ? priority : channel; int ch = isPesRecording ? priority : channel;
int ri = isPesRecording ? lifetime : instanceId; int ri = isPesRecording ? lifetime : instanceId;
char *Name = LimitNameLengths(strdup(name), DirectoryPathMax - strlen(VideoDirectory) - 1 - 42, DirectoryNameMax); // 42 = length of an actual recording directory name (generated with DATAFORMATTS) plus some reserve char *Name = LimitNameLengths(strdup(name), DirectoryPathMax - strlen(cVideoDirectory::Name()) - 1 - 42, DirectoryNameMax); // 42 = length of an actual recording directory name (generated with DATAFORMATTS) plus some reserve
if (strcmp(Name, name) != 0) if (strcmp(Name, name) != 0)
dsyslog("recording file name '%s' truncated to '%s'", name, Name); dsyslog("recording file name '%s' truncated to '%s'", name, Name);
Name = ExchangeChars(Name, true); Name = ExchangeChars(Name, true);
fileName = strdup(cString::sprintf(fmt, VideoDirectory, Name, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, ch, ri)); fileName = strdup(cString::sprintf(fmt, cVideoDirectory::Name(), Name, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, ch, ri));
free(Name); free(Name);
} }
return fileName; return fileName;
@ -1063,7 +1063,7 @@ const char *cRecording::Title(char Delimiter, bool NewIndicator, int Level) cons
const char *cRecording::PrefixFileName(char Prefix) const char *cRecording::PrefixFileName(char Prefix)
{ {
cString p = PrefixVideoFileName(FileName(), Prefix); cString p = cVideoDirectory::PrefixVideoFileName(FileName(), Prefix);
if (*p) { if (*p) {
free(fileName); free(fileName);
fileName = strdup(p); fileName = strdup(p);
@ -1093,7 +1093,7 @@ bool cRecording::IsEdited(void) const
bool cRecording::IsOnVideoDirectoryFileSystem(void) const bool cRecording::IsOnVideoDirectoryFileSystem(void) const
{ {
if (isOnVideoDirectoryFileSystem < 0) if (isOnVideoDirectoryFileSystem < 0)
isOnVideoDirectoryFileSystem = ::IsOnVideoDirectoryFileSystem(FileName()); isOnVideoDirectoryFileSystem = cVideoDirectory::IsOnVideoDirectoryFileSystem(FileName());
return isOnVideoDirectoryFileSystem; return isOnVideoDirectoryFileSystem;
} }
@ -1135,11 +1135,11 @@ bool cRecording::Delete(void)
if (access(NewName, F_OK) == 0) { if (access(NewName, F_OK) == 0) {
// the new name already exists, so let's remove that one first: // the new name already exists, so let's remove that one first:
isyslog("removing recording '%s'", NewName); isyslog("removing recording '%s'", NewName);
RemoveVideoFile(NewName); cVideoDirectory::RemoveVideoFile(NewName);
} }
isyslog("deleting recording '%s'", FileName()); isyslog("deleting recording '%s'", FileName());
if (access(FileName(), F_OK) == 0) { if (access(FileName(), F_OK) == 0) {
result = RenameVideoFile(FileName(), NewName); result = cVideoDirectory::RenameVideoFile(FileName(), NewName);
cRecordingUserCommand::InvokeCommand(RUC_DELETERECORDING, NewName); cRecordingUserCommand::InvokeCommand(RUC_DELETERECORDING, NewName);
} }
else { else {
@ -1159,7 +1159,7 @@ bool cRecording::Remove(void)
return false; return false;
} }
isyslog("removing recording %s", FileName()); isyslog("removing recording %s", FileName());
return RemoveVideoFile(FileName()); return cVideoDirectory::RemoveVideoFile(FileName());
} }
bool cRecording::Undelete(void) bool cRecording::Undelete(void)
@ -1177,7 +1177,7 @@ bool cRecording::Undelete(void)
else { else {
isyslog("undeleting recording '%s'", FileName()); isyslog("undeleting recording '%s'", FileName());
if (access(FileName(), F_OK) == 0) if (access(FileName(), F_OK) == 0)
result = RenameVideoFile(FileName(), NewName); result = cVideoDirectory::RenameVideoFile(FileName(), NewName);
else { else {
isyslog("deleted recording '%s' vanished", FileName()); isyslog("deleted recording '%s' vanished", FileName());
result = false; result = false;
@ -1250,7 +1250,7 @@ void cRecordings::Action(void)
const char *cRecordings::UpdateFileName(void) const char *cRecordings::UpdateFileName(void)
{ {
if (!updateFileName) if (!updateFileName)
updateFileName = strdup(AddDirectory(VideoDirectory, ".update")); updateFileName = strdup(AddDirectory(cVideoDirectory::Name(), ".update"));
return updateFileName; return updateFileName;
} }
@ -1261,7 +1261,7 @@ void cRecordings::Refresh(bool Foreground)
Clear(); Clear();
ChangeState(); ChangeState();
Unlock(); Unlock();
ScanVideoDir(VideoDirectory, Foreground); ScanVideoDir(cVideoDirectory::Name(), Foreground);
} }
void cRecordings::ScanVideoDir(const char *DirName, bool Foreground, int LinkLevel) void cRecordings::ScanVideoDir(const char *DirName, bool Foreground, int LinkLevel)
@ -2274,7 +2274,7 @@ cUnbufferedFile *cFileName::Open(void)
int BlockingFlag = blocking ? 0 : O_NONBLOCK; int BlockingFlag = blocking ? 0 : O_NONBLOCK;
if (record) { if (record) {
dsyslog("recording to '%s'", fileName); dsyslog("recording to '%s'", fileName);
file = OpenVideoFile(fileName, O_RDWR | O_CREAT | O_LARGEFILE | BlockingFlag); file = cVideoDirectory::OpenVideoFile(fileName, O_RDWR | O_CREAT | O_LARGEFILE | BlockingFlag);
if (!file) if (!file)
LOG_ERROR_STR(fileName); LOG_ERROR_STR(fileName);
} }
@ -2295,8 +2295,9 @@ cUnbufferedFile *cFileName::Open(void)
void cFileName::Close(void) void cFileName::Close(void)
{ {
if (file) { if (file) {
if (CloseVideoFile(file) < 0) if (file->Close() < 0)
LOG_ERROR_STR(fileName); LOG_ERROR_STR(fileName);
delete file;
file = NULL; file = NULL;
} }
} }

View File

@ -10,7 +10,7 @@
* and interact with the Video Disk Recorder - or write a full featured * and interact with the Video Disk Recorder - or write a full featured
* graphical interface that sits on top of an SVDRP connection. * graphical interface that sits on top of an SVDRP connection.
* *
* $Id: svdrp.c 2.24 2013/02/17 13:18:01 kls Exp $ * $Id: svdrp.c 3.1 2013/09/10 13:21:38 kls Exp $
*/ */
#include "svdrp.h" #include "svdrp.h"
@ -1550,7 +1550,7 @@ void cSVDRP::CmdSTAT(const char *Option)
if (*Option) { if (*Option) {
if (strcasecmp(Option, "DISK") == 0) { if (strcasecmp(Option, "DISK") == 0) {
int FreeMB, UsedMB; int FreeMB, UsedMB;
int Percent = VideoDiskSpace(&FreeMB, &UsedMB); int Percent = cVideoDirectory::VideoDiskSpace(&FreeMB, &UsedMB);
Reply(250, "%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent); Reply(250, "%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent);
} }
else else

5
vdr.c
View File

@ -22,7 +22,7 @@
* *
* The project's page is at http://www.tvdr.de * The project's page is at http://www.tvdr.de
* *
* $Id: vdr.c 3.1 2013/06/10 14:28:43 kls Exp $ * $Id: vdr.c 3.2 2013/09/10 13:58:34 kls Exp $
*/ */
#include <getopt.h> #include <getopt.h>
@ -663,7 +663,7 @@ int main(int argc, char *argv[])
// Directories: // Directories:
SetVideoDirectory(VideoDirectory); cVideoDirectory::SetName(VideoDirectory);
if (!ConfigDirectory) if (!ConfigDirectory)
ConfigDirectory = DEFAULTCONFDIR; ConfigDirectory = DEFAULTCONFDIR;
cPlugin::SetConfigDirectory(ConfigDirectory); cPlugin::SetConfigDirectory(ConfigDirectory);
@ -1406,6 +1406,7 @@ Exit:
} }
cDevice::Shutdown(); cDevice::Shutdown();
cPositioner::DestroyPositioner(); cPositioner::DestroyPositioner();
cVideoDirectory::Destroy();
EpgHandlers.Clear(); EpgHandlers.Clear();
PluginManager.Shutdown(true); PluginManager.Shutdown(true);
cSchedules::Cleanup(true); cSchedules::Cleanup(true);

View File

@ -1,10 +1,10 @@
/* /*
* videodir.c: Functions to maintain a distributed video directory * videodir.c: Functions to maintain the video directory
* *
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * how to reach the author.
* *
* $Id: videodir.c 3.1 2013/08/23 12:28:06 kls Exp $ * $Id: videodir.c 3.2 2013/09/11 12:20:37 kls Exp $
*/ */
#include "videodir.h" #include "videodir.h"
@ -19,213 +19,129 @@
#include "recording.h" #include "recording.h"
#include "tools.h" #include "tools.h"
//#define DEPRECATED_DISTRIBUTED_VIDEODIR // Code enclosed with this macro is deprecated and will be removed in a future version #ifdef DEPRECATED_VIDEODIR
const char *VideoDirectory = VIDEODIR; const char *VideoDirectory = VIDEODIR;
void SetVideoDirectory(const char *Directory)
{
VideoDirectory = strdup(Directory);
}
class cVideoDirectory {
#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
private:
char *name, *stored, *adjusted;
int length, number, digits;
#endif #endif
public: cString cVideoDirectory::name;
cVideoDirectory(void); cVideoDirectory *cVideoDirectory::current = NULL;
~cVideoDirectory();
int FreeMB(int *UsedMB = NULL);
const char *Name(void) { return
#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
name ? name :
#endif
VideoDirectory; }
#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
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);
#endif
};
cVideoDirectory::cVideoDirectory(void) cVideoDirectory::cVideoDirectory(void)
{ {
#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR delete current;
length = strlen(VideoDirectory); current = this;
name = (VideoDirectory[length - 1] == '0') ? strdup(VideoDirectory) : NULL;
stored = adjusted = NULL;
number = -1;
digits = 0;
#endif
} }
cVideoDirectory::~cVideoDirectory() cVideoDirectory::~cVideoDirectory()
{ {
#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR current = NULL;
free(name); }
free(stored);
free(adjusted); cVideoDirectory *cVideoDirectory::Current(void)
#endif {
if (!current)
current = new cVideoDirectory;
return current;
}
void cVideoDirectory::Destroy(void)
{
delete current;
} }
int cVideoDirectory::FreeMB(int *UsedMB) int cVideoDirectory::FreeMB(int *UsedMB)
{ {
return FreeDiskSpaceMB( return FreeDiskSpaceMB(Name(), UsedMB);
#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR }
name ? name :
const char *cVideoDirectory::Name(void)
{
return name;
}
void cVideoDirectory::SetName(const char *Name)
{
name = Name;
#ifdef DEPRECATED_VIDEODIR
VideoDirectory = Name;
#endif #endif
VideoDirectory, UsedMB);
} }
#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR bool cVideoDirectory::Register(const char *FileName)
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) {
free(stored);
stored = strdup(name);
}
}
const char *cVideoDirectory::Adjust(const char *FileName)
{
if (stored) {
free(adjusted);
adjusted = strdup(FileName);
return strncpy(adjusted, stored, length);
}
return NULL;
}
#endif
cUnbufferedFile *OpenVideoFile(const char *FileName, int Flags)
{
const char *ActualFileName = FileName;
// Incoming name must be in base video directory: // Incoming name must be in base video directory:
if (strstr(FileName, VideoDirectory) != FileName) { if (strstr(FileName, Name()) != FileName) {
esyslog("ERROR: %s not in %s", FileName, VideoDirectory); esyslog("ERROR: %s not in %s", FileName, Name());
errno = ENOENT; // must set 'errno' - any ideas for a better value? errno = ENOENT; // must set 'errno' - any ideas for a better value?
return NULL;
}
#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
// 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:
int MaxFree = Dir.FreeMB();
while (Dir.Next()) {
int Free = FreeDiskSpaceMB(Dir.Name());
if (Free > MaxFree) {
Dir.Store();
MaxFree = Free;
}
}
if (Dir.Stored()) {
ActualFileName = Dir.Adjust(FileName);
if (!MakeDirs(ActualFileName, false))
return NULL; // errno has been set by MakeDirs()
if (symlink(ActualFileName, FileName) < 0) {
LOG_ERROR_STR(FileName);
return NULL;
}
ActualFileName = strdup(ActualFileName); // must survive Dir!
}
}
}
#endif
cUnbufferedFile *File = cUnbufferedFile::Create(ActualFileName, Flags, DEFFILEMODE);
if (ActualFileName != FileName)
free((char *)ActualFileName);
return File;
}
int CloseVideoFile(cUnbufferedFile *File)
{
int Result = File->Close();
delete File;
return Result;
}
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 false;
} }
return true; return true;
} }
bool RemoveVideoFile(const char *FileName) bool cVideoDirectory::Rename(const char *OldName, const char *NewName)
{ {
return RemoveFileOrDir(FileName, true); if (rename(OldName, NewName) == -1) {
} LOG_ERROR_STR(NewName);
bool VideoFileSpaceAvailable(int SizeMB)
{
cVideoDirectory Dir;
#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
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 false;
} }
#endif return true;
return Dir.FreeMB() >= SizeMB;
} }
int VideoDiskSpace(int *FreeMB, int *UsedMB) bool cVideoDirectory::Move(const char *FromName, const char *ToName)
{ {
int free = 0, used = 0; if (rename(FromName, ToName) == -1) {
LOG_ERROR_STR(ToName);
return false;
}
return true;
}
bool cVideoDirectory::Remove(const char *Name)
{
return RemoveFileOrDir(Name);
}
void cVideoDirectory::Cleanup(const char *IgnoreFiles[])
{
RemoveEmptyDirectories(Name(), false, IgnoreFiles);
}
bool cVideoDirectory::Contains(const char *Name)
{
return EntriesOnSameFileSystem(this->Name(), Name);
}
cUnbufferedFile *cVideoDirectory::OpenVideoFile(const char *FileName, int Flags)
{
if (Current()->Register(FileName))
return cUnbufferedFile::Create(FileName, Flags, DEFFILEMODE);
return NULL;
}
bool cVideoDirectory::RenameVideoFile(const char *OldName, const char *NewName)
{
return Current()->Rename(OldName, NewName);
}
bool cVideoDirectory::MoveVideoFile(const char *FromName, const char *ToName)
{
return Current()->Move(FromName, ToName);
}
bool cVideoDirectory::RemoveVideoFile(const char *FileName)
{
return Current()->Remove(FileName);
}
bool cVideoDirectory::VideoFileSpaceAvailable(int SizeMB)
{
return Current()->FreeMB() >= SizeMB;
}
int cVideoDirectory::VideoDiskSpace(int *FreeMB, int *UsedMB)
{
int used = 0;
int free = Current()->FreeMB(&used);
int deleted = DeletedRecordings.TotalFileSizeMB(); int deleted = DeletedRecordings.TotalFileSizeMB();
cVideoDirectory Dir;
#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
do {
#endif
int u;
free += Dir.FreeMB(&u);
used += u;
#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
} while (Dir.Next());
#endif
if (deleted > used) if (deleted > used)
deleted = used; // let's not get beyond 100% deleted = used; // let's not get beyond 100%
free += deleted; free += deleted;
@ -237,7 +153,7 @@ int VideoDiskSpace(int *FreeMB, int *UsedMB)
return (free + used) ? used * 100 / (free + used) : 0; return (free + used) ? used * 100 / (free + used) : 0;
} }
cString PrefixVideoFileName(const char *FileName, char Prefix) cString cVideoDirectory::PrefixVideoFileName(const char *FileName, char Prefix)
{ {
char PrefixedName[strlen(FileName) + 2]; char PrefixedName[strlen(FileName) + 2];
@ -257,30 +173,14 @@ cString PrefixVideoFileName(const char *FileName, char Prefix)
return NULL; return NULL;
} }
void RemoveEmptyVideoDirectories(const char *IgnoreFiles[]) void cVideoDirectory::RemoveEmptyVideoDirectories(const char *IgnoreFiles[])
{ {
cVideoDirectory Dir; Current()->Cleanup(IgnoreFiles);
#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
do {
#endif
RemoveEmptyDirectories(Dir.Name(), false, IgnoreFiles);
#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
} while (Dir.Next());
#endif
} }
bool IsOnVideoDirectoryFileSystem(const char *FileName) bool cVideoDirectory::IsOnVideoDirectoryFileSystem(const char *FileName)
{ {
cVideoDirectory Dir; return Current()->Contains(FileName);
#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
do {
#endif
if (EntriesOnSameFileSystem(Dir.Name(), FileName))
return true;
#ifdef DEPRECATED_DISTRIBUTED_VIDEODIR
} while (Dir.Next());
#endif
return false;
} }
// --- cVideoDiskUsage ------------------------------------------------------- // --- cVideoDiskUsage -------------------------------------------------------
@ -298,7 +198,7 @@ bool cVideoDiskUsage::HasChanged(int &State)
{ {
if (time(NULL) - lastChecked > DISKSPACECHEK) { if (time(NULL) - lastChecked > DISKSPACECHEK) {
int FreeMB; int FreeMB;
int UsedPercent = VideoDiskSpace(&FreeMB); int UsedPercent = cVideoDirectory::VideoDiskSpace(&FreeMB);
if (FreeMB != freeMB) { if (FreeMB != freeMB) {
usedPercent = UsedPercent; usedPercent = UsedPercent;
freeMB = FreeMB; freeMB = FreeMB;

View File

@ -1,10 +1,10 @@
/* /*
* videodir.h: Functions to maintain a distributed video directory * videodir.h: Functions to maintain the video directory
* *
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * how to reach the author.
* *
* $Id: videodir.h 2.3 2012/09/30 11:01:15 kls Exp $ * $Id: videodir.h 3.1 2013/09/11 12:19:47 kls Exp $
*/ */
#ifndef __VIDEODIR_H #ifndef __VIDEODIR_H
@ -13,18 +13,80 @@
#include <stdlib.h> #include <stdlib.h>
#include "tools.h" #include "tools.h"
#define DEPRECATED_VIDEODIR
#ifdef DEPRECATED_VIDEODIR
extern const char *VideoDirectory; extern const char *VideoDirectory;
#endif
void SetVideoDirectory(const char *Directory); class cVideoDirectory {
cUnbufferedFile *OpenVideoFile(const char *FileName, int Flags); private:
int CloseVideoFile(cUnbufferedFile *File); static cString name;
bool RenameVideoFile(const char *OldName, const char *NewName); static cVideoDirectory *current;
bool RemoveVideoFile(const char *FileName); static cVideoDirectory *Current(void);
bool VideoFileSpaceAvailable(int SizeMB); public:
int VideoDiskSpace(int *FreeMB = NULL, int *UsedMB = NULL); // returns the used disk space in percent cVideoDirectory(void);
cString PrefixVideoFileName(const char *FileName, char Prefix); virtual ~cVideoDirectory();
void RemoveEmptyVideoDirectories(const char *IgnoreFiles[] = NULL); virtual int FreeMB(int *UsedMB = NULL);
bool IsOnVideoDirectoryFileSystem(const char *FileName); ///< Returns the total amount (in MB) of free disk space for recording.
///< If UsedMB is given, it returns the amount of disk space in use by
///< existing recordings (or anything else) on that disk.
virtual bool Register(const char *FileName);
///< By default VDR assumes that the video directory consists of one large
///< volume, on which it can store its recordings. A derived cVideoDirectory
///< may, for instance, use several separate disks to store recordings.
///< The given FileName is the full path name (including the video directory) of
///< a recording file ('*.ts') that is about to be opened for writing. If the actual
///< file shall be put on an other disk, the derived cVideoDirectory should
///< create a symbolic link from the given FileName to the other location.
///< Returns true if the operation was successful.
///< The default implementation just checks whether the incoming file name really
///< is under the video directory.
virtual bool Rename(const char *OldName, const char *NewName);
///< Renames the directory OldName to NewName.
///< OldName and NewName are full path names that begin with the name of the
///< video directory and end with '*.rec' or '*.del'. Only the base name (the
///< rightmost component) of the two names may be different.
///< Returns true if the operation was successful.
///< The default implementation just calls the system's rename() function.
virtual bool Move(const char *FromName, const char *ToName);
///< Moves the directory FromName to the location ToName. FromName is the full
///< path name of a recording's '*.rec' directory. ToName has the same '*.rec'
///< part as FromName, but a different directory path above it.
///< Returns true if the operation was successful.
///< The default implementation just calls the system's rename() function.
virtual bool Remove(const char *Name);
///< Removes the directory with the given Name and everything it contains.
///< Name is a full path name that begins with the name of the video directory.
///< Returns true if the operation was successful.
///< The default implementation calls RemoveFileOrDir().
virtual void Cleanup(const char *IgnoreFiles[] = NULL);
///< Recursively removes all empty directories under the video directory.
///< If IgnoreFiles is given, the file names in this (NULL terminated) array
///< are ignored when checking whether a directory is empty. These are
///< typically "dot files", like e.g. ".sort".
///< The default implementation calls RemoveEmptyDirectories().
virtual bool Contains(const char *Name);
///< Checks whether the directory Name is on the same file system as the
///< video directory. Name is the full path name of a recording's '*.rec'
///< directory. This function is usually called when an ongoing recording
///< is about to run out of disk space, and an existing (old) recording needs
///< to be deleted. It shall make sure that deleting this old recording will
///< actually free up space in the video directory, and not on some other
///< device that just happens to be mounted.
///< The default implementation calls EntriesOnSameFileSystem().
static const char *Name(void);
static void SetName(const char *Name);
static void Destroy(void);
static cUnbufferedFile *OpenVideoFile(const char *FileName, int Flags);
static bool RenameVideoFile(const char *OldName, const char *NewName);
static bool MoveVideoFile(const char *FromName, const char *ToName);
static bool RemoveVideoFile(const char *FileName);
static bool VideoFileSpaceAvailable(int SizeMB);
static int VideoDiskSpace(int *FreeMB = NULL, int *UsedMB = NULL); // returns the used disk space in percent
static cString PrefixVideoFileName(const char *FileName, char Prefix);
static void RemoveEmptyVideoDirectories(const char *IgnoreFiles[] = NULL);
static bool IsOnVideoDirectoryFileSystem(const char *FileName);
};
class cVideoDiskUsage { class cVideoDiskUsage {
private: private: