diff options
| -rw-r--r-- | p2pvr/daemon/daemon.cpp | 2 | ||||
| -rw-r--r-- | p2pvr/lib/schedulers/bitDumbScheduler.cpp | 54 | ||||
| -rw-r--r-- | p2pvr/lib/schedules.cpp | 324 | ||||
| -rw-r--r-- | p2pvr/lib/schedules.h | 82 | 
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 + | 
