diff options
| author | Dave <vdr@pickles.me.uk> | 2012-02-20 10:15:18 +0000 |
|---|---|---|
| committer | Dave <vdr@pickles.me.uk> | 2012-02-20 10:15:18 +0000 |
| commit | e3cc1a7fc9d51d7b3b41af3fdf1872d9b51afef1 (patch) | |
| tree | 2aa838a073591004e800d39c57a46c4df8159f0c | |
| parent | b9b48b3f1f8f5384d8425273669a4de2c595962f (diff) | |
| download | vdrtva-e3cc1a7fc9d51d7b3b41af3fdf1872d9b51afef1.tar.gz vdrtva-e3cc1a7fc9d51d7b3b41af3fdf1872d9b51afef1.tar.bz2 | |
Optionally email daily report.
| -rw-r--r-- | HISTORY | 5 | ||||
| -rw-r--r-- | README | 17 | ||||
| -rw-r--r-- | TODO | 8 | ||||
| -rw-r--r-- | vdrtva.c | 199 | ||||
| -rw-r--r-- | vdrtva.h | 12 |
5 files changed, 186 insertions, 55 deletions
@@ -33,3 +33,8 @@ VDR Plugin 'vdrtva' Revision History 2012-02-01: Version 0.1.0 - Fixed OSD setup menu. - Removed duplicates from 'suggestions' report. + +2012-02-20: Version 0.1.1 +- When timer clash detected, check alternatives for further clashes. +- Disable functions needing CRID data if none captured yet. +- Optionally email a daily report. @@ -62,15 +62,28 @@ OSD (default 03:00). It captures CRID data for a time (default 10 minutes) then: - Checks that the event being recorded by each timer is the same as when the timer was set (ie that the EPG has not changed in the meantime) -- Flags any split events (eg a long programme with a news sumary in the middle). +- Flags any split events (eg a long programme with a news summary in the middle). At present a manual check is needed that all parts of the programme are set to be recorded. -The plugin logs its activity through the VDR syslog. +The plugin takes the following parameters: + + -l n --lifetime=n Lifetime of new timers (default 99) + -m addr --mailaddr=addr Address to send mail report + -p n --priority=n Priority of new timers (default 99) + -s n --serieslifetime=n Days to remember a series after the last event + (default 30) + -u HH:MM --updatetime=HH:MM Time to update series links (default 03:00) + +The plugin logs activity through the VDR syslog, unless the -m parameter is set +in which case only errors go to syslog and a report is emailed to the given +address daily. The plugin has an SVDRP interface which is mainly used for debugging, but could be used to interface with other applications. The commands are: +LLOG Print the pending log report + LSTL Print the series links list LSTS Print the 'suggested' events list @@ -1,5 +1,7 @@ -Mail a log file after the update. - +Improve log report (HTML?). + +Add suggestions to log report when new timer added. + Check split recordings should create timers if necessary. Timer clash check code needs to cope with multiple tuner cards. @@ -8,6 +10,8 @@ I18n (for OSD). Some events have a series CRID but no item CRID - how to handle these? +Delete a series link if the only timer is manually deleted. + Bugs: 'Suggestions' list in memory contains duplicates (removed by SVDRP output). Very rare crash 'pure virtual method called' in plugin. @@ -11,17 +11,18 @@ #include <libsi/descriptor.h> #include <stdarg.h> #include <getopt.h> - +#include <pwd.h> #include "vdrtva.h" -#define SVDRPOSD_BUFSIZE KILOBYTE(4) +#define REPORT(a...) void( (tvalog.mailFrom()) ? tvalog.Append(a) : tvasyslog(a) ) + cChanDAs *ChanDAs; cEventCRIDs *EventCRIDs; cSuggestCRIDs *SuggestCRIDs; cLinks *Links; -static const char *VERSION = "0.1.0"; +static const char *VERSION = "0.1.1"; static const char *DESCRIPTION = "TV-Anytime plugin"; //static const char *MAINMENUENTRY = "vdrTva"; @@ -30,7 +31,7 @@ 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) - +cTvaLog tvalog; class cPluginvdrTva : public cPlugin { @@ -56,6 +57,7 @@ private: void StopDataCapture(void); void Update(void); void Check(void); + void tvasyslog(const char *Fmt, ...); public: cPluginvdrTva(void); @@ -107,6 +109,7 @@ 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" + " -m addr --mailaddr=addr Address to send mail report\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"; @@ -120,18 +123,22 @@ bool cPluginvdrTva::ProcessArgs(int argc, char *argv[]) { "priority", required_argument, NULL, 'p' }, { "lifetime", required_argument, NULL, 'l' }, { "updatetime", required_argument, NULL, 'u' }, + { "mailaddr", required_argument, NULL, 'm' }, { 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) { + while ((c = getopt_long(argc, argv, "l:m:p:s:u:", long_options, NULL)) != -1) { switch (c) { case 'l': opt = atoi(optarg); if (opt > 0) lifetime = opt; break; + case 'm': + tvalog.setmailTo(optarg); + break; case 'p': opt = atoi(optarg); if (opt > 0) priority = opt; @@ -165,6 +172,33 @@ bool cPluginvdrTva::Start(void) configDir = strcpyrealloc(configDir, cPlugin::ConfigDirectory("vdrtva")); LoadLinksFile(); statusMonitor = new cTvaStatusMonitor; + if (tvalog.mailTo()) { + struct stat sb; + if (stat("/usr/sbin/sendmail", &sb) == 0) { + char hostname[256]; + if (!gethostname (hostname, sizeof(hostname))) { + char buf[16384]; + struct passwd pwd, *result; + size_t bufsize = sizeof(buf); + int s = getpwuid_r (getuid(), &pwd, buf, bufsize, &result); + if ((s == 0) && (result != NULL)) { + char from[256]; + sprintf(from, "%s@%s", pwd.pw_name, hostname); + tvalog.setmailFrom(from); + isyslog("vdrtva: daily reports will be mailed from %s to %s", from, tvalog.mailTo()); + } + else { + esyslog("vdrtva: unable to establish vdr's user name"); + } + } + else { + esyslog("vdrtva: unable to establish vdr's hostname"); + } + } + else { + esyslog("vdrtva: no mail server found"); + } + } struct tm tm_r; char buff[32]; time_t now = time(NULL); @@ -175,7 +209,7 @@ bool cPluginvdrTva::Start(void) nextactiontime = mktime(&tm_r); if (nextactiontime < now) nextactiontime += SECSINDAY; ctime_r(&nextactiontime, buff); - isyslog("vdrtva: next update due at %s", buff); + REPORT("Vdrtva initialised, next update due at %s", buff); return true; } @@ -186,6 +220,7 @@ void cPluginvdrTva::Stop(void) delete Filter; Filter = NULL; } + tvalog.MailLog(); } void cPluginvdrTva::Housekeeping(void) @@ -211,6 +246,7 @@ void cPluginvdrTva::Housekeeping(void) Check(); nextactiontime += (SECSINDAY - collectionperiod); state = 0; + tvalog.MailLog(); break; } } @@ -267,6 +303,8 @@ const char **cPluginvdrTva::SVDRPHelpPages(void) { // Return help text for SVDRP commands this plugin implements static const char *HelpPages[] = { + "LLOG\n" + " Print the action log.", "LSTL\n" " Print the Links list.", "LSTS\n" @@ -289,16 +327,21 @@ const char **cPluginvdrTva::SVDRPHelpPages(void) cString cPluginvdrTva::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode) { // Process SVDRP commands this plugin implements - cTvaLog log; + cTvaLog reply; isyslog ("vdrtva: processing command %s", Command); - if (strcasecmp(Command, "LSTL") == 0) { + if (strcasecmp(Command, "LLOG") == 0) { + ReplyCode = 250; + if (tvalog.Length() > 0) return cString(tvalog.Buffer()); + else return cString::sprintf("Nothing in the buffer!"); + } + else if (strcasecmp(Command, "LSTL") == 0) { if (Links && (Links->MaxNumber() >=1)) { ReplyCode = 250; for (cLinkItem *linkItem = Links->First(); linkItem; linkItem = Links->Next(linkItem)) { - log.Append("%s,%d;%s\n", linkItem->sCRID(), linkItem->ModTime(), linkItem->iCRIDs()); + reply.Append("%s,%d;%s\n", linkItem->sCRID(), linkItem->ModTime(), linkItem->iCRIDs()); } } - if (log.Length() > 0) return cString(log.Buffer()); + if (reply.Length() > 0) return cString(reply.Buffer()); else return cString::sprintf("Nothing in the buffer!"); } else if (strcasecmp(Command, "LSTS") == 0) { @@ -311,12 +354,12 @@ cString cPluginvdrTva::SVDRPCommand(const char *Command, const char *Option, int if (!next || strcmp(next->iCRID(), suggest->iCRID()) || strcmp(next->gCRID(), suggest->gCRID())) { cChanDA *chanDA = ChanDAs->GetByChannelID(suggest->Cid()); if(chanDA) { - log.Append("%s%s %s%s\n", chanDA->DA(), suggest->iCRID(), chanDA->DA(), suggest->gCRID()); + reply.Append("%s%s %s%s\n", chanDA->DA(), suggest->iCRID(), chanDA->DA(), suggest->gCRID()); } } suggest = next; } - if (log.Length() > 0) return cString(log.Buffer()); + if (reply.Length() > 0) return cString(reply.Buffer()); else return cString::sprintf("Nothing in the buffer!"); } else @@ -328,10 +371,10 @@ cString cPluginvdrTva::SVDRPCommand(const char *Command, const char *Option, int for (cEventCRID *eventCRID = EventCRIDs->First(); eventCRID; eventCRID = EventCRIDs->Next(eventCRID)) { cChanDA *chanDA = ChanDAs->GetByChannelID(eventCRID->Cid()); if(chanDA) { - log.Append("%d %d %s%s %s%s\n", chanDA->Cid(), eventCRID->Eid(), chanDA->DA(), eventCRID->iCRID(), chanDA->DA(), eventCRID->sCRID()); + reply.Append("%d %d %s%s %s%s\n", chanDA->Cid(), eventCRID->Eid(), chanDA->DA(), eventCRID->iCRID(), chanDA->DA(), eventCRID->sCRID()); } } - if (log.Length() > 0) return cString(log.Buffer()); + if (reply.Length() > 0) return cString(reply.Buffer()); else return cString::sprintf("Nothing in the buffer!"); } else @@ -341,9 +384,9 @@ cString cPluginvdrTva::SVDRPCommand(const char *Command, const char *Option, int if (ChanDAs && (ChanDAs->MaxNumber() >= 1)) { ReplyCode = 250; for (cChanDA *chanDA = ChanDAs->First(); chanDA; chanDA = ChanDAs->Next(chanDA)) { - log.Append("%d %s\n", chanDA->Cid(), chanDA->DA()); + reply.Append("%d %s\n", chanDA->Cid(), chanDA->DA()); } - if (log.Length() > 0) return cString(log.Buffer()); + if (reply.Length() > 0) return cString(reply.Buffer()); else return cString::sprintf("Nothing in the buffer!"); } else @@ -435,7 +478,7 @@ bool cPluginvdrTva::AddSeriesLink(const char *scrid, int modtime, const char *ic 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); + REPORT("Adding new event %s to series %s\n", icrid, scrid); return true; } return false; @@ -443,7 +486,7 @@ bool cPluginvdrTva::AddSeriesLink(const char *scrid, int modtime, const char *ic } } Links->NewLinkItem(scrid, modtime, icrid); - isyslog("vdrtva: Creating new series %s for event %s\n", scrid, icrid); + REPORT("Creating new series %s for event %s\n", scrid, icrid); return true; } @@ -468,7 +511,7 @@ void cPluginvdrTva::LoadLinksFile() fclose (f); isyslog("vdrtva: loaded %d series links\n", Links->MaxNumber()); } - else isyslog("vdrtva: series links file not found\n"); + else esyslog("vdrtva: series links file not found\n"); } bool cPluginvdrTva::SaveLinksFile() @@ -589,9 +632,9 @@ void cPluginvdrTva::CheckChangedEvents() 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())); + if (!event) REPORT("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()); + REPORT("Changed timer event at %s: %s <=> %s", *DayDateTime(timer->StartTime()), timer->File(), event->Title()); } } } @@ -616,7 +659,7 @@ void cPluginvdrTva::CheckTimerClashes(void) 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()); + REPORT("Timer collision at %s. %s <=> %s", *DayDateTime(timer1->StartTime()), timer1->File(), timer2->File()); FindAlternatives(timer1->Event()); FindAlternatives(timer2->Event()); } @@ -656,7 +699,7 @@ void cPluginvdrTva::FindAlternatives(const cEvent *event) if (schedule) { const cEvent *event2 = schedule->GetEvent(eventcrid2->Eid(), 0); if (!found) { - isyslog("vdrtva: Alternatives for '%s':", event->Title()); + REPORT("Alternatives for '%s':", event->Title()); found = true; } bool clash = false; @@ -667,21 +710,21 @@ void cPluginvdrTva::FindAlternatives(const cEvent *event) ||(event2->StartTime() >= timer->StartTime() && event2->StartTime() < timer->StopTime())) { cChannel *channel = Channels.GetByChannelID(event2->ChannelID()); if (timer->Channel()->Transponder() != channel->Transponder()) { - isyslog("vdrtva: %s %s (clash with timer '%s')", channel2->Name(), *DayDateTime(event2->StartTime()), timer->File()); + REPORT("%s %s (clash with timer '%s')", channel2->Name(), *DayDateTime(event2->StartTime()), timer->File()); clash = true; } } } } if (!clash) { - isyslog("vdrtva: %s %s", channel2->Name(), *DayDateTime(event2->StartTime())); + REPORT("%s %s", channel2->Name(), *DayDateTime(event2->StartTime())); } } } } } } - if (!found) isyslog("vdrtva: No alternatives for '%s'", event->Title()); + if (!found) REPORT("No alternatives for '%s'", event->Title()); } // Check that, if any split events (eg a long programme with a news break in the middle) @@ -704,7 +747,7 @@ bool cPluginvdrTva::CheckSplitTimers(void) // 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()); + REPORT("Timer for split event '%s' found - check all parts are being recorded!", event->Title()); } } } @@ -740,7 +783,7 @@ bool cPluginvdrTva::CreateTimerFromEvent(const cEvent *event) { if (!t) { Timers.Add(timer); Timers.SetModified(); - isyslog("vdrtva: timer %s added on %s", *timer->ToDescr(), *DateString(timer->StartTime())); + REPORT("timer %s added on %s", *timer->ToDescr(), *DateString(timer->StartTime())); return true; } isyslog("vdrtva: Duplicate timer creation attempted for %s on %s", *timer->ToDescr(), *DateString(timer->StartTime())); @@ -748,6 +791,21 @@ bool cPluginvdrTva::CreateTimerFromEvent(const cEvent *event) { return false; } +// Report actions to syslog if we don't want an email. + +void tvasyslog(const char *Fmt, ...) { + + va_list ap; + char buff[4096]; + + va_start(ap, Fmt); + vsnprintf(buff, sizeof(buff), Fmt, ap); + va_end(ap); + isyslog("vdrtva: %s", buff); +} + + + /* cTvaStatusMonitor - callback for timer changes. */ @@ -755,11 +813,15 @@ bool cPluginvdrTva::CreateTimerFromEvent(const cEvent *event) { cTvaStatusMonitor::cTvaStatusMonitor(void) { timeradded = NULL; + lasttimer = NULL; } void cTvaStatusMonitor::TimerChange(const cTimer *Timer, eTimerChange Change) { - if (Change == tcAdd) timeradded = time(NULL); + if (Change == tcAdd) { + timeradded = time(NULL); + lasttimer = Timer; + } } int cTvaStatusMonitor::GetTimerAddedDelta(void) @@ -810,39 +872,46 @@ void cTvaMenuSetup::Store(void) */ cTvaLog::cTvaLog(void) { - buffer= NULL; + buffer = mailfrom = mailto = NULL; } cTvaLog::~cTvaLog(void) { if (buffer) free(buffer); + if (mailfrom) free(mailfrom); + if (mailto) free(mailto); } -bool cTvaLog::Append(const char *Fmt, ...) +// Append an entry to the log. Ensure the entry is CR-terminated. + +void cTvaLog::Append(const char *Fmt, ...) { va_list ap; if (!buffer) { - length = 0; - size = SVDRPOSD_BUFSIZE; - buffer = (char *) malloc(sizeof(char) * size); + length = 0; + size = 4096; + 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 *= 2; - char *tmp = (char *) realloc(buffer, sizeof(char) * size); - if (!tmp) - free(buffer); - buffer = tmp; + va_start(ap, Fmt); + int n = vsnprintf(buffer + length, size - length, Fmt, ap); + va_end(ap); + if (n < size - length - 1) { + length += n; + if (*(buffer+length-1) != '\n') { + *(buffer+length) = '\n'; + length++; + *(buffer+length) = '\0'; + } + return; + } +// overflow: realloc and try again + size *= 2; + char *tmp = (char *) realloc(buffer, sizeof(char) * size); + if (!tmp) free(buffer); + buffer = tmp; } - return false; + return; } int cTvaLog::Length(void) { @@ -850,6 +919,38 @@ int cTvaLog::Length(void) { return length; } +void cTvaLog::setmailTo(char *opt) { + mailto = strcpyrealloc(mailto, opt); +} + +void cTvaLog::setmailFrom(char *opt) { + mailfrom = strcpyrealloc(mailfrom, opt); +} + +// Mail out the daily report. + +void cTvaLog::MailLog(void) { +FILE* mail; +char mailcmd[256]; + + if (length == 0) return; + + snprintf(mailcmd, sizeof(mailcmd), "/usr/sbin/sendmail -i -oem %s", mailto); + if (!(mail = popen(mailcmd, "w"))) { + esyslog("vdrtva: cannot open sendmail"); + return; + } + fprintf(mail, "From: %s\n", mailfrom); + fprintf(mail, "To: %s\n", mailto); + fprintf(mail, "Subject: vdrTva report\n"); +// fprintf(mail, "Content-Type: text/plain; charset=%s\n", GetCodeset().c_str()); + fprintf(mail, "\n"); + fputs(buffer, mail); + pclose(mail); + Clear(); +} + + /* cTvaFilter - capture the CRID data from EIT. */ @@ -15,6 +15,7 @@ public: class cTvaStatusMonitor : public cStatus { private: time_t timeradded; + const cTimer *lasttimer; protected: virtual void TimerChange(const cTimer *Timer, eTimerChange Change); // Indicates a change in the timer settings. @@ -26,6 +27,7 @@ class cTvaStatusMonitor : public cStatus { cTvaStatusMonitor(void); int GetTimerAddedDelta(void); void ClearTimerAdded(void); + const cTimer *GetLastTimer() { return lasttimer; } }; @@ -45,15 +47,21 @@ public: class cTvaLog { private: - char *buffer; + char *buffer, *mailfrom, *mailto; int length; int size; public: cTvaLog(void); ~cTvaLog(void); - bool Append(const char *Fmt, ...); + void Append(const char *Fmt, ...); const char* Buffer() { return buffer; } int Length(void); + void Clear() { length = 0; } + void MailLog(void); + void setmailTo(char *opt); + const char * mailTo() { return mailto; } + void setmailFrom(char *opt); + const char * mailFrom() { return mailfrom; } }; |
