Home

Dokumentation

Impressum

Dokumentation VDR
 

Main Page   Class Hierarchy   Alphabetical List   Data Structures   File List   Data Fields   Globals  

eit.c

Go to the documentation of this file.
00001 /***************************************************************************
00002                           eit.c  -  description
00003                              -------------------
00004     begin                : Fri Aug 25 2000
00005     copyright            : (C) 2000 by Robert Schneider
00006     email                : Robert.Schneider@web.de
00007 
00008     2001-08-15: Adapted to 'libdtv' by Rolf Hakenes <hakenes@hippomi.de>
00009 
00010  ***************************************************************************/
00011 
00012 /***************************************************************************
00013  *                                                                         *
00014  *   This program is free software; you can redistribute it and/or modify  *
00015  *   it under the terms of the GNU General Public License as published by  *
00016  *   the Free Software Foundation; either version 2 of the License, or     *
00017  *   (at your option) any later version.                                   *
00018  *                                                                         *
00019  * $Id: eit.c 1.61 2002/11/24 14:37:38 kls Exp $
00020  ***************************************************************************/
00021 
00022 #include "eit.h"
00023 #include <ctype.h>
00024 #include <fcntl.h>
00025 #include <limits.h>
00026 #include <linux/dvb/dmx.h>
00027 #include <stdio.h>
00028 #include <stdlib.h>
00029 #include <string.h>
00030 #include <sys/ioctl.h>
00031 #include <sys/poll.h>
00032 #include <sys/stat.h>
00033 #include <sys/types.h>
00034 #include <time.h>
00035 #include <unistd.h>
00036 #include "channels.h"
00037 #include "config.h"
00038 #include "libdtv/libdtv.h"
00039 #include "videodir.h"
00040 
00041 // --- cMJD ------------------------------------------------------------------
00042 
00043 class cMJD {
00044 public:
00045    cMJD();
00046    cMJD(u_char date_hi, u_char date_lo);
00047    cMJD(u_char date_hi, u_char date_lo, u_char timehr, u_char timemi, u_char timese);
00048    ~cMJD();
00050   void ConvertToTime();
00052   bool SetSystemTime();
00054   time_t GetTime_t();
00055 protected: // Protected attributes
00057   time_t mjdtime;
00058 protected: // Protected attributes
00060   u_char time_second;
00061 protected: // Protected attributes
00063   u_char time_minute;
00064 protected: // Protected attributes
00066   u_char time_hour;
00067 protected: // Protected attributes
00069   u_short mjd;
00070 };
00071 
00072 cMJD::cMJD()
00073 {
00074 }
00075 
00076 cMJD::cMJD(u_char date_hi, u_char date_lo)
00077 {
00078    mjd = date_hi << 8 | date_lo;
00079    time_hour = time_minute = time_second = 0;
00080    ConvertToTime();
00081 }
00082 
00083 cMJD::cMJD(u_char date_hi, u_char date_lo, u_char timehr, u_char timemi, u_char timese)
00084 {
00085    mjd = date_hi << 8 | date_lo;
00086    time_hour = timehr;
00087    time_minute = timemi;
00088    time_second = timese;
00089    ConvertToTime();
00090 }
00091 
00092 cMJD::~cMJD()
00093 {
00094 }
00095 
00097 void cMJD::ConvertToTime()
00098 {
00099    struct tm t;
00100 
00101    t.tm_sec = time_second;
00102    t.tm_min = time_minute;
00103    t.tm_hour = time_hour;
00104    int k;
00105 
00106    t.tm_year = (int) ((mjd - 15078.2) / 365.25);
00107    t.tm_mon = (int) ((mjd - 14956.1 - (int)(t.tm_year * 365.25)) / 30.6001);
00108    t.tm_mday = (int) (mjd - 14956 - (int)(t.tm_year * 365.25) - (int)(t.tm_mon * 30.6001));
00109    k = (t.tm_mon == 14 || t.tm_mon == 15) ? 1 : 0;
00110    t.tm_year = t.tm_year + k;
00111    t.tm_mon = t.tm_mon - 1 - k * 12;
00112    t.tm_mon--;
00113 
00114    t.tm_isdst = -1;
00115    t.tm_gmtoff = 0;
00116 
00117    mjdtime = timegm(&t);
00118 
00119    //isyslog("Time parsed = %s\n", ctime(&mjdtime));
00120 }
00121 
00123 bool cMJD::SetSystemTime()
00124 {
00125    struct tm *ptm;
00126    time_t loctim;
00127 
00128    struct tm tm_r;
00129    ptm = localtime_r(&mjdtime, &tm_r);
00130    loctim = time(NULL);
00131 
00132    if (abs(mjdtime - loctim) > 2)
00133    {
00134       isyslog("System Time = %s (%ld)\n", ctime(&loctim), loctim);
00135       isyslog("Local Time  = %s (%ld)\n", ctime(&mjdtime), mjdtime);
00136       if (stime(&mjdtime) < 0)
00137          esyslog("ERROR while setting system time: %m");
00138       return true;
00139    }
00140 
00141    return false;
00142 }
00144 time_t cMJD::GetTime_t()
00145 {
00146    return mjdtime;
00147 }
00148 
00149 // --- cTDT ------------------------------------------------------------------
00150 
00151 class cTDT {
00152 public:
00153    cTDT(tdt_t *ptdt);
00154    ~cTDT();
00156   bool SetSystemTime();
00157 protected: // Protected attributes
00159   tdt_t tdt;
00161   cMJD mjd; // kls 2001-03-02: made this a member instead of a pointer (it wasn't deleted in the destructor!)
00162 };
00163 
00164 #define BCD2DEC(b) (((b >> 4) & 0x0F) * 10 + (b & 0x0F))
00165 
00166 cTDT::cTDT(tdt_t *ptdt)
00167 :tdt(*ptdt)
00168 ,mjd(tdt.utc_mjd_hi, tdt.utc_mjd_lo, BCD2DEC(tdt.utc_time_h), BCD2DEC(tdt.utc_time_m), BCD2DEC(tdt.utc_time_s))
00169 {
00170 }
00171 
00172 cTDT::~cTDT()
00173 {
00174 }
00176 bool cTDT::SetSystemTime()
00177 {
00178    return mjd.SetSystemTime();
00179 }
00180 
00181 // --- cEventInfo ------------------------------------------------------------
00182 
00183 cEventInfo::cEventInfo(tChannelID channelid, unsigned short eventid)
00184 {
00185    pTitle = NULL;
00186    pSubtitle = NULL;
00187    pExtendedDescription = NULL;
00188    bIsPresent = bIsFollowing = false;
00189    lDuration = 0;
00190    tTime = 0;
00191    uTableID = 0;
00192    uEventID = eventid;
00193    channelID = channelid;
00194    nChannelNumber = 0;
00195 }
00196 
00197 cEventInfo::~cEventInfo()
00198 {
00199    free(pTitle);
00200    free(pSubtitle);
00201    free(pExtendedDescription);
00202 }
00203 
00205 const char * cEventInfo::GetTitle() const
00206 {
00207    return pTitle;
00208 }
00210 const char * cEventInfo::GetSubtitle() const
00211 {
00212    return pSubtitle;
00213 }
00215 const char * cEventInfo::GetExtendedDescription() const
00216 {
00217    return pExtendedDescription;
00218 }
00220 bool cEventInfo::IsPresent() const
00221 {
00222    return bIsPresent;
00223 }
00225 void cEventInfo::SetPresent(bool pres)
00226 {
00227    bIsPresent = pres;
00228 }
00230 bool cEventInfo::IsFollowing() const
00231 {
00232    return bIsFollowing;
00233 }
00234 
00235 void cEventInfo::SetTableID(unsigned char tableid)
00236 {
00237    uTableID = tableid;
00238 }
00239 
00241 void cEventInfo::SetFollowing(bool foll)
00242 {
00243    bIsFollowing = foll;
00244 }
00246 const char * cEventInfo::GetDate() const
00247 {
00248    static char szDate[25];
00249 
00250    struct tm tm_r;
00251    strftime(szDate, sizeof(szDate), "%d.%m.%Y", localtime_r(&tTime, &tm_r));
00252 
00253    return szDate;
00254 }
00255 
00256 const unsigned char cEventInfo::GetTableID(void) const
00257 {
00258    return uTableID;
00259 }
00260 
00262 const char * cEventInfo::GetTimeString() const
00263 {
00264    static char szTime[25];
00265 
00266    struct tm tm_r;
00267    strftime(szTime, sizeof(szTime), "%R", localtime_r(&tTime, &tm_r));
00268 
00269    return szTime;
00270 }
00272 const char * cEventInfo::GetEndTimeString() const
00273 {
00274    static char szEndTime[25];
00275    time_t tEndTime = tTime + lDuration;
00276 
00277    struct tm tm_r;
00278    strftime(szEndTime, sizeof(szEndTime), "%R", localtime_r(&tEndTime, &tm_r));
00279 
00280    return szEndTime;
00281 }
00283 time_t cEventInfo::GetTime() const
00284 {
00285    return tTime;
00286 }
00288 long cEventInfo::GetDuration() const
00289 {
00290    return lDuration;
00291 }
00293 unsigned short cEventInfo::GetEventID() const
00294 {
00295    return uEventID;
00296 }
00298 void cEventInfo::SetTitle(const char *string)
00299 {
00300    pTitle = strcpyrealloc(pTitle, string);
00301 }
00303 void cEventInfo::SetSubtitle(const char *string)
00304 {
00305    pSubtitle = strcpyrealloc(pSubtitle, string);
00306 }
00308 void cEventInfo::SetExtendedDescription(const char *string)
00309 {
00310    pExtendedDescription = strcpyrealloc(pExtendedDescription, string);
00311 }
00313 void cEventInfo::SetTime(time_t t)
00314 {
00315    tTime = t;
00316 }
00318 void cEventInfo::SetDuration(long l)
00319 {
00320    lDuration = l;
00321 }
00323 void cEventInfo::SetEventID(unsigned short evid)
00324 {
00325    uEventID = evid;
00326 }
00328 void cEventInfo::SetChannelID(tChannelID channelid)
00329 {
00330    channelID = channelid;
00331 }
00332 
00334 tChannelID cEventInfo::GetChannelID() const
00335 {
00336    return channelID;
00337 }
00338 
00340 void cEventInfo::Dump(FILE *f, const char *Prefix) const
00341 {
00342    if (tTime + lDuration >= time(NULL)) {
00343       fprintf(f, "%sE %u %ld %ld %X\n", Prefix, uEventID, tTime, lDuration, uTableID);
00344       if (!isempty(pTitle))
00345          fprintf(f, "%sT %s\n", Prefix, pTitle);
00346       if (!isempty(pSubtitle))
00347          fprintf(f, "%sS %s\n", Prefix, pSubtitle);
00348       if (!isempty(pExtendedDescription))
00349          fprintf(f, "%sD %s\n", Prefix, pExtendedDescription);
00350       fprintf(f, "%se\n", Prefix);
00351       }
00352 }
00353 
00354 bool cEventInfo::Read(FILE *f, cSchedule *Schedule)
00355 {
00356   if (Schedule) {
00357      cEventInfo *pEvent = NULL;
00358      char *s;
00359      while ((s = readline(f)) != NULL) {
00360            char *t = skipspace(s + 1);
00361            switch (*s) {
00362              case 'E': if (!pEvent) {
00363                           unsigned int uEventID;
00364                           time_t tTime;
00365                           long lDuration;
00366                           unsigned int uTableID = 0;
00367                           int n = sscanf(t, "%u %ld %ld %X", &uEventID, &tTime, &lDuration, &uTableID);
00368                           if (n == 3 || n == 4) {
00369                              pEvent = (cEventInfo *)Schedule->GetEvent(uEventID, tTime);
00370                              if (!pEvent)
00371                                 pEvent = Schedule->AddEvent(new cEventInfo(Schedule->GetChannelID(), uEventID));
00372                              if (pEvent) {
00373                                 pEvent->SetTableID(uTableID);
00374                                 pEvent->SetTime(tTime);
00375                                 pEvent->SetDuration(lDuration);
00376                                 }
00377                              }
00378                           }
00379                        break;
00380              case 'T': if (pEvent)
00381                           pEvent->SetTitle(t);
00382                        break;
00383              case 'S': if (pEvent)
00384                           pEvent->SetSubtitle(t);
00385                        break;
00386              case 'D': if (pEvent)
00387                           pEvent->SetExtendedDescription(t);
00388                        break;
00389              case 'e': pEvent = NULL;
00390                        break;
00391              case 'c': // to keep things simple we react on 'c' here
00392                        return true;
00393              default:  esyslog("ERROR: unexpected tag while reading EPG data: %s", s);
00394                        return false;
00395              }
00396            }
00397      esyslog("ERROR: unexpected end of file while reading EPG data");
00398      }
00399   return false;
00400 }
00401 
00402 #define MAXEPGBUGFIXSTATS 6
00403 #define MAXEPGBUGFIXCHANS 50
00404 struct tEpgBugFixStats {
00405   int hits;
00406   int n;
00407   tChannelID channelIDs[MAXEPGBUGFIXCHANS];
00408   tEpgBugFixStats(void) { hits = n = 0; }
00409   };
00410 
00411 tEpgBugFixStats EpgBugFixStats[MAXEPGBUGFIXSTATS];
00412 
00413 static void EpgBugFixStat(int Number, tChannelID ChannelID)
00414 {
00415   if (0 <= Number && Number < MAXEPGBUGFIXSTATS) {
00416      tEpgBugFixStats *p = &EpgBugFixStats[Number];
00417      p->hits++;
00418      int i = 0;
00419      for (; i < p->n; i++) {
00420          if (p->channelIDs[i] == ChannelID)
00421             break;
00422          }
00423      if (i == p->n && p->n < MAXEPGBUGFIXCHANS)
00424         p->channelIDs[p->n++] = ChannelID;
00425      }
00426 }
00427 
00428 static void ReportEpgBugFixStats(bool Reset = false)
00429 {
00430   if (Setup.EPGBugfixLevel > 0) {
00431      bool GotHits = false;
00432      char buffer[1024];
00433      for (int i = 0; i < MAXEPGBUGFIXSTATS; i++) {
00434          const char *delim = "\t";
00435          tEpgBugFixStats *p = &EpgBugFixStats[i];
00436          if (p->hits) {
00437             if (!GotHits) {
00438                dsyslog("=====================");
00439                dsyslog("EPG bugfix statistics");
00440                dsyslog("=====================");
00441                dsyslog("IF SOMEBODY WHO IS IN CHARGE OF THE EPG DATA FOR ONE OF THE LISTED");
00442                dsyslog("CHANNELS READS THIS: PLEASE TAKE A LOOK AT THE FUNCTION cEventInfo::FixEpgBugs()");
00443                dsyslog("IN VDR/eit.c TO LEARN WHAT'S WRONG WITH YOUR DATA, AND FIX IT!");
00444                dsyslog("=====================");
00445                dsyslog("Fix\tHits\tChannels");
00446                GotHits = true;
00447                }
00448             char *q = buffer;
00449             q += snprintf(q, sizeof(buffer) - (q - buffer), "%d\t%d", i, p->hits);
00450             for (int c = 0; c < p->n; c++) {
00451                 cChannel *channel = Channels.GetByChannelID(p->channelIDs[c], true);
00452                 if (channel) {
00453                    q += snprintf(q, sizeof(buffer) - (q - buffer), "%s%s", delim, channel->Name());
00454                    delim = ", ";
00455                    }
00456                 }
00457             dsyslog("%s", buffer);
00458             }
00459          if (Reset)
00460             p->hits = p->n = 0;
00461          }
00462      if (GotHits)
00463         dsyslog("=====================");
00464      }
00465 }
00466 
00467 void cEventInfo::FixEpgBugs(void)
00468 {
00469   // VDR can't usefully handle newline characters in the EPG data, so let's
00470   // always convert them to blanks (independent of the setting of EPGBugfixLevel):
00471   strreplace(pTitle, '\n', ' ');
00472   strreplace(pSubtitle, '\n', ' ');
00473   strreplace(pExtendedDescription, '\n', ' ');
00474 
00475   if (Setup.EPGBugfixLevel == 0)
00476      return;
00477 
00478   // Some TV stations apparently have their own idea about how to fill in the
00479   // EPG data. Let's fix their bugs as good as we can:
00480   if (pTitle) {
00481 
00482      // VOX puts too much information into the Subtitle and leaves the Extended
00483      // Description empty:
00484      //
00485      // Title
00486      // (NAT, Year Min')[ ["Subtitle". ]Extended Description]
00487      //
00488      if (pSubtitle && !pExtendedDescription) {
00489         if (*pSubtitle == '(') {
00490            char *e = strchr(pSubtitle + 1, ')');
00491            if (e) {
00492               if (*(e + 1)) {
00493                  if (*++e == ' ')
00494                     if (*(e + 1) == '"')
00495                        e++;
00496                  }
00497               else
00498                  e = NULL;
00499               char *s = e ? strdup(e) : NULL;
00500               free(pSubtitle);
00501               pSubtitle = s;
00502               EpgBugFixStat(0, GetChannelID());
00503               // now the fixes #1 and #2 below will handle the rest
00504               }
00505            }
00506         }
00507 
00508      // VOX and VIVA put the Subtitle in quotes and use either the Subtitle
00509      // or the Extended Description field, depending on how long the string is:
00510      //
00511      // Title
00512      // "Subtitle". Extended Description
00513      //
00514      if ((pSubtitle == NULL) != (pExtendedDescription == NULL)) {
00515         char *p = pSubtitle ? pSubtitle : pExtendedDescription;
00516         if (*p == '"') {
00517            const char *delim = "\".";
00518            char *e = strstr(p + 1, delim);
00519            if (e) {
00520               *e = 0;
00521               char *s = strdup(p + 1);
00522               char *d = strdup(e + strlen(delim));
00523               free(pSubtitle);
00524               free(pExtendedDescription);
00525               pSubtitle = s;
00526               pExtendedDescription = d;
00527               EpgBugFixStat(1, GetChannelID());
00528               }
00529            }
00530         }
00531 
00532      // VOX and VIVA put the Extended Description into the Subtitle (preceeded
00533      // by a blank) if there is no actual Subtitle and the Extended Description
00534      // is short enough:
00535      //
00536      // Title
00537      //  Extended Description
00538      //
00539      if (pSubtitle && !pExtendedDescription) {
00540         if (*pSubtitle == ' ') {
00541            memmove(pSubtitle, pSubtitle + 1, strlen(pSubtitle));
00542            pExtendedDescription = pSubtitle;
00543            pSubtitle = NULL;
00544            EpgBugFixStat(2, GetChannelID());
00545            }
00546         }
00547 
00548      // Pro7 sometimes repeats the Title in the Subtitle:
00549      //
00550      // Title
00551      // Title
00552      //
00553      if (pSubtitle && strcmp(pTitle, pSubtitle) == 0) {
00554         free(pSubtitle);
00555         pSubtitle = NULL;
00556         EpgBugFixStat(3, GetChannelID());
00557         }
00558 
00559      // ZDF.info puts the Subtitle between double quotes, which is nothing
00560      // but annoying (some even put a '.' after the closing '"'):
00561      //
00562      // Title
00563      // "Subtitle"[.]
00564      //
00565      if (pSubtitle && *pSubtitle == '"') {
00566         int l = strlen(pSubtitle);
00567         if (l > 2 && (pSubtitle[l - 1] == '"' || (pSubtitle[l - 1] == '.' && pSubtitle[l - 2] == '"'))) {
00568            memmove(pSubtitle, pSubtitle + 1, l);
00569            char *p = strrchr(pSubtitle, '"');
00570            if (p)
00571               *p = 0;
00572            EpgBugFixStat(4, GetChannelID());
00573            }
00574         }
00575 
00576      if (Setup.EPGBugfixLevel <= 1)
00577         return;
00578 
00579      // Some channels apparently try to do some formatting in the texts,
00580      // which is a bad idea because they have no way of knowing the width
00581      // of the window that will actually display the text.
00582      // Remove excess whitespace:
00583      pTitle = compactspace(pTitle);
00584      pSubtitle = compactspace(pSubtitle);
00585      pExtendedDescription = compactspace(pExtendedDescription);
00586      // Remove superfluous hyphens:
00587      if (pExtendedDescription) {
00588         char *p = pExtendedDescription;
00589         while (*p && *(p + 1) && *(p + 2)) {
00590               if (*p == '-' && *(p + 1) == ' ' && *(p + 2) && islower(*(p - 1)) && islower(*(p + 2))) {
00591                  if (!startswith(p + 2, "und ")) { // special case in German, as in "Lach- und Sachgeschichten"
00592                     memmove(p, p + 2, strlen(p + 2) + 1);
00593                     EpgBugFixStat(5, GetChannelID());
00594                     }
00595                  }
00596               p++;
00597               }
00598         }
00599 
00600      // Some channels use the ` ("backtick") character, where a ' (single quote)
00601      // would be normally used. Actually, "backticks" in normal text don't make
00602      // much sense, so let's replace them:
00603      strreplace(pTitle, '`', '\'');
00604      strreplace(pSubtitle, '`', '\'');
00605      strreplace(pExtendedDescription, '`', '\'');
00606      }
00607 }
00608 
00609 // --- cSchedule -------------------------------------------------------------
00610 
00611 cSchedule::cSchedule(tChannelID channelid)
00612 {
00613    pPresent = pFollowing = NULL;
00614    channelID = channelid;
00615 }
00616 
00617 
00618 cSchedule::~cSchedule()
00619 {
00620 }
00621 
00622 cEventInfo *cSchedule::AddEvent(cEventInfo *EventInfo)
00623 {
00624   Events.Add(EventInfo);
00625   return EventInfo;
00626 }
00627 
00628 const cEventInfo *cSchedule::GetPresentEvent(void) const
00629 {
00630   return GetEventAround(time(NULL));
00631 }
00632 
00633 const cEventInfo *cSchedule::GetFollowingEvent(void) const
00634 {
00635   const cEventInfo *pe = NULL;
00636   time_t now = time(NULL);
00637   time_t delta = INT_MAX;
00638   for (cEventInfo *p = Events.First(); p; p = Events.Next(p)) {
00639       time_t dt = p->GetTime() - now;
00640       if (dt > 0 && dt < delta) {
00641          delta = dt;
00642          pe = p;
00643          }
00644       }
00645   return pe;
00646 }
00647 
00648 void cSchedule::SetChannelID(tChannelID channelid)
00649 {
00650    channelID = channelid;
00651 }
00653 tChannelID cSchedule::GetChannelID() const
00654 {
00655    return channelID;
00656 }
00658 const cEventInfo * cSchedule::GetEvent(unsigned short uEventID, time_t tTime) const
00659 {
00660    // Returns either the event info with the given uEventID or, if that one can't
00661    // be found, the one with the given tTime (or NULL if neither can be found)
00662    cEventInfo *pe = Events.First();
00663    cEventInfo *pt = NULL;
00664    while (pe != NULL)
00665    {
00666       if (pe->GetEventID() == uEventID)
00667          return pe;
00668       if (tTime > 0 && pe->GetTime() == tTime) // 'tTime < 0' is apparently used with NVOD channels
00669          pt = pe;
00670 
00671       pe = Events.Next(pe);
00672    }
00673 
00674    return pt;
00675 }
00676 
00677 const cEventInfo *cSchedule::GetEventAround(time_t Time) const
00678 {
00679   const cEventInfo *pe = NULL;
00680   time_t delta = INT_MAX;
00681   for (cEventInfo *p = Events.First(); p; p = Events.Next(p)) {
00682       time_t dt = Time - p->GetTime();
00683       if (dt >= 0 && dt < delta && p->GetTime() + p->GetDuration() >= Time) {
00684          delta = dt;
00685          pe = p;
00686          }
00687       }
00688   return pe;
00689 }
00690 
00691 bool cSchedule::SetPresentEvent(cEventInfo *pEvent)
00692 {
00693    if (pPresent != NULL)
00694       pPresent->SetPresent(false);
00695    pPresent = pEvent;
00696    pPresent->SetPresent(true);
00697 
00698    return true;
00699 }
00700 
00702 bool cSchedule::SetFollowingEvent(cEventInfo *pEvent)
00703 {
00704    if (pFollowing != NULL)
00705       pFollowing->SetFollowing(false);
00706    pFollowing = pEvent;
00707    pFollowing->SetFollowing(true);
00708 
00709    return true;
00710 }
00711 
00713 void cSchedule::Cleanup()
00714 {
00715    Cleanup(time(NULL));
00716 }
00717 
00719 void cSchedule::Cleanup(time_t tTime)
00720 {
00721    cEventInfo *pEvent;
00722    for (int a = 0; true ; a++)
00723    {
00724       pEvent = Events.Get(a);
00725       if (pEvent == NULL)
00726          break;
00727       if (pEvent->GetTime() + pEvent->GetDuration() + 3600 < tTime) // adding one hour for safety
00728       {
00729          Events.Del(pEvent);
00730          a--;
00731       }
00732    }
00733 }
00734 
00736 void cSchedule::Dump(FILE *f, const char *Prefix) const
00737 {
00738    cChannel *channel = Channels.GetByChannelID(channelID, true);
00739    if (channel)
00740    {
00741       fprintf(f, "%sC %s %s\n", Prefix, channel->GetChannelID().ToString(), channel->Name());
00742       for (cEventInfo *p = Events.First(); p; p = Events.Next(p))
00743          p->Dump(f, Prefix);
00744       fprintf(f, "%sc\n", Prefix);
00745    }
00746 }
00747 
00748 bool cSchedule::Read(FILE *f, cSchedules *Schedules)
00749 {
00750   if (Schedules) {
00751      char *s;
00752      while ((s = readline(f)) != NULL) {
00753            if (*s == 'C') {
00754               s = skipspace(s + 1);
00755               char *p = strchr(s, ' ');
00756               if (p)
00757                  *p = 0; // strips optional channel name
00758               if (*s) {
00759                  tChannelID channelID = tChannelID::FromString(s);
00760                  if (channelID.Valid()) {
00761                     cSchedule *p = (cSchedule *)Schedules->AddChannelID(channelID);
00762                     if (p) {
00763                        if (!cEventInfo::Read(f, p))
00764                           return false;
00765                        }
00766                     }
00767                  else {
00768                     esyslog("ERROR: illegal channel ID: %s", s);
00769                     return false;
00770                     }
00771                  }
00772               }
00773            else {
00774               esyslog("ERROR: unexpected tag while reading EPG data: %s", s);
00775               return false;
00776               }
00777            }
00778      return true;
00779      }
00780   return false;
00781 }
00782 
00783 // --- cSchedules ------------------------------------------------------------
00784 
00785 cSchedules::cSchedules()
00786 {
00787    pCurrentSchedule = NULL;
00788 }
00789 
00790 cSchedules::~cSchedules()
00791 {
00792 }
00794 const cSchedule *cSchedules::AddChannelID(tChannelID channelid)
00795 {
00796   const cSchedule *p = GetSchedule(channelid);
00797   if (!p) {
00798      Add(new cSchedule(channelid));
00799      p = GetSchedule(channelid);
00800      }
00801   return p;
00802 }
00804 const cSchedule *cSchedules::SetCurrentChannelID(tChannelID channelid)
00805 {
00806   channelid.ClrRid();
00807   pCurrentSchedule = AddChannelID(channelid);
00808   if (pCurrentSchedule)
00809      currentChannelID = channelid;
00810   return pCurrentSchedule;
00811 }
00813 const cSchedule * cSchedules::GetSchedule() const
00814 {
00815    return pCurrentSchedule;
00816 }
00818 const cSchedule * cSchedules::GetSchedule(tChannelID channelid) const
00819 {
00820    cSchedule *p;
00821 
00822    channelid.ClrRid();
00823    p = First();
00824    while (p != NULL)
00825    {
00826       if (p->GetChannelID() == channelid)
00827          return p;
00828       p = Next(p);
00829    }
00830 
00831    return NULL;
00832 }
00833 
00835 void cSchedules::Cleanup()
00836 {
00837    cSchedule *p;
00838 
00839    p = First();
00840    while (p != NULL)
00841    {
00842       p->Cleanup(time(NULL));
00843       p = Next(p);
00844    }
00845 }
00846 
00848 void cSchedules::Dump(FILE *f, const char *Prefix) const
00849 {
00850    for (cSchedule *p = First(); p; p = Next(p))
00851       p->Dump(f, Prefix);
00852 }
00853 
00855 bool cSchedules::Read(FILE *f)
00856 {
00857    cMutexLock MutexLock;
00858    return cSchedule::Read(f, (cSchedules *)cSIProcessor::Schedules(MutexLock));
00859 }
00860 
00861 // --- cEIT ------------------------------------------------------------------
00862 
00863 class cEIT {
00864 private:
00865    cSchedules *schedules;
00866 public:
00867    cEIT(unsigned char *buf, int length, cSchedules *Schedules);
00868    ~cEIT();
00870   int ProcessEIT(unsigned char *buffer, int CurrentSource);
00871 
00872 protected: // Protected methods
00876   bool IsPresentFollowing();
00877 protected: // Protected attributes
00879   u_char tid;
00880 };
00881 
00882 cEIT::cEIT(unsigned char * buf, int length, cSchedules *Schedules)
00883 {
00884    tid = buf[0];
00885    schedules = Schedules;
00886 }
00887 
00888 cEIT::~cEIT()
00889 {
00890 }
00891 
00893 int cEIT::ProcessEIT(unsigned char *buffer, int CurrentSource)
00894 {
00895    cEventInfo *pEvent, *rEvent = NULL;
00896    cSchedule *pSchedule, *rSchedule = NULL;
00897    struct LIST *VdrProgramInfos;
00898    struct VdrProgramInfo *VdrProgramInfo;
00899 
00900    if (!buffer)
00901       return -1;
00902 
00903    VdrProgramInfos = createVdrProgramInfos(buffer);
00904 
00905    if (VdrProgramInfos) {
00906       for (VdrProgramInfo = (struct VdrProgramInfo *) VdrProgramInfos->Head; VdrProgramInfo; VdrProgramInfo = (struct VdrProgramInfo *) xSucc (VdrProgramInfo)) {
00907           //XXX TODO use complete channel ID
00908           cChannel *channel = Channels.GetByServiceID(CurrentSource, VdrProgramInfo->ServiceID);
00909           tChannelID channelID = channel ? channel->GetChannelID() : tChannelID(CurrentSource, 0, 0, VdrProgramInfo->ServiceID);
00910           channelID.ClrRid();
00911           //XXX
00912           pSchedule = (cSchedule *)schedules->GetSchedule(channelID);
00913           if (!pSchedule) {
00914              schedules->Add(new cSchedule(channelID));
00915              pSchedule = (cSchedule *)schedules->GetSchedule(channelID);
00916              if (!pSchedule)
00917                 break;
00918              }
00919           if (VdrProgramInfo->ReferenceServiceID) {
00920              rSchedule = (cSchedule *)schedules->GetSchedule(tChannelID(CurrentSource, 0, 0, VdrProgramInfo->ReferenceServiceID));
00921              if (!rSchedule)
00922                 break;
00923              rEvent = (cEventInfo *)rSchedule->GetEvent((unsigned short)VdrProgramInfo->ReferenceEventID);
00924              if (!rEvent)
00925                 break;
00926              }
00927           pEvent = (cEventInfo *)pSchedule->GetEvent((unsigned short)VdrProgramInfo->EventID, VdrProgramInfo->StartTime);
00928           if (!pEvent) {
00929              // If we don't have that event ID yet, we create a new one.
00930              // Otherwise we copy the information into the existing event anyway, because the data might have changed.
00931              pEvent = pSchedule->AddEvent(new cEventInfo(channelID, VdrProgramInfo->EventID));
00932              if (!pEvent)
00933                 break;
00934              pEvent->SetTableID(tid);
00935              }
00936           else {
00937              // We have found an existing event, either through its event ID or its start time.
00938              // If the existing event has a zero table ID it was defined externally and shall
00939              // not be overwritten.
00940              if (pEvent->GetTableID() == 0x00)
00941                 continue;
00942              // If the new event comes from a table that belongs to an "other TS" and the existing
00943              // one comes from an "actual TS" table, lets skip it.
00944              if ((tid == 0x4F || tid == 0x60 || tid == 0x61) && (pEvent->GetTableID() == 0x4E || pEvent->GetTableID() == 0x50 || pEvent->GetTableID() == 0x51))
00945                 continue;
00946              }
00947           if (rEvent) {
00948              pEvent->SetTitle(rEvent->GetTitle());
00949              pEvent->SetSubtitle(rEvent->GetSubtitle());
00950              pEvent->SetExtendedDescription(rEvent->GetExtendedDescription());
00951              }
00952           else {
00953              pEvent->SetTableID(tid);
00954              pEvent->SetTitle(VdrProgramInfo->ShortName);
00955              pEvent->SetSubtitle(VdrProgramInfo->ShortText);
00956              pEvent->SetExtendedDescription(VdrProgramInfo->ExtendedName);
00957              //XXX kls 2001-09-22:
00958              //XXX apparently this never occurred, so I have simpified ExtendedDescription handling
00959              //XXX pEvent->AddExtendedDescription(VdrProgramInfo->ExtendedText);
00960              }
00961           pEvent->SetTime(VdrProgramInfo->StartTime);
00962           pEvent->SetDuration(VdrProgramInfo->Duration);
00963           pEvent->FixEpgBugs();
00964           if (IsPresentFollowing()) {
00965              if ((GetRunningStatus(VdrProgramInfo->Status) == RUNNING_STATUS_PAUSING) || (GetRunningStatus(VdrProgramInfo->Status) == RUNNING_STATUS_RUNNING))
00966                 pSchedule->SetPresentEvent(pEvent);
00967              else if (GetRunningStatus(VdrProgramInfo->Status) == RUNNING_STATUS_AWAITING)
00968                 pSchedule->SetFollowingEvent(pEvent);
00969              }
00970           }
00971       }
00972 
00973    xMemFreeAll(NULL);
00974    return 0;
00975 }
00976 
00980 bool cEIT::IsPresentFollowing()
00981 {
00982    if (tid == 0x4e || tid == 0x4f)
00983       return true;
00984 
00985    return false;
00986 }
00987 
00988 // --- cSIProcessor ----------------------------------------------------------
00989 
00990 #define MAX_FILTERS 20
00991 #define EPGDATAFILENAME "epg.data"
00992 
00993 int cSIProcessor::numSIProcessors = 0;
00994 cSchedules *cSIProcessor::schedules = NULL;
00995 cMutex cSIProcessor::schedulesMutex;
00996 const char *cSIProcessor::epgDataFileName = EPGDATAFILENAME;
00997 time_t cSIProcessor::lastDump = time(NULL);
00998 
01000 cSIProcessor::cSIProcessor(const char *FileName)
01001 {
01002    fileName = strdup(FileName);
01003    masterSIProcessor = numSIProcessors == 0; // the first one becomes the 'master'
01004    currentSource = 0;
01005    currentTransponder = 0;
01006    filters = NULL;
01007    if (!numSIProcessors++) // the first one creates it
01008       schedules = new cSchedules;
01009    filters = (SIP_FILTER *)calloc(MAX_FILTERS, sizeof(SIP_FILTER));
01010    SetStatus(true);
01011    Start();
01012 }
01013 
01014 cSIProcessor::~cSIProcessor()
01015 {
01016    if (masterSIProcessor)
01017       ReportEpgBugFixStats();
01018    active = false;
01019    Cancel(3);
01020    ShutDownFilters();
01021    free(filters);
01022    if (!--numSIProcessors) // the last one deletes it
01023       delete schedules;
01024    free(fileName);
01025 }
01026 
01027 const cSchedules *cSIProcessor::Schedules(cMutexLock &MutexLock)
01028 {
01029   if (MutexLock.Lock(&schedulesMutex))
01030      return schedules;
01031   return NULL;
01032 }
01033 
01034 bool cSIProcessor::Read(FILE *f)
01035 {
01036   bool OwnFile = f == NULL;
01037   if (OwnFile) {
01038      const char *FileName = GetEpgDataFileName();
01039      if (access(FileName, R_OK) == 0) {
01040         dsyslog("reading EPG data from %s", FileName);
01041         if ((f = fopen(FileName, "r")) == NULL) {
01042            LOG_ERROR;
01043            return false;
01044            }
01045         }
01046      else
01047         return false;
01048      }
01049   bool result = cSchedules::Read(f);
01050   if (OwnFile)
01051      fclose(f);
01052   return result;
01053 }
01054 
01055 void cSIProcessor::Clear(void)
01056 {
01057   cMutexLock MutexLock(&schedulesMutex);
01058   delete schedules;
01059   schedules = new cSchedules;
01060 }
01061 
01062 void cSIProcessor::SetEpgDataFileName(const char *FileName)
01063 {
01064   epgDataFileName = NULL;
01065   if (FileName)
01066      epgDataFileName = strdup(DirectoryOk(FileName) ? AddDirectory(FileName, EPGDATAFILENAME) : FileName);
01067 }
01068 
01069 const char *cSIProcessor::GetEpgDataFileName(void)
01070 {
01071   if (epgDataFileName)
01072      return *epgDataFileName == '/' ? epgDataFileName : AddDirectory(VideoDirectory, epgDataFileName);
01073   return NULL;
01074 }
01075 
01076 void cSIProcessor::SetStatus(bool On)
01077 {
01078    ShutDownFilters();
01079    if (On)
01080    {
01081       AddFilter(0x14, 0x70);  // TDT
01082       AddFilter(0x14, 0x73);  // TOT
01083       AddFilter(0x12, 0x4e);  // event info, actual TS, present/following
01084       AddFilter(0x12, 0x4f);  // event info, other TS, present/following
01085       AddFilter(0x12, 0x50);  // event info, actual TS, schedule
01086       AddFilter(0x12, 0x60);  // event info, other TS, schedule
01087       AddFilter(0x12, 0x51);  // event info, actual TS, schedule for another 4 days
01088       AddFilter(0x12, 0x61);  // event info, other TS, schedule for another 4 days
01089    }
01090 }
01091 
01095 void cSIProcessor::Action()
01096 {
01097    dsyslog("EIT processing thread started (pid=%d)%s", getpid(), masterSIProcessor ? " - master" : "");
01098 
01099    time_t lastCleanup = time(NULL);
01100 
01101    active = true;
01102 
01103    while(active)
01104    {
01105       if (masterSIProcessor)
01106       {
01107          time_t now = time(NULL);
01108          struct tm tm_r;
01109          struct tm *ptm = localtime_r(&now, &tm_r);
01110          if (now - lastCleanup > 3600 && ptm->tm_hour == 5)
01111          {
01112             cMutexLock MutexLock(&schedulesMutex);
01113             isyslog("cleaning up schedules data");
01114             schedules->Cleanup();
01115             lastCleanup = now;
01116             ReportEpgBugFixStats(true);
01117          }
01118          if (epgDataFileName && now - lastDump > 600)
01119          {
01120             cMutexLock MutexLock(&schedulesMutex);
01121             cSafeFile f(GetEpgDataFileName());
01122             if (f.Open()) {
01123                schedules->Dump(f);
01124                f.Close();
01125                }
01126             else
01127                LOG_ERROR;
01128             lastDump = now;
01129          }
01130       }
01131 
01132       // set up pfd structures for all active filter
01133       pollfd pfd[MAX_FILTERS];
01134       int NumUsedFilters = 0;
01135       for (int a = 0; a < MAX_FILTERS ; a++)
01136       {
01137          if (filters[a].inuse)
01138          {
01139             pfd[NumUsedFilters].fd = filters[a].handle;
01140             pfd[NumUsedFilters].events = POLLIN;
01141             NumUsedFilters++;
01142          }
01143       }
01144 
01145       // wait until data becomes ready from the bitfilter
01146       if (poll(pfd, NumUsedFilters, 1000) != 0)
01147       {
01148          for (int a = 0; a < NumUsedFilters ; a++)
01149          {
01150             if (pfd[a].revents & POLLIN)
01151             {
01152                /* read section */
01153                unsigned char buf[4096+1]; // max. allowed size for any EIT section (+1 for safety ;-)
01154                if (safe_read(filters[a].handle, buf, 3) == 3)
01155                {
01156                   int seclen = ((buf[1] & 0x0F) << 8) | (buf[2] & 0xFF);
01157                   int pid = filters[a].pid;
01158                   int n = safe_read(filters[a].handle, buf + 3, seclen);
01159                   if (n == seclen)
01160                   {
01161                      seclen += 3;
01162                      //dsyslog("Received pid 0x%02x with table ID 0x%02x and length of %04d\n", pid, buf[0], seclen);
01163                      switch (pid)
01164                      {
01165                         case 0x14:
01166                            if (buf[0] == 0x70)
01167                            {
01168                               if (Setup.SetSystemTime && Setup.TimeTransponder && ISTRANSPONDER(currentTransponder, Setup.TimeTransponder))
01169                               {
01170                                  cTDT ctdt((tdt_t *)buf);
01171                                  ctdt.SetSystemTime();
01172                               }
01173                            }
01174                               /*XXX this comes pretty often:
01175                            else
01176                               dsyslog("Time packet was not 0x70 but 0x%02x\n", (int)buf[0]);
01177                               XXX*/
01178                            break;
01179 
01180                         case 0x12:
01181                            if (buf[0] != 0x72)
01182                            {
01183                               cMutexLock MutexLock(&schedulesMutex);
01184                               cEIT ceit(buf, seclen, schedules);
01185                               ceit.ProcessEIT(buf, currentSource);
01186                            }
01187                            else
01188                               dsyslog("Received stuffing section in EIT\n");
01189                            break;
01190 
01191                         default:
01192                            break;
01193                      }
01194                   }
01195                   /*XXX this just fills up the log file - shouldn't we rather try to re-sync?
01196                   else
01197                      dsyslog("read incomplete section - seclen = %d, n = %d", seclen, n);
01198                   XXX*/
01199                }
01200             }
01201          }
01202       }
01203    }
01204 
01205    dsyslog("EIT processing thread ended (pid=%d)%s", getpid(), masterSIProcessor ? " - master" : "");
01206 }
01207 
01210 bool cSIProcessor::AddFilter(u_char pid, u_char tid)
01211 {
01212    dmx_sct_filter_params sctFilterParams;
01213    memset(&sctFilterParams, 0, sizeof(sctFilterParams));
01214    sctFilterParams.pid = pid;
01215    sctFilterParams.timeout = 0;
01216    sctFilterParams.flags = DMX_IMMEDIATE_START;
01217    sctFilterParams.filter.filter[0] = tid;
01218    sctFilterParams.filter.mask[0] = 0xFF;
01219 
01220    for (int a = 0; a < MAX_FILTERS; a++)
01221    {
01222       if (!filters[a].inuse)
01223       {
01224          filters[a].pid = pid;
01225          filters[a].tid = tid;
01226          if ((filters[a].handle = open(fileName, O_RDWR | O_NONBLOCK)) >= 0)
01227          {
01228             if (ioctl(filters[a].handle, DMX_SET_FILTER, &sctFilterParams) >= 0)
01229                filters[a].inuse = true;
01230             else
01231             {
01232                esyslog("ERROR: can't set filter");
01233                close(filters[a].handle);
01234                return false;
01235             }
01236             // dsyslog("Registered filter handle %04x, pid = %02d, tid = %02d", filters[a].handle, filters[a].pid, filters[a].tid);
01237          }
01238          else
01239          {
01240             esyslog("ERROR: can't open filter handle");
01241             return false;
01242          }
01243          return true;
01244       }
01245    }
01246    esyslog("ERROR: too many filters");
01247 
01248    return false;
01249 }
01250 
01252 bool cSIProcessor::ShutDownFilters(void)
01253 {
01254    for (int a = 0; a < MAX_FILTERS; a++)
01255    {
01256       if (filters[a].inuse)
01257       {
01258          close(filters[a].handle);
01259          // dsyslog("Deregistered filter handle %04x, pid = %02d, tid = %02d", filters[a].handle, filters[a].pid, filters[a].tid);
01260          filters[a].inuse = false;
01261       }
01262    }
01263 
01264    return true; // there's no real 'boolean' to return here...
01265 }
01266 
01268 void cSIProcessor::SetCurrentTransponder(int CurrentSource, int CurrentTransponder)
01269 {
01270   currentSource = CurrentSource;
01271   currentTransponder = CurrentTransponder;
01272 }
01273 
01275 bool cSIProcessor::SetCurrentChannelID(tChannelID channelid)
01276 {
01277   cMutexLock MutexLock(&schedulesMutex);
01278   return schedules ? schedules->SetCurrentChannelID(channelid) : false;
01279 }
01280 
01281 void cSIProcessor::TriggerDump(void)
01282 {
01283   cMutexLock MutexLock(&schedulesMutex);
01284   lastDump = 0;
01285 }

Generated on Wed Feb 5 23:30:08 2003 for VDR by doxygen1.3-rc2