From 1deb85fbd8323f30c0ee3f40fc4444490abf6150 Mon Sep 17 00:00:00 2001
From: Roger Siddons <dizygotheca@ntlworld.com>
Date: Mon, 6 Oct 2014 16:12:43 +0100
Subject: [PATCH 5/6] Watchlist: Implement multiple ordering strategies


diff --git a/mythtv/programs/mythfrontend/globalsettings.cpp b/mythtv/programs/mythfrontend/globalsettings.cpp
index 1d856f2..7818a9e 100644
--- a/mythtv/programs/mythfrontend/globalsettings.cpp
+++ b/mythtv/programs/mythfrontend/globalsettings.cpp
@@ -3256,6 +3256,18 @@ static HostCheckBox *PlaybackWLAutoExpire()
     return gc;
 }
 
+static HostComboBox *PlaybackWLStrategy()
+{
+    HostComboBox *gc = new HostComboBox("WatchlistOrder");
+
+    gc->setLabel(PlaybackSettings::tr("Sort method"));
+    gc->addSelection(PlaybackSettings::tr("Classic", "Watchlist"), "Classic");
+    gc->setHelpText(PlaybackSettings::tr("The watchlist ordering method. "
+                                         "Classic keeps up-to-date with current "
+                                         "TV."));
+    return gc;
+}
+
 static HostSpinBox *PlaybackWLMaxAge()
 {
     HostSpinBox *gs = new HostSpinBox("PlaybackWLMaxAge", 30, 180, 10);
@@ -3291,6 +3303,21 @@ static HostSpinBox *PlaybackWLBlackOut()
     return gs;
 }
 
+WatchListStrategy::WatchListStrategy() :
+    TriggeredConfigurationGroup(false, false, true, true)
+{
+     Setting* strategy = PlaybackWLStrategy();
+     addChild(strategy);
+     setTrigger(strategy);
+
+     ConfigurationGroup* classic = new VerticalConfigurationGroup(false,false);
+
+     classic->addChild(PlaybackWLMaxAge());
+     classic->addChild(PlaybackWLBlackOut());
+
+     addTarget("Classic", classic);
+};
+
 WatchListSettings::WatchListSettings() :
     TriggeredConfigurationGroup(false, false, true, true)
 {
@@ -3299,16 +3326,16 @@ WatchListSettings::WatchListSettings() :
      addChild(watchList);
      setTrigger(watchList);
 
-     ConfigurationGroup* settings = new VerticalConfigurationGroup(false);
+     ConfigurationGroup* settings =
+             new VerticalConfigurationGroup(false,false,true,true);
 
      settings->addChild(PlaybackWLStart());
      settings->addChild(PlaybackWLAutoExpire());
-     settings->addChild(PlaybackWLMaxAge());
-     settings->addChild(PlaybackWLBlackOut());
+     settings->addChild(new WatchListStrategy());
 
      addTarget("1", settings);
 
-     addTarget("0", new VerticalConfigurationGroup(true));
+     addTarget("0", new VerticalConfigurationGroup(false,false));
 };
 
 static HostCheckBox *LCDShowTime()
diff --git a/mythtv/programs/mythfrontend/globalsettings.h b/mythtv/programs/mythfrontend/globalsettings.h
index 38fdc48..053aec8 100644
--- a/mythtv/programs/mythfrontend/globalsettings.h
+++ b/mythtv/programs/mythfrontend/globalsettings.h
@@ -38,6 +38,13 @@ class LcdSettings : public TriggeredConfigurationGroup
     LcdSettings();
 };
 
+class WatchListStrategy : public TriggeredConfigurationGroup
+{
+    Q_OBJECT
+
+  public:
+    WatchListStrategy();
+};
 
 class WatchListSettings : public TriggeredConfigurationGroup
 {
diff --git a/mythtv/programs/mythfrontend/playbackbox.cpp b/mythtv/programs/mythfrontend/playbackbox.cpp
index c2924df..eff3d85 100644
--- a/mythtv/programs/mythfrontend/playbackbox.cpp
+++ b/mythtv/programs/mythfrontend/playbackbox.cpp
@@ -439,6 +439,7 @@ PlaybackBox::PlaybackBox(MythScreenStack *parent, QString name,
     m_watchListAutoExpire= gCoreContext->GetNumSetting("PlaybackWLAutoExpire", 0);
     m_watchListMaxAge    = gCoreContext->GetNumSetting("PlaybackWLMaxAge", 60);
     m_watchListBlackOut  = gCoreContext->GetNumSetting("PlaybackWLBlackOut", 2);
+    m_watchListStrategy   = gCoreContext->GetSetting("WatchListOrder", "Classic");
 
     bool displayCat  = gCoreContext->GetNumSetting("DisplayRecGroupIsCategory", 0);
 
@@ -1586,6 +1587,199 @@ static void restore_position(
     }
 }
 
+/// Sort programs by original Watchlist rules
+void PlaybackBox::OrderByClassicStrategy(TitleMap& selections, ProgramOrder& ordered)
+{
+    QDateTime now = MythDate::current();
+    int baseValue = m_watchListMaxAge * 2 / 3;
+
+    QMap<int, int> recType;
+    QMap<int, int> maxEpisodes;
+    QMap<int, int> avgDelay;
+    QMap<int, int> spanHours;
+    QMap<int, int> delHours;
+    QMap<int, int> nextHours;
+
+    MSqlQuery query(MSqlQuery::InitCon());
+    query.prepare("SELECT recordid, type, maxepisodes, avg_delay, "
+                  "next_record, last_record, last_delete FROM record;");
+
+    if (query.exec())
+    {
+        while (query.next())
+        {
+            int recid = query.value(0).toInt();
+            recType[recid] = query.value(1).toInt();
+            maxEpisodes[recid] = query.value(2).toInt();
+            avgDelay[recid] = query.value(3).toInt();
+
+            QDateTime next_record =
+                    MythDate::as_utc(query.value(4).toDateTime());
+            QDateTime last_record =
+                    MythDate::as_utc(query.value(5).toDateTime());
+            QDateTime last_delete =
+                    MythDate::as_utc(query.value(6).toDateTime());
+
+            // Time between the last and next recordings
+            spanHours[recid] = 1000;
+            if (last_record.isValid() && next_record.isValid())
+                spanHours[recid] =
+                        last_record.secsTo(next_record) / 3600 + 1;
+
+            // Time since the last episode was deleted
+            delHours[recid] = 1000;
+            if (last_delete.isValid())
+                delHours[recid] = last_delete.secsTo(now) / 3600 + 1;
+
+            // Time until the next recording if any
+            if (next_record.isValid())
+                nextHours[recid] = now.secsTo(next_record) / 3600 + 1;
+        }
+    }
+
+    TitleMap::iterator it = selections.begin();
+    while (it != selections.end())
+    {
+        uint score = 0;
+        ProgramInfo* p = it.value();
+        int recid = p->GetRecordingRuleID();
+        int avgd =  avgDelay[recid];
+
+        if (avgd == 0)
+            avgd = 100;
+
+        // Set the intervals beyond range if there is no record entry
+        if (spanHours[recid] == 0)
+        {
+            spanHours[recid] = 1000;
+            delHours[recid] = 1000;
+        }
+
+        // add point equal to baseValue for each additional episode
+        if (recid && maxEpisodes[recid] == 0)
+            score += (m_watchlistCount[extract_watchlist_title(*p)] - 1) *
+                    baseValue;
+
+        // add points every 3hr leading up to the next recording
+        if (nextHours[recid] > 0 && nextHours[recid] < baseValue * 3)
+            score += (baseValue * 3 - nextHours[recid]) / 3;
+
+        int hrs = p->GetScheduledEndTime().secsTo(now) / 3600;
+        if (hrs < 1)
+            hrs = 1;
+
+        // add points for a new recording that decrease each hour
+        if (hrs < 42)
+            score += 42 - hrs;
+
+        // add points for how close the recorded time of day is to 'now'
+        score += abs((hrs % 24) - 12) * 2;
+
+        // Daily
+        if (spanHours[recid] < 50 ||
+                recType[recid] == kDailyRecord)
+        {
+            if (delHours[recid] < m_watchListBlackOut * 4)
+            {
+                LOG(VB_GUI, LOG_DEBUG,
+                    QString("Watchlist: Recently deleted daily:  %1")
+                    .arg(p->GetTitle()));
+
+                it = selections.erase(it);
+                continue;
+            }
+            else
+            {
+                LOG(VB_GUI, LOG_DEBUG, QString("Watchlist: Daily interval:  %1")
+                    .arg(p->GetTitle()));
+
+                if (maxEpisodes[recid] > 0)
+                    score += (baseValue / 2) + (hrs / 24);
+                else
+                    score += (baseValue / 5) + hrs;
+            }
+        }
+        // Weekly
+        else if (nextHours[recid] ||
+                 recType[recid] == kWeeklyRecord)
+
+        {
+            if (delHours[recid] < (m_watchListBlackOut * 24) - 4)
+            {
+                LOG(VB_GUI, LOG_DEBUG,
+                    QString("Watchlist: Recently deleted weekly:  %1")
+                    .arg(p->GetTitle()));
+
+                it = selections.erase(it);
+                continue;
+            }
+            else
+            {
+                LOG(VB_GUI, LOG_DEBUG, QString("Watchlist: Weekly interval: %1")
+                    .arg(p->GetTitle()));
+
+                if (maxEpisodes[recid] > 0)
+                    score += (baseValue / 2) + (hrs / 24);
+                else
+                    score += (baseValue / 3) + (baseValue * hrs / 24 / 4);
+            }
+        }
+        // Not recurring
+        else
+        {
+            if (delHours[recid] < (m_watchListBlackOut * 48) - 4)
+            {
+                it = selections.erase(it);
+                continue;
+            }
+            else
+            {
+                // add points for a new Single or final episode
+                if (hrs < 36)
+                    score += baseValue * (36 - hrs) / 36;
+
+                if (avgd != 100)
+                {
+                    if (maxEpisodes[recid] > 0)
+                        score += (baseValue / 2) + (hrs / 24);
+                    else
+                        score += (baseValue / 3) + (baseValue * hrs / 24 / 4);
+                }
+                else if ((hrs / 24) < m_watchListMaxAge)
+                    score += hrs / 24;
+                else
+                    score += m_watchListMaxAge;
+            }
+        }
+
+        // Factor based on the average time shift delay.
+        // Scale the avgd range of 0 thru 200 hours to 133% thru 67%
+        int delaypct = avgd / 3 + 67;
+
+        if (avgd < 100)
+            score = score * (200 - delaypct) / 100;
+        else if (avgd > 100)
+            score = score * 100 / delaypct;
+
+        // use score as primary key in top 32 bits,
+        // use age in secs as a secondary key in low 32 bits to ensure equal
+        // scores are ordered oldest first. Copes with progs up to 136 yrs old
+        score_type longScore = (static_cast<score_type>(score) << 32)
+                | p->GetScheduledStartTime().secsTo(now);
+
+        ordered.insert(longScore, p);
+
+        ++it;
+
+        LOG(VB_GUI, LOG_DEBUG, QString("Watchlist:%1 %2 %3 %4")
+            .arg(score, 5)
+            .arg(longScore, 12)
+            .arg(p->GetTitle())
+            .arg(MythDate::toString(p->GetScheduledStartTime(),
+                                    MythDate::kDateShort)));
+    }
+}
+
 bool PlaybackBox::UpdateUILists(void)
 {
     m_isFilling = true;
@@ -1918,222 +2112,11 @@ bool PlaybackBox::UpdateUILists(void)
 
     if (!watchEpisode.empty())
     {
-        QDateTime now = MythDate::current();
-        int baseValue = m_watchListMaxAge * 2 / 3;
+        ProgramOrder watchList;
 
-        QMap<int, int> recType;
-        QMap<int, int> maxEpisodes;
-        QMap<int, int> avgDelay;
-        QMap<int, int> spanHours;
-        QMap<int, int> delHours;
-        QMap<int, int> nextHours;
-        typedef unsigned long long score_type; // 64 bit
-        QMultiMap<score_type, ProgramInfo*> watchList; // progs keyed by score
+        if (m_watchListStrategy == "Classic")
 
-        MSqlQuery query(MSqlQuery::InitCon());
-        query.prepare("SELECT recordid, type, maxepisodes, avg_delay, "
-                      "next_record, last_record, last_delete FROM record;");
-
-        if (query.exec())
-        {
-            while (query.next())
-            {
-                int recid = query.value(0).toInt();
-                recType[recid] = query.value(1).toInt();
-                maxEpisodes[recid] = query.value(2).toInt();
-                avgDelay[recid] = query.value(3).toInt();
-
-                QDateTime next_record =
-                    MythDate::as_utc(query.value(4).toDateTime());
-                QDateTime last_record =
-                    MythDate::as_utc(query.value(5).toDateTime());
-                QDateTime last_delete =
-                    MythDate::as_utc(query.value(6).toDateTime());
-
-                // Time between the last and next recordings
-                spanHours[recid] = 1000;
-                if (last_record.isValid() && next_record.isValid())
-                    spanHours[recid] =
-                        last_record.secsTo(next_record) / 3600 + 1;
-
-                // Time since the last episode was deleted
-                delHours[recid] = 1000;
-                if (last_delete.isValid())
-                    delHours[recid] = last_delete.secsTo(now) / 3600 + 1;
-
-                // Time until the next recording if any
-                if (next_record.isValid())
-                    nextHours[recid] = now.secsTo(next_record) / 3600 + 1;
-            }
-        }
-
-        TitleMap::iterator it = watchEpisode.begin();
-        while (it != watchEpisode.end())
-        {
-            uint score = 0;
-            ProgramInfo* p = it.value();
-            int recid = p->GetRecordingRuleID();
-            int avgd =  avgDelay[recid];
-
-            if (avgd == 0)
-                avgd = 100;
-
-            // Set the intervals beyond range if there is no record entry
-            if (spanHours[recid] == 0)
-            {
-                spanHours[recid] = 1000;
-                delHours[recid] = 1000;
-            }
-
-            // add point equal to baseValue for each additional episode
-            if (recid && maxEpisodes[recid] == 0)
-            {
-                score += (m_watchlistCount[extract_watchlist_title(*p)] - 1) *
-                        baseValue;
-            }
-
-            // add points every 3hr leading up to the next recording
-            if (nextHours[recid] > 0 && nextHours[recid] < baseValue * 3)
-            {
-                score += (baseValue * 3 - nextHours[recid]) / 3;
-            }
-
-            int hrs = p->GetScheduledEndTime().secsTo(now) / 3600;
-            if (hrs < 1)
-                hrs = 1;
-
-            // add points for a new recording that decrease each hour
-            if (hrs < 42)
-            {
-                score += 42 - hrs;
-            }
-
-            // add points for how close the recorded time of day is to 'now'
-            score += abs((hrs % 24) - 12) * 2;
-
-            // Daily
-            if (spanHours[recid] < 50 ||
-                recType[recid] == kDailyRecord)
-            {
-                if (delHours[recid] < m_watchListBlackOut * 4)
-                {
-                    LOG(VB_GUI, LOG_DEBUG,
-                        QString("Watchlist: Recently deleted daily:  %1")
-                            .arg(p->GetTitle()));
-                    it = watchEpisode.erase(it);
-                    continue;
-                }
-                else
-                {
-                    LOG(VB_GUI, LOG_DEBUG, QString("Watchlist: Daily interval:  %1")
-                            .arg(p->GetTitle()));
-
-                    if (maxEpisodes[recid] > 0)
-                    {
-                        score += (baseValue / 2) + (hrs / 24);
-                    }
-                    else
-                    {
-                        score += (baseValue / 5) + hrs;
-                    }
-                }
-            }
-            // Weekly
-            else if (nextHours[recid] ||
-                     recType[recid] == kWeeklyRecord)
-
-            {
-                if (delHours[recid] < (m_watchListBlackOut * 24) - 4)
-                {
-                    LOG(VB_GUI, LOG_DEBUG,
-                        QString("Watchlist: Recently deleted weekly:  %1")
-                            .arg(p->GetTitle()));
-                    it = watchEpisode.erase(it);
-                    continue;
-                }
-                else
-                {
-                    LOG(VB_GUI, LOG_DEBUG, QString("Watchlist: Weekly interval: %1")
-                            .arg(p->GetTitle()));
-
-                    if (maxEpisodes[recid] > 0)
-                    {
-                        score += (baseValue / 2) + (hrs / 24);
-                    }
-                    else
-                    {
-                        score += (baseValue / 3) + (baseValue * hrs / 24 / 4);
-                    }
-                }
-            }
-            // Not recurring
-            else
-            {
-                if (delHours[recid] < (m_watchListBlackOut * 48) - 4)
-                {
-                    it = watchEpisode.erase(it);
-                    continue;
-                }
-                else
-                {
-                    // add points for a new Single or final episode
-                    if (hrs < 36)
-                    {
-                        score += baseValue * (36 - hrs) / 36;
-                    }
-
-                    if (avgd != 100)
-                    {
-                        if (maxEpisodes[recid] > 0)
-                        {
-                            score += (baseValue / 2) + (hrs / 24);
-                        }
-                        else
-                        {
-                            score += (baseValue / 3) + (baseValue * hrs / 24 / 4);
-                        }
-                    }
-                    else if ((hrs / 24) < m_watchListMaxAge)
-                    {
-                        score += hrs / 24;
-                    }
-                    else
-                    {
-                        score += m_watchListMaxAge;
-                    }
-                }
-            }
-
-            // Factor based on the average time shift delay.
-            // Scale the avgd range of 0 thru 200 hours to 133% thru 67%
-            int delaypct = avgd / 3 + 67;
-
-            if (avgd < 100)
-            {
-                score = score * (200 - delaypct) / 100;
-            }
-            else if (avgd > 100)
-            {
-                score = score * 100 / delaypct;
-            }
-
-            // use score as primary key in top 32 bits,
-            // use age in secs as a secondary key in low 32 bits to ensure equal
-            // scores are ordered oldest first. Copes with progs up to 136 yrs old
-            score_type longScore = (static_cast<score_type>(score) << 32)
-                    | p->GetScheduledStartTime().secsTo(now);
-
-            watchList.insert(longScore, p);
-
-            ++it;
-
-            LOG(VB_GUI, LOG_DEBUG, QString("Watchlist:%1 %2 %3 %4")
-                .arg(score, 5)
-                .arg(longScore, 14)
-                .arg(p->GetTitle())
-                .arg(MythDate::toString(p->GetScheduledStartTime(),
-                                        MythDate::kDateShort)));
-        }
+            OrderByClassicStrategy(watchEpisode, watchList);
 
         // populate watchlist group;
         // duplicate keys will appear in reverse alphabetic order
diff --git a/mythtv/programs/mythfrontend/playbackbox.h b/mythtv/programs/mythfrontend/playbackbox.h
index ff4b692..1266509 100644
--- a/mythtv/programs/mythfrontend/playbackbox.h
+++ b/mythtv/programs/mythfrontend/playbackbox.h
@@ -262,8 +262,12 @@ class PlaybackBox : public ScheduleCommon
     void coverartLoad(void);
 
   private:
+    typedef unsigned long long score_type; // 64 bit
+    typedef QMultiMap<score_type, ProgramInfo*> ProgramOrder; // progs keyed by score
     typedef QMap<QString, ProgramInfo*> TitleMap; // progs keyed by title
 
+    void OrderByClassicStrategy(TitleMap& selections, ProgramOrder &ordered);
+
     bool UpdateUILists(void);
     void UpdateUIGroupList(const QStringList &groupPreferences);
     void UpdateUIRecGroupList(void);
@@ -365,6 +369,8 @@ class PlaybackBox : public ScheduleCommon
     int                 m_watchListMaxAge;
     /// adjust exclusion of a title from the Watch List after a delete
     int                 m_watchListBlackOut;
+    /// strategy for ordering the watchlist
+    QString             m_watchListStrategy;
     /// allOrder controls the ordering of the "All Programs" list
     int                 m_allOrder;
     /// listOrder controls the ordering of the recordings in the list
-- 
1.9.1

