vdr  2.0.6
videodir.c
Go to the documentation of this file.
1 /*
2  * videodir.c: Functions to maintain a distributed video directory
3  *
4  * See the main source file 'vdr.c' for copyright information and
5  * how to reach the author.
6  *
7  * $Id: videodir.c 2.4 2012/09/30 12:06:33 kls Exp $
8  */
9 
10 #include "videodir.h"
11 #include <ctype.h>
12 #include <errno.h>
13 #include <fcntl.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <sys/stat.h>
18 #include <unistd.h>
19 #include "recording.h"
20 #include "tools.h"
21 
22 const char *VideoDirectory = VIDEODIR;
23 
24 void SetVideoDirectory(const char *Directory)
25 {
26  VideoDirectory = strdup(Directory);
27 }
28 
30 private:
31  char *name, *stored, *adjusted;
33 public:
34  cVideoDirectory(void);
36  int FreeMB(int *UsedMB = NULL);
37  const char *Name(void) { return name ? name : VideoDirectory; }
38  const char *Stored(void) { return stored; }
39  int Length(void) { return length; }
40  bool IsDistributed(void) { return name != NULL; }
41  bool Next(void);
42  void Store(void);
43  const char *Adjust(const char *FileName);
44  };
45 
47 {
48  length = strlen(VideoDirectory);
49  name = (VideoDirectory[length - 1] == '0') ? strdup(VideoDirectory) : NULL;
50  stored = adjusted = NULL;
51  number = -1;
52  digits = 0;
53 }
54 
56 {
57  free(name);
58  free(stored);
59  free(adjusted);
60 }
61 
62 int cVideoDirectory::FreeMB(int *UsedMB)
63 {
64  return FreeDiskSpaceMB(name ? name : VideoDirectory, UsedMB);
65 }
66 
68 {
69  if (name) {
70  if (number < 0) {
71  int l = length;
72  while (l-- > 0 && isdigit(name[l]))
73  ;
74  l++;
75  digits = length - l;
76  int n = atoi(&name[l]);
77  if (n == 0)
78  number = n;
79  else
80  return false; // base video directory must end with zero
81  }
82  if (++number > 0) {
83  char buf[16];
84  if (sprintf(buf, "%0*d", digits, number) == digits) {
85  strcpy(&name[length - digits], buf);
86  return DirectoryOk(name);
87  }
88  }
89  }
90  return false;
91 }
92 
94 {
95  if (name) {
96  free(stored);
97  stored = strdup(name);
98  }
99 }
100 
101 const char *cVideoDirectory::Adjust(const char *FileName)
102 {
103  if (stored) {
104  free(adjusted);
105  adjusted = strdup(FileName);
106  return strncpy(adjusted, stored, length);
107  }
108  return NULL;
109 }
110 
111 cUnbufferedFile *OpenVideoFile(const char *FileName, int Flags)
112 {
113  const char *ActualFileName = FileName;
114 
115  // Incoming name must be in base video directory:
116  if (strstr(FileName, VideoDirectory) != FileName) {
117  esyslog("ERROR: %s not in %s", FileName, VideoDirectory);
118  errno = ENOENT; // must set 'errno' - any ideas for a better value?
119  return NULL;
120  }
121  // Are we going to create a new file?
122  if ((Flags & O_CREAT) != 0) {
123  cVideoDirectory Dir;
124  if (Dir.IsDistributed()) {
125  // Find the directory with the most free space:
126  int MaxFree = Dir.FreeMB();
127  while (Dir.Next()) {
128  int Free = FreeDiskSpaceMB(Dir.Name());
129  if (Free > MaxFree) {
130  Dir.Store();
131  MaxFree = Free;
132  }
133  }
134  if (Dir.Stored()) {
135  ActualFileName = Dir.Adjust(FileName);
136  if (!MakeDirs(ActualFileName, false))
137  return NULL; // errno has been set by MakeDirs()
138  if (symlink(ActualFileName, FileName) < 0) {
139  LOG_ERROR_STR(FileName);
140  return NULL;
141  }
142  ActualFileName = strdup(ActualFileName); // must survive Dir!
143  }
144  }
145  }
146  cUnbufferedFile *File = cUnbufferedFile::Create(ActualFileName, Flags, DEFFILEMODE);
147  if (ActualFileName != FileName)
148  free((char *)ActualFileName);
149  return File;
150 }
151 
153 {
154  int Result = File->Close();
155  delete File;
156  return Result;
157 }
158 
159 bool RenameVideoFile(const char *OldName, const char *NewName)
160 {
161  // Only the base video directory entry will be renamed, leaving the
162  // possible symlinks untouched. Going through all the symlinks and disks
163  // would be unnecessary work - maybe later...
164  if (rename(OldName, NewName) == -1) {
165  LOG_ERROR_STR(OldName);
166  return false;
167  }
168  return true;
169 }
170 
171 bool RemoveVideoFile(const char *FileName)
172 {
173  return RemoveFileOrDir(FileName, true);
174 }
175 
176 bool VideoFileSpaceAvailable(int SizeMB)
177 {
178  cVideoDirectory Dir;
179  if (Dir.IsDistributed()) {
180  if (Dir.FreeMB() >= SizeMB * 2) // base directory needs additional space
181  return true;
182  while (Dir.Next()) {
183  if (Dir.FreeMB() >= SizeMB)
184  return true;
185  }
186  return false;
187  }
188  return Dir.FreeMB() >= SizeMB;
189 }
190 
191 int VideoDiskSpace(int *FreeMB, int *UsedMB)
192 {
193  int free = 0, used = 0;
194  int deleted = DeletedRecordings.TotalFileSizeMB();
195  cVideoDirectory Dir;
196  do {
197  int u;
198  free += Dir.FreeMB(&u);
199  used += u;
200  } while (Dir.Next());
201  if (deleted > used)
202  deleted = used; // let's not get beyond 100%
203  free += deleted;
204  used -= deleted;
205  if (FreeMB)
206  *FreeMB = free;
207  if (UsedMB)
208  *UsedMB = used;
209  return (free + used) ? used * 100 / (free + used) : 0;
210 }
211 
212 cString PrefixVideoFileName(const char *FileName, char Prefix)
213 {
214  char PrefixedName[strlen(FileName) + 2];
215 
216  const char *p = FileName + strlen(FileName); // p points at the terminating 0
217  int n = 2;
218  while (p-- > FileName && n > 0) {
219  if (*p == '/') {
220  if (--n == 0) {
221  int l = p - FileName + 1;
222  strncpy(PrefixedName, FileName, l);
223  PrefixedName[l] = Prefix;
224  strcpy(PrefixedName + l + 1, p + 1);
225  return PrefixedName;
226  }
227  }
228  }
229  return NULL;
230 }
231 
232 cString NewVideoFileName(const char *FileName, const char *NewDirName)
233 {
234  char *NewDir = ExchangeChars(strdup(NewDirName), true);
235  if (NewDir) {
236  const char *p = FileName + strlen(FileName); // p points at the terminating 0
237  while (p-- > FileName) {
238  if (*p == '/')
239  break;
240  }
241  cString NewName = cString::sprintf("%s/%s%s", VideoDirectory, NewDir, p);
242  free(NewDir);
243  return NewName;
244  }
245  return NULL;
246 }
247 
248 void RemoveEmptyVideoDirectories(const char *IgnoreFiles[])
249 {
250  cVideoDirectory Dir;
251  do {
252  RemoveEmptyDirectories(Dir.Name(), false, IgnoreFiles);
253  } while (Dir.Next());
254 }
255 
256 bool IsOnVideoDirectoryFileSystem(const char *FileName)
257 {
258  cVideoDirectory Dir;
259  do {
260  if (EntriesOnSameFileSystem(Dir.Name(), FileName))
261  return true;
262  } while (Dir.Next());
263  return false;
264 }
265 
266 // --- cVideoDiskUsage -------------------------------------------------------
267 
268 #define DISKSPACECHEK 5 // seconds between disk space checks
269 #define MB_PER_MINUTE 25.75 // this is just an estimate!
270 
271 int cVideoDiskUsage::state = 0;
276 
278 {
279  if (time(NULL) - lastChecked > DISKSPACECHEK) {
280  int FreeMB;
281  int UsedPercent = VideoDiskSpace(&FreeMB);
282  if (FreeMB != freeMB) {
284  freeMB = FreeMB;
285  double MBperMinute = Recordings.MBperMinute();
286  if (MBperMinute <= 0)
287  MBperMinute = MB_PER_MINUTE;
288  freeMinutes = int(double(FreeMB) / MBperMinute);
289  state++;
290  }
291  lastChecked = time(NULL);
292  }
293  if (State != state) {
294  State = state;
295  return true;
296  }
297  return false;
298 }
299 
301 {
302  HasChanged(state);
303  return cString::sprintf("%s %d%% - %2d:%02d %s", tr("Disk"), usedPercent, freeMinutes / 60, freeMinutes % 60, tr("free"));
304 }
static int usedPercent
Definition: videodir.h:34
cString NewVideoFileName(const char *FileName, const char *NewDirName)
Definition: videodir.c:232
int TotalFileSizeMB(void)
Definition: recording.c:1444
bool IsDistributed(void)
Definition: videodir.c:40
bool RemoveVideoFile(const char *FileName)
Definition: videodir.c:171
static cString String(void)
Returns a localized string of the form "Disk nn% - hh:mm free".
Definition: videodir.c:300
char * name
Definition: videodir.c:31
bool DirectoryOk(const char *DirName, bool LogErrors)
Definition: tools.c:378
bool MakeDirs(const char *FileName, bool IsDirectory)
Definition: tools.c:396
void SetVideoDirectory(const char *Directory)
Definition: videodir.c:24
bool VideoFileSpaceAvailable(int SizeMB)
Definition: videodir.c:176
static cString sprintf(const char *fmt,...) __attribute__((format(printf
Definition: tools.c:1011
char * stored
Definition: videodir.c:31
const char * VideoDirectory
Definition: videodir.c:22
#define MB_PER_MINUTE
Definition: videodir.c:269
#define esyslog(a...)
Definition: tools.h:34
const char * Name(void)
Definition: videodir.c:37
#define LOG_ERROR_STR(s)
Definition: tools.h:39
cUnbufferedFile * OpenVideoFile(const char *FileName, int Flags)
Definition: videodir.c:111
const char * Adjust(const char *FileName)
Definition: videodir.c:101
void RemoveEmptyVideoDirectories(const char *IgnoreFiles[])
Definition: videodir.c:248
cUnbufferedFile is used for large files that are mainly written or read in a streaming manner...
Definition: tools.h:408
char * adjusted
Definition: videodir.c:31
static bool HasChanged(int &State)
Returns true if the usage of the video disk space has changed since the last call to this function wi...
Definition: videodir.c:277
int FreeMB(int *UsedMB=NULL)
Definition: videodir.c:62
int Length(void)
Definition: videodir.c:39
int FreeDiskSpaceMB(const char *Directory, int *UsedMB)
Definition: tools.c:361
cRecordings DeletedRecordings
const char * Stored(void)
Definition: videodir.c:38
static int freeMB
Definition: videodir.h:35
void Store(void)
Definition: videodir.c:93
int VideoDiskSpace(int *FreeMB, int *UsedMB)
Definition: videodir.c:191
cVideoDirectory(void)
Definition: videodir.c:46
cString PrefixVideoFileName(const char *FileName, char Prefix)
Definition: videodir.c:212
static int state
Definition: videodir.h:32
#define tr(s)
Definition: i18n.h:85
bool IsOnVideoDirectoryFileSystem(const char *FileName)
Definition: videodir.c:256
cRecordings Recordings
Any access to Recordings that loops through the list of recordings needs to hold a thread lock on thi...
Definition: recording.c:1255
double MBperMinute(void)
Returns the average data rate (in MB/min) of all recordings, or -1 if this value is unknown...
Definition: recording.c:1456
bool EntriesOnSameFileSystem(const char *File1, const char *File2)
Definition: tools.c:346
#define DISKSPACECHEK
Definition: videodir.c:268
bool Next(void)
Definition: videodir.c:67
bool RemoveEmptyDirectories(const char *DirName, bool RemoveThis, const char *IgnoreFiles[])
Removes all empty directories under the given directory DirName.
Definition: tools.c:482
int CloseVideoFile(cUnbufferedFile *File)
Definition: videodir.c:152
static cUnbufferedFile * Create(const char *FileName, int Flags, mode_t Mode=DEFFILEMODE)
Definition: tools.c:1814
bool RemoveFileOrDir(const char *FileName, bool FollowSymlinks)
Definition: tools.c:424
char * ExchangeChars(char *s, bool ToFileSystem)
Definition: recording.c:565
bool RenameVideoFile(const char *OldName, const char *NewName)
Definition: videodir.c:159
static int freeMinutes
Definition: videodir.h:36
static time_t lastChecked
Definition: videodir.h:33
Definition: tools.h:166
int Close(void)
Definition: tools.c:1662
static int FreeMB(void)
Returns the amount of free space on the video disk in MB.
Definition: videodir.h:58
static int UsedPercent(void)
Returns the used space of the video disk in percent.
Definition: videodir.h:55