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:
parent
9041117867
commit
9be641ee87
@ -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);
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
26
ext/libclementine-common/core/waitforsignal.cpp
Normal file
26
ext/libclementine-common/core/waitforsignal.cpp
Normal 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();
|
||||||
|
}
|
25
ext/libclementine-common/core/waitforsignal.h
Normal file
25
ext/libclementine-common/core/waitforsignal.h
Normal 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
|
@ -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
|
||||||
|
@ -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}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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_;
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
|
@ -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>
|
||||||
|
@ -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"
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
14
src/main.cpp
14
src/main.cpp
@ -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
|
||||||
|
@ -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() {
|
||||||
|
@ -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();
|
||||||
|
@ -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 {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(), ©);
|
||||||
|
|
||||||
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()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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_;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user