Ticket #12296: 0005-Watchlist-Implement-multiple-ordering-strategies.patch

File 0005-Watchlist-Implement-multiple-ordering-strategies.patch, 19.7 KB (added by Roger Siddons <dizygotheca@…>, 6 years ago)
  • mythtv/programs/mythfrontend/globalsettings.cpp

    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 b static HostCheckBox *PlaybackWLAutoExpire() 
    32563256    return gc;
    32573257}
    32583258
     3259static HostComboBox *PlaybackWLStrategy()
     3260{
     3261    HostComboBox *gc = new HostComboBox("WatchlistOrder");
     3262
     3263    gc->setLabel(PlaybackSettings::tr("Sort method"));
     3264    gc->addSelection(PlaybackSettings::tr("Classic", "Watchlist"), "Classic");
     3265    gc->setHelpText(PlaybackSettings::tr("The watchlist ordering method. "
     3266                                         "Classic keeps up-to-date with current "
     3267                                         "TV."));
     3268    return gc;
     3269}
     3270
    32593271static HostSpinBox *PlaybackWLMaxAge()
    32603272{
    32613273    HostSpinBox *gs = new HostSpinBox("PlaybackWLMaxAge", 30, 180, 10);
    static HostSpinBox *PlaybackWLBlackOut() 
    32913303    return gs;
    32923304}
    32933305
     3306WatchListStrategy::WatchListStrategy() :
     3307    TriggeredConfigurationGroup(false, false, true, true)
     3308{
     3309     Setting* strategy = PlaybackWLStrategy();
     3310     addChild(strategy);
     3311     setTrigger(strategy);
     3312
     3313     ConfigurationGroup* classic = new VerticalConfigurationGroup(false,false);
     3314
     3315     classic->addChild(PlaybackWLMaxAge());
     3316     classic->addChild(PlaybackWLBlackOut());
     3317
     3318     addTarget("Classic", classic);
     3319};
     3320
    32943321WatchListSettings::WatchListSettings() :
    32953322    TriggeredConfigurationGroup(false, false, true, true)
    32963323{
    WatchListSettings::WatchListSettings() : 
    32993326     addChild(watchList);
    33003327     setTrigger(watchList);
    33013328
    3302      ConfigurationGroup* settings = new VerticalConfigurationGroup(false);
     3329     ConfigurationGroup* settings =
     3330             new VerticalConfigurationGroup(false,false,true,true);
    33033331
    33043332     settings->addChild(PlaybackWLStart());
    33053333     settings->addChild(PlaybackWLAutoExpire());
    3306      settings->addChild(PlaybackWLMaxAge());
    3307      settings->addChild(PlaybackWLBlackOut());
     3334     settings->addChild(new WatchListStrategy());
    33083335
    33093336     addTarget("1", settings);
    33103337
    3311      addTarget("0", new VerticalConfigurationGroup(true));
     3338     addTarget("0", new VerticalConfigurationGroup(false,false));
    33123339};
    33133340
    33143341static HostCheckBox *LCDShowTime()
  • mythtv/programs/mythfrontend/globalsettings.h

    diff --git a/mythtv/programs/mythfrontend/globalsettings.h b/mythtv/programs/mythfrontend/globalsettings.h
    index 38fdc48..053aec8 100644
    a b class LcdSettings : public TriggeredConfigurationGroup 
    3838    LcdSettings();
    3939};
    4040
     41class WatchListStrategy : public TriggeredConfigurationGroup
     42{
     43    Q_OBJECT
     44
     45  public:
     46    WatchListStrategy();
     47};
    4148
    4249class WatchListSettings : public TriggeredConfigurationGroup
    4350{
  • mythtv/programs/mythfrontend/playbackbox.cpp

    diff --git a/mythtv/programs/mythfrontend/playbackbox.cpp b/mythtv/programs/mythfrontend/playbackbox.cpp
    index c2924df..eff3d85 100644
    a b PlaybackBox::PlaybackBox(MythScreenStack *parent, QString name, 
    439439    m_watchListAutoExpire= gCoreContext->GetNumSetting("PlaybackWLAutoExpire", 0);
    440440    m_watchListMaxAge    = gCoreContext->GetNumSetting("PlaybackWLMaxAge", 60);
    441441    m_watchListBlackOut  = gCoreContext->GetNumSetting("PlaybackWLBlackOut", 2);
     442    m_watchListStrategy   = gCoreContext->GetSetting("WatchListOrder", "Classic");
    442443
    443444    bool displayCat  = gCoreContext->GetNumSetting("DisplayRecGroupIsCategory", 0);
    444445
    static void restore_position( 
    15861587    }
    15871588}
    15881589
     1590/// Sort programs by original Watchlist rules
     1591void PlaybackBox::OrderByClassicStrategy(TitleMap& selections, ProgramOrder& ordered)
     1592{
     1593    QDateTime now = MythDate::current();
     1594    int baseValue = m_watchListMaxAge * 2 / 3;
     1595
     1596    QMap<int, int> recType;
     1597    QMap<int, int> maxEpisodes;
     1598    QMap<int, int> avgDelay;
     1599    QMap<int, int> spanHours;
     1600    QMap<int, int> delHours;
     1601    QMap<int, int> nextHours;
     1602
     1603    MSqlQuery query(MSqlQuery::InitCon());
     1604    query.prepare("SELECT recordid, type, maxepisodes, avg_delay, "
     1605                  "next_record, last_record, last_delete FROM record;");
     1606
     1607    if (query.exec())
     1608    {
     1609        while (query.next())
     1610        {
     1611            int recid = query.value(0).toInt();
     1612            recType[recid] = query.value(1).toInt();
     1613            maxEpisodes[recid] = query.value(2).toInt();
     1614            avgDelay[recid] = query.value(3).toInt();
     1615
     1616            QDateTime next_record =
     1617                    MythDate::as_utc(query.value(4).toDateTime());
     1618            QDateTime last_record =
     1619                    MythDate::as_utc(query.value(5).toDateTime());
     1620            QDateTime last_delete =
     1621                    MythDate::as_utc(query.value(6).toDateTime());
     1622
     1623            // Time between the last and next recordings
     1624            spanHours[recid] = 1000;
     1625            if (last_record.isValid() && next_record.isValid())
     1626                spanHours[recid] =
     1627                        last_record.secsTo(next_record) / 3600 + 1;
     1628
     1629            // Time since the last episode was deleted
     1630            delHours[recid] = 1000;
     1631            if (last_delete.isValid())
     1632                delHours[recid] = last_delete.secsTo(now) / 3600 + 1;
     1633
     1634            // Time until the next recording if any
     1635            if (next_record.isValid())
     1636                nextHours[recid] = now.secsTo(next_record) / 3600 + 1;
     1637        }
     1638    }
     1639
     1640    TitleMap::iterator it = selections.begin();
     1641    while (it != selections.end())
     1642    {
     1643        uint score = 0;
     1644        ProgramInfo* p = it.value();
     1645        int recid = p->GetRecordingRuleID();
     1646        int avgd =  avgDelay[recid];
     1647
     1648        if (avgd == 0)
     1649            avgd = 100;
     1650
     1651        // Set the intervals beyond range if there is no record entry
     1652        if (spanHours[recid] == 0)
     1653        {
     1654            spanHours[recid] = 1000;
     1655            delHours[recid] = 1000;
     1656        }
     1657
     1658        // add point equal to baseValue for each additional episode
     1659        if (recid && maxEpisodes[recid] == 0)
     1660            score += (m_watchlistCount[extract_watchlist_title(*p)] - 1) *
     1661                    baseValue;
     1662
     1663        // add points every 3hr leading up to the next recording
     1664        if (nextHours[recid] > 0 && nextHours[recid] < baseValue * 3)
     1665            score += (baseValue * 3 - nextHours[recid]) / 3;
     1666
     1667        int hrs = p->GetScheduledEndTime().secsTo(now) / 3600;
     1668        if (hrs < 1)
     1669            hrs = 1;
     1670
     1671        // add points for a new recording that decrease each hour
     1672        if (hrs < 42)
     1673            score += 42 - hrs;
     1674
     1675        // add points for how close the recorded time of day is to 'now'
     1676        score += abs((hrs % 24) - 12) * 2;
     1677
     1678        // Daily
     1679        if (spanHours[recid] < 50 ||
     1680                recType[recid] == kDailyRecord)
     1681        {
     1682            if (delHours[recid] < m_watchListBlackOut * 4)
     1683            {
     1684                LOG(VB_GUI, LOG_DEBUG,
     1685                    QString("Watchlist: Recently deleted daily:  %1")
     1686                    .arg(p->GetTitle()));
     1687
     1688                it = selections.erase(it);
     1689                continue;
     1690            }
     1691            else
     1692            {
     1693                LOG(VB_GUI, LOG_DEBUG, QString("Watchlist: Daily interval:  %1")
     1694                    .arg(p->GetTitle()));
     1695
     1696                if (maxEpisodes[recid] > 0)
     1697                    score += (baseValue / 2) + (hrs / 24);
     1698                else
     1699                    score += (baseValue / 5) + hrs;
     1700            }
     1701        }
     1702        // Weekly
     1703        else if (nextHours[recid] ||
     1704                 recType[recid] == kWeeklyRecord)
     1705
     1706        {
     1707            if (delHours[recid] < (m_watchListBlackOut * 24) - 4)
     1708            {
     1709                LOG(VB_GUI, LOG_DEBUG,
     1710                    QString("Watchlist: Recently deleted weekly:  %1")
     1711                    .arg(p->GetTitle()));
     1712
     1713                it = selections.erase(it);
     1714                continue;
     1715            }
     1716            else
     1717            {
     1718                LOG(VB_GUI, LOG_DEBUG, QString("Watchlist: Weekly interval: %1")
     1719                    .arg(p->GetTitle()));
     1720
     1721                if (maxEpisodes[recid] > 0)
     1722                    score += (baseValue / 2) + (hrs / 24);
     1723                else
     1724                    score += (baseValue / 3) + (baseValue * hrs / 24 / 4);
     1725            }
     1726        }
     1727        // Not recurring
     1728        else
     1729        {
     1730            if (delHours[recid] < (m_watchListBlackOut * 48) - 4)
     1731            {
     1732                it = selections.erase(it);
     1733                continue;
     1734            }
     1735            else
     1736            {
     1737                // add points for a new Single or final episode
     1738                if (hrs < 36)
     1739                    score += baseValue * (36 - hrs) / 36;
     1740
     1741                if (avgd != 100)
     1742                {
     1743                    if (maxEpisodes[recid] > 0)
     1744                        score += (baseValue / 2) + (hrs / 24);
     1745                    else
     1746                        score += (baseValue / 3) + (baseValue * hrs / 24 / 4);
     1747                }
     1748                else if ((hrs / 24) < m_watchListMaxAge)
     1749                    score += hrs / 24;
     1750                else
     1751                    score += m_watchListMaxAge;
     1752            }
     1753        }
     1754
     1755        // Factor based on the average time shift delay.
     1756        // Scale the avgd range of 0 thru 200 hours to 133% thru 67%
     1757        int delaypct = avgd / 3 + 67;
     1758
     1759        if (avgd < 100)
     1760            score = score * (200 - delaypct) / 100;
     1761        else if (avgd > 100)
     1762            score = score * 100 / delaypct;
     1763
     1764        // use score as primary key in top 32 bits,
     1765        // use age in secs as a secondary key in low 32 bits to ensure equal
     1766        // scores are ordered oldest first. Copes with progs up to 136 yrs old
     1767        score_type longScore = (static_cast<score_type>(score) << 32)
     1768                | p->GetScheduledStartTime().secsTo(now);
     1769
     1770        ordered.insert(longScore, p);
     1771
     1772        ++it;
     1773
     1774        LOG(VB_GUI, LOG_DEBUG, QString("Watchlist:%1 %2 %3 %4")
     1775            .arg(score, 5)
     1776            .arg(longScore, 12)
     1777            .arg(p->GetTitle())
     1778            .arg(MythDate::toString(p->GetScheduledStartTime(),
     1779                                    MythDate::kDateShort)));
     1780    }
     1781}
     1782
    15891783bool PlaybackBox::UpdateUILists(void)
    15901784{
    15911785    m_isFilling = true;
    bool PlaybackBox::UpdateUILists(void) 
    19182112
    19192113    if (!watchEpisode.empty())
    19202114    {
    1921         QDateTime now = MythDate::current();
    1922         int baseValue = m_watchListMaxAge * 2 / 3;
     2115        ProgramOrder watchList;
    19232116
    1924         QMap<int, int> recType;
    1925         QMap<int, int> maxEpisodes;
    1926         QMap<int, int> avgDelay;
    1927         QMap<int, int> spanHours;
    1928         QMap<int, int> delHours;
    1929         QMap<int, int> nextHours;
    1930         typedef unsigned long long score_type; // 64 bit
    1931         QMultiMap<score_type, ProgramInfo*> watchList; // progs keyed by score
     2117        if (m_watchListStrategy == "Classic")
    19322118
    1933         MSqlQuery query(MSqlQuery::InitCon());
    1934         query.prepare("SELECT recordid, type, maxepisodes, avg_delay, "
    1935                       "next_record, last_record, last_delete FROM record;");
    1936 
    1937         if (query.exec())
    1938         {
    1939             while (query.next())
    1940             {
    1941                 int recid = query.value(0).toInt();
    1942                 recType[recid] = query.value(1).toInt();
    1943                 maxEpisodes[recid] = query.value(2).toInt();
    1944                 avgDelay[recid] = query.value(3).toInt();
    1945 
    1946                 QDateTime next_record =
    1947                     MythDate::as_utc(query.value(4).toDateTime());
    1948                 QDateTime last_record =
    1949                     MythDate::as_utc(query.value(5).toDateTime());
    1950                 QDateTime last_delete =
    1951                     MythDate::as_utc(query.value(6).toDateTime());
    1952 
    1953                 // Time between the last and next recordings
    1954                 spanHours[recid] = 1000;
    1955                 if (last_record.isValid() && next_record.isValid())
    1956                     spanHours[recid] =
    1957                         last_record.secsTo(next_record) / 3600 + 1;
    1958 
    1959                 // Time since the last episode was deleted
    1960                 delHours[recid] = 1000;
    1961                 if (last_delete.isValid())
    1962                     delHours[recid] = last_delete.secsTo(now) / 3600 + 1;
    1963 
    1964                 // Time until the next recording if any
    1965                 if (next_record.isValid())
    1966                     nextHours[recid] = now.secsTo(next_record) / 3600 + 1;
    1967             }
    1968         }
    1969 
    1970         TitleMap::iterator it = watchEpisode.begin();
    1971         while (it != watchEpisode.end())
    1972         {
    1973             uint score = 0;
    1974             ProgramInfo* p = it.value();
    1975             int recid = p->GetRecordingRuleID();
    1976             int avgd =  avgDelay[recid];
    1977 
    1978             if (avgd == 0)
    1979                 avgd = 100;
    1980 
    1981             // Set the intervals beyond range if there is no record entry
    1982             if (spanHours[recid] == 0)
    1983             {
    1984                 spanHours[recid] = 1000;
    1985                 delHours[recid] = 1000;
    1986             }
    1987 
    1988             // add point equal to baseValue for each additional episode
    1989             if (recid && maxEpisodes[recid] == 0)
    1990             {
    1991                 score += (m_watchlistCount[extract_watchlist_title(*p)] - 1) *
    1992                         baseValue;
    1993             }
    1994 
    1995             // add points every 3hr leading up to the next recording
    1996             if (nextHours[recid] > 0 && nextHours[recid] < baseValue * 3)
    1997             {
    1998                 score += (baseValue * 3 - nextHours[recid]) / 3;
    1999             }
    2000 
    2001             int hrs = p->GetScheduledEndTime().secsTo(now) / 3600;
    2002             if (hrs < 1)
    2003                 hrs = 1;
    2004 
    2005             // add points for a new recording that decrease each hour
    2006             if (hrs < 42)
    2007             {
    2008                 score += 42 - hrs;
    2009             }
    2010 
    2011             // add points for how close the recorded time of day is to 'now'
    2012             score += abs((hrs % 24) - 12) * 2;
    2013 
    2014             // Daily
    2015             if (spanHours[recid] < 50 ||
    2016                 recType[recid] == kDailyRecord)
    2017             {
    2018                 if (delHours[recid] < m_watchListBlackOut * 4)
    2019                 {
    2020                     LOG(VB_GUI, LOG_DEBUG,
    2021                         QString("Watchlist: Recently deleted daily:  %1")
    2022                             .arg(p->GetTitle()));
    2023                     it = watchEpisode.erase(it);
    2024                     continue;
    2025                 }
    2026                 else
    2027                 {
    2028                     LOG(VB_GUI, LOG_DEBUG, QString("Watchlist: Daily interval:  %1")
    2029                             .arg(p->GetTitle()));
    2030 
    2031                     if (maxEpisodes[recid] > 0)
    2032                     {
    2033                         score += (baseValue / 2) + (hrs / 24);
    2034                     }
    2035                     else
    2036                     {
    2037                         score += (baseValue / 5) + hrs;
    2038                     }
    2039                 }
    2040             }
    2041             // Weekly
    2042             else if (nextHours[recid] ||
    2043                      recType[recid] == kWeeklyRecord)
    2044 
    2045             {
    2046                 if (delHours[recid] < (m_watchListBlackOut * 24) - 4)
    2047                 {
    2048                     LOG(VB_GUI, LOG_DEBUG,
    2049                         QString("Watchlist: Recently deleted weekly:  %1")
    2050                             .arg(p->GetTitle()));
    2051                     it = watchEpisode.erase(it);
    2052                     continue;
    2053                 }
    2054                 else
    2055                 {
    2056                     LOG(VB_GUI, LOG_DEBUG, QString("Watchlist: Weekly interval: %1")
    2057                             .arg(p->GetTitle()));
    2058 
    2059                     if (maxEpisodes[recid] > 0)
    2060                     {
    2061                         score += (baseValue / 2) + (hrs / 24);
    2062                     }
    2063                     else
    2064                     {
    2065                         score += (baseValue / 3) + (baseValue * hrs / 24 / 4);
    2066                     }
    2067                 }
    2068             }
    2069             // Not recurring
    2070             else
    2071             {
    2072                 if (delHours[recid] < (m_watchListBlackOut * 48) - 4)
    2073                 {
    2074                     it = watchEpisode.erase(it);
    2075                     continue;
    2076                 }
    2077                 else
    2078                 {
    2079                     // add points for a new Single or final episode
    2080                     if (hrs < 36)
    2081                     {
    2082                         score += baseValue * (36 - hrs) / 36;
    2083                     }
    2084 
    2085                     if (avgd != 100)
    2086                     {
    2087                         if (maxEpisodes[recid] > 0)
    2088                         {
    2089                             score += (baseValue / 2) + (hrs / 24);
    2090                         }
    2091                         else
    2092                         {
    2093                             score += (baseValue / 3) + (baseValue * hrs / 24 / 4);
    2094                         }
    2095                     }
    2096                     else if ((hrs / 24) < m_watchListMaxAge)
    2097                     {
    2098                         score += hrs / 24;
    2099                     }
    2100                     else
    2101                     {
    2102                         score += m_watchListMaxAge;
    2103                     }
    2104                 }
    2105             }
    2106 
    2107             // Factor based on the average time shift delay.
    2108             // Scale the avgd range of 0 thru 200 hours to 133% thru 67%
    2109             int delaypct = avgd / 3 + 67;
    2110 
    2111             if (avgd < 100)
    2112             {
    2113                 score = score * (200 - delaypct) / 100;
    2114             }
    2115             else if (avgd > 100)
    2116             {
    2117                 score = score * 100 / delaypct;
    2118             }
    2119 
    2120             // use score as primary key in top 32 bits,
    2121             // use age in secs as a secondary key in low 32 bits to ensure equal
    2122             // scores are ordered oldest first. Copes with progs up to 136 yrs old
    2123             score_type longScore = (static_cast<score_type>(score) << 32)
    2124                     | p->GetScheduledStartTime().secsTo(now);
    2125 
    2126             watchList.insert(longScore, p);
    2127 
    2128             ++it;
    2129 
    2130             LOG(VB_GUI, LOG_DEBUG, QString("Watchlist:%1 %2 %3 %4")
    2131                 .arg(score, 5)
    2132                 .arg(longScore, 14)
    2133                 .arg(p->GetTitle())
    2134                 .arg(MythDate::toString(p->GetScheduledStartTime(),
    2135                                         MythDate::kDateShort)));
    2136         }
     2119            OrderByClassicStrategy(watchEpisode, watchList);
    21372120
    21382121        // populate watchlist group;
    21392122        // duplicate keys will appear in reverse alphabetic order
  • mythtv/programs/mythfrontend/playbackbox.h

    diff --git a/mythtv/programs/mythfrontend/playbackbox.h b/mythtv/programs/mythfrontend/playbackbox.h
    index ff4b692..1266509 100644
    a b class PlaybackBox : public ScheduleCommon 
    262262    void coverartLoad(void);
    263263
    264264  private:
     265    typedef unsigned long long score_type; // 64 bit
     266    typedef QMultiMap<score_type, ProgramInfo*> ProgramOrder; // progs keyed by score
    265267    typedef QMap<QString, ProgramInfo*> TitleMap; // progs keyed by title
    266268
     269    void OrderByClassicStrategy(TitleMap& selections, ProgramOrder &ordered);
     270
    267271    bool UpdateUILists(void);
    268272    void UpdateUIGroupList(const QStringList &groupPreferences);
    269273    void UpdateUIRecGroupList(void);
    class PlaybackBox : public ScheduleCommon 
    365369    int                 m_watchListMaxAge;
    366370    /// adjust exclusion of a title from the Watch List after a delete
    367371    int                 m_watchListBlackOut;
     372    /// strategy for ordering the watchlist
     373    QString             m_watchListStrategy;
    368374    /// allOrder controls the ordering of the "All Programs" list
    369375    int                 m_allOrder;
    370376    /// listOrder controls the ordering of the recordings in the list