vdr/videodir.c
Klaus Schmidinger d34026c18b Version 1.7.30
VDR developer version 1.7.30 is now available at

       ftp://ftp.tvdr.de/vdr/Developer/vdr-1.7.30.tar.bz2

A 'diff' against the previous version is available at

       ftp://ftp.tvdr.de/vdr/Developer/vdr-1.7.29-1.7.30.diff

MD5 checksums:

c6d75f2962bc3e22d9313c0ee4fa113a  vdr-1.7.30.tar.bz2
a63098efcc58bc697d6b890097d9c370  vdr-1.7.29-1.7.30.diff

WARNING:
========

This is a developer version. Even though I use it in my productive
environment. I strongly recommend that you only use it under controlled
conditions and for testing and debugging.

The default skin "LCARS" displays the signal strengths and qualities of
all devices in its main menu. For devices that have an stb0899 frontend chip
(like the TT-budget S2-3200) retrieving this information from the driver is
rather slow, which results in a sluggish response to user input in the main
menu. To speed this up you may want to apply the patches from

   ftp://ftp.tvdr.de/vdr/Developer/Driver-Patches

to the LinuxDVB driver source.

From the HISTORY file:
- Fixed sorting recordings in the top level video directory.
- Fixed handling control characters in SI data in case of UTF-8 encoded strings
   (thanks to Mehdi Karamnejad for reporting a problem with garbled UTF-8 EPG data
   and helping to debug it).
- Updated the Finnish OSD texts (thanks to Rolf Ahrenberg).
- When checking whether a video directory is empty, file names that start with a
   dot ('.') are now ignored and will be implicitly removed if the directory contains
   no other files. This fixes the leftover ".sort" files that were introduced in
   version 1.7.29.
- Added IsUpdate() to the EPG handler interface (thanks to Jörg Wendel).
- Fixed detecting transfer mode on full featured DVB cards (thanks to Stefan Huelswitt
   for reporting a problem with updating CA descriptors in such cases).
- Fixed a race condition when zapping in transfer mode (reported by Reinhard Nissl).
   This involves a slight change in the semantics of the cReceiver::Activate() function,
   which is now called with 'false' after the receiver has been detached from the
   device.
- The new function cDevice::ReadFilter() can be used by devices to implement their
   own way of retrieving section filter data (thanks to Deti Fliegl).
- The new function cDevice::HasInternalCam() can be implemented by devices that
   provide encrypted channels in an already decrypted form, without requiring explicit
   handling of a CAM (thanks to Tobias Grimm).
- VDR can now be built according to the FHS ("File system Hierarchy Standard") by
   activating the line
   #USEFHS = 1
   in a copy of the file Make.config.template (thanks to Dennis Bendlin, as well as
   Christopher Reimer and Udo Richter for contributing to the patch).
- By default (without FHS support) the config directory is now set to the value
   given in the -v option if only -v and no -c is given.
- Fixed a long delay at the end when replaying a recording that has stopped recording
   less than an hour ago (typically time shift mode or a freshly edited recording).
- Fixed getting the file size and number of frames of ongoing recordings (only the
   timestamp of the recording's directory was checked, while it should have been that
   of the index file).
- Fixed sluggish response when manipulating editing marks while a cutting thread
   is running (reported by Torsten Lang).
- The new setup options "OSD/Color key [0123]" can be used to adjust the sequence
   of the color buttons displayed in the menus to that of the color keys on your
   remote control (based on a patch from Oliver Schinagl).
   Authors of plugins that implement skins may want to adapt their SetButtons()
   function in order to make use of this new feature. See, for instance, the function
   cSkinClassicDisplayMenu::SetButtons() in skinclassic.c for details.
2012-09-11 23:49:53 +02:00

289 lines
7.1 KiB
C

/*
* 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 2.3 2012/09/01 10:57:44 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 "recording.h"
#include "tools.h"
const char *VideoDirectory = VIDEODIR;
void SetVideoDirectory(const char *Directory)
{
VideoDirectory = strdup(Directory);
}
class cVideoDirectory {
private:
char *name, *stored, *adjusted;
int length, number, digits;
public:
cVideoDirectory(void);
~cVideoDirectory();
int FreeMB(int *UsedMB = NULL);
const char *Name(void) { return name ? name : VideoDirectory; }
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()
{
free(name);
free(stored);
free(adjusted);
}
int cVideoDirectory::FreeMB(int *UsedMB)
{
return FreeDiskSpaceMB(name ? name : VideoDirectory, UsedMB);
}
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;
}
cUnbufferedFile *OpenVideoFile(const char *FileName, int Flags)
{
const char *ActualFileName = FileName;
// Incoming name must be in base video directory:
if (strstr(FileName, VideoDirectory) != FileName) {
esyslog("ERROR: %s not in %s", FileName, VideoDirectory);
errno = ENOENT; // must set 'errno' - any ideas for a better value?
return NULL;
}
// 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!
}
}
}
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 true;
}
bool RemoveVideoFile(const char *FileName)
{
return RemoveFileOrDir(FileName, true);
}
bool VideoFileSpaceAvailable(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;
}
int VideoDiskSpace(int *FreeMB, int *UsedMB)
{
int free = 0, used = 0;
int deleted = DeletedRecordings.TotalFileSizeMB();
cVideoDirectory Dir;
do {
int u;
free += Dir.FreeMB(&u);
used += u;
} while (Dir.Next());
if (deleted > used)
deleted = used; // let's not get beyond 100%
free += deleted;
used -= deleted;
if (FreeMB)
*FreeMB = free;
if (UsedMB)
*UsedMB = used;
return (free + used) ? used * 100 / (free + used) : 0;
}
cString PrefixVideoFileName(const char *FileName, char Prefix)
{
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;
}
}
}
return NULL;
}
void RemoveEmptyVideoDirectories(void)
{
cVideoDirectory Dir;
do {
RemoveEmptyDirectories(Dir.Name());
} while (Dir.Next());
}
bool IsOnVideoDirectoryFileSystem(const char *FileName)
{
cVideoDirectory Dir;
do {
if (EntriesOnSameFileSystem(Dir.Name(), FileName))
return true;
} while (Dir.Next());
return false;
}
// --- 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;
double MBperMinute = Recordings.MBperMinute();
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"));
}