summaryrefslogtreecommitdiff
path: root/vdrtva.c
diff options
context:
space:
mode:
Diffstat (limited to 'vdrtva.c')
-rw-r--r--vdrtva.c1143
1 files changed, 1143 insertions, 0 deletions
diff --git a/vdrtva.c b/vdrtva.c
new file mode 100644
index 0000000..ce39114
--- /dev/null
+++ b/vdrtva.c
@@ -0,0 +1,1143 @@
+/*
+ * vdrtva.c: A plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#include <vdr/plugin.h>
+#include <libsi/section.h>
+#include <libsi/descriptor.h>
+#include <stdarg.h>
+#include <getopt.h>
+
+#include "vdrtva.h"
+
+#define SVDRPOSD_BUFSIZE KILOBYTE(4)
+#define SECONDSPERDAY (86400)
+
+cChanDAs *ChanDAs;
+cEventCRIDs *EventCRIDs;
+cSuggestCRIDs *SuggestCRIDs;
+cLinks *Links;
+
+static const char *VERSION = "0.0.6";
+static const char *DESCRIPTION = "TV-Anytime plugin";
+static const char *MAINMENUENTRY = "vdrTva";
+
+int collectionperiod; // Time to collect all CRID data (default 10 minutes)
+int lifetime; // Lifetime of series link recordings (default 99)
+int priority; // Priority of series link recordings (default 99)
+int seriesLifetime; // Expiry time of a series link (default 30 days)
+int updatetime; // Time to carry out the series link update HHMM (default 03:00)
+
+
+
+class cPluginvdrTva : public cPlugin {
+private:
+ // Add any member variables or functions you may need here.
+ int length;
+ int size;
+ int flags;
+ int state;
+ time_t nextactiontime;
+ char *buffer;
+ char* configDir;
+ cTvaFilter *Filter;
+ cTvaStatusMonitor *statusMonitor;
+ bool Append(const char *Fmt, ...);
+ bool AppendItems(const char* Option);
+ const char* Reply();
+ bool AddSeriesLink(const char *scrid, int modtime, const char *icrid);
+ void LoadLinksFile(void);
+ bool SaveLinksFile(void);
+ bool UpdateLinksFromTimers(void);
+ bool AddNewEventsToSeries(void);
+ bool CheckSplitTimers(void);
+ void CheckChangedEvents(void);
+ void CheckTimerClashes(void);
+ void FindAlternatives(const cEvent *event);
+ void StartDataCapture(void);
+ void StopDataCapture(void);
+ void Update(void);
+ void Check(void);
+// void Reset();
+public:
+ cPluginvdrTva(void);
+ virtual ~cPluginvdrTva();
+ virtual const char *Version(void) { return VERSION; }
+ virtual const char *Description(void) { return tr(DESCRIPTION); }
+ virtual const char *CommandLineHelp(void);
+ virtual bool ProcessArgs(int argc, char *argv[]);
+ virtual bool Initialize(void);
+ virtual bool Start(void);
+ virtual void Stop(void);
+ virtual void Housekeeping(void);
+ virtual void MainThreadHook(void);
+ virtual cString Active(void);
+ virtual time_t WakeupTime(void);
+ virtual const char *MainMenuEntry(void) { return MAINMENUENTRY; }
+ virtual cOsdObject *MainMenuAction(void);
+ virtual cMenuSetupPage *SetupMenu(void);
+ virtual bool SetupParse(const char *Name, const char *Value);
+ virtual bool Service(const char *Id, void *Data = NULL);
+ virtual const char **SVDRPHelpPages(void);
+ virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode);
+ };
+
+cPluginvdrTva::cPluginvdrTva(void)
+{
+ // Initialize any member variables here.
+ // DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL
+ // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT!
+ buffer = NULL;
+ configDir = NULL;
+ Filter = NULL;
+ ChanDAs = NULL;
+ EventCRIDs = NULL;
+ SuggestCRIDs = NULL;
+ Links = NULL;
+ seriesLifetime = 30 * SECONDSPERDAY;
+ priority = 99;
+ lifetime = 99;
+ flags = 5;
+ state = 0;
+ collectionperiod = 10 * 60;
+ updatetime = 300;
+}
+
+cPluginvdrTva::~cPluginvdrTva()
+{
+ // Clean up after yourself!
+ delete statusMonitor;
+}
+
+const char *cPluginvdrTva::CommandLineHelp(void)
+{
+ // Return a string that describes all known command line options.
+ return " -l n --lifetime=n Lifetime of new timers (default 99)\n"
+ " -p n --priority=n Priority of new timers (default 99)\n"
+ " -s n --serieslifetime=n Days to remember a series after the last event (default 30)\n"
+ " -u HH:MM --updatetime=HH:MM Time to update series links (default 03:00)\n";
+}
+
+bool cPluginvdrTva::ProcessArgs(int argc, char *argv[])
+{
+ // Implement command line argument processing here if applicable.
+ static struct option long_options[] = {
+ { "serieslifetime", required_argument, NULL, 's' },
+ { "priority", required_argument, NULL, 'p' },
+ { "lifetime", required_argument, NULL, 'l' },
+ { "updatetime", required_argument, NULL, 'u' },
+ { NULL }
+ };
+
+ int c, opt;
+ char *hours, *mins, *strtok_next;
+ char buf[32];
+ while ((c = getopt_long(argc, argv, "l:p:s:u:", long_options, NULL)) != -1) {
+ switch (c) {
+ case 'l':
+ opt = atoi(optarg);
+ if (opt > 0) lifetime = opt;
+ break;
+ case 'p':
+ opt = atoi(optarg);
+ if (opt > 0) priority = opt;
+ break;
+ case 's':
+ opt = atoi(optarg);
+ if (opt > 0) seriesLifetime = opt * SECONDSPERDAY;
+ break;
+ case 'u':
+ strncpy(buf, optarg,sizeof(buf));
+ hours = strtok_r(buf, ":", &strtok_next);
+ mins = strtok_r(NULL, "!", &strtok_next);
+ updatetime = atoi(hours)*100 + atoi(mins);
+ break;
+ default:
+ return false;
+ }
+ }
+ return true;
+}
+
+bool cPluginvdrTva::Initialize(void)
+{
+ // Initialize any background activities the plugin shall perform.
+ return true;
+}
+
+bool cPluginvdrTva::Start(void)
+{
+ // Start any background activities the plugin shall perform.
+ configDir = strcpyrealloc(configDir, cPlugin::ConfigDirectory("vdrtva"));
+ LoadLinksFile();
+ statusMonitor = new cTvaStatusMonitor;
+ struct tm tm_r;
+ char buff[32];
+ time_t now = time(NULL);
+ localtime_r(&now, &tm_r);
+ tm_r.tm_sec = 0;
+ tm_r.tm_hour = updatetime / 100;
+ tm_r.tm_min = updatetime % 100;
+ nextactiontime = mktime(&tm_r);
+ if (nextactiontime < now) nextactiontime += SECONDSPERDAY;
+ ctime_r(&nextactiontime, buff);
+ isyslog("vdrtva: next update due at %s", buff);
+ return true;
+}
+
+void cPluginvdrTva::Stop(void)
+{
+ // Stop any background activities the plugin is performing.
+ if (Filter) {
+ delete Filter;
+ Filter = NULL;
+ }
+}
+
+void cPluginvdrTva::Housekeeping(void)
+{
+ // Perform any cleanup or other regular tasks.
+ if (nextactiontime < time(NULL)) {
+ statusMonitor->ClearTimerAdded(); // Ignore any timer changes while update is in progress
+ switch (state) {
+ case 0:
+ StartDataCapture();
+ nextactiontime += collectionperiod;
+ state++;
+ break;
+ case 1:
+ StopDataCapture();
+ state++;
+ break;
+ case 2:
+ Update();
+ state++;
+ break;
+ case 3:
+ Check();
+ nextactiontime += (SECONDSPERDAY - collectionperiod);
+ state = 0;
+ break;
+ }
+ }
+ else if (EventCRIDs && statusMonitor->GetTimerAddedDelta() > 60) {
+ Update(); // Wait 1 minute for VDR to enter the event data into the new timer.
+ Check();
+ statusMonitor->ClearTimerAdded();
+ }
+}
+
+void cPluginvdrTva::MainThreadHook(void)
+{
+ // Perform actions in the context of the main program thread.
+ // WARNING: Use with great care - see PLUGINS.html!
+}
+
+cString cPluginvdrTva::Active(void)
+{
+ // Return a message string if shutdown should be postponed
+ return NULL;
+}
+
+time_t cPluginvdrTva::WakeupTime(void)
+{
+ // Return custom wakeup time for shutdown script
+ return 0;
+}
+
+cOsdObject *cPluginvdrTva::MainMenuAction(void)
+{
+ // Perform the action when selected from the main VDR menu.
+ return NULL;
+}
+
+cMenuSetupPage *cPluginvdrTva::SetupMenu(void)
+{
+ // Return a setup menu in case the plugin supports one.
+ return new cTvaMenuSetup;
+}
+
+bool cPluginvdrTva::SetupParse(const char *Name, const char *Value)
+{
+ // Parse your own setup parameters and store their values.
+ if (!strcasecmp(Name, "CollectionPeriod")) collectionperiod = atoi(Value);
+ else if (!strcasecmp(Name, "SeriesLifetime")) seriesLifetime = atoi(Value);
+ else if (!strcasecmp(Name, "TimerLifetime")) lifetime = atoi(Value);
+ else if (!strcasecmp(Name, "TimerPriority")) priority = atoi(Value);
+ else if (!strcasecmp(Name, "UpdateTime")) updatetime = atoi(Value);
+ else return false;
+ return true;
+}
+
+bool cPluginvdrTva::Service(const char *Id, void *Data)
+{
+ // Handle custom service requests from other plugins
+ return false;
+}
+
+const char **cPluginvdrTva::SVDRPHelpPages(void)
+{
+ // Return help text for SVDRP commands this plugin implements
+ static const char *HelpPages[] = {
+ "LSTL\n"
+ " Print the Links list.",
+ "LSTS\n"
+ " Print the suggested events list",
+ "LSTY\n"
+ " Print the Event list including CRIDs.",
+ "LSTZ\n"
+ " Print the channel list with Default Authority.",
+ "STOP\n"
+ " Stop Event data capture (retaining data).",
+ "STRT\n"
+ " Start Event data capture (erasing any existing data)",
+ "UPDT\n"
+ " Update timers and links (series link functionality)",
+ NULL
+ };
+ return HelpPages;
+}
+
+cString cPluginvdrTva::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
+{
+ // Process SVDRP commands this plugin implements
+ isyslog ("vdrtva: processing command %s", Command);
+ if (strcasecmp(Command, "LSTL") == 0) {
+ if (Links && (Links->MaxNumber() >=1)) {
+ ReplyCode = 250;
+ for (cLinkItem *linkItem = Links->First(); linkItem; linkItem = Links->Next(linkItem)) {
+ Append("%s,%d;%s\n", linkItem->sCRID(), linkItem->ModTime(), linkItem->iCRIDs());
+ }
+ }
+ if (buffer && length > 0) return cString(Reply(), true);
+ else return cString::sprintf("Nothing in the buffer!");
+ }
+ else if (strcasecmp(Command, "LSTS") == 0) {
+ if (SuggestCRIDs && (SuggestCRIDs->MaxNumber() >= 1)) {
+ ReplyCode = 250;
+ for (cSuggestCRID *suggestCRID = SuggestCRIDs->First(); suggestCRID; suggestCRID = SuggestCRIDs->Next(suggestCRID)) {
+ cChanDA *chanDA = ChanDAs->GetByChannelID(suggestCRID->Cid());
+ if(chanDA) {
+ Append("%s%s %s%s\n", chanDA->DA(), suggestCRID->iCRID(), chanDA->DA(), suggestCRID->gCRID());
+ }
+ }
+ if (buffer && length > 0) return cString(Reply(), true);
+ else return cString::sprintf("Nothing in the buffer!");
+ }
+ else
+ return cString::sprintf("No suggested events defined");
+ }
+ else if (strcasecmp(Command, "LSTY") == 0) {
+ if (EventCRIDs && (EventCRIDs->MaxNumber() >= 1)) {
+ ReplyCode = 250;
+ for (cEventCRID *eventCRID = EventCRIDs->First(); eventCRID; eventCRID = EventCRIDs->Next(eventCRID)) {
+ cChanDA *chanDA = ChanDAs->GetByChannelID(eventCRID->Cid());
+ if(chanDA) {
+ Append("%d %d %s%s %s%s\n", chanDA->Cid(), eventCRID->Eid(), chanDA->DA(), eventCRID->iCRID(), chanDA->DA(), eventCRID->sCRID());
+ }
+ }
+ if (buffer && length > 0) return cString(Reply(), true);
+ else return cString::sprintf("Nothing in the buffer!");
+ }
+ else
+ return cString::sprintf("No events defined");
+ }
+ else if (strcasecmp(Command, "LSTZ") == 0) {
+ if (ChanDAs && (ChanDAs->MaxNumber() >= 1)) {
+ ReplyCode = 250;
+ for (cChanDA *chanDA = ChanDAs->First(); chanDA; chanDA = ChanDAs->Next(chanDA)) {
+ Append("%d %s\n", chanDA->Cid(), chanDA->DA());
+ }
+ if (buffer && length > 0) return cString(Reply(), true);
+ else return cString::sprintf("Nothing in the buffer!");
+ }
+ else
+ return cString::sprintf("No channels defined");
+ }
+ else if (strcasecmp(Command, "STRT") == 0) {
+ if (!Filter) {
+ StartDataCapture();
+ return cString::sprintf("Data capture started");
+ }
+ else {
+ ReplyCode = 999;
+ return cString::sprintf("Double start attempted");
+ }
+ }
+ else if (strcasecmp(Command, "STOP") == 0) {
+ if (Filter) {
+ StopDataCapture();
+ return cString::sprintf("Data capture stopped");
+ }
+ else {
+ ReplyCode = 999;
+ return cString::sprintf("Double stop attempted");
+ }
+ }
+ else if (strcasecmp(Command, "UPDT") == 0) {
+ Update();
+ Check();
+ return cString::sprintf("Update completed");
+ }
+ return NULL;
+}
+
+bool cPluginvdrTva::Append(const char *Fmt, ...)
+{
+ va_list ap;
+
+ if (!buffer) {
+ length = 0;
+ size = SVDRPOSD_BUFSIZE;
+ buffer = (char *) malloc(sizeof(char) * size);
+ }
+ while (buffer) {
+ va_start(ap, Fmt);
+ int n = vsnprintf(buffer + length, size - length, Fmt, ap);
+ va_end(ap);
+
+ if (n < size - length) {
+ length += n;
+ return true;
+ }
+ // overflow: realloc and try again
+ size += SVDRPOSD_BUFSIZE;
+ char *tmp = (char *) realloc(buffer, sizeof(char) * size);
+ if (!tmp)
+ free(buffer);
+ buffer = tmp;
+ }
+ return false;
+}
+
+const char* cPluginvdrTva::Reply()
+{
+ char *tmp = buffer;
+ buffer = NULL;
+ return tmp;
+}
+
+void cPluginvdrTva::StartDataCapture()
+{
+ if (!Filter) {
+ if (EventCRIDs) delete EventCRIDs;
+ if (ChanDAs) delete ChanDAs;
+ if (SuggestCRIDs) delete SuggestCRIDs;
+ EventCRIDs = new cEventCRIDs();
+ SuggestCRIDs = new cSuggestCRIDs;
+ ChanDAs = new cChanDAs();
+ Filter = new cTvaFilter();
+ cDevice::ActualDevice()->AttachFilter(Filter);
+ isyslog("vdrtva: Data capture started");
+ }
+}
+
+void cPluginvdrTva::StopDataCapture()
+{
+ if (Filter) {
+ delete Filter;
+ Filter = NULL;
+ isyslog("vdrtva: Data capture stopped");
+ }
+}
+
+void cPluginvdrTva::Update()
+{
+ bool status = UpdateLinksFromTimers();
+ status |= AddNewEventsToSeries();
+ if(status) SaveLinksFile();
+ isyslog("vdrtva: Updates complete");
+}
+
+void cPluginvdrTva::Check()
+{
+ CheckChangedEvents();
+ CheckTimerClashes();
+ CheckSplitTimers();
+ isyslog("vdrtva: Checks complete");
+}
+
+// add a new event to the Links table, either as an addition to an existing series or as a new series.
+// return false = nothing done, true = new event for old series, or new series.
+
+bool cPluginvdrTva::AddSeriesLink(const char *scrid, int modtime, const char *icrid)
+{
+ if (Links && (Links->MaxNumber() >=1)) {
+ for (cLinkItem *Item = Links->First(); Item; Item = Links->Next(Item)) {
+ if (strcasecmp(Item->sCRID(), scrid) == 0) {
+ if (strstr(Item->iCRIDs(), icrid) == NULL) {
+ cString icrids = cString::sprintf("%s:%s", Item->iCRIDs(), icrid);
+ modtime = max(Item->ModTime(), modtime);
+ Item->Set(Item->sCRID(), modtime, icrids);
+ isyslog("vdrtva: Adding new event %s to series %s\n", icrid, scrid);
+ return true;
+ }
+ return false;
+ }
+ }
+ }
+ Links->NewLinkItem(scrid, modtime, icrid);
+ isyslog("vdrtva: Creating new series %s for event %s\n", scrid, icrid);
+ return true;
+}
+
+void cPluginvdrTva::LoadLinksFile()
+{
+ Links = new cLinks();
+ cString curlinks = AddDirectory(configDir, "links.data");
+ FILE *f = fopen(curlinks, "r");
+ if (f) {
+ char *s;
+ char *strtok_next;
+ cReadLine ReadLine;
+ cLinkItem *LinkItem;
+ int modtime;
+ while ((s = ReadLine.Read(f)) != NULL) {
+ char *scrid = strtok_r(s, ",", &strtok_next);
+ char *mtime = strtok_r(NULL, ";", &strtok_next);
+ char *icrids = strtok_r(NULL, "!", &strtok_next);
+ modtime = atoi(mtime);
+ LinkItem = Links->NewLinkItem(scrid, modtime, icrids);
+ }
+ fclose (f);
+ isyslog("vdrtva: loaded %d series links\n", Links->MaxNumber());
+ }
+ else isyslog("vdrtva: series links file not found\n");
+}
+
+bool cPluginvdrTva::SaveLinksFile()
+{
+ cString curlinks = AddDirectory(configDir, "links.data");
+ cString newlinks = AddDirectory(configDir, "links.new");
+ cString oldlinks = AddDirectory(configDir, "links.old");
+ FILE *f = fopen(newlinks, "w");
+ if (f) {
+ cLinkItem *Item = Links->First();
+ while (Item) {
+ cLinkItem *next = Links->Next(Item);
+ if ((Item->ModTime() + seriesLifetime) > time(NULL)) {
+ fprintf(f, "%s,%d;%s\n", Item->sCRID(), Item->ModTime(), Item->iCRIDs());
+ }
+ else {
+ isyslog ("vdrtva: Expiring series %s\n", Item->sCRID());
+ Links->Del(Item);
+ }
+ Item = next;
+ }
+ fclose(f);
+ unlink (oldlinks); // Allow to fail if the save file does not exist
+ rename (curlinks, oldlinks);
+ rename (newlinks, curlinks);
+ }
+ return true;
+}
+
+// Check that all timers are part of series links and update the links.
+
+bool cPluginvdrTva::UpdateLinksFromTimers()
+{
+ if ((Timers.Count() == 0) || (!EventCRIDs)) return false;
+ bool status = false;
+ for (int i = 0; i < Timers.Count(); i++) {
+ cTimer *timer = Timers.Get(i);
+ if (timer) {
+// find the event for this timer
+ const cEvent *event = timer->Event();
+ if (event) {
+ cChannel *channel = Channels.GetByChannelID(event->ChannelID());
+// find the sCRID and iCRID for the event
+ cChanDA *chanda = ChanDAs->GetByChannelID(channel->Number());
+ cEventCRID *eventcrid = EventCRIDs->GetByID(channel->Number(), event->EventID());
+ if (eventcrid && chanda) {
+ cString scrid = cString::sprintf("%s%s", chanda->DA(),eventcrid->sCRID());
+ cString icrid = cString::sprintf("%s%s", chanda->DA(),eventcrid->iCRID());
+// scan the links table for the sCRID
+// if found, check if the iCRID is present, if not add it
+// else create a new links entry
+ status |= AddSeriesLink(scrid, event->StartTime(), icrid);
+ }
+ }
+ }
+ }
+ return status;
+}
+
+// Find new events for series links and create timers for them.
+// It would be simpler to create the timer directly from the event, but it would not then be possible to
+// control the VPS parameters.
+
+bool cPluginvdrTva::AddNewEventsToSeries()
+{
+ bool saveNeeded = false;
+ if (!Links || (Links->MaxNumber() < 1)) return false;
+// Foreach CRID
+ for (cEventCRID *eventCRID = EventCRIDs->First(); eventCRID; eventCRID = EventCRIDs->Next(eventCRID)) {
+ cChanDA *chanDA = ChanDAs->GetByChannelID(eventCRID->Cid());
+ if (chanDA) {
+// Check for an entry in the Links table with the same sCRID
+ cString scrid = cString::sprintf("%s%s", chanDA->DA(),eventCRID->sCRID());
+ for (cLinkItem *Item = Links->First(); Item; Item = Links->Next(Item)) {
+ if (strcasecmp(Item->sCRID(), scrid) == 0) {
+// if found, look for the event's icrid in ALL series
+ cString icrid = cString::sprintf("%s%s", chanDA->DA(),eventCRID->iCRID());
+ bool done = false;
+ for (cLinkItem *Item2 = Links->First(); Item2; Item2 = Links->Next(Item2)) {
+ if (strstr(Item2->iCRIDs(), icrid) != NULL) {
+ done = true;
+ }
+ }
+// if not found, add a new timer for the event and update the series.
+ if (!done) {
+ cChannel *channel = Channels.GetByNumber(eventCRID->Cid());
+ cSchedulesLock SchedulesLock;
+ const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
+ if (Schedules) {
+ const cSchedule *schedule = Schedules->GetSchedule(channel);
+ if (schedule) {
+ const cEvent *event = schedule->GetEvent(eventCRID->Eid());
+ struct tm tm_r;
+ char startbuff[64], endbuff[64], etitle[256];
+ time_t starttime = event->StartTime();
+ time_t endtime = event->EndTime();
+ if (!Setup.UseVps) {
+ starttime -= Setup.MarginStart;
+ endtime += Setup.MarginStop;
+ }
+ localtime_r(&starttime, &tm_r);
+ strftime(startbuff, sizeof(startbuff), "%Y-%m-%d:%H%M", &tm_r);
+ localtime_r(&endtime, &tm_r);
+ strftime(endbuff, sizeof(endbuff), "%H%M", &tm_r);
+ strn0cpy (etitle, event->Title(), sizeof(etitle));
+ strreplace(etitle, ':', '|');
+ cString timercmd = cString::sprintf("%u:%d:%s:%s:%d:%d:%s:\n", flags, channel->Number(), startbuff, endbuff, priority, lifetime, etitle);
+ cTimer *timer = new cTimer;
+ if (timer->Parse(timercmd)) {
+ cTimer *t = Timers.GetTimer(timer);
+ if (!t) {
+ Timers.Add(timer);
+ Timers.SetModified();
+ isyslog("vdrtva: timer %s added on %s", *timer->ToDescr(), *DateString(timer->StartTime()));
+ AddSeriesLink(scrid, event->StartTime(), icrid);
+ saveNeeded = true;
+ }
+ else isyslog("vdrtva: Duplicate timer creation attempted for %s on %s", *timer->ToDescr(), *DateString(timer->StartTime()));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return saveNeeded;
+}
+
+// Check timers to see if the event they were set to record is still in the EPG.
+// This won't work if the start time is padded.
+
+void cPluginvdrTva::CheckChangedEvents()
+{
+ if (Timers.Count() == 0) return;
+ for (int i = 0; i < Timers.Count(); i++) {
+ cTimer *timer = Timers.Get(i);
+ if (timer) {
+ const cChannel *channel = timer->Channel();
+ cSchedulesLock SchedulesLock;
+ const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
+ if (Schedules) {
+ const cSchedule *schedule = Schedules->GetSchedule(channel);
+ if (schedule) {
+ const cEvent *event = schedule->GetEvent(NULL, timer->StartTime());
+ if (!event) isyslog("Event for timer '%s' at %s seems to no longer exist", timer->File(), *DayDateTime(timer->StartTime()));
+ else if (strcmp(timer->File(), event->Title())) {
+ isyslog("vdrtva: Changed timer event at %s: %s <=> %s", *DayDateTime(timer->StartTime()), timer->File(), event->Title());
+ }
+ }
+ }
+ }
+ }
+}
+
+// Check for timer clashes - overlapping timers which are not on the same transponder.
+// FIXME How to deal with multiple input devices??
+
+void cPluginvdrTva::CheckTimerClashes(void)
+{
+ if (Timers.Count() < 2) return;
+ for (int i = 1; i < Timers.Count(); i++) {
+ cTimer *timer1 = Timers.Get(i);
+ if (timer1) {
+ for (int j = 0; j < i; j++) {
+ cTimer *timer2 = Timers.Get(j);
+ if (timer2) {
+ if((timer1->StartTime() >= timer2->StartTime() && timer1->StartTime() < timer2->StopTime())
+ ||(timer2->StartTime() >= timer1->StartTime() && timer2->StartTime() < timer1->StopTime())) {
+ const cChannel *channel1 = timer1->Channel();
+ const cChannel *channel2 = timer2->Channel();
+ if (channel1->Transponder() != channel2->Transponder()) {
+ isyslog("vdrtva: Collision at %s. %s <=> %s", *DayDateTime(timer1->StartTime()), timer1->File(), timer2->File());
+ FindAlternatives(timer1->Event());
+ FindAlternatives(timer2->Event());
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+void cPluginvdrTva::FindAlternatives(const cEvent *event)
+{
+ if (!event) return;
+ cChannel *channel = Channels.GetByChannelID(event->ChannelID());
+ cChanDA *chanda = ChanDAs->GetByChannelID(channel->Number());
+ cEventCRID *eventcrid = EventCRIDs->GetByID(channel->Number(), event->EventID());
+ if (!eventcrid || !chanda) return;
+
+ bool found = false;
+ for (cEventCRID *eventcrid2 = EventCRIDs->First(); eventcrid2; eventcrid2 = EventCRIDs->Next(eventcrid2)) {
+ if ((strcmp(eventcrid->iCRID(), eventcrid2->iCRID()) == 0) && (event->EventID() != eventcrid2->Eid())) {
+ cChanDA *chanda2 = ChanDAs->GetByChannelID(eventcrid2->Cid());
+ if (strcmp(chanda->DA(), chanda2->DA()) == 0) {
+ cChannel *channel2 = Channels.GetByNumber(eventcrid2->Cid());
+ cSchedulesLock SchedulesLock;
+ const cSchedules *schedules = cSchedules::Schedules(SchedulesLock);
+ if (schedules) {
+ const cSchedule *schedule = schedules->GetSchedule(channel2);
+ if (schedule) {
+ const cEvent *event2 = schedule->GetEvent(eventcrid2->Eid(), 0);
+ if (!found) {
+ isyslog("vdrtva: Alternatives for '%s':", event->Title());
+ found = true;
+ }
+ isyslog("vdrtva: %s %s", channel2->Name(), *DayDateTime(event2->StartTime()));
+ }
+ }
+ }
+ }
+ }
+ if (!found) isyslog("vdrtva: No alternatives for '%s':", event->Title());
+}
+
+// Check that, if any split events (eg a long programme with a news break in the middle)
+// are being recorded, that timers are set for all of the parts.
+// FIXME This may not work if the programme is being repeated. Inefficient algorithm.
+
+bool cPluginvdrTva::CheckSplitTimers(void)
+{
+ if (Timers.Count() == 0) return false;
+ for (int i = 0; i < Timers.Count(); i++) {
+ cTimer *timer = Timers.Get(i);
+ if (timer) {
+ const cEvent *event = timer->Event();
+ if (event) {
+ cChannel *channel = Channels.GetByChannelID(event->ChannelID());
+ cChanDA *chanda = ChanDAs->GetByChannelID(channel->Number());
+ cEventCRID *eventcrid = EventCRIDs->GetByID(channel->Number(), event->EventID());
+ if (eventcrid && chanda && strchr(eventcrid->iCRID(), '#')) {
+// char crid[Utf8BufSize(256)], *next;
+// strcpy(crid, eventcrid->iCRID());
+// char *prefix = strtok_r(crid, "#", &next);
+// char *suffix = strtok_r(NULL, "#", &next);
+ isyslog("Timer for split event '%s' found - check all parts are being recorded!", event->Title());
+ }
+ }
+ }
+ }
+ return false;
+}
+
+
+/*
+ cTvaStatusMonitor - callback for timer changes.
+*/
+
+cTvaStatusMonitor::cTvaStatusMonitor(void)
+{
+ timeradded = NULL;
+}
+
+void cTvaStatusMonitor::TimerChange(const cTimer *Timer, eTimerChange Change)
+{
+ if (Change == tcAdd) timeradded = time(NULL);
+}
+
+int cTvaStatusMonitor::GetTimerAddedDelta(void)
+{
+ if (timeradded) {
+ return (time(NULL) - timeradded);
+ }
+ return 0;
+}
+
+void cTvaStatusMonitor::ClearTimerAdded(void)
+{
+ timeradded = NULL;
+ return;
+}
+
+
+/*
+ cTvaMenuSetup - setup menu function.
+*/
+
+cTvaMenuSetup::cTvaMenuSetup(void)
+{
+ newcollectionperiod = collectionperiod;
+ newlifetime = lifetime;
+ newpriority = priority;
+ newseriesLifetime = seriesLifetime;
+ newupdatetime = updatetime;
+ Add(new cMenuEditIntItem(tr("Collection period (min)"), &newcollectionperiod));
+ Add(new cMenuEditIntItem(tr("Series link lifetime (days)"), &newseriesLifetime));
+ Add(new cMenuEditIntItem(tr("New timer lifetime"), &newlifetime));
+ Add(new cMenuEditIntItem(tr("New timer priority"), &newpriority));
+ Add(new cMenuEditIntItem(tr("Update Time (HHMM)"), &newupdatetime));
+}
+
+void cTvaMenuSetup::Store(void)
+{
+ SetupStore("CollectionPeriod", newcollectionperiod);
+ SetupStore("SeriesLifetime", newseriesLifetime);
+ SetupStore("TimerLifetime", newlifetime);
+ SetupStore("TimerPriority", newpriority);
+ SetupStore("UpdateTime", newupdatetime);
+}
+
+
+
+/*
+ cTvaFilter - capture the CRID data from EIT.
+*/
+
+cTvaFilter::cTvaFilter(void)
+{
+ Set(0x11, 0x42); // SDT (Actual)
+ Set(0x11, 0x46); // SDT (Other)
+ Set(0x12, 0x40, 0xC0); // event info, actual(0x4E)/other(0x4F) TS, present/following
+ // event info, actual TS, schedule(0x50)/schedule for future days(0x5X)
+ // event info, other TS, schedule(0x60)/schedule for future days(0x6X)
+}
+
+void cTvaFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length)
+{
+ // do something with the data here
+ switch (Pid) {
+ case 0x11: {
+ sectionSyncer.Reset();
+ SI::SDT sdt(Data, false);
+ if (!sdt.CheckCRCAndParse()) {
+ dsyslog ("vdrtva: SDT Parse / CRC error\n");
+ return;
+ }
+ if (!sectionSyncer.Sync(sdt.getVersionNumber(), sdt.getSectionNumber(), sdt.getLastSectionNumber())) {
+ dsyslog ("vdrtva: SDT Syncer error\n");
+ return;
+ }
+ SI::SDT::Service SiSdtService;
+ for (SI::Loop::Iterator it; sdt.serviceLoop.getNext(SiSdtService, it); ) {
+ cChannel *chan = Channels.GetByChannelID(tChannelID(Source(),sdt.getOriginalNetworkId(),sdt.getTransportStreamId(),SiSdtService.getServiceId()));
+ if (chan) {
+ cChanDA *chanDA = ChanDAs->GetByChannelID(chan->Number());
+ if (!chanDA) {
+ SI::Descriptor *d;
+ for (SI::Loop::Iterator it2; (d = SiSdtService.serviceDescriptors.getNext(it2)); ) {
+ switch (d->getDescriptorTag()) {
+ case SI::DefaultAuthorityDescriptorTag: {
+ SI::DefaultAuthorityDescriptor *da = (SI::DefaultAuthorityDescriptor *)d;
+ char DaBuf[Utf8BufSize(1024)];
+ da->DefaultAuthority.getText(DaBuf, sizeof(DaBuf));
+ chanDA = ChanDAs->NewChanDA(chan->Number());
+ chanDA->SetDA(DaBuf);
+ }
+ break;
+ default: ;
+ }
+ delete d;
+ }
+ }
+ }
+ }
+ }
+ case 0x12: {
+ if (Tid >= 0x4E && Tid <= 0x6F) {
+// sectionSyncer.Reset();
+ SI::EIT eit(Data, false);
+ if (!eit.CheckCRCAndParse()) {
+ dsyslog ("vdrtva: EIT Parse / CRC error\n");
+ return;
+ }
+
+ cChannel *chan = Channels.GetByChannelID(tChannelID(Source(),eit.getOriginalNetworkId(),eit.getTransportStreamId(),eit.getServiceId()));
+ if (!chan) {
+ return;
+ }
+ SI::EIT::Event SiEitEvent;
+ for (SI::Loop::Iterator it; eit.eventLoop.getNext(SiEitEvent, it); ) {
+ cEventCRID *eventCRID = EventCRIDs->GetByID(chan->Number(), SiEitEvent.getEventId());
+ if (!eventCRID) {
+ SI::Descriptor *d;
+ char iCRIDBuf[Utf8BufSize(256)] = {'\0'}, sCRIDBuf[Utf8BufSize(256)] = {'\0'}, gCRIDBuf[Utf8BufSize(256)] = {'\0'};
+ for (SI::Loop::Iterator it2; (d = SiEitEvent.eventDescriptors.getNext(it2)); ) {
+ switch (d->getDescriptorTag()) {
+ case SI::ContentIdentifierDescriptorTag: {
+ SI::ContentIdentifierDescriptor *cd = (SI::ContentIdentifierDescriptor *)d;
+ SI::ContentIdentifierDescriptor::Identifier cde;
+ for (SI::Loop::Iterator ite; (cd->identifierLoop.getNext(cde,ite)); ) {
+ if (cde.getCridLocation() == 0) {
+ switch (cde.getCridType()) {
+ case 0x01: // ETSI 102 323 code
+ case 0x31: // UK Freeview private code
+ cde.identifier.getText(iCRIDBuf, sizeof(iCRIDBuf));
+ break;
+ case 0x02: // ETSI 102 323 code
+ case 0x32: // UK Freeview private code
+ cde.identifier.getText(sCRIDBuf, sizeof(sCRIDBuf));
+ break;
+ // ETSI 102 323 defines CRID type 0x03, which describes 'related' or 'suggested' events.
+ // Freeview broadcasts these as CRID type 0x33.
+ // There can be more than one type 0x33 descriptor per event (each with one CRID).
+ case 0x03:
+ case 0x33:
+ cde.identifier.getText(gCRIDBuf, sizeof(gCRIDBuf)); // FIXME Rashly assuming that a 0x31 CRID will always precede a 0x33 CRID.
+ if (iCRIDBuf[0]) SuggestCRIDs->NewSuggestCRID(chan->Number(), iCRIDBuf, gCRIDBuf);
+ }
+ }
+ else {
+ dsyslog ("vdrtva: Incorrect CRID Loc %x\n", cde.getCridLocation());
+ }
+ }
+ }
+ break;
+ default: ;
+ }
+ delete d;
+ }
+ if (iCRIDBuf[0] && sCRIDBuf[0]) { // Only log events which are part of a series.
+ eventCRID = EventCRIDs->NewEventCRID(chan->Number(), SiEitEvent.getEventId());
+ eventCRID->SetCRIDs(iCRIDBuf, sCRIDBuf);
+ }
+ }
+ }
+ }
+ }
+ break;
+ }
+}
+
+
+/*
+ cChanDA - Default Authority for a channel.
+*/
+
+cChanDA::cChanDA(void)
+{
+ defaultAuthority = NULL;
+}
+
+cChanDA::~cChanDA(void)
+{
+ free(defaultAuthority);
+}
+
+
+void cChanDA::Set(int Cid) {
+ cid = Cid;
+}
+
+void cChanDA::SetDA(char *DA) {
+ defaultAuthority = strcpyrealloc(defaultAuthority, DA);
+}
+
+/*
+ cChanDAs - in-memory list of channels and Default Authorities.
+*/
+
+cChanDAs::cChanDAs(void)
+{
+ maxNumber = 0;
+}
+
+cChanDAs::~cChanDAs(void)
+{
+ chanDAHash.Clear();
+}
+
+cChanDA *cChanDAs::GetByChannelID(int cid)
+{
+ cList<cHashObject> *list = chanDAHash.GetList(cid);
+ if (list) {
+ for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) {
+ cChanDA *chanDA = (cChanDA *)hobj->Object();
+ if (chanDA->Cid() == cid)
+ return chanDA;
+ }
+ }
+ return NULL;
+}
+
+cChanDA *cChanDAs::NewChanDA(int Cid)
+{
+ cChanDA *NewChanDA = new cChanDA;
+ NewChanDA->Set(Cid);
+ Add(NewChanDA);
+ chanDAHash.Add(NewChanDA, Cid);
+ ChanDAs->SetMaxNumber(ChanDAs->MaxNumber()+1);
+ return NewChanDA;
+}
+
+
+/*
+ cEventCRID - CRIDs for an event.
+*/
+
+cEventCRID::cEventCRID(void)
+{
+ iCrid = sCrid = NULL;
+}
+
+cEventCRID::~cEventCRID(void)
+{
+ free (iCrid);
+ free (sCrid);
+}
+
+void cEventCRID::Set(int Cid, tEventID Eid) {
+ eid = Eid;
+ cid = Cid;
+}
+
+void cEventCRID::SetCRIDs(char *iCRID, char *sCRID) {
+ iCrid = strcpyrealloc(iCrid, iCRID);
+ sCrid = strcpyrealloc(sCrid, sCRID);
+}
+
+
+/*
+ cEventCRIDs - in-memory list of events and CRIDs.
+*/
+
+cEventCRIDs::cEventCRIDs(void)
+{
+ maxNumber = 0;
+}
+
+cEventCRIDs::~cEventCRIDs(void)
+{
+ EventCRIDHash.Clear();
+}
+
+cEventCRID *cEventCRIDs::GetByID(int Cid, tEventID Eid)
+{
+ cList<cHashObject> *list = EventCRIDHash.GetList(Cid*33000 + Eid);
+ if (list) {
+ for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) {
+ cEventCRID *EventCRID = (cEventCRID *)hobj->Object();
+ if ((EventCRID->Eid() == Eid) && (EventCRID->Cid() == Cid))
+ return EventCRID;
+ }
+ }
+ return NULL;
+}
+
+cEventCRID *cEventCRIDs::NewEventCRID(int Cid, tEventID Eid)
+{
+ cEventCRID *NewEventCRID = new cEventCRID;
+ NewEventCRID->Set(Cid, Eid);
+ Add(NewEventCRID);
+ EventCRIDHash.Add(NewEventCRID, Eid + Cid*33000);
+ EventCRIDs->SetMaxNumber(EventCRIDs->MaxNumber()+1);
+ return NewEventCRID;
+}
+
+
+/*
+ cSuggestCRID - CRIDs of suggested items for an event.
+*/
+
+cSuggestCRID::cSuggestCRID(void)
+{
+ iCrid = gCrid = NULL;
+}
+
+cSuggestCRID::~cSuggestCRID(void)
+{
+ free (iCrid);
+ free (gCrid);
+}
+
+void cSuggestCRID::Set(int Cid, char *iCRID, char *gCRID) {
+ iCrid = strcpyrealloc(iCrid, iCRID);
+ gCrid = strcpyrealloc(gCrid, gCRID);
+ cid = Cid;
+}
+
+
+/*
+ cSuggestCRIDs - in-memory list of suggested events
+*/
+
+cSuggestCRIDs::cSuggestCRIDs(void)
+{
+ maxNumber = 0;
+}
+
+cSuggestCRIDs::~cSuggestCRIDs(void)
+{
+}
+
+cSuggestCRID *cSuggestCRIDs::NewSuggestCRID(int cid, char *icrid, char *gcrid)
+{
+ cSuggestCRID *NewSuggestCRID = new cSuggestCRID;
+ NewSuggestCRID->Set(cid, icrid, gcrid);
+ Add(NewSuggestCRID);
+ SuggestCRIDs->SetMaxNumber(SuggestCRIDs->MaxNumber()+1);
+ return NewSuggestCRID;
+}
+
+
+/*
+ cLinkItem - Entry from the links file
+*/
+
+cLinkItem::cLinkItem(void)
+{
+ sCrid = iCrids = NULL;
+}
+
+cLinkItem::~cLinkItem(void)
+{
+ free(sCrid);
+ free(iCrids);
+}
+
+void cLinkItem::Set(const char *sCRID, int ModTime, const char *iCRIDs)
+{
+ sCrid = strcpyrealloc(sCrid, sCRID);
+ modtime = ModTime;
+ iCrids = strcpyrealloc(iCrids, iCRIDs);
+}
+
+/*
+ cLinks - list of cLinkItem entities
+*/
+
+cLinks::cLinks(void)
+{
+ maxNumber = 0;
+}
+
+cLinkItem *cLinks::NewLinkItem(const char *sCRID, int ModTime, const char *iCRIDs)
+{
+ cLinkItem *NewLinkItem = new cLinkItem;
+ NewLinkItem->Set(sCRID, ModTime, iCRIDs);
+ Add(NewLinkItem);
+ Links->SetMaxNumber(Links->MaxNumber()+1);
+ return NewLinkItem;
+}
+
+VDRPLUGINCREATOR(cPluginvdrTva); // Don't touch this!