2000-07-29 15:21:42 +02:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*
|
2012-06-10 13:46:41 +02:00
|
|
|
* $Id: videodir.c 2.2 2012/06/10 13:45:21 kls Exp $
|
2000-07-29 15:21:42 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
#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>
|
2005-12-18 10:41:26 +01:00
|
|
|
#include "recording.h"
|
2000-07-29 15:21:42 +02:00
|
|
|
#include "tools.h"
|
|
|
|
|
2003-08-02 14:32:53 +02:00
|
|
|
const char *VideoDirectory = VIDEODIR;
|
2000-07-29 15:21:42 +02:00
|
|
|
|
|
|
|
class cVideoDirectory {
|
|
|
|
private:
|
|
|
|
char *name, *stored, *adjusted;
|
|
|
|
int length, number, digits;
|
|
|
|
public:
|
|
|
|
cVideoDirectory(void);
|
|
|
|
~cVideoDirectory();
|
2002-01-27 13:11:23 +01:00
|
|
|
int FreeMB(int *UsedMB = NULL);
|
2001-02-11 14:53:44 +01:00
|
|
|
const char *Name(void) { return name ? name : VideoDirectory; }
|
2000-07-29 15:21:42 +02:00
|
|
|
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()
|
|
|
|
{
|
2002-08-11 13:32:23 +02:00
|
|
|
free(name);
|
|
|
|
free(stored);
|
|
|
|
free(adjusted);
|
2000-07-29 15:21:42 +02:00
|
|
|
}
|
|
|
|
|
2002-01-27 13:11:23 +01:00
|
|
|
int cVideoDirectory::FreeMB(int *UsedMB)
|
2000-07-29 15:21:42 +02:00
|
|
|
{
|
2002-01-27 13:11:23 +01:00
|
|
|
return FreeDiskSpaceMB(name ? name : VideoDirectory, UsedMB);
|
2000-07-29 15:21:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2002-08-11 13:32:23 +02:00
|
|
|
free(stored);
|
2000-07-29 15:21:42 +02:00
|
|
|
stored = strdup(name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *cVideoDirectory::Adjust(const char *FileName)
|
|
|
|
{
|
|
|
|
if (stored) {
|
2002-08-11 13:32:23 +02:00
|
|
|
free(adjusted);
|
2000-07-29 15:21:42 +02:00
|
|
|
adjusted = strdup(FileName);
|
|
|
|
return strncpy(adjusted, stored, length);
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2005-10-31 13:14:26 +01:00
|
|
|
cUnbufferedFile *OpenVideoFile(const char *FileName, int Flags)
|
2000-07-29 15:21:42 +02:00
|
|
|
{
|
|
|
|
const char *ActualFileName = FileName;
|
|
|
|
|
|
|
|
// Incoming name must be in base video directory:
|
|
|
|
if (strstr(FileName, VideoDirectory) != FileName) {
|
2002-05-13 16:35:49 +02:00
|
|
|
esyslog("ERROR: %s not in %s", FileName, VideoDirectory);
|
2000-07-29 15:21:42 +02:00
|
|
|
errno = ENOENT; // must set 'errno' - any ideas for a better value?
|
2005-10-31 13:14:26 +01:00
|
|
|
return NULL;
|
2000-07-29 15:21:42 +02:00
|
|
|
}
|
|
|
|
// 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:
|
2002-01-27 13:11:23 +01:00
|
|
|
int MaxFree = Dir.FreeMB();
|
2000-07-29 15:21:42 +02:00
|
|
|
while (Dir.Next()) {
|
2002-01-27 13:11:23 +01:00
|
|
|
int Free = FreeDiskSpaceMB(Dir.Name());
|
2000-07-29 15:21:42 +02:00
|
|
|
if (Free > MaxFree) {
|
|
|
|
Dir.Store();
|
|
|
|
MaxFree = Free;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (Dir.Stored()) {
|
|
|
|
ActualFileName = Dir.Adjust(FileName);
|
|
|
|
if (!MakeDirs(ActualFileName, false))
|
2005-10-31 13:14:26 +01:00
|
|
|
return NULL; // errno has been set by MakeDirs()
|
2000-07-29 15:21:42 +02:00
|
|
|
if (symlink(ActualFileName, FileName) < 0) {
|
|
|
|
LOG_ERROR_STR(FileName);
|
2005-10-31 13:14:26 +01:00
|
|
|
return NULL;
|
2000-07-29 15:21:42 +02:00
|
|
|
}
|
|
|
|
ActualFileName = strdup(ActualFileName); // must survive Dir!
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2005-10-31 13:14:26 +01:00
|
|
|
cUnbufferedFile *File = cUnbufferedFile::Create(ActualFileName, Flags, DEFFILEMODE);
|
2000-07-29 15:21:42 +02:00
|
|
|
if (ActualFileName != FileName)
|
2002-08-11 13:32:23 +02:00
|
|
|
free((char *)ActualFileName);
|
2005-10-31 13:14:26 +01:00
|
|
|
return File;
|
2000-07-29 15:21:42 +02:00
|
|
|
}
|
|
|
|
|
2005-10-31 13:14:26 +01:00
|
|
|
int CloseVideoFile(cUnbufferedFile *File)
|
2000-07-29 15:21:42 +02:00
|
|
|
{
|
2005-10-31 13:14:26 +01:00
|
|
|
int Result = File->Close();
|
|
|
|
delete File;
|
|
|
|
return Result;
|
2000-07-29 15:21:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2002-01-27 13:11:23 +01:00
|
|
|
bool VideoFileSpaceAvailable(int SizeMB)
|
2000-07-29 15:21:42 +02:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
2000-12-28 12:57:16 +01:00
|
|
|
|
2002-01-27 13:11:23 +01:00
|
|
|
int VideoDiskSpace(int *FreeMB, int *UsedMB)
|
|
|
|
{
|
|
|
|
int free = 0, used = 0;
|
2005-12-18 10:41:26 +01:00
|
|
|
int deleted = DeletedRecordings.TotalFileSizeMB();
|
2002-01-27 13:11:23 +01:00
|
|
|
cVideoDirectory Dir;
|
|
|
|
do {
|
|
|
|
int u;
|
|
|
|
free += Dir.FreeMB(&u);
|
|
|
|
used += u;
|
|
|
|
} while (Dir.Next());
|
2005-12-18 10:41:26 +01:00
|
|
|
if (deleted > used)
|
|
|
|
deleted = used; // let's not get beyond 100%
|
|
|
|
free += deleted;
|
|
|
|
used -= deleted;
|
2002-01-27 13:11:23 +01:00
|
|
|
if (FreeMB)
|
|
|
|
*FreeMB = free;
|
|
|
|
if (UsedMB)
|
|
|
|
*UsedMB = used;
|
|
|
|
return (free + used) ? used * 100 / (free + used) : 0;
|
|
|
|
}
|
|
|
|
|
2004-12-26 12:45:22 +01:00
|
|
|
cString PrefixVideoFileName(const char *FileName, char Prefix)
|
2000-12-28 12:57:16 +01:00
|
|
|
{
|
2004-12-26 12:45:22 +01:00
|
|
|
char PrefixedName[strlen(FileName) + 2];
|
|
|
|
|
|
|
|
const char *p = FileName + strlen(FileName); // p points at the terminating 0
|
|
|
|
int n = 2;
|
|
|
|
while (p-- > FileName && n > 0) {
|
|
|
|
if (*p == '/') {
|
|
|
|
if (--n == 0) {
|
|
|
|
int l = p - FileName + 1;
|
|
|
|
strncpy(PrefixedName, FileName, l);
|
|
|
|
PrefixedName[l] = Prefix;
|
|
|
|
strcpy(PrefixedName + l + 1, p + 1);
|
|
|
|
return PrefixedName;
|
2001-09-02 15:21:54 +02:00
|
|
|
}
|
|
|
|
}
|
2004-12-26 12:45:22 +01:00
|
|
|
}
|
2001-09-02 15:21:54 +02:00
|
|
|
return NULL;
|
2000-12-28 12:57:16 +01:00
|
|
|
}
|
|
|
|
|
2001-02-11 14:53:44 +01:00
|
|
|
void RemoveEmptyVideoDirectories(void)
|
|
|
|
{
|
|
|
|
cVideoDirectory Dir;
|
|
|
|
do {
|
|
|
|
RemoveEmptyDirectories(Dir.Name());
|
|
|
|
} while (Dir.Next());
|
|
|
|
}
|
|
|
|
|
2008-02-16 13:38:22 +01:00
|
|
|
bool IsOnVideoDirectoryFileSystem(const char *FileName)
|
|
|
|
{
|
|
|
|
cVideoDirectory Dir;
|
|
|
|
do {
|
|
|
|
if (EntriesOnSameFileSystem(Dir.Name(), FileName))
|
|
|
|
return true;
|
|
|
|
} while (Dir.Next());
|
|
|
|
return false;
|
|
|
|
}
|
2012-04-23 09:07:55 +02:00
|
|
|
|
|
|
|
// --- cVideoDiskUsage -------------------------------------------------------
|
|
|
|
|
|
|
|
#define DISKSPACECHEK 5 // seconds between disk space checks
|
|
|
|
#define MB_PER_MINUTE 25.75 // this is just an estimate!
|
|
|
|
|
|
|
|
int cVideoDiskUsage::state = 0;
|
|
|
|
time_t cVideoDiskUsage::lastChecked = 0;
|
|
|
|
int cVideoDiskUsage::usedPercent = 0;
|
|
|
|
int cVideoDiskUsage::freeMB = 0;
|
|
|
|
int cVideoDiskUsage::freeMinutes = 0;
|
|
|
|
|
|
|
|
bool cVideoDiskUsage::HasChanged(int &State)
|
|
|
|
{
|
|
|
|
if (time(NULL) - lastChecked > DISKSPACECHEK) {
|
|
|
|
int FreeMB;
|
|
|
|
int UsedPercent = VideoDiskSpace(&FreeMB);
|
|
|
|
if (FreeMB != freeMB) {
|
|
|
|
usedPercent = UsedPercent;
|
|
|
|
freeMB = FreeMB;
|
2012-06-10 13:46:41 +02:00
|
|
|
double MBperMinute = Recordings.MBperMinute();
|
2012-04-23 09:07:55 +02:00
|
|
|
if (MBperMinute <= 0)
|
|
|
|
MBperMinute = MB_PER_MINUTE;
|
|
|
|
freeMinutes = int(double(FreeMB) / MBperMinute);
|
|
|
|
state++;
|
|
|
|
}
|
|
|
|
lastChecked = time(NULL);
|
|
|
|
}
|
|
|
|
if (State != state) {
|
|
|
|
State = state;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
cString cVideoDiskUsage::String(void)
|
|
|
|
{
|
|
|
|
HasChanged(state);
|
|
|
|
return cString::sprintf("%s %d%% - %2d:%02d %s", tr("Disk"), usedPercent, freeMinutes / 60, freeMinutes % 60, tr("free"));
|
|
|
|
}
|