Safely close database connections and delete backends

Also fix NewClosure leak caused by disconnected object signals
This commit is contained in:
Jonas Kvinge 2019-07-24 19:16:51 +02:00
parent bd78e8c275
commit b5eb13449b
47 changed files with 490 additions and 53 deletions

View File

@ -22,6 +22,7 @@
#include "closure.h"
#include "core/logging.h"
#include "core/timeconstants.h"
namespace _detail {
@ -30,8 +31,7 @@ ClosureBase::ClosureBase(ObjectHelper *helper)
: helper_(helper) {
}
ClosureBase::~ClosureBase() {
}
ClosureBase::~ClosureBase() {}
CallbackClosure::CallbackClosure(QObject *sender, const char *signal, std::function<void()> callback)
: ClosureBase(new ObjectHelper(sender, signal, this)),
@ -53,6 +53,8 @@ ObjectHelper::ObjectHelper(QObject *sender, const char *signal, ClosureBase *clo
}
ObjectHelper::~ObjectHelper() {}
void ObjectHelper::Invoked() {
closure_->Invoke();
deleteLater();

View File

@ -34,6 +34,8 @@
#include <QList>
#include <QTimer>
#include "core/logging.h"
namespace _detail {
class ObjectHelper;
@ -62,6 +64,7 @@ class ObjectHelper : public QObject {
Q_OBJECT
public:
ObjectHelper(QObject *parent, const char *signal, ClosureBase *closure);
~ObjectHelper();
private slots:
void Invoked();

View File

@ -21,6 +21,7 @@
#include "config.h"
#include <stdbool.h>
#include <unistd.h>
#include <QObject>
#include <QThread>
@ -50,7 +51,10 @@ SCollection::SCollection(Application *app, QObject *parent)
backend_(nullptr),
model_(nullptr),
watcher_(nullptr),
watcher_thread_(nullptr) {
watcher_thread_(nullptr),
original_thread_(nullptr) {
original_thread_ = thread();
backend_ = new CollectionBackend();
backend()->moveToThread(app->database()->thread());
@ -64,11 +68,17 @@ SCollection::SCollection(Application *app, QObject *parent)
}
SCollection::~SCollection() {
watcher_->Stop();
watcher_->deleteLater();
watcher_thread_->exit();
watcher_thread_->wait(5000 /* five seconds */);
if (watcher_) {
watcher_->Stop();
watcher_->deleteLater();
}
if (watcher_thread_) {
watcher_thread_->exit();
watcher_thread_->wait(5000 /* five seconds */);
}
backend_->deleteLater();
}
void SCollection::Init() {
@ -98,6 +108,29 @@ void SCollection::Init() {
// This will start the watcher checking for updates
backend_->LoadDirectoriesAsync();
}
void SCollection::Exit() {
wait_for_exit_ << backend_ << watcher_;
disconnect(backend_, 0, watcher_, 0);
disconnect(watcher_, 0, backend_, 0);
connect(backend_, SIGNAL(ExitFinished()), this, SLOT(ExitReceived()));
connect(watcher_, SIGNAL(ExitFinished()), this, SLOT(ExitReceived()));
backend_->ExitAsync();
watcher_->ExitAsync();
}
void SCollection::ExitReceived() {
disconnect(sender(), 0, this, 0);
wait_for_exit_.removeAll(sender());
if (wait_for_exit_.isEmpty()) emit ExitFinished();
}
void SCollection::IncrementalScan() { watcher_->IncrementalScanAsync(); }

View File

@ -49,6 +49,7 @@ class SCollection : public QObject {
static const char *kFtsTable;
void Init();
void Exit();
CollectionBackend *backend() const { return backend_; }
CollectionModel *model() const { return model_; }
@ -70,12 +71,17 @@ class SCollection : public QObject {
void Rescan(const SongList &songs);
private slots:
void ExitReceived();
void IncrementalScan();
void CurrentSongChanged(const Song &song);
void SongsStatisticsChanged(const SongList& songs);
void Stopped();
signals:
void ExitFinished();
private:
Application *app_;
CollectionBackend *backend_;
@ -83,9 +89,12 @@ class SCollection : public QObject {
CollectionWatcher *watcher_;
Thread *watcher_thread_;
QThread *original_thread_;
// DB schema versions which should trigger a full collection rescan (each of those with a short reason why).
QHash<int, QString> full_rescan_revisions_;
QList<QObject*> wait_for_exit_;
};
#endif

View File

@ -20,8 +20,12 @@
#include "config.h"
#include <assert.h>
#include <QtGlobal>
#include <QObject>
#include <QApplication>
#include <QThread>
#include <QMutex>
#include <QSet>
#include <QMap>
@ -52,7 +56,14 @@ const char *CollectionBackend::kSettingsGroup = "Collection";
CollectionBackend::CollectionBackend(QObject *parent) :
CollectionBackendInterface(parent),
db_(nullptr) {}
db_(nullptr),
original_thread_(nullptr) {
original_thread_ = thread();
}
CollectionBackend::~CollectionBackend() {}
void CollectionBackend::Init(Database *db, const Song::Source source, const QString &songs_table, const QString &dirs_table, const QString &subdirs_table, const QString &fts_table) {
db_ = db;
@ -63,6 +74,29 @@ void CollectionBackend::Init(Database *db, const Song::Source source, const QStr
fts_table_ = fts_table;
}
void CollectionBackend::Close() {
if (db_) {
QMutexLocker l(db_->Mutex());
db_->Close();
}
}
void CollectionBackend::ExitAsync() {
metaObject()->invokeMethod(this, "Exit", Qt::QueuedConnection);
}
void CollectionBackend::Exit() {
assert(QThread::currentThread() == thread());
Close();
moveToThread(original_thread_);
emit ExitFinished();
}
void CollectionBackend::LoadDirectoriesAsync() {
metaObject()->invokeMethod(this, "LoadDirectories", Qt::QueuedConnection);
}

View File

@ -41,6 +41,7 @@
#include "collectionquery.h"
#include "directory.h"
class QThread;
class Database;
class CollectionBackendInterface : public QObject {
@ -123,7 +124,12 @@ class CollectionBackend : public CollectionBackendInterface {
static const char *kSettingsGroup;
Q_INVOKABLE CollectionBackend(QObject *parent = nullptr);
~CollectionBackend();
void Init(Database *db, const Song::Source source, const QString &songs_table, const QString &dirs_table, const QString &subdirs_table, const QString &fts_table);
void Close();
void ExitAsync();
Database *db() const { return db_; }
@ -183,6 +189,7 @@ class CollectionBackend : public CollectionBackendInterface {
SongList GetSongsBySongId(const QStringList &song_ids);
public slots:
void Exit();
void LoadDirectories();
void UpdateTotalSongCount();
void UpdateTotalArtistCount();
@ -200,7 +207,7 @@ class CollectionBackend : public CollectionBackendInterface {
void ResetStatistics(int id);
void SongPathChanged(const Song &song, const QFileInfo &new_file);
signals:
signals:
void DirectoryDiscovered(const Directory &dir, const SubdirectoryList &subdirs);
void DirectoryDeleted(const Directory &dir);
@ -214,6 +221,8 @@ signals:
void TotalArtistCountUpdated(int total);
void TotalAlbumCountUpdated(int total);
void ExitFinished();
private:
struct CompilationInfo {
CompilationInfo() : has_compilation_detected(false), has_not_compilation_detected(false) {}
@ -243,6 +252,7 @@ signals:
QString dirs_table_;
QString subdirs_table_;
QString fts_table_;
QThread *original_thread_;
};

View File

@ -129,7 +129,10 @@ CollectionModel::CollectionModel(CollectionBackend *backend, Application *app, Q
}
CollectionModel::~CollectionModel() { delete root_; }
CollectionModel::~CollectionModel() {
backend_->Close();
delete root_;
}
void CollectionModel::set_pretty_covers(bool use_pretty_covers) {
@ -757,6 +760,11 @@ CollectionModel::QueryResult CollectionModel::RunQuery(CollectionItem *parent) {
while (q.Next()) {
result.rows << SqlRow(q);
}
if (QThread::currentThread() != thread() && QThread::currentThread() != backend_->thread()) {
backend_->Close();
}
return result;
}

View File

@ -20,7 +20,11 @@
#include "config.h"
#include <assert.h>
#include <QObject>
#include <QApplication>
#include <QThread>
#include <QIODevice>
#include <QDir>
#include <QDirIterator>
@ -79,7 +83,10 @@ CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
rescan_timer_(new QTimer(this)),
rescan_paused_(false),
total_watches_(0),
cue_parser_(new CueParser(backend_, this)) {
cue_parser_(new CueParser(backend_, this)),
original_thread_(nullptr) {
original_thread_ = thread();
rescan_timer_->setInterval(1000);
rescan_timer_->setSingleShot(true);
@ -93,6 +100,21 @@ CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
connect(rescan_timer_, SIGNAL(timeout()), SLOT(RescanPathsNow()));
}
void CollectionWatcher::ExitAsync() {
metaObject()->invokeMethod(this, "Exit", Qt::QueuedConnection);
}
void CollectionWatcher::Exit() {
assert(QThread::currentThread() == thread());
Stop();
if (backend_) backend_->Close();
moveToThread(original_thread_);
emit ExitFinished();
}
CollectionWatcher::ScanTransaction::ScanTransaction(CollectionWatcher *watcher, const int dir, const bool incremental, const bool ignores_mtime, const bool prevent_delete)
: progress_(0),
progress_max_(0),

View File

@ -60,6 +60,8 @@ class CollectionWatcher : public QObject {
void Stop() { stop_requested_ = true; }
void ExitAsync();
signals:
void NewOrUpdatedSongs(const SongList &songs);
void SongsMTimeUpdated(const SongList &songs);
@ -68,6 +70,7 @@ signals:
void SubdirsDiscovered(const SubdirectoryList &subdirs);
void SubdirsMTimeUpdated(const SubdirectoryList &subdirs);
void CompilationsNeedUpdating();
void ExitFinished();
void ScanStarted(int task_id);
@ -142,6 +145,7 @@ signals:
};
private slots:
void Exit();
void DirectoryChanged(const QString &path);
void IncrementalScanNow();
void FullScanNow();
@ -204,6 +208,8 @@ signals:
SongList song_rescan_queue_; // Set by ui thread
QThread *original_thread_;
};
inline QString CollectionWatcher::NoExtensionPart(const QString& fileName) {

View File

@ -223,10 +223,12 @@ Application::~Application() {
for (QThread *thread : threads_) {
thread->wait();
thread->deleteLater();
}
}
void Application::MoveToNewThread(QObject *object) {
QThread *Application::MoveToNewThread(QObject *object) {
QThread *thread = new QThread(this);
@ -234,6 +236,9 @@ void Application::MoveToNewThread(QObject *object) {
thread->start();
threads_ << thread;
return thread;
}
void Application::MoveToThread(QObject *object, QThread *thread) {
@ -241,6 +246,38 @@ void Application::MoveToThread(QObject *object, QThread *thread) {
object->moveToThread(thread);
}
void Application::Exit() {
wait_for_exit_ << collection()
<< playlist_backend()
<< device_manager()
<< internet_services();
connect(collection(), SIGNAL(ExitFinished()), this, SLOT(ExitReceived()));
collection()->Exit();
connect(playlist_backend(), SIGNAL(ExitFinished()), this, SLOT(ExitReceived()));
playlist_backend()->ExitAsync();
connect(device_manager(), SIGNAL(ExitFinished()), this, SLOT(ExitReceived()));
device_manager()->Exit();
connect(internet_services(), SIGNAL(ExitFinished()), this, SLOT(ExitReceived()));
internet_services()->Exit();
database()->Close();
}
void Application::ExitReceived() {
disconnect(sender(), 0, this, 0);
wait_for_exit_.removeAll(sender());
if (wait_for_exit_.isEmpty()) emit ExitFinished();
}
void Application::AddError(const QString& message) { emit ErrorAdded(message); }
void Application::ReloadSettings() { emit SettingsChanged(); }
void Application::OpenSettingsDialogAtPage(SettingsDialog::Page page) { emit SettingsDialogRequested(page); }

View File

@ -111,9 +111,14 @@ class Application : public QObject {
MoodbarLoader *moodbar_loader() const;
#endif
void MoveToNewThread(QObject *object);
void Exit();
QThread *MoveToNewThread(QObject *object);
void MoveToThread(QObject *object, QThread *thread);
private slots:
void ExitReceived();
public slots:
void AddError(const QString &message);
void ReloadSettings();
@ -123,10 +128,12 @@ signals:
void ErrorAdded(const QString &message);
void SettingsChanged();
void SettingsDialogRequested(SettingsDialog::Page page);
void ExitFinished();
private:
std::unique_ptr<ApplicationImpl> p_;
QList<QThread*> threads_;
QList<QObject*> wait_for_exit_;
};

View File

@ -211,6 +211,7 @@ int Database::FTSNext(sqlite3_tokenizer_cursor *cursor, const char* *token, int
void Database::StaticInit() {
if (sFTSTokenizer) return;
sFTSTokenizer = new sqlite3_tokenizer_module;
sFTSTokenizer->iVersion = 0;
sFTSTokenizer->xCreate = &Database::FTSCreate;
@ -242,7 +243,21 @@ Database::Database(Application *app, QObject *parent, const QString &database_na
}
Database::~Database() {}
Database::~Database() {
QMutexLocker l(&connect_mutex_);
for (QString connection : connections_) {
qLog(Error) << connection << "still open!";
}
if (!connections_.isEmpty())
qLog(Error) << connections_.count() << "connections still open!";
if (sFTSTokenizer)
delete sFTSTokenizer;
}
QSqlDatabase Database::Connect() {
@ -257,6 +272,11 @@ QSqlDatabase Database::Connect() {
const QString connection_id = QString("%1_thread_%2").arg(connection_id_).arg(reinterpret_cast<quint64>(QThread::currentThread()));
if (!connections_.contains(connection_id)) {
//qLog(Debug) << "Opened database with connection id" << connection_id;
connections_ << connection_id;
}
// Try to find an existing connection for this thread
QSqlDatabase db = QSqlDatabase::database(connection_id);
if (db.isOpen()) {
@ -346,6 +366,25 @@ QSqlDatabase Database::Connect() {
}
void Database::Close() {
QMutexLocker l(&connect_mutex_);
const QString connection_id = QString("%1_thread_%2").arg(connection_id_).arg(reinterpret_cast<quint64>(QThread::currentThread()));
// Try to find an existing connection for this thread
QSqlDatabase db = QSqlDatabase::database(connection_id);
if (db.isOpen()) {
db.close();
}
if (connections_.contains(connection_id)) {
//qLog(Debug) << "Closed database with connection id" << connection_id;
connections_.removeAll(connection_id);
}
}
void Database::UpdateMainSchema(QSqlDatabase *db) {
// Get the database's schema version

View File

@ -68,6 +68,7 @@ class Database : public QObject {
static const char *kMagicAllSongsTables;
QSqlDatabase Connect();
void Close();
bool CheckErrors(const QSqlQuery &query);
QMutex *Mutex() { return &mutex_; }
@ -111,6 +112,7 @@ signals:
// This ID makes the QSqlDatabase name unique to the object as well as the thread
int connection_id_;
QStringList connections_;
static QMutex sNextConnectionIdMutex;
static int sNextConnectionId;

View File

@ -55,7 +55,7 @@ void DeleteFiles::Start(const SongList &songs) {
task_id_ = task_manager_->StartTask(tr("Deleting files"));
task_manager_->SetTaskBlocksCollectionScans(true);
thread_ = new QThread;
thread_ = new QThread(this);
connect(thread_, SIGNAL(started()), SLOT(ProcessSomeFiles()));
moveToThread(thread_);

View File

@ -989,7 +989,7 @@ void MainWindow::Exit() {
if (app_->player()->engine()->is_fadeout_enabled()) {
// To shut down the application when fadeout will be finished
connect(app_->player()->engine(), SIGNAL(FadeoutFinishedSignal()), qApp, SLOT(quit()));
connect(app_->player()->engine(), SIGNAL(FadeoutFinishedSignal()), this, SLOT(DoExit()));
if (app_->player()->GetState() == Engine::Playing) {
app_->player()->Stop();
hide();
@ -998,6 +998,19 @@ void MainWindow::Exit() {
}
}
DoExit();
}
void MainWindow::DoExit() {
connect(app_, SIGNAL(ExitFinished()), this, SLOT(ExitFinished()));
app_->Exit();
}
void MainWindow::ExitFinished() {
qApp->quit();
}

View File

@ -243,6 +243,7 @@ signals:
void Raise();
void Exit();
void DoExit();
void HandleNotificationPreview(OSD::Behaviour type, QString line1, QString line2);
@ -262,6 +263,8 @@ signals:
void LoveButtonVisibilityChanged(bool value);
void Love();
void ExitFinished();
private:
void SaveSettings();

View File

@ -52,7 +52,10 @@ CurrentAlbumCoverLoader::CurrentAlbumCoverLoader(Application *app, QObject *pare
}
CurrentAlbumCoverLoader::~CurrentAlbumCoverLoader() {}
CurrentAlbumCoverLoader::~CurrentAlbumCoverLoader() {
if (temp_cover_) temp_cover_->remove();
if (temp_cover_thumbnail_) temp_cover_thumbnail_->remove();
}
void CurrentAlbumCoverLoader::LoadAlbumCover(const Song &song) {
last_song_ = song;

View File

@ -18,9 +18,14 @@
*
*/
#include "config.h"
#include <stdbool.h>
#include <assert.h>
#include <QObject>
#include <QApplication>
#include <QThread>
#include <QMutex>
#include <QIODevice>
#include <QFile>
@ -32,17 +37,47 @@
#include "core/database.h"
#include "core/scopedtransaction.h"
#include "core/logging.h"
#include "devicedatabasebackend.h"
const int DeviceDatabaseBackend::kDeviceSchemaVersion = 0;
DeviceDatabaseBackend::DeviceDatabaseBackend(QObject *parent) :
QObject(parent),
db_(nullptr)
{}
db_(nullptr),
original_thread_(nullptr)
{
original_thread_ = thread();
}
DeviceDatabaseBackend::~DeviceDatabaseBackend() {}
void DeviceDatabaseBackend::Init(Database* db) { db_ = db; }
void DeviceDatabaseBackend::Close() {
if (db_) {
QMutexLocker l(db_->Mutex());
db_->Close();
}
}
void DeviceDatabaseBackend::ExitAsync() {
metaObject()->invokeMethod(this, "Exit", Qt::QueuedConnection);
}
void DeviceDatabaseBackend::Exit() {
assert(QThread::currentThread() == thread());
Close();
moveToThread(original_thread_);
emit ExitFinished();
}
DeviceDatabaseBackend::DeviceList DeviceDatabaseBackend::GetAllDevices() {
QMutexLocker l(db_->Mutex());
@ -101,6 +136,7 @@ int DeviceDatabaseBackend::AddDevice(const Device &device) {
db_->ExecSchemaCommands(db, schema, 0, true);
t.Commit();
return id;
}

View File

@ -40,6 +40,7 @@ class DeviceDatabaseBackend : public QObject {
public:
Q_INVOKABLE DeviceDatabaseBackend(QObject *parent = nullptr);
~DeviceDatabaseBackend();
struct Device {
Device() : id_(-1) {}
@ -58,6 +59,9 @@ class DeviceDatabaseBackend : public QObject {
static const int kDeviceSchemaVersion;
void Init(Database *db);
void Close();
void ExitAsync();
Database *db() const { return db_; }
DeviceList GetAllDevices();
@ -66,8 +70,16 @@ class DeviceDatabaseBackend : public QObject {
void SetDeviceOptions(int id, const QString &friendly_name, const QString &icon_name, MusicStorage::TranscodeMode mode, Song::FileType format);
private slots:
void Exit();
signals:
void ExitFinished();
private:
Database *db_;
QThread *original_thread_;
};
#endif // DEVICEDATABASEBACKEND_H

View File

@ -158,9 +158,17 @@ DeviceManager::~DeviceManager() {
delete lister;
}
backend_->deleteLater();
backend_->Close();
delete root_;
delete backend_;
}
void DeviceManager::Exit() {
connect(backend_, SIGNAL(ExitFinished()), this, SIGNAL(ExitFinished()));
backend_->ExitAsync();
}
@ -174,6 +182,7 @@ void DeviceManager::LoadAllDevices() {
info->InitFromDb(device);
emit DeviceCreatedFromDB(info);
}
backend_->Close();
}

View File

@ -85,6 +85,8 @@ class DeviceManager : public SimpleTreeModel<DeviceInfo> {
static const int kDeviceIconSize;
static const int kDeviceIconOverlaySize;
void Exit();
DeviceStateFilterModel *connected_devices_model() const { return connected_devices_model_; }
// Get info about devices
@ -115,6 +117,7 @@ class DeviceManager : public SimpleTreeModel<DeviceInfo> {
void Unmount(QModelIndex idx);
signals:
void ExitFinished();
void DeviceConnected(QModelIndex idx);
void DeviceDisconnected(QModelIndex idx);
void DeviceCreatedFromDB(DeviceInfo* info);

View File

@ -102,6 +102,8 @@ Itdb_iTunesDB *GPodLoader::TryLoad() {
// Add the songs we've just loaded
backend_->AddOrUpdateSongs(songs);
backend_->Close();
return db;
}

View File

@ -96,6 +96,8 @@ bool MtpLoader::TryLoad() {
// Add the songs we've just loaded
backend_->AddOrUpdateSongs(songs);
backend_->Close();
return true;
}

View File

@ -40,6 +40,7 @@ class InternetService : public QObject {
InternetService(Song::Source source, const QString &name, const QString &url_scheme, Application *app, QObject *parent = nullptr);
virtual ~InternetService() {}
virtual void Exit() {}
virtual Song::Source source() const { return source_; }
virtual QString name() const { return name_; }
@ -75,6 +76,7 @@ class InternetService : public QObject {
virtual void ResetSongsRequest() {}
signals:
void ExitFinished();
void Login();
void Logout();
void Login(const QString &api_token, const QString &username, const QString &password);

View File

@ -72,3 +72,22 @@ void InternetServices::ReloadSettings() {
service->ReloadSettings();
}
}
void InternetServices::Exit() {
for (InternetService *service : services_.values()) {
wait_for_exit_ << service;
connect(service, SIGNAL(ExitFinished()), this, SLOT(ExitReceived()));
service->Exit();
}
}
void InternetServices::ExitReceived() {
InternetService *service = qobject_cast<InternetService*>(sender());
wait_for_exit_.removeAll(service);
if (wait_for_exit_.isEmpty()) emit ExitFinished();
}

View File

@ -47,9 +47,17 @@ class InternetServices : public QObject {
void AddService(InternetService *service);
void RemoveService(InternetService *service);
void ReloadSettings();
void Exit();
signals:
void ExitFinished();
private slots:
void ExitReceived();
private:
QMap<Song::Source, InternetService*> services_;
QList<InternetService*> wait_for_exit_;
};

View File

@ -115,7 +115,7 @@ void MusicBrainzClient::Cancel(int id) {
while (!requests_.isEmpty() && requests_.contains(id)) {
QNetworkReply *reply = requests_.take(id);
disconnect(reply, 0, nullptr, 0);
disconnect(reply, 0, this, 0);
if (reply->isRunning()) reply->abort();
reply->deleteLater();
}

View File

@ -20,8 +20,11 @@
#include <memory>
#include <functional>
#include <assert.h>
#include <QObject>
#include <QApplication>
#include <QThread>
#include <QMutex>
#include <QIODevice>
#include <QDir>
@ -56,7 +59,36 @@ using std::shared_ptr;
const int PlaylistBackend::kSongTableJoins = 2;
PlaylistBackend::PlaylistBackend(Application *app, QObject *parent)
: QObject(parent), app_(app), db_(app_->database()) {}
: QObject(parent), app_(app), db_(app_->database()), original_thread_(nullptr) {
original_thread_ = thread();
}
PlaylistBackend::~PlaylistBackend() {}
void PlaylistBackend::Close() {
if (db_) {
QMutexLocker l(db_->Mutex());
db_->Close();
}
}
void PlaylistBackend::ExitAsync() {
metaObject()->invokeMethod(this, "Exit", Qt::QueuedConnection);
}
void PlaylistBackend::Exit() {
assert(QThread::currentThread() == thread());
Close();
moveToThread(original_thread_);
emit ExitFinished();
}
PlaylistBackend::PlaylistList PlaylistBackend::GetAllPlaylists() {
return GetPlaylists(GetPlaylists_All);
@ -157,6 +189,10 @@ QSqlQuery PlaylistBackend::GetPlaylistRows(int playlist) {
q.bindValue(":playlist", playlist);
q.exec();
if (QThread::currentThread() != thread() && QThread::currentThread() != qApp->thread()) {
db_->Close();
}
return q;
}

View File

@ -39,6 +39,7 @@
#include "collection/sqlrow.h"
#include "playlistitem.h"
class QThread;
class Application;
class Database;
@ -47,6 +48,7 @@ class PlaylistBackend : public QObject {
public:
Q_INVOKABLE PlaylistBackend(Application *app, QObject *parent = nullptr);
~PlaylistBackend();
struct Playlist {
Playlist() : id(-1), favorite(false), last_played(0) {}
@ -62,6 +64,9 @@ class PlaylistBackend : public QObject {
static const int kSongTableJoins;
void Close();
void ExitAsync();
PlaylistList GetAllPlaylists();
PlaylistList GetAllOpenPlaylists();
PlaylistList GetAllFavoritePlaylists();
@ -82,8 +87,12 @@ class PlaylistBackend : public QObject {
Application *app() const { return app_; }
public slots:
void Exit();
void SavePlaylist(int playlist, const PlaylistItemList &items, int last_played);
signals:
void ExitFinished();
private:
struct NewSongFromQueryState {
QHash<QString, SongList> cached_cues_;
@ -105,6 +114,7 @@ class PlaylistBackend : public QObject {
Application *app_;
Database *db_;
QThread *original_thread_;
};
#endif // PLAYLISTBACKEND_H

View File

@ -22,6 +22,7 @@
#include "config.h"
#include <QtGlobal>
#include <QApplication>
#include <QObject>
#include <QWidget>
#include <QtConcurrentRun>
@ -363,6 +364,10 @@ TagCompletionModel::TagCompletionModel(CollectionBackend *backend, Playlist::Col
setStringList(backend->GetAll(col));
}
if (QThread::currentThread() != backend->thread() && QThread::currentThread() != qApp->thread()) {
backend->Close();
}
}
QString TagCompletionModel::database_column(Playlist::Column column) {

View File

@ -80,6 +80,7 @@ class PlaylistHeader;
class PlaylistProxyStyle : public QProxyStyle {
public:
PlaylistProxyStyle(QStyle *base);
void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const;
void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const;

View File

@ -51,7 +51,7 @@ QobuzBaseRequest::~QobuzBaseRequest() {
while (!replies_.isEmpty()) {
QNetworkReply *reply = replies_.takeFirst();
disconnect(reply, 0, nullptr, 0);
disconnect(reply, 0, this, 0);
if (reply->isRunning()) reply->abort();
reply->deleteLater();
}

View File

@ -49,7 +49,7 @@ QobuzFavoriteRequest::~QobuzFavoriteRequest() {
while (!replies_.isEmpty()) {
QNetworkReply *reply = replies_.takeFirst();
disconnect(reply, 0, nullptr, 0);
disconnect(reply, 0, this, 0);
reply->abort();
reply->deleteLater();
}

View File

@ -77,7 +77,7 @@ QobuzRequest::~QobuzRequest() {
while (!album_cover_replies_.isEmpty()) {
QNetworkReply *reply = album_cover_replies_.takeFirst();
disconnect(reply, 0, nullptr, 0);
disconnect(reply, 0, this, 0);
if (reply->isRunning()) reply->abort();
reply->deleteLater();
}

View File

@ -176,13 +176,35 @@ QobuzService::~QobuzService() {
while (!stream_url_requests_.isEmpty()) {
QobuzStreamURLRequest *stream_url_req = stream_url_requests_.takeFirst();
disconnect(stream_url_req, 0, nullptr, 0);
disconnect(stream_url_req, 0, this, 0);
stream_url_req->deleteLater();
}
artists_collection_backend_->deleteLater();
albums_collection_backend_->deleteLater();
songs_collection_backend_->deleteLater();
delete artists_collection_backend_;
delete albums_collection_backend_;
delete songs_collection_backend_;
}
void QobuzService::Exit() {
wait_for_exit_ << artists_collection_backend_ << albums_collection_backend_ << songs_collection_backend_;
connect(artists_collection_backend_, SIGNAL(ExitFinished()), this, SLOT(ExitReceived()));
connect(albums_collection_backend_, SIGNAL(ExitFinished()), this, SLOT(ExitReceived()));
connect(songs_collection_backend_, SIGNAL(ExitFinished()), this, SLOT(ExitReceived()));
artists_collection_backend_->ExitAsync();
albums_collection_backend_->ExitAsync();
songs_collection_backend_->ExitAsync();
}
void QobuzService::ExitReceived() {
disconnect(sender(), 0, this, 0);
wait_for_exit_.removeAll(sender());
if (wait_for_exit_.isEmpty()) emit ExitFinished();
}
@ -395,7 +417,7 @@ void QobuzService::TryLogin() {
void QobuzService::ResetArtistsRequest() {
if (artists_request_.get()) {
disconnect(artists_request_.get(), 0, nullptr, 0);
disconnect(artists_request_.get(), 0, this, 0);
disconnect(this, 0, artists_request_.get(), 0);
artists_request_.reset();
}
@ -446,7 +468,7 @@ void QobuzService::ArtistsUpdateProgressReceived(const int id, const int progres
void QobuzService::ResetAlbumsRequest() {
if (albums_request_.get()) {
disconnect(albums_request_.get(), 0, nullptr, 0);
disconnect(albums_request_.get(), 0, this, 0);
disconnect(this, 0, albums_request_.get(), 0);
albums_request_.reset();
}
@ -495,7 +517,7 @@ void QobuzService::AlbumsUpdateProgressReceived(const int id, const int progress
void QobuzService::ResetSongsRequest() {
if (songs_request_.get()) {
disconnect(songs_request_.get(), 0, nullptr, 0);
disconnect(songs_request_.get(), 0, this, 0);
disconnect(this, 0, songs_request_.get(), 0);
songs_request_.reset();
}

View File

@ -40,6 +40,7 @@
#include "internet/internetsearch.h"
#include "settings/qobuzsettingspage.h"
class QThread;
class Application;
class NetworkAccessManager;
class QobuzUrlHandler;
@ -60,6 +61,7 @@ class QobuzService : public InternetService {
static const Song::Source kSource;
void Exit();
void ReloadSettings();
void Logout();
@ -123,6 +125,7 @@ class QobuzService : public InternetService {
void ResetSongsRequest();
private slots:
void ExitReceived();
void SendLogin();
void HandleLoginSSLErrors(QList<QSslError> ssl_errors);
void HandleAuthReply(QNetworkReply *reply);
@ -217,6 +220,8 @@ class QobuzService : public InternetService {
QStringList login_errors_;
QList<QObject*> wait_for_exit_;
};
#endif // QOBUZSERVICE_H

View File

@ -54,7 +54,7 @@ QobuzStreamURLRequest::QobuzStreamURLRequest(QobuzService *service, NetworkAcces
QobuzStreamURLRequest::~QobuzStreamURLRequest() {
if (reply_) {
disconnect(reply_, 0, nullptr, 0);
disconnect(reply_, 0, this, 0);
if (reply_->isRunning()) reply_->abort();
reply_->deleteLater();
}
@ -107,7 +107,7 @@ void QobuzStreamURLRequest::GetStreamURL() {
++tries_;
if (reply_) {
disconnect(reply_, 0, nullptr, 0);
disconnect(reply_, 0, this, 0);
if (reply_->isRunning()) reply_->abort();
reply_->deleteLater();
}
@ -147,7 +147,7 @@ void QobuzStreamURLRequest::GetStreamURL() {
void QobuzStreamURLRequest::StreamURLReceived() {
if (!reply_) return;
disconnect(reply_, 0, nullptr, 0);
disconnect(reply_, 0, this, 0);
reply_->deleteLater();
QByteArray data = GetReplyData(reply_);

View File

@ -50,7 +50,7 @@ SubsonicBaseRequest::~SubsonicBaseRequest() {
while (!replies_.isEmpty()) {
QNetworkReply *reply = replies_.takeFirst();
disconnect(reply, 0, nullptr, 0);
disconnect(reply, 0, this, 0);
if (reply->isRunning()) reply->abort();
reply->deleteLater();
}

View File

@ -71,7 +71,7 @@ SubsonicRequest::~SubsonicRequest() {
while (!album_cover_replies_.isEmpty()) {
QNetworkReply *reply = album_cover_replies_.takeFirst();
disconnect(reply, 0, nullptr, 0);
disconnect(reply, 0, this, 0);
if (reply->isRunning()) reply->abort();
reply->deleteLater();
}

View File

@ -95,7 +95,14 @@ SubsonicService::SubsonicService(Application *app, QObject *parent)
}
SubsonicService::~SubsonicService() {
collection_backend_->deleteLater();
delete collection_backend_;
}
void SubsonicService::Exit() {
connect(collection_backend_, SIGNAL(ExitFinished()), this, SIGNAL(ExitFinished()));
collection_backend_->ExitAsync();
}
void SubsonicService::ShowConfig() {
@ -317,8 +324,8 @@ void SubsonicService::CheckConfiguration() {
void SubsonicService::ResetSongsRequest() {
if (songs_request_.get()) {
disconnect(songs_request_.get(), 0, nullptr, 0);
if (songs_request_.get()) { // WARNING: Don't disconnect everything. NewClosure() relies on destroyed()!!!
disconnect(songs_request_.get(), 0, this, 0);
disconnect(this, 0, songs_request_.get(), 0);
songs_request_.reset();
}

View File

@ -60,6 +60,7 @@ class SubsonicService : public InternetService {
static const Song::Source kSource;
void ReloadSettings();
void Exit();
Application *app() { return app_; }

View File

@ -52,7 +52,7 @@ TidalBaseRequest::~TidalBaseRequest() {
while (!replies_.isEmpty()) {
QNetworkReply *reply = replies_.takeFirst();
disconnect(reply, 0, nullptr, 0);
disconnect(reply, 0, this, 0);
if (reply->isRunning()) reply->abort();
reply->deleteLater();
}

View File

@ -50,7 +50,7 @@ TidalFavoriteRequest::~TidalFavoriteRequest() {
while (!replies_.isEmpty()) {
QNetworkReply *reply = replies_.takeFirst();
disconnect(reply, 0, nullptr, 0);
disconnect(reply, 0, this, 0);
reply->abort();
reply->deleteLater();
}

View File

@ -80,7 +80,7 @@ TidalRequest::~TidalRequest() {
while (!album_cover_replies_.isEmpty()) {
QNetworkReply *reply = album_cover_replies_.takeFirst();
disconnect(reply, 0, nullptr, 0);
disconnect(reply, 0, this, 0);
if (reply->isRunning()) reply->abort();
reply->deleteLater();
}

View File

@ -182,13 +182,35 @@ TidalService::~TidalService() {
while (!stream_url_requests_.isEmpty()) {
TidalStreamURLRequest *stream_url_req = stream_url_requests_.takeFirst();
disconnect(stream_url_req, 0, nullptr, 0);
disconnect(stream_url_req, 0, this, 0);
stream_url_req->deleteLater();
}
artists_collection_backend_->deleteLater();
albums_collection_backend_->deleteLater();
songs_collection_backend_->deleteLater();
delete artists_collection_backend_;
delete albums_collection_backend_;
delete songs_collection_backend_;
}
void TidalService::Exit() {
wait_for_exit_ << artists_collection_backend_ << albums_collection_backend_ << songs_collection_backend_;
connect(artists_collection_backend_, SIGNAL(ExitFinished()), this, SLOT(ExitReceived()));
connect(albums_collection_backend_, SIGNAL(ExitFinished()), this, SLOT(ExitReceived()));
connect(songs_collection_backend_, SIGNAL(ExitFinished()), this, SLOT(ExitReceived()));
artists_collection_backend_->ExitAsync();
albums_collection_backend_->ExitAsync();
songs_collection_backend_->ExitAsync();
}
void TidalService::ExitReceived() {
disconnect(sender(), 0, this, 0);
wait_for_exit_.removeAll(sender());
if (wait_for_exit_.isEmpty()) emit ExitFinished();
}
@ -639,7 +661,7 @@ void TidalService::TryLogin() {
void TidalService::ResetArtistsRequest() {
if (artists_request_.get()) {
disconnect(artists_request_.get(), 0, nullptr, 0);
disconnect(artists_request_.get(), 0, this, 0);
disconnect(this, 0, artists_request_.get(), 0);
artists_request_.reset();
}
@ -694,7 +716,7 @@ void TidalService::ArtistsUpdateProgressReceived(const int id, const int progres
void TidalService::ResetAlbumsRequest() {
if (albums_request_.get()) {
disconnect(albums_request_.get(), 0, nullptr, 0);
disconnect(albums_request_.get(), 0, this, 0);
disconnect(this, 0, albums_request_.get(), 0);
albums_request_.reset();
}
@ -747,7 +769,7 @@ void TidalService::AlbumsUpdateProgressReceived(const int id, const int progress
void TidalService::ResetSongsRequest() {
if (songs_request_.get()) {
disconnect(songs_request_.get(), 0, nullptr, 0);
disconnect(songs_request_.get(), 0, this, 0);
disconnect(this, 0, songs_request_.get(), 0);
songs_request_.reset();
}

View File

@ -61,6 +61,7 @@ class TidalService : public InternetService {
static const Song::Source kSource;
void Exit();
void ReloadSettings();
void Logout();
@ -132,6 +133,7 @@ class TidalService : public InternetService {
void ResetSongsRequest();
private slots:
void ExitReceived();
void StartAuthorisation();
void AuthorisationUrlReceived(const QUrl &url);
void HandleLoginSSLErrors(QList<QSslError> ssl_errors);
@ -245,6 +247,8 @@ class TidalService : public InternetService {
QStringList login_errors_;
QList<QObject*> wait_for_exit_;
};
#endif // TIDALSERVICE_H

View File

@ -52,7 +52,7 @@ TidalStreamURLRequest::TidalStreamURLRequest(TidalService *service, NetworkAcces
TidalStreamURLRequest::~TidalStreamURLRequest() {
if (reply_) {
disconnect(reply_, 0, nullptr, 0);
disconnect(reply_, 0, this, 0);
if (reply_->isRunning()) reply_->abort();
reply_->deleteLater();
}
@ -109,7 +109,7 @@ void TidalStreamURLRequest::GetStreamURL() {
++tries_;
if (reply_) {
disconnect(reply_, 0, nullptr, 0);
disconnect(reply_, 0, this, 0);
if (reply_->isRunning()) reply_->abort();
reply_->deleteLater();
}
@ -144,7 +144,7 @@ void TidalStreamURLRequest::GetStreamURL() {
void TidalStreamURLRequest::StreamURLReceived() {
if (!reply_) return;
disconnect(reply_, 0, nullptr, 0);
disconnect(reply_, 0, this, 0);
reply_->deleteLater();
QByteArray data = GetReplyData(reply_, true);