Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

289 lines (266 sloc) 9.644 kb
/*
Playdar - music content resolver
Copyright (C) 2009 Richard Jones
Copyright (C) 2009 Last.fm Ltd.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "rs_local_library.h"
#include <boost/foreach.hpp>
#include "library.h"
#include "playdar/utils/levenshtein.h"
#include "resolved_item_builder.hpp"
#include "playdar/resolver_query.hpp"
#include "playdar/playdar_request.h"
#include "playdar/playdar_response.h"
#include "playdar/logger.h"
using namespace std;
namespace playdar { namespace resolvers {
/*
I want to integrate the ngram2/l implementation done by erikf
in moost here. This is a bit hacky, but gets the job done 99% for now.
Specifically it currently doesnt know about words..
so "title" and "title (LIVE)" aren't very similar due to large edit-dist.
*/
bool
local::init(pa_ptr pap)
{
m_pap = pap;
string default_db_path = "collection.db";
m_library = new Library( m_pap->getstring( "database", default_db_path ).get_str());
m_exiting = false;
log::info() << "Local library resolver: " << m_library->num_files()
<< " files indexed." << endl;
if(m_library->num_files() == 0)
{
log::info() << "WARNING! You don't have any files in your database!"
<< "Run the scanner, then restart Playdar." << endl;
}
// worker thread for doing actual resolving:
m_t = new boost::thread(boost::bind(&local::run, this));
return true;
}
void
local::start_resolving( rq_ptr rq )
{
boost::mutex::scoped_lock lk(m_mutex);
m_pending.push_front( rq );
m_cond.notify_one();
}
// thread that loops forever processing incoming queries:
void
local::run()
{
try
{
while(true)
{
rq_ptr rq;
{
boost::mutex::scoped_lock lk(m_mutex);
if(m_pending.size() == 0) m_cond.wait(lk);
if(m_exiting) break;
rq = m_pending.back();
m_pending.pop_back();
}
if(rq && !rq->cancelled())
{
process( rq );
}
}
}
catch(...)
{
//cout << "local runner exiting." << endl;
}
}
//
/// this is some what fugly atm, but gets the job done for now.
/// it does the fuzzy library search using the ngram table from the db:
void
local::process( rq_ptr rq )
{
//Ignore this if it's missing artist+track fields
if( !rq->param_exists( "artist" )
|| rq->param("artist").type() != str_type
|| !rq->param_exists( "track" )
|| rq->param("track").type() != str_type )
{
return;
}
vector< json_spirit::Object > final_results;
// check if this is a special "random" query
if( rq->param("artist").get_str() == "*" &&
rq->param("track").get_str() == "*" )
{
int fid = m_library->get_random_fid();
if( fid == -1 ) return;
json_spirit::Object js;
ResolvedItemBuilder::createFromFid( *m_library, fid, js );
js.push_back( json_spirit::Pair( "sid", m_pap->gen_uuid()) );
js.push_back( json_spirit::Pair( "source", m_pap->hostname()) );
js.push_back( json_spirit::Pair( "score", 0.99) );
// dangerous, will propagate forever? muhahah
final_results.push_back( js );
}
else // end special random query check
{
// get candidates (rough potential matches):
vector<scorepair> candidates = find_candidates(rq, 10);
// now do the "real" scoring of candidate results:
string reason; // for scoring debug.
BOOST_FOREACH(scorepair &sp, candidates)
{
// multiple files in our collection may have matching metadata.
// add them all to the results.
vector<int> fids = m_library->get_fids_for_tid(sp.id);
BOOST_FOREACH(int fid, fids)
{
json_spirit::Object js;
js.reserve(12);
ResolvedItemBuilder::createFromFid( *m_library, fid, js );
js.push_back( json_spirit::Pair( "sid", m_pap->gen_uuid()) );
js.push_back( json_spirit::Pair( "source", m_pap->hostname()) );
final_results.push_back( js );
}
}
}
if(final_results.size())
{
m_pap->report_results( rq->id(), final_results );
}
}
/// Search library for candidates roughly matching the query.
/// This works with track ids and associated metadata. It's possible
/// that our library has many files for the same track id (ie, same metadata)
/// this is of no concern to this method.
///
/// First find suitable artists, then collect matching tracks for each artist.
vector<scorepair>
local::find_candidates(rq_ptr rq, unsigned int limit)
{
vector<scorepair> candidates;
float maxartscore = 0;
vector<scorepair> artistresults =
m_library->search_catalogue("artist", rq->param( "artist" ).get_str());
BOOST_FOREACH( scorepair & sp, artistresults )
{
if(maxartscore==0) maxartscore = sp.score;
float artist_multiplier = (float)sp.score / maxartscore;
float maxtrkscore = 0;
vector<scorepair> trackresults =
m_library->search_catalogue_for_artist(sp.id,
"track",
rq->param( "track" ).get_str());
BOOST_FOREACH( scorepair & sptrk, trackresults )
{
if(maxtrkscore==0) maxtrkscore = sptrk.score;
float track_multiplier = (float) sptrk.score / maxtrkscore;
// combine two scores:
float combined_score = artist_multiplier * track_multiplier;
scorepair cand;
cand.id = sptrk.id;
cand.score = combined_score;
candidates.push_back(cand);
}
}
// sort candidates by combined score
sort(candidates.begin(), candidates.end(), sortbyscore());
if(limit > 0 && candidates.size()>limit) candidates.resize(limit);
return candidates;
}
// todo: set content-encoding
bool
local::authed_http_handler(const playdar_request& req, playdar_response& resp, playdar::auth& pauth)
{
using namespace json_spirit;
ostringstream response;
if( req.parts().size() > 1 &&
req.parts()[1] == "list_artists" )
{
vector< artist_ptr > artists = m_library->list_artists();
Array qresults;
BOOST_FOREACH(artist_ptr artist, artists)
{
Object a;
a.push_back( Pair("name", artist->name()) );
qresults.push_back(a);
}
// wrap that in an object, so we can add stats to it later
Object jq;
jq.push_back( Pair("results", qresults) );
write_formatted( jq, response );
} else if(req.parts()[1] == "list_artist_tracks" &&
req.getvar_exists("artistname"))
{
Array qresults;
artist_ptr artist = m_library->load_artist( req.getvar("artistname") );
if(artist)
{
vector< track_ptr > tracks = m_library->list_artist_tracks(artist);
BOOST_FOREACH(track_ptr t, tracks)
{
Object a;
a.push_back( Pair("name", t->name()) );
qresults.push_back(a);
}
}
// wrap that in an object, so we can cram in stats etc later
Object jq;
jq.push_back( Pair("results", qresults) );
write_formatted( jq, response );
} else {
return false;
}
string retval;
if( req.getvar_exists( "jsonp" ))
{
retval = req.getvar( "jsonp" );
retval += "(" ;
retval += response.str();
retval += ");\n";
}
else
{
retval = response.str();
}
resp = playdar_response( retval, false );
return true;
}
bool
local::anon_http_handler(const playdar_request& req, playdar_response& resp, playdar::auth&)
{
if( req.parts().size() > 1 &&
(req.parts()[1] == "config" || req.parts()[1] == "stats") )
{
std::ostringstream reply;
reply << "<h2>Local Library Stats</h2>"
<< "<table>"
<< "<tr><td>Num Files</td><td>" << m_library->num_files() << "</td></tr>\n"
<< "<tr><td>Artists</td><td>" << m_library->num_artists() << "</td></tr>\n"
<< "<tr><td>Albums</td><td>" << m_library->num_albums() << "</td></tr>\n"
<< "<tr><td>Tracks</td><td>" << m_library->num_tracks() << "</td></tr>\n"
<< "</table>";
resp = reply.str();
return true;
}
return false;
}
//value
json_spirit::Value
local::capabilities() const
{
json_spirit::Object o;
o.push_back( json_spirit::Pair( "plugin", name() ));
o.push_back( json_spirit::Pair( "description", "Resolve music tracks against your local library."));
return o;
}
}}
Jump to Line
Something went wrong with that request. Please try again.