Switch to side-by-side view

--- a/src/mediaserver/cdplugins/plgwithslave.cxx
+++ b/src/mediaserver/cdplugins/plgwithslave.cxx
@@ -341,6 +341,102 @@
     return decoded.size();
 }
 
+
+class ContentCacheEntry {
+public:
+    ContentCacheEntry()
+        : m_time(time(0)) {
+    }
+    int toResult(const string& classfilter, int stidx, int cnt,
+                 vector<UpSong>& entries) const;
+    time_t m_time;
+    vector<UpSong> m_results;
+};
+
+int ContentCacheEntry::toResult(const string& classfilter, int stidx, int cnt,
+                                vector<UpSong>& entries) const
+{
+    const vector<UpSong>& res = m_results;
+    LOGDEB0("searchCacheEntryToResult: filter " << classfilter << " start " <<
+            stidx << " cnt " << cnt << " res.size " << res.size() << endl);
+    entries.reserve(cnt);
+    int total = 0;
+    for (unsigned int i = 0; i < res.size(); i++) {
+        if (!classfilter.empty() && res[i].upnpClass.find(classfilter) != 0) {
+            continue;
+        }
+        total++;
+        if (stidx > int(i)) {
+            continue;
+        }
+        if (int(entries.size()) >= cnt) {
+            break;
+        }
+        LOGDEB1("ContentCacheEntry::toResult: pushing class " <<
+                res[i].upnpClass << " tt " << res[i].title << endl);
+        entries.push_back(res[i]);
+    }
+    return total;
+}
+
+class ContentCache {
+public:
+    ContentCache(int retention_secs = 300);
+    ContentCacheEntry *get(const string& query);
+    void set(const string& query, ContentCacheEntry &entry);
+    void purge();
+private:
+    time_t m_lastpurge;
+    int m_retention_secs;
+    unordered_map<string, ContentCacheEntry> m_cache;
+};
+
+ContentCache::ContentCache(int retention_secs)
+    : m_lastpurge(time(0)), m_retention_secs(retention_secs)
+{
+}
+
+void ContentCache::purge()
+{
+    time_t now(time(0));
+    if (now - m_lastpurge < 5) {
+        return;
+    }
+    for (auto it = m_cache.begin(); it != m_cache.end(); ) {
+        if (now - it->second.m_time > m_retention_secs) {
+            LOGDEB0("ContentCache::purge: erasing " << it->first << endl);
+            it = m_cache.erase(it);
+        } else {
+            it++;
+        }
+    }
+    m_lastpurge = now;
+}
+
+ContentCacheEntry *ContentCache::get(const string& key)
+{
+    purge();
+    auto it = m_cache.find(key);
+    if (it != m_cache.end()) {
+        LOGDEB0("ContentCache::get: found " << key << endl);
+        // we return a copy of the vector. Make our multi-access life simpler...
+        return new ContentCacheEntry(it->second);
+    }
+    LOGDEB0("ContentCache::get: not found " << key << endl);
+    return nullptr;
+}
+
+void ContentCache::set(const string& key, ContentCacheEntry &entry)
+{
+    LOGDEB0("ContentCache::set: " << key << endl);
+    m_cache[key] = entry;
+}
+
+// Cache for searches
+static ContentCache o_scache(300);
+// Cache for browsing
+static ContentCache o_bcache(180);
+
 // Better return a bogus informative entry than an outright error:
 static int errorEntries(const string& pid, vector<UpSong>& entries)
 {
@@ -350,6 +446,12 @@
     return 1;
 }
 
+// Note that the offset and count don't get to the plugin for
+// now. Plugins just return a (plugin-dependant) fixed number of
+// entries from offset 0, which we cache. There is no good reason for
+// this, beyond the fact that we have to cap the entry count anyway,
+// else the CP is going to read to the end which might be
+// reaaaaalllllyyyy long.
 int PlgWithSlave::browse(const string& objid, int stidx, int cnt,
                          vector<UpSong>& entries,
                          const vector<string>& sortcrits,
@@ -371,6 +473,17 @@
         break;
     }
 
+    string cachekey(m_name + ":" + objid);
+    if (flg == CDPlugin::BFChildren) {
+        // Check cache
+        ContentCacheEntry *cep;
+        if ((cep = o_bcache.get(cachekey)) != nullptr) {
+            int total = cep->toResult("", stidx, cnt, entries);
+            delete cep;
+            return total;
+        }
+    }
+    
     unordered_map<string, string> res;
     if (!m->cmd.callproc("browse", {{"objid", objid}, {"flag", sbflg}}, res)) {
 	LOGERR("PlgWithSlave::browse: slave failure\n");
@@ -382,102 +495,23 @@
 	LOGERR("PlgWithSlave::browse: no entries returned\n");
         return errorEntries(objid, entries);
     }
-    return resultToEntries(it->second, stidx, cnt, entries);
-}
-
-
-class SearchCacheEntry {
-public:
-    SearchCacheEntry()
-        : m_time(time(0)) {
-    }
-    time_t m_time;
-    vector<UpSong> m_results;
-};
-
-const int retention_secs = 300;
-class SearchCache {
-public:
-    SearchCache();
-    SearchCacheEntry *get(const string& query);
-    void set(const string& query, SearchCacheEntry &entry);
-    void flush();
-private:
-    time_t m_lastflush;
-    unordered_map<string, SearchCacheEntry> m_cache;
-};
-
-SearchCache::SearchCache()
-        : m_lastflush(time(0))
-{
-}
-
-void SearchCache::flush()
-{
-    time_t now(time(0));
-    if (now - m_lastflush < 5) {
-        return;
-    }
-    for (unordered_map<string, SearchCacheEntry>::iterator it = m_cache.begin();
-         it != m_cache.end(); ) {
-        if (now - it->second.m_time > retention_secs) {
-            LOGDEB0("SearchCache::flush: erasing " << it->first << endl);
-            it = m_cache.erase(it);
-        } else {
-            it++;
-        }
-    }
-    m_lastflush = now;
-}
-
-SearchCacheEntry *SearchCache::get(const string& key)
-{
-    flush();
-    auto it = m_cache.find(key);
-    if (it != m_cache.end()) {
-        LOGDEB0("SearchCache::get: found " << key << endl);
-        // we return a copy of the vector. Make our multi-access life simpler...
-        return new SearchCacheEntry(it->second);
-    }
-    LOGDEB0("SearchCache::get: not found " << key << endl);
-    return nullptr;
-}
-
-void SearchCache::set(const string& key, SearchCacheEntry &entry)
-{
-    LOGDEB0("SearchCache::set: " << key << endl);
-    m_cache[key] = entry;
-}
-
-static SearchCache o_scache;
-
-int resultFromCacheEntry(const string& classfilter, int stidx, int cnt,
-                         const SearchCacheEntry& e,
-                         vector<UpSong>& entries)
-{
-    const vector<UpSong>& res = e.m_results;
-    LOGDEB0("resultFromCacheEntry: filter " << classfilter << " start " <<
-            stidx << " cnt " << cnt << " res.size " << res.size() << endl);
-    entries.reserve(cnt);
-    int total = 0;
-    for (unsigned int i = 0; i < res.size(); i++) {
-        if (!classfilter.empty() && res[i].upnpClass.find(classfilter) != 0) {
-            continue;
-        }
-        total++;
-        if (stidx > int(i)) {
-            continue;
-        }
-        if (int(entries.size()) >= cnt) {
-            continue;
-        }
-        LOGDEB1("resultFromCacheEntry: pushing class "  << res[i].upnpClass <<
-               " tt " << res[i].title << endl);
-        entries.push_back(res[i]);
-    }
-    return total;
-}
-
+
+    if (flg == CDPlugin::BFChildren) {
+        ContentCacheEntry e;
+        resultToEntries(it->second, 0, 0, e.m_results);
+        o_bcache.set(cachekey, e);
+        return e.toResult("", stidx, cnt, entries);
+    } else {
+        return resultToEntries(it->second, stidx, cnt, entries);
+    }
+}
+
+// Note that the offset and count don't get to the plugin for
+// now. Plugins just return a (plugin-dependant) fixed number of
+// entries from offset 0, which we cache. There is no good reason for
+// this, beyond the fact that we have to cap the entry count anyway,
+// else the CP is going to read to the end which might be
+// reaaaaalllllyyyy long.
 int PlgWithSlave::search(const string& ctid, int stidx, int cnt,
                          const string& searchstr,
                          vector<UpSong>& entries,
@@ -554,10 +588,10 @@
     }
 
     // In cache ?
-    SearchCacheEntry *cep;
+    ContentCacheEntry *cep;
     string cachekey(m_name + ":" + objkind + ":" + slavefield + ":" + value);
     if ((cep = o_scache.get(cachekey)) != nullptr) {
-        int total = resultFromCacheEntry(classfilter, stidx, cnt, *cep, entries);
+        int total = cep->toResult(classfilter, stidx, cnt, entries);
         delete cep;
         return total;
     }
@@ -579,8 +613,8 @@
 	return errorEntries(ctid, entries);
     }
     // Convert the whole set and store in cache
-    SearchCacheEntry e;
+    ContentCacheEntry e;
     resultToEntries(it->second, 0, 0, e.m_results);
     o_scache.set(cachekey, e);
-    return resultFromCacheEntry(classfilter, stidx, cnt, e, entries);
-}
+    return e.toResult(classfilter, stidx, cnt, entries);
+}