diff --git a/BUGS b/BUGS index cd96197d..d24d3707 100644 --- a/BUGS +++ b/BUGS @@ -4,3 +4,5 @@ Video Disk Recorder - Known Bugs * Sometimes the picture "jumps" as if a frame is skipped. Presumably this is a problem in the card driver or firmware? +* The first picture captured with the GRAB command in SVDRP + is empty. Also, these pictures appear to be too dark. diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 91c9540e..db47d8b9 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -26,6 +26,7 @@ Guido Fiala Robert Schneider for implementing EIT support for displaying the current/next info + for extending EIT support to implement a complete EPG Niels de Carpentier for adding a workaround for a driver timing problem in cDvbApi::Cmd() diff --git a/HISTORY b/HISTORY index 717102a6..012f54d4 100644 --- a/HISTORY +++ b/HISTORY @@ -241,3 +241,28 @@ Video Disk Recorder Revision History - The "Blue" button in the "Main" menu can now be used to "Resume" a previously stopped replay session (suggested by Martin Hammerschmid). - The low and high LNB frequencies can now be changed in the "Setup" menu. + +2000-10-29: Version 0.67 + +- The EIT information is now gathered in a separate thread. +- While the "current/next" information is being displayed (either after switching + channels or due to pressing the "Ok" button in normal viewing mode), the "Green" + and "Yellow" button display detailed information about the current/next broad- + cast (if such information is available). +- The sytem time can now be synchronized to the time broadcast in the DVB data + stream. This can be enabled in the "Setup" menu by setting "SetSystemTime" to + 1. Note that this works only if VDR is running under a user id that has + permisson to set the system time. +- The new item "Schedule" in the "Main" menu opens VDR's EPG (thanks to Robert + Schneider). See the MANUAL file for a detailed description. +- The new setup parameters MarginStart and MarginStop define how long (in + minutes) before the official start time of a broadcast VDR shall begin + recording, and how long after the official end time it shall stop recording. + These are used when a recording is programmed from the "Schedules" menu. +- The delay value in the dvb.c.071.diff patch to the driver has been increased + to '3', because on some systems the OSD was not displayed correctly. If you + are running an already patched version 0.71 driver and encounter problems + with the OSD, please make sure the parameter in the ddelay call is '3', not + '2'. +- Fixed initializing the RCU remote control code (didn't work after switching + on the system). diff --git a/MANUAL b/MANUAL index 9b864e36..9f0c2d68 100644 --- a/MANUAL +++ b/MANUAL @@ -53,20 +53,60 @@ Video Disk Recorder User's Manual At any point in the menu system, pressing the "Menu" key again will immediately leave the menu system (discarding any pending changes). +* The "Schedule" Menu + + The "Schedule" menu implements VDR's "Electronic Program Guide" (EPG). + + Select "Schedule" from the "Main" menu and you get a list of all upcoming + broadcasts on the current channel. + + "Up" and "Down" can be used to scroll through this list, and pressing "Ok" + displays detailed information about the selected programme. Pressing "Ok" + again (or pressing "Back") gets you back into the "Schedule" menu. + + From the "Schedule" menu, the "Green" button opens the "What's on now?" + menu, which displays all programmes that are currently running on all + channels that broadcast their programme information on the current + transponder, or from channels that have been current lately (VDR stores + all information it gathers in an internal list). The more channels you + have been switching through lately, the longer this list will be. + The "Yellow" button opens the "What's on next?" menu, which lists all + programmes that will start next on all channels. + + Inside the "What's on now/next?" menus the "Green" button toggles between + the "Now" and "Next" display, and the "Yellow" button gets you back to the + "Schedule" menu of the current channel. + + The "Red" button allows you to instantly program a timer to record the + selected programme. You will get into the "Edit Timer" menu in which + everything has already been filled in, and you can make any modifications + you may want to apply. Note that the Start and Stop time are offset by the + MarginStart and MarginStop parameters (see Setup) in order to make sure the + entire programme is recorded in case it doesn't exactly adhere to its + published start/stop times. Of course, no guarantee can be given that the + default margin values will be sufficient, so in case this recording is + really important you may want to add an extra margin ;-) + + The "Blue" button can be pressed to switch to the channel with the selected + programme. + * Selecting a Channel - There are three ways to select a channel: + There are four ways to select a channel: 1. With no On Screen Menu displayed press the "Up" or "Down" key to switch to the next higher or lower channel. 2. Press the "Menu" button to bring up the On Screen Menu, select "Channels" and browse through the list with the "Up" and "Down" key; to switch to the selected channel press "Ok". - 2. Directly type in the channel number with the numeric keys ('0'..'9'); + 3. Directly type in the channel number with the numeric keys ('0'..'9'); if no key is pressed for about half a second, the digits collected so far will define the channel number. + 4. From the "Now", "Next" and "Event" menus (accessible through the "Schedule" + menu) by pressing the "Blue" button. - Pressing the '0' key toggles between the current and the previous channel. + Pressing the '0' key in normal viewing mode toggles between the current and + the previous channel. After switching to a different channel the channel number and name, as well as the current time are displayed at the top of the screen. If available, the @@ -75,6 +115,11 @@ Video Disk Recorder User's Manual To bring up the channel display without switching channels you can press the "Ok" button. + When the "current/next" information is being displayed, pressing the "Green" + button will display the detailed information about the current broadcast, + if such information is available. The "Yellow" button will do the same thing + for the next broadcast. + * Switching through channel groups If the 'channels.conf' file contains "group separators" you can switch @@ -178,6 +223,9 @@ Video Disk Recorder User's Manual If this field is left blank, the channel name will be used to form the name of the recording. + A timer can also be programmed by pressing the "Red" button on the "Schedule", + "Now", "Next" or "Event" menus. + * Parameters in the "Setup" menu Select "Setup" from the "Main" menu to enter the setup menu. From there you can @@ -216,3 +264,13 @@ Video Disk Recorder User's Manual LnbFrequLo = 9750 The low and high LNB frequencies (in MHz) LnbFrequHi = 10600 + + SetSystemTime = 0 Defines whether the system time will be set according to + the time received from the DVB data stream. + 0 = system time will not be set + 1 = system time wil be set + Note that this works only if VDR is running under a user + id that has permisson to set the system time. + MarginStart = 2 Defines how many minutes before the official start time + MarginStop = 10 of a broadcast VDR shall start recording, and how long + after the official end time it shall stop recording. diff --git a/Makefile b/Makefile index 8b32be2d..9db79f39 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.13 2000/10/07 16:24:08 kls Exp $ +# $Id: Makefile 1.14 2000/10/28 16:24:16 kls Exp $ DVBDIR = ../DVB @@ -35,20 +35,20 @@ font: genfontfile fontosd.c # Dependencies: -config.o : config.c config.h dvbapi.h dvbosd.h eit.h font.h interface.h svdrp.h tools.h -dvbapi.o : dvbapi.c config.h dvbapi.h dvbosd.h font.h interface.h svdrp.h tools.h videodir.h +config.o : config.c config.h dvbapi.h dvbosd.h eit.h font.h interface.h remote.h svdrp.h thread.h tools.h +dvbapi.o : dvbapi.c config.h dvbapi.h dvbosd.h eit.h font.h interface.h remote.h svdrp.h thread.h tools.h videodir.h dvbosd.o : dvbosd.c dvbosd.h font.h tools.h -eit.o : eit.c eit.h tools.h +eit.o : eit.c eit.h thread.h tools.h font.o : font.c font.h fontosd.c tools.h interface.o: interface.c config.h dvbapi.h dvbosd.h eit.h font.h interface.h remote.h svdrp.h thread.h tools.h -menu.o : menu.c config.h dvbapi.h dvbosd.h font.h interface.h menu.h osd.h recording.h svdrp.h tools.h -osd.o : osd.c config.h dvbapi.h dvbosd.h font.h interface.h osd.h svdrp.h tools.h -recording.o: recording.c config.h dvbapi.h dvbosd.h font.h interface.h recording.h svdrp.h tools.h videodir.h -remote.o : remote.c config.h dvbapi.h dvbosd.h font.h remote.h thread.h tools.h -svdrp.o : svdrp.c config.h dvbapi.h dvbosd.h font.h interface.h svdrp.h tools.h +menu.o : menu.c config.h dvbapi.h dvbosd.h eit.h font.h interface.h menu.h osd.h recording.h remote.h svdrp.h thread.h tools.h +osd.o : osd.c config.h dvbapi.h dvbosd.h eit.h font.h interface.h osd.h remote.h svdrp.h thread.h tools.h +recording.o: recording.c config.h dvbapi.h dvbosd.h eit.h font.h interface.h recording.h remote.h svdrp.h thread.h tools.h videodir.h +remote.o : remote.c config.h dvbapi.h dvbosd.h eit.h font.h remote.h thread.h tools.h +svdrp.o : svdrp.c config.h dvbapi.h dvbosd.h eit.h font.h interface.h remote.h svdrp.h thread.h tools.h thread.o : thread.c thread.h tools.o : tools.c tools.h -vdr.o : vdr.c config.h dvbapi.h dvbosd.h font.h interface.h menu.h osd.h recording.h svdrp.h tools.h videodir.h +vdr.o : vdr.c config.h dvbapi.h dvbosd.h eit.h font.h interface.h menu.h osd.h recording.h remote.h svdrp.h thread.h tools.h videodir.h videodir.o : videodir.c tools.h videodir.h # The main program: diff --git a/config.c b/config.c index 1714651f..18234f53 100644 --- a/config.c +++ b/config.c @@ -4,18 +4,15 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: config.c 1.26 2000/10/08 16:10:40 kls Exp $ + * $Id: config.c 1.27 2000/10/29 13:04:37 kls Exp $ */ #include "config.h" #include #include #include "dvbapi.h" -#include "eit.h" #include "interface.h" -extern cEIT EIT; - // -- cKeys ------------------------------------------------------------------ tKey keyTable[] = { // "Up" and "Down" must be the first two keys! @@ -267,10 +264,8 @@ bool cChannel::Switch(cDvbApi *DvbApi) isyslog(LOG_INFO, "switching to channel %d", number); CurrentChannel = number; for (int i = 3; i--;) { - if (DvbApi->SetChannel(frequency, polarization, diseqc, srate, vpid, apid, ca, pnr)) { - EIT.SetProgramNumber(pnr); + if (DvbApi->SetChannel(frequency, polarization, diseqc, srate, vpid, apid, ca, pnr)) return true; - } esyslog(LOG_ERR, "retrying"); } return false; @@ -306,6 +301,47 @@ cTimer::cTimer(bool Instant) snprintf(file, sizeof(file), "%s%s", Setup.MarkInstantRecord ? "@" : "", ch->name); } +cTimer::cTimer(const cEventInfo *EventInfo) +{ + startTime = stopTime = 0; + recording = false; + active = true; + cChannel *ch = Channels.GetByServiceID(EventInfo->GetServiceID()); + channel = ch ? ch->number : 0; + time_t tstart = EventInfo->GetTime(); + time_t tstop = tstart + EventInfo->GetDuration() + Setup.MarginStop * 60; + tstart -= Setup.MarginStart * 60; + struct tm *time = localtime(&tstart); + day = time->tm_mday; + start = time->tm_hour * 100 + time->tm_min; + time = localtime(&tstop); + stop = time->tm_hour * 100 + time->tm_min; + if (stop >= 2400) + stop -= 2400; + priority = 99; + lifetime = 99; + *file = 0; + const char *Title = EventInfo->GetTitle(); + if (!isempty(Title)) + strn0cpy(file, EventInfo->GetTitle(), sizeof(file)); + summary = NULL; + const char *Subtitle = EventInfo->GetSubtitle(); + if (isempty(Subtitle)) + Subtitle = ""; + const char *Summary = EventInfo->GetExtendedDescription(); + if (isempty(Summary)) + Summary = ""; + if (*Subtitle || *Summary) { + asprintf(&summary, "%s%s%s", Subtitle, (*Subtitle && *Summary) ? "\n\n" : "", Summary); + char *p = summary; + while (*p) { + if (*p == '\n') + *p = '|'; + p++; + } + } +} + cTimer::~cTimer() { delete summary; @@ -388,14 +424,33 @@ bool cTimer::Parse(const char *s) char *buffer2 = NULL; delete summary; summary = NULL; + //XXX Apparently sscanf() doesn't work correctly if the last %a argument + //XXX results in an empty string (this firt occured when the EIT gathering + //XXX was put into a separate thread - don't know why this happens... + //XXX As a cure we copy the original string and add a blank. + //XXX If anybody can shed some light on why sscanf() failes here, I'd love + //XXX to hear about that! + char *s2 = NULL; + int l2 = strlen(s); + if (s[l2 - 2] == ':') { // note that 's' has a trailing '\n' + s2 = (char *)malloc(l2 + 2); + strcat(strn0cpy(s2, s, l2), " \n"); + s = s2; + } if (8 <= sscanf(s, "%d:%d:%a[^:]:%d:%d:%d:%d:%a[^:\n]:%a[^\n]", &active, &channel, &buffer1, &start, &stop, &priority, &lifetime, &buffer2, &summary)) { + if (summary && !*skipspace(summary)) { + delete summary; + summary = NULL; + } //TODO add more plausibility checks day = ParseDay(buffer1); strn0cpy(file, buffer2, MaxFileName); delete buffer1; delete buffer2; + delete s2; return day != 0; } + delete s2; return false; } @@ -550,6 +605,17 @@ cChannel *cChannels::GetByNumber(int Number) return NULL; } +cChannel *cChannels::GetByServiceID(unsigned short ServiceId) +{ + cChannel *channel = (cChannel *)First(); + while (channel) { + if (channel->pnr == ServiceId) + return channel; + channel = (cChannel *)channel->Next(); + } + return NULL; +} + bool cChannels::SwitchTo(int Number, cDvbApi *DvbApi) { cChannel *channel = GetByNumber(Number); @@ -599,6 +665,9 @@ cSetup::cSetup(void) MarkInstantRecord = 1; LnbFrequLo = 9750; LnbFrequHi = 10600; + SetSystemTime = 0; + MarginStart = 2; + MarginStop = 10; } bool cSetup::Parse(char *s) @@ -613,6 +682,9 @@ bool cSetup::Parse(char *s) else if (!strcasecmp(Name, "MarkInstantRecord")) MarkInstantRecord = atoi(Value); else if (!strcasecmp(Name, "LnbFrequLo")) LnbFrequLo = atoi(Value); else if (!strcasecmp(Name, "LnbFrequHi")) LnbFrequHi = atoi(Value); + else if (!strcasecmp(Name, "SetSystemTime")) SetSystemTime = atoi(Value); + else if (!strcasecmp(Name, "MarginStart")) MarginStart = atoi(Value); + else if (!strcasecmp(Name, "MarginStop")) MarginStop = atoi(Value); else return false; return true; @@ -660,6 +732,9 @@ bool cSetup::Save(const char *FileName) fprintf(f, "MarkInstantRecord = %d\n", MarkInstantRecord); fprintf(f, "LnbFrequLo = %d\n", LnbFrequLo); fprintf(f, "LnbFrequHi = %d\n", LnbFrequHi); + fprintf(f, "SetSystemTime = %d\n", SetSystemTime); + fprintf(f, "MarginStart = %d\n", MarginStart); + fprintf(f, "MarginStop = %d\n", MarginStop); fclose(f); isyslog(LOG_INFO, "saved setup to %s", FileName); return true; diff --git a/config.h b/config.h index 56f36182..59e7c696 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.27 2000/10/08 16:33:48 kls Exp $ + * $Id: config.h 1.28 2000/10/29 09:34:10 kls Exp $ */ #ifndef __CONFIG_H @@ -15,9 +15,10 @@ #include #include #include "dvbapi.h" +#include "eit.h" #include "tools.h" -#define VDRVERSION "0.66" +#define VDRVERSION "0.67" #define MaxBuffer 10000 @@ -113,6 +114,7 @@ public: char file[MaxFileName]; char *summary; cTimer(bool Instant = false); + cTimer(const cEventInfo *EventInfo); ~cTimer(); cTimer& operator= (const cTimer &Timer); const char *ToText(void); @@ -204,6 +206,7 @@ public: int GetNextNormal(int Idx); // Get next normal channel (not group) void ReNumber(void); // Recalculate 'number' based on channel type cChannel *GetByNumber(int Number); + cChannel *GetByServiceID(unsigned short ServiceId); const char *GetChannelNameByNumber(int Number); bool SwitchTo(int Number, cDvbApi *DvbApi = NULL); int MaxNumber(void) { return maxNumber; } @@ -234,6 +237,8 @@ public: int MarkInstantRecord; int LnbFrequLo; int LnbFrequHi; + int SetSystemTime; + int MarginStart, MarginStop; cSetup(void); bool Load(const char *FileName); bool Save(const char *FileName = NULL); diff --git a/dvbapi.c b/dvbapi.c index 62901ca8..f7272f76 100644 --- a/dvbapi.c +++ b/dvbapi.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: dvbapi.c 1.32 2000/10/14 08:56:08 kls Exp $ + * $Id: dvbapi.c 1.33 2000/10/29 10:39:57 kls Exp $ */ #include "dvbapi.h" @@ -25,6 +25,7 @@ extern "C" { #include "videodir.h" #define VIDEODEVICE "/dev/video" +#define VBIDEVICE "/dev/vbi" // The size of the array used to buffer video data: #define VIDEOBUFSIZE (1024*1024) @@ -1084,14 +1085,27 @@ int cDvbApi::NumDvbApis = 0; cDvbApi *cDvbApi::dvbApi[MAXDVBAPI] = { NULL }; cDvbApi *cDvbApi::PrimaryDvbApi = NULL; -cDvbApi::cDvbApi(const char *FileName) +cDvbApi::cDvbApi(const char *VideoFileName, const char *VbiFileName) { + siProcessor = NULL; pidRecord = pidReplay = 0; fromRecord = toRecord = -1; fromReplay = toReplay = -1; - videoDev = open(FileName, O_RDWR | O_NONBLOCK); - if (videoDev < 0) - LOG_ERROR; + videoDev = open(VideoFileName, O_RDWR | O_NONBLOCK); + if (videoDev >= 0) { + siProcessor = new cSIProcessor(VbiFileName); + if (!NumDvbApis) // only the first one shall set the system time + siProcessor->SetUseTSTime(Setup.SetSystemTime); + siProcessor->AddFilter(0x14, 0x70); // TDT + siProcessor->AddFilter(0x14, 0x73); // TOT + siProcessor->AddFilter(0x12, 0x4e); // event info, actual TS, present/following + siProcessor->AddFilter(0x12, 0x4f); // event info, other TS, present/following + siProcessor->AddFilter(0x12, 0x50); // event info, actual TS, schedule + siProcessor->AddFilter(0x12, 0x60); // event info, other TS, schedule + siProcessor->Start(); + } + else + LOG_ERROR_STR(VideoFileName); cols = rows = 0; ovlGeoSet = ovlStat = ovlFbSet = false; @@ -1121,6 +1135,7 @@ cDvbApi::cDvbApi(const char *FileName) cDvbApi::~cDvbApi() { if (videoDev >= 0) { + delete siProcessor; Close(); Stop(); StopRecord(); @@ -1174,11 +1189,9 @@ int cDvbApi::Index(void) bool cDvbApi::Init(void) { - char fileName[strlen(VIDEODEVICE) + 10]; - int i; - NumDvbApis = 0; - for (i = 0; i < MAXDVBAPI; i++) { + for (int i = 0; i < MAXDVBAPI; i++) { + char fileName[strlen(VIDEODEVICE) + 10]; sprintf(fileName, "%s%d", VIDEODEVICE, i); if (access(fileName, F_OK | R_OK | W_OK) == 0) { dsyslog(LOG_INFO, "probing %s", fileName); @@ -1188,7 +1201,9 @@ bool cDvbApi::Init(void) int r = ioctl(f, VIDIOCGCAP, &cap); close(f); if (r == 0 && (cap.type & VID_TYPE_DVB)) { - dvbApi[i] = new cDvbApi(fileName); + char vbiFileName[strlen(VBIDEVICE) + 10]; + sprintf(vbiFileName, "%s%d", VBIDEVICE, i); + dvbApi[i] = new cDvbApi(fileName, vbiFileName); NumDvbApis++; } } @@ -1223,6 +1238,13 @@ void cDvbApi::Cleanup(void) PrimaryDvbApi = NULL; } +const cSchedules *cDvbApi::Schedules(cThreadLock *ThreadLock) const +{ + if (siProcessor && ThreadLock->Lock(siProcessor)) + return siProcessor->Schedules(); + return NULL; +} + bool cDvbApi::GrabImage(const char *FileName, bool Jpeg, int Quality, int SizeX, int SizeY) { int result = 0; @@ -1671,8 +1693,11 @@ bool cDvbApi::SetChannel(int FrequencyMHz, char Polarization, int Diseqc, int Sr front.fec = 8; front.AFC = 1; ioctl(videoDev, VIDIOCSFRONTEND, &front); - if (front.sync & 0x1F == 0x1F) + if (front.sync & 0x1F == 0x1F) { + if (siProcessor) + siProcessor->SetCurrentServiceID(Pnr); return true; + } esyslog(LOG_ERR, "ERROR: channel not sync'ed (front.sync=%X)!", front.sync); } return false; diff --git a/dvbapi.h b/dvbapi.h index b35f3fb8..2a67585d 100644 --- a/dvbapi.h +++ b/dvbapi.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: dvbapi.h 1.18 2000/10/03 11:26:10 kls Exp $ + * $Id: dvbapi.h 1.19 2000/10/29 12:11:16 kls Exp $ */ #ifndef __DVBAPI_H @@ -21,6 +21,7 @@ typedef unsigned char __u8; #include #include #include "dvbosd.h" +#include "eit.h" // Overlay facilities #define MAXCLIPRECTS 100 @@ -44,7 +45,8 @@ public: class cDvbApi { private: int videoDev; - cDvbApi(const char *FileName); + cSIProcessor *siProcessor; + cDvbApi(const char *VideoFileName, const char *VbiFileName); public: ~cDvbApi(); @@ -71,6 +73,14 @@ public: // Closes down all DVB devices. // Must be called at the end of the program. + // EIT facilities + + const cSchedules *Schedules(cThreadLock *ThreadLock) const; + // Caller must provide a cThreadLock which has to survive the entire + // time the returned cSchedules is accessed. Once the cSchedules is no + // longer used, the cThreadLock must be destroyed. + void SetUseTSTime(bool On) { if (siProcessor) siProcessor->SetUseTSTime(On); } + // Image Grab facilities bool GrabImage(const char *FileName, bool Jpeg = true, int Quality = -1, int SizeX = -1, int SizeY = -1); diff --git a/eit.c b/eit.c index 44c873b1..ab2f6590 100644 --- a/eit.c +++ b/eit.c @@ -13,20 +13,576 @@ * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * - * $Id: eit.c 1.4 2000/10/01 14:09:05 kls Exp $ + * $Id: eit.c 1.5 2000/10/29 10:58:31 kls Exp $ ***************************************************************************/ #include "eit.h" -#include -#include -#include -#include -#include -#include +#include #include -#include "tools.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// --- cMJD ------------------------------------------------------------------ + +class cMJD { +public: + cMJD(); + cMJD(u_char date_hi, u_char date_lo); + cMJD(u_char date_hi, u_char date_lo, u_char timehr, u_char timemi, u_char timese); + ~cMJD(); + /** */ + void ConvertToTime(); + /** */ + bool SetSystemTime(); + /** */ + time_t GetTime_t(); +protected: // Protected attributes + /** */ + time_t mjdtime; +protected: // Protected attributes + /** */ + u_char time_second; +protected: // Protected attributes + /** */ + u_char time_minute; +protected: // Protected attributes + /** */ + u_char time_hour; +protected: // Protected attributes + /** */ + u_short mjd; +}; + +cMJD::cMJD() +{ +} + +cMJD::cMJD(u_char date_hi, u_char date_lo) +{ + mjd = date_hi << 8 | date_lo; + time_hour = time_minute = time_second = 0; + ConvertToTime(); +} + +cMJD::cMJD(u_char date_hi, u_char date_lo, u_char timehr, u_char timemi, u_char timese) +{ + mjd = date_hi << 8 | date_lo; + time_hour = timehr; + time_minute = timemi; + time_second = timese; + ConvertToTime(); +} + +cMJD::~cMJD() +{ +} + +/** */ +void cMJD::ConvertToTime() +{ + struct tm t; + + t.tm_sec = time_second; + t.tm_min = time_minute; + t.tm_hour = time_hour; + int k; + + t.tm_year = (int) ((mjd - 15078.2) / 365.25); + t.tm_mon = (int) ((mjd - 14956.1 - (int)(t.tm_year * 365.25)) / 30.6001); + t.tm_mday = (int) (mjd - 14956 - (int)(t.tm_year * 365.25) - (int)(t.tm_mon * 30.6001)); + k = (t.tm_mon == 14 || t.tm_mon == 15) ? 1 : 0; + t.tm_year = t.tm_year + k; + t.tm_mon = t.tm_mon - 1 - k * 12; + t.tm_mon--; + + t.tm_isdst = -1; + t.tm_gmtoff = 0; + + mjdtime = timegm(&t); + + //isyslog(LOG_INFO, "Time parsed = %s\n", ctime(&mjdtime)); +} + +/** */ +bool cMJD::SetSystemTime() +{ + struct tm *ptm; + time_t loctim; + + ptm = localtime(&mjdtime); + loctim = time(NULL); + + if (abs(mjdtime - loctim) > 2) + { + isyslog(LOG_INFO, "System Time = %s (%ld)\n", ctime(&loctim), loctim); + isyslog(LOG_INFO, "Local Time = %s (%ld)\n", ctime(&mjdtime), mjdtime); + if (stime(&mjdtime) < 0) + esyslog(LOG_ERR, "ERROR while setting system time: %s", strerror(errno)); + return true; + } + + return false; +} +/** */ +time_t cMJD::GetTime_t() +{ + return mjdtime; +} + +// --- cTDT ------------------------------------------------------------------ typedef struct { + u_char table_id : 8; + +#if BYTE_ORDER == BIG_ENDIAN + u_char section_syntax_indicator : 1; + u_char : 3; + u_char section_length_hi : 4; +#else + u_char section_length_hi : 4; + u_char : 3; + u_char section_syntax_indicator : 1; +#endif + + u_char section_length_lo : 8; + + + u_char utc_date_hi : 8; + u_char utc_date_lo : 8; + u_char utc_hour : 4; + u_char utc_hour_ten : 4; + u_char utc_min : 4; + u_char utc_min_ten : 4; + u_char utc_sec : 4; + u_char utc_sec_ten : 4; +} tdt_t; + +class cTDT { +public: + cTDT(tdt_t *ptdt); + ~cTDT(); + /** */ + bool SetSystemTime(); +protected: // Protected attributes + /** */ + tdt_t tdt; + /** */ + cMJD * mjd; +}; + +cTDT::cTDT(tdt_t *ptdt) +{ + tdt = *ptdt; + mjd = new cMJD(tdt.utc_date_hi, tdt.utc_date_lo, + tdt.utc_hour_ten * 10 + tdt.utc_hour, + tdt.utc_min_ten * 10 + tdt.utc_min, + tdt.utc_sec_ten * 10 + tdt.utc_sec); +} + +cTDT::~cTDT() +{ +} +/** */ +bool cTDT::SetSystemTime() +{ + return mjd->SetSystemTime(); +} + +// --- cEventInfo ------------------------------------------------------------ + +cEventInfo::cEventInfo(unsigned short serviceid, unsigned short eventid) +{ + pTitle = NULL; + pSubtitle = NULL; + pExtendedDescription = NULL; + bIsPresent = bIsFollowing = false; + lDuration = 0; + tTime = 0; + uEventID = eventid; + uServiceID = serviceid; + cExtendedDescriptorNumber = 0; + nChannelNumber = 0; +} + +cEventInfo::~cEventInfo() +{ + //XXX why not just use 'delete'??? + if (pTitle != NULL) free(pTitle); + if (pSubtitle != NULL) free(pSubtitle); + if (pExtendedDescription != NULL) free(pExtendedDescription); +} + +/** */ +const char * cEventInfo::GetTitle() const +{ + return pTitle; +} +/** */ +const char * cEventInfo::GetSubtitle() const +{ + return pSubtitle; +} +/** */ +const char * cEventInfo::GetExtendedDescription() const +{ + return pExtendedDescription; +} +/** */ +bool cEventInfo::IsPresent() const +{ + return bIsPresent; +} +/** */ +void cEventInfo::SetPresent(bool pres) +{ + bIsPresent = pres; +} +/** */ +bool cEventInfo::IsFollowing() const +{ + return bIsFollowing; +} +/** */ +void cEventInfo::SetFollowing(bool foll) +{ + bIsFollowing = foll; +} +/** */ +const char * cEventInfo::GetDate() const +{ + static char szDate[25]; + + strftime(szDate, sizeof(szDate), "%d.%m.%Y", localtime(&tTime)); + + return szDate; +} +/** */ +const char * cEventInfo::GetTimeString() const +{ + static char szTime[25]; + + strftime(szTime, sizeof(szTime), "%R", localtime(&tTime)); + + return szTime; +} +/** */ +const char * cEventInfo::GetEndTimeString() const +{ + static char szEndTime[25]; + time_t tEndTime = tTime + lDuration; + + strftime(szEndTime, sizeof(szEndTime), "%R", localtime(&tEndTime)); + + return szEndTime; +} +/** */ +time_t cEventInfo::GetTime() const +{ + return tTime; +} +/** */ +long cEventInfo::GetDuration() const +{ + return lDuration; +} +/** */ +unsigned short cEventInfo::GetEventID() const +{ + return uEventID; +} +/** */ +bool cEventInfo::SetTitle(char *string) +{ + if (string == NULL) + return false; + + pTitle = strdup(string); + if (pTitle == NULL) + return false; + + return true; +} +/** */ +bool cEventInfo::SetSubtitle(char *string) +{ + if (string == NULL) + return false; + + pSubtitle = strdup(string); + if (pSubtitle == NULL) + return false; + + return true; +} +/** */ +bool cEventInfo::AddExtendedDescription(char *string) +{ + int size = 0; + bool first = true; + char *p; + + if (string == NULL) + return false; + + if (pExtendedDescription) + { + first = false; + size += strlen(pExtendedDescription); + } + + size += (strlen(string) + 1); + + p = (char *)realloc(pExtendedDescription, size); + if (p == NULL) + return false; + + if (first) + *p = 0; + + strcat(p, string); + + pExtendedDescription = p; + + return true; +} +/** */ +void cEventInfo::SetTime(time_t t) +{ + tTime = t; +} +/** */ +void cEventInfo::SetDuration(long l) +{ + lDuration = l; +} +/** */ +void cEventInfo::SetEventID(unsigned short evid) +{ + uEventID = evid; +} +/** */ +void cEventInfo::SetServiceID(unsigned short servid) +{ + uServiceID = servid; +} +/** */ +u_char cEventInfo::GetExtendedDescriptorNumber() const +{ + return cExtendedDescriptorNumber; +} +/** */ +void cEventInfo::IncreaseExtendedDescriptorNumber() +{ + cExtendedDescriptorNumber++; +} + +/** */ +unsigned short cEventInfo::GetServiceID() const +{ + return uServiceID; +} + +// --- cSchedule ------------------------------------------------------------- + +cSchedule::cSchedule(unsigned short servid) +{ + pPresent = pFollowing = NULL; + uServiceID = servid; +} + + +cSchedule::~cSchedule() +{ +} +/** */ +const cEventInfo * cSchedule::GetPresentEvent() const +{ + return pPresent; +} +/** */ +const cEventInfo * cSchedule::GetFollowingEvent() const +{ + return pFollowing; +} +/** */ +void cSchedule::SetServiceID(unsigned short servid) +{ + uServiceID = servid; +} +/** */ +unsigned short cSchedule::GetServiceID() const +{ + return uServiceID; +} +/** */ +const cEventInfo * cSchedule::GetEvent(unsigned short uEventID) const +{ + cEventInfo *pe = Events.First(); + while (pe != NULL) + { + if (pe->GetEventID() == uEventID) + return pe; + + pe = Events.Next(pe); + } + + return NULL; +} +/** */ +const cEventInfo * cSchedule::GetEvent(time_t tTime) const +{ + cEventInfo *pe = Events.First(); + while (pe != NULL) + { + if (pe->GetTime() == tTime) + return pe; + + pe = Events.Next(pe); + } + + return NULL; +} +/** */ +bool cSchedule::SetPresentEvent(cEventInfo *pEvent) +{ + if (pPresent != NULL) + pPresent->SetPresent(false); + pPresent = pEvent; + pPresent->SetPresent(true); + + return true; +} + +/** */ +bool cSchedule::SetFollowingEvent(cEventInfo *pEvent) +{ + if (pFollowing != NULL) + pFollowing->SetFollowing(false); + pFollowing = pEvent; + pFollowing->SetFollowing(true); + + return true; +} + +/** */ +void cSchedule::Cleanup() +{ + Cleanup(time(NULL)); +} + +/** */ +void cSchedule::Cleanup(time_t tTime) +{ + cEventInfo *pEvent; + for (int a = 0; true ; a++) + { + pEvent = Events.Get(a); + if (pEvent == NULL) + break; + if (pEvent->GetTime() + pEvent->GetDuration() < tTime) + { + Events.Del(pEvent); + a--; + } + } +} + +// --- cSchedules ------------------------------------------------------------ + +cSchedules::cSchedules() +{ + pCurrentSchedule = NULL; + uCurrentServiceID = 0; +} + +cSchedules::~cSchedules() +{ +} +/** */ +bool cSchedules::SetCurrentServiceID(unsigned short servid) +{ + pCurrentSchedule = GetSchedule(servid); + if (pCurrentSchedule == NULL) + { + Add(new cSchedule(servid)); + pCurrentSchedule = GetSchedule(servid); + if (pCurrentSchedule == NULL) + return false; + } + + uCurrentServiceID = servid; + + return true; +} +/** */ +const cSchedule * cSchedules::GetSchedule() const +{ + return pCurrentSchedule; +} +/** */ +const cSchedule * cSchedules::GetSchedule(unsigned short servid) const +{ + cSchedule *p; + + p = First(); + while (p != NULL) + { + if (p->GetServiceID() == servid) + return p; + p = Next(p); + } + + return NULL; +} + +/** */ +void cSchedules::Cleanup() +{ + cSchedule *p; + + p = First(); + while (p != NULL) + { + p->Cleanup(time(NULL)); + p = Next(p); + } +} + +// --- cEIT ------------------------------------------------------------------ + +#define DEC(N) dec << setw(N) << setfill(int('0')) +#define HEX(N) hex << setw(N) << setfill(int('0')) + +#define EIT_STUFFING_DESCRIPTOR 0x42 +#define EIT_LINKAGE_DESCRIPTOR 0x4a +#define EIT_SHORT_EVENT_DESCRIPTOR 0x4d +#define EIT_EXTENDED_EVENT_DESCRIPTOR 0x4e +#define EIT_TIME_SHIFTED_EVENT_DESCRIPTOR 0x4f +#define EIT_COMPONENT_DESCRIPTOR 0x50 +#define EIT_CA_IDENTIFIER_DESCRIPTOR 0x53 +#define EIT_CONTENT_DESCRIPTOR 0x54 +#define EIT_PARENTAL_RATING_DESCRIPTOR 0x55 +#define EIT_TELEPHONE_DESCRIPTOR 0x57 +#define EIT_MULTILINGUAL_COMPONENT_DESCRIPTOR 0x5e +#define EIT_PRIVATE_DATE_SPECIFIER_DESCRIPTOR 0x5f +#define EIT_SHORT_SMOOTHING_BUFFER_DESCRIPTOR 0x61 +#define EIT_DATA_BROADCAST_DESCRIPTOR 0x64 +#define EIT_PDC_DESCRIPTOR 0x69 + +typedef struct eit_struct { u_char table_id : 8; #if BYTE_ORDER == BIG_ENDIAN @@ -64,9 +620,7 @@ typedef struct { u_char segment_last_table_id : 8; } eit_t; -#define EIT_SIZE 14 - -struct eit_loop_struct1 { +typedef struct eit_loop_struct { u_char event_id_hi : 8; u_char event_id_lo : 8; @@ -79,12 +633,12 @@ struct eit_loop_struct1 { u_char time_second : 4; u_char time_second_ten : 4; - u_char dur_hour_ten : 4; u_char dur_hour : 4; - u_char dur_minute_ten : 4; + u_char dur_hour_ten : 4; u_char dur_minute : 4; - u_char dur_second_ten : 4; + u_char dur_minute_ten : 4; u_char dur_second : 4; + u_char dur_second_ten : 4; #if BYTE_ORDER == BIG_ENDIAN u_char running_status : 3; @@ -97,12 +651,9 @@ struct eit_loop_struct1 { #endif u_char descriptors_loop_length_lo : 8; -}; +} eit_loop_t; -#define EIT_SHORT_EVENT_DESCRIPTOR 0x4d -#define EIT_SHORT_EVENT_DESCRIPTOR_SIZE 6 - -struct eit_short_event_descriptor_struct { +typedef struct eit_short_event_struct { u_char descriptor_tag : 8; u_char descriptor_length : 8; @@ -111,415 +662,584 @@ struct eit_short_event_descriptor_struct { u_char language_code_3 : 8; u_char event_name_length : 8; -}; - -#define EIT_EXTENDED_EVENT_DESCRIPOR 0x4e - -#define EIT_DESCRIPTOR_SIZE - -typedef struct eit_event_struct { - u_char event_id_hi : 8; - u_char event_id_lo : 8; - - u_char start_time_1 : 8; - u_char start_time_2 : 8; - u_char start_time_3 : 8; - u_char start_time_4 : 8; - u_char start_time_5 : 8; - - u_char duration_1 : 8; - u_char duration_2 : 8; - u_char duration_3 : 8; - -#if BYTE_ORDER == BIG_ENDIAN - u_char running_status : 3; - u_char free_CA_mode : 1; - u_char descriptors_loop_length_hi : 4; -#else - u_char descriptors_loop_length_hi : 4; - u_char free_CA_mode : 1; - u_char running_status : 3; -#endif - - u_char descriptors_loop_length_lo : 8; - -} eit_event_t; -#define EIT_LOOP_SIZE 12 - - -typedef struct tot_t { - u_char table_id : 8; - -#if BYTE_ORDER == BIG_ENDIAN - u_char section_syntax_indicator : 1; - u_char : 3; - u_char section_length_hi : 4; -#else - u_char section_length_hi : 4; - u_char : 3; - u_char section_syntax_indicator : 1; -#endif - - u_char date_hi : 8; - u_char date_lo : 8; - u_char time_hour : 4; - u_char time_hour_ten : 4; - u_char time_minute : 4; - u_char time_minute_ten : 4; - u_char time_second : 4; - u_char time_second_ten : 4; - -#if BYTE_ORDER == BIG_ENDIAN - u_char : 4; - u_char descriptor_loop_length_hi : 4; -#else - u_char descriptor_loop_length_hi : 4; - u_char : 4; -#endif - - u_char descriptor_loop_length_lo : 8; -} tot_t; - -typedef struct local_time_offset { +} eit_short_event_t; +typedef struct eit_extended_event_struct { u_char descriptor_tag : 8; u_char descriptor_length : 8; + u_char last_descriptor_number : 4; + u_char descriptor_number : 4; + u_char language_code_1 : 8; u_char language_code_2 : 8; u_char language_code_3 : 8; - u_char : 8; - - u_char offset_hour : 4; - u_char offset_hour_ten : 4; - u_char offset_minute : 4; - u_char offset_minute_ten : 4; + u_char length_of_items : 8; +} eit_extended_event_t; - u_char change_date_hi : 8; - u_char change_date_lo : 8; - u_char change_time_hour : 4; - u_char change_time_hour_ten : 4; - u_char change_time_minute : 4; - u_char change_time_minute_ten : 4; - u_char change_time_second : 4; - u_char change_time_second_ten : 4; +typedef struct eit_content_descriptor { + u_char descriptor_tag : 8; + u_char descriptor_length : 8; +} eit_content_descriptor_t; - u_char next_offset_hour : 4; - u_char next_offset_hour_ten : 4; - u_char next_offset_minute : 4; - u_char next_offset_minute_ten : 4; -} local_time_offset; +typedef struct eit_content_loop { + u_char content_nibble_level_2 : 4; + u_char content_nibble_level_1 : 4; + u_char user_nibble_2 : 4; + u_char user_nibble_1 : 4; +} eit_content_loop_t; -cEIT::cEIT() +class cEIT { +private: + cSchedules *schedules; +public: + cEIT(void *buf, int length, cSchedules *Schedules); + ~cEIT(); + /** */ + int ProcessEIT(); + +protected: // Protected methods + /** */ + int strdvbcpy(unsigned char *dst, unsigned char *src, int max); + /** returns true if this EIT covers a +present/following information, false if it's +schedule information */ + bool IsPresentFollowing(); + /** */ + bool WriteShortEventDescriptor(unsigned short service, eit_loop_t *eitloop, u_char *buf); + /** */ + bool WriteExtEventDescriptor(unsigned short service, eit_loop_t *eitloop, u_char *buf); +protected: // Protected attributes + int buflen; +protected: // Protected attributes + /** */ + u_char buffer[4097]; + /** Table ID of this EIT struct */ + u_char tid; + /** EITs service id (program number) */ + u_short pid; +}; + +cEIT::cEIT(void * buf, int length, cSchedules *Schedules) { - cszBitFilter = "/dev/vbi"; - if((fsvbi = open(cszBitFilter, O_RDWR))<0) - { - fsvbi = 0; - esyslog(LOG_ERR, "Failed to open DVB bitfilter device: %s", cszBitFilter); - return; - } + buflen = min((unsigned int)length, sizeof(buffer)); + memset(buffer, 0, sizeof(buffer)); + memcpy(buffer, buf, buflen); + tid = buffer[0]; + schedules = Schedules; } cEIT::~cEIT() { - if (fsvbi != 0) - close(fsvbi); - fsvbi = 0; -} - -/** Set the bitfilter in vbi device to return -correct tables */ -int cEIT::SetBitFilter(unsigned short pid, unsigned short section, unsigned short mode) -{ - struct bitfilter filt = { - pid, - { section, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, - 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, - mode,0, - FILTER_MEM, - {}, - }; - - if (ioctl(fsvbi, VIDIOCSBITFILTER, &filt) < 0) - return 0xffff; - return 0; -} -/** */ -int cEIT::GetSection(unsigned char *buf, ushort PID, unsigned char sec) -{ - int seclen=0; - unsigned short handle, pid; - unsigned char section, sectionnum=0xff, maxsec=0; - - if ((handle = SetBitFilter(PID, (sec<<8)|0x00ff, SECTION_CONTINUOS))==0xffff) - return -1; - - seclen=0; - if (!cFile::AnyFileReady(fsvbi, 20000)) - { - //cerr << "Timeout\n"; - return -1; - } - - read(fsvbi, buf, 8); - seclen=(buf[6]<<8)|buf[7]; - pid=(buf[4]<<8)|buf[5]; - - read(fsvbi, buf, seclen); - section=buf[0]; - sectionnum=buf[6]; - maxsec=buf[7]; - - //cerr << "secnum: " << HEX(2) << (int)sectionnum - // << ", secmax: " << HEX(2) << (int) msecnum << "\n"; - - CloseFilter(handle); - - return seclen; } /** */ -int cEIT::CloseFilter(unsigned short handle) +int cEIT::ProcessEIT() { - if (ioctl(fsvbi, VIDIOCSSHUTDOWNFILTER, &handle)<0) - return -1; - return 0; -} - -/** */ -char * cEIT::mjd2string(unsigned short mjd) -{ - int y, m, d, k; - static char buf[20]; - - y = (int) ((mjd - 15078.2) / 365.25); - m = (int) ((mjd - 14956.1 - (int)(y * 365.25)) / 30.6001); - d = (int) (mjd - 14956 - (int)(y * 365.25) - (int)(m * 30.6001)); - k = (m == 14 || m == 15) ? 1 : 0; - y = y + k; - m = m - 1 - k * 12; - sprintf(buf, "%d.%d.%4d", d, m, y + 1900); - - return(buf); -} - -/** */ -int cEIT::GetEIT() -{ - unsigned char buf[4096+1]; // max. allowed size for any EIT section (+1 for safety ;-) + int bufact = 0; eit_t *eit; - struct eit_loop_struct1 *eitloop; - struct eit_short_event_descriptor_struct *eitevt; - unsigned int seclen; - unsigned short handle, pid; - eit_event * pevt = (eit_event *)0; - time_t tstart; - - if ((handle = SetBitFilter(0x12, (0x4e << 8) | 0x00ff, SECTION_CONTINUOS))==0xffff) - { - return -1; - } -/* - pid_t process = fork(); - if (process < 0) - { - cerr << "GetEIT -1" << endl; - return -1; - } - - if (process != 0) - { - cerr << "GetEIT 0" << endl; + eit_loop_t *eitloop; + u_char tmp[256]; + + if (bufact + (int)sizeof(eit_t) > buflen) return 0; - } -*/ - int nReceivedEITs = 0; - tstart = time(NULL); - while ((!evtRunning.bIsValid || !evtNext.bIsValid) && nReceivedEITs < 20 && difftime(time(NULL), tstart) < 4) - { - if (!cFile::AnyFileReady(fsvbi, 5000)) - { - //cerr << "Timeout\n"; - CloseFilter(handle); - return -1; - } - - read(fsvbi, buf, 8); - seclen=(buf[6]<<8)|buf[7]; - pid=(buf[4]<<8)|buf[5]; - - if (seclen >= sizeof(buf)) - seclen = sizeof(buf) - 1; - read(fsvbi, buf, seclen); - - if (seclen < (int)(sizeof(eit_t) - + sizeof(struct eit_loop_struct1) - + sizeof(struct eit_short_event_descriptor_struct))) - continue; - - eit = (eit_t *)buf; - eitloop = (struct eit_loop_struct1 *)&eit[1]; - eitevt = (struct eit_short_event_descriptor_struct *)&eitloop[1]; - - if (eitevt->descriptor_tag != EIT_SHORT_EVENT_DESCRIPTOR) - { - // printf("Tag = '%c'\n", eitevt->descriptor_tag); - continue; - } - - if (((eit->service_id_hi << 8) | eit->service_id_lo) != uProgramNumber) - { - // printf("Wrong program %04x need %04x\n", (eit->service_id_hi << 8) | eit->service_id_lo, uProgramNumber); - continue; - } - - nReceivedEITs++; - - pevt = (eit_event *)0; - if (eitloop->running_status == 4 | eitloop->running_status == 3) - pevt = (eit_event *)&evtRunning; - else if (eitloop->running_status == 1 || eitloop->running_status == 2 || eitloop->running_status == 0) - pevt = (eit_event *)&evtNext; + eit = (eit_t *)buffer; + bufact += sizeof(eit_t); - if (pevt) + unsigned int service = (eit->service_id_hi << 8) | eit->service_id_lo; + + while(bufact + (int)sizeof(eit_loop_t) <= buflen) + { + eitloop = (eit_loop_t *)&buffer[bufact]; + bufact += sizeof(eit_loop_t); + + int descdatalen = (eitloop->descriptors_loop_length_hi << 8) + eitloop->descriptors_loop_length_lo; + int descdataact = 0; + + while (descdataact < descdatalen && bufact < buflen) { - unsigned char *p = (unsigned char *)&eitevt[1]; - strdvbcpy((unsigned char *)pevt->szTitle, p, eitevt->event_name_length); - pevt->szSubTitle[0] = 0; - strdvbcpy((unsigned char *)pevt->szSubTitle, &p[eitevt->event_name_length+1], (int)p[eitevt->event_name_length]); - strcpy(pevt->szDate, mjd2string((eitloop->date_hi << 8) + eitloop->date_lo)); - int hr = eitloop->time_hour + (eitloop->time_hour_ten * 10); - hr += 2; - if (hr >=24) + switch (buffer[bufact]) { - hr -= 24; - // need to switch date one day ahead here + eit_content_descriptor_t *cont; + eit_content_loop_t *contloop; + + case EIT_STUFFING_DESCRIPTOR : + //dsyslog(LOG_INFO, "Found EIT_STUFFING_DESCRIPTOR"); + break; + + case EIT_LINKAGE_DESCRIPTOR : + //dsyslog(LOG_INFO, "Found EIT_LINKAGE_DESCRIPTOR"); + break; + + case EIT_SHORT_EVENT_DESCRIPTOR: + WriteShortEventDescriptor(service, eitloop, &buffer[bufact]); + break; + + case EIT_EXTENDED_EVENT_DESCRIPTOR: + WriteExtEventDescriptor(service, eitloop, &buffer[bufact]); + break; + + case EIT_TIME_SHIFTED_EVENT_DESCRIPTOR : + //dsyslog(LOG_INFO, "Found EIT_TIME_SHIFTED_EVENT_DESCRIPTOR"); + break; + + case EIT_COMPONENT_DESCRIPTOR : + strdvbcpy(tmp, &buffer[bufact + 8], buffer[bufact + 1] - 6); + //dsyslog(LOG_INFO, "Found EIT_COMPONENT_DESCRIPTOR %c%c%c 0x%02x/0x%02x/0x%02x '%s'\n", buffer[bufact + 5], buffer[bufact + 6], buffer[bufact + 7], buffer[2], buffer[3], buffer[4], tmp); + break; + + case EIT_CA_IDENTIFIER_DESCRIPTOR : + //dsyslog(LOG_INFO, "Found EIT_CA_IDENTIFIER_DESCRIPTOR"); + break; + + case EIT_CONTENT_DESCRIPTOR : + cont = (eit_content_descriptor_t *)buffer; + contloop = (eit_content_loop_t *)&buffer[sizeof(eit_content_descriptor_t)]; + //dsyslog(LOG_INFO, "Found EIT_CONTENT_DESCRIPTOR 0x%02x/0x%02x\n", contloop->content_nibble_level_1, contloop->content_nibble_level_2); + break; + + case EIT_PARENTAL_RATING_DESCRIPTOR : + //dsyslog(LOG_INFO, "Found EIT_PARENTAL_RATING_DESCRIPTOR"); + break; + + case EIT_TELEPHONE_DESCRIPTOR : + //dsyslog(LOG_INFO, "Found EIT_TELEPHONE_DESCRIPTOR"); + break; + + case EIT_MULTILINGUAL_COMPONENT_DESCRIPTOR : + //dsyslog(LOG_INFO, "Found EIT_MULTILINGUAL_COMPONENT_DESCRIPTOR"); + break; + + case EIT_PRIVATE_DATE_SPECIFIER_DESCRIPTOR : + //dsyslog(LOG_INFO, "Found EIT_PRIVATE_DATE_SPECIFIER_DESCRIPTOR"); + break; + + case EIT_SHORT_SMOOTHING_BUFFER_DESCRIPTOR : + //dsyslog(LOG_INFO, "Found EIT_SHORT_SMOOTHING_BUFFER_DESCRIPTOR"); + break; + + case EIT_DATA_BROADCAST_DESCRIPTOR : + //dsyslog(LOG_INFO, "Found EIT_DATA_BROADCAST_DESCRIPTOR"); + break; + + case EIT_PDC_DESCRIPTOR : + //dsyslog(LOG_INFO, "Found EIT_PDC_DESCRIPTOR"); + break; + + default: + //dsyslog(LOG_INFO, "Found unhandled descriptor 0x%02x with length of %04d\n", (int)buffer[bufact], (int)buffer[bufact + 1]); + break; } - sprintf(pevt->szTime, "%d:%c%c", hr, - eitloop->time_minute_ten + '0', - eitloop->time_minute + '0'); - pevt->bIsValid = true; + descdataact += (buffer[bufact + 1] + 2); + bufact += (buffer[bufact + 1] + 2); } } - CloseFilter(handle); - - return 1; -} - -/** */ -int cEIT::SetProgramNumber(unsigned short pnr) -{ - if (pnr == 0) - { - evtRunning.bIsValid = false; - evtNext.bIsValid = false; - return -1; - } - - if (pnr != uProgramNumber) - { - evtRunning.bIsValid = false; - evtNext.bIsValid = false; - uProgramNumber = pnr; - } - return 1; -} - -/** retrieves the string for the running title */ -char * cEIT::GetRunningTitle() -{ - if (evtRunning.bIsValid) - return evtRunning.szTitle; - else - return "---"; -} -/** Retrieves the string for the running subtitle */ -char * cEIT::GetRunningSubtitle() -{ - if (evtRunning.bIsValid) - return evtRunning.szSubTitle; - else - return "---"; -} -/** Retrieves the string representing the -date of the current event - */ -char * cEIT::GetRunningDate() -{ - if (evtRunning.bIsValid) - return evtRunning.szDate; - else - return "---"; -} -/** Retrieves the string representing the -time of the current event */ -char * cEIT::GetRunningTime() -{ - if (evtRunning.bIsValid) - return evtRunning.szTime; - else - return "---"; -} -/** retrieves the string for the running title */ -char * cEIT::GetNextTitle() -{ - if (evtNext.bIsValid) - return evtNext.szTitle; - else - return "---"; -} -/** Retrieves the string for the running subtitle */ -char * cEIT::GetNextSubtitle() -{ - if (evtNext.bIsValid) - return evtNext.szSubTitle; - else - return "---"; -} -/** Retrieves the string representing the -date of the current event - */ -char * cEIT::GetNextDate() -{ - if (evtNext.bIsValid) - return evtNext.szDate; - else - return "---"; -} -/** Retrieves the string representing the -time of the current event */ -char * cEIT::GetNextTime() -{ - if (evtNext.bIsValid) - return evtNext.szTime; - else - return "---"; -} - -/** */ -bool cEIT::IsValid() -{ - GetEIT(); - return (evtRunning.bIsValid && evtNext.bIsValid); + return 0; } /** */ int cEIT::strdvbcpy(unsigned char *dst, unsigned char *src, int max) { - int a; - for (a = 0; a < max; a++) + int a = 0; + + if (*src == 0x05 || (*src >= 0x20 && *src <= 0xff)) { - if (*src == 0) - break; + for (a = 0; a < max; a++) + { + if (*src == 0) + break; - if ((*src >= ' ' && *src <= '~') || (*src >= 0xa0 && *src <= 0xff)) - *dst++ = *src++; - else - src++; + if ((*src >= ' ' && *src <= '~') || (*src >= 0xa0 && *src <= 0xff)) + *dst++ = *src++; + else + { + // if ((*src > '~' && *src < 0xa0) || *src == 0xff) + // cerr << "found special character 0x" << HEX(2) << (int)*src << endl; + src++; + } + } + *dst = 0; + } + else + { + const char *ret; + + switch (*src) + { + case 0x01: ret = "Coding according to character table 1"; break; + case 0x02: ret = "Coding according to character table 2"; break; + case 0x03: ret = "Coding according to character table 3"; break; + case 0x04: ret = "Coding according to character table 4"; break; + case 0x10: ret = "Coding according to ISO/IEC 8859"; break; + case 0x11: ret = "Coding according to ISO/IEC 10646"; break; + case 0x12: ret = "Coding according to KSC 5601"; break; + default: ret = "Unknown coding"; break; + } + strncpy((char *)dst, ret, max); } - *dst = 0; return a; } + +/** returns true if this EIT covers a +present/following information, false if it's +schedule information */ +bool cEIT::IsPresentFollowing() +{ + if (tid == 0x4e || tid == 0x4f) + return true; + + return false; +} + +/** */ +bool cEIT::WriteShortEventDescriptor(unsigned short service, eit_loop_t *eitloop, u_char *buf) +{ + u_char tmp[256]; + eit_short_event_t *evt = (eit_short_event_t *)buf; + unsigned short eventid = (unsigned short)((eitloop->event_id_hi << 8) | eitloop->event_id_lo); + cEventInfo *pEvent; + + //isyslog(LOG_INFO, "Found Short Event Descriptor"); + + cSchedule *pSchedule = (cSchedule *)schedules->GetSchedule(service); + if (pSchedule == NULL) + { + schedules->Add(new cSchedule(service)); + pSchedule = (cSchedule *)schedules->GetSchedule(service); + if (pSchedule == NULL) + return false; + } + + // + // if we are working on a present/following info, let's see whether + // we already have present/following info for this service and if yes + // check whether it's the same eventid, if yes, just return, nothing + // left to do. + // + if (IsPresentFollowing()) + { + if (eitloop->running_status == 4 || eitloop->running_status == 3) + pEvent = (cEventInfo *)pSchedule->GetPresentEvent(); + else + pEvent = (cEventInfo *)pSchedule->GetFollowingEvent(); + + if (pEvent != NULL) + if (pEvent->GetEventID() == eventid) + return true; + } + + // + // let's see whether we have that eventid already + // in case not, we have to create a new cEventInfo for it + // + pEvent = (cEventInfo *)pSchedule->GetEvent(eventid); + if (pEvent == NULL) + { + pSchedule->Events.Add(new cEventInfo(service, eventid)); + pEvent = (cEventInfo *)pSchedule->GetEvent(eventid); + if (pEvent == NULL) + return false; + + strdvbcpy(tmp, &buf[sizeof(eit_short_event_t)], evt->event_name_length); + pEvent->SetTitle((char *)tmp); + strdvbcpy(tmp, &buf[sizeof(eit_short_event_t) + evt->event_name_length + 1], + (int)buf[sizeof(eit_short_event_t) + evt->event_name_length]); + pEvent->SetSubtitle((char *)tmp); + cMJD mjd(eitloop->date_hi, eitloop->date_lo, + eitloop->time_hour_ten * 10 + eitloop->time_hour, + eitloop->time_minute_ten * 10 + eitloop->time_minute, + eitloop->time_second_ten * 10 + eitloop->time_second); + pEvent->SetTime(mjd.GetTime_t()); + pEvent->SetDuration((long)((long)((eitloop->dur_hour_ten * 10 + eitloop->dur_hour) * 60l * 60l) + + (long)((eitloop->dur_minute_ten * 10 + eitloop->dur_minute) * 60l) + + (long)(eitloop->dur_second_ten * 10 + eitloop->dur_second))); + } + + if (IsPresentFollowing()) + { + if (eitloop->running_status == 4 || eitloop->running_status == 3) + pSchedule->SetPresentEvent(pEvent); + else if (eitloop->running_status == 1 || eitloop->running_status == 2 || eitloop->running_status == 0) + pSchedule->SetFollowingEvent(pEvent); + } + + return true; +} + +/** */ +bool cEIT::WriteExtEventDescriptor(unsigned short service, eit_loop_t *eitloop, u_char *buf) +{ + u_char tmp[256]; + eit_extended_event_t *evt = (eit_extended_event_t *)buf; + int bufact, buflen; + unsigned short eventid = (unsigned short)((eitloop->event_id_hi << 8) | eitloop->event_id_lo); + cEventInfo *pEvent; + + //isyslog(LOG_INFO, "Found Extended Event Descriptor"); + + cSchedule *pSchedule = (cSchedule *)schedules->GetSchedule(service); + if (pSchedule == NULL) + { + schedules->Add(new cSchedule(service)); + pSchedule = (cSchedule *)schedules->GetSchedule(service); + if (pSchedule == NULL) + return false; + } + + pEvent = (cEventInfo *)pSchedule->GetEvent(eventid); + if (pEvent == NULL) + return false; + + if (evt->descriptor_number != pEvent->GetExtendedDescriptorNumber()) + return false; + + bufact = sizeof(eit_extended_event_t); + buflen = buf[1] + 2; + + if (evt->length_of_items > 0) + { + while (bufact - sizeof(eit_extended_event_t) < evt->length_of_items) + { + strdvbcpy(tmp, &buf[bufact + 1], (int)buf[bufact]); + // could use value in tmp now to do something, + // haven't seen any items as of yet transmitted from satellite + bufact += (buf[bufact] + 1); + } + } + + strdvbcpy(tmp, &buf[bufact + 1], (int)buf[bufact]); + if (pEvent->AddExtendedDescription((char *)tmp)) + { + pEvent->IncreaseExtendedDescriptorNumber(); + return true; + } + + return false; +} + +// --- cSIProcessor ---------------------------------------------------------- + +#define MAX_FILTERS 20 + +/** */ +cSIProcessor::cSIProcessor(const char *FileName) +{ + useTStime = false; + filters = NULL; + schedules = NULL; + if ((fsvbi = open(FileName, O_RDONLY)) >= 0) + { + schedules = new cSchedules; + filters = (SIP_FILTER *)calloc(MAX_FILTERS, sizeof(SIP_FILTER)); + } + else + LOG_ERROR_STR(FileName); +} + +cSIProcessor::~cSIProcessor() +{ + if (fsvbi >= 0) + { + Stop(); + ShutDownFilters(); + delete filters; + delete schedules; + close(fsvbi); + } +} + +/** use the vbi device to parse all relevant SI +information and let the classes corresponding +to the tables write their information to the disk */ +void cSIProcessor::Action() +{ + if (fsvbi < 0) { + esyslog(LOG_ERR, "cSIProcessor::Action() called without open file - returning"); + return; + } + + dsyslog(LOG_INFO, "EIT processing thread started (pid=%d)", getpid()); + + unsigned char buf[4096+1]; // max. allowed size for any EIT section (+1 for safety ;-) + unsigned int seclen; + unsigned int pid; + int dayofdelete = -1; + struct pollfd pfd; + + while(true) + { + time_t now = time(NULL); + struct tm *ptm = localtime(&now); + if (dayofdelete != ptm->tm_yday && ptm->tm_hour == 0 && ptm->tm_min < 5) + { + dsyslog(LOG_INFO, "Now cleaning up things"); + + LOCK_THREAD; + + schedules->Cleanup(); + dayofdelete = ptm->tm_yday; + } + + /* wait data become ready from the bitfilter */ + pfd.fd = fsvbi; + pfd.events = POLLIN; + if(poll(&pfd, 1, 1000) != 0) /* timeout is 5 secs */ + { + // fprintf(stderr, "\n"); + /* read section */ + read(fsvbi, buf, 8); + seclen = (buf[6] << 8) | buf[7]; + pid = (buf[4] << 8) | buf[5]; + read(fsvbi, buf, seclen); + + //dsyslog(LOG_INFO, "Received pid 0x%02x with table ID 0x%02x and length of %04d\n", pid, buf[0], seclen); + + switch (pid) + { + case 0x14: + if (buf[0] == 0x70) + { + if (useTStime) + { + //XXX wouldn't it be ok to have ctdt on the stack??? + cTDT *ctdt = new cTDT((tdt_t *)buf); + ctdt->SetSystemTime(); + delete ctdt; + } + } + /*XXX this comes pretty often: + else + dsyslog(LOG_INFO, "Time packet was not 0x70 but 0x%02x\n", (int)buf[0]); + XXX*/ + break; + + case 0x12: + if (buf[0] != 0x72) + { + LOCK_THREAD; + + //XXX wouldn't it be ok to have ceit on the stack??? + cEIT *ceit = new cEIT(buf, seclen, schedules); + ceit->ProcessEIT(); + delete ceit; + } + else + dsyslog(LOG_INFO, "Received stuffing section in EIT\n"); + break; + + default: + break; + } + } + else + { + LOCK_THREAD; + + //XXX this comes pretty often + //isyslog(LOG_INFO, "Received timeout from poll, refreshing filters\n"); + RefreshFilters(); + } +// WakeUp(); + } +} + +/** Add a filter with packet identifier pid and +table identifer tid */ +bool cSIProcessor::AddFilter(u_char pid, u_char tid) +{ + if (fsvbi < 0) + return false; + + int section = ((int)tid << 8) | 0x00ff; + + struct bitfilter filt = { + pid, + { section, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, + SECTION_CONTINUOS, 0, + FILTER_MEM, + {}, + }; + + if (ioctl(fsvbi, VIDIOCSBITFILTER, &filt) < 0) + return false; + + for (int a = 0; a < MAX_FILTERS; a++) + { + if (filters[a].inuse == false) + { + filters[a].pid = pid; + filters[a].tid = tid; + filters[a].handle = filt.handle; + filters[a].inuse = true; + // dsyslog(LOG_INFO, " Registered filter handle %04x, pid = %02d, tid = %02d", filters[a].handle, filters[a].pid, filters[a].tid); + return true; + } + } + + return false; +} + +/** set whether local systems time should be +set by the received TDT or TOT packets */ +bool cSIProcessor::SetUseTSTime(bool use) +{ + useTStime = use; + return useTStime; +} + +/** */ +bool cSIProcessor::ShutDownFilters() +{ + if (fsvbi < 0) + return false; + + bool ret = true; + + for (int a = 0; a < MAX_FILTERS; a++) + { + if (filters[a].inuse == true) + { + if (ioctl(fsvbi, VIDIOCSSHUTDOWNFILTER, &filters[a].handle) < 0) + ret = false; + + // dsyslog(LOG_INFO, "Deregistered filter handle %04x, pid = %02d, tid = %02d", filters[a].handle, filters[a].pid, filters[a].tid); + + filters[a].inuse = false; + } + } + + return ret; +} + +/** */ +bool cSIProcessor::SetCurrentServiceID(unsigned short servid) +{ + LOCK_THREAD; + return schedules ? schedules->SetCurrentServiceID(servid) : false; +} + +/** */ +bool cSIProcessor::RefreshFilters() +{ + if (fsvbi < 0) + return false; + + bool ret = true; + + ret = ShutDownFilters(); + + for (int a = 0; a < MAX_FILTERS; a++) + { + if (filters[a].inuse == false && filters[a].pid != 0 && filters[a].tid != 0) + { + if (!AddFilter(filters[a].pid, filters[a].tid)) + ret = false; + } + } + + return ret; +} + diff --git a/eit.h b/eit.h index 28329f42..fec498ba 100644 --- a/eit.h +++ b/eit.h @@ -13,80 +13,127 @@ * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * - * $Id: eit.h 1.1 2000/09/03 10:23:24 kls Exp $ + * $Id: eit.h 1.2 2000/10/29 10:21:56 kls Exp $ ***************************************************************************/ #ifndef __EIT_H #define __EIT_H -#include +#include "thread.h" +#include "tools.h" -typedef struct eit_event { - - bool bIsValid; - char szTitle[512]; - char szSubTitle[512]; - char szDate[12]; - char szTime[12]; - -}eit_event; - -/** - *@author Robert Schneider - */ - -class cEIT { +class cEventInfo : public cListObject { + friend class cSchedule; + friend class cEIT; +private: + unsigned short uServiceID; // Service ID of program for that event + bool bIsFollowing; // true if this is the next event on this channel + bool bIsPresent; // true if this is the present event running + char *pExtendedDescription; // Extended description of this event + char *pSubtitle; // Subtitle of event + char *pTitle; // Title of event + unsigned short uEventID; // Event ID of this event + long lDuration; // duration of event in seconds + time_t tTime; // Start time + u_char cExtendedDescriptorNumber; // current extended descriptor number that has to be inserted + int nChannelNumber; // the actual channel number from VDR's channel list (used in cMenuSchedule for sorting by channel number) +protected: + void SetFollowing(bool foll); + void SetPresent(bool pres); + bool SetTitle(char *string); + void SetServiceID(unsigned short servid); + void SetEventID(unsigned short evid); + void SetDuration(long l); + void SetTime(time_t t); + bool AddExtendedDescription(char *string); + bool SetSubtitle(char *string); + void IncreaseExtendedDescriptorNumber(void); + cEventInfo(unsigned short serviceid, unsigned short eventid); public: - cEIT(); - ~cEIT(); - /** */ - int GetEIT(); - /** */ - int SetProgramNumber(unsigned short pnr); - /** Retrieves the string representing the time of the current event */ - char * GetRunningTime(); - /** Retrieves the string representing the date of the current event */ - char * GetRunningDate(); - /** Retrieves the string for the running subtitle */ - char * GetRunningSubtitle(); - /** retrieves the string for the running title */ - char * GetRunningTitle(); - /** Retrieves the string representing the time of the next event */ - char * GetNextTime(); - /** Retrieves the string representing the date of the next event */ - char * GetNextDate(); - /** Retrieves the string for the next subtitle */ - char * GetNextSubtitle(); - /** retrieves the string for the next title */ - char * GetNextTitle(); - /** */ - bool IsValid(); + ~cEventInfo(); + const char *GetTimeString(void) const; + const char *GetEndTimeString(void) const; + const char *GetDate(void) const; + bool IsFollowing(void) const; + bool IsPresent(void) const; + const char *GetExtendedDescription(void) const; + const char *GetSubtitle(void) const; + const char *GetTitle(void) const; + unsigned short GetEventID(void) const; + long GetDuration(void) const; + time_t GetTime(void) const; + u_char GetExtendedDescriptorNumber(void) const; + unsigned short GetServiceID(void) const; + int GetChannelNumber(void) const { return nChannelNumber; } + void SetChannelNumber(int ChannelNumber) const { ((cEventInfo *)this)->nChannelNumber = ChannelNumber; } // doesn't modify the EIT data, so it's ok to make it 'const' + }; -protected: // Protected attributes - /** Device name of VBI device */ - const char * cszBitFilter; -protected: // Protected attributes - /** handle to VBI device (usually /dev/vbi) */ - int fsvbi; - /** Describes the event next on */ - eit_event evtNext; - /** Describes the running event */ - eit_event evtRunning; -protected: // Protected methods - /** Set the bitfilter in vbi device to return -correct tables */ - int SetBitFilter(unsigned short pid, unsigned short section, unsigned short mode); - /** */ - int GetSection(unsigned char *buf, ushort PID, unsigned char sec); - /** */ - int CloseFilter(unsigned short handle); - /** */ - char * mjd2string(unsigned short mjd); - /** */ - int strdvbcpy(unsigned char *dst, unsigned char *src, int max); -public: // Public attributes - /** */ - unsigned short uProgramNumber; +class cSchedule : public cListObject { + friend class cSchedules; + friend class cEIT; +private: + cEventInfo *pPresent; + cEventInfo *pFollowing; + unsigned short uServiceID; + cList Events; +protected: + void SetServiceID(unsigned short servid); + bool SetFollowingEvent(cEventInfo *pEvent); + bool SetPresentEvent(cEventInfo *pEvent); + void Cleanup(time_t tTime); + void Cleanup(void); + cSchedule(unsigned short servid = 0); +public: + ~cSchedule(); + const cEventInfo *GetPresentEvent(void) const; + const cEventInfo *GetFollowingEvent(void) const; + unsigned short GetServiceID(void) const; + const cEventInfo *GetEvent(unsigned short uEventID) const; + const cEventInfo *GetEvent(time_t tTime) const; + const cEventInfo *GetEventNumber(int n) const { return Events.Get(n); } + int NumEvents(void) const { return Events.Count(); } + }; + +class cSchedules : public cList { + friend class cSIProcessor; +private: + const cSchedule *pCurrentSchedule; + unsigned short uCurrentServiceID; +protected: + bool SetCurrentServiceID(unsigned short servid); + void Cleanup(); +public: + cSchedules(void); + ~cSchedules(); + const cSchedule *GetSchedule(unsigned short servid) const; + const cSchedule *GetSchedule(void) const; }; +typedef struct sip_filter { + + u_char pid; + u_char tid; + int handle; + bool inuse; + +}SIP_FILTER; + +class cSIProcessor : public cThread { +private: + cSchedules *schedules; + bool useTStime; + SIP_FILTER *filters; + int fsvbi; + bool RefreshFilters(void); + void Action(void); +public: + cSIProcessor(const char *FileName); + ~cSIProcessor(); + bool SetUseTSTime(bool use); + bool AddFilter(u_char pid, u_char tid); + bool ShutDownFilters(void); + bool SetCurrentServiceID(unsigned short servid); + const cSchedules *Schedules(void) { return schedules; } + }; + #endif diff --git a/interface.c b/interface.c index 5940195f..3e1232f0 100644 --- a/interface.c +++ b/interface.c @@ -4,14 +4,11 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: interface.c 1.25 2000/10/08 16:34:17 kls Exp $ + * $Id: interface.c 1.26 2000/10/29 12:53:55 kls Exp $ */ #include "interface.h" #include -#include "eit.h" - -cEIT EIT; cInterface *Interface = NULL; @@ -355,46 +352,141 @@ eKeys cInterface::DisplayChannel(int Number, const char *Name, bool WithInfo) Write(-5, 0, buffer); cDvbApi::PrimaryDvbApi->Flush(); - char *RunningTitle = "", *RunningSubtitle = "", *NextTitle = "", *NextSubtitle = ""; - int Lines = 0; - if (Number && WithInfo && EIT.IsValid()) { - if (*(RunningTitle = EIT.GetRunningTitle())) Lines++; - if (*(RunningSubtitle = EIT.GetRunningSubtitle())) Lines++; - if (*(NextTitle = EIT.GetNextTitle())) Lines++; - if (*(NextSubtitle = EIT.GetNextSubtitle())) Lines++; +#define INFO_TIMEOUT 5 + + const cEventInfo *Present = NULL, *Following = NULL; + + int Tries = 0; + if (Number && WithInfo) { + for (; Tries < INFO_TIMEOUT; Tries++) { + { + cThreadLock ThreadLock; + const cSchedules *Schedules = cDvbApi::PrimaryDvbApi->Schedules(&ThreadLock); + if (Schedules) { + const cSchedule *Schedule = Schedules->GetSchedule(); + if (Schedule) { + const char *PresentTitle = NULL, *PresentSubtitle = NULL, *FollowingTitle = NULL, *FollowingSubtitle = NULL; + int Lines = 0; + if ((Present = Schedule->GetPresentEvent()) != NULL) { + PresentTitle = Present->GetTitle(); + if (!isempty(PresentTitle)) + Lines++; + PresentSubtitle = Present->GetSubtitle(); + if (!isempty(PresentSubtitle)) + Lines++; + } + if ((Following = Schedule->GetFollowingEvent()) != NULL) { + FollowingTitle = Following->GetTitle(); + if (!isempty(FollowingTitle)) + Lines++; + FollowingSubtitle = Following->GetSubtitle(); + if (!isempty(FollowingSubtitle)) + Lines++; + } + if (Lines > 0) { + const int t = 6; + int l = 1; + cDvbApi::PrimaryDvbApi->Fill(0, 1, MenuColumns, Lines, clrBackground); + if (!isempty(PresentTitle)) { + Write(0, l, Present->GetTimeString(), clrYellow, clrBackground); + Write(t, l, PresentTitle, clrCyan, clrBackground); + l++; + } + if (!isempty(PresentSubtitle)) { + Write(t, l, PresentSubtitle, clrCyan, clrBackground); + l++; + } + if (!isempty(FollowingTitle)) { + Write(0, l, Following->GetTimeString(), clrYellow, clrBackground); + Write(t, l, FollowingTitle, clrCyan, clrBackground); + l++; + } + if (!isempty(FollowingSubtitle)) { + Write(t, l, FollowingSubtitle, clrCyan, clrBackground); + } + cDvbApi::PrimaryDvbApi->Flush(); + if (Lines == 4) { + Tries = 0; + break; + } + } + } + } + } + eKeys Key = Wait(1, true); + if (Key != kNone) + break; + } } - if (Lines > 0) { - const int t = 6; - int l = 1; - cDvbApi::PrimaryDvbApi->Fill(0, 1, MenuColumns, Lines, clrBackground); - if (*RunningTitle) { - Write(0, l, EIT.GetRunningTime(), clrYellow, clrBackground); - Write(t, l, RunningTitle, clrCyan, clrBackground); - l++; - } - if (*RunningSubtitle) { - Write(t, l, RunningSubtitle, clrCyan, clrBackground); - l++; - } - if (*NextTitle) { - Write(0, l, EIT.GetNextTime(), clrYellow, clrBackground); - Write(t, l, NextTitle, clrCyan, clrBackground); - l++; - } - if (*NextSubtitle) { - Write(t, l, NextSubtitle, clrCyan, clrBackground); - } - cDvbApi::PrimaryDvbApi->Flush(); - } - eKeys Key = Wait(5, true); + eKeys Key = Wait(INFO_TIMEOUT - Tries, true); + Close(); if (Key == kOk) GetKey(); - Close(); + if (Key == kGreen || Key == kYellow) { + GetKey(); + do { + Key = DisplayDescription((Key == kGreen) ? Present : Following); + } while (Key == kGreen || Key == kYellow); + Key = kNone; + } return Key; } return kNone; } +eKeys cInterface::DisplayDescription(const cEventInfo *EventInfo) +{ + eKeys Key = kNone; + + if (EventInfo) { + int line = 0; + + Open(); + Clear(); + + char buffer[MenuColumns + 1]; + snprintf(buffer, sizeof(buffer), "%s %s", EventInfo->GetDate() ? EventInfo->GetDate() : "", EventInfo->GetTimeString() ? EventInfo->GetTimeString() : ""); + Write(-strlen(buffer), line, buffer, clrYellow); + + line = WriteParagraph(line, EventInfo->GetTitle()); + line = WriteParagraph(line, EventInfo->GetSubtitle()); + line = WriteParagraph(line, EventInfo->GetExtendedDescription()); + + Key = Wait(300); + Close(); + } + return Key; +} + +int cInterface::WriteParagraph(int Line, const char *Text) +{ + if (Line < MenuLines && Text) { + Line++; + char *s = strdup(Text); + char *pStart = s, *pEnd; + char *pEndText = &s[strlen(s) - 1]; + + while (pStart < pEndText) { + if (strlen(pStart) > (unsigned)(MenuColumns - 2)) + pEnd = &pStart[MenuColumns - 2]; + else + pEnd = &pStart[strlen(pStart)]; + + while (*pEnd != 0 && *pEnd != ' ' && pEnd > pStart) + pEnd--; + + //XXX what if there are no blanks??? + //XXX need to scroll if text is longer + *pEnd = 0; + Write(1, Line++, pStart, clrCyan); + if (Line >= MenuLines) + return Line; + pStart = pEnd + 1; + } + } + return Line; +} + void cInterface::DisplayRecording(int Index, bool On) { rcIo->SetPoints(1 << Index, On); diff --git a/interface.h b/interface.h index 8d0f8f68..684adfec 100644 --- a/interface.h +++ b/interface.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: interface.h 1.16 2000/10/08 12:15:49 kls Exp $ + * $Id: interface.h 1.17 2000/10/29 12:32:12 kls Exp $ */ #ifndef __INTERFACE_H @@ -28,6 +28,8 @@ private: void QueryKeys(void); void HelpButton(int Index, const char *Text, eDvbColor FgColor, eDvbColor BgColor); eKeys Wait(int Seconds = 1, bool KeepChar = false); + eKeys DisplayDescription(const cEventInfo *EventInfo); + int WriteParagraph(int Line, const char *Text); public: cInterface(int SVDRPport = 0); ~cInterface(); diff --git a/menu.c b/menu.c index 39689c9c..d5b0a32f 100644 --- a/menu.c +++ b/menu.c @@ -4,15 +4,17 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: menu.c 1.36 2000/10/08 16:11:22 kls Exp $ + * $Id: menu.c 1.37 2000/10/29 11:23:33 kls Exp $ */ #include "menu.h" #include #include #include +#include #include #include "config.h" +#include "eit.h" #define MENUTIMEOUT 120 // seconds @@ -973,6 +975,300 @@ eOSState cMenuTimers::ProcessKey(eKeys Key) return state; } +// --- cMenuEventItem -------------------------------------------------------- + +class cMenuEventItem : public cOsdItem { +public: + cMenuEventItem(const char *Text); +}; + +cMenuEventItem::cMenuEventItem(const char *Text) +:cOsdItem(Text, osBack) +{ +} + +// --- cMenuEvent ------------------------------------------------------------ + +class cMenuEvent : public cOsdMenu { +private: + void AddParagraph(const char *text); + const cEventInfo *eventInfo; +public: + cMenuEvent(const cEventInfo *EventInfo, bool CanSwitch = false); +}; + +cMenuEvent::cMenuEvent(const cEventInfo *EventInfo, bool CanSwitch) +:cOsdMenu("Event", 1) +{ + const char *p; + char buffer[MenuColumns + 1]; + + eventInfo = EventInfo; + + cChannel *channel = Channels.GetByServiceID(eventInfo->GetServiceID()); + + snprintf(buffer, sizeof(buffer), "\t%-17.*s %.*s %s - %s", 17, channel->name, 5, eventInfo->GetDate(), eventInfo->GetTimeString(), eventInfo->GetEndTimeString()); + Add(new cMenuEventItem(buffer)); + if ((p = eventInfo->GetTitle()) != NULL && *p) { + Add(new cMenuEventItem("")); + AddParagraph(p); + } + if ((p = eventInfo->GetSubtitle()) != NULL && *p) { + Add(new cMenuEventItem("")); + AddParagraph(p); + } + if ((p = eventInfo->GetExtendedDescription()) != NULL && *p) { + Add(new cMenuEventItem("")); + AddParagraph(p); + } + SetHelp("Record", NULL, NULL, CanSwitch ? "Switch" : NULL); +} + +void cMenuEvent::AddParagraph(const char *text) +{ + char *ptextsave = strdup(text); + + if (ptextsave) { + + int column = 1; + char buffer[MenuColumns + 1]; + char *pStart = ptextsave; + char *pEndText = &ptextsave[strlen(text) - 1]; + + while (pStart < pEndText) { + char *pEnd; + if (strlen(pStart) > (unsigned)(MenuColumns - column - 2)) + pEnd = &pStart[MenuColumns - column - 2]; + else + pEnd = &pStart[strlen(pStart)]; + + while (*pEnd && *pEnd != ' ' && pEnd > pStart) + pEnd--; + + *pEnd = 0; + sprintf(buffer, "\t%s", pStart); + Add(new cMenuEventItem(buffer)); + pStart = pEnd + 1; + } + } + + delete ptextsave; +} + +// --- cMenuWhatsOnItem ------------------------------------------------------ + +class cMenuWhatsOnItem : public cOsdItem { +public: + const cEventInfo *eventInfo; + cMenuWhatsOnItem(const cEventInfo *EventInfo); +}; + +cMenuWhatsOnItem::cMenuWhatsOnItem(const cEventInfo *EventInfo) +{ + eventInfo = EventInfo; + char *buffer = NULL; + cChannel *channel = Channels.GetByNumber(eventInfo->GetChannelNumber()); + asprintf(&buffer, "%d\t%.*s\t%.*s\t%s", eventInfo->GetChannelNumber(), 6, channel ? channel->name : "???", 5, eventInfo->GetTimeString(), eventInfo->GetTitle()); + SetText(buffer, false); +} + +// --- cMenuWhatsOn ---------------------------------------------------------- + +class cMenuWhatsOn : public cOsdMenu { +private: + eOSState Record(void); + eOSState Switch(void); +public: + cMenuWhatsOn(const cSchedules *Schedules, bool Now); + virtual eOSState ProcessKey(eKeys Key); + }; + +static int CompareEventChannel(const void *p1, const void *p2) +{ + return (int)( (*(const cEventInfo **)p1)->GetChannelNumber() - (*(const cEventInfo **)p2)->GetChannelNumber()); +} + +cMenuWhatsOn::cMenuWhatsOn(const cSchedules *Schedules, bool Now) +:cOsdMenu(Now ? "What's on now?" : "What's on next?", 4, 7, 6) +{ + const cSchedule *Schedule = Schedules->First(); + const cEventInfo **pArray = NULL; + int num = 0; + + while (Schedule) { + pArray = (const cEventInfo **)realloc(pArray, (num + 1) * sizeof(cEventInfo *)); + + pArray[num] = Now ? Schedule->GetPresentEvent() : Schedule->GetFollowingEvent(); + if (pArray[num]) { + cChannel *channel = Channels.GetByServiceID(pArray[num]->GetServiceID()); + if (channel) { + pArray[num]->SetChannelNumber(channel->number); + num++; + } + } + Schedule = (const cSchedule *)Schedules->Next(Schedule); + } + + qsort(pArray, num, sizeof(cEventInfo *), CompareEventChannel); + + for (int a = 0; a < num; a++) + Add(new cMenuWhatsOnItem(pArray[a])); + + delete pArray; + SetHelp("Record", Now ? "Next" : "Now", "Schedule", "Switch"); +} + +eOSState cMenuWhatsOn::Switch(void) +{ + cMenuWhatsOnItem *item = (cMenuWhatsOnItem *)Get(Current()); + if (item) { + cChannel *channel = Channels.GetByServiceID(item->eventInfo->GetServiceID()); + if (channel && channel->Switch()) + return osEnd; + } + Interface->Error("Can't switch channel!"); + return osContinue; +} + +eOSState cMenuWhatsOn::Record(void) +{ + cMenuWhatsOnItem *item = (cMenuWhatsOnItem *)Get(Current()); + if (item) { + cTimer *timer = new cTimer(item->eventInfo); + Timers.Add(timer); + Timers.Save(); + isyslog(LOG_INFO, "timer %d added", timer->Index() + 1); + return AddSubMenu(new cMenuEditTimer(timer->Index(), true)); + } + return osContinue; +} + +eOSState cMenuWhatsOn::ProcessKey(eKeys Key) +{ + eOSState state = cOsdMenu::ProcessKey(Key); + + if (state == osUnknown) { + switch (Key) { + case kRed: return Record(); + case kYellow: return osBack; + case kBlue: return Switch(); + case kOk: return AddSubMenu(new cMenuEvent(((cMenuWhatsOnItem *)Get(Current()))->eventInfo, true)); + default: break; + } + } + return state; +} + +// --- cMenuScheduleItem ----------------------------------------------------- + +class cMenuScheduleItem : public cOsdItem { +public: + const cEventInfo *eventInfo; + cMenuScheduleItem(const cEventInfo *EventInfo); +}; + +cMenuScheduleItem::cMenuScheduleItem(const cEventInfo *EventInfo) +{ + eventInfo = EventInfo; + char *buffer = NULL; + asprintf(&buffer, "%.*s\t%.*s\t%s", 5, eventInfo->GetDate(), 5, eventInfo->GetTimeString(), eventInfo->GetTitle()); + SetText(buffer, false); +} + +// --- cMenuSchedule --------------------------------------------------------- + +class cMenuSchedule : public cOsdMenu { +private: + cThreadLock threadLock; + const cSchedules *schedules; + bool now, next; + eOSState Record(void); + void PrepareSchedule(void); + void PrepareWhatsOnNext(bool On); +public: + cMenuSchedule(void); + virtual eOSState ProcessKey(eKeys Key); + }; + +cMenuSchedule::cMenuSchedule(void) +:cOsdMenu("Schedule", 6, 6) +{ + now = next = false; + cChannel *channel = Channels.GetByNumber(CurrentChannel); + if (channel) { + char *buffer = NULL; + asprintf(&buffer, "Schedule - %s", channel->name); + SetTitle(buffer, false); + } + PrepareSchedule(); + SetHelp("Record", "Now", "Next"); +} + +static int CompareEventTime(const void *p1, const void *p2) +{ + return (int)((*(cEventInfo **)p1)->GetTime() - (*(cEventInfo **)p2)->GetTime()); +} + +void cMenuSchedule::PrepareSchedule(void) +{ + schedules = cDvbApi::PrimaryDvbApi->Schedules(&threadLock); + if (schedules) { + const cSchedule *Schedule = schedules->GetSchedule(); + int num = Schedule->NumEvents(); + const cEventInfo **pArray = (const cEventInfo **)malloc(num * sizeof(cEventInfo *)); + if (pArray) { + time_t now = time(NULL); + int numreal = 0; + for (int a = 0; a < num; a++) { + const cEventInfo *EventInfo = Schedule->GetEventNumber(a); + if (EventInfo->GetTime() + EventInfo->GetDuration() > now) + pArray[numreal++] = EventInfo; + } + + qsort(pArray, numreal, sizeof(cEventInfo *), CompareEventTime); + + for (int a = 0; a < numreal; a++) + Add(new cMenuScheduleItem(pArray[a])); + delete pArray; + } + } +} + +eOSState cMenuSchedule::Record(void) +{ + cMenuScheduleItem *item = (cMenuScheduleItem *)Get(Current()); + if (item) { + cTimer *timer = new cTimer(item->eventInfo); + Timers.Add(timer); + Timers.Save(); + isyslog(LOG_INFO, "timer %d added", timer->Index() + 1); + return AddSubMenu(new cMenuEditTimer(timer->Index(), true)); + } + return osContinue; +} + +eOSState cMenuSchedule::ProcessKey(eKeys Key) +{ + eOSState state = cOsdMenu::ProcessKey(Key); + + if (state == osUnknown) { + switch (Key) { + case kRed: return Record(); + case kGreen: if (!now && !next) { + now = true; + return AddSubMenu(new cMenuWhatsOn(schedules, true)); + } + now = !now; + next = !next; + return AddSubMenu(new cMenuWhatsOn(schedules, now)); + case kYellow: return AddSubMenu(new cMenuWhatsOn(schedules, false)); + case kOk: return AddSubMenu(new cMenuEvent(((cMenuScheduleItem *)Get(Current()))->eventInfo)); + default: break; + } + } + return state; +} + // --- cMenuRecordingItem ---------------------------------------------------- class cMenuRecordingItem : public cOsdItem { @@ -1089,6 +1385,9 @@ cMenuSetup::cMenuSetup(void) Add(new cMenuEditBoolItem("MarkInstantRecord", &data.MarkInstantRecord)); Add(new cMenuEditIntItem( "LnbFrequLo", &data.LnbFrequLo)); Add(new cMenuEditIntItem( "LnbFrequHi", &data.LnbFrequHi)); + Add(new cMenuEditIntItem( "SetSystemTime", &data.SetSystemTime)); + Add(new cMenuEditIntItem( "MarginStart", &data.MarginStart)); + Add(new cMenuEditIntItem( "MarginStop", &data.MarginStop)); } eOSState cMenuSetup::ProcessKey(eKeys Key) @@ -1098,6 +1397,7 @@ eOSState cMenuSetup::ProcessKey(eKeys Key) if (state == osUnknown) { switch (Key) { case kOk: state = (Setup.PrimaryDVB != data.PrimaryDVB) ? osSwitchDvb : osBack; + cDvbApi::PrimaryDvbApi->SetUseTSTime(data.SetSystemTime); Setup = data; Setup.Save(); break; @@ -1114,6 +1414,7 @@ eOSState cMenuSetup::ProcessKey(eKeys Key) cMenuMain::cMenuMain(bool Replaying) :cOsdMenu("Main") { + Add(new cOsdItem("Schedule", osSchedule)); Add(new cOsdItem("Channels", osChannels)); Add(new cOsdItem("Timer", osTimer)); Add(new cOsdItem("Recordings", osRecordings)); @@ -1137,6 +1438,7 @@ eOSState cMenuMain::ProcessKey(eKeys Key) eOSState state = cOsdMenu::ProcessKey(Key); switch (state) { + case osSchedule: return AddSubMenu(new cMenuSchedule); case osChannels: return AddSubMenu(new cMenuChannels); case osTimer: return AddSubMenu(new cMenuTimers); case osRecordings: return AddSubMenu(new cMenuRecordings); diff --git a/osd.c b/osd.c index 75d9eade..3225e681 100644 --- a/osd.c +++ b/osd.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: osd.c 1.9 2000/10/08 12:20:34 kls Exp $ + * $Id: osd.c 1.10 2000/10/28 09:33:47 kls Exp $ */ #include "osd.h" @@ -24,7 +24,7 @@ cOsdItem::cOsdItem(eOSState State) bgColor = clrBackground; } -cOsdItem::cOsdItem(char *Text, eOSState State) +cOsdItem::cOsdItem(const char *Text, eOSState State) { text = NULL; offset = -1; @@ -74,7 +74,7 @@ eOSState cOsdItem::ProcessKey(eKeys Key) // --- cOsdMenu -------------------------------------------------------------- -cOsdMenu::cOsdMenu(char *Title, int c0, int c1, int c2, int c3, int c4) +cOsdMenu::cOsdMenu(const char *Title, int c0, int c1, int c2, int c3, int c4) { visible = false; title = strdup(Title); @@ -108,6 +108,12 @@ void cOsdMenu::SetStatus(const char *s) Interface->Status(status); } +void cOsdMenu::SetTitle(const char *Title, bool Copy) +{ + delete title; + title = Copy ? strdup(Title) : Title; +} + void cOsdMenu::SetHelp(const char *Red, const char *Green, const char *Yellow, const char *Blue) { // strings are NOT copied - must be constants!!! diff --git a/osd.h b/osd.h index 876c87cf..5e2ad300 100644 --- a/osd.h +++ b/osd.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: osd.h 1.11 2000/09/10 09:50:38 kls Exp $ + * $Id: osd.h 1.12 2000/10/28 09:32:59 kls Exp $ */ #ifndef __OSD_H @@ -19,6 +19,7 @@ enum eOSState { osUnknown, osMenu, osContinue, + osSchedule, osChannels, osTimer, osRecordings, @@ -43,7 +44,7 @@ protected: eDvbColor fgColor, bgColor; public: cOsdItem(eOSState State = osUnknown); - cOsdItem(char *Text, eOSState State = osUnknown); + cOsdItem(const char *Text, eOSState State = osUnknown); virtual ~cOsdItem(); bool HasUserColor(void) { return userColor; } void SetText(const char *Text, bool Copy = true); @@ -66,7 +67,7 @@ public: class cOsdMenu : public cOsdBase, public cList { private: - char *title; + const char *title; int cols[cInterface::MaxCols]; int first, current, marked; cOsdMenu *subMenu; @@ -83,10 +84,11 @@ protected: eOSState AddSubMenu(cOsdMenu *SubMenu); bool HasSubMenu(void) { return subMenu; } void SetStatus(const char *s); + void SetTitle(const char *Title, bool Copy = true); void SetHelp(const char *Red, const char *Green = NULL, const char *Yellow = NULL, const char *Blue = NULL); virtual void Del(int Index); public: - cOsdMenu(char *Title, int c0 = 0, int c1 = 0, int c2 = 0, int c3 = 0, int c4 = 0); + cOsdMenu(const char *Title, int c0 = 0, int c1 = 0, int c2 = 0, int c3 = 0, int c4 = 0); virtual ~cOsdMenu(); int Current(void) { return current; } void Add(cOsdItem *Item, bool Current = false); diff --git a/thread.c b/thread.c index 7304e682..b124581f 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.2 2000/10/08 16:45:50 kls Exp $ + * $Id: thread.c 1.3 2000/10/28 15:26:02 kls Exp $ */ #include "thread.h" @@ -88,16 +88,27 @@ void cThread::WakeUp(void) cThreadLock::cThreadLock(cThread *Thread) { - thread = Thread; - locked = Thread->Lock(); + thread = NULL; + locked = false; + Lock(Thread); } cThreadLock::~cThreadLock() { - if (locked) + if (thread && locked) thread->Unlock(); } +bool cThreadLock::Lock(cThread *Thread) +{ + if (Thread && !thread) { + thread = Thread; + locked = Thread->Lock(); + return locked; + } + return false; +} + bool cThreadLock::Locked(void) { return locked; diff --git a/thread.h b/thread.h index 86b9e922..b47f6d71 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.1 2000/10/08 08:36:21 kls Exp $ + * $Id: thread.h 1.2 2000/10/28 15:08:09 kls Exp $ */ #ifndef __THREAD_H @@ -47,8 +47,9 @@ private: cThread *thread; bool locked; public: - cThreadLock(cThread *Thread); + cThreadLock(cThread *Thread = NULL); ~cThreadLock(); + bool Lock(cThread *Thread); bool Locked(void); }; diff --git a/tools.c b/tools.c index fc15a985..4acea0b3 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.21 2000/10/07 18:02:24 kls Exp $ + * $Id: tools.c 1.22 2000/10/29 11:21:55 kls Exp $ */ #define _GNU_SOURCE @@ -85,11 +85,16 @@ char *strreplace(char *s, char c1, char c2) return s; } -char *skipspace(char *s) +char *skipspace(const char *s) { while (*s && isspace(*s)) s++; - return s; + return (char *)s; +} + +bool isempty(const char *s) +{ + return !(s && *skipspace(s)); } int time_ms(void) @@ -520,7 +525,7 @@ void cListBase::Clear(void) objects = lastObject = NULL; } -cListObject *cListBase::Get(int Index) +cListObject *cListBase::Get(int Index) const { if (Index < 0) return NULL; @@ -530,7 +535,7 @@ cListObject *cListBase::Get(int Index) return object; } -int cListBase::Count(void) +int cListBase::Count(void) const { int n = 0; cListObject *object = objects; diff --git a/tools.h b/tools.h index 17b643ff..b5bdd278 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.17 2000/10/07 18:00:21 kls Exp $ + * $Id: tools.h 1.18 2000/10/29 11:19:20 kls Exp $ */ #ifndef __TOOLS_H @@ -40,7 +40,8 @@ void purge(int filedes); char *readline(FILE *f); char *strn0cpy(char *dest, const char *src, size_t n); char *strreplace(char *s, char c1, char c2); -char *skipspace(char *s); +char *skipspace(const char *s); +bool isempty(const char *s); int time_ms(void); void delay_ms(int ms); bool isnumber(const char *s); @@ -80,8 +81,8 @@ public: void Append(cListObject *Object); void Unlink(void); int Index(void); - cListObject *Prev(void) { return prev; } - cListObject *Next(void) { return next; } + cListObject *Prev(void) const { return prev; } + cListObject *Next(void) const { return next; } }; class cListBase { @@ -95,15 +96,15 @@ public: virtual void Move(int From, int To); void Move(cListObject *From, cListObject *To); void Clear(void); - cListObject *Get(int Index); - int Count(void); + cListObject *Get(int Index) const; + int Count(void) const; }; template class cList : public cListBase { public: - T *Get(int Index) { return (T *)cListBase::Get(Index); } - T *First(void) { return (T *)objects; } - T *Next(T *object) { return (T *)object->Next(); } + T *Get(int Index) const { return (T *)cListBase::Get(Index); } + T *First(void) const { return (T *)objects; } + T *Next(const T *object) const { return (T *)object->Next(); } }; #endif //__TOOLS_H