mirror of
https://github.com/clementine-player/Clementine
synced 2024-12-16 11:19:18 +01:00
Put the Database object in its own thread, and create the Library and Playlist backends in that database thread. The database calls don't happen in the database thread yet, but this is the first step towards making sure sqlite access is thread safe.
This commit is contained in:
parent
c7d351f68a
commit
03d876a599
@ -16,6 +16,41 @@
|
||||
|
||||
#include "backgroundthread.h"
|
||||
|
||||
int BackgroundThreadBase::CreateInThreadEvent::sEventType = -1;
|
||||
|
||||
BackgroundThreadBase::BackgroundThreadBase(QObject *parent)
|
||||
: QThread(parent),
|
||||
io_priority_(IOPRIO_CLASS_NONE),
|
||||
cpu_priority_(InheritPriority),
|
||||
object_creator_(NULL)
|
||||
{
|
||||
if (CreateInThreadEvent::sEventType == -1)
|
||||
CreateInThreadEvent::sEventType = QEvent::registerEventType();
|
||||
}
|
||||
|
||||
BackgroundThreadBase::CreateInThreadEvent::CreateInThreadEvent(CreateInThreadRequest *req)
|
||||
: QEvent(QEvent::Type(sEventType)),
|
||||
req_(req)
|
||||
{
|
||||
}
|
||||
|
||||
bool BackgroundThreadBase::ObjectCreator::event(QEvent* e) {
|
||||
if (e->type() != CreateInThreadEvent::sEventType)
|
||||
return false;
|
||||
|
||||
// Create the object, parented to this object so it gets destroyed when the
|
||||
// thread ends.
|
||||
CreateInThreadRequest* req = static_cast<CreateInThreadEvent*>(e)->req_;
|
||||
req->object_ = req->meta_object_.newInstance(Q_ARG(QObject*, this));
|
||||
|
||||
// Wake up the calling thread
|
||||
QMutexLocker l(&req->mutex_);
|
||||
req->wait_condition_.wakeAll();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
int BackgroundThreadBase::SetIOPriority() {
|
||||
#ifdef Q_OS_LINUX
|
||||
return syscall(SYS_ioprio_set, IOPRIO_WHO_PROCESS, gettid(),
|
||||
@ -39,7 +74,7 @@ int BackgroundThreadBase::gettid() {
|
||||
void BackgroundThreadBase::Start(bool block) {
|
||||
if (!block) {
|
||||
// Just start the thread and return immediately
|
||||
QThread::start(cpu_priority_);
|
||||
start(cpu_priority_);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -48,8 +83,9 @@ void BackgroundThreadBase::Start(bool block) {
|
||||
QMutexLocker l(&started_wait_condition_mutex_);
|
||||
|
||||
// Start the thread.
|
||||
QThread::start(cpu_priority_);
|
||||
start(cpu_priority_);
|
||||
|
||||
// Wait for the thread to initalise.
|
||||
started_wait_condition_.wait(l.mutex());
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <QtDebug>
|
||||
#include <QWaitCondition>
|
||||
#include <QMutexLocker>
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
@ -53,10 +54,7 @@
|
||||
class BackgroundThreadBase : public QThread {
|
||||
Q_OBJECT
|
||||
public:
|
||||
BackgroundThreadBase(QObject* parent = 0)
|
||||
: QThread(parent),
|
||||
io_priority_(IOPRIO_CLASS_NONE),
|
||||
cpu_priority_(InheritPriority) {}
|
||||
BackgroundThreadBase(QObject* parent = 0);
|
||||
|
||||
// Borrowed from schedutils
|
||||
enum IoPriority {
|
||||
@ -71,10 +69,41 @@ class BackgroundThreadBase : public QThread {
|
||||
|
||||
virtual void Start(bool block = false);
|
||||
|
||||
// Creates a new QObject in this thread synchronously.
|
||||
// The class T needs to have a Q_INVOKABLE constructor that takes a single
|
||||
// QObject* argument.
|
||||
template <typename T>
|
||||
T* CreateInThread();
|
||||
|
||||
signals:
|
||||
void Initialised();
|
||||
|
||||
protected:
|
||||
struct CreateInThreadRequest {
|
||||
CreateInThreadRequest(const QMetaObject& meta_object)
|
||||
: meta_object_(meta_object), object_(NULL) {}
|
||||
|
||||
const QMetaObject& meta_object_;
|
||||
QObject* object_;
|
||||
|
||||
QWaitCondition wait_condition_;
|
||||
QMutex mutex_;
|
||||
};
|
||||
|
||||
struct CreateInThreadEvent : public QEvent {
|
||||
CreateInThreadEvent(CreateInThreadRequest* req);
|
||||
|
||||
static int sEventType;
|
||||
|
||||
CreateInThreadRequest* req_;
|
||||
};
|
||||
|
||||
class ObjectCreator : public QObject {
|
||||
public:
|
||||
bool event(QEvent *);
|
||||
};
|
||||
|
||||
|
||||
int SetIOPriority();
|
||||
static int gettid();
|
||||
|
||||
@ -90,6 +119,8 @@ class BackgroundThreadBase : public QThread {
|
||||
|
||||
QWaitCondition started_wait_condition_;
|
||||
QMutex started_wait_condition_mutex_;
|
||||
|
||||
ObjectCreator* object_creator_;
|
||||
};
|
||||
|
||||
// This is the templated class that stores and returns the worker object.
|
||||
@ -136,6 +167,22 @@ class BackgroundThreadFactoryImplementation : public BackgroundThreadFactory<Int
|
||||
|
||||
|
||||
|
||||
template <typename T>
|
||||
T* BackgroundThreadBase::CreateInThread() {
|
||||
// Create the request and lock it.
|
||||
CreateInThreadRequest req(T::staticMetaObject);
|
||||
QMutexLocker l(&req.mutex_);
|
||||
|
||||
// Post an event to the thread. It will create the object and signal the
|
||||
// wait condition. Ownership of the event is transferred to Qt.
|
||||
CreateInThreadEvent* event = new CreateInThreadEvent(&req);
|
||||
QCoreApplication::postEvent(object_creator_, event);
|
||||
|
||||
req.wait_condition_.wait(&req.mutex_);
|
||||
|
||||
return static_cast<T*>(req.object_);
|
||||
}
|
||||
|
||||
template <typename InterfaceType>
|
||||
BackgroundThread<InterfaceType>::BackgroundThread(QObject *parent)
|
||||
: BackgroundThreadBase(parent)
|
||||
@ -169,6 +216,7 @@ void BackgroundThreadImplementation<InterfaceType, DerivedType>::run() {
|
||||
this->SetIOPriority();
|
||||
|
||||
this->worker_.reset(new DerivedType);
|
||||
this->object_creator_ = new BackgroundThreadBase::ObjectCreator;
|
||||
|
||||
{
|
||||
// Tell the calling thread that we've initialised the worker.
|
||||
@ -179,6 +227,7 @@ void BackgroundThreadImplementation<InterfaceType, DerivedType>::run() {
|
||||
emit this->Initialised();
|
||||
QThread::exec();
|
||||
|
||||
delete this->object_creator_;
|
||||
this->worker_.reset();
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,8 @@ class Database : public QObject {
|
||||
static const int kSchemaVersion;
|
||||
static const char* kDatabaseFilename;
|
||||
|
||||
void Stop() {}
|
||||
|
||||
QSqlDatabase Connect();
|
||||
bool CheckErrors(const QSqlError& error);
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "core/database.h"
|
||||
#include "library.h"
|
||||
#include "librarymodel.h"
|
||||
#include "librarybackend.h"
|
||||
@ -22,13 +23,17 @@ const char* Library::kSongsTable = "songs";
|
||||
const char* Library::kDirsTable = "directories";
|
||||
const char* Library::kSubdirsTable = "subdirectories";
|
||||
|
||||
Library::Library(Database *db, QObject *parent)
|
||||
Library::Library(BackgroundThread<Database>* db_thread, QObject *parent)
|
||||
: QObject(parent),
|
||||
backend_(new LibraryBackend(db, kSongsTable, kDirsTable, kSubdirsTable, this)),
|
||||
model_(new LibraryModel(backend_, parent)),
|
||||
backend_(NULL),
|
||||
model_(NULL),
|
||||
watcher_factory_(new BackgroundThreadFactoryImplementation<LibraryWatcher, LibraryWatcher>),
|
||||
watcher_(NULL)
|
||||
{
|
||||
backend_ = db_thread->CreateInThread<LibraryBackend>();
|
||||
backend_->Init(db_thread->Worker(), kSongsTable, kDirsTable, kSubdirsTable);
|
||||
|
||||
model_ = new LibraryModel(backend_, this);
|
||||
}
|
||||
|
||||
void Library::set_watcher_factory(BackgroundThreadFactory<LibraryWatcher>* factory) {
|
||||
|
@ -32,7 +32,7 @@ class Library : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Library(Database* db, QObject* parent);
|
||||
Library(BackgroundThread<Database>* db_thread, QObject* parent);
|
||||
|
||||
static const char* kSongsTable;
|
||||
static const char* kDirsTable;
|
||||
|
@ -25,17 +25,19 @@
|
||||
#include <QtDebug>
|
||||
#include <QCoreApplication>
|
||||
|
||||
LibraryBackend::LibraryBackend(Database *db, const QString& songs_table,
|
||||
const QString& dirs_table,
|
||||
const QString& subdirs_table, QObject *parent)
|
||||
: QObject(parent),
|
||||
db_(db),
|
||||
songs_table_(songs_table),
|
||||
dirs_table_(dirs_table),
|
||||
subdirs_table_(subdirs_table)
|
||||
LibraryBackend::LibraryBackend(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void LibraryBackend::Init(boost::shared_ptr<Database> db, const QString &songs_table,
|
||||
const QString &dirs_table, const QString &subdirs_table) {
|
||||
db_ = db;
|
||||
songs_table_ = songs_table;
|
||||
dirs_table_ = dirs_table;
|
||||
subdirs_table_ = subdirs_table;
|
||||
}
|
||||
|
||||
void LibraryBackend::LoadDirectoriesAsync() {
|
||||
metaObject()->invokeMethod(this, "LoadDirectories", Qt::QueuedConnection);
|
||||
}
|
||||
|
@ -24,15 +24,17 @@
|
||||
#include "libraryquery.h"
|
||||
#include "core/song.h"
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
class Database;
|
||||
|
||||
class LibraryBackend : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
LibraryBackend(Database* db, const QString& songs_table,
|
||||
const QString& dirs_table, const QString& subdirs_table,
|
||||
QObject* parent = 0);
|
||||
Q_INVOKABLE LibraryBackend(QObject* parent = 0);
|
||||
void Init(boost::shared_ptr<Database> db, const QString& songs_table,
|
||||
const QString& dirs_table, const QString& subdirs_table);
|
||||
|
||||
struct Album {
|
||||
Album() {}
|
||||
@ -121,7 +123,7 @@ class LibraryBackend : public QObject {
|
||||
SubdirectoryList SubdirsInDirectory(int id, QSqlDatabase& db);
|
||||
|
||||
private:
|
||||
Database* db_;
|
||||
boost::shared_ptr<Database> db_;
|
||||
QString songs_table_;
|
||||
QString dirs_table_;
|
||||
QString subdirs_table_;
|
||||
|
@ -24,9 +24,8 @@
|
||||
|
||||
using boost::shared_ptr;
|
||||
|
||||
PlaylistBackend::PlaylistBackend(Database* db, QObject* parent)
|
||||
: QObject(parent),
|
||||
db_(db)
|
||||
PlaylistBackend::PlaylistBackend(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -22,13 +22,16 @@
|
||||
|
||||
#include "playlistitem.h"
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
class Database;
|
||||
|
||||
class PlaylistBackend : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PlaylistBackend(Database* db, QObject* parent = 0);
|
||||
Q_INVOKABLE PlaylistBackend(QObject* parent = 0);
|
||||
void SetDatabase(boost::shared_ptr<Database> db) { db_ = db; }
|
||||
|
||||
struct Playlist {
|
||||
int id;
|
||||
@ -52,7 +55,7 @@ class PlaylistBackend : public QObject {
|
||||
void SavePlaylist(int playlist, const PlaylistItemList& items, int last_played);
|
||||
|
||||
private:
|
||||
Database* db_;
|
||||
boost::shared_ptr<Database> db_;
|
||||
};
|
||||
|
||||
#endif // PLAYLISTBACKEND_H
|
||||
|
@ -50,13 +50,18 @@ MagnatuneService::MagnatuneService(RadioModel* parent)
|
||||
: RadioService(kServiceName, parent),
|
||||
root_(NULL),
|
||||
context_menu_(new QMenu),
|
||||
library_backend_(new LibraryBackend(parent->db(), kSongsTable,
|
||||
QString::null, QString::null, this)),
|
||||
library_model_(new LibraryModel(library_backend_, this)),
|
||||
library_backend_(NULL),
|
||||
library_model_(NULL),
|
||||
library_sort_model_(new QSortFilterProxyModel(this)),
|
||||
total_song_count_(0),
|
||||
network_(parent->network()->network())
|
||||
{
|
||||
// Create the library backend in the database thread
|
||||
library_backend_ = parent->db_thread()->CreateInThread<LibraryBackend>();
|
||||
library_backend_->Init(parent->db_thread()->Worker(), kSongsTable,
|
||||
QString::null, QString::null);
|
||||
library_model_ = new LibraryModel(library_backend_, this);
|
||||
|
||||
connect(library_backend_, SIGNAL(TotalSongCountUpdated(int)),
|
||||
SLOT(UpdateTotalSongCount(int)));
|
||||
|
||||
|
@ -28,9 +28,10 @@
|
||||
|
||||
QMap<QString, RadioService*> RadioModel::sServices;
|
||||
|
||||
RadioModel::RadioModel(Database* db, NetworkAccessManager* network, QObject* parent)
|
||||
RadioModel::RadioModel(BackgroundThread<Database>* db_thread,
|
||||
NetworkAccessManager* network, QObject* parent)
|
||||
: SimpleTreeModel<RadioItem>(new RadioItem(this), parent),
|
||||
db_(db),
|
||||
db_thread_(db_thread),
|
||||
merged_model_(new MergedProxyModel(this)),
|
||||
network_(network)
|
||||
{
|
||||
|
@ -18,6 +18,7 @@
|
||||
#define RADIOMODEL_H
|
||||
|
||||
#include "radioitem.h"
|
||||
#include "core/backgroundthread.h"
|
||||
#include "core/simpletreemodel.h"
|
||||
#include "core/song.h"
|
||||
#include "playlist/playlistitem.h"
|
||||
@ -33,7 +34,8 @@ class RadioModel : public SimpleTreeModel<RadioItem> {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
RadioModel(Database* db, NetworkAccessManager* network, QObject* parent = 0);
|
||||
RadioModel(BackgroundThread<Database>* db_thread,
|
||||
NetworkAccessManager* network, QObject* parent = 0);
|
||||
|
||||
enum {
|
||||
Role_Type = Qt::UserRole + 1,
|
||||
@ -65,7 +67,7 @@ class RadioModel : public SimpleTreeModel<RadioItem> {
|
||||
const QPoint& global_pos);
|
||||
void ReloadSettings();
|
||||
|
||||
Database* db() const { return db_; }
|
||||
BackgroundThread<Database>* db_thread() const { return db_thread_; }
|
||||
MergedProxyModel* merged_model() const { return merged_model_; }
|
||||
NetworkAccessManager* network() const { return network_; }
|
||||
|
||||
@ -88,7 +90,7 @@ class RadioModel : public SimpleTreeModel<RadioItem> {
|
||||
|
||||
private:
|
||||
static QMap<QString, RadioService*> sServices;
|
||||
Database* db_;
|
||||
BackgroundThread<Database>* db_thread_;
|
||||
MergedProxyModel* merged_model_;
|
||||
NetworkAccessManager* network_;
|
||||
};
|
||||
|
@ -103,17 +103,17 @@ MainWindow::MainWindow(NetworkAccessManager* network, Engine::Type engine, QWidg
|
||||
edit_tag_dialog_(new EditTagDialog),
|
||||
multi_loading_indicator_(new MultiLoadingIndicator(this)),
|
||||
about_dialog_(new About),
|
||||
database_(new Database(this)),
|
||||
radio_model_(new RadioModel(database_, network, this)),
|
||||
playlist_backend_(new PlaylistBackend(database_, this)),
|
||||
database_(new BackgroundThreadImplementation<Database, Database>(this)),
|
||||
radio_model_(NULL),
|
||||
playlist_backend_(NULL),
|
||||
playlists_(new PlaylistManager(this)),
|
||||
playlist_parser_(new PlaylistParser(this)),
|
||||
player_(new Player(playlists_, radio_model_->GetLastFMService(), engine, this)),
|
||||
library_(new Library(database_, this)),
|
||||
player_(NULL),
|
||||
library_(NULL),
|
||||
global_shortcuts_(new GlobalShortcuts(this)),
|
||||
settings_dialog_(new SettingsDialog),
|
||||
add_stream_dialog_(new AddStreamDialog),
|
||||
cover_manager_(new AlbumCoverManager(network, library_->model()->backend())),
|
||||
cover_manager_(NULL),
|
||||
equalizer_(new Equalizer),
|
||||
transcode_dialog_(new TranscodeDialog),
|
||||
global_shortcuts_dialog_(new GlobalShortcutsDialog(global_shortcuts_)),
|
||||
@ -123,6 +123,20 @@ MainWindow::MainWindow(NetworkAccessManager* network, Engine::Type engine, QWidg
|
||||
track_position_timer_(new QTimer(this)),
|
||||
was_maximized_(false)
|
||||
{
|
||||
// Wait for the database thread to start - lots of stuff depends on it.
|
||||
database_->Start(true);
|
||||
|
||||
// Create some objects in the database thread
|
||||
playlist_backend_ = database_->CreateInThread<PlaylistBackend>();
|
||||
playlist_backend_->SetDatabase(database_->Worker());
|
||||
|
||||
// Create stuff that needs the database
|
||||
radio_model_ = new RadioModel(database_, network, this);
|
||||
player_ = new Player(playlists_, radio_model_->GetLastFMService(), engine, this);
|
||||
library_ = new Library(database_, this);
|
||||
cover_manager_.reset(new AlbumCoverManager(network, library_->backend()));
|
||||
|
||||
// Initialise the UI
|
||||
ui_->setupUi(this);
|
||||
tray_icon_->setIcon(windowIcon());
|
||||
tray_icon_->setToolTip(QCoreApplication::applicationName());
|
||||
@ -278,7 +292,7 @@ MainWindow::MainWindow(NetworkAccessManager* network, Engine::Type engine, QWidg
|
||||
connect(track_slider_, SIGNAL(ValueChanged(int)), player_, SLOT(Seek(int)));
|
||||
|
||||
// Database connections
|
||||
connect(database_, SIGNAL(Error(QString)), error_dialog_.get(), SLOT(ShowMessage(QString)));
|
||||
connect(database_->Worker().get(), SIGNAL(Error(QString)), error_dialog_.get(), SLOT(ShowMessage(QString)));
|
||||
|
||||
// Library connections
|
||||
connect(ui_->library_view, SIGNAL(doubleClicked(QModelIndex)), SLOT(LibraryItemDoubleClicked(QModelIndex)));
|
||||
|
@ -159,7 +159,7 @@ class MainWindow : public QMainWindow {
|
||||
MultiLoadingIndicator* multi_loading_indicator_;
|
||||
boost::scoped_ptr<About> about_dialog_;
|
||||
|
||||
Database* database_;
|
||||
BackgroundThread<Database>* database_;
|
||||
RadioModel* radio_model_;
|
||||
PlaylistBackend* playlist_backend_;
|
||||
PlaylistManager* playlists_;
|
||||
|
Loading…
Reference in New Issue
Block a user