1
0
mirror of https://github.com/vdr-projects/vdr.git synced 2025-03-01 10:50:46 +00:00
Klaus Schmidinger 8465398c6d Version 0.97
- Implemented a lock file to prevent more than one instance of VDR from removing
  files from the video directory at the same time.
- The new setup parameter SplitEditedFiles can be used to control whether or
  not the result of an editing process shall be written into separate files.
- Fixed handling repeat function when using LIRC (thanks to Matthias Weingart).
- The shutdown program (defined with the '-s' option) now also gets the channel
  number and recording title of the timer (see INSTALL).
- New channel data for 'Premiere One', 'Premiere X-Action', 'Fox Kids T�rkce'
  and 'N24' (thanks to Andreas Share, Ulrich R�der, Uwe Scheffler and Simon
  Bauschulte). Note that if you are using the default 'channels.conf' or
  'channels.conf.cable' files you may need to check any exiting timers to see
  whether they still use the correct channel number.
- Fixed the "EPG bugfix" (sometimes had duplicate information in Subtitle and
  Extended Description).
- Fixed checking for valid video device when setting the video mode.
- The external command 'sort' is no longer required. VDR now sorts the list of
  recordings itself, making sure that recordings that stem from repeating timers
  are sorted chronologically. Sorting is done according to the setting of the
  current locale, so you may want to make sure LC_COLLATE is set to the desired
  value (see INSTALL).
- Fixed handling 'newline' characters in EPG texts (thanks to Rolf Hakenes for
  an improved version of his 'libdtv').
  Newline characters are always mapped to a single "blank" in VDR, because they
  would otherwise disturb the Title and Subtitle layout in the channel display
  (where these are assumed to be single line texts) and would have to be
  specially handled in the 'epg.data' file and the LSTE command in SVDRP.
- Mapping ` ("backtick") characters in EPG texts to ' (single quote).
- Fixed timers starting and ending at unexpected times. 'localtime()' was not
  thread safe, now using localtime_r().
- Removed the "system time seen..." message.
- Fixed a bug in the replay mode display when pressing the Green or Yellow
  button while in trick mode (thanks to Stefan Huelswitt)
- Closing all open file descriptors when calling external programs.
- The menu timeout now also works when pressing the "Back" button during replay
  to enter the "Recordings" menu.
- Updated 'channels.conf' for the "Bundesliga" channels of Premiere World
  (thanks to Helmut Sch�chner).
- Fixed reading timers.conf and channels.conf that contain blanks after numeric
  values.
- Fixed handling trick modes near the beginning and end of a recording.
- Pressing the "Back" button while replaying a DVD now leads to the DVD menu.
2001-10-21 18:00:00 +02:00

549 lines
21 KiB
C

/*
* vdr.c: Video Disk Recorder main program
*
* Copyright (C) 2000 Klaus Schmidinger
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
* Or, point your browser to http://www.gnu.org/copyleft/gpl.html
*
* The author can be reached at kls@cadsoft.de
*
* The project's page is at http://www.cadsoft.de/people/kls/vdr
*
* $Id: vdr.c 1.86 2001/10/20 11:18:38 kls Exp $
*/
#include <getopt.h>
#include <locale.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include "config.h"
#include "dvbapi.h"
#ifdef DVDSUPPORT
#include "dvd.h"
#endif //DVDSUPPORT
#include "i18n.h"
#include "interface.h"
#include "menu.h"
#include "recording.h"
#include "tools.h"
#include "videodir.h"
#ifdef REMOTE_KBD
#define KEYS_CONF "keys-pc.conf"
#else
#define KEYS_CONF "keys.conf"
#endif
#define ACTIVITYTIMEOUT 60 // seconds before starting housekeeping
#define SHUTDOWNWAIT 300 // seconds to wait in user prompt before automatic shutdown
#define MANUALSTART 600 // seconds the next timer must be in the future to assume manual start
#define VOLUMEDELTA 5 // used to increase/decrease the volume
static int Interrupted = 0;
static void SignalHandler(int signum)
{
if (signum != SIGPIPE) {
Interrupted = signum;
Interface->Interrupt();
}
signal(signum, SignalHandler);
}
static void Watchdog(int signum)
{
// Something terrible must have happened that prevented the 'alarm()' from
// being called in time, so let's get out of here:
esyslog(LOG_ERR, "PANIC: watchdog timer expired - exiting!");
exit(1);
}
int main(int argc, char *argv[])
{
// Initiate locale:
setlocale(LC_ALL, "");
// Command line options:
#define DEFAULTSVDRPPORT 2001
#define DEFAULTWATCHDOG 0 // seconds
int SVDRPport = DEFAULTSVDRPPORT;
const char *ConfigDirectory = NULL;
bool DaemonMode = false;
int WatchdogTimeout = DEFAULTWATCHDOG;
const char *Terminal = NULL;
const char *Shutdown = NULL;
static struct option long_options[] = {
{ "audio", required_argument, NULL, 'a' },
{ "config", required_argument, NULL, 'c' },
{ "daemon", no_argument, NULL, 'd' },
{ "device", required_argument, NULL, 'D' },
{ "epgfile", required_argument, NULL, 'E' },
{ "help", no_argument, NULL, 'h' },
{ "log", required_argument, NULL, 'l' },
{ "port", required_argument, NULL, 'p' },
{ "record", required_argument, NULL, 'r' },
{ "shutdown", required_argument, NULL, 's' },
{ "terminal", required_argument, NULL, 't' },
{ "video", required_argument, NULL, 'v' },
{ "dvd", required_argument, NULL, 'V' },
{ "watchdog", required_argument, NULL, 'w' },
{ NULL }
};
int c;
int option_index = 0;
while ((c = getopt_long(argc, argv, "a:c:dD:E:hl:p:r:s:t:v:V:w:", long_options, &option_index)) != -1) {
switch (c) {
case 'a': cDvbApi::SetAudioCommand(optarg);
break;
case 'c': ConfigDirectory = optarg;
break;
case 'd': DaemonMode = true; break;
case 'D': if (isnumber(optarg)) {
int n = atoi(optarg);
if (0 <= n && n < MAXDVBAPI) {
cDvbApi::SetUseDvbApi(n);
break;
}
}
fprintf(stderr, "vdr: invalid DVB device number: %s\n", optarg);
return 2;
break;
case 'E': cSIProcessor::SetEpgDataFileName(*optarg != '-' ? optarg : NULL);
break;
case 'h': printf("Usage: vdr [OPTION]\n\n" // for easier orientation, this is column 80|
" -a CMD, --audio=CMD send Dolby Digital audio to stdin of command CMD\n"
" -c DIR, --config=DIR read config files from DIR (default is to read them\n"
" from the video directory)\n"
" -d, --daemon run in daemon mode\n"
" -D NUM, --device=NUM use only the given DVB device (NUM = 0, 1, 2...)\n"
" there may be several -D options (default: all DVB\n"
" devices will be used)\n"
" -E FILE --epgfile=FILE write the EPG data into the given FILE (default is\n"
" %s); use '-E-' to disable this\n"
" if FILE is a directory, the default EPG file will be\n"
" created in that directory\n"
" -h, --help display this help and exit\n"
" -l LEVEL, --log=LEVEL set log level (default: 3)\n"
" 0 = no logging, 1 = errors only,\n"
" 2 = errors and info, 3 = errors, info and debug\n"
" -p PORT, --port=PORT use PORT for SVDRP (default: %d)\n"
" 0 turns off SVDRP\n"
" -r CMD, --record=CMD call CMD before and after a recording\n"
" -s CMD, --shutdown=CMD call CMD to shutdown the computer\n"
" -t TTY, --terminal=TTY controlling tty\n"
" -v DIR, --video=DIR use DIR as video directory (default: %s)\n"
" -V DEV, --dvd=DEV use DEV as the DVD device (default: %s)\n"
" -w SEC, --watchdog=SEC activate the watchdog timer with a timeout of SEC\n"
" seconds (default: %d); '0' disables the watchdog\n"
"\n"
"Report bugs to <vdr-bugs@cadsoft.de>\n",
cSIProcessor::GetEpgDataFileName() ? cSIProcessor::GetEpgDataFileName() : "'-'",
DEFAULTSVDRPPORT,
VideoDirectory,
#ifdef DVDSUPPORT
cDVD::DeviceName(),
#else
"no DVD support",
#endif //DVDSUPPORT
DEFAULTWATCHDOG
);
return 0;
break;
case 'l': if (isnumber(optarg)) {
int l = atoi(optarg);
if (0 <= l && l <= 3) {
SysLogLevel = l;
break;
}
}
fprintf(stderr, "vdr: invalid log level: %s\n", optarg);
return 2;
break;
case 'p': if (isnumber(optarg))
SVDRPport = atoi(optarg);
else {
fprintf(stderr, "vdr: invalid port number: %s\n", optarg);
return 2;
}
break;
case 'r': cRecordingUserCommand::SetCommand(optarg);
break;
case 's': Shutdown = optarg;
break;
case 't': Terminal = optarg;
break;
case 'v': VideoDirectory = optarg;
while (optarg && *optarg && optarg[strlen(optarg) - 1] == '/')
optarg[strlen(optarg) - 1] = 0;
break;
case 'V':
#ifdef DVDSUPPORT
cDVD::SetDeviceName(optarg);
if (!cDVD::DriveExists()) {
fprintf(stderr, "vdr: DVD drive not found: %s\n", optarg);
return 2;
}
#else
fprintf(stderr, "vdr: DVD support has not been compiled in!");
return 2;
#endif //DVDSUPPORT
break;
case 'w': if (isnumber(optarg)) {
int t = atoi(optarg);
if (t >= 0) {
WatchdogTimeout = t;
break;
}
}
fprintf(stderr, "vdr: invalid watchdog timeout: %s\n", optarg);
return 2;
break;
default: return 2;
}
}
// Log file:
if (SysLogLevel > 0)
openlog("vdr", LOG_PID | LOG_CONS, LOG_USER);
// Check the video directory:
if (!DirectoryOk(VideoDirectory, true)) {
fprintf(stderr, "vdr: can't access video directory %s\n", VideoDirectory);
return 2;
}
// Daemon mode:
if (DaemonMode) {
#if !defined(DEBUG_OSD) && !defined(REMOTE_KBD)
pid_t pid = fork();
if (pid < 0) {
fprintf(stderr, "%m\n");
esyslog(LOG_ERR, "ERROR: %m");
return 2;
}
if (pid != 0)
return 0; // initial program immediately returns
fclose(stdin);
fclose(stdout);
fclose(stderr);
#else
fprintf(stderr, "vdr: can't run in daemon mode with DEBUG_OSD or REMOTE_KBD on!\n");
return 2;
#endif
}
else if (Terminal) {
// Claim new controlling terminal
stdin = freopen(Terminal, "r", stdin);
stdout = freopen(Terminal, "w", stdout);
stderr = freopen(Terminal, "w", stderr);
}
isyslog(LOG_INFO, "VDR version %s started", VDRVERSION);
// Configuration data:
if (!ConfigDirectory)
ConfigDirectory = VideoDirectory;
Setup.Load(AddDirectory(ConfigDirectory, "setup.conf"));
Channels.Load(AddDirectory(ConfigDirectory, "channels.conf"));
Timers.Load(AddDirectory(ConfigDirectory, "timers.conf"));
Commands.Load(AddDirectory(ConfigDirectory, "commands.conf"));
#if defined(REMOTE_LIRC)
Keys.SetDummyValues();
#elif !defined(REMOTE_NONE)
bool KeysLoaded = Keys.Load(AddDirectory(ConfigDirectory, KEYS_CONF));
#endif
// DVB interfaces:
if (!cDvbApi::Init())
return 2;
cDvbApi::SetPrimaryDvbApi(Setup.PrimaryDVB);
Channels.SwitchTo(Setup.CurrentChannel);
cDvbApi::PrimaryDvbApi->SetVolume(Setup.CurrentVolume, true);
cEITScanner EITScanner;
// User interface:
Interface = new cInterface(SVDRPport);
#if !defined(REMOTE_LIRC) && !defined(REMOTE_NONE)
if (!KeysLoaded)
Interface->LearnKeys();
#endif
// Signal handlers:
if (signal(SIGHUP, SignalHandler) == SIG_IGN) signal(SIGHUP, SIG_IGN);
if (signal(SIGINT, SignalHandler) == SIG_IGN) signal(SIGINT, SIG_IGN);
if (signal(SIGTERM, SignalHandler) == SIG_IGN) signal(SIGTERM, SIG_IGN);
if (signal(SIGPIPE, SignalHandler) == SIG_IGN) signal(SIGPIPE, SIG_IGN);
if (WatchdogTimeout > 0)
if (signal(SIGALRM, Watchdog) == SIG_IGN) signal(SIGALRM, SIG_IGN);
// Main program loop:
cOsdBase *Menu = NULL;
cReplayControl *ReplayControl = NULL;
int LastChannel = -1;
int PreviousChannel = cDvbApi::CurrentChannel();
time_t LastActivity = 0;
time_t LinearTime = time(NULL);
int MaxLatencyTime = 0;
bool ForceShutdown = false;
if (WatchdogTimeout > 0) {
dsyslog(LOG_INFO, "setting watchdog timer to %d seconds", WatchdogTimeout);
alarm(WatchdogTimeout); // Initial watchdog timer start
}
while (!Interrupted) {
// Test if we are running in the Einstein continuum:
time_t Now = time(NULL);
time_t LinearDelta = Now - LinearTime;
if (LinearDelta) {
if (LinearDelta < 0 || LinearDelta > 300) // assuming nothing will block for more than 5 minutes
esyslog(LOG_ERR, "ERROR: time warp detected (%d seconds)", LinearDelta);
LinearTime = Now;
}
// Handle emergency exits:
if (cThread::EmergencyExit()) {
esyslog(LOG_ERR, "emergency exit requested - shutting down");
break;
}
// Restart the Watchdog timer:
if (WatchdogTimeout > 0) {
int LatencyTime = WatchdogTimeout - alarm(WatchdogTimeout);
if (LatencyTime > MaxLatencyTime) {
MaxLatencyTime = LatencyTime;
dsyslog(LOG_INFO, "max. latency time %d seconds", MaxLatencyTime);
}
}
// Channel display:
if (!EITScanner.Active() && cDvbApi::CurrentChannel() != LastChannel) {
if (!Menu)
Menu = new cDisplayChannel(cDvbApi::CurrentChannel(), LastChannel > 0);
if (LastChannel > 0)
PreviousChannel = LastChannel;
LastChannel = cDvbApi::CurrentChannel();
}
// Timers and Recordings:
if (!Menu) {
time_t Now = time(NULL); // must do both following calls with the exact same time!
cRecordControls::Process(Now);
cTimer *Timer = Timers.GetMatch(Now);
if (Timer) {
if (!cRecordControls::Start(Timer))
Timer->SetPending(true);
}
}
// User Input:
cOsdBase **Interact = Menu ? &Menu : (cOsdBase **)&ReplayControl;
eKeys key = Interface->GetKey(!*Interact || !(*Interact)->NeedsFastResponse());
if (NORMALKEY(key) != kNone) {
EITScanner.Activity();
LastActivity = time(NULL);
}
// Keys that must work independent of any interactive mode:
switch (key) {
// Volume Control:
case kVolUp|k_Repeat:
case kVolUp:
case kVolDn|k_Repeat:
case kVolDn:
cDvbApi::PrimaryDvbApi->SetVolume(NORMALKEY(key) == kVolDn ? -VOLUMEDELTA : VOLUMEDELTA);
break;
case kMute:
cDvbApi::PrimaryDvbApi->ToggleMute();
break;
// Power off:
case kPower: isyslog(LOG_INFO, "Power button pressed");
DELETENULL(*Interact);
if (!Shutdown) {
Interface->Error(tr("Can't shutdown - option '-s' not given!"));
break;
}
if (cRecordControls::Active()) {
if (Interface->Confirm(tr("Recording - shut down anyway?")))
ForceShutdown = true;
}
LastActivity = 1; // not 0, see below!
break;
default:
if (*Interact) {
switch ((*Interact)->ProcessKey(key)) {
case osMenu: DELETENULL(Menu);
Menu = new cMenuMain(ReplayControl);
break;
case osRecord: DELETENULL(Menu);
if (!cRecordControls::Start())
Interface->Error(tr("No free DVB device to record!"));
break;
case osRecordings:
DELETENULL(Menu);
DELETENULL(ReplayControl);
Menu = new cMenuMain(ReplayControl, osRecordings);
break;
case osReplay: DELETENULL(Menu);
DELETENULL(ReplayControl);
ReplayControl = new cReplayControl;
break;
#ifdef DVDSUPPORT
case osDVD: DELETENULL(Menu);
DELETENULL(ReplayControl);
Menu = new cMenuMain(ReplayControl, osDVD);
break;
#endif //DVDSUPPORT
case osStopReplay:
DELETENULL(*Interact);
DELETENULL(ReplayControl);
break;
case osSwitchDvb:
DELETENULL(*Interact);
Interface->Info(tr("Switching primary DVB..."));
cDvbApi::SetPrimaryDvbApi(Setup.PrimaryDVB);
break;
case osBack:
case osEnd: DELETENULL(*Interact);
break;
default: ;
}
}
else {
// Key functions in "normal" viewing mode:
switch (key) {
// Toggle channels:
case k0: {
int CurrentChannel = cDvbApi::CurrentChannel();
Channels.SwitchTo(PreviousChannel);
PreviousChannel = CurrentChannel;
break;
}
// Direct Channel Select:
case k1 ... k9:
Menu = new cDisplayChannel(key);
break;
// Left/Right rotates trough channel groups:
case kLeft|k_Repeat:
case kLeft:
case kRight|k_Repeat:
case kRight:
Menu = new cDisplayChannel(NORMALKEY(key));
break;
// Up/Down Channel Select:
case kUp|k_Repeat:
case kUp:
case kDown|k_Repeat:
case kDown: {
int n = cDvbApi::CurrentChannel() + (NORMALKEY(key) == kUp ? 1 : -1);
cChannel *channel = Channels.GetByNumber(n);
if (channel)
channel->Switch();
break;
}
// Menu Control:
case kMenu: Menu = new cMenuMain(ReplayControl); break;
// Viewing Control:
case kOk: LastChannel = -1; break; // forces channel display
default: break;
}
}
}
if (!Menu) {
EITScanner.Process();
cVideoCutter::Active();
}
if (!*Interact && (!cRecordControls::Active() || ForceShutdown)) {
time_t Now = time(NULL);
if (Now - LastActivity > ACTIVITYTIMEOUT) {
// Shutdown:
if (Shutdown && Setup.MinUserInactivity && 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(LOG_INFO, "assuming manual start of VDR");
LastActivity = Now;
continue; // don't run into the actual shutdown procedure below
}
else
LastActivity = 1;
}
if (!Next || Delta > Setup.MinEventTimeout * 60 || ForceShutdown) {
ForceShutdown = false;
if (timer)
dsyslog(LOG_INFO, "next timer event at %s", ctime(&Next));
if (WatchdogTimeout > 0)
signal(SIGALRM, SIG_IGN);
if (Interface->Confirm(tr("Press any key to cancel shutdown"), LastActivity == 1 ? 5 : SHUTDOWNWAIT, true)) {
int Channel = timer ? timer->channel : 0;
const char *File = timer ? timer->file : "";
char *cmd;
asprintf(&cmd, "%s %ld %ld %d '%s'", Shutdown, Next, Delta, Channel, File);
isyslog(LOG_INFO, "executing '%s'", cmd);
SystemExec(cmd);
delete cmd;
}
else if (WatchdogTimeout > 0) {
alarm(WatchdogTimeout);
if (signal(SIGALRM, Watchdog) == SIG_IGN)
signal(SIGALRM, SIG_IGN);
}
LastActivity = Now; // don't try again too soon
continue; // skip the rest of the housekeeping for now
}
}
// Disk housekeeping:
RemoveDeletedRecordings();
}
}
}
if (Interrupted)
isyslog(LOG_INFO, "caught signal %d", Interrupted);
Setup.CurrentChannel = cDvbApi::CurrentChannel();
Setup.Save();
cVideoCutter::Stop();
delete Menu;
delete ReplayControl;
delete Interface;
cDvbApi::Cleanup();
if (WatchdogTimeout > 0)
dsyslog(LOG_INFO, "max. latency time %d seconds", MaxLatencyTime);
isyslog(LOG_INFO, "exiting");
if (SysLogLevel > 0)
closelog();
if (cThread::EmergencyExit()) {
esyslog(LOG_ERR, "emergency exit!");
return 1;
}
return 0;
}