diff --git a/HISTORY b/HISTORY index 45b726dc..770a28f8 100644 --- a/HISTORY +++ b/HISTORY @@ -2893,7 +2893,7 @@ Video Disk Recorder Revision History strings in order to avoid buffer overflows (thanks to Philip Lawatsch for debugging a buffer overflow in eit.c). -2004-06-12: Version 1.3.11 +2004-06-13: Version 1.3.11 - In order to avoid problems on NPTL systems, VDR now checks for the presence of NPTL at program start, and if it is, exists and tells the user to do @@ -2911,3 +2911,10 @@ Video Disk Recorder Revision History - Fixed switching channels while an encrypted channel is being recorded, because the channel was switched if the new channel was on the same transponder and was a radio channel (thanks to Martin Dauskardt for reporting this one). +- The list of recordings is now kept statically in memory to avoid long delays + when opening the "Recordings" menu. As a side effect, external modifications to + the video directory are no longer immediately reflected in the "Recordings" menu. + If a plugin manipulates the video directory in any way, it can call the function + Recordings.TriggerUpdate() to trigger an update of the list of recordings. + If some external tool manipulates the video directory, it can touch the file + '.update' in the video directory to trigger an update of the list of recordings. diff --git a/cutter.c b/cutter.c index 25169a2a..c8ed60ec 100644 --- a/cutter.c +++ b/cutter.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: cutter.c 1.6 2003/10/18 11:29:37 kls Exp $ + * $Id: cutter.c 1.7 2004/06/13 16:04:08 kls Exp $ */ #include "cutter.h" @@ -205,6 +205,7 @@ bool cCutter::Start(const char *FileName) // XXX editedVersionName = strdup(evn); Recording.WriteSummary(); + Recordings.AddByName(editedVersionName); cuttingThread = new cCuttingThread(FileName, editedVersionName); return true; } @@ -224,6 +225,7 @@ void cCutter::Stop(void) if (Error) esyslog("ERROR: '%s' during editing process", Error); RemoveVideoFile(editedVersionName); //XXX what if this file is currently being replayed? + Recordings.DelByName(editedVersionName); } } diff --git a/menu.c b/menu.c index 3fd39b15..95d72536 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.308 2004/06/06 09:47:44 kls Exp $ + * $Id: menu.c 1.309 2004/06/13 20:26:51 kls Exp $ */ #include "menu.h" @@ -1410,7 +1410,6 @@ void cMenuRecordingItem::IncrementCounter(bool New) // --- cMenuRecordings ------------------------------------------------------- -cRecordings cMenuRecordings::Recordings; int cMenuRecordings::helpKeys = -1; cMenuRecordings::cMenuRecordings(const char *Base, int Level, bool OpenSubMenus) @@ -1419,40 +1418,35 @@ cMenuRecordings::cMenuRecordings(const char *Base, int Level, bool OpenSubMenus) base = Base ? strdup(Base) : NULL; level = Setup.RecordingDirs ? Level : -1; 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) - Skins.Message(mtStatus, tr("scanning recordings...")); - bool Loaded = Base || Recordings.Load(); - if (!Base) - Skins.Message(mtStatus, NULL); - if (Loaded) { - const char *LastReplayed = cReplayControl::LastReplayed(); - cMenuRecordingItem *LastItem = NULL; - char *LastItemText = NULL; - 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()); - } + 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) - SetCurrent(First()); - else if (OpenSubMenus && Open(true)) - return; - } + } + free(LastItemText); + if (Current() < 0) + SetCurrent(First()); + else if (OpenSubMenus && Open(true)) + return; SetHelpKeys(); } @@ -2780,6 +2774,7 @@ cRecordControl::cRecordControl(cDevice *Device, cTimer *Timer, bool Pause) cStatus::MsgRecording(device, Recording.Name()); if (!Timer && !cReplayControl::LastReplayed()) // an instant recording, maybe from cRecordControls::PauseLiveVideo() cReplayControl::SetRecording(fileName, Recording.Name()); + Recordings.AddByName(fileName); } else DELETENULL(recorder); diff --git a/menu.h b/menu.h index 150f88de..5990a2ea 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.63 2004/05/23 09:47:26 kls Exp $ + * $Id: menu.h 1.64 2004/06/13 11:46:03 kls Exp $ */ #ifndef __MENU_H @@ -16,7 +16,6 @@ #include "osdbase.h" #include "dvbplayer.h" #include "recorder.h" -#include "recording.h" #include "skins.h" class cMenuText : public cOsdMenu { @@ -107,7 +106,6 @@ class cMenuRecordingItem; class cMenuRecordings : public cOsdMenu { private: - static cRecordings Recordings; char *base; int level; static int helpKeys; diff --git a/recording.c b/recording.c index 05efc440..cbc916a2 100644 --- a/recording.c +++ b/recording.c @@ -4,10 +4,11 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: recording.c 1.87 2004/05/07 14:24:18 kls Exp $ + * $Id: recording.c 1.88 2004/06/13 20:25:19 kls Exp $ */ #include "recording.h" +#include #include #include #include @@ -25,7 +26,7 @@ #define RECEXT ".rec" #define DELEXT ".del" /* This was the original code, which works fine in a Linux only environment. - Unfortunately, because of windows and its brain dead file system, we have + Unfortunately, because of Windows and its brain dead file system, we have to use a more complicated approach, in order to allow users who have enabled the VFAT compile time option to see their recordings even if they forget to enable VFAT when compiling a new version of VDR... Gee, do I hate Windows. @@ -47,8 +48,6 @@ #define SUMMARYFILESUFFIX "/summary.vdr" #define MARKSFILESUFFIX "/marks.vdr" -#define FINDCMD "cd '%s' && find '%s' -follow -type d -name '%s' 2> /dev/null" - #define MINDISKSPACE 1024 // MB #define DELETEDLIFETIME 1 // hours after which a deleted recording will be actually removed @@ -70,14 +69,14 @@ void RemoveDeletedRecordings(void) if (!LockFile.Lock()) return; // Remove the oldest file that has been "deleted": - cRecordings Recordings; - if (Recordings.Load(true)) { - cRecording *r = Recordings.First(); + cRecordings DeletedRecordings(true); + if (DeletedRecordings.Load()) { + cRecording *r = DeletedRecordings.First(); cRecording *r0 = r; while (r) { if (r->start < r0->start) r0 = r; - r = Recordings.Next(r); + r = DeletedRecordings.Next(r); } if (r0 && time(NULL) - r0->start > DELETEDLIFETIME * 3600) { r0->Remove(); @@ -105,14 +104,14 @@ 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 Recordings; - if (Recordings.Load(true)) { - cRecording *r = Recordings.First(); + cRecordings DeletedRecordings(true); + if (DeletedRecordings.Load()) { + cRecording *r = DeletedRecordings.First(); cRecording *r0 = r; while (r) { if (r->start < r0->start) r0 = r; - r = Recordings.Next(r); + r = DeletedRecordings.Next(r); } if (r0 && r0->Remove()) { LastFreeDiskCheck += REMOVELATENCY / Factor; @@ -121,7 +120,7 @@ void AssertFreeDiskSpace(int Priority) } // 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(false)) { + if (Recordings.Load()) { cRecording *r = Recordings.First(); cRecording *r0 = NULL; while (r) { @@ -138,8 +137,10 @@ void AssertFreeDiskSpace(int Priority) } r = Recordings.Next(r); } - if (r0 && r0->Delete()) + if (r0 && r0->Delete()) { + Recordings.Del(r0); return; + } } // Unable to free disk space, but there's nothing we can do about that... isyslog("...no old recording found, giving up"); @@ -617,30 +618,75 @@ bool cRecording::Remove(void) // --- cRecordings ----------------------------------------------------------- -bool cRecordings::Load(bool Deleted) +cRecordings Recordings; + +cRecordings::cRecordings(bool Deleted) { - Clear(); - bool result = false; - char *cmd = NULL; - asprintf(&cmd, FINDCMD, VideoDirectory, VideoDirectory, Deleted ? "*" DELEXT : "*" RECEXT); - FILE *p = popen(cmd, "r"); - if (p) { - char *s; - while ((s = readline(p)) != NULL) { - cRecording *r = new cRecording(s); - if (r->Name()) - Add(r); - else - delete r; + deleted = Deleted; + lastUpdate = 0; +} + +bool cRecordings::ScanVideoDir(const char *DirName) +{ + DIR *d = opendir(DirName); + if (d) { + struct dirent *e; + while ((e = readdir(d)) != NULL) { + if (strcmp(e->d_name, ".") && strcmp(e->d_name, "..")) { + char *buffer; + asprintf(&buffer, "%s/%s", DirName, e->d_name); + struct stat st; + if (stat(buffer, &st) == 0) { + if (S_ISLNK(st.st_mode)) { + free(buffer); + buffer = ReadLink(buffer); + if (!buffer) + return false; + if (stat(buffer, &st) != 0) { + LOG_ERROR_STR(DirName); + return false; + } + } + if (S_ISDIR(st.st_mode)) { + if (endswith(buffer, deleted ? DELEXT : RECEXT)) { + cRecording *r = new cRecording(buffer); + if (r->Name()) + Add(r); + else + delete r; + } + else if (!ScanVideoDir(buffer)) + return false; + } + } + else { + LOG_ERROR_STR(DirName); + return false; + } + free(buffer); + } } - pclose(p); - Sort(); - result = Count() > 0; + closedir(d); } - else - Skins.Message(mtError, "Error while opening pipe!"); - free(cmd); - return result; + else { + LOG_ERROR_STR(DirName); + return false; + } + return true; +} + +bool cRecordings::NeedsUpdate(void) +{ + return lastUpdate <= LastModifiedTime(AddDirectory(VideoDirectory, ".update")); +} + +bool cRecordings::Load(void) +{ + lastUpdate = time(NULL); // doing this first to make sure we don't miss anything + Clear(); + ScanVideoDir(VideoDirectory); + Sort(); + return Count() > 0; } cRecording *cRecordings::GetByName(const char *FileName) @@ -652,6 +698,22 @@ cRecording *cRecordings::GetByName(const char *FileName) return NULL; } +void cRecordings::AddByName(const char *FileName) +{ + cRecording *recording = GetByName(FileName); + if (!recording) { + recording = new cRecording(FileName); + Add(recording); + } +} + +void cRecordings::DelByName(const char *FileName) +{ + cRecording *recording = GetByName(FileName); + if (recording) + Del(recording); +} + // --- cMark ----------------------------------------------------------------- char *cMark::buffer = NULL; diff --git a/recording.h b/recording.h index 01a25ab4..04a1885b 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.29 2004/05/07 14:24:22 kls Exp $ + * $Id: recording.h 1.30 2004/06/13 15:37:42 kls Exp $ */ #ifndef __RECORDING_H @@ -69,11 +69,22 @@ public: }; class cRecordings : public cList { +private: + bool deleted; + time_t lastUpdate; + bool ScanVideoDir(const char *DirName); public: - bool Load(bool Deleted = false); + cRecordings(bool Deleted = false); + bool Load(void); + void TriggerUpdate(void) { lastUpdate = 0; } + bool NeedsUpdate(void); cRecording *GetByName(const char *FileName); + void AddByName(const char *FileName); + void DelByName(const char *FileName); }; +extern cRecordings Recordings; + class cMark : public cListObject { private: static char *buffer; diff --git a/svdrp.c b/svdrp.c index 63126f51..87f146e5 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.62 2004/03/25 17:00:23 kls Exp $ + * $Id: svdrp.c 1.63 2004/06/13 13:38:38 kls Exp $ */ #include "svdrp.h" @@ -504,8 +504,10 @@ void cSVDRP::CmdDELR(const char *Option) if (isnumber(Option)) { cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1); if (recording) { - if (recording->Delete()) + if (recording->Delete()) { Reply(250, "Recording \"%s\" deleted", Option); + ::Recordings.Load(); // must make sure the global recordings list is updated + } else Reply(554, "Error while deleting recording!"); } diff --git a/tools.c b/tools.c index 42193b2f..0f42182e 100644 --- a/tools.c +++ b/tools.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: tools.c 1.79 2004/05/22 12:13:27 kls Exp $ + * $Id: tools.c 1.80 2004/06/13 14:36:41 kls Exp $ */ #include "tools.h" @@ -481,6 +481,14 @@ bool SpinUpDisk(const char *FileName) return false; } +time_t LastModifiedTime(const char *FileName) +{ + struct stat fs; + if (stat(FileName, &fs) == 0) + return fs.st_mtime; + return 0; +} + const char *WeekDayName(int WeekDay) { static char buffer[4]; diff --git a/tools.h b/tools.h index 2132d001..071e35e0 100644 --- a/tools.h +++ b/tools.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: tools.h 1.56 2004/05/22 12:11:44 kls Exp $ + * $Id: tools.h 1.57 2004/06/13 14:13:26 kls Exp $ */ #ifndef __TOOLS_H @@ -83,6 +83,7 @@ bool RemoveFileOrDir(const char *FileName, bool FollowSymlinks = false); bool RemoveEmptyDirectories(const char *DirName, bool RemoveThis = false); char *ReadLink(const char *FileName); bool SpinUpDisk(const char *FileName); +time_t LastModifiedTime(const char *FileName); const char *WeekDayName(int WeekDay); ///< \warning returns a statically allocated string! const char *WeekDayName(time_t t); ///< \warning returns a statically allocated string! const char *DayDateTime(time_t t = 0); ///< \warning returns a statically allocated string! diff --git a/vdr.1 b/vdr.1 index b519f9d9..106b1d3f 100644 --- a/vdr.1 +++ b/vdr.1 @@ -8,7 +8,7 @@ .\" License as specified in the file COPYING that comes with the .\" vdr distribution. .\" -.\" $Id: vdr.1 1.10 2004/05/16 12:10:52 kls Exp $ +.\" $Id: vdr.1 1.11 2004/06/13 14:48:03 kls Exp $ .\" .TH vdr 1 "1 June 2003" "1.2.0" "Video Disk Recorder" .SH NAME @@ -176,6 +176,10 @@ The actual data files of a recording. .I epg.data Contains all current EPG data. Can be used for external processing and will also be read at program startup to have the full EPG data available immediately. +.TP +.I .update +If this file is present in the video directory, its last modification time will +be used to trigger an update of the list of recordings in the "Recordings" menu. .SH SEE ALSO .BR vdr (5) .SH AUTHOR diff --git a/vdr.c b/vdr.c index 23f24957..fc1f9e09 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.183 2004/06/12 10:07:17 kls Exp $ + * $Id: vdr.c 1.184 2004/06/13 13:52:09 kls Exp $ */ #include @@ -475,6 +475,10 @@ 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); @@ -606,6 +610,8 @@ int main(int argc, char *argv[]) TimerInVpsMargin = true; } } + if (!Menu && Recordings.NeedsUpdate()) + Recordings.Load(); // CAM control: if (!Menu && !cOsd::IsOpen()) Menu = CamControl();