diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 51970b8f..231c1ab1 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -1485,6 +1485,10 @@ Udo Richter for some hints on how to improve handling cPluginManager::Active() for fixing a possible segfault if VDR gets terminated while a message is displayed for reporting an error in the INSTALL section on retrying shutdown later + for rewriting shutdown handling + for implementing cPlugin::WakeupTime() to allow plugins to request VDR to wake + up at a particular time + for making the HUP signal force a restart of VDR Sven Kreiensen for his help in keeping 'channels.conf.terr' up to date diff --git a/HISTORY b/HISTORY index 84004438..55b87257 100644 --- a/HISTORY +++ b/HISTORY @@ -5099,7 +5099,7 @@ Video Disk Recorder Revision History - Fixed handling error status in cDvbTuner::GetFrontendStatus() (thanks to Reinhard Nissl). -2007-02-03: Version 1.5.1 +2007-02-25: Version 1.5.1 - Added cDevice::HasCi() so that devices with Common Interface can be avoided when tuning to an FTA channel, thus preserving the CAM resources even on budget @@ -5110,3 +5110,8 @@ Video Disk Recorder Revision History Nissl). - Removed 'assert(0)' from cDvbSpuDecoder::setTime() (thanks to Marco Schlüßler). - Adapted 'libsi' to DVB-S2 (thanks to Marco Schlüßler). +- Shutdown handling has been rewritten (thanks to Udo Richter). +- Plugins can now implement the new function WakeupTime() to request VDR to wake + up at a particular time (thanks to Udo Richter). +- The HUP signal now forces a restart of VDR (thanks to Udo Richter). +- cThread::EmergencyExit() has been replaced by ShutdownHandler.RequestEmergencyExit(). diff --git a/INSTALL b/INSTALL index 21dfcfad..7f50c44d 100644 --- a/INSTALL +++ b/INSTALL @@ -160,30 +160,33 @@ and the next timer event is at least MinEventTimeout minutes in the future (see the Setup parameters in MANUAL). The command given in the '-s' option will be called with five parameters. -The first one is the time (in UTC) of the next timer event (as a time_t -type number), and the second one is the number of seconds from the current -time until the next timer event. Your program can choose which one to use -for programming some sort of hardware device that makes sure the computer -will be restarted in time before the next timer event. Your program must -also initiate the actual shutdown procedure of the computer. After this -your program should return to VDR. VDR will not automatically exit after -calling the shutdown program, but will rather continue normally until it -receives a SIGTERM when the computer is actually shut down. So in case -the shutdown fails, or the shutdown program for some reason decides not to -perform a shutdown, VDR will stay up and running and will call the shutdown -program again after another five minutes. -If there are currently no timers active, both parameters will be '0'. -In that case the program shall not set the hardware for automatic restart -and only perform the system shutdown. A program that uses the second parameter -to set the hardware for restart must therefore also check whether the first -parameter is '0'. +The first one is the time (in UTC) of the next timer event or plugin wakeup +time (as a time_t type number), and the second one is the number of +seconds from the current time until the next timer event. Your program can +choose which one to use for programming some sort of hardware device that +makes sure the computer will be restarted in time before the next timer +event. Your program must also initiate the actual shutdown procedure of the +computer. VDR will not automatically exit after calling the shutdown +program, but will rather continue normally until it receives a SIGTERM when +the computer is actually shut down. So in case the shutdown fails, or the +shutdown program for some reason decides not to perform a shutdown, VDR +will stay up and running and will call the shutdown program again after a +while. The command will be started in a separate background session, so it +can continue to run even after VDR has terminated. -The third parameter contains the number of the channel that will be recorded -by the next timer (or 0 if no timer is present), and the fourth parameter -contains the file name of the recording as defined in the timer (or an empty -string if no timer is present). These can be used by the shutdown program to -show that information on some display interface etc. +If there are currently no timers active and there is no plugin wakeup +time, both parameters will be '0'. In that case the program shall not set +the hardware for automatic restart and only perform the system shutdown. +A program that uses the second parameter to set the hardware for restart +must therefore also check whether the first parameter is '0'. + +If the wakeup time is given by a timer, the third parameter will be the +number of the channel that will be recorded, otherwise it will be 0. The +fourth parameter contains the file name of the recording as defined in the +timer, the name of the plugin that requested the wakeup time, or an empty +string if no wakeup time is present. These can be used by the shutdown +program to show that information on some display interface etc. The fifth parameter indicates the reason why the shutdown was requested. '0' means this is an automatic shutdown due to some timeout, while '1' means diff --git a/Makefile b/Makefile index d70973a0..c20c7f2c 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ # See the main source file 'vdr.c' for copyright information and # how to reach the author. # -# $Id: Makefile 1.96 2007/01/07 14:38:00 kls Exp $ +# $Id: Makefile 1.97 2007/02/24 13:23:12 kls Exp $ .DELETE_ON_ERROR: @@ -35,7 +35,7 @@ SILIB = $(LSIDIR)/libsi.a OBJS = audio.o channels.o ci.o config.o cutter.o device.o diseqc.o dvbdevice.o dvbci.o dvbosd.o\ dvbplayer.o dvbspu.o eit.o eitscan.o epg.o filter.o font.o i18n.o interface.o keys.o\ lirc.o menu.o menuitems.o nit.o osdbase.o osd.o pat.o player.o plugin.o rcu.o\ - receiver.o recorder.o recording.o remote.o remux.o ringbuffer.o sdt.o sections.o\ + receiver.o recorder.o recording.o remote.o remux.o ringbuffer.o sdt.o sections.o shutdown.o\ skinclassic.o skins.o skinsttng.o sources.o spu.o status.o svdrp.o themes.o thread.o\ timers.o tools.o transfer.o vdr.o videodir.o diff --git a/PLUGINS.html b/PLUGINS.html index 71c1b77e..64034538 100644 --- a/PLUGINS.html +++ b/PLUGINS.html @@ -6,7 +6,7 @@

The VDR Plugin System

-
Version 1.5.0
+
Version 1.5.1

Copyright © 2006 Klaus Schmidinger
@@ -14,9 +14,12 @@ Copyright © 2006 Klaus Schmidinger
www.cadsoft.de/vdr

-
  +
  Important modifications introduced in version 1.5.0 are marked like this.
+
  +Important modifications introduced in version 1.5.1 are marked like this. +

VDR provides an easy to use plugin interface that allows additional functionality to be added to the program by implementing a dynamically loadable library file. @@ -55,6 +58,9 @@ structures and allows it to hook itself into specific areas to perform special a

  • Housekeeping
  • Main thread hook
  • Activity +
      +
  • Wakeup +
  • Setup parameters
  • The Setup menu
  • Configuration files @@ -76,7 +82,7 @@ structures and allows it to hook itself into specific areas to perform special a
  • Devices
  • Audio
  • Remote Control -
      +
     
  • Conditional Access
  • @@ -675,6 +681,41 @@ be queried, and further prompts may show up. If all prompts have been confirmed, the shutdown will take place. As soon as one prompt is not confirmed, no further plugins will be queried and no shutdown will be done. +
      +

    Wakeup

    + +
    Wake me up before you go-go

    + +If a plugin wants to schedule activity for a later time, or wants to perform +periodic activity at a certain time at night, and if VDR shall wake up from +shutdown at that time, the plugin can implement the function + +

    +virtual time_t WakeupTime(void);
    +

    + +which shall return the time of the next custom wakeup time, or 0 if no wakeup +is planned. VDR will pass the most recent wakeup time of all plugins, or the next +timer time, whichever comes first, to the shutdown script. The following sample +will wake up VDR every night at 1:00: + +

    +time_t MyPlugin::WakeupTime(void)
    +{
    +  time_t Now = time(NULL);
    +  time_t Time = cTimer::SetTime(Now, cTimer::TimeToInt(100));
    +  if (Time <= Now)
    +     Time = cTimer::IncDay(Time, 1);
    +  return Time;
    +}
    +

    + +After wakeup, the plugin shall continue to return the wakeup time and shall +return a string when Active() is called at that time, otherwise VDR may shut down +again instantly. If WakeupTime() returns a time that is not in +the future, the time will be ignored. +

    +

    Setup parameters

    Remember me...

    @@ -2046,7 +2087,7 @@ Put(uint64 Code, bool Repeat = false, bool Release = false); The other parameters have the same meaning as in the first version of this function. -
      +
     

    Conditional Access

    Members only!

    diff --git a/config.c b/config.c index 6ac72a8f..399bb21a 100644 --- a/config.c +++ b/config.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: config.c 1.147 2007/01/26 13:32:19 kls Exp $ + * $Id: config.c 1.148 2007/02/24 13:29:52 kls Exp $ */ #include "config.h" @@ -267,6 +267,7 @@ cSetup::cSetup(void) SplitEditedFiles = 0; MinEventTimeout = 30; MinUserInactivity = 300; + NextWakeupTime = 0; MultiSpeedMode = 0; ShowReplayMode = 0; ResumeID = 0; @@ -428,6 +429,7 @@ bool cSetup::Parse(const char *Name, const char *Value) else if (!strcasecmp(Name, "SplitEditedFiles")) SplitEditedFiles = atoi(Value); else if (!strcasecmp(Name, "MinEventTimeout")) MinEventTimeout = atoi(Value); else if (!strcasecmp(Name, "MinUserInactivity")) MinUserInactivity = atoi(Value); + else if (!strcasecmp(Name, "NextWakeupTime")) NextWakeupTime = atoi(Value); else if (!strcasecmp(Name, "MultiSpeedMode")) MultiSpeedMode = atoi(Value); else if (!strcasecmp(Name, "ShowReplayMode")) ShowReplayMode = atoi(Value); else if (!strcasecmp(Name, "ResumeID")) ResumeID = atoi(Value); @@ -496,6 +498,7 @@ bool cSetup::Save(void) Store("SplitEditedFiles", SplitEditedFiles); Store("MinEventTimeout", MinEventTimeout); Store("MinUserInactivity", MinUserInactivity); + Store("NextWakeupTime", NextWakeupTime); Store("MultiSpeedMode", MultiSpeedMode); Store("ShowReplayMode", ShowReplayMode); Store("ResumeID", ResumeID); diff --git a/config.h b/config.h index 00fd784c..5bad37e2 100644 --- a/config.h +++ b/config.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: config.h 1.284 2007/01/13 11:42:43 kls Exp $ + * $Id: config.h 1.285 2007/02/24 13:23:12 kls Exp $ */ #ifndef __CONFIG_H @@ -244,6 +244,7 @@ public: int MaxVideoFileSize; int SplitEditedFiles; int MinEventTimeout, MinUserInactivity; + time_t NextWakeupTime; int MultiSpeedMode; int ShowReplayMode; int ResumeID; diff --git a/i18n.c b/i18n.c index b48dd523..293081d0 100644 --- a/i18n.c +++ b/i18n.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: i18n.c 1.287 2007/01/26 13:26:05 kls Exp $ + * $Id: i18n.c 1.288 2007/02/25 10:11:26 kls Exp $ * * Translations provided by: * @@ -1341,6 +1341,28 @@ const tI18nPhrase Phrases[] = { "Optagelse igang - genstart alligevel?", "Systém je zaneprázdnìn - pøesto restartovat?", }, + { "Editing - restart anyway?", + "Schnitt läuft - trotzdem neu starten?", + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + }, { "Recording - shut down anyway?", "Aufnahme läuft - trotzdem ausschalten?", "Snemanje - zares izklopi?", @@ -1407,6 +1429,116 @@ const tI18nPhrase Phrases[] = { "Tryk vilkårlig tast for at annullere sluk", "Jakákoliv klávesa zru¹í vypnutí", }, + { "Press any key to cancel restart", + "Taste drücken, um Neustart abzubrechen", + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + }, + { "VDR will shut down later - press Power to force", + "VDR schaltet später aus - Power zum erzwingen", + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "VDR sammuu myöhemmin - pakota virtakytkimellä", + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + }, + { "VDR will shut down in %s minutes", + "VDR wird in %s Minuten ausschalten", + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "VDR sammuu %s minuutin kuluttua", + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + }, + { "Editing - shut down anyway?", + "Schnitt läuft - trotzdem ausschalten?", + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "Leikkaus kesken - sammutetaanko?", + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + }, + { "Plugin %s wakes up in %ld min, continue?", + "Plugin %s wacht in %ld Min auf, weiter?", + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "Laajennos %s herää %ld minuutin kuluttua - sammutetaanko?", + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + "",//TODO + }, // Channel parameters: { "Name", "Name", diff --git a/keys.h b/keys.h index be87c798..0799a3b4 100644 --- a/keys.h +++ b/keys.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: keys.h 1.10 2006/10/14 10:41:20 kls Exp $ + * $Id: keys.h 1.11 2007/02/25 10:49:35 kls Exp $ */ #ifndef __KEYS_H @@ -74,6 +74,7 @@ enum eKeys { // "Up" and "Down" must be the first two keys! #define ISRAWKEY(k) ((k) != kNone && ((k) & k_Flags) == 0) #define NORMALKEY(k) (eKeys((k) & ~k_Repeat)) #define ISMODELESSKEY(k) (RAWKEY(k) > k9) +#define ISREALKEY(k) (k != kNone && k != k_Plugin) #define BASICKEY(k) (eKeys((k) & 0xFFFF)) #define KBDKEY(k) (eKeys(((k) << 16) | kKbd)) diff --git a/menu.c b/menu.c index b8bc7c70..9674e5eb 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.447 2007/01/07 14:19:48 kls Exp $ + * $Id: menu.c 1.448 2007/02/25 09:51:53 kls Exp $ */ #include "menu.h" @@ -22,6 +22,7 @@ #include "plugin.h" #include "recording.h" #include "remote.h" +#include "shutdown.h" #include "sources.h" #include "status.h" #include "themes.h" @@ -2770,10 +2771,8 @@ void cMenuSetup::Set(void) eOSState cMenuSetup::Restart(void) { - if (Interface->Confirm(tr("Really restart?")) - && (!cRecordControls::Active() || Interface->Confirm(tr("Recording - restart anyway?"))) - && !cPluginManager::Active(tr("restart anyway?"))) { - cThread::EmergencyExit(true); + if (Interface->Confirm(tr("Really restart?")) && ShutdownHandler.ConfirmRestart(true)) { + ShutdownHandler.Exit(1); return osEnd; } return osContinue; @@ -3699,7 +3698,7 @@ bool cRecordControls::Start(cTimer *Timer, bool Pause) if (device) { dsyslog("switching device %d to channel %d", device->DeviceNumber() + 1, channel->Number()); if (!device->SwitchChannel(channel, false)) { - cThread::EmergencyExit(true); + ShutdownHandler.RequestEmergencyExit(); return false; } if (!Timer || Timer->Matches()) { diff --git a/newplugin b/newplugin index 363baae4..dbb56e6d 100755 --- a/newplugin +++ b/newplugin @@ -12,7 +12,7 @@ # See the main source file 'vdr.c' for copyright information and # how to reach the author. # -# $Id: newplugin 1.30 2006/09/09 12:38:35 kls Exp $ +# $Id: newplugin 1.31 2007/02/24 13:23:08 kls Exp $ $PLUGIN_NAME = $ARGV[0] || die "Usage: newplugin \n"; @@ -169,6 +169,7 @@ public: virtual void Housekeeping(void); virtual void MainThreadHook(void); virtual cString Active(void); + virtual time_t WakeupTime(void); virtual const char *MainMenuEntry(void) { return MAINMENUENTRY; } virtual cOsdObject *MainMenuAction(void); virtual cMenuSetupPage *SetupMenu(void); @@ -236,6 +237,12 @@ cString cPlugin${PLUGIN_CLASS}::Active(void) return NULL; } +time_t cPlugin${PLUGIN_CLASS}::WakeupTime(void) +{ + // Return custom wakeup time for shutdown script + return 0; +} + cOsdObject *cPlugin${PLUGIN_CLASS}::MainMenuAction(void) { // Perform the action when selected from the main VDR menu. diff --git a/plugin.c b/plugin.c index 569e941a..16087cf6 100644 --- a/plugin.c +++ b/plugin.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: plugin.c 1.24 2006/10/14 09:49:16 kls Exp $ + * $Id: plugin.c 1.25 2007/02/24 13:44:23 kls Exp $ */ #include "plugin.h" @@ -80,6 +80,11 @@ cString cPlugin::Active(void) return NULL; } +time_t cPlugin::WakeupTime(void) +{ + return 0; +} + const char *cPlugin::MainMenuEntry(void) { return NULL; @@ -403,6 +408,26 @@ bool cPluginManager::Active(const char *Prompt) return false; } +cPlugin *cPluginManager::GetNextWakeupPlugin(void) +{ + cPlugin *NextPlugin = NULL; + if (pluginManager) { + time_t Now = time(NULL); + time_t Next = 0; + for (cDll *dll = pluginManager->dlls.First(); dll; dll = pluginManager->dlls.Next(dll)) { + cPlugin *p = dll->Plugin(); + if (p) { + time_t t = p->WakeupTime(); + if (t > Now && (!Next || t < Next)) { + Next = t; + NextPlugin = p; + } + } + } + } + return NextPlugin; +} + bool cPluginManager::HasPlugins(void) { return pluginManager && pluginManager->dlls.Count(); diff --git a/plugin.h b/plugin.h index 19db6134..ddb498c1 100644 --- a/plugin.h +++ b/plugin.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: plugin.h 1.13 2006/04/17 09:18:16 kls Exp $ + * $Id: plugin.h 1.14 2007/02/24 13:45:28 kls Exp $ */ #ifndef __PLUGIN_H @@ -41,6 +41,7 @@ public: virtual void Housekeeping(void); virtual void MainThreadHook(void); virtual cString Active(void); + virtual time_t WakeupTime(void); virtual const char *MainMenuEntry(void); virtual cOsdObject *MainMenuAction(void); @@ -93,6 +94,7 @@ public: void Housekeeping(void); void MainThreadHook(void); static bool Active(const char *Prompt = NULL); + static cPlugin *GetNextWakeupPlugin(void); static bool HasPlugins(void); static cPlugin *GetPlugin(int Index); static cPlugin *GetPlugin(const char *Name); diff --git a/recorder.c b/recorder.c index 8c426a5a..7bcd0cc7 100644 --- a/recorder.c +++ b/recorder.c @@ -4,13 +4,14 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: recorder.c 1.18 2007/01/07 14:43:09 kls Exp $ + * $Id: recorder.c 1.19 2007/02/24 16:36:24 kls Exp $ */ #include "recorder.h" #include #include #include +#include "shutdown.h" #define RECORDERBUFSIZE MEGABYTE(5) @@ -117,7 +118,7 @@ void cFileWriter::Action(void) } else if (time(NULL) - t > MAXBROKENTIMEOUT) { esyslog("ERROR: video data stream broken"); - cThread::EmergencyExit(true); + ShutdownHandler.RequestEmergencyExit(); t = time(NULL); } } diff --git a/remote.c b/remote.c index fa341846..bbc73787 100644 --- a/remote.c +++ b/remote.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: remote.c 1.55 2006/12/02 11:12:42 kls Exp $ + * $Id: remote.c 1.56 2007/02/24 13:23:12 kls Exp $ */ #include "remote.h" @@ -31,6 +31,7 @@ cMutex cRemote::mutex; cCondVar cRemote::keyPressed; const char *cRemote::keyMacroPlugin = NULL; const char *cRemote::callPlugin = NULL; +time_t cRemote::lastActivity = 0; cRemote::cRemote(const char *Name) { @@ -183,6 +184,7 @@ eKeys cRemote::Get(int WaitMs, char **UnknownCode) out = 0; if ((k & k_Repeat) != 0) repeatTimeout.Set(REPEATTIMEOUT); + lastActivity = time(NULL); return k; } else if (!WaitMs || !keyPressed.TimedWait(mutex, WaitMs) && repeatTimeout.TimedOut()) diff --git a/remote.h b/remote.h index 03be5288..fb326897 100644 --- a/remote.h +++ b/remote.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: remote.h 1.38 2006/12/02 11:12:49 kls Exp $ + * $Id: remote.h 1.39 2007/02/24 15:53:00 kls Exp $ */ #ifndef __REMOTE_H @@ -28,6 +28,7 @@ private: static char *unknownCode; static cMutex mutex; static cCondVar keyPressed; + static time_t lastActivity; static const char *keyMacroPlugin; static const char *callPlugin; char *name; @@ -61,6 +62,8 @@ public: ///< plugin name will be reset to NULL by this call. static bool HasKeys(void); static eKeys Get(int WaitMs = 1000, char **UnknownCode = NULL); + static time_t LastActivity(void) { return lastActivity; } + ///< Absolute time when last key was delivered by Get(). }; class cRemotes : public cList {}; diff --git a/remux.c b/remux.c index da805b76..ccc5b00d 100644 --- a/remux.c +++ b/remux.c @@ -11,13 +11,13 @@ * The cRepacker family's code was originally written by Reinhard Nissl , * and adapted to the VDR coding style by Klaus.Schmidinger@cadsoft.de. * - * $Id: remux.c 1.57 2006/12/01 14:46:25 kls Exp $ + * $Id: remux.c 1.58 2007/02/24 16:36:10 kls Exp $ */ #include "remux.h" #include #include "channels.h" -#include "thread.h" +#include "shutdown.h" #include "tools.h" ePesHeader AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOffset, bool *ContinuationHeader) @@ -2011,7 +2011,7 @@ int cRemux::Put(const uchar *Data, int Count) esyslog("ERROR: no useful data seen within %d byte of video stream", skipped); skipped = -1; if (exitOnFailure) - cThread::EmergencyExit(true); + ShutdownHandler.RequestEmergencyExit(); } else skipped += used; @@ -2059,7 +2059,7 @@ uchar *cRemux::Get(int &Count, uchar *PictureType) if (pt < I_FRAME || B_FRAME < pt) { esyslog("ERROR: unknown picture type '%d'", pt); if (++numUPTerrors > MAXNUMUPTERRORS && exitOnFailure) - cThread::EmergencyExit(true); + ShutdownHandler.RequestEmergencyExit(); } else if (!synced) { if (pt == I_FRAME) { diff --git a/shutdown.c b/shutdown.c new file mode 100644 index 00000000..4029a78c --- /dev/null +++ b/shutdown.c @@ -0,0 +1,250 @@ +/* + * shutdown.c: Handling of shutdown and inactivity + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * Original version written by Udo Richter . + * + * $Id: shutdown.c 1.1 2007/02/24 17:24:11 kls Exp $ + */ + +#include "shutdown.h" +#include +#include +#include +#include +#include "channels.h" +#include "config.h" +#include "cutter.h" +#include "i18n.h" +#include "interface.h" +#include "menu.h" +#include "plugin.h" +#include "timers.h" +#include "tools.h" + +cShutdownHandler ShutdownHandler; + +cCountdown::cCountdown(void) +{ + timeout = 0; + counter = 0; + timedOut = false; + message = NULL; +} + +void cCountdown::Start(const char *Message, int Seconds) +{ + timeout = time(NULL) + Seconds; + counter = -1; + timedOut = false; + message = Message; + Update(); +} + +void cCountdown::Cancel(void) +{ + if (timeout) { + timeout = 0; + timedOut = false; + Skins.Message(mtStatus, NULL); + } +} + +bool cCountdown::Done(void) +{ + if (timedOut) { + Cancel(); + return true; + } + return false; +} + +bool cCountdown::Update(void) +{ + if (timeout) { + int NewCounter = (timeout - time(NULL) + 9) / 10; + if (NewCounter <= 0) + timedOut = true; + if (counter != NewCounter) { + counter = NewCounter; + char time[10]; + snprintf(time, sizeof(time), "%d:%d0", counter > 0 ? counter / 6 : 0, counter > 0 ? counter % 6 : 0); + cString Message = cString::sprintf(message, time); + Skins.Message(mtStatus, Message); + return true; + } + } + return false; +} + +cShutdownHandler::cShutdownHandler(void) +{ + activeTimeout = 0; + retry = 0; + shutdownCommand = NULL; + exitCode = -1; + emergencyExitRequested = false; +} + +cShutdownHandler::~cShutdownHandler() +{ + free(shutdownCommand); +} + +void cShutdownHandler::RequestEmergencyExit(void) +{ + esyslog("initiating emergency exit"); + emergencyExitRequested = true; + Exit(1); +} + +void cShutdownHandler::CheckManualStart(int ManualStart) +{ + time_t Delta = Setup.NextWakeupTime ? Setup.NextWakeupTime - time(NULL) : 0; + + if (!Setup.NextWakeupTime || abs(Delta) > ManualStart) { + // Apparently the user started VDR manually + dsyslog("assuming manual start of VDR"); + // Set inactive after MinUserInactivity + SetUserInactiveTimeout(); + } + else + // Set inactive from now on + SetUserInactive(); +} + +void cShutdownHandler::SetShutdownCommand(const char *ShutdownCommand) +{ + free(shutdownCommand); + shutdownCommand = ShutdownCommand ? strdup(ShutdownCommand) : NULL; +} + +void cShutdownHandler::CallShutdownCommand(time_t WakeupTime, int Channel, const char *File, bool UserShutdown) +{ + time_t Delta = WakeupTime ? WakeupTime - time(NULL) : 0; + cString cmd = cString::sprintf("%s %ld %ld %d \"%s\" %d", shutdownCommand, WakeupTime, Delta, Channel, *strescape(File, "\"$"), UserShutdown); + isyslog("executing '%s'", *cmd); + if (SystemExec(cmd, true) == 0) + Setup.NextWakeupTime = WakeupTime; // Remember this wakeup time for comparison on reboot +} + +void cShutdownHandler::SetUserInactiveTimeout(int Seconds, bool Force) +{ + if (!Setup.MinUserInactivity && !Force) { + activeTimeout = 0; + return; + } + if (Seconds < 0) + Seconds = Setup.MinUserInactivity * 60; + activeTimeout = time(NULL) + Seconds; +} + +bool cShutdownHandler::ConfirmShutdown(bool Interactive) +{ + if (!shutdownCommand) { + if (Interactive) + Skins.Message(mtError, tr("Can't shutdown - option '-s' not given!")); + return false; + } + if (cCutter::Active()) { + if (!Interactive || !Interface->Confirm(tr("Editing - shut down anyway?"))) + return false; + } + + cTimer *timer = Timers.GetNextActiveTimer(); + time_t Next = timer ? timer->StartTime() : 0; + time_t Delta = timer ? Next - time(NULL) : 0; + + if (cRecordControls::Active() || (Next && Delta <= 0)) { + // VPS recordings in timer end margin may cause Delta <= 0 + if (!Interactive || !Interface->Confirm(tr("Recording - shut down anyway?"))) + return false; + } + else if (Next && Delta <= Setup.MinEventTimeout * 60) { + // Timer within Min Event Timeout + if (!Interactive) + return false; + cString buf = cString::sprintf(tr("Recording in %ld minutes, shut down anyway?"), Delta / 60); + if (!Interface->Confirm(buf)) + return false; + } + + if (cPluginManager::Active(Interactive ? tr("shut down anyway?") : NULL)) + return false; + + cPlugin *Plugin = cPluginManager::GetNextWakeupPlugin(); + Next = Plugin ? Plugin->WakeupTime() : 0; + Delta = Next ? Next - time(NULL) : 0; + if (Next && Delta <= Setup.MinEventTimeout * 60) { + // Plugin wakeup within Min Event Timeout + if (!Interactive) + return false; + cString buf = cString::sprintf(tr("Plugin %s wakes up in %ld min, continue?"), Plugin->Name(), Delta / 60); + if (!Interface->Confirm(buf)) + return false; + } + + return true; +} + +bool cShutdownHandler::ConfirmRestart(bool Interactive) +{ + if (cCutter::Active()) { + if (!Interactive || !Interface->Confirm(tr("Editing - restart anyway?"))) + return false; + } + + cTimer *timer = Timers.GetNextActiveTimer(); + time_t Next = timer ? timer->StartTime() : 0; + time_t Delta = timer ? Next - time(NULL) : 0; + + if (cRecordControls::Active() || (Next && Delta <= 0)) { + // VPS recordings in timer end margin may cause Delta <= 0 + if (!Interactive || !Interface->Confirm(tr("Recording - restart anyway?"))) + return false; + } + + if (cPluginManager::Active(Interactive ? tr("restart anyway?") : NULL)) + return false; + + return true; +} + +bool cShutdownHandler::DoShutdown(bool Force) +{ + time_t Now = time(NULL); + cTimer *timer = Timers.GetNextActiveTimer(); + cPlugin *Plugin = cPluginManager::GetNextWakeupPlugin(); + + time_t Next = timer ? timer->StartTime() : 0; + time_t NextPlugin = Plugin ? Plugin->WakeupTime() : 0; + if (NextPlugin && (!Next || Next > NextPlugin)) { + Next = NextPlugin; + timer = NULL; + } + time_t Delta = Next ? Next - Now : 0; + + if (Next && Delta < Setup.MinEventTimeout * 60) { + if (!Force) + return false; + Delta = Setup.MinEventTimeout * 60; + Next = Now + Delta; + timer = NULL; + dsyslog("reboot at %s", *TimeToString(Next)); + } + + if (Next && timer) { + dsyslog("next timer event at %s", *TimeToString(Next)); + CallShutdownCommand(Next, timer->Channel()->Number(), timer->File(), Force); + } + else if (Next && Plugin) { + CallShutdownCommand(Next, 0, Plugin->Name(), Force); + dsyslog("next plugin wakeup at %s", *TimeToString(Next)); + } + else + CallShutdownCommand(Next, 0, "", Force); // Next should always be 0 here. Just for safety, pass it. + + return true; +} diff --git a/shutdown.h b/shutdown.h new file mode 100644 index 00000000..4dff8eab --- /dev/null +++ b/shutdown.h @@ -0,0 +1,112 @@ +/* + * shutdown.h: Handling of shutdown and inactivity + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * Original version written by Udo Richter . + * + * $Id: shutdown.h 1.1 2007/02/24 17:23:59 kls Exp $ + */ + +#ifndef __SHUTDOWN_H +#define __SHUTDOWN_H + +#include + +class cCountdown { +private: + time_t timeout; ///< 5-minute countdown timer + int counter; ///< last shown time in 10s units + bool timedOut; ///< countdown did run down to 0 and was not canceled + const char *message; ///< message to display, %s is placeholder for time +public: + cCountdown(void); + void Start(const char *Message, int Seconds); + ///< Start the 5 minute shutdown warning countdown. + void Cancel(void); + ///< Cancel the 5 minute shutdown warning countdown. + bool Done(void); + ///< Check if countdown timer has run out without canceling. + operator bool(void) const { return timeout != 0; } + ///< Check if countdown is running. + bool Update(void); + ///< Update status display of the countdown. + ///< Returns true on actual update. + }; + +class cShutdownHandler { +private: + time_t activeTimeout; + ///< Time when VDR will become non-interactive. 0 means never. + time_t retry; + ///< Time for retrying the shutdown. + char *shutdownCommand; + ///< Command for shutting down VDR. + int exitCode; + ///< Exit code, if VDR exit was requested, or -1 if not requested. + bool emergencyExitRequested; + ///< The requested exit is an emergency exit. +public: + cCountdown countdown; + cShutdownHandler(void); + ~cShutdownHandler(); + void Exit(int ExitCode) { exitCode = ExitCode; } + ///< Set VDR exit code and initiate end of VDR main loop. + ///< This will exit VDR without any confirmation. + bool DoExit(void) { return exitCode >= 0; } + ///< Check if an exit code was set, and VDR should exit. + int GetExitCode(void) { return exitCode >= 0 ? exitCode : 0; } + ///< Get the currently set exit code of VDR. + bool EmergencyExitRequested(void) { return emergencyExitRequested; } + ///< Returns true if an emergency exit was requested. + void RequestEmergencyExit(void); + ///< Requests an emergency exit of the VDR main loop. + void CheckManualStart(int ManualStart); + ///< Check whether the next timer is in ManualStart time window. + ///< If yes, assume non-interactive use. + void SetShutdownCommand(const char *ShutdownCommand); + ///< Set the command string for shutdown command. + void CallShutdownCommand(time_t WakeupTime, int Channel, const char *File, bool UserShutdown); + ///< Call the shutdown command with the given parameters. + bool IsUserInactive(time_t AtTime = 0) { return activeTimeout && activeTimeout <= (AtTime ? AtTime : time(NULL)); } + ///< Check whether VDR is in interactive mode or non-interactive mode (waiting for shutdown). + ///< AtTime checks whether VDR will probably be inactive at that time. + time_t GetUserInactiveTime(void) { return activeTimeout; } + ///< Time when user will become non-inactive, or 0 if never. + void SetUserInactiveTimeout(int Seconds = -1, bool Force = false); + ///< Set the time when VDR will switch into non-interactive mode or power down. + ///< -1 means Setup.MinUserInactivity in the future. + ///< Otherwise, seconds in the future. + ///< If MinUserInactivity = 0 and Force = false, Seconds is ignored and VDR will + ///< stay interactive forever. + void SetUserInactive(void) { SetUserInactiveTimeout(0, true); } + ///< Set VDR manually into non-interactive mode. + bool Retry(time_t AtTime = 0) { return retry <= (AtTime ? AtTime : time(NULL)); } + ///< Check whether its time to re-try the shutdown. + ///< AtTime checks whether VDR will probably be inactive at that time. + time_t GetRetry(void) { return retry; } + ///< Time when shutdown retry block ends. + void SetRetry(int Seconds) { retry = time(NULL) + Seconds; } + ///< Set shutdown retry so that VDR will not try to automatically shut down + ///< within Seconds. + bool ConfirmShutdown(bool Ask); + ///< Check for background activity that blocks shutdown. + ///< Returns immediately and without user interaction if Ask = false. + ///< Asks for confirmation if Ask = true. + ///< Returns true if ready for shutdown. + bool ConfirmRestart(bool Ask); + ///< Check for background activity that blocks restart. + ///< Returns immediately and without user interaction if Ask = false. + ///< Asks for confirmation if Ask = true. + ///< Returns true if ready for restart. + bool DoShutdown(bool Force); + ///< Call the shutdown script with data of the next pending timer. + ///< Fails if Force = false and a timer is running or within MinEventTimeout. + ///< Always calls shutdown on Force = true. + ///< Returns true on success. + }; + +extern cShutdownHandler ShutdownHandler; + +#endif diff --git a/thread.c b/thread.c index 05f1d016..8882252a 100644 --- a/thread.c +++ b/thread.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: thread.c 1.59 2007/01/07 14:44:22 kls Exp $ + * $Id: thread.c 1.60 2007/02/24 16:13:33 kls Exp $ */ #include "thread.h" @@ -200,7 +200,6 @@ void cMutex::Unlock(void) // --- cThread --------------------------------------------------------------- tThreadId cThread::mainThreadId = 0; -bool cThread::emergencyExitRequested = false; cThread::cThread(const char *Description) { @@ -320,14 +319,6 @@ void cThread::Cancel(int WaitSeconds) } } -bool cThread::EmergencyExit(bool Request) -{ - if (!Request) - return emergencyExitRequested; - esyslog("initiating emergency exit"); - return emergencyExitRequested = true; // yes, it's an assignment, not a comparison! -} - tThreadId cThread::ThreadId(void) { return syscall(__NR_gettid); @@ -505,7 +496,7 @@ int cPipe::Close(void) // --- SystemExec ------------------------------------------------------------ -int SystemExec(const char *Command) +int SystemExec(const char *Command, bool Detached) { pid_t pid; @@ -515,14 +506,24 @@ int SystemExec(const char *Command) } if (pid > 0) { // parent process - int status; - if (waitpid(pid, &status, 0) < 0) { + int status = 0; + if (!Detached && waitpid(pid, &status, 0) < 0) { LOG_ERROR; return -1; } return status; } else { // child process + if (Detached) { + // Start a new session + pid_t sid = setsid(); + if (sid < 0) + LOG_ERROR; + // close STDIN and re-open as /dev/null + int devnull = open("/dev/null", O_RDONLY); + if (devnull < 0 || dup2(devnull, 0) < 0) + LOG_ERROR; + } int MaxPossibleFileDescriptors = getdtablesize(); for (int i = STDERR_FILENO + 1; i < MaxPossibleFileDescriptors; i++) close(i); //close all dup'ed filedescriptors diff --git a/thread.h b/thread.h index dc45a83b..6eaa79a7 100644 --- a/thread.h +++ b/thread.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: thread.h 1.38 2007/01/07 14:44:38 kls Exp $ + * $Id: thread.h 1.39 2007/02/24 16:13:28 kls Exp $ */ #ifndef __THREAD_H @@ -84,7 +84,6 @@ private: cMutex mutex; char *description; static tThreadId mainThreadId; - static bool emergencyExitRequested; static void *StartThread(cThread *Thread); protected: void SetPriority(int Priority); @@ -118,7 +117,6 @@ public: ///< If the thread is already running, nothing happens. bool Active(void); ///< Checks whether the thread is still alive. - static bool EmergencyExit(bool Request = false); static tThreadId ThreadId(void); static tThreadId IsMainThread(void) { return ThreadId() == mainThreadId; } static void SetMainThreadId(void); @@ -175,7 +173,9 @@ public: // SystemExec() implements a 'system()' call that closes all unnecessary file // descriptors in the child process. +// With Detached=true, calls command in background and in a separate session, +// with stdin connected to /dev/null. -int SystemExec(const char *Command); +int SystemExec(const char *Command, bool Detached = false); #endif //__THREAD_H diff --git a/vdr.1 b/vdr.1 index 5d2aa90c..1fa65392 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.28 2007/01/07 14:03:56 kls Exp $ +.\" $Id: vdr.1 1.29 2007/02/24 17:40:20 kls Exp $ .\" .TH vdr 1 "07 Jan 2007" "1.4.5" "Video Disk Recorder" .SH NAME @@ -153,6 +153,14 @@ Print version information and exit. .BI \-w\ sec ,\ \-\-watchdog= sec Activate the watchdog timer with a timeout of \fIsec\fR seconds. A value of \fB0\fR (default) disables the watchdog. +.SH SIGNALS +.TP +.B SIGINT, SIGTERM +Program exits with status 0. +.TP +.B SIGHUP +Program exits with status 1. This can be used to force a reload, for example +if an update has been installed. .SH EXIT STATUS .TP .B 0 diff --git a/vdr.c b/vdr.c index 2416442f..2254e88a 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.283 2007/01/07 14:46:14 kls Exp $ + * $Id: vdr.c 1.284 2007/02/25 10:56:29 kls Exp $ */ #include @@ -54,6 +54,7 @@ #include "plugin.h" #include "rcu.h" #include "recording.h" +#include "shutdown.h" #include "skinclassic.h" #include "skinsttng.h" #include "sources.h" @@ -66,20 +67,23 @@ #define MINCHANNELWAIT 10 // seconds to wait between failed channel switchings #define ACTIVITYTIMEOUT 60 // seconds before starting housekeeping #define SHUTDOWNWAIT 300 // seconds to wait in user prompt before automatic shutdown +#define SHUTDOWNRETRY 360 // seconds before trying again to shut down +#define SHUTDOWNFORCEPROMPT 5 // seconds to wait in user prompt to allow forcing shutdown +#define SHUTDOWNCANCELROMPT 5 // seconds to wait in user prompt to allow canceling shutdown +#define RESTARTCANCELPROMPT 5 // seconds to wait in user prompt before restarting on SIGHUP #define MANUALSTART 600 // seconds the next timer must be in the future to assume manual start #define CHANNELSAVEDELTA 600 // seconds before saving channels.conf after automatic modifications #define DEVICEREADYTIMEOUT 30 // seconds to wait until all devices are ready #define MENUTIMEOUT 120 // seconds of user inactivity after which an OSD display is closed -#define SHUTDOWNRETRY 300 // seconds before trying again to shut down #define TIMERCHECKDELTA 10 // seconds between checks for timers that need to see their channel #define TIMERDEVICETIMEOUT 8 // seconds before a device used for timer check may be reused #define TIMERLOOKAHEADTIME 60 // seconds before a non-VPS timer starts and the channel is switched if possible #define VPSLOOKAHEADTIME 24 // hours within which VPS timers will make sure their events are up to date #define VPSUPTODATETIME 3600 // seconds before the event or schedule of a VPS timer needs to be refreshed -#define EXIT(v) { ExitCode = (v); goto Exit; } +#define EXIT(v) { ShutdownHandler.Exit(v); goto Exit; } -static int Interrupted = 0; +static int LastSignal = 0; static bool SetUser(const char *UserName) { @@ -138,10 +142,18 @@ static bool SetKeepCaps(bool On) static void SignalHandler(int signum) { - if (signum != SIGPIPE) { - Interrupted = signum; - Interface->Interrupt(); - } + isyslog("caught signal %d", signum); + switch (signum) { + case SIGPIPE: + break; + case SIGHUP: + LastSignal = signum; + break; + default: + LastSignal = signum; + Interface->Interrupt(); + ShutdownHandler.Exit(0); + } signal(signum, SignalHandler); } @@ -184,7 +196,6 @@ int main(int argc, char *argv[]) bool MuteAudio = false; int WatchdogTimeout = DEFAULTWATCHDOG; const char *Terminal = NULL; - const char *Shutdown = NULL; bool UseKbd = true; const char *LircDevice = NULL; @@ -205,7 +216,6 @@ int main(int argc, char *argv[]) #endif cPluginManager PluginManager(DEFAULTPLUGINDIR); - int ExitCode = 0; static struct option long_options[] = { { "audio", required_argument, NULL, 'a' }, @@ -313,7 +323,7 @@ int main(int argc, char *argv[]) break; case 'r': cRecordingUserCommand::SetCommand(optarg); break; - case 's': Shutdown = optarg; + case 's': ShutdownHandler.SetShutdownCommand(optarg); break; case 't': Terminal = optarg; if (access(Terminal, R_OK | W_OK) < 0) { @@ -498,10 +508,7 @@ int main(int argc, char *argv[]) int PreviousChannel[2] = { 1, 1 }; int PreviousChannelIndex = 0; time_t LastChannelChanged = time(NULL); - time_t LastActivity = 0; int MaxLatencyTime = 0; - bool ForceShutdown = false; - bool UserShutdown = false; bool InhibitEpgScan = false; bool IsInfoMenu = false; cSkin *CurrentSkin = NULL; @@ -596,6 +603,10 @@ int main(int argc, char *argv[]) } } + // Check for timers in automatic start time window: + + ShutdownHandler.CheckManualStart(MANUALSTART); + // User interface: Interface = new cInterface(SVDRPport); @@ -668,12 +679,7 @@ int main(int argc, char *argv[]) #define DELETE_MENU ((IsInfoMenu &= (Menu == NULL)), delete Menu, Menu = NULL) - while (!Interrupted) { - // Handle emergency exits: - if (cThread::EmergencyExit()) { - esyslog("emergency exit requested - shutting down"); - break; - } + while (!ShutdownHandler.DoExit()) { #ifdef DEBUGRINGBUFFERS cRingBufferLinear::PrintDebugRBL(); #endif @@ -856,9 +862,13 @@ int main(int argc, char *argv[]) // User Input: cOsdObject *Interact = Menu ? Menu : cControl::Control(); eKeys key = Interface->GetKey(!Interact || !Interact->NeedsFastResponse()); - if (NORMALKEY(key) != kNone) { + if (ISREALKEY(key)) { EITScanner.Activity(); - LastActivity = time(NULL); + // Cancel shutdown countdown: + if (ShutdownHandler.countdown) + ShutdownHandler.countdown.Cancel(); + // Set user active for MinUserInactivity time in the future: + ShutdownHandler.SetUserInactiveTimeout(); } // Keys that must work independent of any interactive mode: switch (key) { @@ -996,37 +1006,32 @@ int main(int argc, char *argv[]) } break; // Power off: - case kPower: { + case kPower: isyslog("Power button pressed"); DELETE_MENU; - if (!Shutdown) { - Skins.Message(mtError, tr("Can't shutdown - option '-s' not given!")); + // Check for activity, request power button again if active: + if (!ShutdownHandler.ConfirmShutdown(false) && Skins.Message(mtWarning, tr("VDR will shut down later - press Power to force"), SHUTDOWNFORCEPROMPT) != kPower) { + // Not pressed power - set VDR to be non-interactive and power down later: + ShutdownHandler.SetUserInactive(); break; } - LastActivity = 1; // not 0, see below! - UserShutdown = true; - if (cRecordControls::Active()) { - if (!Interface->Confirm(tr("Recording - shut down anyway?"))) - break; - } - if (cPluginManager::Active(tr("shut down anyway?"))) + // No activity or power button pressed twice - ask for confirmation: + if (!ShutdownHandler.ConfirmShutdown(true)) { + // Non-confirmed background activity - set VDR to be non-interactive and power down later: + ShutdownHandler.SetUserInactive(); break; - if (!cRecordControls::Active()) { - cTimer *timer = Timers.GetNextActiveTimer(); - time_t Next = timer ? timer->StartTime() : 0; - time_t Delta = timer ? Next - time(NULL) : 0; - if (Next && Delta <= Setup.MinEventTimeout * 60) { - char *buf; - asprintf(&buf, tr("Recording in %ld minutes, shut down anyway?"), Delta / 60); - bool confirm = Interface->Confirm(buf); - free(buf); - if (!confirm) - break; - } } - ForceShutdown = true; + // Ask the final question: + if (!Interface->Confirm(tr("Press any key to cancel shutdown"), SHUTDOWNCANCELROMPT, true)) + // If final question was canceled, continue to be active: + break; + // Ok, now call the shutdown script: + ShutdownHandler.DoShutdown(true); + // Set VDR to be non-interactive and power down again later: + ShutdownHandler.SetUserInactive(); + // Do not attempt to automatically shut down for a while: + ShutdownHandler.SetRetry(SHUTDOWNRETRY); break; - } default: break; } Interact = Menu ? Menu : cControl::Control(); // might have been closed in the mean time @@ -1041,7 +1046,7 @@ int main(int argc, char *argv[]) continue; } } - else if (time(NULL) - LastActivity > MENUTIMEOUT) + else if (time(NULL) - cRemote::LastActivity() > MENUTIMEOUT) state = osEnd; } switch (state) { @@ -1143,77 +1148,55 @@ int main(int argc, char *argv[]) Skins.Message(mtInfo, tr("Editing process finished")); } } - if (!Interact && ((!cRecordControls::Active() && !cCutter::Active() && (!Interface->HasSVDRPConnection() || UserShutdown)) || ForceShutdown)) { - time_t Now = time(NULL); - if (Now - LastActivity > ACTIVITYTIMEOUT) { - // Shutdown: - if (Shutdown && (Setup.MinUserInactivity || LastActivity == 1) && Now - LastActivity > Setup.MinUserInactivity * 60) { - cTimer *timer = Timers.GetNextActiveTimer(); - time_t Next = timer ? timer->StartTime() : 0; - time_t Delta = timer ? Next - Now : 0; - if (!LastActivity) { - if (!timer || Delta > MANUALSTART) { - // Apparently the user started VDR manually - dsyslog("assuming manual start of VDR"); - LastActivity = Now; - continue; // don't run into the actual shutdown procedure below - } - else - LastActivity = 1; - } - if (timer && Delta < Setup.MinEventTimeout * 60 && ForceShutdown) { - Delta = Setup.MinEventTimeout * 60; - Next = Now + Delta; - timer = NULL; - dsyslog("reboot at %s", *TimeToString(Next)); - } - if (!ForceShutdown && cPluginManager::Active()) { - LastActivity = Now - Setup.MinUserInactivity * 60 + SHUTDOWNRETRY; // try again later - continue; - } - if (!Next || Delta > Setup.MinEventTimeout * 60 || ForceShutdown) { - ForceShutdown = false; - if (timer) - dsyslog("next timer event at %s", *TimeToString(Next)); - if (WatchdogTimeout > 0) - signal(SIGALRM, SIG_IGN); - if (Interface->Confirm(tr("Press any key to cancel shutdown"), UserShutdown ? 5 : SHUTDOWNWAIT, true)) { - cControl::Shutdown(); - int Channel = timer ? timer->Channel()->Number() : 0; - const char *File = timer ? timer->File() : ""; - if (timer) - Delta = Next - time(NULL); // compensates for Confirm() timeout - char *cmd; - asprintf(&cmd, "%s %ld %ld %d \"%s\" %d", Shutdown, Next, Delta, Channel, *strescape(File, "\"$"), UserShutdown); - isyslog("executing '%s'", cmd); - SystemExec(cmd); - free(cmd); - LastActivity = time(NULL) - Setup.MinUserInactivity * 60 + SHUTDOWNRETRY; // try again later - } - else { - LastActivity = Now; - if (WatchdogTimeout > 0) { - alarm(WatchdogTimeout); - if (signal(SIGALRM, Watchdog) == SIG_IGN) - signal(SIGALRM, SIG_IGN); - } - } - UserShutdown = false; - continue; // skip the rest of the housekeeping for now - } - } - // Disk housekeeping: - RemoveDeletedRecordings(); - cSchedules::Cleanup(); - // Plugins housekeeping: - PluginManager.Housekeeping(); - } + + // SIGHUP shall cause a restart: + if (LastSignal == SIGHUP) { + if (ShutdownHandler.ConfirmRestart(true) && Interface->Confirm(tr("Press any key to cancel restart"), RESTARTCANCELPROMPT, true)) + EXIT(1); + LastSignal = 0; } + + // Update the shutdown countdown: + if (ShutdownHandler.countdown && ShutdownHandler.countdown.Update()) { + if (!ShutdownHandler.ConfirmShutdown(false)) + ShutdownHandler.countdown.Cancel(); + } + + if (!Interact && !cRecordControls::Active() && !cCutter::Active() && !Interface->HasSVDRPConnection() && cRemote::LastActivity() > ACTIVITYTIMEOUT) { + // Handle housekeeping tasks + + // Shutdown: + // Check whether VDR will be ready for shutdown in SHUTDOWNWAIT seconds: + time_t Soon = time(NULL) + SHUTDOWNWAIT; + if (ShutdownHandler.IsUserInactive(Soon) && ShutdownHandler.Retry(Soon) && !ShutdownHandler.countdown) { + if (ShutdownHandler.ConfirmShutdown(false)) + // Time to shut down - start final countdown: + ShutdownHandler.countdown.Start(tr("VDR will shut down in %s minutes"), SHUTDOWNWAIT); // the placeholder is really %s! + // Dont try to shut down again for a while: + ShutdownHandler.SetRetry(SHUTDOWNRETRY); + } + // Countdown run down to 0? + if (ShutdownHandler.countdown.Done()) { + // Timed out, now do a final check: + if (ShutdownHandler.IsUserInactive() && ShutdownHandler.ConfirmShutdown(false)) + ShutdownHandler.DoShutdown(false); + // Do this again a bit later: + ShutdownHandler.SetRetry(SHUTDOWNRETRY); + } + + // Disk housekeeping: + RemoveDeletedRecordings(); + cSchedules::Cleanup(); + // Plugins housekeeping: + PluginManager.Housekeeping(); + } + // Main thread hooks of plugins: PluginManager.MainThreadHook(); } - if (Interrupted) - isyslog("caught signal %d", Interrupted); + + if (ShutdownHandler.EmergencyExitRequested()) + esyslog("emergency exit requested - shutting down"); Exit: @@ -1227,7 +1210,7 @@ Exit: Remotes.Clear(); Audios.Clear(); Skins.Clear(); - if (ExitCode != 2) { + if (ShutdownHandler.GetExitCode() != 2) { Setup.CurrentChannel = cDevice::CurrentChannel(); Setup.CurrentVolume = cDevice::CurrentVolume(); Setup.Save(); @@ -1238,14 +1221,12 @@ Exit: ReportEpgBugFixStats(); if (WatchdogTimeout > 0) dsyslog("max. latency time %d seconds", MaxLatencyTime); - isyslog("exiting"); + isyslog("exiting, exit code %d", ShutdownHandler.GetExitCode()); + if (ShutdownHandler.EmergencyExitRequested()) + esyslog("emergency exit!"); if (SysLogLevel > 0) closelog(); if (HasStdin) tcsetattr(STDIN_FILENO, TCSANOW, &savedTm); - if (cThread::EmergencyExit()) { - esyslog("emergency exit!"); - return 1; - } - return ExitCode; + return ShutdownHandler.GetExitCode(); }