The external tagreader mostly works now:

* Make TagReaderClient a singleton until it's easier to pass dependencies around
 * Add a WaitForSignal() that uses a local event loop to wait for a signal to be emitted
 * Add a WaitForFinished() to _MessageReplyBase that blocks using a semaphore
 * Add blocking versions of all TagReaderClient methods
 * Use the TagReaderClient everywhere that Song::InitFromFile and friends were used before
This commit is contained in:
David Sansome 2012-01-06 21:27:02 +00:00
parent 9041117867
commit 9be641ee87
31 changed files with 293 additions and 89 deletions

View File

@ -17,6 +17,7 @@
#include "tagreaderworker.h" #include "tagreaderworker.h"
#include "core/encoding.h" #include "core/encoding.h"
#include "core/logging.h"
#include <QCoreApplication> #include <QCoreApplication>
#include <QLocalSocket> #include <QLocalSocket>
@ -35,6 +36,9 @@ int main(int argc, char** argv) {
return 1; return 1;
} }
logging::Init();
qLog(Info) << "TagReader worker connecting to" << args[1];
// Detect technically invalid usage of non-ASCII in ID3v1 tags. // Detect technically invalid usage of non-ASCII in ID3v1 tags.
UniversalEncodingHandler handler; UniversalEncodingHandler handler;
TagLib::ID3v1::Tag::setStringHandler(&handler); TagLib::ID3v1::Tag::setStringHandler(&handler);

View File

@ -18,6 +18,7 @@
#include "fmpsparser.h" #include "fmpsparser.h"
#include "tagreaderworker.h" #include "tagreaderworker.h"
#include "core/encoding.h" #include "core/encoding.h"
#include "core/logging.h"
#include "core/timeconstants.h" #include "core/timeconstants.h"
#include <QDateTime> #include <QDateTime>
@ -90,6 +91,7 @@ TagLib::String QStringToTaglibString(const QString& s) {
TagReaderWorker::TagReaderWorker(QIODevice* socket, QObject* parent) TagReaderWorker::TagReaderWorker(QIODevice* socket, QObject* parent)
: AbstractMessageHandler<pb::tagreader::Message>(socket, parent), : AbstractMessageHandler<pb::tagreader::Message>(socket, parent),
factory_(new TagLibFileRefFactory),
kEmbeddedCover("(embedded)") kEmbeddedCover("(embedded)")
{ {
} }
@ -122,6 +124,8 @@ void TagReaderWorker::ReadFile(const QString& filename,
const QByteArray url(QUrl::fromLocalFile(filename).toEncoded()); const QByteArray url(QUrl::fromLocalFile(filename).toEncoded());
const QFileInfo info(filename); const QFileInfo info(filename);
qLog(Debug) << "Reading tags from" << filename;
song->set_basefilename(DataCommaSizeFromQString(info.fileName())); song->set_basefilename(DataCommaSizeFromQString(info.fileName()));
song->set_url(url.constData(), url.size()); song->set_url(url.constData(), url.size());
song->set_filesize(info.size()); song->set_filesize(info.size());
@ -402,6 +406,8 @@ bool TagReaderWorker::SaveFile(const QString& filename,
if (filename.isNull()) if (filename.isNull())
return false; return false;
qLog(Debug) << "Saving tags to" << filename;
scoped_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename)); scoped_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
if (!fileref || fileref->isNull()) // The file probably doesn't exist if (!fileref || fileref->isNull()) // The file probably doesn't exist
@ -475,6 +481,8 @@ void TagReaderWorker::SetTextFrame(const char* id, const std::string& value,
} }
bool TagReaderWorker::IsMediaFile(const QString& filename) const { bool TagReaderWorker::IsMediaFile(const QString& filename) const {
qLog(Debug) << "Checking for valid file" << filename;
scoped_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename)); scoped_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
return !fileref->isNull() && fileref->tag(); return !fileref->isNull() && fileref->tag();
} }
@ -483,6 +491,8 @@ QByteArray TagReaderWorker::LoadEmbeddedArt(const QString& filename) const {
if (filename.isEmpty()) if (filename.isEmpty())
return QByteArray(); return QByteArray();
qLog(Debug) << "Loading art from" << filename;
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
TagLib::FileRef ref(filename.toStdWString().c_str()); TagLib::FileRef ref(filename.toStdWString().c_str());
#else #else

View File

@ -8,6 +8,7 @@ set(SOURCES
core/encoding.cpp core/encoding.cpp
core/logging.cpp core/logging.cpp
core/messagehandler.cpp core/messagehandler.cpp
core/waitforsignal.cpp
core/workerpool.cpp core/workerpool.cpp
) )

View File

@ -95,13 +95,21 @@ void _MessageHandlerBase::WriteMessage(const QByteArray& data) {
_MessageReplyBase::_MessageReplyBase(int id, QObject* parent) _MessageReplyBase::_MessageReplyBase(int id, QObject* parent)
: QObject(parent), : QObject(parent),
id_(id), id_(id),
finished_(false) finished_(false),
success_(false)
{ {
} }
bool _MessageReplyBase::WaitForFinished() {
semaphore_.acquire();
return success_;
}
void _MessageReplyBase::Abort() { void _MessageReplyBase::Abort() {
Q_ASSERT(!finished_); Q_ASSERT(!finished_);
finished_ = true; finished_ = true;
success_ = false;
emit Finished(false); emit Finished(success_);
semaphore_.release();
} }

View File

@ -27,6 +27,7 @@
#include <QMutex> #include <QMutex>
#include <QMutexLocker> #include <QMutexLocker>
#include <QObject> #include <QObject>
#include <QSemaphore>
#include <QThread> #include <QThread>
class QAbstractSocket; class QAbstractSocket;
@ -46,10 +47,16 @@ class _MessageReplyBase : public QObject {
Q_OBJECT Q_OBJECT
public: public:
_MessageReplyBase(int id, QObject* parent); _MessageReplyBase(int id, QObject* parent = 0);
int id() const { return id_; } int id() const { return id_; }
bool is_finished() const { return finished_; } bool is_finished() const { return finished_; }
bool is_successful() const { return success_; }
// Waits for the reply to finish by waiting on a semaphore. Never call this
// from the MessageHandler's thread or it will block forever.
// Returns true if the call was successful.
bool WaitForFinished();
void Abort(); void Abort();
@ -59,6 +66,9 @@ signals:
protected: protected:
int id_; int id_;
bool finished_; bool finished_;
bool success_;
QSemaphore semaphore_;
}; };
@ -67,7 +77,7 @@ protected:
template <typename MessageType> template <typename MessageType>
class MessageReply : public _MessageReplyBase { class MessageReply : public _MessageReplyBase {
public: public:
MessageReply(int id, QObject* parent); MessageReply(int id, QObject* parent = 0);
const MessageType& message() const { return message_; } const MessageType& message() const { return message_; }
@ -219,7 +229,7 @@ AbstractMessageHandler<MessageType>::NewReply(
QMutexLocker l(&mutex_); QMutexLocker l(&mutex_);
const int id = next_id_ ++; const int id = next_id_ ++;
reply = new ReplyType(id, this); reply = new ReplyType(id);
pending_replies_[id] = reply; pending_replies_[id] = reply;
} }
@ -260,8 +270,10 @@ void MessageReply<MessageType>::SetReply(const MessageType& message) {
message_.MergeFrom(message); message_.MergeFrom(message);
finished_ = true; finished_ = true;
success_ = true;
emit Finished(true); emit Finished(success_);
semaphore_.release();
} }
#endif // MESSAGEHANDLER_H #endif // MESSAGEHANDLER_H

View File

@ -0,0 +1,26 @@
/* 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 "waitforsignal.h"
#include <QEventLoop>
void WaitForSignal(QObject* sender, const char* signal) {
QEventLoop loop;
QObject::connect(sender, signal, &loop, SLOT(quit()));
loop.exec();
}

View File

@ -0,0 +1,25 @@
/* 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 WAITFORSIGNAL_H
#define WAITFORSIGNAL_H
class QObject;
void WaitForSignal(QObject* sender, const char* signal);
#endif // WAITFORSIGNAL_H

View File

@ -21,11 +21,14 @@
#include <QCoreApplication> #include <QCoreApplication>
#include <QFile> #include <QFile>
#include <QLocalServer> #include <QLocalServer>
#include <QLocalSocket>
#include <QObject> #include <QObject>
#include <QProcess> #include <QProcess>
#include <QThread> #include <QThread>
#include "core/closure.h" #include "core/closure.h"
#include "core/logging.h"
#include "core/waitforsignal.h"
// Base class containing signals and slots - required because moc doesn't do // Base class containing signals and slots - required because moc doesn't do
@ -46,6 +49,7 @@ signals:
void WorkerConnected(); void WorkerConnected();
protected slots: protected slots:
virtual void DoStart() {}
virtual void NewConnection() {} virtual void NewConnection() {}
virtual void ProcessError(QProcess::ProcessError) {} virtual void ProcessError(QProcess::ProcessError) {}
}; };
@ -83,6 +87,7 @@ public:
HandlerType* NextHandler(); HandlerType* NextHandler();
protected: protected:
void DoStart();
void NewConnection(); void NewConnection();
void ProcessError(QProcess::ProcessError error); void ProcessError(QProcess::ProcessError error);
@ -99,15 +104,17 @@ private:
template <typename T> template <typename T>
Worker* FindWorker(T Worker::*member, T value) { Worker* FindWorker(T Worker::*member, T value) {
foreach (Worker& worker, workers_) { for (typename QList<Worker>::iterator it = workers_.begin() ;
if (worker.*member == value) { it != workers_.end() ; ++it) {
return &worker; if ((*it).*member == value) {
return &(*it);
} }
} }
return NULL; return NULL;
} }
void DeleteQObjectPointerLater(QObject** p) { template <typename T>
void DeleteQObjectPointerLater(T** p) {
if (*p) { if (*p) {
(*p)->deleteLater(); (*p)->deleteLater();
*p = NULL; *p = NULL;
@ -157,7 +164,13 @@ void WorkerPool<HandlerType>::SetExecutableName(const QString& executable_name)
template <typename HandlerType> template <typename HandlerType>
void WorkerPool<HandlerType>::Start() { void WorkerPool<HandlerType>::Start() {
metaObject()->invokeMethod(this, "DoStart");
}
template <typename HandlerType>
void WorkerPool<HandlerType>::DoStart() {
Q_ASSERT(workers_.isEmpty()); Q_ASSERT(workers_.isEmpty());
Q_ASSERT(!executable_name_.isEmpty());
// Find the executable if we can, default to searching $PATH // Find the executable if we can, default to searching $PATH
executable_path_ = executable_name_; executable_path_ = executable_name_;
@ -200,7 +213,7 @@ void WorkerPool<HandlerType>::StartOneWorker(Worker* worker) {
// Create a server, find an unused name and start listening // Create a server, find an unused name and start listening
forever { forever {
const int unique_number = qrand() ^ reinterpret_cast<int>(this); const int unique_number = qrand() ^ ((int)(quint64(this) & 0xFFFFFFFF));
const QString name = QString("%1_%2").arg(local_server_name_).arg(unique_number); const QString name = QString("%1_%2").arg(local_server_name_).arg(unique_number);
if (worker->local_server_->listen(name)) { if (worker->local_server_->listen(name)) {
@ -208,10 +221,13 @@ void WorkerPool<HandlerType>::StartOneWorker(Worker* worker) {
} }
} }
qLog(Debug) << "Starting worker" << executable_path_
<< worker->local_server_->fullServerName();
// Start the process // Start the process
worker->process_->setProcessChannelMode(QProcess::ForwardedChannels); worker->process_->setProcessChannelMode(QProcess::ForwardedChannels);
worker->process_->start(executable_path_, worker->process_->start(executable_path_,
QStringList() << worker->socket_server_->fullServerName()); QStringList() << worker->local_server_->fullServerName());
} }
template <typename HandlerType> template <typename HandlerType>
@ -223,11 +239,14 @@ void WorkerPool<HandlerType>::NewConnection() {
if (!worker) if (!worker)
return; return;
qLog(Debug) << "Worker connected to" << server->fullServerName();
// Accept the connection. // Accept the connection.
QLocalSocket* socket = server->nextPendingConnection(); QLocalSocket* socket = server->nextPendingConnection();
// We only ever accept one connection per worker, so destroy the server now. // We only ever accept one connection per worker, so destroy the server now.
server->deleteLater(); socket->setParent(this);
worker->local_server_->deleteLater();
worker->local_server_ = NULL; worker->local_server_ = NULL;
// Create the handler. // Create the handler.
@ -250,11 +269,13 @@ void WorkerPool<HandlerType>::ProcessError(QProcess::ProcessError error) {
// Failed to start errors are bad - it usually means the worker isn't // Failed to start errors are bad - it usually means the worker isn't
// installed. Don't restart the process, but tell our owner, who will // installed. Don't restart the process, but tell our owner, who will
// probably want to do something fatal. // probably want to do something fatal.
qLog(Error) << "Worker failed to start";
emit WorkerFailedToStart(); emit WorkerFailedToStart();
break; break;
default: default:
// On any other error we just restart the process. // On any other error we just restart the process.
qLog(Debug) << "Worker failed with error" << error << "- restarting";
StartOneWorker(worker); StartOneWorker(worker);
break; break;
} }
@ -262,15 +283,19 @@ void WorkerPool<HandlerType>::ProcessError(QProcess::ProcessError error) {
template <typename HandlerType> template <typename HandlerType>
HandlerType* WorkerPool<HandlerType>::NextHandler() { HandlerType* WorkerPool<HandlerType>::NextHandler() {
for (int i=0 ; i<workers_.count() ; ++i) { forever {
const int worker_index = (next_worker_ + i) % workers_.count(); for (int i=0 ; i<workers_.count() ; ++i) {
const int worker_index = (next_worker_ + i) % workers_.count();
if (workers_[worker_index].handler_) { if (workers_[worker_index].handler_) {
next_worker_ = (worker_index + 1) % workers_.count(); next_worker_ = (worker_index + 1) % workers_.count();
return workers_[worker_index].handler_; return workers_[worker_index].handler_;
}
} }
// No workers were connected, wait for one.
WaitForSignal(this, SIGNAL(WorkerConnected()));
} }
return NULL;
} }
#endif // WORKERPOOL_H #endif // WORKERPOOL_H

View File

@ -702,6 +702,9 @@ optional_source(HAVE_SPOTIFY
internet/spotifyserver.h internet/spotifyserver.h
internet/spotifyservice.h internet/spotifyservice.h
internet/spotifysettingspage.h internet/spotifysettingspage.h
INCLUDE_DIRECTORIES
${CMAKE_SOURCE_DIR}/ext/libclementine-spotifyblob
${CMAKE_BINARY_DIR}/ext/libclementine-spotifyblob
) )
optional_source(HAVE_QCA INCLUDE_DIRECTORIES ${QCA_INCLUDE_DIRS}) optional_source(HAVE_QCA INCLUDE_DIRECTORIES ${QCA_INCLUDE_DIRS})
@ -961,6 +964,7 @@ target_link_libraries(clementine_lib
libclementine-tagreader libclementine-tagreader
echoprint echoprint
sha2 sha2
${TAGLIB_LIBRARIES}
${ECHONEST_LIBRARIES} ${ECHONEST_LIBRARIES}
${GOBJECT_LIBRARIES} ${GOBJECT_LIBRARIES}
${GLIB_LIBRARIES} ${GLIB_LIBRARIES}

View File

@ -19,6 +19,7 @@
#include "organise.h" #include "organise.h"
#include "taskmanager.h" #include "taskmanager.h"
#include "core/logging.h" #include "core/logging.h"
#include "core/tagreaderclient.h"
#include <QDir> #include <QDir>
#include <QFileInfo> #include <QFileInfo>
@ -137,7 +138,7 @@ void Organise::ProcessSomeFiles() {
// Read metadata from the file // Read metadata from the file
Song song; Song song;
song.InitFromFile(task.filename_, -1); TagReaderClient::Instance()->ReadFileBlocking(task.filename_, &song);
if (!song.is_valid()) if (!song.is_valid())
continue; continue;

View File

@ -208,8 +208,6 @@ class Song {
// Setters // Setters
bool IsEditable() const; bool IsEditable() const;
bool Save() const;
QFuture<bool> BackgroundSave() const;
void set_id(int id); void set_id(int id);
void set_valid(bool v); void set_valid(bool v);

View File

@ -19,13 +19,14 @@
#include "songloader.h" #include "songloader.h"
#include "core/logging.h" #include "core/logging.h"
#include "core/song.h" #include "core/song.h"
#include "core/tagreaderclient.h"
#include "core/timeconstants.h" #include "core/timeconstants.h"
#include "internet/fixlastfm.h"
#include "library/librarybackend.h" #include "library/librarybackend.h"
#include "library/sqlrow.h" #include "library/sqlrow.h"
#include "playlistparsers/parserbase.h" #include "playlistparsers/parserbase.h"
#include "playlistparsers/cueparser.h" #include "playlistparsers/cueparser.h"
#include "playlistparsers/playlistparser.h" #include "playlistparsers/playlistparser.h"
#include "internet/fixlastfm.h"
#include <QBuffer> #include <QBuffer>
#include <QDirIterator> #include <QDirIterator>
@ -286,7 +287,7 @@ SongLoader::Result SongLoader::LoadLocal(const QString& filename, bool block,
// it's a normal media file // it's a normal media file
} else { } else {
Song song; Song song;
song.InitFromFile(filename, -1); TagReaderClient::Instance()->ReadFileBlocking(filename, &song);
song_list << song; song_list << song;
@ -316,7 +317,7 @@ void SongLoader::EffectiveSongsLoad() {
} else { } else {
// it's a normal media file // it's a normal media file
QString filename = song.url().toLocalFile(); QString filename = song.url().toLocalFile();
song.InitFromFile(filename, -1); TagReaderClient::Instance()->ReadFileBlocking(filename, &song);
} }
} }
} }

View File

@ -24,11 +24,15 @@
const char* TagReaderClient::kWorkerExecutableName = "clementine-tagreader"; const char* TagReaderClient::kWorkerExecutableName = "clementine-tagreader";
TagReaderClient* TagReaderClient::sInstance = NULL;
TagReaderClient::TagReaderClient(QObject* parent) TagReaderClient::TagReaderClient(QObject* parent)
: QObject(parent), : QObject(parent),
worker_pool_(new WorkerPool<HandlerType>(this)) worker_pool_(new WorkerPool<HandlerType>(this))
{ {
sInstance = this;
worker_pool_->SetExecutableName(kWorkerExecutableName);
} }
void TagReaderClient::Start() { void TagReaderClient::Start() {
@ -41,7 +45,7 @@ TagReaderReply* TagReaderClient::ReadFile(const QString& filename) {
req->set_filename(DataCommaSizeFromQString(filename)); req->set_filename(DataCommaSizeFromQString(filename));
return handler_->SendMessageWithReply(&message); return worker_pool_->NextHandler()->SendMessageWithReply(&message);
} }
TagReaderReply* TagReaderClient::SaveFile(const QString& filename, const Song& metadata) { TagReaderReply* TagReaderClient::SaveFile(const QString& filename, const Song& metadata) {
@ -51,7 +55,7 @@ TagReaderReply* TagReaderClient::SaveFile(const QString& filename, const Song& m
req->set_filename(DataCommaSizeFromQString(filename)); req->set_filename(DataCommaSizeFromQString(filename));
metadata.ToProtobuf(req->mutable_metadata()); metadata.ToProtobuf(req->mutable_metadata());
return handler_->SendMessageWithReply(&message); return worker_pool_->NextHandler()->SendMessageWithReply(&message);
} }
TagReaderReply* TagReaderClient::IsMediaFile(const QString& filename) { TagReaderReply* TagReaderClient::IsMediaFile(const QString& filename) {
@ -60,7 +64,7 @@ TagReaderReply* TagReaderClient::IsMediaFile(const QString& filename) {
req->set_filename(DataCommaSizeFromQString(filename)); req->set_filename(DataCommaSizeFromQString(filename));
return handler_->SendMessageWithReply(&message); return worker_pool_->NextHandler()->SendMessageWithReply(&message);
} }
TagReaderReply* TagReaderClient::LoadEmbeddedArt(const QString& filename) { TagReaderReply* TagReaderClient::LoadEmbeddedArt(const QString& filename) {
@ -69,5 +73,59 @@ TagReaderReply* TagReaderClient::LoadEmbeddedArt(const QString& filename) {
req->set_filename(DataCommaSizeFromQString(filename)); req->set_filename(DataCommaSizeFromQString(filename));
return handler_->SendMessageWithReply(&message); return worker_pool_->NextHandler()->SendMessageWithReply(&message);
}
void TagReaderClient::ReadFileBlocking(const QString& filename, Song* song) {
Q_ASSERT(QThread::currentThread() != thread());
TagReaderReply* reply = ReadFile(filename);
if (reply->WaitForFinished()) {
song->InitFromProtobuf(reply->message().read_file_response().metadata());
}
reply->deleteLater();
}
bool TagReaderClient::SaveFileBlocking(const QString& filename, const Song& metadata) {
Q_ASSERT(QThread::currentThread() != thread());
bool ret = false;
TagReaderReply* reply = SaveFile(filename, metadata);
if (reply->WaitForFinished()) {
ret = reply->message().save_file_response().success();
}
reply->deleteLater();
return ret;
}
bool TagReaderClient::IsMediaFileBlocking(const QString& filename) {
Q_ASSERT(QThread::currentThread() != thread());
bool ret = false;
TagReaderReply* reply = IsMediaFile(filename);
if (reply->WaitForFinished()) {
ret = reply->message().is_media_file_response().success();
}
reply->deleteLater();
return ret;
}
QImage TagReaderClient::LoadEmbeddedArtBlocking(const QString& filename) {
Q_ASSERT(QThread::currentThread() != thread());
QImage ret;
TagReaderReply* reply = LoadEmbeddedArt(filename);
if (reply->WaitForFinished()) {
const std::string& data_str =
reply->message().load_embedded_art_response().data();
ret.loadFromData(QByteArray(data_str.data(), data_str.size()));
}
reply->deleteLater();
return ret;
} }

View File

@ -46,10 +46,20 @@ public:
ReplyType* IsMediaFile(const QString& filename); ReplyType* IsMediaFile(const QString& filename);
ReplyType* LoadEmbeddedArt(const QString& filename); ReplyType* LoadEmbeddedArt(const QString& filename);
private: // Convenience functions that call the above functions and wait for a
void SendOrQueue(const pb::tagreader::Message& message); // response. These block the calling thread with a semaphore, and must NOT
// be called from the TagReaderClient's thread.
void ReadFileBlocking(const QString& filename, Song* song);
bool SaveFileBlocking(const QString& filename, const Song& metadata);
bool IsMediaFileBlocking(const QString& filename);
QImage LoadEmbeddedArtBlocking(const QString& filename);
// TODO: Make this not a singleton
static TagReaderClient* Instance() { return sInstance; }
private: private:
static TagReaderClient* sInstance;
WorkerPool<HandlerType>* worker_pool_; WorkerPool<HandlerType>* worker_pool_;
QList<pb::tagreader::Message> message_queue_; QList<pb::tagreader::Message> message_queue_;
}; };

View File

@ -19,6 +19,7 @@
#include "config.h" #include "config.h"
#include "core/logging.h" #include "core/logging.h"
#include "core/network.h" #include "core/network.h"
#include "core/tagreaderclient.h"
#include "core/utilities.h" #include "core/utilities.h"
#include "internet/internetmodel.h" #include "internet/internetmodel.h"
@ -137,7 +138,9 @@ AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(
return TryLoadResult(false, true, default_); return TryLoadResult(false, true, default_);
if (filename == Song::kEmbeddedCover && !task.song_filename.isEmpty()) { if (filename == Song::kEmbeddedCover && !task.song_filename.isEmpty()) {
QImage taglib_image = Song::LoadEmbeddedArt(task.song_filename); const QImage taglib_image =
TagReaderClient::Instance()->LoadEmbeddedArtBlocking(task.song_filename);
if (!taglib_image.isNull()) if (!taglib_image.isNull())
return TryLoadResult(false, true, ScaleAndPad(taglib_image)); return TryLoadResult(false, true, ScaleAndPad(taglib_image));
} }
@ -263,7 +266,8 @@ QPixmap AlbumCoverLoader::TryLoadPixmap(const QString& automatic,
ret.load(manual); ret.load(manual);
if (ret.isNull()) { if (ret.isNull()) {
if (automatic == Song::kEmbeddedCover && !filename.isNull()) if (automatic == Song::kEmbeddedCover && !filename.isNull())
ret = QPixmap::fromImage(Song::LoadEmbeddedArt(filename)); ret = QPixmap::fromImage(
TagReaderClient::Instance()->LoadEmbeddedArtBlocking(filename));
else if (!automatic.isEmpty()) else if (!automatic.isEmpty())
ret.load(automatic); ret.load(automatic);
} }

View File

@ -15,13 +15,13 @@
along with Clementine. If not, see <http://www.gnu.org/licenses/>. along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "spotifymessagehandler.h"
#include "spotifysearchprovider.h" #include "spotifysearchprovider.h"
#include "core/logging.h" #include "core/logging.h"
#include "internet/internetmodel.h" #include "internet/internetmodel.h"
#include "internet/spotifyserver.h" #include "internet/spotifyserver.h"
#include "internet/spotifyservice.h" #include "internet/spotifyservice.h"
#include "playlist/songmimedata.h" #include "playlist/songmimedata.h"
#include "spotifyblob/common/spotifymessagehandler.h"
SpotifySearchProvider::SpotifySearchProvider(QObject* parent) SpotifySearchProvider::SpotifySearchProvider(QObject* parent)
: SearchProvider(parent), : SearchProvider(parent),

View File

@ -19,8 +19,8 @@
#include "core/closure.h" #include "core/closure.h"
#include "core/logging.h" #include "core/logging.h"
#include "spotifyblob/common/spotifymessages.pb.h" #include "spotifymessages.pb.h"
#include "spotifyblob/common/spotifymessagehandler.h" #include "spotifymessagehandler.h"
#include <QTcpServer> #include <QTcpServer>
#include <QTcpSocket> #include <QTcpSocket>

View File

@ -1,6 +1,8 @@
#include "blobversion.h"
#include "config.h" #include "config.h"
#include "internetmodel.h" #include "internetmodel.h"
#include "spotifyblobdownloader.h" #include "spotifyblobdownloader.h"
#include "spotifymessagehandler.h"
#include "spotifyserver.h" #include "spotifyserver.h"
#include "spotifyservice.h" #include "spotifyservice.h"
#include "spotifysearchplaylisttype.h" #include "spotifysearchplaylisttype.h"
@ -15,8 +17,6 @@
#include "playlist/playlist.h" #include "playlist/playlist.h"
#include "playlist/playlistcontainer.h" #include "playlist/playlistcontainer.h"
#include "playlist/playlistmanager.h" #include "playlist/playlistmanager.h"
#include "spotifyblob/common/blobversion.h"
#include "spotifyblob/common/spotifymessagehandler.h"
#include "widgets/didyoumean.h" #include "widgets/didyoumean.h"
#include "ui/iconloader.h" #include "ui/iconloader.h"

View File

@ -34,7 +34,7 @@ class Library : public QObject {
Q_OBJECT Q_OBJECT
public: public:
Library(BackgroundThread<Database>* db_thread, TaskManager* task_manager_, Library(BackgroundThread<Database>* db_thread, TaskManager* task_manager,
QObject* parent); QObject* parent);
static const char* kSongsTable; static const char* kSongsTable;

View File

@ -16,6 +16,7 @@
*/ */
#include "libraryplaylistitem.h" #include "libraryplaylistitem.h"
#include "core/tagreaderclient.h"
#include <QSettings> #include <QSettings>
@ -36,7 +37,7 @@ QUrl LibraryPlaylistItem::Url() const {
} }
void LibraryPlaylistItem::Reload() { void LibraryPlaylistItem::Reload() {
song_.InitFromFile(song_.url().toLocalFile(), song_.directory_id()); TagReaderClient::Instance()->ReadFileBlocking(song_.url().toLocalFile(), &song_);
} }
bool LibraryPlaylistItem::InitFromQuery(const SqlRow& query) { bool LibraryPlaylistItem::InitFromQuery(const SqlRow& query) {

View File

@ -18,6 +18,7 @@
#include "librarywatcher.h" #include "librarywatcher.h"
#include "librarybackend.h" #include "librarybackend.h"
#include "core/logging.h" #include "core/logging.h"
#include "core/tagreaderclient.h"
#include "core/taskmanager.h" #include "core/taskmanager.h"
#include "playlistparsers/cueparser.h" #include "playlistparsers/cueparser.h"
@ -451,7 +452,8 @@ void LibraryWatcher::UpdateNonCueAssociatedSong(const QString& file, const Song&
} }
Song song_on_disk; Song song_on_disk;
song_on_disk.InitFromFile(file, t->dir()); song_on_disk.set_directory_id(t->dir());
TagReaderClient::Instance()->ReadFileBlocking(file, &song_on_disk);
if(song_on_disk.is_valid()) { if(song_on_disk.is_valid()) {
PreserveUserSetData(file, image, matching_song, &song_on_disk, t); PreserveUserSetData(file, image, matching_song, &song_on_disk, t);
@ -476,8 +478,10 @@ SongList LibraryWatcher::ScanNewFile(const QString& file, const QString& path,
// media files. Playlist parser for CUEs considers every entry in sheet // media files. Playlist parser for CUEs considers every entry in sheet
// valid and we don't want invalid media getting into library! // valid and we don't want invalid media getting into library!
foreach(const Song& cue_song, cue_parser_->Load(&cue, matching_cue, path)) { foreach(const Song& cue_song, cue_parser_->Load(&cue, matching_cue, path)) {
if(cue_song.url().toLocalFile() == file && cue_song.HasProperMediaFile()) { if (cue_song.url().toLocalFile() == file) {
song_list << cue_song; if (TagReaderClient::Instance()->IsMediaFileBlocking(file)) {
song_list << cue_song;
}
} }
} }
@ -488,7 +492,7 @@ SongList LibraryWatcher::ScanNewFile(const QString& file, const QString& path,
// it's a normal media file // it's a normal media file
} else { } else {
Song song; Song song;
song.InitFromFile(file, -1); TagReaderClient::Instance()->ReadFileBlocking(file, &song);
if (song.is_valid()) { if (song.is_valid()) {
song_list << song; song_list << song;

View File

@ -369,6 +369,14 @@ int main(int argc, char *argv[]) {
CoverProviders cover_providers; CoverProviders cover_providers;
cover_providers.AddProvider(new AmazonCoverProvider); cover_providers.AddProvider(new AmazonCoverProvider);
// Create the tag loader on another thread.
TagReaderClient tag_reader_client;
QThread tag_reader_thread;
tag_reader_thread.start();
tag_reader_client.moveToThread(&tag_reader_thread);
tag_reader_client.Start();
// Create some key objects // Create some key objects
scoped_ptr<BackgroundThread<Database> > database( scoped_ptr<BackgroundThread<Database> > database(
new BackgroundThreadImplementation<Database, Database>(NULL)); new BackgroundThreadImplementation<Database, Database>(NULL));
@ -408,9 +416,6 @@ int main(int argc, char *argv[]) {
GlobalSearchService global_search_service(&global_search); GlobalSearchService global_search_service(&global_search);
#endif #endif
// Tag reader client
TagReaderClient tag_reader_client;
// Window // Window
MainWindow w( MainWindow w(
database.get(), database.get(),
@ -422,8 +427,7 @@ int main(int argc, char *argv[]) {
&osd, &osd,
&art_loader, &art_loader,
&cover_providers, &cover_providers,
&global_search, &global_search);
&tag_reader_client);
#ifdef HAVE_DBUS #ifdef HAVE_DBUS
QObject::connect(&mpris, SIGNAL(RaiseMainWindow()), &w, SLOT(Raise())); QObject::connect(&mpris, SIGNAL(RaiseMainWindow()), &w, SLOT(Raise()));
#endif #endif

View File

@ -25,8 +25,10 @@
#include "songloaderinserter.h" #include "songloaderinserter.h"
#include "songmimedata.h" #include "songmimedata.h"
#include "songplaylistitem.h" #include "songplaylistitem.h"
#include "core/closure.h"
#include "core/logging.h" #include "core/logging.h"
#include "core/modelfuturewatcher.h" #include "core/modelfuturewatcher.h"
#include "core/tagreaderclient.h"
#include "core/timeconstants.h" #include "core/timeconstants.h"
#include "internet/jamendoplaylistitem.h" #include "internet/jamendoplaylistitem.h"
#include "internet/jamendoservice.h" #include "internet/jamendoservice.h"
@ -321,24 +323,25 @@ bool Playlist::setData(const QModelIndex &index, const QVariant &value, int) {
library_->AddOrUpdateSongs(SongList() << song); library_->AddOrUpdateSongs(SongList() << song);
emit EditingFinished(index); emit EditingFinished(index);
} else { } else {
QFuture<bool> future = song.BackgroundSave(); TagReaderReply* reply = TagReaderClient::Instance()->SaveFile(
ModelFutureWatcher<bool>* watcher = new ModelFutureWatcher<bool>(index, this); song.url().toLocalFile(), song);
watcher->setFuture(future);
connect(watcher, SIGNAL(finished()), SLOT(SongSaveComplete())); NewClosure(reply, SIGNAL(Finished(bool)),
this, SLOT(SongSaveComplete(TagReaderReply*,QPersistentModelIndex)),
reply, QPersistentModelIndex(index));
} }
return true; return true;
} }
void Playlist::SongSaveComplete() { void Playlist::SongSaveComplete(TagReaderReply* reply, const QPersistentModelIndex& index) {
ModelFutureWatcher<bool>* watcher = static_cast<ModelFutureWatcher<bool>*>(sender()); if (reply->is_successful() && index.isValid()) {
watcher->deleteLater();
const QPersistentModelIndex& index = watcher->index();
if (index.isValid()) {
QFuture<void> future = item_at(index.row())->BackgroundReload(); QFuture<void> future = item_at(index.row())->BackgroundReload();
ModelFutureWatcher<void>* watcher = new ModelFutureWatcher<void>(index, this); ModelFutureWatcher<void>* watcher = new ModelFutureWatcher<void>(index, this);
watcher->setFuture(future); watcher->setFuture(future);
connect(watcher, SIGNAL(finished()), SLOT(ItemReloadComplete())); connect(watcher, SIGNAL(finished()), SLOT(ItemReloadComplete()));
} }
reply->deleteLater();
} }
void Playlist::ItemReloadComplete() { void Playlist::ItemReloadComplete() {

View File

@ -25,6 +25,7 @@
#include "playlistitem.h" #include "playlistitem.h"
#include "playlistsequence.h" #include "playlistsequence.h"
#include "core/tagreaderclient.h"
#include "core/song.h" #include "core/song.h"
#include "smartplaylists/generator_fwd.h" #include "smartplaylists/generator_fwd.h"
@ -328,7 +329,7 @@ class Playlist : public QAbstractListModel {
void TracksDequeued(); void TracksDequeued();
void TracksEnqueued(const QModelIndex&, int begin, int end); void TracksEnqueued(const QModelIndex&, int begin, int end);
void QueueLayoutChanged(); void QueueLayoutChanged();
void SongSaveComplete(); void SongSaveComplete(TagReaderReply* reply, const QPersistentModelIndex& index);
void ItemReloadComplete(); void ItemReloadComplete();
void ItemsLoaded(); void ItemsLoaded();
void SongInsertVetoListenerDestroyed(); void SongInsertVetoListenerDestroyed();

View File

@ -17,6 +17,7 @@
#include "playlistbackend.h" #include "playlistbackend.h"
#include "songplaylistitem.h" #include "songplaylistitem.h"
#include "core/tagreaderclient.h"
#include "library/sqlrow.h" #include "library/sqlrow.h"
@ -40,8 +41,6 @@ bool SongPlaylistItem::InitFromQuery(const SqlRow& query) {
if (type() == "Stream") { if (type() == "Stream") {
song_.set_filetype(Song::Type_Stream); song_.set_filetype(Song::Type_Stream);
} else {
song_.set_directory_id(-1);
} }
return true; return true;
@ -54,11 +53,8 @@ QUrl SongPlaylistItem::Url() const {
void SongPlaylistItem::Reload() { void SongPlaylistItem::Reload() {
if (song_.url().scheme() != "file") if (song_.url().scheme() != "file")
return; return;
QString old_filename = song_.url().toLocalFile();
int old_directory_id = song_.directory_id();
song_ = Song(); TagReaderClient::Instance()->ReadFileBlocking(song_.url().toLocalFile(), &song_);
song_.InitFromFile(old_filename, old_directory_id);
} }
Song SongPlaylistItem::Metadata() const { Song SongPlaylistItem::Metadata() const {

View File

@ -16,6 +16,7 @@
*/ */
#include "parserbase.h" #include "parserbase.h"
#include "core/tagreaderclient.h"
#include "library/librarybackend.h" #include "library/librarybackend.h"
#include "library/libraryquery.h" #include "library/libraryquery.h"
#include "library/sqlrow.h" #include "library/sqlrow.h"
@ -74,7 +75,7 @@ void ParserBase::LoadSong(const QString& filename_or_url, qint64 beginning,
if (library_song.is_valid()) { if (library_song.is_valid()) {
*song = library_song; *song = library_song;
} else { } else {
song->InitFromFile(filename, -1); TagReaderClient::Instance()->ReadFileBlocking(filename, song);
} }
} }

View File

@ -20,6 +20,7 @@
#include "trackselectiondialog.h" #include "trackselectiondialog.h"
#include "ui_edittagdialog.h" #include "ui_edittagdialog.h"
#include "core/logging.h" #include "core/logging.h"
#include "core/tagreaderclient.h"
#include "core/utilities.h" #include "core/utilities.h"
#include "covers/albumcoverloader.h" #include "covers/albumcoverloader.h"
#include "covers/coverproviders.h" #include "covers/coverproviders.h"
@ -195,7 +196,7 @@ QList<EditTagDialog::Data> EditTagDialog::LoadData(const SongList& songs) const
if (song.IsEditable()) { if (song.IsEditable()) {
// Try reloading the tags from file // Try reloading the tags from file
Song copy(song); Song copy(song);
copy.InitFromFile(copy.url().toLocalFile(), copy.directory_id()); TagReaderClient::Instance()->ReadFileBlocking(copy.url().toLocalFile(), &copy);
if (copy.is_valid()) if (copy.is_valid())
ret << Data(copy); ret << Data(copy);
@ -607,7 +608,8 @@ void EditTagDialog::SaveData(const QList<Data>& data) {
if (ref.current_.IsMetadataEqual(ref.original_)) if (ref.current_.IsMetadataEqual(ref.original_))
continue; continue;
if (!ref.current_.Save()) { if (!TagReaderClient::Instance()->SaveFileBlocking(
ref.current_.url().toLocalFile(), ref.current_)) {
emit Error(tr("An error occurred writing metadata to '%1'").arg(ref.current_.url().toLocalFile())); emit Error(tr("An error occurred writing metadata to '%1'").arg(ref.current_.url().toLocalFile()));
} }
} }

View File

@ -161,7 +161,6 @@ MainWindow::MainWindow(
ArtLoader* art_loader, ArtLoader* art_loader,
CoverProviders* cover_providers, CoverProviders* cover_providers,
GlobalSearch* global_search, GlobalSearch* global_search,
TagReader* tag_reader_client,
QWidget* parent) QWidget* parent)
: QMainWindow(parent), : QMainWindow(parent),
ui_(new Ui_MainWindow), ui_(new Ui_MainWindow),
@ -178,7 +177,6 @@ MainWindow::MainWindow(
library_(NULL), library_(NULL),
global_shortcuts_(new GlobalShortcuts(this)), global_shortcuts_(new GlobalShortcuts(this)),
global_search_(global_search), global_search_(global_search),
tag_reader_client_(tag_reader_client),
remote_(NULL), remote_(NULL),
devices_(NULL), devices_(NULL),
library_view_(new LibraryViewContainer(this)), library_view_(new LibraryViewContainer(this)),
@ -1499,21 +1497,24 @@ void MainWindow::RenumberTracks() {
if (song.IsEditable()) { if (song.IsEditable()) {
song.set_track(track); song.set_track(track);
QFuture<bool> future = song.BackgroundSave();
ModelFutureWatcher<bool>* watcher = new ModelFutureWatcher<bool>(source_index, this); TagReaderReply* reply =
watcher->setFuture(future); TagReaderClient::Instance()->SaveFile(song.url().toLocalFile(), song);
connect(watcher, SIGNAL(finished()), SLOT(SongSaveComplete()));
NewClosure(reply, SIGNAL(Finished(bool)),
this, SLOT(SongSaveComplete(TagReaderReply*,QPersistentModelIndex)),
reply, QPersistentModelIndex(source_index));
} }
track++; track++;
} }
} }
void MainWindow::SongSaveComplete() { void MainWindow::SongSaveComplete(TagReaderReply* reply,
ModelFutureWatcher<bool>* watcher = static_cast<ModelFutureWatcher<bool>*>(sender()); const QPersistentModelIndex& index) {
watcher->deleteLater(); if (reply->is_successful() && index.isValid()) {
if (watcher->index().isValid()) { playlists_->current()->ReloadItems(QList<int>() << index.row());
playlists_->current()->ReloadItems(QList<int>() << watcher->index().row());
} }
reply->deleteLater();
} }
void MainWindow::SelectionSetValue() { void MainWindow::SelectionSetValue() {
@ -1531,10 +1532,12 @@ void MainWindow::SelectionSetValue() {
Song song = playlists_->current()->item_at(row)->Metadata(); Song song = playlists_->current()->item_at(row)->Metadata();
if (Playlist::set_column_value(song, column, column_value)) { if (Playlist::set_column_value(song, column, column_value)) {
QFuture<bool> future = song.BackgroundSave(); TagReaderReply* reply =
ModelFutureWatcher<bool>* watcher = new ModelFutureWatcher<bool>(source_index, this); TagReaderClient::Instance()->SaveFile(song.url().toLocalFile(), song);
watcher->setFuture(future);
connect(watcher, SIGNAL(finished()), SLOT(SongSaveComplete())); NewClosure(reply, SIGNAL(Finished(bool)),
this, SLOT(SongSaveComplete(TagReaderReply*,QPersistentModelIndex)),
reply, QPersistentModelIndex(source_index));
} }
} }
} }

View File

@ -26,6 +26,7 @@
#include "config.h" #include "config.h"
#include "core/mac_startup.h" #include "core/mac_startup.h"
#include "core/tagreaderclient.h"
#include "engines/engine_fwd.h" #include "engines/engine_fwd.h"
#include "library/librarymodel.h" #include "library/librarymodel.h"
#include "playlist/playlistitem.h" #include "playlist/playlistitem.h"
@ -68,7 +69,6 @@ class SongInfoBase;
class SongInfoView; class SongInfoView;
class SystemTrayIcon; class SystemTrayIcon;
class TagFetcher; class TagFetcher;
class TagReaderClient;
class TaskManager; class TaskManager;
class TrackSelectionDialog; class TrackSelectionDialog;
class TranscodeDialog; class TranscodeDialog;
@ -93,7 +93,6 @@ class MainWindow : public QMainWindow, public PlatformInterface {
ArtLoader* art_loader, ArtLoader* art_loader,
CoverProviders* cover_providers, CoverProviders* cover_providers,
GlobalSearch* global_search, GlobalSearch* global_search,
TagReaderClient* tag_reader_client,
QWidget *parent = 0); QWidget *parent = 0);
~MainWindow(); ~MainWindow();
@ -222,7 +221,8 @@ class MainWindow : public QMainWindow, public PlatformInterface {
void NowPlayingWidgetPositionChanged(bool above_status_bar); void NowPlayingWidgetPositionChanged(bool above_status_bar);
void SongSaveComplete(); void SongSaveComplete(TagReaderReply* reply,
const QPersistentModelIndex& index);
void ShowCoverManager(); void ShowCoverManager();
#ifdef HAVE_LIBLASTFM #ifdef HAVE_LIBLASTFM
@ -281,7 +281,6 @@ class MainWindow : public QMainWindow, public PlatformInterface {
Library* library_; Library* library_;
GlobalShortcuts* global_shortcuts_; GlobalShortcuts* global_shortcuts_;
GlobalSearch* global_search_; GlobalSearch* global_search_;
TagReaderClient* tag_reader_client_;
Remote* remote_; Remote* remote_;
DeviceManager* devices_; DeviceManager* devices_;

View File

@ -21,6 +21,7 @@
#include "ui_organisedialog.h" #include "ui_organisedialog.h"
#include "core/musicstorage.h" #include "core/musicstorage.h"
#include "core/organise.h" #include "core/organise.h"
#include "core/tagreaderclient.h"
#include <QDir> #include <QDir>
#include <QFileInfo> #include <QFileInfo>
@ -172,7 +173,8 @@ void OrganiseDialog::LoadPreviewSongs(const QString& filename) {
} }
Song song; Song song;
song.InitFromFile(filename, -1); TagReaderClient::Instance()->ReadFileBlocking(filename, &song);
if (song.is_valid()) if (song.is_valid())
preview_songs_ << song; preview_songs_ << song;
} }

View File

@ -18,6 +18,7 @@
#include "iconloader.h" #include "iconloader.h"
#include "trackselectiondialog.h" #include "trackselectiondialog.h"
#include "ui_trackselectiondialog.h" #include "ui_trackselectiondialog.h"
#include "core/tagreaderclient.h"
#include <QFileInfo> #include <QFileInfo>
#include <QFutureWatcher> #include <QFutureWatcher>
@ -241,7 +242,7 @@ void TrackSelectionDialog::SaveData(const QList<Data>& data) {
copy.set_album(new_metadata.album()); copy.set_album(new_metadata.album());
copy.set_track(new_metadata.track()); copy.set_track(new_metadata.track());
copy.Save(); TagReaderClient::Instance()->SaveFileBlocking(copy.url().toLocalFile(), copy);
} }
} }