summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--p2pvr/daemon/daemon.cpp2
-rw-r--r--p2pvr/lib/schedulers/bitDumbScheduler.cpp54
-rw-r--r--p2pvr/lib/schedules.cpp324
-rw-r--r--p2pvr/lib/schedules.h82
4 files changed, 462 insertions, 0 deletions
diff --git a/p2pvr/daemon/daemon.cpp b/p2pvr/daemon/daemon.cpp
index 1bd328c..58ca66e 100644
--- a/p2pvr/daemon/daemon.cpp
+++ b/p2pvr/daemon/daemon.cpp
@@ -5,6 +5,7 @@
#include "globalDevices.h"
#include "maintenance.h"
#include "si.h"
+#include "schedules.h"
#include <logger.h>
#include <linux/dvb/frontend.h>
@@ -30,6 +31,7 @@ class P2PvrDaemon : public Daemon {
adapter->add(new GlobalDevices(), ic->stringToIdentity("GlobalDevices"));
adapter->add(new Maintenance(), ic->stringToIdentity("Maintenance"));
adapter->add(new SI(), ic->stringToIdentity("SI"));
+ adapter->add(new Schedules(), ic->stringToIdentity("Schedules"));
adapter->activate();
auto maint = P2PVR::MaintenancePrx::checkedCast(adapter->createProxy(ic->stringToIdentity("Maintenance")));
diff --git a/p2pvr/lib/schedulers/bitDumbScheduler.cpp b/p2pvr/lib/schedulers/bitDumbScheduler.cpp
new file mode 100644
index 0000000..3c16601
--- /dev/null
+++ b/p2pvr/lib/schedulers/bitDumbScheduler.cpp
@@ -0,0 +1,54 @@
+#include "../schedules.h"
+
+class TheBitDumbScheduler : public EpisodeGroup {
+ public:
+ TheBitDumbScheduler(const Episodes & eps) :
+ episodes(eps)
+ {
+ }
+
+ protected:
+ void SelectShowings()
+ {
+ SelectShowings(episodes.begin(), 1);
+ }
+
+ private:
+ void SelectShowings(Episodes::const_iterator e, uint64_t complexityLevel)
+ {
+ if (e != episodes.end()) {
+ complexityLevel *= (*e)->showings.size();
+ if (complexityLevel > 2 << 16) {
+ auto current = showings.size();
+ for (EpisodesIter ne = e; ne != episodes.end(); ne++) {
+ BOOST_FOREACH(const auto & s, (*ne)->showings) {
+ showings.push_back(s);
+ if (SuggestWithFeedback(showings) & 0x1) {
+ break;
+ }
+ showings.pop_back();
+ }
+ }
+ showings.resize(current);
+ }
+ else {
+ EpisodesIter ne = e;
+ ne++;
+ BOOST_FOREACH(const auto & s, (*e)->showings) {
+ showings.push_back(s);
+ SelectShowings(ne, complexityLevel);
+ showings.pop_back();
+ }
+ }
+ }
+ else {
+ Suggest(showings);
+ }
+ }
+
+ mutable Showings showings; // working set
+ const Episodes episodes;
+};
+
+DECLARE_GENERIC_LOADER("BitDumb", EpisodeGroupLoader, TheBitDumbScheduler);
+
diff --git a/p2pvr/lib/schedules.cpp b/p2pvr/lib/schedules.cpp
new file mode 100644
index 0000000..387dba8
--- /dev/null
+++ b/p2pvr/lib/schedules.cpp
@@ -0,0 +1,324 @@
+#include "schedules.h"
+#include "sqlContainerCreator.h"
+#include <rdbmsDataSource.h>
+#include <logger.h>
+#include <Ice/Ice.h>
+#include <sqlVariableBinder.h>
+#include "p2Helpers.h"
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+// This gets the list of things we want to record (all showings)
+static const std::string GetScheduleConditates = "\
+select (e.title, e.subtitle, e.description)::text what, e.serviceid, e.eventid, sv.transportstreamid, \
+ e.starttime - max(s.early), e.stoptime - interval '1 second' + max(s.late), \
+ max(s.priority) \
+from services sv, events e, schedules s \
+where (s.serviceid is null or s.serviceid = e.serviceid) \
+and (s.title is null or lower(s.title) = lower(e.title)) \
+and (s.eventid is null or s.eventid = e.eventid) \
+and (s.search is null or event_tsvector(e) @@ plainto_tsquery(s.search)) \
+and sv.serviceid = e.serviceid \
+and e.starttime > now() \
+and not exists ( \
+ select 1 \
+ from seen \
+ where lower(e.title) = lower(seen.title) \
+ and coalesce(lower(e.subtitle), '') = coalesce(lower(seen.subtitle), '') \
+ and ts_rank(to_tsvector(e.description), plainto_tsquery(seen.description)) + \
+ ts_rank(to_tsvector(seen.description), plainto_tsquery(e.description)) > 1) \
+group by e.serviceid, e.eventid, sv.serviceid \
+order by max(s.priority) desc, e.title, e.subtitle, e.description, sv.transportstreamid, e.starttime \
+";
+
+std::string Schedules::SchedulerAlgorithm;
+
+DECLARE_OPTIONS(Schedules, "P2PVR Scheduler options")
+("p2pvr.scheduler.algorithm", Options::value(&SchedulerAlgorithm, "BitDumb"),
+ "Implementation of episode group scheduler problem solver")
+END_OPTIONS()
+
+typedef boost::tuple<std::string, int, int, int, datetime, datetime, int> ScheduleCandidate;
+typedef boost::shared_ptr<ScheduleCandidate> ScheduleCandidatePtr;
+typedef std::vector<ScheduleCandidatePtr> ScheduleCandidates;
+
+template<>
+void
+CreateColumns<ScheduleCandidatePtr>(const ColumnCreator & cc)
+{
+ cc("what", true);
+ cc("serviceid", false);
+ cc("eventid", false);
+ cc("transportstreamid", false);
+ cc("starttime", false);
+ cc("stoptime", false);
+ cc("priority", false);
+}
+
+template<>
+void
+UnbindColumns(RowState & rs, ScheduleCandidatePtr const & s)
+{
+ rs.fields[0] >> boost::get<0>(*s);
+ rs.fields[1] >> boost::get<1>(*s);
+ rs.fields[2] >> boost::get<2>(*s);
+ rs.fields[3] >> boost::get<3>(*s);
+ rs.fields[4] >> boost::get<4>(*s);
+ rs.fields[5] >> boost::get<5>(*s);
+ rs.fields[6] >> boost::get<6>(*s);
+}
+
+Showing::Showing(unsigned int s, unsigned int e, unsigned int t, datetime start, datetime stop, int p, const Episode * ep) :
+ episode(ep),
+ serviceId(s),
+ eventId(e),
+ priority(p),
+ transportStreamId(t),
+ startTime(start),
+ stopTime(stop),
+ period(start, stop)
+{
+}
+
+Episode::Episode(const std::string & w) :
+ priority(0),
+ what(w)
+{
+}
+
+EpisodeGroup::EpisodeGroup() :
+ tuners(1),
+ sumTimeToStart(0),
+ score(0)
+{
+}
+
+bool
+EpisodeGroup::IsShowingListValid(const Showings & showings) const
+{
+ struct TransOffset {
+ unsigned int trans;
+ char offset;
+ };
+ typedef std::multimap<datetime, TransOffset> Periods;
+ typedef std::map<int, unsigned char> Usage;
+
+ Periods periods;
+ Usage usage;
+
+ BOOST_FOREACH(const auto & s, showings) {
+ if (s) {
+ periods.insert(Periods::value_type(s->startTime, {s->transportStreamId, 1}));
+ periods.insert(Periods::value_type(s->stopTime, {s->transportStreamId, -1}));
+ }
+ }
+ bool result = true;
+ BOOST_FOREACH(const auto & p, periods) {
+ auto & u = usage[p.second.trans];
+ u += p.second.offset;
+ if (std::count_if(usage.begin(), usage.end(), [](const Usage::value_type & uv) { return uv.second > 0;}) > tuners) {
+ result = false;
+ break;
+ }
+ }
+ periods.clear();
+ usage.clear();
+ return result;
+}
+
+template<typename T>
+inline
+bool NotNull(const T & p)
+{
+ return p != NULL;
+}
+
+class SumTimeToStart {
+ public:
+ SumTimeToStart(time_t & t) :
+ total(t),
+ now(boost::posix_time::second_clock::universal_time())
+ {
+ total = 0;
+ }
+ inline void operator()(const ShowingPtr & s) const
+ {
+ if (s) {
+ total += (now - s->startTime).seconds();
+ }
+ }
+ public:
+ time_t & total;
+ datetime now;
+};
+
+const Showings &
+EpisodeGroup::Solve()
+{
+ SelectShowings();
+ return selected;
+}
+
+void
+EpisodeGroup::Suggest(const Showings & showings)
+{
+ unsigned int c = 0;
+ std::for_each(showings.begin(), showings.end(), [&c](const ShowingPtr & s) { if (s) c+= s->episode->priority; });
+ if (c >= score) {
+ time_t stt;
+ std::for_each(showings.begin(), showings.end(), SumTimeToStart(stt));
+ if (stt < sumTimeToStart) {
+ if (IsShowingListValid(showings)) {
+ selected = showings;
+ score = c;
+ sumTimeToStart = stt;
+ }
+ }
+ }
+}
+
+EpisodeGroup::SuggestionResult
+EpisodeGroup::SuggestWithFeedback(const Showings & showings)
+{
+ if (IsShowingListValid(showings)) {
+ unsigned int c = 0;
+ std::for_each(showings.begin(), showings.end(), [&c](const ShowingPtr & s) { if (s) c+= s->episode->priority; });
+ if (c >= score) {
+ time_t stt;
+ std::for_each(showings.begin(), showings.end(), SumTimeToStart(stt));
+ if (stt < sumTimeToStart) {
+ selected = showings;
+ score = c;
+ sumTimeToStart = stt;
+ return SuggestionValidAndAccepted;
+ }
+ }
+ return SuggestionValid;
+ }
+ else {
+ return SuggestionInvalid;
+ }
+}
+
+void
+Schedules::GetEpisodeIntersects(Episodes & all, Episodes & grouped)
+{
+ for (Episodes::iterator aei = all.begin(); aei != all.end(); aei++) {
+ const auto & ae = *aei;
+ BOOST_FOREACH(const auto & ge, grouped) {
+ BOOST_FOREACH(const auto & gs, ge->showings) {
+ BOOST_FOREACH(const auto & as, ae->showings) {
+ if (gs->period.intersects(as->period)) {
+ Logger()->messagebf(LOG_DEBUG, " added %s", ae->what);
+ grouped.push_back(ae);
+ all.erase(aei);
+ GetEpisodeIntersects(all, grouped);
+ return;
+ }
+ }
+ }
+ }
+ }
+}
+
+void
+Schedules::DoReschedule(const Ice::Current & ice)
+{
+ auto ic = ice.adapter->getCommunicator();
+ auto devs = P2PVR::DevicesPrx::checkedCast(ice.adapter->createProxy(ic->stringToIdentity("GlobalDevices")));
+ unsigned int tunerCount = devs->TunerCount();
+
+ // Load list from database
+ ScheduleCandidates episodes;
+ SqlContainerCreator<ScheduleCandidates, ScheduleCandidate, ScheduleCandidatePtr> cct(episodes);
+ cct.populate(Select(GetScheduleConditates));
+
+ Episodes scheduleList;
+ Showings allShowings;
+ EpisodePtr cur;
+ int minPriority = 0;
+ BOOST_FOREACH(const auto & c, episodes) {
+ const auto & thisWhat = boost::get<0>(*c);
+ if (!cur || cur->what != thisWhat) {
+ cur = new Episode(thisWhat);
+ scheduleList.push_back(cur);
+ }
+ ShowingPtr s = new Showing(boost::get<1>(*c), boost::get<2>(*c), boost::get<3>(*c),
+ boost::get<4>(*c), boost::get<5>(*c), boost::get<6>(*c), cur.get());
+ minPriority = std::min(minPriority, s->priority);
+ cur->showings.push_back(s);
+ allShowings.push_back(s);
+ }
+ Logger()->messagebf(LOG_DEBUG, "%d episodes created", scheduleList.size());
+ BOOST_FOREACH(const auto & e, scheduleList) {
+ Logger()->messagebf(LOG_DEBUG, " %s", e->what);
+ BOOST_FOREACH(const auto & s, e->showings) {
+ s->priority += 1 - minPriority;
+ e->priority += s->priority;
+ }
+ e->priority /= e->showings.size();
+ }
+
+ // Solve
+ while (!scheduleList.empty()) {
+ auto work = scheduleList.begin();
+ Logger()->messagebf(LOG_DEBUG, "start %s", (*work)->what);
+ Episodes group;
+ group.push_back(*work);
+ scheduleList.erase(work);
+ GetEpisodeIntersects(scheduleList, group);
+ std::sort(group.begin(), group.end(), [](const EpisodePtr & a, const EpisodePtr & b) {
+ if (a->priority > b->priority) return true;
+ if (a->priority < b->priority) return false;
+ return a->what < b->what;
+ });
+
+ Logger()->messagebf(LOG_DEBUG, "group created with %d episodes", group.size());
+ double total = 1;
+ // Measure and add the optional to not record
+ BOOST_FOREACH(const auto & e, group) {
+ Logger()->messagebf(LOG_DEBUG, " %d * %d:%s", e->showings.size(), e->priority, e->what);
+ e->showings.push_back(NULL);
+ total *= e->showings.size();
+ }
+ Logger()->messagebf(LOG_DEBUG, "group complexity of %d options", total);
+
+ EpisodeGroupPtr sched = EpisodeGroupPtr(EpisodeGroupLoader::createNew(SchedulerAlgorithm, group));
+ sched->tuners = tunerCount;
+ std::set<ShowingPtr> selected;
+ BOOST_FOREACH(const auto & s, sched->Solve()) {
+ if (s) selected.insert(s);
+ }
+
+ BOOST_FOREACH(const auto & c, group) {
+ Logger()->messagebf(LOG_DEBUG, "Episode %s, %d options", c->what, c->showings.size());
+ BOOST_FOREACH(const auto & i, c->showings) {
+ if (selected.find(i) != selected.end()) {
+ Logger()->messagebf(LOG_DEBUG, " %s - %s (%d) <-", i->startTime, i->stopTime, i->transportStreamId);
+ }
+ else if (i) {
+ Logger()->messagebf(LOG_DEBUG, " %s - %s (%d)", i->startTime, i->stopTime, i->transportStreamId);
+ }
+ }
+ }
+ Logger()->message(LOG_DEBUG, "----------");
+ }
+}
+
+void
+Schedules::DeleteSchedule(int , const Ice::Current &)
+{
+}
+
+P2PVR::ScheduleList
+Schedules::GetSchedules(const Ice::Current &)
+{
+ P2PVR::ScheduleList rtn;
+ return rtn;
+}
+
+int
+Schedules::UpdateSchedule(const P2PVR::SchedulePtr &, const Ice::Current &)
+{
+ return 0;
+}
+
diff --git a/p2pvr/lib/schedules.h b/p2pvr/lib/schedules.h
new file mode 100644
index 0000000..caee1f7
--- /dev/null
+++ b/p2pvr/lib/schedules.h
@@ -0,0 +1,82 @@
+#ifndef SCHEDULER_H
+#define SCHEDULER_H
+
+#include <p2pvr.h>
+#include <options.h>
+#include "dbClient.h"
+#include <genLoader.h>
+
+typedef boost::posix_time::ptime datetime;
+class Episode;
+
+class Showing : public IntrusivePtrBase {
+ public:
+ Showing(unsigned int s, unsigned int e, unsigned int t, datetime start, datetime stop, int p, const Episode * ep);
+ // Record what?
+ const Episode * episode;
+ const unsigned int serviceId;
+ const unsigned int eventId;
+ int priority;
+ // Requires
+ const unsigned int transportStreamId;
+ const datetime startTime;
+ const datetime stopTime;
+ const boost::posix_time::time_period period;
+};
+typedef boost::intrusive_ptr<Showing> ShowingPtr;
+typedef std::vector<ShowingPtr> Showings;
+typedef Showings::const_iterator ShowingsIter;
+
+class Episode : public IntrusivePtrBase {
+ public:
+ Episode(const std::string & w);
+ int priority;
+ const std::string what;
+ Showings showings;
+};
+typedef boost::intrusive_ptr<Episode> EpisodePtr;
+typedef std::vector<EpisodePtr> Episodes;
+typedef Episodes::const_iterator EpisodesIter;
+
+class EpisodeGroup {
+ public:
+ EpisodeGroup();
+
+ const Showings & Solve();
+ unsigned int tuners;
+
+ protected:
+ enum SuggestionResult {
+ SuggestionInvalid = 0, SuggestionValid = 1, SuggestionValidAndAccepted = 3
+ };
+ SuggestionResult SuggestWithFeedback(const Showings &);
+ void Suggest(const Showings &);
+ virtual void SelectShowings() = 0;
+
+ private:
+ bool IsShowingListValid(const Showings & showings) const;
+ // chosen set
+ time_t sumTimeToStart;
+ unsigned int score;
+ Showings selected;
+};
+
+class Schedules : public P2PVR::Schedules, public DatabaseClient {
+ public:
+ void DeleteSchedule(int id, const Ice::Current &);
+ P2PVR::ScheduleList GetSchedules(const Ice::Current &);
+ int UpdateSchedule(const P2PVR::SchedulePtr &, const Ice::Current &);
+ void DoReschedule(const Ice::Current &);
+
+ INITOPTIONS;
+ protected:
+ static void GetEpisodeIntersects(Episodes &, Episodes &);
+ private:
+ static std::string SchedulerAlgorithm;
+};
+
+typedef GenLoader<EpisodeGroup, std::string, const Episodes &> EpisodeGroupLoader;
+typedef boost::shared_ptr<EpisodeGroup> EpisodeGroupPtr;
+
+#endif
+