|
a/src/mediaserver/cdplugins/plgwithslave.cxx |
|
b/src/mediaserver/cdplugins/plgwithslave.cxx |
|
... |
|
... |
339 |
// We return the total match size, the count of actually returned
|
339 |
// We return the total match size, the count of actually returned
|
340 |
// entries can be obtained from the vector
|
340 |
// entries can be obtained from the vector
|
341 |
return decoded.size();
|
341 |
return decoded.size();
|
342 |
}
|
342 |
}
|
343 |
|
343 |
|
|
|
344 |
|
|
|
345 |
class ContentCacheEntry {
|
|
|
346 |
public:
|
|
|
347 |
ContentCacheEntry()
|
|
|
348 |
: m_time(time(0)) {
|
|
|
349 |
}
|
|
|
350 |
int toResult(const string& classfilter, int stidx, int cnt,
|
|
|
351 |
vector<UpSong>& entries) const;
|
|
|
352 |
time_t m_time;
|
|
|
353 |
vector<UpSong> m_results;
|
|
|
354 |
};
|
|
|
355 |
|
|
|
356 |
int ContentCacheEntry::toResult(const string& classfilter, int stidx, int cnt,
|
|
|
357 |
vector<UpSong>& entries) const
|
|
|
358 |
{
|
|
|
359 |
const vector<UpSong>& res = m_results;
|
|
|
360 |
LOGDEB0("searchCacheEntryToResult: filter " << classfilter << " start " <<
|
|
|
361 |
stidx << " cnt " << cnt << " res.size " << res.size() << endl);
|
|
|
362 |
entries.reserve(cnt);
|
|
|
363 |
int total = 0;
|
|
|
364 |
for (unsigned int i = 0; i < res.size(); i++) {
|
|
|
365 |
if (!classfilter.empty() && res[i].upnpClass.find(classfilter) != 0) {
|
|
|
366 |
continue;
|
|
|
367 |
}
|
|
|
368 |
total++;
|
|
|
369 |
if (stidx > int(i)) {
|
|
|
370 |
continue;
|
|
|
371 |
}
|
|
|
372 |
if (int(entries.size()) >= cnt) {
|
|
|
373 |
break;
|
|
|
374 |
}
|
|
|
375 |
LOGDEB1("ContentCacheEntry::toResult: pushing class " <<
|
|
|
376 |
res[i].upnpClass << " tt " << res[i].title << endl);
|
|
|
377 |
entries.push_back(res[i]);
|
|
|
378 |
}
|
|
|
379 |
return total;
|
|
|
380 |
}
|
|
|
381 |
|
|
|
382 |
class ContentCache {
|
|
|
383 |
public:
|
|
|
384 |
ContentCache(int retention_secs = 300);
|
|
|
385 |
ContentCacheEntry *get(const string& query);
|
|
|
386 |
void set(const string& query, ContentCacheEntry &entry);
|
|
|
387 |
void purge();
|
|
|
388 |
private:
|
|
|
389 |
time_t m_lastpurge;
|
|
|
390 |
int m_retention_secs;
|
|
|
391 |
unordered_map<string, ContentCacheEntry> m_cache;
|
|
|
392 |
};
|
|
|
393 |
|
|
|
394 |
ContentCache::ContentCache(int retention_secs)
|
|
|
395 |
: m_lastpurge(time(0)), m_retention_secs(retention_secs)
|
|
|
396 |
{
|
|
|
397 |
}
|
|
|
398 |
|
|
|
399 |
void ContentCache::purge()
|
|
|
400 |
{
|
|
|
401 |
time_t now(time(0));
|
|
|
402 |
if (now - m_lastpurge < 5) {
|
|
|
403 |
return;
|
|
|
404 |
}
|
|
|
405 |
for (auto it = m_cache.begin(); it != m_cache.end(); ) {
|
|
|
406 |
if (now - it->second.m_time > m_retention_secs) {
|
|
|
407 |
LOGDEB0("ContentCache::purge: erasing " << it->first << endl);
|
|
|
408 |
it = m_cache.erase(it);
|
|
|
409 |
} else {
|
|
|
410 |
it++;
|
|
|
411 |
}
|
|
|
412 |
}
|
|
|
413 |
m_lastpurge = now;
|
|
|
414 |
}
|
|
|
415 |
|
|
|
416 |
ContentCacheEntry *ContentCache::get(const string& key)
|
|
|
417 |
{
|
|
|
418 |
purge();
|
|
|
419 |
auto it = m_cache.find(key);
|
|
|
420 |
if (it != m_cache.end()) {
|
|
|
421 |
LOGDEB0("ContentCache::get: found " << key << endl);
|
|
|
422 |
// we return a copy of the vector. Make our multi-access life simpler...
|
|
|
423 |
return new ContentCacheEntry(it->second);
|
|
|
424 |
}
|
|
|
425 |
LOGDEB0("ContentCache::get: not found " << key << endl);
|
|
|
426 |
return nullptr;
|
|
|
427 |
}
|
|
|
428 |
|
|
|
429 |
void ContentCache::set(const string& key, ContentCacheEntry &entry)
|
|
|
430 |
{
|
|
|
431 |
LOGDEB0("ContentCache::set: " << key << endl);
|
|
|
432 |
m_cache[key] = entry;
|
|
|
433 |
}
|
|
|
434 |
|
|
|
435 |
// Cache for searches
|
|
|
436 |
static ContentCache o_scache(300);
|
|
|
437 |
// Cache for browsing
|
|
|
438 |
static ContentCache o_bcache(180);
|
|
|
439 |
|
344 |
// Better return a bogus informative entry than an outright error:
|
440 |
// Better return a bogus informative entry than an outright error:
|
345 |
static int errorEntries(const string& pid, vector<UpSong>& entries)
|
441 |
static int errorEntries(const string& pid, vector<UpSong>& entries)
|
346 |
{
|
442 |
{
|
347 |
entries.push_back(
|
443 |
entries.push_back(
|
348 |
UpSong::item(pid + "$bogus", pid,
|
444 |
UpSong::item(pid + "$bogus", pid,
|
349 |
"Service login or communication failure"));
|
445 |
"Service login or communication failure"));
|
350 |
return 1;
|
446 |
return 1;
|
351 |
}
|
447 |
}
|
352 |
|
448 |
|
|
|
449 |
// Note that the offset and count don't get to the plugin for
|
|
|
450 |
// now. Plugins just return a (plugin-dependant) fixed number of
|
|
|
451 |
// entries from offset 0, which we cache. There is no good reason for
|
|
|
452 |
// this, beyond the fact that we have to cap the entry count anyway,
|
|
|
453 |
// else the CP is going to read to the end which might be
|
|
|
454 |
// reaaaaalllllyyyy long.
|
353 |
int PlgWithSlave::browse(const string& objid, int stidx, int cnt,
|
455 |
int PlgWithSlave::browse(const string& objid, int stidx, int cnt,
|
354 |
vector<UpSong>& entries,
|
456 |
vector<UpSong>& entries,
|
355 |
const vector<string>& sortcrits,
|
457 |
const vector<string>& sortcrits,
|
356 |
BrowseFlag flg)
|
458 |
BrowseFlag flg)
|
357 |
{
|
459 |
{
|
|
... |
|
... |
369 |
default:
|
471 |
default:
|
370 |
sbflg = "children";
|
472 |
sbflg = "children";
|
371 |
break;
|
473 |
break;
|
372 |
}
|
474 |
}
|
373 |
|
475 |
|
|
|
476 |
string cachekey(m_name + ":" + objid);
|
|
|
477 |
if (flg == CDPlugin::BFChildren) {
|
|
|
478 |
// Check cache
|
|
|
479 |
ContentCacheEntry *cep;
|
|
|
480 |
if ((cep = o_bcache.get(cachekey)) != nullptr) {
|
|
|
481 |
int total = cep->toResult("", stidx, cnt, entries);
|
|
|
482 |
delete cep;
|
|
|
483 |
return total;
|
|
|
484 |
}
|
|
|
485 |
}
|
|
|
486 |
|
374 |
unordered_map<string, string> res;
|
487 |
unordered_map<string, string> res;
|
375 |
if (!m->cmd.callproc("browse", {{"objid", objid}, {"flag", sbflg}}, res)) {
|
488 |
if (!m->cmd.callproc("browse", {{"objid", objid}, {"flag", sbflg}}, res)) {
|
376 |
LOGERR("PlgWithSlave::browse: slave failure\n");
|
489 |
LOGERR("PlgWithSlave::browse: slave failure\n");
|
377 |
return errorEntries(objid, entries);
|
490 |
return errorEntries(objid, entries);
|
378 |
}
|
491 |
}
|
|
... |
|
... |
380 |
auto it = res.find("entries");
|
493 |
auto it = res.find("entries");
|
381 |
if (it == res.end()) {
|
494 |
if (it == res.end()) {
|
382 |
LOGERR("PlgWithSlave::browse: no entries returned\n");
|
495 |
LOGERR("PlgWithSlave::browse: no entries returned\n");
|
383 |
return errorEntries(objid, entries);
|
496 |
return errorEntries(objid, entries);
|
384 |
}
|
497 |
}
|
|
|
498 |
|
|
|
499 |
if (flg == CDPlugin::BFChildren) {
|
|
|
500 |
ContentCacheEntry e;
|
|
|
501 |
resultToEntries(it->second, 0, 0, e.m_results);
|
|
|
502 |
o_bcache.set(cachekey, e);
|
|
|
503 |
return e.toResult("", stidx, cnt, entries);
|
|
|
504 |
} else {
|
385 |
return resultToEntries(it->second, stidx, cnt, entries);
|
505 |
return resultToEntries(it->second, stidx, cnt, entries);
|
386 |
}
|
|
|
387 |
|
|
|
388 |
|
|
|
389 |
class SearchCacheEntry {
|
|
|
390 |
public:
|
|
|
391 |
SearchCacheEntry()
|
|
|
392 |
: m_time(time(0)) {
|
|
|
393 |
}
|
506 |
}
|
394 |
time_t m_time;
|
|
|
395 |
vector<UpSong> m_results;
|
|
|
396 |
};
|
507 |
}
|
397 |
|
508 |
|
398 |
const int retention_secs = 300;
|
509 |
// Note that the offset and count don't get to the plugin for
|
399 |
class SearchCache {
|
510 |
// now. Plugins just return a (plugin-dependant) fixed number of
|
400 |
public:
|
511 |
// entries from offset 0, which we cache. There is no good reason for
|
401 |
SearchCache();
|
512 |
// this, beyond the fact that we have to cap the entry count anyway,
|
402 |
SearchCacheEntry *get(const string& query);
|
513 |
// else the CP is going to read to the end which might be
|
403 |
void set(const string& query, SearchCacheEntry &entry);
|
514 |
// reaaaaalllllyyyy long.
|
404 |
void flush();
|
|
|
405 |
private:
|
|
|
406 |
time_t m_lastflush;
|
|
|
407 |
unordered_map<string, SearchCacheEntry> m_cache;
|
|
|
408 |
};
|
|
|
409 |
|
|
|
410 |
SearchCache::SearchCache()
|
|
|
411 |
: m_lastflush(time(0))
|
|
|
412 |
{
|
|
|
413 |
}
|
|
|
414 |
|
|
|
415 |
void SearchCache::flush()
|
|
|
416 |
{
|
|
|
417 |
time_t now(time(0));
|
|
|
418 |
if (now - m_lastflush < 5) {
|
|
|
419 |
return;
|
|
|
420 |
}
|
|
|
421 |
for (unordered_map<string, SearchCacheEntry>::iterator it = m_cache.begin();
|
|
|
422 |
it != m_cache.end(); ) {
|
|
|
423 |
if (now - it->second.m_time > retention_secs) {
|
|
|
424 |
LOGDEB0("SearchCache::flush: erasing " << it->first << endl);
|
|
|
425 |
it = m_cache.erase(it);
|
|
|
426 |
} else {
|
|
|
427 |
it++;
|
|
|
428 |
}
|
|
|
429 |
}
|
|
|
430 |
m_lastflush = now;
|
|
|
431 |
}
|
|
|
432 |
|
|
|
433 |
SearchCacheEntry *SearchCache::get(const string& key)
|
|
|
434 |
{
|
|
|
435 |
flush();
|
|
|
436 |
auto it = m_cache.find(key);
|
|
|
437 |
if (it != m_cache.end()) {
|
|
|
438 |
LOGDEB0("SearchCache::get: found " << key << endl);
|
|
|
439 |
// we return a copy of the vector. Make our multi-access life simpler...
|
|
|
440 |
return new SearchCacheEntry(it->second);
|
|
|
441 |
}
|
|
|
442 |
LOGDEB0("SearchCache::get: not found " << key << endl);
|
|
|
443 |
return nullptr;
|
|
|
444 |
}
|
|
|
445 |
|
|
|
446 |
void SearchCache::set(const string& key, SearchCacheEntry &entry)
|
|
|
447 |
{
|
|
|
448 |
LOGDEB0("SearchCache::set: " << key << endl);
|
|
|
449 |
m_cache[key] = entry;
|
|
|
450 |
}
|
|
|
451 |
|
|
|
452 |
static SearchCache o_scache;
|
|
|
453 |
|
|
|
454 |
int resultFromCacheEntry(const string& classfilter, int stidx, int cnt,
|
|
|
455 |
const SearchCacheEntry& e,
|
|
|
456 |
vector<UpSong>& entries)
|
|
|
457 |
{
|
|
|
458 |
const vector<UpSong>& res = e.m_results;
|
|
|
459 |
LOGDEB0("resultFromCacheEntry: filter " << classfilter << " start " <<
|
|
|
460 |
stidx << " cnt " << cnt << " res.size " << res.size() << endl);
|
|
|
461 |
entries.reserve(cnt);
|
|
|
462 |
int total = 0;
|
|
|
463 |
for (unsigned int i = 0; i < res.size(); i++) {
|
|
|
464 |
if (!classfilter.empty() && res[i].upnpClass.find(classfilter) != 0) {
|
|
|
465 |
continue;
|
|
|
466 |
}
|
|
|
467 |
total++;
|
|
|
468 |
if (stidx > int(i)) {
|
|
|
469 |
continue;
|
|
|
470 |
}
|
|
|
471 |
if (int(entries.size()) >= cnt) {
|
|
|
472 |
continue;
|
|
|
473 |
}
|
|
|
474 |
LOGDEB1("resultFromCacheEntry: pushing class " << res[i].upnpClass <<
|
|
|
475 |
" tt " << res[i].title << endl);
|
|
|
476 |
entries.push_back(res[i]);
|
|
|
477 |
}
|
|
|
478 |
return total;
|
|
|
479 |
}
|
|
|
480 |
|
|
|
481 |
int PlgWithSlave::search(const string& ctid, int stidx, int cnt,
|
515 |
int PlgWithSlave::search(const string& ctid, int stidx, int cnt,
|
482 |
const string& searchstr,
|
516 |
const string& searchstr,
|
483 |
vector<UpSong>& entries,
|
517 |
vector<UpSong>& entries,
|
484 |
const vector<string>& sortcrits)
|
518 |
const vector<string>& sortcrits)
|
485 |
{
|
519 |
{
|
|
... |
|
... |
552 |
LOGERR("PlgWithSlave: unsupported search: [" << searchstr << "]\n");
|
586 |
LOGERR("PlgWithSlave: unsupported search: [" << searchstr << "]\n");
|
553 |
return errorEntries(ctid, entries);
|
587 |
return errorEntries(ctid, entries);
|
554 |
}
|
588 |
}
|
555 |
|
589 |
|
556 |
// In cache ?
|
590 |
// In cache ?
|
557 |
SearchCacheEntry *cep;
|
591 |
ContentCacheEntry *cep;
|
558 |
string cachekey(m_name + ":" + objkind + ":" + slavefield + ":" + value);
|
592 |
string cachekey(m_name + ":" + objkind + ":" + slavefield + ":" + value);
|
559 |
if ((cep = o_scache.get(cachekey)) != nullptr) {
|
593 |
if ((cep = o_scache.get(cachekey)) != nullptr) {
|
560 |
int total = resultFromCacheEntry(classfilter, stidx, cnt, *cep, entries);
|
594 |
int total = cep->toResult(classfilter, stidx, cnt, entries);
|
561 |
delete cep;
|
595 |
delete cep;
|
562 |
return total;
|
596 |
return total;
|
563 |
}
|
597 |
}
|
564 |
|
598 |
|
565 |
// Run query
|
599 |
// Run query
|
|
... |
|
... |
577 |
if (it == res.end()) {
|
611 |
if (it == res.end()) {
|
578 |
LOGERR("PlgWithSlave::search: no entries returned\n");
|
612 |
LOGERR("PlgWithSlave::search: no entries returned\n");
|
579 |
return errorEntries(ctid, entries);
|
613 |
return errorEntries(ctid, entries);
|
580 |
}
|
614 |
}
|
581 |
// Convert the whole set and store in cache
|
615 |
// Convert the whole set and store in cache
|
582 |
SearchCacheEntry e;
|
616 |
ContentCacheEntry e;
|
583 |
resultToEntries(it->second, 0, 0, e.m_results);
|
617 |
resultToEntries(it->second, 0, 0, e.m_results);
|
584 |
o_scache.set(cachekey, e);
|
618 |
o_scache.set(cachekey, e);
|
585 |
return resultFromCacheEntry(classfilter, stidx, cnt, e, entries);
|
619 |
return e.toResult(classfilter, stidx, cnt, entries);
|
586 |
}
|
620 |
}
|