Switch to side-by-side view

--- a/src/mediaserver/cdplugins/plgwithslave.cxx
+++ b/src/mediaserver/cdplugins/plgwithslave.cxx
@@ -150,8 +150,6 @@
     }
 
     if (media_url.find("http") == 0) {
-        LOGDEB1("answer_to_connection: redirecting to " << media_url << endl);
-
         static char data[] = "<html><body></body></html>";
         struct MHD_Response *response =
             MHD_create_response_from_buffer(strlen(data), data,
@@ -287,7 +285,6 @@
 static int resultToEntries(const string& encoded, int stidx, int cnt,
 			   vector<UpSong>& entries)
 {
-    entries.clear();
     Json::Value decoded;
     istringstream input(encoded);
     input >> decoded;
@@ -303,11 +300,9 @@
 	UpSong song;
 	// tp is container ("ct") or item ("it")
         string stp = decoded[i].get("tp", "").asString();
-        LOGDEB("PlgWithSlave::results: tp is " << stp << endl)
 	if (!stp.compare("ct")) {
 	    song.iscontainer = true;
             string ss = decoded[i].get("searchable", "").asString();
-            LOGDEB("PlgWithSlave::results: searchable is " << ss << endl)
             if (!ss.empty()) {
                 song.searchable = stringToBool(ss);
             }
@@ -317,6 +312,11 @@
 	    JSONTOUPS(artist, dc:creator);
 	    JSONTOUPS(genre, upnp:genre);
 	    JSONTOUPS(tracknum, upnp:originalTrackNumber);
+            JSONTOUPS(mime, res:mime);
+            string srate = decoded[i].get("res:samplefreq", "").asString();
+            if (!srate.empty()) {
+                song.samplefreq = atoi(srate.c_str());
+            }
             string sdur = decoded[i].get("duration", "").asString();
             if (!sdur.empty()) {
                 song.duration_secs = atoi(sdur.c_str());
@@ -330,6 +330,8 @@
 	JSONTOUPS(title, tt);
         JSONTOUPS(artUri, upnp:albumArtURI);
         JSONTOUPS(artist, upnp:artist);
+        JSONTOUPS(upnpClass, upnp:class);
+        LOGDEB1("PlgWitSlave::result: pushing: " << song.dump() << endl);
 	entries.push_back(song);
     }
     // We return the total match size, the count of actually returned
@@ -340,7 +342,6 @@
 // Better return a bogus informative entry than an outright error:
 static int errorEntries(const string& pid, vector<UpSong>& entries)
 {
-    entries.clear();
     entries.push_back(
         UpSong::item(pid + "$bogus", pid,
                      "Service login or communication failure"));
@@ -352,7 +353,8 @@
                          const vector<string>& sortcrits,
                          BrowseFlag flg)
 {
-    LOGDEB("PlgWithSlave::browse\n");
+    LOGDEB1("PlgWithSlave::browse\n");
+    entries.clear();
     if (!m->maybeStartCmd()) {
 	return errorEntries(objid, entries);
     }
@@ -382,12 +384,105 @@
 }
 
 
+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;
+}
+
 int PlgWithSlave::search(const string& ctid, int stidx, int cnt,
                          const string& searchstr,
                          vector<UpSong>& entries,
                          const vector<string>& sortcrits)
 {
-    LOGDEB("PlgWithSlave::search\n");
+    LOGDEB1("PlgWithSlave::search\n");
+    entries.clear();
     if (!m->maybeStartCmd()) {
 	return errorEntries(ctid, entries);
     }
@@ -415,10 +510,11 @@
     }
     string slavefield;
     string value;
+    string classfilter;
     for (unsigned int i = 0; i < vs.size()-2; i += 4) {
         const string& upnpproperty = vs[i];
-        LOGDEB("Looking at " << vs[i] << " " << vs[i+1] << " " <<
-               vs[i+2] << endl);
+        LOGDEB("PlgWithSlave::search:clause: " << vs[i] << " " << vs[i+1] <<
+               " " << vs[i+2] << endl);
         if (!upnpproperty.compare("upnp:artist") ||
             !upnpproperty.compare("dc:author")) {
             slavefield = "artist";
@@ -432,17 +528,28 @@
             slavefield = "track";
             value = vs[i+2];
             break;
+        } else if (!upnpproperty.compare("upnp:class")) {
+            classfilter = vs[i+2];
         }
     }
     if (slavefield.empty()) {
         LOGERR("PlgWithSlave: unsupported search: [" << searchstr << "]\n");
         return errorEntries(ctid, entries);
     }
-	
+
+    // In cache ?
+    SearchCacheEntry *cep;
+    string cachekey(m_name + ":" + value);
+    if ((cep = o_scache.get(cachekey)) != nullptr) {
+        int total = resultFromCacheEntry(classfilter, stidx,cnt, *cep, entries);
+        delete cep;
+        return total;
+    }
+
+    // Run query
     unordered_map<string, string> res;
     if (!m->cmd.callproc("search", {
 		{"objid", ctid},
-		{"field", slavefield},
 		{"value", value} },  res)) {
 	LOGERR("PlgWithSlave::search: slave failure\n");
 	return errorEntries(ctid, entries);
@@ -453,5 +560,9 @@
 	LOGERR("PlgWithSlave::search: no entries returned\n");
 	return errorEntries(ctid, entries);
     }
-    return resultToEntries(it->second, stidx, cnt, entries);
-}
+    // Convert the whole set and store in cache
+    SearchCacheEntry e;
+    resultToEntries(it->second, 0, 0, e.m_results);
+    o_scache.set(cachekey, e);
+    return resultFromCacheEntry(classfilter, stidx, cnt, e, entries);
+}