Switch to unified view

a/src/mediaserver/cdplugins/plgwithslave.cxx b/src/mediaserver/cdplugins/plgwithslave.cxx
...
...
64
64
65
class PlgWithSlave::Internal {
65
class PlgWithSlave::Internal {
66
public:
66
public:
67
    Internal(PlgWithSlave *_plg, const string& exe, const string& hst,
67
    Internal(PlgWithSlave *_plg, const string& exe, const string& hst,
68
             int prt, const string& pp)
68
             int prt, const string& pp)
69
  : plg(_plg), exepath(exe), upnphost(hst), upnpport(prt), pathprefix(pp), 
69
        : plg(_plg), exepath(exe), upnphost(hst), upnpport(prt), pathprefix(pp), 
70
          laststream(this) {
70
          laststream(this) {
71
    }
71
    }
72
72
73
    bool maybeStartCmd();
73
    bool maybeStartCmd();
74
74
...
...
116
    // The 'plgi' here is just whatever plugin started up the httpd task
116
    // The 'plgi' here is just whatever plugin started up the httpd task
117
    // We just use it to find the appropriate plugin for this path,
117
    // We just use it to find the appropriate plugin for this path,
118
    // and then dispatch the request.
118
    // and then dispatch the request.
119
    PlgWithSlave::Internal *plgi = (PlgWithSlave::Internal*)cls;
119
    PlgWithSlave::Internal *plgi = (PlgWithSlave::Internal*)cls;
120
    PlgWithSlave *realplg =
120
    PlgWithSlave *realplg =
121
      dynamic_cast<PlgWithSlave*>(plgi->plg->m_services->getpluginforpath(url));
121
        dynamic_cast<PlgWithSlave*>(plgi->plg->m_services->getpluginforpath(url));
122
    if (nullptr == realplg) {
122
    if (nullptr == realplg) {
123
        LOGERR("answer_to_connection: no plugin for path [" << url << endl);
123
        LOGERR("answer_to_connection: no plugin for path [" << url << endl);
124
        return MHD_NO;
124
        return MHD_NO;
125
    }
125
    }
126
126
...
...
243
// to tidal.
243
// to tidal.
244
string PlgWithSlave::get_media_url(const string& path)
244
string PlgWithSlave::get_media_url(const string& path)
245
{
245
{
246
    LOGDEB0("PlgWithSlave::get_media_url: " << path << endl);
246
    LOGDEB0("PlgWithSlave::get_media_url: " << path << endl);
247
    if (!m->maybeStartCmd()) {
247
    if (!m->maybeStartCmd()) {
248
  return string();
248
        return string();
249
    }
249
    }
250
    time_t now = time(0);
250
    time_t now = time(0);
251
    if (m->laststream.path.compare(path) ||
251
    if (m->laststream.path.compare(path) ||
252
        (now - m->laststream.opentime > 10)) {
252
        (now - m->laststream.opentime > 10)) {
253
  unordered_map<string, string> res;
253
        unordered_map<string, string> res;
254
  if (!m->cmd.callproc("trackuri", {{"path", path}}, res)) {
254
        if (!m->cmd.callproc("trackuri", {{"path", path}}, res)) {
255
      LOGERR("PlgWithSlave::get_media_url: slave failure\n");
255
            LOGERR("PlgWithSlave::get_media_url: slave failure\n");
256
      return string();
256
            return string();
257
  }
257
        }
258
258
259
  auto it = res.find("media_url");
259
        auto it = res.find("media_url");
260
  if (it == res.end()) {
260
        if (it == res.end()) {
261
      LOGERR("PlgWithSlave::get_media_url: no media url in result\n");
261
            LOGERR("PlgWithSlave::get_media_url: no media url in result\n");
262
      return string();
262
            return string();
263
  }
263
        }
264
        m->laststream.clear();
264
        m->laststream.clear();
265
        m->laststream.path = path;
265
        m->laststream.path = path;
266
        m->laststream.media_url = it->second;
266
        m->laststream.media_url = it->second;
267
        m->laststream.opentime = now;
267
        m->laststream.opentime = now;
268
    }
268
    }
...
...
285
{
285
{
286
    delete m;
286
    delete m;
287
}
287
}
288
288
289
static int resultToEntries(const string& encoded, int stidx, int cnt,
289
static int resultToEntries(const string& encoded, int stidx, int cnt,
290
             vector<UpSong>& entries)
290
                           vector<UpSong>& entries)
291
{
291
{
292
    Json::Value decoded;
292
    Json::Value decoded;
293
    istringstream input(encoded);
293
    istringstream input(encoded);
294
    input >> decoded;
294
    input >> decoded;
295
    LOGDEB0("PlgWithSlave::results: got " << decoded.size() << " entries \n");
295
    LOGDEB0("PlgWithSlave::results: got " << decoded.size() << " entries \n");
296
    LOGDEB1("PlgWithSlave::results: undecoded: " << decoded.dump() << endl);
296
    LOGDEB1("PlgWithSlave::results: undecoded: " << decoded.dump() << endl);
297
    bool dolimit = cnt > 0;
297
    bool dolimit = cnt > 0;
298
    
298
    
299
    for (unsigned int i = stidx; i < decoded.size(); i++) {
299
    for (unsigned int i = stidx; i < decoded.size(); i++) {
300
#define JSONTOUPS(fld, nm) {song.fld = decoded[i].get(#nm, "").asString();}
300
#define JSONTOUPS(fld, nm) {song.fld = decoded[i].get(#nm, "").asString();}
301
  if (dolimit && --cnt < 0) {
301
        if (dolimit && --cnt < 0) {
302
      break;
302
            break;
303
  }
303
        }
304
  UpSong song;
304
        UpSong song;
305
        JSONTOUPS(id, id);
306
        JSONTOUPS(parentid, pid);
307
        JSONTOUPS(title, tt);
308
        JSONTOUPS(artUri, upnp:albumArtURI);
309
        JSONTOUPS(artist, upnp:artist);
310
        JSONTOUPS(upnpClass, upnp:class);
305
  // tp is container ("ct") or item ("it")
311
        // tp is container ("ct") or item ("it")
306
        string stp = decoded[i].get("tp", "").asString();
312
        string stp = decoded[i].get("tp", "").asString();
307
  if (!stp.compare("ct")) {
313
        if (!stp.compare("ct")) {
308
      song.iscontainer = true;
314
            song.iscontainer = true;
309
            string ss = decoded[i].get("searchable", "").asString();
315
            string ss = decoded[i].get("searchable", "").asString();
310
            if (!ss.empty()) {
316
            if (!ss.empty()) {
311
                song.searchable = stringToBool(ss);
317
                song.searchable = stringToBool(ss);
312
            }
318
            }
313
  } else   if (!stp.compare("it")) {
319
        } else  if (!stp.compare("it")) {
314
      song.iscontainer = false;
320
            song.iscontainer = false;
315
      JSONTOUPS(uri, uri);
321
            JSONTOUPS(uri, uri);
316
      JSONTOUPS(artist, dc:creator);
322
            JSONTOUPS(artist, dc:creator);
317
      JSONTOUPS(genre, upnp:genre);
323
            JSONTOUPS(genre, upnp:genre);
318
            JSONTOUPS(album, upnp:album);
324
            JSONTOUPS(album, upnp:album);
319
      JSONTOUPS(tracknum, upnp:originalTrackNumber);
325
            JSONTOUPS(tracknum, upnp:originalTrackNumber);
320
            JSONTOUPS(mime, res:mime);
326
            JSONTOUPS(mime, res:mime);
321
            string srate = decoded[i].get("res:samplefreq", "").asString();
327
            string srate = decoded[i].get("res:samplefreq", "").asString();
322
            if (!srate.empty()) {
328
            if (!srate.empty()) {
323
                song.samplefreq = atoi(srate.c_str());
329
                song.samplefreq = atoi(srate.c_str());
324
            }
330
            }
325
            string sdur = decoded[i].get("duration", "").asString();
331
            string sdur = decoded[i].get("duration", "").asString();
326
            if (!sdur.empty()) {
332
            if (!sdur.empty()) {
327
                song.duration_secs = atoi(sdur.c_str());
333
                song.duration_secs = atoi(sdur.c_str());
328
            }
334
            }
329
  } else {
335
        } else {
330
      LOGERR("PlgWithSlave::result: bad type in entry: " << stp << endl);
336
            LOGERR("PlgWithSlave::result: bad type in entry: " << stp <<
337
                   "(title: " << song.title << ")\n");
331
      continue;
338
            continue;
332
  }
339
        }
333
  JSONTOUPS(id, id);
334
  JSONTOUPS(parentid, pid);
335
  JSONTOUPS(title, tt);
336
        JSONTOUPS(artUri, upnp:albumArtURI);
337
        JSONTOUPS(artist, upnp:artist);
338
        JSONTOUPS(upnpClass, upnp:class);
339
        LOGDEB1("PlgWitSlave::result: pushing: " << song.dump() << endl);
340
        LOGDEB1("PlgWitSlave::result: pushing: " << song.dump() << endl);
340
  entries.push_back(song);
341
        entries.push_back(song);
341
    }
342
    }
342
    // We return the total match size, the count of actually returned
343
    // We return the total match size, the count of actually returned
343
    // entries can be obtained from the vector
344
    // entries can be obtained from the vector
344
    return decoded.size();
345
    return decoded.size();
345
}
346
}
...
...
461
                         BrowseFlag flg)
462
                         BrowseFlag flg)
462
{
463
{
463
    LOGDEB1("PlgWithSlave::browse\n");
464
    LOGDEB1("PlgWithSlave::browse\n");
464
    entries.clear();
465
    entries.clear();
465
    if (!m->maybeStartCmd()) {
466
    if (!m->maybeStartCmd()) {
466
  return errorEntries(objid, entries);
467
        return errorEntries(objid, entries);
467
    }
468
    }
468
    string sbflg;
469
    string sbflg;
469
    switch (flg) {
470
    switch (flg) {
470
    case CDPlugin::BFMeta:
471
    case CDPlugin::BFMeta:
471
        sbflg = "meta";
472
        sbflg = "meta";
...
...
487
        }
488
        }
488
    }
489
    }
489
    
490
    
490
    unordered_map<string, string> res;
491
    unordered_map<string, string> res;
491
    if (!m->cmd.callproc("browse", {{"objid", objid}, {"flag", sbflg}}, res)) {
492
    if (!m->cmd.callproc("browse", {{"objid", objid}, {"flag", sbflg}}, res)) {
492
  LOGERR("PlgWithSlave::browse: slave failure\n");
493
        LOGERR("PlgWithSlave::browse: slave failure\n");
493
  return errorEntries(objid, entries);
494
        return errorEntries(objid, entries);
494
    }
495
    }
495
496
496
    auto it = res.find("entries");
497
    auto it = res.find("entries");
497
    if (it == res.end()) {
498
    if (it == res.end()) {
498
  LOGERR("PlgWithSlave::browse: no entries returned\n");
499
        LOGERR("PlgWithSlave::browse: no entries returned\n");
499
        return errorEntries(objid, entries);
500
        return errorEntries(objid, entries);
500
    }
501
    }
501
502
502
    if (flg == CDPlugin::BFChildren) {
503
    if (flg == CDPlugin::BFChildren) {
503
        ContentCacheEntry e;
504
        ContentCacheEntry e;
...
...
521
                         const vector<string>& sortcrits)
522
                         const vector<string>& sortcrits)
522
{
523
{
523
    LOGDEB("PlgWithSlave::search: [" << searchstr << "]\n");
524
    LOGDEB("PlgWithSlave::search: [" << searchstr << "]\n");
524
    entries.clear();
525
    entries.clear();
525
    if (!m->maybeStartCmd()) {
526
    if (!m->maybeStartCmd()) {
526
  return errorEntries(ctid, entries);
527
        return errorEntries(ctid, entries);
527
    }
528
    }
528
529
530
    // Computing a pre-cooked query. For simple-minded plugins.
531
    // Note that none of the qobuz/gmusic/tidal plugins actually use
532
    // the slavefield part (defining in what field the term should
533
    // match).
534
    // 
529
    // Ok, so the upnp query language is quite powerful, but us, not
535
    // Ok, so the upnp query language is quite powerful, but us, not
530
    // so much. We get rid of parenthesis and then try to find the
536
    // so much. We get rid of parenthesis and then try to find the
531
    // first searchExp on a field we can handle, pretend the operator
537
    // first searchExp on a field we can handle, pretend the operator
532
    // is "contains" and just do it. I so don't want to implement a
538
    // is "contains" and just do it. I so don't want to implement a
533
    // parser for the query language when the services don't support
539
    // parser for the query language when the services don't support
...
...
541
    stringToStrings(ss, vs);
547
    stringToStrings(ss, vs);
542
548
543
    // The sequence can now be either [field, op, value], or
549
    // The sequence can now be either [field, op, value], or
544
    // [field, op, value, and/or, field, op, value,...]
550
    // [field, op, value, and/or, field, op, value,...]
545
    if ((vs.size() + 1) % 4 != 0) {
551
    if ((vs.size() + 1) % 4 != 0) {
546
  LOGERR("PlgWithSlave::search: bad search string: [" << searchstr <<
552
        LOGERR("PlgWithSlave::search: bad search string: [" << searchstr <<
547
               "]\n");
553
               "]\n");
548
  return errorEntries(ctid, entries);
554
        return errorEntries(ctid, entries);
549
    }
555
    }
550
    string slavefield;
556
    string slavefield;
551
    string value;
557
    string value;
552
    string classfilter;
558
    string classfilter;
553
    string objkind;
559
    string objkind;
...
...
569
                       || beginswith(what, "object.container.playlist")) {
575
                       || beginswith(what, "object.container.playlist")) {
570
                objkind = "playlist";
576
                objkind = "playlist";
571
            }
577
            }
572
            classfilter = what;
578
            classfilter = what;
573
        } else if (!upnpproperty.compare("upnp:artist") ||
579
        } else if (!upnpproperty.compare("upnp:artist") ||
574
            !upnpproperty.compare("dc:author")) {
580
                   !upnpproperty.compare("dc:author")) {
575
            slavefield = "artist";
581
            slavefield = "artist";
576
            value = vs[i+2];
582
            value = vs[i+2];
577
            break;
583
            break;
578
        } else if (!upnpproperty.compare("upnp:album")) {
584
        } else if (!upnpproperty.compare("upnp:album")) {
579
            slavefield = "album";
585
            slavefield = "album";
...
...
583
            slavefield = "track";
589
            slavefield = "track";
584
            value = vs[i+2];
590
            value = vs[i+2];
585
            break;
591
            break;
586
        }
592
        }
587
    }
593
    }
588
    if (slavefield.empty()) {
589
        LOGERR("PlgWithSlave: unsupported search: [" << searchstr << "]\n");
590
        return errorEntries(ctid, entries);
591
    }
592
594
593
    // In cache ?
595
    // In cache ?
594
    ContentCacheEntry *cep;
596
    ContentCacheEntry *cep;
595
    string cachekey(m_name + ":" + objkind + ":" + slavefield + ":" + value);
597
    string cachekey(m_name + ":" + ctid + ":" + searchstr);
596
    if ((cep = o_scache.get(cachekey)) != nullptr) {
598
    if ((cep = o_scache.get(cachekey)) != nullptr) {
597
        int total = cep->toResult(classfilter, stidx, cnt, entries);
599
        int total = cep->toResult(classfilter, stidx, cnt, entries);
598
        delete cep;
600
        delete cep;
599
        return total;
601
        return total;
600
    }
602
    }
601
603
602
    // Run query
604
    // Run query
603
    unordered_map<string, string> res;
605
    unordered_map<string, string> res;
604
    if (!m->cmd.callproc("search", {
606
    if (!m->cmd.callproc("search", {
605
      {"objid", ctid},
607
                {"objid", ctid},
606
      {"objkind", objkind},
608
                {"objkind", objkind},
607
                {"origsearch", searchstr},
609
                {"origsearch", searchstr},
608
                {"field", slavefield},
610
                {"field", slavefield},
609
      {"value", value} },  res)) {
611
                {"value", value} },  res)) {
610
  LOGERR("PlgWithSlave::search: slave failure\n");
612
        LOGERR("PlgWithSlave::search: slave failure\n");
611
  return errorEntries(ctid, entries);
613
        return errorEntries(ctid, entries);
612
    }
614
    }
613
615
614
    auto it = res.find("entries");
616
    auto it = res.find("entries");
615
    if (it == res.end()) {
617
    if (it == res.end()) {
616
  LOGERR("PlgWithSlave::search: no entries returned\n");
618
        LOGERR("PlgWithSlave::search: no entries returned\n");
617
  return errorEntries(ctid, entries);
619
        return errorEntries(ctid, entries);
618
    }
620
    }
619
    // Convert the whole set and store in cache
621
    // Convert the whole set and store in cache
620
    ContentCacheEntry e;
622
    ContentCacheEntry e;
621
    resultToEntries(it->second, 0, 0, e.m_results);
623
    resultToEntries(it->second, 0, 0, e.m_results);
622
    o_scache.set(cachekey, e);
624
    o_scache.set(cachekey, e);