The list of recordings is now read in a separate thread

This commit is contained in:
Klaus Schmidinger 2005-09-25 11:00:57 +02:00
parent 83985eff47
commit 9e8aac3882
8 changed files with 178 additions and 63 deletions

View File

@ -14,6 +14,8 @@ Carsten Koch <Carsten.Koch@icem.de>
for fixing the watchdog timer if the program hangs in OSD activities 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 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 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 <pganev@com-it.net> Plamen Ganev <pganev@com-it.net>
for fixing the frequency offset for Hotbird channels for fixing the frequency offset for Hotbird channels

19
HISTORY
View File

@ -3807,7 +3807,7 @@ Video Disk Recorder Revision History
- Implemented a hash for the channels to reduce the system load in the EIT scanning - Implemented a hash for the channels to reduce the system load in the EIT scanning
thread (based on a patch by Georg Acher). 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 two errors in 'newplugin' (thanks to Alexander Rieger).
- Fixed converting arbitrarily formatted summary.vdr files (thanks to Thomas Günther). - 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]. - Removed obsolete 'shift' code in device.[hc].
- The SVDRP command DELR no longer triggers a complete reload of the global Recordings - The SVDRP command DELR no longer triggers a complete reload of the global Recordings
list, but rather deletes that particular entry. 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.

74
menu.c
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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" #include "menu.h"
@ -1506,35 +1506,12 @@ cMenuRecordings::cMenuRecordings(const char *Base, int Level, bool OpenSubMenus)
{ {
base = Base ? strdup(Base) : NULL; base = Base ? strdup(Base) : NULL;
level = Setup.RecordingDirs ? Level : -1; 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 Display(); // this keeps the higher level menus from showing up briefly when pressing 'Back' during replay
const char *LastReplayed = cReplayControl::LastReplayed(); Set();
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);
if (Current() < 0) if (Current() < 0)
SetCurrent(First()); SetCurrent(First());
else if (OpenSubMenus && Open(true)) else if (OpenSubMenus && cReplayControl::LastReplayed() && Open(true))
return; return;
SetHelpKeys(); 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 *cMenuRecordings::GetRecording(cMenuRecordingItem *Item)
{ {
cRecording *recording = Recordings.GetByName(Item->FileName()); cRecording *recording = Recordings.GetByName(Item->FileName());
@ -1716,6 +1732,9 @@ eOSState cMenuRecordings::ProcessKey(eKeys Key)
case kYellow: return Delete(); case kYellow: return Delete();
case kBlue: return Info(); case kBlue: return Info();
case k1...k9: return Commands(Key); case k1...k9: return Commands(Key);
case kNone: if (Recordings.StateChanged(recordingsState))
Set(true);
break;
default: break; default: break;
} }
} }
@ -2597,6 +2616,7 @@ static void SetTrackDescriptions(bool Live)
} }
} }
else if (cReplayControl::LastReplayed()) { else if (cReplayControl::LastReplayed()) {
cThreadLock RecordingsLock(&Recordings);
cRecording *Recording = Recordings.GetByName(cReplayControl::LastReplayed()); cRecording *Recording = Recordings.GetByName(cReplayControl::LastReplayed());
if (Recording) if (Recording)
Components = Recording->Info()->Components(); Components = Recording->Info()->Components();

4
menu.h
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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 #ifndef __MENU_H
@ -147,8 +147,10 @@ class cMenuRecordings : public cOsdMenu {
private: private:
char *base; char *base;
int level; int level;
int recordingsState;
static int helpKeys; static int helpKeys;
void SetHelpKeys(void); void SetHelpKeys(void);
void Set(bool Refresh = false);
cRecording *GetRecording(cMenuRecordingItem *Item); cRecording *GetRecording(cMenuRecordingItem *Item);
bool Open(bool OpenSubMenus = false); bool Open(bool OpenSubMenus = false);
eOSState Play(void); eOSState Play(void);

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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" #include "recording.h"
@ -60,17 +60,23 @@
bool VfatFileSystem = false; bool VfatFileSystem = false;
static cRecordings DeletedRecordings(true);
void RemoveDeletedRecordings(void) void RemoveDeletedRecordings(void)
{ {
static time_t LastRemoveCheck = 0; 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: // Make sure only one instance of VDR does this:
cLockFile LockFile(VideoDirectory); cLockFile LockFile(VideoDirectory);
if (!LockFile.Lock()) if (!LockFile.Lock())
return; return;
// Remove the oldest file that has been "deleted": // Remove the oldest file that has been "deleted":
cRecordings DeletedRecordings(true); cThreadLock DeletedRecordingsLock(&DeletedRecordings);
if (DeletedRecordings.Load()) { if (DeletedRecordings.Count()) {
cRecording *r = DeletedRecordings.First(); cRecording *r = DeletedRecordings.First();
cRecording *r0 = r; cRecording *r0 = r;
while (r) { while (r) {
@ -80,11 +86,14 @@ void RemoveDeletedRecordings(void)
} }
if (r0 && time(NULL) - r0->start > DELETEDLIFETIME * 3600) { if (r0 && time(NULL) - r0->start > DELETEDLIFETIME * 3600) {
r0->Remove(); r0->Remove();
DeletedRecordings.Del(r0);
RemoveEmptyVideoDirectories(); RemoveEmptyVideoDirectories();
LastRemoveCheck += REMOVELATENCY; LastRemoveCheck += REMOVELATENCY;
return; return;
} }
} }
else
DeletedRecordings.Update();
LastRemoveCheck = time(NULL); LastRemoveCheck = time(NULL);
} }
} }
@ -104,8 +113,8 @@ void AssertFreeDiskSpace(int Priority)
return; return;
// Remove the oldest file that has been "deleted": // Remove the oldest file that has been "deleted":
isyslog("low disk space while recording, trying to remove a deleted recording..."); isyslog("low disk space while recording, trying to remove a deleted recording...");
cRecordings DeletedRecordings(true); cThreadLock DeletedRecordingsLock(&DeletedRecordings);
if (DeletedRecordings.Load()) { if (DeletedRecordings.Count()) {
cRecording *r = DeletedRecordings.First(); cRecording *r = DeletedRecordings.First();
cRecording *r0 = r; cRecording *r0 = r;
while (r) { while (r) {
@ -114,13 +123,20 @@ void AssertFreeDiskSpace(int Priority)
r = DeletedRecordings.Next(r); r = DeletedRecordings.Next(r);
} }
if (r0 && r0->Remove()) { if (r0 && r0->Remove()) {
DeletedRecordings.Del(r0);
LastFreeDiskCheck += REMOVELATENCY / Factor; LastFreeDiskCheck += REMOVELATENCY / Factor;
return; 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: // 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..."); 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 *r = Recordings.First();
cRecording *r0 = NULL; cRecording *r0 = NULL;
while (r) { while (r) {
@ -454,6 +470,7 @@ cRecording::cRecording(const char *FileName)
name[p - FileName] = 0; name[p - FileName] = 0;
name = ExchangeChars(name, false); name = ExchangeChars(name, false);
} }
GetResume();
// read an optional info file: // read an optional info file:
char *InfoFileName = NULL; char *InfoFileName = NULL;
asprintf(&InfoFileName, "%s%s", fileName, INFOFILESUFFIX); asprintf(&InfoFileName, "%s%s", fileName, INFOFILESUFFIX);
@ -720,16 +737,38 @@ bool cRecording::Remove(void)
cRecordings Recordings; cRecordings Recordings;
cRecordings::cRecordings(bool Deleted) cRecordings::cRecordings(bool Deleted)
:cThread("video directory scanner")
{ {
deleted = Deleted; deleted = Deleted;
lastUpdate = 0; lastUpdate = 0;
state = 0;
} }
void cRecordings::ScanVideoDir(const char *DirName) 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, bool Foreground)
{ {
cReadDir d(DirName); cReadDir d(DirName);
struct dirent *e; struct dirent *e;
while ((e = d.Next()) != NULL) { while ((Foreground || Running()) && (e = d.Next()) != NULL) {
if (strcmp(e->d_name, ".") && strcmp(e->d_name, "..")) { if (strcmp(e->d_name, ".") && strcmp(e->d_name, "..")) {
char *buffer; char *buffer;
asprintf(&buffer, "%s/%s", DirName, e->d_name); 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 (S_ISDIR(st.st_mode)) {
if (endswith(buffer, deleted ? DELEXT : RECEXT)) { if (endswith(buffer, deleted ? DELEXT : RECEXT)) {
cRecording *r = new cRecording(buffer); cRecording *r = new cRecording(buffer);
if (r->Name()) if (r->Name()) {
Lock();
Add(r); Add(r);
ChangeState();
Unlock();
}
else else
delete r; delete r;
} }
else else
ScanVideoDir(buffer); ScanVideoDir(buffer, Foreground);
} }
} }
free(buffer); free(buffer);
@ -763,19 +806,29 @@ void cRecordings::ScanVideoDir(const char *DirName)
} }
} }
bool cRecordings::NeedsUpdate(void) bool cRecordings::StateChanged(int &State)
{ {
return lastUpdate <= LastModifiedTime(AddDirectory(VideoDirectory, ".update")); int NewState = state;
bool Result = State != NewState;
State = state;
return Result;
} }
bool cRecordings::Load(void) bool cRecordings::NeedsUpdate(void)
{ {
lastUpdate = time(NULL); // doing this first to make sure we don't miss anything return lastUpdate < LastModifiedTime(AddDirectory(VideoDirectory, ".update"));
Clear(); }
ScanVideoDir(VideoDirectory);
Sort(); bool cRecordings::Update(bool Wait)
{
if (Wait) {
Refresh(true);
return Count() > 0; return Count() > 0;
} }
else
Start();
return false;
}
cRecording *cRecordings::GetByName(const char *FileName) cRecording *cRecordings::GetByName(const char *FileName)
{ {
@ -788,18 +841,23 @@ cRecording *cRecordings::GetByName(const char *FileName)
void cRecordings::AddByName(const char *FileName) void cRecordings::AddByName(const char *FileName)
{ {
LOCK_THREAD;
cRecording *recording = GetByName(FileName); cRecording *recording = GetByName(FileName);
if (!recording) { if (!recording) {
recording = new cRecording(FileName); recording = new cRecording(FileName);
Add(recording); Add(recording);
ChangeState();
} }
} }
void cRecordings::DelByName(const char *FileName) void cRecordings::DelByName(const char *FileName)
{ {
LOCK_THREAD;
cRecording *recording = GetByName(FileName); cRecording *recording = GetByName(FileName);
if (recording) if (recording) {
Del(recording); Del(recording);
ChangeState();
}
} }
// --- cMark ----------------------------------------------------------------- // --- cMark -----------------------------------------------------------------

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and * See the main source file 'vdr.c' for copyright information and
* how to reach the author. * 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 #ifndef __RECORDING_H
@ -71,7 +71,7 @@ public:
int lifetime; int lifetime;
cRecording(cTimer *Timer, const cEvent *Event); cRecording(cTimer *Timer, const cEvent *Event);
cRecording(const char *FileName); cRecording(const char *FileName);
~cRecording(); virtual ~cRecording();
virtual int Compare(const cListObject &ListObject) const; virtual int Compare(const cListObject &ListObject) const;
const char *Name(void) const { return name; } const char *Name(void) const { return name; }
const char *FileName(void) const; const char *FileName(void) const;
@ -90,16 +90,32 @@ public:
// Returns false in case of error // Returns false in case of error
}; };
class cRecordings : public cList<cRecording> { class cRecordings : public cList<cRecording>, public cThread {
private: private:
bool deleted; bool deleted;
time_t lastUpdate; 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: public:
cRecordings(bool Deleted = false); 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; } void TriggerUpdate(void) { lastUpdate = 0; }
bool NeedsUpdate(void); bool NeedsUpdate(void);
void ChangeState(void) { state++; }
bool StateChanged(int &State);
cRecording *GetByName(const char *FileName); cRecording *GetByName(const char *FileName);
void AddByName(const char *FileName); void AddByName(const char *FileName);
void DelByName(const char *FileName); void DelByName(const char *FileName);
@ -112,7 +128,7 @@ public:
int position; int position;
char *comment; char *comment;
cMark(int Position = 0, const char *Comment = NULL); cMark(int Position = 0, const char *Comment = NULL);
~cMark(); virtual ~cMark();
cString ToText(void); cString ToText(void);
bool Parse(const char *s); bool Parse(const char *s);
bool Save(FILE *f); bool Save(FILE *f);

View File

@ -10,7 +10,7 @@
* and interact with the Video Disk Recorder - or write a full featured * and interact with the Video Disk Recorder - or write a full featured
* graphical interface that sits on top of an SVDRP connection. * 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" #include "svdrp.h"
@ -890,7 +890,7 @@ void cSVDRP::CmdLSTE(const char *Option)
void cSVDRP::CmdLSTR(const char *Option) void cSVDRP::CmdLSTR(const char *Option)
{ {
bool recordings = Recordings.Load(); bool recordings = Recordings.Update(true);
if (*Option) { if (*Option) {
if (isnumber(Option)) { if (isnumber(Option)) {
cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1); cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1);

12
vdr.c
View File

@ -22,7 +22,7 @@
* *
* The project's page is at http://www.cadsoft.de/vdr * 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 <getopt.h> #include <getopt.h>
@ -437,6 +437,10 @@ int main(int argc, char *argv[])
cFont::SetCode(I18nCharSets()[Setup.OSDLanguage]); cFont::SetCode(I18nCharSets()[Setup.OSDLanguage]);
// Recordings:
Recordings.Update();
// EPG data: // EPG data:
if (EpgDataFileName) { if (EpgDataFileName) {
@ -539,10 +543,6 @@ int main(int argc, char *argv[])
else else
cDevice::PrimaryDevice()->SetVolume(Setup.CurrentVolume, true); cDevice::PrimaryDevice()->SetVolume(Setup.CurrentVolume, true);
// Recordings:
Recordings.Load();
// Signal handlers: // Signal handlers:
if (signal(SIGHUP, SignalHandler) == SIG_IGN) signal(SIGHUP, SIG_IGN); if (signal(SIGHUP, SignalHandler) == SIG_IGN) signal(SIGHUP, SIG_IGN);
@ -674,7 +674,7 @@ int main(int argc, char *argv[])
} }
} }
if (!Menu && Recordings.NeedsUpdate()) if (!Menu && Recordings.NeedsUpdate())
Recordings.Load(); Recordings.Update();
// CAM control: // CAM control:
if (!Menu && !cOsd::IsOpen()) { if (!Menu && !cOsd::IsOpen()) {
Menu = CamControl(); Menu = CamControl();