1
0
mirror of https://github.com/clementine-player/Clementine synced 2024-12-17 03:45:56 +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:
David Sansome 2010-06-02 15:58:07 +00:00
parent c7d351f68a
commit 03d876a599
14 changed files with 163 additions and 43 deletions

View File

@ -16,6 +16,41 @@
#include "backgroundthread.h" #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() { int BackgroundThreadBase::SetIOPriority() {
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
return syscall(SYS_ioprio_set, IOPRIO_WHO_PROCESS, gettid(), return syscall(SYS_ioprio_set, IOPRIO_WHO_PROCESS, gettid(),
@ -39,7 +74,7 @@ int BackgroundThreadBase::gettid() {
void BackgroundThreadBase::Start(bool block) { void BackgroundThreadBase::Start(bool block) {
if (!block) { if (!block) {
// Just start the thread and return immediately // Just start the thread and return immediately
QThread::start(cpu_priority_); start(cpu_priority_);
return; return;
} }
@ -48,8 +83,9 @@ void BackgroundThreadBase::Start(bool block) {
QMutexLocker l(&started_wait_condition_mutex_); QMutexLocker l(&started_wait_condition_mutex_);
// Start the thread. // Start the thread.
QThread::start(cpu_priority_); start(cpu_priority_);
// Wait for the thread to initalise. // Wait for the thread to initalise.
started_wait_condition_.wait(l.mutex()); started_wait_condition_.wait(l.mutex());
} }

View File

@ -21,6 +21,7 @@
#include <QtDebug> #include <QtDebug>
#include <QWaitCondition> #include <QWaitCondition>
#include <QMutexLocker> #include <QMutexLocker>
#include <QCoreApplication>
#include <boost/shared_ptr.hpp> #include <boost/shared_ptr.hpp>
@ -53,10 +54,7 @@
class BackgroundThreadBase : public QThread { class BackgroundThreadBase : public QThread {
Q_OBJECT Q_OBJECT
public: public:
BackgroundThreadBase(QObject* parent = 0) BackgroundThreadBase(QObject* parent = 0);
: QThread(parent),
io_priority_(IOPRIO_CLASS_NONE),
cpu_priority_(InheritPriority) {}
// Borrowed from schedutils // Borrowed from schedutils
enum IoPriority { enum IoPriority {
@ -71,10 +69,41 @@ class BackgroundThreadBase : public QThread {
virtual void Start(bool block = false); 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: signals:
void Initialised(); void Initialised();
protected: 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(); int SetIOPriority();
static int gettid(); static int gettid();
@ -90,6 +119,8 @@ class BackgroundThreadBase : public QThread {
QWaitCondition started_wait_condition_; QWaitCondition started_wait_condition_;
QMutex started_wait_condition_mutex_; QMutex started_wait_condition_mutex_;
ObjectCreator* object_creator_;
}; };
// This is the templated class that stores and returns the worker object. // 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> template <typename InterfaceType>
BackgroundThread<InterfaceType>::BackgroundThread(QObject *parent) BackgroundThread<InterfaceType>::BackgroundThread(QObject *parent)
: BackgroundThreadBase(parent) : BackgroundThreadBase(parent)
@ -169,6 +216,7 @@ void BackgroundThreadImplementation<InterfaceType, DerivedType>::run() {
this->SetIOPriority(); this->SetIOPriority();
this->worker_.reset(new DerivedType); this->worker_.reset(new DerivedType);
this->object_creator_ = new BackgroundThreadBase::ObjectCreator;
{ {
// Tell the calling thread that we've initialised the worker. // Tell the calling thread that we've initialised the worker.
@ -179,6 +227,7 @@ void BackgroundThreadImplementation<InterfaceType, DerivedType>::run() {
emit this->Initialised(); emit this->Initialised();
QThread::exec(); QThread::exec();
delete this->object_creator_;
this->worker_.reset(); this->worker_.reset();
} }

View File

@ -36,6 +36,8 @@ class Database : public QObject {
static const int kSchemaVersion; static const int kSchemaVersion;
static const char* kDatabaseFilename; static const char* kDatabaseFilename;
void Stop() {}
QSqlDatabase Connect(); QSqlDatabase Connect();
bool CheckErrors(const QSqlError& error); bool CheckErrors(const QSqlError& error);

View File

@ -14,6 +14,7 @@
along with Clementine. If not, see <http://www.gnu.org/licenses/>. along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "core/database.h"
#include "library.h" #include "library.h"
#include "librarymodel.h" #include "librarymodel.h"
#include "librarybackend.h" #include "librarybackend.h"
@ -22,13 +23,17 @@ const char* Library::kSongsTable = "songs";
const char* Library::kDirsTable = "directories"; const char* Library::kDirsTable = "directories";
const char* Library::kSubdirsTable = "subdirectories"; const char* Library::kSubdirsTable = "subdirectories";
Library::Library(Database *db, QObject *parent) Library::Library(BackgroundThread<Database>* db_thread, QObject *parent)
: QObject(parent), : QObject(parent),
backend_(new LibraryBackend(db, kSongsTable, kDirsTable, kSubdirsTable, this)), backend_(NULL),
model_(new LibraryModel(backend_, parent)), model_(NULL),
watcher_factory_(new BackgroundThreadFactoryImplementation<LibraryWatcher, LibraryWatcher>), watcher_factory_(new BackgroundThreadFactoryImplementation<LibraryWatcher, LibraryWatcher>),
watcher_(NULL) 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) { void Library::set_watcher_factory(BackgroundThreadFactory<LibraryWatcher>* factory) {

View File

@ -32,7 +32,7 @@ class Library : public QObject {
Q_OBJECT Q_OBJECT
public: public:
Library(Database* db, QObject* parent); Library(BackgroundThread<Database>* db_thread, QObject* parent);
static const char* kSongsTable; static const char* kSongsTable;
static const char* kDirsTable; static const char* kDirsTable;

View File

@ -25,17 +25,19 @@
#include <QtDebug> #include <QtDebug>
#include <QCoreApplication> #include <QCoreApplication>
LibraryBackend::LibraryBackend(Database *db, const QString& songs_table, LibraryBackend::LibraryBackend(QObject *parent)
const QString& dirs_table, : QObject(parent)
const QString& subdirs_table, QObject *parent)
: QObject(parent),
db_(db),
songs_table_(songs_table),
dirs_table_(dirs_table),
subdirs_table_(subdirs_table)
{ {
} }
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() { void LibraryBackend::LoadDirectoriesAsync() {
metaObject()->invokeMethod(this, "LoadDirectories", Qt::QueuedConnection); metaObject()->invokeMethod(this, "LoadDirectories", Qt::QueuedConnection);
} }

View File

@ -24,15 +24,17 @@
#include "libraryquery.h" #include "libraryquery.h"
#include "core/song.h" #include "core/song.h"
#include <boost/shared_ptr.hpp>
class Database; class Database;
class LibraryBackend : public QObject { class LibraryBackend : public QObject {
Q_OBJECT Q_OBJECT
public: public:
LibraryBackend(Database* db, const QString& songs_table, Q_INVOKABLE LibraryBackend(QObject* parent = 0);
const QString& dirs_table, const QString& subdirs_table, void Init(boost::shared_ptr<Database> db, const QString& songs_table,
QObject* parent = 0); const QString& dirs_table, const QString& subdirs_table);
struct Album { struct Album {
Album() {} Album() {}
@ -121,7 +123,7 @@ class LibraryBackend : public QObject {
SubdirectoryList SubdirsInDirectory(int id, QSqlDatabase& db); SubdirectoryList SubdirsInDirectory(int id, QSqlDatabase& db);
private: private:
Database* db_; boost::shared_ptr<Database> db_;
QString songs_table_; QString songs_table_;
QString dirs_table_; QString dirs_table_;
QString subdirs_table_; QString subdirs_table_;

View File

@ -24,9 +24,8 @@
using boost::shared_ptr; using boost::shared_ptr;
PlaylistBackend::PlaylistBackend(Database* db, QObject* parent) PlaylistBackend::PlaylistBackend(QObject* parent)
: QObject(parent), : QObject(parent)
db_(db)
{ {
} }

View File

@ -22,13 +22,16 @@
#include "playlistitem.h" #include "playlistitem.h"
#include <boost/shared_ptr.hpp>
class Database; class Database;
class PlaylistBackend : public QObject { class PlaylistBackend : public QObject {
Q_OBJECT Q_OBJECT
public: public:
PlaylistBackend(Database* db, QObject* parent = 0); Q_INVOKABLE PlaylistBackend(QObject* parent = 0);
void SetDatabase(boost::shared_ptr<Database> db) { db_ = db; }
struct Playlist { struct Playlist {
int id; int id;
@ -52,7 +55,7 @@ class PlaylistBackend : public QObject {
void SavePlaylist(int playlist, const PlaylistItemList& items, int last_played); void SavePlaylist(int playlist, const PlaylistItemList& items, int last_played);
private: private:
Database* db_; boost::shared_ptr<Database> db_;
}; };
#endif // PLAYLISTBACKEND_H #endif // PLAYLISTBACKEND_H

View File

@ -50,13 +50,18 @@ MagnatuneService::MagnatuneService(RadioModel* parent)
: RadioService(kServiceName, parent), : RadioService(kServiceName, parent),
root_(NULL), root_(NULL),
context_menu_(new QMenu), context_menu_(new QMenu),
library_backend_(new LibraryBackend(parent->db(), kSongsTable, library_backend_(NULL),
QString::null, QString::null, this)), library_model_(NULL),
library_model_(new LibraryModel(library_backend_, this)),
library_sort_model_(new QSortFilterProxyModel(this)), library_sort_model_(new QSortFilterProxyModel(this)),
total_song_count_(0), total_song_count_(0),
network_(parent->network()->network()) 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)), connect(library_backend_, SIGNAL(TotalSongCountUpdated(int)),
SLOT(UpdateTotalSongCount(int))); SLOT(UpdateTotalSongCount(int)));

View File

@ -28,9 +28,10 @@
QMap<QString, RadioService*> RadioModel::sServices; 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), : SimpleTreeModel<RadioItem>(new RadioItem(this), parent),
db_(db), db_thread_(db_thread),
merged_model_(new MergedProxyModel(this)), merged_model_(new MergedProxyModel(this)),
network_(network) network_(network)
{ {

View File

@ -18,6 +18,7 @@
#define RADIOMODEL_H #define RADIOMODEL_H
#include "radioitem.h" #include "radioitem.h"
#include "core/backgroundthread.h"
#include "core/simpletreemodel.h" #include "core/simpletreemodel.h"
#include "core/song.h" #include "core/song.h"
#include "playlist/playlistitem.h" #include "playlist/playlistitem.h"
@ -33,7 +34,8 @@ class RadioModel : public SimpleTreeModel<RadioItem> {
Q_OBJECT Q_OBJECT
public: public:
RadioModel(Database* db, NetworkAccessManager* network, QObject* parent = 0); RadioModel(BackgroundThread<Database>* db_thread,
NetworkAccessManager* network, QObject* parent = 0);
enum { enum {
Role_Type = Qt::UserRole + 1, Role_Type = Qt::UserRole + 1,
@ -65,7 +67,7 @@ class RadioModel : public SimpleTreeModel<RadioItem> {
const QPoint& global_pos); const QPoint& global_pos);
void ReloadSettings(); void ReloadSettings();
Database* db() const { return db_; } BackgroundThread<Database>* db_thread() const { return db_thread_; }
MergedProxyModel* merged_model() const { return merged_model_; } MergedProxyModel* merged_model() const { return merged_model_; }
NetworkAccessManager* network() const { return network_; } NetworkAccessManager* network() const { return network_; }
@ -88,7 +90,7 @@ class RadioModel : public SimpleTreeModel<RadioItem> {
private: private:
static QMap<QString, RadioService*> sServices; static QMap<QString, RadioService*> sServices;
Database* db_; BackgroundThread<Database>* db_thread_;
MergedProxyModel* merged_model_; MergedProxyModel* merged_model_;
NetworkAccessManager* network_; NetworkAccessManager* network_;
}; };

View File

@ -103,17 +103,17 @@ MainWindow::MainWindow(NetworkAccessManager* network, Engine::Type engine, QWidg
edit_tag_dialog_(new EditTagDialog), edit_tag_dialog_(new EditTagDialog),
multi_loading_indicator_(new MultiLoadingIndicator(this)), multi_loading_indicator_(new MultiLoadingIndicator(this)),
about_dialog_(new About), about_dialog_(new About),
database_(new Database(this)), database_(new BackgroundThreadImplementation<Database, Database>(this)),
radio_model_(new RadioModel(database_, network, this)), radio_model_(NULL),
playlist_backend_(new PlaylistBackend(database_, this)), playlist_backend_(NULL),
playlists_(new PlaylistManager(this)), playlists_(new PlaylistManager(this)),
playlist_parser_(new PlaylistParser(this)), playlist_parser_(new PlaylistParser(this)),
player_(new Player(playlists_, radio_model_->GetLastFMService(), engine, this)), player_(NULL),
library_(new Library(database_, this)), library_(NULL),
global_shortcuts_(new GlobalShortcuts(this)), global_shortcuts_(new GlobalShortcuts(this)),
settings_dialog_(new SettingsDialog), settings_dialog_(new SettingsDialog),
add_stream_dialog_(new AddStreamDialog), add_stream_dialog_(new AddStreamDialog),
cover_manager_(new AlbumCoverManager(network, library_->model()->backend())), cover_manager_(NULL),
equalizer_(new Equalizer), equalizer_(new Equalizer),
transcode_dialog_(new TranscodeDialog), transcode_dialog_(new TranscodeDialog),
global_shortcuts_dialog_(new GlobalShortcutsDialog(global_shortcuts_)), 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)), track_position_timer_(new QTimer(this)),
was_maximized_(false) 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); ui_->setupUi(this);
tray_icon_->setIcon(windowIcon()); tray_icon_->setIcon(windowIcon());
tray_icon_->setToolTip(QCoreApplication::applicationName()); 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))); connect(track_slider_, SIGNAL(ValueChanged(int)), player_, SLOT(Seek(int)));
// Database connections // 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 // Library connections
connect(ui_->library_view, SIGNAL(doubleClicked(QModelIndex)), SLOT(LibraryItemDoubleClicked(QModelIndex))); connect(ui_->library_view, SIGNAL(doubleClicked(QModelIndex)), SLOT(LibraryItemDoubleClicked(QModelIndex)));

View File

@ -159,7 +159,7 @@ class MainWindow : public QMainWindow {
MultiLoadingIndicator* multi_loading_indicator_; MultiLoadingIndicator* multi_loading_indicator_;
boost::scoped_ptr<About> about_dialog_; boost::scoped_ptr<About> about_dialog_;
Database* database_; BackgroundThread<Database>* database_;
RadioModel* radio_model_; RadioModel* radio_model_;
PlaylistBackend* playlist_backend_; PlaylistBackend* playlist_backend_;
PlaylistManager* playlists_; PlaylistManager* playlists_;