From 9e8aac388237e7afff6cae5bbf5ee08756633f53 Mon Sep 17 00:00:00 2001 From: Klaus Schmidinger Date: Sun, 25 Sep 2005 11:00:57 +0200 Subject: The list of recordings is now read in a separate thread --- CONTRIBUTORS | 2 ++ HISTORY | 19 +++++++++++- menu.c | 74 +++++++++++++++++++++++++++++----------------- menu.h | 4 ++- recording.c | 96 ++++++++++++++++++++++++++++++++++++++++++++++++------------ recording.h | 28 ++++++++++++++---- svdrp.c | 4 +-- vdr.c | 12 ++++---- 8 files changed, 177 insertions(+), 62 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 7fdaf60d..9a6d1692 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -14,6 +14,8 @@ Carsten Koch for fixing the watchdog timer if the program hangs in OSD activities for his support in keeping the Premiere World channels up to date in 'channels.conf' for fixing converting summary.vdr files that would result in a very long 'short text' + for his help in testing and debugging reading the list of recordings in a + separate thread Plamen Ganev for fixing the frequency offset for Hotbird channels diff --git a/HISTORY b/HISTORY index 6463e58b..72e030be 100644 --- a/HISTORY +++ b/HISTORY @@ -3807,7 +3807,7 @@ Video Disk Recorder Revision History - Implemented a hash for the channels to reduce the system load in the EIT scanning thread (based on a patch by Georg Acher). -2005-09-18: Version 1.3.33 +2005-09-25: Version 1.3.33 - Fixed two errors in 'newplugin' (thanks to Alexander Rieger). - Fixed converting arbitrarily formatted summary.vdr files (thanks to Thomas Günther). @@ -3824,3 +3824,20 @@ Video Disk Recorder Revision History - Removed obsolete 'shift' code in device.[hc]. - The SVDRP command DELR no longer triggers a complete reload of the global Recordings list, but rather deletes that particular entry. +- The list of recordings is now read in a separate thread, resulting in a faster + startup if there are a great many of recordings, or the disk(s) have to spin up. + If the Recordings menu is opened while the list of recordings is still being read, + the menu will be updated accordingly. + Plugins that access the global Recordings variable should lock the thread, either + by calling + + Recordings.Lock(); + ... + Recordings.Unlock(); + + or by putting something like + + cThreadLock RecordingsLock(&Recordings); + + into the respective code block. Thanks to Carsten Koch for his help in testing + and debugging this. diff --git a/menu.c b/menu.c index 9709212d..ce415ea3 100644 --- a/menu.c +++ b/menu.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: menu.c 1.359 2005/09/03 11:42:27 kls Exp $ + * $Id: menu.c 1.360 2005/09/25 09:45:01 kls Exp $ */ #include "menu.h" @@ -1506,35 +1506,12 @@ cMenuRecordings::cMenuRecordings(const char *Base, int Level, bool OpenSubMenus) { base = Base ? strdup(Base) : NULL; level = Setup.RecordingDirs ? Level : -1; + Recordings.StateChanged(recordingsState); // just to get the current state Display(); // this keeps the higher level menus from showing up briefly when pressing 'Back' during replay - const char *LastReplayed = cReplayControl::LastReplayed(); - cMenuRecordingItem *LastItem = NULL; - char *LastItemText = NULL; - if (!Base) - Recordings.Sort(); - for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording)) { - if (!Base || (strstr(recording->Name(), Base) == recording->Name() && recording->Name()[strlen(Base)] == '~')) { - cMenuRecordingItem *Item = new cMenuRecordingItem(recording, level); - if (*Item->Text() && (!LastItem || strcmp(Item->Text(), LastItemText) != 0)) { - Add(Item); - LastItem = Item; - free(LastItemText); - LastItemText = strdup(LastItem->Text()); // must use a copy because of the counters! - } - else - delete Item; - if (LastItem) { - if (LastReplayed && strcmp(LastReplayed, recording->FileName()) == 0) - SetCurrent(LastItem); - if (LastItem->IsDirectory()) - LastItem->IncrementCounter(recording->IsNew()); - } - } - } - free(LastItemText); + Set(); if (Current() < 0) SetCurrent(First()); - else if (OpenSubMenus && Open(true)) + else if (OpenSubMenus && cReplayControl::LastReplayed() && Open(true)) return; SetHelpKeys(); } @@ -1570,6 +1547,45 @@ void cMenuRecordings::SetHelpKeys(void) } } +void cMenuRecordings::Set(bool Refresh) +{ + const char *CurrentRecording = cReplayControl::LastReplayed(); + cMenuRecordingItem *LastItem = NULL; + char *LastItemText = NULL; + cThreadLock RecordingsLock(&Recordings); + if (Refresh) { + cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current()); + if (ri) { + cRecording *Recording = GetRecording(ri); + if (Recording) + CurrentRecording = Recording->FileName(); + } + } + Clear(); + Recordings.Sort(); + for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording)) { + if (!base || (strstr(recording->Name(), base) == recording->Name() && recording->Name()[strlen(base)] == '~')) { + cMenuRecordingItem *Item = new cMenuRecordingItem(recording, level); + if (*Item->Text() && (!LastItem || strcmp(Item->Text(), LastItemText) != 0)) { + Add(Item); + LastItem = Item; + free(LastItemText); + LastItemText = strdup(LastItem->Text()); // must use a copy because of the counters! + } + else + delete Item; + if (LastItem) { + if (CurrentRecording && strcmp(CurrentRecording, recording->FileName()) == 0) + SetCurrent(LastItem); + if (LastItem->IsDirectory()) + LastItem->IncrementCounter(recording->IsNew()); + } + } + } + free(LastItemText); + Display(); +} + cRecording *cMenuRecordings::GetRecording(cMenuRecordingItem *Item) { cRecording *recording = Recordings.GetByName(Item->FileName()); @@ -1716,6 +1732,9 @@ eOSState cMenuRecordings::ProcessKey(eKeys Key) case kYellow: return Delete(); case kBlue: return Info(); case k1...k9: return Commands(Key); + case kNone: if (Recordings.StateChanged(recordingsState)) + Set(true); + break; default: break; } } @@ -2597,6 +2616,7 @@ static void SetTrackDescriptions(bool Live) } } else if (cReplayControl::LastReplayed()) { + cThreadLock RecordingsLock(&Recordings); cRecording *Recording = Recordings.GetByName(cReplayControl::LastReplayed()); if (Recording) Components = Recording->Info()->Components(); diff --git a/menu.h b/menu.h index 9d0ecbd9..a122fa39 100644 --- a/menu.h +++ b/menu.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: menu.h 1.73 2005/09/03 11:41:41 kls Exp $ + * $Id: menu.h 1.74 2005/09/25 09:03:32 kls Exp $ */ #ifndef __MENU_H @@ -147,8 +147,10 @@ class cMenuRecordings : public cOsdMenu { private: char *base; int level; + int recordingsState; static int helpKeys; void SetHelpKeys(void); + void Set(bool Refresh = false); cRecording *GetRecording(cMenuRecordingItem *Item); bool Open(bool OpenSubMenus = false); eOSState Play(void); diff --git a/recording.c b/recording.c index 4a12b71e..ccdb71c4 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 1.114 2005/09/17 09:14:36 kls Exp $ + * $Id: recording.c 1.115 2005/09/25 10:40:31 kls Exp $ */ #include "recording.h" @@ -60,17 +60,23 @@ bool VfatFileSystem = false; +static cRecordings DeletedRecordings(true); + void RemoveDeletedRecordings(void) { static time_t LastRemoveCheck = 0; - if (time(NULL) - LastRemoveCheck > REMOVECHECKDELTA) { + if (LastRemoveCheck == 0) { + DeletedRecordings.Update(); + LastRemoveCheck = time(NULL) - REMOVECHECKDELTA * 9 / 10; + } + else if (time(NULL) - LastRemoveCheck > REMOVECHECKDELTA) { // Make sure only one instance of VDR does this: cLockFile LockFile(VideoDirectory); if (!LockFile.Lock()) return; // Remove the oldest file that has been "deleted": - cRecordings DeletedRecordings(true); - if (DeletedRecordings.Load()) { + cThreadLock DeletedRecordingsLock(&DeletedRecordings); + if (DeletedRecordings.Count()) { cRecording *r = DeletedRecordings.First(); cRecording *r0 = r; while (r) { @@ -80,11 +86,14 @@ void RemoveDeletedRecordings(void) } if (r0 && time(NULL) - r0->start > DELETEDLIFETIME * 3600) { r0->Remove(); + DeletedRecordings.Del(r0); RemoveEmptyVideoDirectories(); LastRemoveCheck += REMOVELATENCY; return; } } + else + DeletedRecordings.Update(); LastRemoveCheck = time(NULL); } } @@ -104,8 +113,8 @@ void AssertFreeDiskSpace(int Priority) return; // Remove the oldest file that has been "deleted": isyslog("low disk space while recording, trying to remove a deleted recording..."); - cRecordings DeletedRecordings(true); - if (DeletedRecordings.Load()) { + cThreadLock DeletedRecordingsLock(&DeletedRecordings); + if (DeletedRecordings.Count()) { cRecording *r = DeletedRecordings.First(); cRecording *r0 = r; while (r) { @@ -114,13 +123,20 @@ void AssertFreeDiskSpace(int Priority) r = DeletedRecordings.Next(r); } if (r0 && r0->Remove()) { + DeletedRecordings.Del(r0); LastFreeDiskCheck += REMOVELATENCY / Factor; return; } } + // DeletedRecordings was empty, so to be absolutely sure there are no + // deleted recordings we need to double check: + DeletedRecordings.Update(true); + if (DeletedRecordings.Count()) + return; // the next call will actually remove it // No "deleted" files to remove, so let's see if we can delete a recording: isyslog("...no deleted recording found, trying to delete an old recording..."); - if (Recordings.Load()) { + cThreadLock RecordingsLock(&Recordings); + if (Recordings.Count()) { cRecording *r = Recordings.First(); cRecording *r0 = NULL; while (r) { @@ -454,6 +470,7 @@ cRecording::cRecording(const char *FileName) name[p - FileName] = 0; name = ExchangeChars(name, false); } + GetResume(); // read an optional info file: char *InfoFileName = NULL; asprintf(&InfoFileName, "%s%s", fileName, INFOFILESUFFIX); @@ -720,16 +737,38 @@ bool cRecording::Remove(void) cRecordings Recordings; cRecordings::cRecordings(bool Deleted) +:cThread("video directory scanner") { deleted = Deleted; lastUpdate = 0; + state = 0; +} + +cRecordings::~cRecordings() +{ + Cancel(3); +} + +void cRecordings::Action(void) +{ + Refresh(); +} + +void cRecordings::Refresh(bool Foreground) +{ + lastUpdate = time(NULL); // doing this first to make sure we don't miss anything + Lock(); + Clear(); + ChangeState(); + Unlock(); + ScanVideoDir(VideoDirectory, Foreground); } -void cRecordings::ScanVideoDir(const char *DirName) +void cRecordings::ScanVideoDir(const char *DirName, bool Foreground) { cReadDir d(DirName); struct dirent *e; - while ((e = d.Next()) != NULL) { + while ((Foreground || Running()) && (e = d.Next()) != NULL) { if (strcmp(e->d_name, ".") && strcmp(e->d_name, "..")) { char *buffer; asprintf(&buffer, "%s/%s", DirName, e->d_name); @@ -749,13 +788,17 @@ void cRecordings::ScanVideoDir(const char *DirName) if (S_ISDIR(st.st_mode)) { if (endswith(buffer, deleted ? DELEXT : RECEXT)) { cRecording *r = new cRecording(buffer); - if (r->Name()) + if (r->Name()) { + Lock(); Add(r); + ChangeState(); + Unlock(); + } else delete r; } else - ScanVideoDir(buffer); + ScanVideoDir(buffer, Foreground); } } free(buffer); @@ -763,18 +806,28 @@ void cRecordings::ScanVideoDir(const char *DirName) } } +bool cRecordings::StateChanged(int &State) +{ + int NewState = state; + bool Result = State != NewState; + State = state; + return Result; +} + bool cRecordings::NeedsUpdate(void) { - return lastUpdate <= LastModifiedTime(AddDirectory(VideoDirectory, ".update")); + return lastUpdate < LastModifiedTime(AddDirectory(VideoDirectory, ".update")); } -bool cRecordings::Load(void) +bool cRecordings::Update(bool Wait) { - lastUpdate = time(NULL); // doing this first to make sure we don't miss anything - Clear(); - ScanVideoDir(VideoDirectory); - Sort(); - return Count() > 0; + if (Wait) { + Refresh(true); + return Count() > 0; + } + else + Start(); + return false; } cRecording *cRecordings::GetByName(const char *FileName) @@ -788,18 +841,23 @@ cRecording *cRecordings::GetByName(const char *FileName) void cRecordings::AddByName(const char *FileName) { + LOCK_THREAD; cRecording *recording = GetByName(FileName); if (!recording) { recording = new cRecording(FileName); Add(recording); + ChangeState(); } } void cRecordings::DelByName(const char *FileName) { + LOCK_THREAD; cRecording *recording = GetByName(FileName); - if (recording) + if (recording) { Del(recording); + ChangeState(); + } } // --- cMark ----------------------------------------------------------------- diff --git a/recording.h b/recording.h index 567c6c41..23c4fd84 100644 --- a/recording.h +++ b/recording.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: recording.h 1.40 2005/09/03 13:04:41 kls Exp $ + * $Id: recording.h 1.41 2005/09/25 10:07:40 kls Exp $ */ #ifndef __RECORDING_H @@ -71,7 +71,7 @@ public: int lifetime; cRecording(cTimer *Timer, const cEvent *Event); cRecording(const char *FileName); - ~cRecording(); + virtual ~cRecording(); virtual int Compare(const cListObject &ListObject) const; const char *Name(void) const { return name; } const char *FileName(void) const; @@ -90,16 +90,32 @@ public: // Returns false in case of error }; -class cRecordings : public cList { +class cRecordings : public cList, public cThread { private: bool deleted; time_t lastUpdate; - void ScanVideoDir(const char *DirName); + int state; + void Refresh(bool Foreground = false); + void ScanVideoDir(const char *DirName, bool Foreground = false); +protected: + void Action(void); public: cRecordings(bool Deleted = false); - bool Load(void); + virtual ~cRecordings(); + bool Load(void) { return Update(true); } + ///< Loads the current list of recordings and returns true if there + ///< is anything in it (for compatibility with older plugins - use + ///< Update(true) instead). + bool Update(bool Wait = false); + ///< Triggers an update of the list of recordings, which will run + ///< as a separate thread if Wait is false. If Wait is true, the + ///< function returns only after the update has completed. + ///< Returns true if Wait is true and there is anyting in the list + ///< of recordings, false otherwise. void TriggerUpdate(void) { lastUpdate = 0; } bool NeedsUpdate(void); + void ChangeState(void) { state++; } + bool StateChanged(int &State); cRecording *GetByName(const char *FileName); void AddByName(const char *FileName); void DelByName(const char *FileName); @@ -112,7 +128,7 @@ public: int position; char *comment; cMark(int Position = 0, const char *Comment = NULL); - ~cMark(); + virtual ~cMark(); cString ToText(void); bool Parse(const char *s); bool Save(FILE *f); diff --git a/svdrp.c b/svdrp.c index 68570b85..cc5df706 100644 --- a/svdrp.c +++ b/svdrp.c @@ -10,7 +10,7 @@ * and interact with the Video Disk Recorder - or write a full featured * graphical interface that sits on top of an SVDRP connection. * - * $Id: svdrp.c 1.81 2005/09/18 10:50:08 kls Exp $ + * $Id: svdrp.c 1.82 2005/09/25 10:36:59 kls Exp $ */ #include "svdrp.h" @@ -890,7 +890,7 @@ void cSVDRP::CmdLSTE(const char *Option) void cSVDRP::CmdLSTR(const char *Option) { - bool recordings = Recordings.Load(); + bool recordings = Recordings.Update(true); if (*Option) { if (isnumber(Option)) { cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1); diff --git a/vdr.c b/vdr.c index 0e0e4288..0fab1899 100644 --- a/vdr.c +++ b/vdr.c @@ -22,7 +22,7 @@ * * The project's page is at http://www.cadsoft.de/vdr * - * $Id: vdr.c 1.216 2005/09/04 08:57:15 kls Exp $ + * $Id: vdr.c 1.217 2005/09/24 13:27:26 kls Exp $ */ #include @@ -437,6 +437,10 @@ int main(int argc, char *argv[]) cFont::SetCode(I18nCharSets()[Setup.OSDLanguage]); + // Recordings: + + Recordings.Update(); + // EPG data: if (EpgDataFileName) { @@ -539,10 +543,6 @@ int main(int argc, char *argv[]) else cDevice::PrimaryDevice()->SetVolume(Setup.CurrentVolume, true); - // Recordings: - - Recordings.Load(); - // Signal handlers: if (signal(SIGHUP, SignalHandler) == SIG_IGN) signal(SIGHUP, SIG_IGN); @@ -674,7 +674,7 @@ int main(int argc, char *argv[]) } } if (!Menu && Recordings.NeedsUpdate()) - Recordings.Load(); + Recordings.Update(); // CAM control: if (!Menu && !cOsd::IsOpen()) { Menu = CamControl(); -- cgit v1.2.3