diff options
author | Klaus Schmidinger <vdr@tvdr.de> | 2013-10-10 13:13:30 +0200 |
---|---|---|
committer | Klaus Schmidinger <vdr@tvdr.de> | 2013-10-10 13:13:30 +0200 |
commit | 2bdb599fbcfea5bac49fcf5550cd0fd1bc328d4a (patch) | |
tree | 88e9d6c0b7505f1c4d9374c26a4c3ee27dd6ee90 /recording.c | |
parent | 3971cc6e8845f2a70018b20706f4a30d71edd41d (diff) | |
download | vdr-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.c | 524 |
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; |