summaryrefslogtreecommitdiff
path: root/recording.c
diff options
context:
space:
mode:
authorKlaus Schmidinger <vdr@tvdr.de>2013-10-10 13:13:30 +0200
committerKlaus Schmidinger <vdr@tvdr.de>2013-10-10 13:13:30 +0200
commit2bdb599fbcfea5bac49fcf5550cd0fd1bc328d4a (patch)
tree88e9d6c0b7505f1c4d9374c26a4c3ee27dd6ee90 /recording.c
parent3971cc6e8845f2a70018b20706f4a30d71edd41d (diff)
downloadvdr-2bdb599fbcfea5bac49fcf5550cd0fd1bc328d4a.tar.gz
vdr-2bdb599fbcfea5bac49fcf5550cd0fd1bc328d4a.tar.bz2
Added renaming and moving recordings and folders, and editing a recording's priority and lifetime; added cutting queue
Diffstat (limited to 'recording.c')
-rw-r--r--recording.c524
1 files changed, 515 insertions, 9 deletions
diff --git a/recording.c b/recording.c
index 5cbf1070..31eb25b5 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 3.3 2013/09/11 08:28:27 kls Exp $
+ * $Id: recording.c 3.4 2013/10/09 11:53:37 kls Exp $
*/
#include "recording.h"
@@ -20,8 +20,10 @@
#include <sys/stat.h>
#include <unistd.h>
#include "channels.h"
+#include "cutter.h"
#include "i18n.h"
#include "interface.h"
+#include "menu.h"
#include "remux.h"
#include "ringbuffer.h"
#include "skins.h"
@@ -422,6 +424,13 @@ void cRecordingInfo::SetFramesPerSecond(double FramesPerSecond)
framesPerSecond = FramesPerSecond;
}
+void cRecordingInfo::SetFileName(const char *FileName)
+{
+ bool IsPesRecording = fileName && endswith(fileName, ".vdr");
+ free(fileName);
+ fileName = strdup(cString::sprintf("%s%s", FileName, IsPesRecording ? INFOFILESUFFIX ".vdr" : INFOFILESUFFIX));
+}
+
bool cRecordingInfo::Read(FILE *f)
{
if (ownEvent) {
@@ -769,7 +778,7 @@ cRecording::cRecording(cTimer *Timer, const cEvent *Event)
else if (Timer->IsSingleEvent() || !Setup.UseSubtitle)
name = strdup(Timer->File());
else
- name = strdup(cString::sprintf("%s~%s", Timer->File(), Subtitle));
+ name = strdup(cString::sprintf("%s%c%s", Timer->File(), FOLDERDELIMCHAR, Subtitle));
// substitute characters that would cause problems in file names:
strreplace(name, '\n', ' ');
start = Timer->StartTime();
@@ -963,8 +972,9 @@ char *cRecording::SortName(void) const
void cRecording::ClearSortName(void)
{
- DELETENULL(sortBufferName);
- DELETENULL(sortBufferTime);
+ free(sortBufferName);
+ free(sortBufferTime);
+ sortBufferName = sortBufferTime = NULL;
}
int cRecording::GetResume(void) const
@@ -982,6 +992,28 @@ int cRecording::Compare(const cListObject &ListObject) const
return strcasecmp(SortName(), r->SortName());
}
+bool cRecording::IsInPath(const char *Path)
+{
+ if (isempty(Path))
+ return true;
+ int l = strlen(Path);
+ return strncmp(Path, name, l) == 0 && (name[l] == FOLDERDELIMCHAR);
+}
+
+cString cRecording::Folder(void) const
+{
+ if (char *s = strrchr(name, FOLDERDELIMCHAR))
+ return cString(name, s);
+ return "";
+}
+
+cString cRecording::BaseName(void) const
+{
+ if (char *s = strrchr(name, FOLDERDELIMCHAR))
+ return cString(s + 1);
+ return name;
+}
+
const char *cRecording::FileName(void) const
{
if (!fileName) {
@@ -1097,6 +1129,22 @@ bool cRecording::IsOnVideoDirectoryFileSystem(void) const
return isOnVideoDirectoryFileSystem;
}
+bool cRecording::HasMarks(void)
+{
+ return access(cMarks::MarksFileName(this), F_OK) == 0;
+}
+
+bool cRecording::DeleteMarks(void)
+{
+ if (remove(cMarks::MarksFileName(this)) < 0) {
+ if (errno != ENOENT) {
+ LOG_ERROR_STR(fileName);
+ return false;
+ }
+ }
+ return true;
+}
+
void cRecording::ReadInfo(void)
{
info->Read();
@@ -1105,13 +1153,13 @@ void cRecording::ReadInfo(void)
framesPerSecond = info->framesPerSecond;
}
-bool cRecording::WriteInfo(void)
+bool cRecording::WriteInfo(const char *OtherFileName)
{
- cString InfoFileName = cString::sprintf("%s%s", fileName, isPesRecording ? INFOFILESUFFIX ".vdr" : INFOFILESUFFIX);
- FILE *f = fopen(InfoFileName, "w");
- if (f) {
+ cString InfoFileName = cString::sprintf("%s%s", OtherFileName ? OtherFileName : FileName(), isPesRecording ? INFOFILESUFFIX ".vdr" : INFOFILESUFFIX);
+ cSafeFile f(InfoFileName);
+ if (f.Open()) {
info->Write(f);
- fclose(f);
+ f.Close();
}
else
LOG_ERROR_STR(*InfoFileName);
@@ -1125,6 +1173,58 @@ void cRecording::SetStartTime(time_t Start)
fileName = NULL;
}
+bool cRecording::ChangePriorityLifetime(int NewPriority, int NewLifetime)
+{
+ if (NewPriority != Priority() || NewLifetime != Lifetime()) {
+ dsyslog("changing priority/lifetime of '%s' to %d/%d", Name(), NewPriority, NewLifetime);
+ if (IsPesRecording()) {
+ cString OldFileName = FileName();
+ priority = NewPriority;
+ lifetime = NewLifetime;
+ free(fileName);
+ fileName = NULL;
+ cString NewFileName = FileName();
+ if (!cVideoDirectory::RenameVideoFile(OldFileName, NewFileName))
+ return false;
+ info->SetFileName(NewFileName);
+ }
+ else {
+ priority = info->priority = NewPriority;
+ lifetime = info->lifetime = NewLifetime;
+ if (!WriteInfo())
+ return false;
+ }
+ Recordings.ChangeState();
+ Recordings.TouchUpdate();
+ }
+ return true;
+}
+
+bool cRecording::ChangeName(const char *NewName)
+{
+ if (strcmp(NewName, Name())) {
+ dsyslog("changing name of '%s' to '%s'", Name(), NewName);
+ cString OldName = Name();
+ cString OldFileName = FileName();
+ free(fileName);
+ fileName = NULL;
+ free(name);
+ name = strdup(NewName);
+ cString NewFileName = FileName();
+ if (!(MakeDirs(NewFileName, true) && cVideoDirectory::MoveVideoFile(OldFileName, NewFileName))) {
+ free(name);
+ name = strdup(OldName);
+ free(fileName);
+ fileName = strdup(OldFileName);
+ return false;
+ }
+ ClearSortName();
+ Recordings.ChangeState();
+ Recordings.TouchUpdate();
+ }
+ return true;
+}
+
bool cRecording::Delete(void)
{
bool result = true;
@@ -1188,6 +1288,17 @@ bool cRecording::Undelete(void)
return result;
}
+int cRecording::IsInUse(void) const
+{
+ int Use = ruNone;
+ if (cRecordControls::GetRecordControl(FileName()))
+ Use |= ruTimer;
+ if (cReplayControl::NowReplaying() && strcmp(cReplayControl::NowReplaying(), FileName()) == 0)
+ Use |= ruReplay;
+ Use |= RecordingsHandler.GetUsage(FileName());
+ return Use;
+}
+
void cRecording::ResetResume(void) const
{
resume = RESUME_NOT_INITIALIZED;
@@ -1426,6 +1537,46 @@ double cRecordings::MBperMinute(void)
return (size && length) ? double(size) * 60 / length : -1;
}
+int cRecordings::PathIsInUse(const char *Path)
+{
+ LOCK_THREAD;
+ int Use = ruNone;
+ for (cRecording *recording = First(); recording; recording = Next(recording)) {
+ if (recording->IsInPath(Path))
+ Use |= recording->IsInUse();
+ }
+ return Use;
+}
+
+int cRecordings::GetNumRecordingsInPath(const char *Path)
+{
+ LOCK_THREAD;
+ int n = 0;
+ for (cRecording *recording = First(); recording; recording = Next(recording)) {
+ if (recording->IsInPath(Path))
+ n++;
+ }
+ return n;
+}
+
+bool cRecordings::MoveRecordings(const char *OldPath, const char *NewPath)
+{
+ if (OldPath && NewPath && strcmp(OldPath, NewPath)) {
+ LOCK_THREAD;
+ dsyslog("moving '%s' to '%s'", OldPath, NewPath);
+ for (cRecording *recording = First(); recording; recording = Next(recording)) {
+ if (recording->IsInPath(OldPath)) {
+ const char *p = recording->Name() + strlen(OldPath);
+ cString NewName = cString::sprintf("%s%s", NewPath, p);
+ if (!recording->ChangeName(NewName))
+ return false;
+ ChangeState();
+ }
+ }
+ }
+ return true;
+}
+
void cRecordings::ResetResume(const char *ResumeFileName)
{
LOCK_THREAD;
@@ -1443,6 +1594,356 @@ void cRecordings::ClearSortNames(void)
recording->ClearSortName();
}
+// --- cDirCopier ------------------------------------------------------------
+
+class cDirCopier : public cThread {
+private:
+ cString dirNameSrc;
+ cString dirNameDst;
+ bool error;
+ bool suspensionLogged;
+ bool Throttled(void);
+ virtual void Action(void);
+public:
+ cDirCopier(const char *DirNameSrc, const char *DirNameDst);
+ virtual ~cDirCopier();
+ void Stop(void);
+ bool Error(void) { return error; }
+ };
+
+cDirCopier::cDirCopier(const char *DirNameSrc, const char *DirNameDst)
+:cThread("file copier", true)
+{
+ dirNameSrc = DirNameSrc;
+ dirNameDst = DirNameDst;
+ error = false;
+ suspensionLogged = false;
+}
+
+cDirCopier::~cDirCopier()
+{
+ Stop();
+}
+
+bool cDirCopier::Throttled(void)
+{
+ if (cIoThrottle::Engaged()) {
+ if (!suspensionLogged) {
+ dsyslog("suspending copy thread");
+ suspensionLogged = true;
+ }
+ return true;
+ }
+ else if (suspensionLogged) {
+ dsyslog("resuming copy thread");
+ suspensionLogged = false;
+ }
+ return false;
+}
+
+void cDirCopier::Action(void)
+{
+ if (DirectoryOk(dirNameDst, true)) {
+ cReadDir d(dirNameSrc);
+ if (d.Ok()) {
+ dsyslog("copying directory '%s' to '%s'", *dirNameSrc, *dirNameDst);
+ dirent *e = NULL;
+ cString FileNameSrc;
+ cString FileNameDst;
+ int From = -1;
+ int To = -1;
+ size_t BufferSize = BUFSIZ;
+ while (Running()) {
+ // Suspend cutting if we have severe throughput problems:
+ if (Throttled()) {
+ cCondWait::SleepMs(100);
+ continue;
+ }
+ // Copy all files in the source directory to the destination directory:
+ if (e) {
+ // We're currently copying a file:
+ uchar Buffer[BufferSize];
+ size_t Read = safe_read(From, Buffer, sizeof(Buffer));
+ if (Read > 0) {
+ size_t Written = safe_write(To, Buffer, Read);
+ if (Written != Read) {
+ esyslog("ERROR: can't write to destination file '%s': %m", *FileNameDst);
+ break;
+ }
+ }
+ else if (Read == 0) { // EOF on From
+ e = NULL; // triggers switch to next entry
+ if (fsync(To) < 0) {
+ esyslog("ERROR: can't sync destination file '%s': %m", *FileNameDst);
+ break;
+ }
+ if (close(From) < 0) {
+ esyslog("ERROR: can't close source file '%s': %m", *FileNameSrc);
+ break;
+ }
+ if (close(To) < 0) {
+ esyslog("ERROR: can't close destination file '%s': %m", *FileNameDst);
+ break;
+ }
+ // Plausibility check:
+ off_t FileSizeSrc = FileSize(FileNameSrc);
+ off_t FileSizeDst = FileSize(FileNameDst);
+ if (FileSizeSrc != FileSizeDst) {
+ esyslog("ERROR: file size discrepancy: %lld != %lld", FileSizeSrc, FileSizeDst);
+ break;
+ }
+ }
+ else {
+ esyslog("ERROR: can't read from source file '%s': %m", *FileNameSrc);
+ break;
+ }
+ }
+ else if ((e = d.Next()) != NULL) {
+ // We're switching to the next directory entry:
+ FileNameSrc = AddDirectory(dirNameSrc, e->d_name);
+ FileNameDst = AddDirectory(dirNameDst, e->d_name);
+ struct stat st;
+ if (stat(FileNameSrc, &st) < 0) {
+ esyslog("ERROR: can't access source file '%s': %m", *FileNameSrc);
+ break;
+ }
+ if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) {
+ esyslog("ERROR: source file '%s' is neither a regular file nor a symbolic link", *FileNameSrc);
+ break;
+ }
+ dsyslog("copying file '%s' to '%s'", *FileNameSrc, *FileNameDst);
+ BufferSize = max(size_t(st.st_blksize * 10), size_t(BUFSIZ));
+ if (access(FileNameDst, F_OK) == 0) {
+ esyslog("ERROR: destination file '%s' already exists", *FileNameDst);
+ break;
+ }
+ if ((From = open(FileNameSrc, O_RDONLY)) < 0) {
+ esyslog("ERROR: can't open source file '%s': %m", *FileNameSrc);
+ break;
+ }
+ if ((To = open(FileNameDst, O_WRONLY | O_CREAT | O_EXCL, DEFFILEMODE)) < 0) {
+ esyslog("ERROR: can't open destination file '%s': %m", *FileNameDst);
+ close(From);
+ break;
+ }
+ }
+ else {
+ // We're done:
+ dsyslog("done copying directory '%s' to '%s'", *dirNameSrc, *dirNameDst);
+ return;
+ }
+ }
+ close(From); // just to be absolutely sure
+ close(To);
+ esyslog("ERROR: copying directory '%s' to '%s' ended prematurely", *dirNameSrc, *dirNameDst);
+ }
+ else
+ esyslog("ERROR: can't open '%s'", *dirNameSrc);
+ }
+ else
+ esyslog("ERROR: can't access '%s'", *dirNameDst);
+ error = true;
+}
+
+void cDirCopier::Stop(void)
+{
+ Cancel(3);
+ if (error) {
+ cVideoDirectory::RemoveVideoFile(dirNameDst);
+ Recordings.DelByName(dirNameDst);
+ }
+}
+
+// --- cRecordingsHandlerEntry -----------------------------------------------
+
+class cRecordingsHandlerEntry : public cListObject {
+private:
+ int usage;
+ cString fileNameSrc;
+ cString fileNameDst;
+ cCutter *cutter;
+ cDirCopier *copier;
+ void ClearPending(void) { usage &= ~ruPending; }
+public:
+ cRecordingsHandlerEntry(int Usage, const char *FileNameSrc, const char *FileNameDst);
+ ~cRecordingsHandlerEntry();
+ int Usage(const char *FileName = NULL) const;
+ const char *FileNameSrc(void) const { return fileNameSrc; }
+ const char *FileNameDst(void) const { return fileNameDst; }
+ bool Active(bool &Error);
+ };
+
+cRecordingsHandlerEntry::cRecordingsHandlerEntry(int Usage, const char *FileNameSrc, const char *FileNameDst)
+{
+ usage = Usage;
+ fileNameSrc = FileNameSrc;
+ fileNameDst = FileNameDst;
+ cutter = NULL;
+ copier = NULL;
+}
+
+cRecordingsHandlerEntry::~cRecordingsHandlerEntry()
+{
+ delete cutter;
+ delete copier;
+}
+
+int cRecordingsHandlerEntry::Usage(const char *FileName) const
+{
+ int u = usage;
+ if (FileName && *FileName) {
+ if (strcmp(FileName, fileNameSrc) == 0)
+ u |= ruSrc;
+ else if (strcmp(FileName, fileNameDst) == 0)
+ u |= ruDst;
+ }
+ return u;
+}
+
+bool cRecordingsHandlerEntry::Active(bool &Error)
+{
+ bool CopierFinishedOk = false;
+ // First test whether there is an ongoing operation:
+ if (cutter) {
+ if (cutter->Active())
+ return true;
+ Error |= cutter->Error();
+ delete cutter;
+ cutter = NULL;
+ }
+ else if (copier) {
+ if (copier->Active())
+ return true;
+ Error |= copier->Error();
+ CopierFinishedOk = !copier->Error();
+ delete copier;
+ copier = NULL;
+ }
+ // Now check if there is something to start:
+ if ((Usage() & ruPending) != 0) {
+ if ((Usage() & ruCut) != 0) {
+ cutter = new cCutter(FileNameSrc());
+ cutter->Start();
+ }
+ else if ((Usage() & (ruMove | ruCopy)) != 0) {
+ copier = new cDirCopier(FileNameSrc(), FileNameDst());
+ copier->Start();
+ }
+ ClearPending();
+ return true;
+ }
+ else {
+ if (CopierFinishedOk && (Usage() & ruMove) != 0) {
+ cRecording Recording(FileNameSrc());
+ Recording.Delete();
+ Recordings.ChangeState();
+ Recordings.TouchUpdate();
+ }
+ }
+ return false;
+}
+
+// --- cRecordingsHandler ----------------------------------------------------
+
+cRecordingsHandler RecordingsHandler;
+
+cRecordingsHandler::cRecordingsHandler(void)
+{
+ finished = true;
+ error = false;
+}
+
+cRecordingsHandler::~cRecordingsHandler()
+{
+}
+
+cRecordingsHandlerEntry *cRecordingsHandler::Get(const char *FileName)
+{
+ if (FileName && *FileName) {
+ for (cRecordingsHandlerEntry *r = operations.First(); r; r = operations.Next(r)) {
+ if (strcmp(FileName, r->FileNameSrc()) == 0 || strcmp(FileName, r->FileNameDst()) == 0)
+ return r;
+ }
+ }
+ return NULL;
+}
+
+bool cRecordingsHandler::Add(int Usage, const char *FileNameSrc, const char *FileNameDst)
+{
+ dsyslog("recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
+ cMutexLock MutexLock(&mutex);
+ if (Usage == ruCut || Usage == ruMove || Usage == ruCopy) {
+ if (FileNameSrc && *FileNameSrc) {
+ if (Usage == ruCut || FileNameDst && *FileNameDst) {
+ cString fnd;
+ if (Usage == ruCut && !FileNameDst)
+ FileNameDst = fnd = cCutter::EditedFileName(FileNameSrc);
+ if (!Get(FileNameSrc) && !Get(FileNameDst)) {
+ Usage |= ruPending;
+ operations.Add(new cRecordingsHandlerEntry(Usage, FileNameSrc, FileNameDst));
+ finished = false;
+ Active(); // start it right away if possible
+ return true;
+ }
+ else
+ esyslog("ERROR: file name already present in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
+ }
+ else
+ esyslog("ERROR: missing dst file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
+ }
+ else
+ esyslog("ERROR: missing src file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
+ }
+ else
+ esyslog("ERROR: invalid usage in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
+ return false;
+}
+
+void cRecordingsHandler::Del(const char *FileName)
+{
+ cMutexLock MutexLock(&mutex);
+ if (cRecordingsHandlerEntry *r = Get(FileName))
+ operations.Del(r);
+}
+
+void cRecordingsHandler::DelAll(void)
+{
+ cMutexLock MutexLock(&mutex);
+ operations.Clear();
+}
+
+int cRecordingsHandler::GetUsage(const char *FileName)
+{
+ cMutexLock MutexLock(&mutex);
+ if (cRecordingsHandlerEntry *r = Get(FileName))
+ return r->Usage(FileName);
+ return ruNone;
+}
+
+bool cRecordingsHandler::Active(void)
+{
+ cMutexLock MutexLock(&mutex);
+ while (cRecordingsHandlerEntry *r = operations.First()) {
+ if (r->Active(error))
+ return true;
+ else
+ operations.Del(r);
+ }
+ return false;
+}
+
+bool cRecordingsHandler::Finished(bool &Error)
+{
+ cMutexLock MutexLock(&mutex);
+ if (!finished && operations.Count() == 0) {
+ finished = true;
+ Error = error;
+ error = false;
+ return true;
+ }
+ return false;
+}
+
// --- cMark -----------------------------------------------------------------
double MarkFramesPerSecond = DEFAULTFRAMESPERSECOND;
@@ -1485,6 +1986,11 @@ bool cMark::Save(FILE *f)
// --- cMarks ----------------------------------------------------------------
+cString cMarks::MarksFileName(const cRecording *Recording)
+{
+ return AddDirectory(Recording->FileName(), Recording->IsPesRecording() ? MARKSFILESUFFIX ".vdr" : MARKSFILESUFFIX);
+}
+
bool cMarks::Load(const char *RecordingFileName, double FramesPerSecond, bool IsPesRecording)
{
recordingFileName = RecordingFileName;