mirror of
https://github.com/clementine-player/Clementine
synced 2024-12-19 04:52:30 +01:00
Add a DBus interface onto the global search engine, and add a KDE krunner plugin that performs global searches in Clementine.
This is optional and is not compiled unless you have the plasma developer packages installed.
This commit is contained in:
parent
348faef9e1
commit
9a739a3346
1
3rdparty/libprojectm/CMakeLists.txt
vendored
1
3rdparty/libprojectm/CMakeLists.txt
vendored
@ -5,6 +5,7 @@ SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "The projectM core library.")
|
||||
include(CheckCXXCompilerFlag)
|
||||
|
||||
cmake_policy(SET CMP0005 OLD)
|
||||
cmake_policy(SET CMP0017 OLD)
|
||||
|
||||
|
||||
set(USE_DEVIL OFF)
|
||||
|
2
3rdparty/qsqlite/CMakeLists.txt
vendored
2
3rdparty/qsqlite/CMakeLists.txt
vendored
@ -1,5 +1,7 @@
|
||||
cmake_minimum_required(VERSION 2.6)
|
||||
|
||||
add_definitions(-DQT_STATICPLUGIN)
|
||||
|
||||
# Source files
|
||||
set(SQLITE-SOURCES
|
||||
qsql_sqlite.cpp
|
||||
|
@ -279,7 +279,6 @@ endif(ENABLE_REMOTE AND HAVE_GNUTLS AND HAVE_PJSIP)
|
||||
set(HAVE_STATIC_SQLITE ${STATIC_SQLITE})
|
||||
if(STATIC_SQLITE)
|
||||
message(STATUS "Building static qsqlite plugin")
|
||||
add_definitions(-DQT_STATICPLUGIN)
|
||||
add_subdirectory(3rdparty/qsqlite)
|
||||
include_directories("3rdparty/qsqlite")
|
||||
endif(STATIC_SQLITE)
|
||||
@ -381,6 +380,13 @@ if(HAVE_SPOTIFY_BLOB)
|
||||
add_subdirectory(spotifyblob/blob)
|
||||
endif(HAVE_SPOTIFY_BLOB)
|
||||
|
||||
# This goes after everything else because KDE fucks everything else up with its
|
||||
# cmake includes.
|
||||
find_package(KDE4 4.3.60)
|
||||
if(KDE4_PLASMA_LIBS)
|
||||
add_subdirectory(plasmarunner)
|
||||
endif(KDE4_PLASMA_LIBS)
|
||||
|
||||
# Uninstall support
|
||||
configure_file(
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in"
|
||||
|
5
data/clementineplasmarunner.qrc
Normal file
5
data/clementineplasmarunner.qrc
Normal file
@ -0,0 +1,5 @@
|
||||
<RCC>
|
||||
<qresource prefix="/clementineplasmarunner">
|
||||
<file>nocover.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
33
plasmarunner/CMakeLists.txt
Normal file
33
plasmarunner/CMakeLists.txt
Normal file
@ -0,0 +1,33 @@
|
||||
include(KDE4Defaults)
|
||||
include(MacroLibrary)
|
||||
|
||||
add_definitions(${KDE4_DEFINITIONS})
|
||||
include_directories(${CMAKE_SOURCE_DIR})
|
||||
include_directories(${CMAKE_BINARY_DIR}/src)
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
include_directories(${KDE4_INCLUDES})
|
||||
|
||||
set(SOURCES
|
||||
clementinerunner.cpp
|
||||
../src/globalsearch/common.cpp
|
||||
)
|
||||
|
||||
set(RESOURCES
|
||||
../data/clementineplasmarunner.qrc
|
||||
)
|
||||
|
||||
list(APPEND QT_DBUSXML2CPP_EXECUTABLE -i src/globalsearch/common.h)
|
||||
|
||||
qt4_add_dbus_interface(SOURCES
|
||||
${CMAKE_SOURCE_DIR}/src/dbus/org.clementineplayer.GlobalSearch.xml
|
||||
globalsearchinterface)
|
||||
qt4_add_resources(QRC ${RESOURCES})
|
||||
kde4_add_plugin(plasma_runner_clementine ${SOURCES} ${QRC})
|
||||
|
||||
target_link_libraries(plasma_runner_clementine ${KDE4_PLASMA_LIBS})
|
||||
|
||||
# Install the library and .desktop file
|
||||
install(TARGETS plasma_runner_clementine DESTINATION ${PLUGIN_INSTALL_DIR})
|
||||
install(FILES plasma-runner-clementine.desktop DESTINATION ${SERVICES_INSTALL_DIR})
|
||||
|
295
plasmarunner/clementinerunner.cpp
Normal file
295
plasmarunner/clementinerunner.cpp
Normal file
@ -0,0 +1,295 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2011, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine 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.
|
||||
|
||||
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "clementinerunner.h"
|
||||
#include "globalsearchinterface.h"
|
||||
|
||||
#include <QDBusConnection>
|
||||
#include <QMutexLocker>
|
||||
#include <QtDebug>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
const char* ClementineRunner::kDbusService = "org.mpris.clementine";
|
||||
const char* ClementineRunner::kDbusPath = "/GlobalSearch";
|
||||
|
||||
|
||||
ClementineRunner::ClementineRunner(QObject* parent, const QVariantList& args)
|
||||
: Plasma::AbstractRunner(parent, args),
|
||||
interface_(NULL)
|
||||
{
|
||||
qDBusRegisterMetaType<GlobalSearchServiceResult>();
|
||||
qDBusRegisterMetaType<GlobalSearchServiceResultList>();
|
||||
|
||||
Q_INIT_RESOURCE(clementineplasmarunner);
|
||||
|
||||
nocover_ = QIcon(":/clementineplasmarunner/nocover.png");
|
||||
|
||||
setIgnoredTypes(Plasma::RunnerContext::NetworkLocation |
|
||||
Plasma::RunnerContext::FileSystem);
|
||||
setSpeed(SlowSpeed);
|
||||
setPriority(LowPriority);
|
||||
setHasRunOptions(true);
|
||||
|
||||
Plasma::RunnerSyntax syntax = Plasma::RunnerSyntax("<query>", tr("Search music"));
|
||||
syntax.addExampleQuery("foo fighters");
|
||||
addSyntax(syntax);
|
||||
|
||||
QDBusConnection bus = QDBusConnection::sessionBus();
|
||||
bus.connect(
|
||||
kDbusService, kDbusPath,
|
||||
OrgClementineplayerGlobalSearchInterface::staticInterfaceName(),
|
||||
"SearchFinished", "i", this, SLOT(SearchFinished(int)));
|
||||
bus.connect(
|
||||
kDbusService, kDbusPath,
|
||||
OrgClementineplayerGlobalSearchInterface::staticInterfaceName(),
|
||||
"ResultsAvailable", "ia(ibsiiissssbi)",
|
||||
this, SLOT(ResultsAvailable(int,GlobalSearchServiceResultList)));
|
||||
bus.connect(
|
||||
kDbusService, kDbusPath,
|
||||
OrgClementineplayerGlobalSearchInterface::staticInterfaceName(),
|
||||
"ArtLoaded", "iay", this, SLOT(ArtLoaded(int,QByteArray)));
|
||||
}
|
||||
|
||||
void ClementineRunner::match(Plasma::RunnerContext& context) {
|
||||
|
||||
if (context.query().length() < 3)
|
||||
return;
|
||||
|
||||
if (!interface_) {
|
||||
interface_ = new OrgClementineplayerGlobalSearchInterface(
|
||||
kDbusService, kDbusPath, QDBusConnection::sessionBus());
|
||||
}
|
||||
|
||||
int id = 0;
|
||||
PendingQuery query;
|
||||
query.results_mutex_.lock();
|
||||
|
||||
// Start the search
|
||||
{
|
||||
QMutexLocker l(&pending_mutex_);
|
||||
QDBusPendingReply<int> reply = interface_->StartSearch(context.query(), true);
|
||||
reply.waitForFinished();
|
||||
|
||||
if (!reply.isValid()) {
|
||||
qDebug() << reply.error();
|
||||
return;
|
||||
}
|
||||
id = reply.value();
|
||||
|
||||
pending_queries_[id] = &query;
|
||||
}
|
||||
|
||||
query.results_mutex_.unlock();
|
||||
|
||||
// Wait for results to arrive
|
||||
forever {
|
||||
query.results_semaphore_.acquire();
|
||||
|
||||
// If the user closed the query then stop early.
|
||||
if (!context.isValid())
|
||||
break;
|
||||
|
||||
// Take the next result on the list
|
||||
GlobalSearchServiceResult result;
|
||||
{
|
||||
QMutexLocker l(&query.results_mutex_);
|
||||
if (query.results_.isEmpty() && query.finished_)
|
||||
break;
|
||||
|
||||
result = query.results_.takeFirst();
|
||||
}
|
||||
|
||||
// Create a match for the result.
|
||||
Plasma::QueryMatch match(this);
|
||||
FillMatch(result, &match);
|
||||
|
||||
// Emit the match
|
||||
context.addMatch(QString(), match);
|
||||
}
|
||||
|
||||
// Remove the pending search
|
||||
{
|
||||
QMutexLocker l(&pending_mutex_);
|
||||
pending_queries_.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
void ClementineRunner::FillMatch(const GlobalSearchServiceResult& result,
|
||||
Plasma::QueryMatch* match) const {
|
||||
QString line[2];
|
||||
|
||||
switch (result.type_) {
|
||||
case globalsearch::Type_Track:
|
||||
case globalsearch::Type_Stream: {
|
||||
line[0] = result.title_;
|
||||
|
||||
if (!result.artist_.isEmpty()) {
|
||||
line[1] += result.artist_;
|
||||
} else if (!result.album_artist_.isEmpty()) {
|
||||
line[1] += result.album_artist_;
|
||||
}
|
||||
|
||||
if (!result.album_.isEmpty()) {
|
||||
line[1] += " - " + result.album_;
|
||||
}
|
||||
|
||||
if (result.track_ > 0) {
|
||||
line[1] += " - " + tr("track %1").arg(result.track_);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case globalsearch::Type_Album: {
|
||||
if (!result.album_artist_.isEmpty())
|
||||
line[0] += result.album_artist_;
|
||||
else if (result.is_compilation_)
|
||||
line[0] += tr("Various Artists");
|
||||
else if (!result.artist_.isEmpty())
|
||||
line[0] += result.artist_;
|
||||
else
|
||||
line[0] += tr("Unknown");
|
||||
|
||||
// Dash
|
||||
line[0] += " - ";
|
||||
|
||||
// Album
|
||||
if (result.album_.isEmpty())
|
||||
line[0] += tr("Unknown");
|
||||
else
|
||||
line[0] += result.album_;
|
||||
|
||||
if (result.album_size_ > 1)
|
||||
line[1] = tr("Album with %1 tracks").arg(result.album_size_);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
match->setType(Plasma::QueryMatch::CompletionMatch);
|
||||
match->setText(line[0]);
|
||||
match->setSubtext(line[1]);
|
||||
match->setRelevance(ResultRelevance(result));
|
||||
|
||||
if (!result.image_.isNull()) {
|
||||
match->setIcon(result.image_);
|
||||
} else {
|
||||
match->setIcon(nocover_);
|
||||
}
|
||||
}
|
||||
|
||||
void ClementineRunner::run(const Plasma::RunnerContext& context, const Plasma::QueryMatch& match) {
|
||||
// TODO
|
||||
qDebug() << __PRETTY_FUNCTION__;
|
||||
}
|
||||
|
||||
qreal ClementineRunner::ResultRelevance(const GlobalSearchServiceResult& result) const {
|
||||
// Plasma sorts results by a qreal that we provide. We have to do some dumb
|
||||
// maths to create a qreal for the result such that it will produce the same
|
||||
// sorting as you'd get in clementine.
|
||||
|
||||
// Clementine sorts by:
|
||||
// provider
|
||||
// match quality
|
||||
// type
|
||||
// title (for tracks and streams)
|
||||
// artist/album (for albums)
|
||||
|
||||
qreal ret =
|
||||
10000 * qreal(result.provider_order_) / 10.0 +
|
||||
100 * qreal(globalsearch::Quality_None + 1 - result.match_quality_) +
|
||||
10 * qreal(globalsearch::Type_Album + 1 - result.type_);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ClementineRunner::ResultsAvailable(int id, GlobalSearchServiceResultList results) {
|
||||
// Lock the mutex and add the results to the list.
|
||||
QMutexLocker l(&pending_mutex_);
|
||||
PendingMap::iterator it = pending_queries_.find(id);
|
||||
if (it == pending_queries_.end())
|
||||
return;
|
||||
|
||||
(*it)->provider_order_ ++;
|
||||
|
||||
QMutexLocker result_l(&(*it)->results_mutex_);
|
||||
foreach (GlobalSearchServiceResult result, results) {
|
||||
result.provider_order_ = (*it)->provider_order_;
|
||||
|
||||
if (result.art_on_the_way_) {
|
||||
// Brace yourselves, art is coming.
|
||||
(*it)->results_waiting_for_art_ << result;
|
||||
} else {
|
||||
(*it)->results_.append(results);
|
||||
(*it)->results_semaphore_.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClementineRunner::SearchFinished(int id) {
|
||||
QMutexLocker l(&pending_mutex_);
|
||||
PendingMap::iterator it = pending_queries_.find(id);
|
||||
if (it == pending_queries_.end())
|
||||
return;
|
||||
|
||||
(*it)->finished_signal_emitted_ = true;
|
||||
|
||||
if ((*it)->results_waiting_for_art_.isEmpty()) {
|
||||
QMutexLocker result_l(&(*it)->results_mutex_);
|
||||
(*it)->finished_ = true;
|
||||
(*it)->results_semaphore_.release();
|
||||
}
|
||||
}
|
||||
|
||||
void ClementineRunner::ArtLoaded(int result_id, const QByteArray& image_data) {
|
||||
QMutexLocker l(&pending_mutex_);
|
||||
|
||||
// Find a query that has a result with this ID waiting for art
|
||||
foreach (PendingQuery* query, pending_queries_.values()) {
|
||||
GlobalSearchServiceResultList::iterator it;
|
||||
for (it = query->results_waiting_for_art_.begin() ;
|
||||
it != query->results_waiting_for_art_.end() ; ++it) {
|
||||
if (it->result_id_ == result_id) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (it == query->results_waiting_for_art_.end())
|
||||
continue;
|
||||
|
||||
// The API is stupid so we have to create the QIcon here on the GUI thread.
|
||||
if (!image_data.isEmpty()) {
|
||||
QImage image;
|
||||
if (image.loadFromData(image_data)) {
|
||||
it->image_ = QIcon(QPixmap::fromImage(image));
|
||||
}
|
||||
}
|
||||
|
||||
// Add the art to this result and remote it from the waiting list
|
||||
QMutexLocker result_l(&query->results_mutex_);
|
||||
query->results_ << *it;
|
||||
query->results_waiting_for_art_.erase(it);
|
||||
query->finished_ =
|
||||
query->finished_signal_emitted_ &&
|
||||
query->results_waiting_for_art_.isEmpty();
|
||||
query->results_semaphore_.release(query->finished_ ? 2 : 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
K_EXPORT_PLASMA_RUNNER(clementine, ClementineRunner)
|
83
plasmarunner/clementinerunner.h
Normal file
83
plasmarunner/clementinerunner.h
Normal file
@ -0,0 +1,83 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2011, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine 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.
|
||||
|
||||
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CLEMENTINERUNNER_H
|
||||
#define CLEMENTINERUNNER_H
|
||||
|
||||
#include "src/globalsearch/common.h"
|
||||
|
||||
#include <QSemaphore>
|
||||
|
||||
#include <Plasma/AbstractRunner>
|
||||
|
||||
class OrgClementineplayerGlobalSearchInterface;
|
||||
|
||||
class ClementineRunner : public Plasma::AbstractRunner {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ClementineRunner(QObject* parent, const QVariantList& args);
|
||||
|
||||
static const char* kDbusService;
|
||||
static const char* kDbusPath;
|
||||
|
||||
void match(Plasma::RunnerContext& context);
|
||||
void run(const Plasma::RunnerContext& context, const Plasma::QueryMatch& match);
|
||||
|
||||
private slots:
|
||||
void ResultsAvailable(int id, GlobalSearchServiceResultList results);
|
||||
void SearchFinished(int id);
|
||||
void ArtLoaded(int result_id, const QByteArray& image_data);
|
||||
|
||||
private:
|
||||
struct PendingQuery {
|
||||
PendingQuery()
|
||||
: provider_order_(0),
|
||||
finished_signal_emitted_(false),
|
||||
finished_(false)
|
||||
{}
|
||||
|
||||
// The main thread is the only one to access these variables.
|
||||
int provider_order_;
|
||||
GlobalSearchServiceResultList results_waiting_for_art_;
|
||||
bool finished_signal_emitted_;
|
||||
|
||||
// This list contains results that are finished and waiting to be processed
|
||||
// by the match() thread. results_mutex_ locks results_ and
|
||||
// results_semaphore_ is released once for each result.
|
||||
GlobalSearchServiceResultList results_;
|
||||
QMutex results_mutex_;
|
||||
QSemaphore results_semaphore_;
|
||||
bool finished_;
|
||||
};
|
||||
typedef QMap<int, PendingQuery*> PendingMap;
|
||||
|
||||
qreal ResultRelevance(const GlobalSearchServiceResult& result) const;
|
||||
void FillMatch(const GlobalSearchServiceResult& result,
|
||||
Plasma::QueryMatch* match) const;
|
||||
|
||||
private:
|
||||
OrgClementineplayerGlobalSearchInterface* interface_;
|
||||
|
||||
QIcon nocover_;
|
||||
|
||||
// pending_mutex_ locks any access to the PendingMap.
|
||||
QMutex pending_mutex_;
|
||||
PendingMap pending_queries_;
|
||||
};
|
||||
|
||||
#endif // CLEMENTINERUNNER_H
|
17
plasmarunner/plasma-runner-clementine.desktop
Normal file
17
plasmarunner/plasma-runner-clementine.desktop
Normal file
@ -0,0 +1,17 @@
|
||||
[Desktop Entry]
|
||||
Name=Clementine
|
||||
Icon=clementine
|
||||
Comment=Clementine global music search
|
||||
Type=Service
|
||||
ServiceTypes=Plasma/Runner
|
||||
|
||||
X-KDE-Library=plasma_runner_clementine
|
||||
X-KDE-PluginInfo-Author=David Sansome
|
||||
X-KDE-PluginInfo-Email=me@davidsansome.com
|
||||
X-KDE-PluginInfo-Name=clementine
|
||||
X-KDE-PluginInfo-Version=1.0
|
||||
X-KDE-PluginInfo-Website=http://www.clementine-player.org/
|
||||
X-KDE-PluginInfo-Category=Audio
|
||||
X-KDE-PluginInfo-Depends=
|
||||
X-KDE-PluginInfo-License=GPL
|
||||
X-KDE-PluginInfo-EnabledByDefault=true
|
@ -116,6 +116,7 @@ set(SOURCES
|
||||
engines/gstenginepipeline.cpp
|
||||
engines/gstelementdeleter.cpp
|
||||
|
||||
globalsearch/common.cpp
|
||||
globalsearch/digitallyimportedsearchprovider.cpp
|
||||
globalsearch/globalsearch.cpp
|
||||
globalsearch/globalsearchitemdelegate.cpp
|
||||
@ -779,7 +780,6 @@ if(HAVE_DBUS)
|
||||
dbus/org.gnome.SettingsDaemon.MediaKeys.xml
|
||||
dbus/gnomesettingsdaemon)
|
||||
|
||||
|
||||
# DeviceKit DBUS interfaces
|
||||
if(HAVE_DEVICEKIT)
|
||||
qt4_add_dbus_interface(SOURCES
|
||||
@ -790,6 +790,16 @@ if(HAVE_DBUS)
|
||||
dbus/udisksdevice)
|
||||
endif(HAVE_DEVICEKIT)
|
||||
|
||||
# Global search interface
|
||||
qt4_add_dbus_adaptor(SOURCES
|
||||
dbus/org.clementineplayer.GlobalSearch.xml
|
||||
globalsearch/globalsearchservice.h GlobalSearchService
|
||||
globalsearch/globalsearchadaptor)
|
||||
|
||||
# Global search source
|
||||
list(APPEND SOURCES globalsearch/globalsearchservice.cpp)
|
||||
list(APPEND HEADERS globalsearch/globalsearchservice.h)
|
||||
|
||||
# MPRIS source
|
||||
list(APPEND SOURCES core/mpris.cpp core/mpris1.cpp core/mpris2.cpp)
|
||||
list(APPEND HEADERS core/mpris.h core/mpris1.h core/mpris2.h)
|
||||
|
@ -305,25 +305,11 @@ void Mpris2::CurrentSongChanged(const Song& song) {
|
||||
// ... and we add the cover information later, when it's available.
|
||||
void Mpris2::ArtLoaded(const Song& song, const QString& art_uri) {
|
||||
last_metadata_ = QVariantMap();
|
||||
song.ToXesam(&last_metadata_);
|
||||
|
||||
using mpris::AddMetadata;
|
||||
AddMetadata("mpris:trackid", current_track_id(), &last_metadata_);
|
||||
AddMetadata("xesam:url", song.url().toString(), &last_metadata_);
|
||||
AddMetadata("xesam:title", song.PrettyTitle(), &last_metadata_);
|
||||
AddMetadataAsList("xesam:artist", song.artist(), &last_metadata_);
|
||||
AddMetadata("xesam:album", song.album(), &last_metadata_);
|
||||
AddMetadataAsList("xesam:albumArtist", song.albumartist(), &last_metadata_);
|
||||
AddMetadata("mpris:length", song.length_nanosec() / kNsecPerUsec, &last_metadata_);
|
||||
AddMetadata("xesam:trackNumber", song.track(), &last_metadata_);
|
||||
AddMetadataAsList("xesam:genre", song.genre(), &last_metadata_);
|
||||
AddMetadata("xesam:discNumber", song.disc(), &last_metadata_);
|
||||
AddMetadataAsList("xesam:comment", song.comment(), &last_metadata_);
|
||||
AddMetadata("xesam:contentCreated", AsMPRISDateTimeType(song.ctime()), &last_metadata_);
|
||||
AddMetadata("xesam:lastUsed", AsMPRISDateTimeType(song.lastplayed()), &last_metadata_);
|
||||
AddMetadata("xesam:audioBPM", song.bpm(), &last_metadata_);
|
||||
AddMetadataAsList("xesam:composer", song.composer(), &last_metadata_);
|
||||
AddMetadata("xesam:useCount", song.playcount(), &last_metadata_);
|
||||
AddMetadata("xesam:autoRating", song.score(), &last_metadata_);
|
||||
|
||||
if (song.rating() != -1.0) {
|
||||
AddMetadata("rating", song.rating() * 5, &last_metadata_);
|
||||
}
|
||||
|
@ -175,10 +175,6 @@ private:
|
||||
Mpris1* mpris1_;
|
||||
};
|
||||
|
||||
inline QString AsMPRISDateTimeType(uint time) {
|
||||
return time != -1 ? QDateTime::fromTime_t(time).toString(Qt::ISODate) : "";
|
||||
}
|
||||
|
||||
} // namespace mpris
|
||||
|
||||
#endif
|
||||
|
@ -41,6 +41,10 @@ inline void AddMetadata(const QString& key, const QDateTime& metadata, QVariantM
|
||||
if (metadata.isValid()) (*map)[key] = metadata;
|
||||
}
|
||||
|
||||
inline QString AsMPRISDateTimeType(uint time) {
|
||||
return time != -1 ? QDateTime::fromTime_t(time).toString(Qt::ISODate) : "";
|
||||
}
|
||||
|
||||
} // namespace mpris
|
||||
|
||||
#endif // MPRIS_COMMON_H
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "fmpsparser.h"
|
||||
#include "song.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/mpris_common.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@ -1357,3 +1358,29 @@ bool Song::IsOnSameAlbum(const Song& other) const {
|
||||
|
||||
return album() == other.album() && artist() == other.artist();
|
||||
}
|
||||
|
||||
void Song::ToXesam(QVariantMap* map) const {
|
||||
using mpris::AddMetadata;
|
||||
using mpris::AddMetadataAsList;
|
||||
using mpris::AsMPRISDateTimeType;
|
||||
|
||||
AddMetadata("xesam:url", url().toString(), map);
|
||||
AddMetadata("xesam:title", PrettyTitle(), map);
|
||||
AddMetadataAsList("xesam:artist", artist(), map);
|
||||
AddMetadata("xesam:album", album(), map);
|
||||
AddMetadataAsList("xesam:albumArtist", albumartist(), map);
|
||||
AddMetadata("mpris:length", length_nanosec() / kNsecPerUsec, map);
|
||||
AddMetadata("xesam:trackNumber", track(), map);
|
||||
AddMetadataAsList("xesam:genre", genre(), map);
|
||||
AddMetadata("xesam:discNumber", disc(), map);
|
||||
AddMetadataAsList("xesam:comment", comment(), map);
|
||||
AddMetadata("xesam:contentCreated", AsMPRISDateTimeType(ctime()), map);
|
||||
AddMetadata("xesam:lastUsed", AsMPRISDateTimeType(lastplayed()), map);
|
||||
AddMetadata("xesam:audioBPM", bpm(), map);
|
||||
AddMetadataAsList("xesam:composer", composer(), map);
|
||||
AddMetadata("xesam:useCount", playcount(), map);
|
||||
AddMetadata("xesam:autoRating", score(), map);
|
||||
if (rating() != -1.0) {
|
||||
AddMetadata("xesam:userRating", rating(), map);
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include <QSqlQuery>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QVariantMap>
|
||||
|
||||
#include <xiphcomment.h>
|
||||
|
||||
@ -63,6 +64,7 @@ namespace TagLib {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class FileRefFactory {
|
||||
public:
|
||||
virtual ~FileRefFactory() {}
|
||||
@ -161,6 +163,7 @@ class Song {
|
||||
#ifdef HAVE_LIBLASTFM
|
||||
void ToLastFM(lastfm::Track* track) const;
|
||||
#endif
|
||||
void ToXesam(QVariantMap* map) const;
|
||||
|
||||
// Simple accessors
|
||||
bool is_valid() const { return d->valid_; }
|
||||
|
32
src/dbus/org.clementineplayer.GlobalSearch.xml
Normal file
32
src/dbus/org.clementineplayer.GlobalSearch.xml
Normal file
@ -0,0 +1,32 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
|
||||
<node>
|
||||
<interface name='org.clementineplayer.GlobalSearch'>
|
||||
<method name='StartSearch'>
|
||||
<arg type='i' name='id' direction='out' />
|
||||
<arg type='s' name='query' direction='in' />
|
||||
<arg type='b' name='prefetch_art' direction='in' />
|
||||
</method>
|
||||
|
||||
<method name='CancelSearch'>
|
||||
<arg type='i' name='id' direction='in' />
|
||||
</method>
|
||||
|
||||
<signal name='ResultsAvailable'>
|
||||
<arg type='i' name='id' />
|
||||
<arg type='a(ibsiiissssbi)' name='results' />
|
||||
<annotation name="com.trolltech.QtDBus.QtTypeName.In1" value="GlobalSearchServiceResultList" />
|
||||
</signal>
|
||||
|
||||
<signal name='SearchFinished'>
|
||||
<arg type='i' name='id' />
|
||||
</signal>
|
||||
|
||||
<signal name='ArtLoaded'>
|
||||
<arg type='i' name='result_id' />
|
||||
<arg type='ay' name='image_data' />
|
||||
<annotation name="com.trolltech.QtDBus.QtTypeName.Out1" value="QByteArray" />
|
||||
</signal>
|
||||
</interface>
|
||||
</node>
|
66
src/globalsearch/common.cpp
Normal file
66
src/globalsearch/common.cpp
Normal file
@ -0,0 +1,66 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2011, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine 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.
|
||||
|
||||
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
|
||||
QDBusArgument& operator <<(QDBusArgument& arg, const GlobalSearchServiceResult& result) {
|
||||
arg.beginStructure();
|
||||
arg << result.result_id_
|
||||
<< result.art_on_the_way_
|
||||
<< result.provider_name_
|
||||
<< result.type_
|
||||
<< result.match_quality_
|
||||
<< result.album_size_
|
||||
<< result.title_
|
||||
<< result.artist_
|
||||
<< result.album_
|
||||
<< result.album_artist_
|
||||
<< result.is_compilation_
|
||||
<< result.track_;
|
||||
arg.endStructure();
|
||||
|
||||
return arg;
|
||||
}
|
||||
|
||||
const QDBusArgument& operator >>(const QDBusArgument& arg, GlobalSearchServiceResult& result) {
|
||||
int type;
|
||||
int match_quality;
|
||||
|
||||
arg.beginStructure();
|
||||
arg >> result.result_id_
|
||||
>> result.art_on_the_way_
|
||||
>> result.provider_name_
|
||||
>> type
|
||||
>> match_quality
|
||||
>> result.album_size_
|
||||
>> result.title_
|
||||
>> result.artist_
|
||||
>> result.album_
|
||||
>> result.album_artist_
|
||||
>> result.is_compilation_
|
||||
>> result.track_;
|
||||
arg.endStructure();
|
||||
|
||||
result.type_ = static_cast<globalsearch::Type>(type);
|
||||
result.match_quality_ = static_cast<globalsearch::MatchQuality>(match_quality);
|
||||
|
||||
return arg;
|
||||
}
|
||||
|
||||
#endif // HAVE_DBUS
|
92
src/globalsearch/common.h
Normal file
92
src/globalsearch/common.h
Normal file
@ -0,0 +1,92 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2011, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine 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.
|
||||
|
||||
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef GLOBALSEARCHSERVICERESULT_H
|
||||
#define GLOBALSEARCHSERVICERESULT_H
|
||||
|
||||
// This file contains definitions that are shared between Clementine and any
|
||||
// external applications that need to do global searches over DBus.
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
# include <QDBusArgument>
|
||||
#endif
|
||||
|
||||
#include <QIcon>
|
||||
|
||||
namespace globalsearch {
|
||||
|
||||
// The order of types here is the order they'll appear in the UI.
|
||||
enum Type {
|
||||
Type_Track = 0,
|
||||
Type_Stream,
|
||||
Type_Album
|
||||
};
|
||||
|
||||
enum MatchQuality {
|
||||
// A token in the search string matched at the beginning of the song
|
||||
// metadata.
|
||||
Quality_AtStart = 0,
|
||||
|
||||
// A token matched somewhere else.
|
||||
Quality_Middle,
|
||||
|
||||
Quality_None
|
||||
};
|
||||
|
||||
} // namespace globalsearch
|
||||
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
|
||||
struct GlobalSearchServiceResult {
|
||||
// When adding new fields to this struct remember to update the dbus signature
|
||||
// which is duplicated in the xml specification and in clementinerunner.cpp
|
||||
|
||||
int result_id_;
|
||||
bool art_on_the_way_;
|
||||
|
||||
QString provider_name_;
|
||||
globalsearch::Type type_;
|
||||
globalsearch::MatchQuality match_quality_;
|
||||
|
||||
int album_size_;
|
||||
|
||||
QString title_;
|
||||
QString artist_;
|
||||
QString album_;
|
||||
QString album_artist_;
|
||||
bool is_compilation_;
|
||||
int track_;
|
||||
|
||||
// Not included in the dbus emission.
|
||||
QIcon image_;
|
||||
int provider_order_;
|
||||
};
|
||||
typedef QList<GlobalSearchServiceResult> GlobalSearchServiceResultList;
|
||||
|
||||
Q_DECLARE_METATYPE(GlobalSearchServiceResult)
|
||||
Q_DECLARE_METATYPE(GlobalSearchServiceResultList)
|
||||
|
||||
QDBusArgument& operator <<(QDBusArgument& arg, const GlobalSearchServiceResult& result);
|
||||
const QDBusArgument& operator >>(const QDBusArgument& arg, GlobalSearchServiceResult& result);
|
||||
|
||||
#endif // HAVE_DBUS
|
||||
|
||||
|
||||
#endif // GLOBALSEARCHSERVICERESULT_H
|
@ -88,14 +88,14 @@ void GlobalSearchItemDelegate::paint(QPainter* p,
|
||||
|
||||
QString count;
|
||||
switch (result.type_) {
|
||||
case SearchProvider::Result::Type_Track:
|
||||
case globalsearch::Type_Track:
|
||||
break;
|
||||
|
||||
case SearchProvider::Result::Type_Stream:
|
||||
case globalsearch::Type_Stream:
|
||||
count = QString::fromUtf8("∞");
|
||||
break;
|
||||
|
||||
case SearchProvider::Result::Type_Album:
|
||||
case globalsearch::Type_Album:
|
||||
if (result.album_size_ <= 0)
|
||||
count = "-";
|
||||
else
|
||||
@ -130,8 +130,8 @@ void GlobalSearchItemDelegate::paint(QPainter* p,
|
||||
|
||||
// The text we draw depends on the type of result.
|
||||
switch (result.type_) {
|
||||
case SearchProvider::Result::Type_Track:
|
||||
case SearchProvider::Result::Type_Stream: {
|
||||
case globalsearch::Type_Track:
|
||||
case globalsearch::Type_Stream: {
|
||||
// Title
|
||||
line_1 += m.title() + " ";
|
||||
|
||||
@ -153,7 +153,7 @@ void GlobalSearchItemDelegate::paint(QPainter* p,
|
||||
break;
|
||||
}
|
||||
|
||||
case SearchProvider::Result::Type_Album: {
|
||||
case globalsearch::Type_Album: {
|
||||
// Line 1 is Artist - Album
|
||||
// Artist
|
||||
if (!m.albumartist().isEmpty())
|
||||
|
129
src/globalsearch/globalsearchservice.cpp
Normal file
129
src/globalsearch/globalsearchservice.cpp
Normal file
@ -0,0 +1,129 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2011, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine 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.
|
||||
|
||||
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "globalsearch.h"
|
||||
#include "globalsearchservice.h"
|
||||
#include "globalsearch/globalsearchadaptor.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/mpris_common.h"
|
||||
|
||||
|
||||
GlobalSearchService::GlobalSearchService(GlobalSearch* engine, QObject* parent)
|
||||
: QObject(parent),
|
||||
engine_(engine)
|
||||
{
|
||||
qDBusRegisterMetaType<GlobalSearchServiceResult>();
|
||||
qDBusRegisterMetaType<GlobalSearchServiceResultList>();
|
||||
|
||||
new GlobalSearchAdaptor(this);
|
||||
QDBusConnection::sessionBus().registerObject("/GlobalSearch", this);
|
||||
|
||||
connect(engine_, SIGNAL(ResultsAvailable(int,SearchProvider::ResultList)),
|
||||
this, SLOT(ResultsAvailableSlot(int,SearchProvider::ResultList)),
|
||||
Qt::QueuedConnection);
|
||||
connect(engine_, SIGNAL(SearchFinished(int)),
|
||||
this, SLOT(SearchFinishedSlot(int)),
|
||||
Qt::QueuedConnection);
|
||||
connect(engine_, SIGNAL(ArtLoaded(int,QPixmap)),
|
||||
this, SLOT(ArtLoadedSlot(int,QPixmap)),
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
int GlobalSearchService::StartSearch(const QString& query, bool prefetch_art) {
|
||||
PendingSearch pending_search;
|
||||
pending_search.prefetch_art_ = prefetch_art;
|
||||
|
||||
const int id = engine_->SearchAsync(query);
|
||||
pending_searches_[id] = pending_search;
|
||||
return id;
|
||||
}
|
||||
|
||||
void GlobalSearchService::CancelSearch(int id) {
|
||||
if (!pending_searches_.contains(id))
|
||||
return;
|
||||
|
||||
engine_->CancelSearch(id);
|
||||
pending_searches_.remove(id);
|
||||
}
|
||||
|
||||
void GlobalSearchService::ResultsAvailableSlot(int id, const SearchProvider::ResultList& results) {
|
||||
if (!pending_searches_.contains(id))
|
||||
return;
|
||||
|
||||
const PendingSearch& pending = pending_searches_[id];
|
||||
|
||||
GlobalSearchServiceResultList ret;
|
||||
foreach (const SearchProvider::Result& result, results) {
|
||||
const int result_id = next_result_id_ ++;
|
||||
|
||||
RecentResult& recent = recent_results_[result_id];
|
||||
recent.result_.art_on_the_way_ = false;
|
||||
|
||||
// Prefetch art if it was requested
|
||||
if (pending.prefetch_art_ && !result.provider_->art_is_probably_remote()) {
|
||||
const int art_id = engine_->LoadArtAsync(result);
|
||||
prefetching_art_[art_id] = result_id;
|
||||
recent.result_.art_on_the_way_ = true;
|
||||
}
|
||||
|
||||
// Build the result to send back
|
||||
recent.result_.result_id_ = result_id;
|
||||
recent.result_.provider_name_ = result.provider_->name();
|
||||
recent.result_.type_ = result.type_;
|
||||
recent.result_.match_quality_ = result.match_quality_;
|
||||
|
||||
recent.result_.album_size_ = result.album_size_;
|
||||
|
||||
recent.result_.title_ = result.metadata_.title();
|
||||
recent.result_.artist_ = result.metadata_.artist();
|
||||
recent.result_.album_ = result.metadata_.album();
|
||||
recent.result_.album_artist_ = result.metadata_.albumartist();
|
||||
recent.result_.is_compilation_ = result.metadata_.is_compilation();
|
||||
recent.result_.track_ = result.metadata_.track();
|
||||
|
||||
ret << recent.result_;
|
||||
}
|
||||
|
||||
emit ResultsAvailable(id, ret);
|
||||
}
|
||||
|
||||
void GlobalSearchService::SearchFinishedSlot(int id) {
|
||||
if (!pending_searches_.contains(id))
|
||||
return;
|
||||
|
||||
emit SearchFinished(id);
|
||||
pending_searches_.remove(id);
|
||||
}
|
||||
|
||||
void GlobalSearchService::ArtLoadedSlot(int id, const QPixmap& pixmap) {
|
||||
QMap<int, int>::iterator it = prefetching_art_.find(id);
|
||||
if (it == prefetching_art_.end())
|
||||
return;
|
||||
|
||||
const int result_id = prefetching_art_.take(id);
|
||||
QMap<int, RecentResult>::iterator it2 = recent_results_.find(result_id);
|
||||
if (it2 == recent_results_.end())
|
||||
return;
|
||||
|
||||
// Encode the pixmap as a png
|
||||
QBuffer buf;
|
||||
buf.open(QIODevice::WriteOnly);
|
||||
pixmap.toImage().save(&buf, "PNG");
|
||||
buf.close();
|
||||
|
||||
emit ArtLoaded(result_id, buf.data());
|
||||
}
|
74
src/globalsearch/globalsearchservice.h
Normal file
74
src/globalsearch/globalsearchservice.h
Normal file
@ -0,0 +1,74 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2011, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine 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.
|
||||
|
||||
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef GLOBALSEARCHSERVICE_H
|
||||
#define GLOBALSEARCHSERVICE_H
|
||||
|
||||
#include <QDBusArgument>
|
||||
#include <QObject>
|
||||
#include <QSet>
|
||||
|
||||
#include "common.h"
|
||||
#include "searchprovider.h"
|
||||
|
||||
class GlobalSearch;
|
||||
|
||||
class QEventLoop;
|
||||
|
||||
|
||||
class GlobalSearchService : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GlobalSearchService(GlobalSearch* engine, QObject* parent = 0);
|
||||
|
||||
public slots:
|
||||
int StartSearch(const QString& query, bool prefetch_art);
|
||||
void CancelSearch(int id);
|
||||
|
||||
signals:
|
||||
void ResultsAvailable(int id, const GlobalSearchServiceResultList& results);
|
||||
void SearchFinished(int id);
|
||||
void ArtLoaded(int result_id, const QByteArray& image_data);
|
||||
|
||||
private slots:
|
||||
void ResultsAvailableSlot(int id, const SearchProvider::ResultList& results);
|
||||
void SearchFinishedSlot(int id);
|
||||
void ArtLoadedSlot(int id, const QPixmap& pixmap);
|
||||
|
||||
private:
|
||||
struct PendingSearch {
|
||||
bool prefetch_art_;
|
||||
};
|
||||
|
||||
struct RecentResult {
|
||||
GlobalSearchServiceResult result_;
|
||||
};
|
||||
|
||||
GlobalSearch* engine_;
|
||||
|
||||
// GlobalSearch request ids
|
||||
QMap<int, PendingSearch> pending_searches_;
|
||||
|
||||
// Result ids
|
||||
QMap<int, RecentResult> recent_results_;
|
||||
QMap<int, int> prefetching_art_; // LoadArt id -> result id
|
||||
|
||||
int next_result_id_;
|
||||
};
|
||||
|
||||
#endif // GLOBALSEARCHSERVICE_H
|
@ -56,11 +56,11 @@ bool GlobalSearchSortModel::lessThan(const QModelIndex& left, const QModelIndex&
|
||||
|
||||
// Failing that, compare title, artist and album
|
||||
switch (r1.type_) {
|
||||
case SearchProvider::Result::Type_Track:
|
||||
case SearchProvider::Result::Type_Stream:
|
||||
case globalsearch::Type_Track:
|
||||
case globalsearch::Type_Stream:
|
||||
CompareString(title);
|
||||
// fallthrough
|
||||
case SearchProvider::Result::Type_Album:
|
||||
case globalsearch::Type_Album:
|
||||
CompareString(artist);
|
||||
CompareString(album);
|
||||
break;
|
||||
|
@ -616,15 +616,15 @@ GlobalSearchWidget::CombineAction GlobalSearchWidget::CanCombineResults(
|
||||
(QString::compare(r1.metadata_.field(), r2.metadata_.field(), Qt::CaseInsensitive) != 0)
|
||||
|
||||
switch (r1.type_) {
|
||||
case SearchProvider::Result::Type_Track:
|
||||
case globalsearch::Type_Track:
|
||||
if (StringsDiffer(title))
|
||||
return CannotCombine;
|
||||
// fallthrough
|
||||
case SearchProvider::Result::Type_Album:
|
||||
case globalsearch::Type_Album:
|
||||
if (StringsDiffer(album) || StringsDiffer(artist))
|
||||
return CannotCombine;
|
||||
break;
|
||||
case SearchProvider::Result::Type_Stream:
|
||||
case globalsearch::Type_Stream:
|
||||
if (StringsDiffer(url().toString))
|
||||
return CannotCombine;
|
||||
break;
|
||||
|
@ -67,9 +67,9 @@ void GroovesharkSearchProvider::SearchDone(int id, const SongList& songs) {
|
||||
ResultList ret;
|
||||
foreach (const Song& song, songs) {
|
||||
Result result(this);
|
||||
result.type_ = Result::Type_Track;
|
||||
result.type_ = globalsearch::Type_Track;
|
||||
result.metadata_ = song;
|
||||
result.match_quality_ = Result::Quality_AtStart;
|
||||
result.match_quality_ = globalsearch::Quality_AtStart;
|
||||
|
||||
ret << result;
|
||||
}
|
||||
@ -84,8 +84,8 @@ void GroovesharkSearchProvider::AlbumSearchResult(int id, const SongList& songs)
|
||||
ResultList ret;
|
||||
foreach (const Song& s, songs) {
|
||||
Result result(this);
|
||||
result.type_ = Result::Type_Album;
|
||||
result.match_quality_ = Result::Quality_AtStart;
|
||||
result.type_ = globalsearch::Type_Album;
|
||||
result.match_quality_ = globalsearch::Quality_AtStart;
|
||||
result.metadata_ = s;
|
||||
|
||||
ret << result;
|
||||
@ -120,7 +120,7 @@ void GroovesharkSearchProvider::LoadTracksAsync(int id, const Result& result) {
|
||||
SongList ret;
|
||||
|
||||
switch (result.type_) {
|
||||
case Result::Type_Track: {
|
||||
case globalsearch::Type_Track: {
|
||||
ret << result.metadata_;
|
||||
SortSongs(&ret);
|
||||
|
||||
@ -131,7 +131,7 @@ void GroovesharkSearchProvider::LoadTracksAsync(int id, const Result& result) {
|
||||
break;
|
||||
}
|
||||
|
||||
case Result::Type_Album:
|
||||
case globalsearch::Type_Album:
|
||||
FetchAlbum(id, result);
|
||||
break;
|
||||
|
||||
|
@ -74,13 +74,13 @@ SearchProvider::ResultList LibrarySearchProvider::Search(int id, const QString&
|
||||
album_key.prepend(song.artist());
|
||||
}
|
||||
|
||||
Result::MatchQuality quality = MatchQuality(tokens, song.title());
|
||||
globalsearch::MatchQuality quality = MatchQuality(tokens, song.title());
|
||||
|
||||
if (quality != Result::Quality_None) {
|
||||
if (quality != globalsearch::Quality_None) {
|
||||
// If the query matched in the song title then we're interested in this
|
||||
// as an individual song.
|
||||
Result result(this);
|
||||
result.type_ = Result::Type_Track;
|
||||
result.type_ = globalsearch::Type_Track;
|
||||
result.metadata_ = song;
|
||||
result.match_quality_ = quality;
|
||||
ret << result;
|
||||
@ -96,7 +96,7 @@ SearchProvider::ResultList LibrarySearchProvider::Search(int id, const QString&
|
||||
// song title.
|
||||
foreach (const QString& key, albums_with_non_track_matches) {
|
||||
Result result(this);
|
||||
result.type_ = Result::Type_Album;
|
||||
result.type_ = globalsearch::Type_Album;
|
||||
result.metadata_ = albums.value(key);
|
||||
result.album_size_ = albums.count(key);
|
||||
result.match_quality_ =
|
||||
@ -131,12 +131,12 @@ void LibrarySearchProvider::LoadTracksAsync(int id, const Result& result) {
|
||||
SongList ret;
|
||||
|
||||
switch (result.type_) {
|
||||
case Result::Type_Track:
|
||||
case globalsearch::Type_Track:
|
||||
// This is really easy - we just emit the track again.
|
||||
ret << result.metadata_;
|
||||
break;
|
||||
|
||||
case Result::Type_Album: {
|
||||
case globalsearch::Type_Album: {
|
||||
// Find all the songs in this album.
|
||||
LibraryQuery query;
|
||||
query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
|
||||
|
@ -55,16 +55,16 @@ QStringList SearchProvider::TokenizeQuery(const QString& query) {
|
||||
return tokens;
|
||||
}
|
||||
|
||||
SearchProvider::Result::MatchQuality SearchProvider::MatchQuality(
|
||||
globalsearch::MatchQuality SearchProvider::MatchQuality(
|
||||
const QStringList& tokens, const QString& string) {
|
||||
Result::MatchQuality ret = Result::Quality_None;
|
||||
globalsearch::MatchQuality ret = globalsearch::Quality_None;
|
||||
|
||||
foreach (const QString& token, tokens) {
|
||||
const int index = string.indexOf(token, 0, Qt::CaseInsensitive);
|
||||
if (index == 0) {
|
||||
return Result::Quality_AtStart;
|
||||
return globalsearch::Quality_AtStart;
|
||||
} else if (index != -1) {
|
||||
ret = Result::Quality_Middle;
|
||||
ret = globalsearch::Quality_Middle;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <QObject>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "globalsearch/common.h"
|
||||
|
||||
class MimeData;
|
||||
|
||||
@ -39,31 +40,13 @@ public:
|
||||
Result(SearchProvider* provider = 0)
|
||||
: provider_(provider), album_size_(0) {}
|
||||
|
||||
// The order of types here is the order they'll appear in the UI.
|
||||
enum Type {
|
||||
Type_Track = 0,
|
||||
Type_Stream,
|
||||
Type_Album
|
||||
};
|
||||
|
||||
enum MatchQuality {
|
||||
// A token in the search string matched at the beginning of the song
|
||||
// metadata.
|
||||
Quality_AtStart = 0,
|
||||
|
||||
// A token matched somewhere else.
|
||||
Quality_Middle,
|
||||
|
||||
Quality_None
|
||||
};
|
||||
|
||||
// This must be set by the provider using the constructor.
|
||||
SearchProvider* provider_;
|
||||
|
||||
// These must be set explicitly by the provider.
|
||||
Type type_;
|
||||
globalsearch::Type type_;
|
||||
globalsearch::MatchQuality match_quality_;
|
||||
Song metadata_;
|
||||
MatchQuality match_quality_;
|
||||
|
||||
// How many songs in the album - valid only if type == Type_Album.
|
||||
int album_size_;
|
||||
@ -132,7 +115,7 @@ protected:
|
||||
// useful for figuring out whether you got a result because it matched in
|
||||
// the song title or the artist/album name.
|
||||
static QStringList TokenizeQuery(const QString& query);
|
||||
static Result::MatchQuality MatchQuality(const QStringList& tokens, const QString& string);
|
||||
static globalsearch::MatchQuality MatchQuality(const QStringList& tokens, const QString& string);
|
||||
|
||||
// Sorts a list of songs by disc, then by track.
|
||||
static void SortSongs(SongList* list);
|
||||
|
@ -68,12 +68,12 @@ SearchProvider::ResultList SimpleSearchProvider::Search(int id, const QString& q
|
||||
QMutexLocker l(&items_mutex_);
|
||||
foreach (const Item& item, items_) {
|
||||
Result result(this);
|
||||
result.type_ = Result::Type_Stream;
|
||||
result.match_quality_ = Result::Quality_None;
|
||||
result.type_ = globalsearch::Type_Stream;
|
||||
result.match_quality_ = globalsearch::Quality_None;
|
||||
|
||||
foreach (const QString& token, tokens) {
|
||||
if (item.keyword_.startsWith(token, Qt::CaseInsensitive)) {
|
||||
result.match_quality_ = Result::Quality_AtStart;
|
||||
result.match_quality_ = globalsearch::Quality_AtStart;
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -88,17 +88,17 @@ SearchProvider::ResultList SimpleSearchProvider::Search(int id, const QString& q
|
||||
|
||||
if (matched_safe_word)
|
||||
continue;
|
||||
result.match_quality_ = Result::Quality_None;
|
||||
result.match_quality_ = globalsearch::Quality_None;
|
||||
break;
|
||||
}
|
||||
|
||||
result.match_quality_ = qMin(result.match_quality_, Result::Quality_Middle);
|
||||
result.match_quality_ = qMin(result.match_quality_, globalsearch::Quality_Middle);
|
||||
}
|
||||
|
||||
if (result.match_quality_ == Result::Quality_Middle) {
|
||||
if (result.match_quality_ == globalsearch::Quality_Middle) {
|
||||
result.match_quality_ = MatchQuality(tokens, item.metadata_.title());
|
||||
}
|
||||
if (result.match_quality_ != Result::Quality_None) {
|
||||
if (result.match_quality_ != globalsearch::Quality_None) {
|
||||
result.metadata_ = item.metadata_;
|
||||
ret << result;
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ void SpotifySearchProvider::SearchFinishedSlot(const spotify_pb::SearchResponse&
|
||||
const spotify_pb::Track& track = response.result(i);
|
||||
|
||||
Result result(this);
|
||||
result.type_ = Result::Type_Track;
|
||||
result.type_ = globalsearch::Type_Track;
|
||||
SpotifyService::SongFromProtobuf(track, &result.metadata_);
|
||||
result.match_quality_ = MatchQuality(state.tokens_, result.metadata_.title());
|
||||
|
||||
@ -99,7 +99,7 @@ void SpotifySearchProvider::SearchFinishedSlot(const spotify_pb::SearchResponse&
|
||||
const spotify_pb::Album& album = response.album(i);
|
||||
|
||||
Result result(this);
|
||||
result.type_ = Result::Type_Album;
|
||||
result.type_ = globalsearch::Type_Album;
|
||||
SpotifyService::SongFromProtobuf(album.metadata(), &result.metadata_);
|
||||
result.match_quality_ =
|
||||
qMin(MatchQuality(state.tokens_, result.metadata_.album()),
|
||||
@ -147,14 +147,14 @@ void SpotifySearchProvider::ArtLoadedSlot(const QString& id, const QImage& image
|
||||
|
||||
void SpotifySearchProvider::LoadTracksAsync(int id, const Result& result) {
|
||||
switch (result.type_) {
|
||||
case Result::Type_Track: {
|
||||
case globalsearch::Type_Track: {
|
||||
SongMimeData* mime_data = new SongMimeData;
|
||||
mime_data->songs = SongList() << result.metadata_;
|
||||
emit TracksLoaded(id, mime_data);
|
||||
break;
|
||||
}
|
||||
|
||||
case Result::Type_Album: {
|
||||
case globalsearch::Type_Album: {
|
||||
SpotifyServer* s = server();
|
||||
if (!s) {
|
||||
emit TracksLoaded(id, NULL);
|
||||
|
@ -58,11 +58,11 @@ QSize TooltipResultWidget::CalculateSizeHint() const {
|
||||
bold_metrics_.width(TitleText()) + kBorder);
|
||||
|
||||
switch (result_.type_) {
|
||||
case SearchProvider::Result::Type_Track:
|
||||
case SearchProvider::Result::Type_Stream:
|
||||
case globalsearch::Type_Track:
|
||||
case globalsearch::Type_Stream:
|
||||
break;
|
||||
|
||||
case SearchProvider::Result::Type_Album:
|
||||
case globalsearch::Type_Album:
|
||||
if (result_.album_songs_.isEmpty())
|
||||
break;
|
||||
|
||||
@ -119,11 +119,11 @@ void TooltipResultWidget::paintEvent(QPaintEvent*) {
|
||||
y += kLineHeight;
|
||||
|
||||
switch (result_.type_) {
|
||||
case SearchProvider::Result::Type_Track:
|
||||
case SearchProvider::Result::Type_Stream:
|
||||
case globalsearch::Type_Track:
|
||||
case globalsearch::Type_Stream:
|
||||
break;
|
||||
|
||||
case SearchProvider::Result::Type_Album:
|
||||
case globalsearch::Type_Album:
|
||||
if (result_.album_songs_.isEmpty())
|
||||
break;
|
||||
|
||||
|
@ -93,6 +93,7 @@ using boost::scoped_ptr;
|
||||
#include "core/mpris.h"
|
||||
#include "core/mpris2.h"
|
||||
#include "dbus/metatypes.h"
|
||||
#include "globalsearch/globalsearchservice.h"
|
||||
#include <QDBusArgument>
|
||||
#include <QDBusConnection>
|
||||
#include <QImage>
|
||||
@ -220,6 +221,7 @@ int main(int argc, char *argv[]) {
|
||||
qRegisterMetaTypeStreamOperators<QMap<int, int> >("ColumnAlignmentMap");
|
||||
qRegisterMetaType<QNetworkCookie>("QNetworkCookie");
|
||||
qRegisterMetaType<QList<QNetworkCookie> >("QList<QNetworkCookie>");
|
||||
qRegisterMetaType<SearchProvider::Result>("SearchProvider::Result");
|
||||
qRegisterMetaType<SearchProvider::ResultList>("SearchProvider::ResultList");
|
||||
|
||||
qRegisterMetaType<GstBuffer*>("GstBuffer*");
|
||||
@ -438,6 +440,8 @@ int main(int argc, char *argv[]) {
|
||||
QObject::connect(&playlists, SIGNAL(CurrentSongChanged(Song)), &art_loader, SLOT(LoadArt(Song)));
|
||||
QObject::connect(&art_loader, SIGNAL(ThumbnailLoaded(Song, QString, QImage)),
|
||||
&osd, SLOT(CoverArtPathReady(Song, QString)));
|
||||
|
||||
GlobalSearchService global_search_service(&global_search);
|
||||
#endif
|
||||
|
||||
// Window
|
||||
|
Loading…
Reference in New Issue
Block a user