diff --git a/src/collection/collection.cpp b/src/collection/collection.cpp index 86256eab9..8e664acc6 100644 --- a/src/collection/collection.cpp +++ b/src/collection/collection.cpp @@ -127,8 +127,10 @@ void SCollection::Exit() { void SCollection::ExitReceived() { - disconnect(sender(), 0, this, 0); - wait_for_exit_.removeAll(sender()); + QObject *obj = static_cast(sender()); + disconnect(obj, 0, this, 0); + qLog(Debug) << obj << "successfully exited."; + wait_for_exit_.removeAll(obj); if (wait_for_exit_.isEmpty()) emit ExitFinished(); } diff --git a/src/core/application.cpp b/src/core/application.cpp index 205918b78..19c9f4d96 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -248,19 +248,27 @@ void Application::MoveToThread(QObject *object, QThread *thread) { void Application::Exit() { - wait_for_exit_ << collection() + wait_for_exit_ << tag_reader_client() + << collection() << playlist_backend() + << album_cover_loader() #ifndef Q_OS_WIN << device_manager() #endif << internet_services(); + connect(tag_reader_client(), SIGNAL(ExitFinished()), this, SLOT(ExitReceived())); + tag_reader_client()->ExitAsync(); + connect(collection(), SIGNAL(ExitFinished()), this, SLOT(ExitReceived())); collection()->Exit(); connect(playlist_backend(), SIGNAL(ExitFinished()), this, SLOT(ExitReceived())); playlist_backend()->ExitAsync(); + connect(album_cover_loader(), SIGNAL(ExitFinished()), this, SLOT(ExitReceived())); + album_cover_loader()->ExitAsync(); + #ifndef Q_OS_WIN connect(device_manager(), SIGNAL(ExitFinished()), this, SLOT(ExitReceived())); device_manager()->Exit(); @@ -269,16 +277,21 @@ void Application::Exit() { connect(internet_services(), SIGNAL(ExitFinished()), this, SLOT(ExitReceived())); internet_services()->Exit(); - database()->Close(); - } void Application::ExitReceived() { - disconnect(sender(), 0, this, 0); + QObject *obj = static_cast(sender()); + disconnect(obj, 0, this, 0); - wait_for_exit_.removeAll(sender()); - if (wait_for_exit_.isEmpty()) emit ExitFinished(); + qLog(Debug) << obj << "successfully exited."; + + wait_for_exit_.removeAll(obj); + if (wait_for_exit_.isEmpty()) { + database()->Close(); + connect(database(), SIGNAL(ExitFinished()), this, SIGNAL(ExitFinished())); + database()->ExitAsync(); + } } diff --git a/src/core/database.cpp b/src/core/database.cpp index 1739f0a12..28e2e9dc5 100644 --- a/src/core/database.cpp +++ b/src/core/database.cpp @@ -229,7 +229,10 @@ Database::Database(Application *app, QObject *parent, const QString &database_na mutex_(QMutex::Recursive), injected_database_name_(database_name), query_hash_(0), - startup_schema_version_(-1) { + startup_schema_version_(-1), + original_thread_(nullptr) { + + original_thread_ = thread(); { QMutexLocker l(&sNextConnectionIdMutex); @@ -259,6 +262,19 @@ Database::~Database() { } +void Database::ExitAsync() { + metaObject()->invokeMethod(this, "Exit", Qt::QueuedConnection); +} + +void Database::Exit() { + + assert(QThread::currentThread() == thread()); + Close(); + moveToThread(original_thread_); + emit ExitFinished(); + +} + QSqlDatabase Database::Connect() { QMutexLocker l(&connect_mutex_); diff --git a/src/core/database.h b/src/core/database.h index 029420c4c..77d636e99 100644 --- a/src/core/database.h +++ b/src/core/database.h @@ -44,6 +44,7 @@ struct sqlite3_tokenizer_cursor; struct sqlite3_tokenizer_module; } +class QThread; class Application; class Database : public QObject { @@ -67,6 +68,7 @@ class Database : public QObject { static const char *kDatabaseFilename; static const char *kMagicAllSongsTables; + void ExitAsync(); QSqlDatabase Connect(); void Close(); bool CheckErrors(const QSqlQuery &query); @@ -82,9 +84,13 @@ class Database : public QObject { void AttachDatabaseOnDbConnection(const QString &database_name, const AttachedDatabase &database, QSqlDatabase &db); void DetachDatabase(const QString &database_name); -signals: + signals: + void ExitFinished(); void Error(const QString &message); + private slots: + void Exit(); + public slots: void DoBackup(); @@ -126,6 +132,8 @@ signals: // This is the schema version of Strawberry's DB from the app's last run. int startup_schema_version_; + QThread *original_thread_; + // Do static initialisation like loading sqlite functions. static void StaticInit(); diff --git a/src/core/mainwindow.cpp b/src/core/mainwindow.cpp index b22b03b00..9ed934010 100644 --- a/src/core/mainwindow.cpp +++ b/src/core/mainwindow.cpp @@ -237,7 +237,8 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co playing_widget_(true), doubleclick_addmode_(BehaviourSettingsPage::AddBehaviour_Append), doubleclick_playmode_(BehaviourSettingsPage::PlayBehaviour_Never), - menu_playmode_(BehaviourSettingsPage::PlayBehaviour_Never) + menu_playmode_(BehaviourSettingsPage::PlayBehaviour_Never), + exit_count_(0) { qLog(Debug) << "Starting"; @@ -985,20 +986,26 @@ void MainWindow::SaveSettings() { void MainWindow::Exit() { + ++exit_count_; + SaveSettings(); - if (app_->player()->engine()->is_fadeout_enabled()) { - // To shut down the application when fadeout will be finished - connect(app_->player()->engine(), SIGNAL(FadeoutFinishedSignal()), this, SLOT(DoExit())); - if (app_->player()->GetState() == Engine::Playing) { - app_->player()->Stop(); - hide(); - if (tray_icon_) tray_icon_->SetVisible(false); - return; // Don't quit the application now: wait for the fadeout finished signal - } + if (exit_count_ > 1) { + qApp->quit(); + } + else { + if (app_->player()->engine()->is_fadeout_enabled()) { + // To shut down the application when fadeout will be finished + connect(app_->player()->engine(), SIGNAL(FadeoutFinishedSignal()), this, SLOT(DoExit())); + if (app_->player()->GetState() == Engine::Playing) { + app_->player()->Stop(); + hide(); + if (tray_icon_) tray_icon_->SetVisible(false); + return; // Don't quit the application now: wait for the fadeout finished signal + } + } + DoExit(); } - - DoExit(); } @@ -1323,8 +1330,8 @@ void MainWindow::closeEvent(QCloseEvent *event) { } else { Exit(); - QApplication::quit(); } + } void MainWindow::SetHiddenInTray(bool hidden) { diff --git a/src/core/mainwindow.h b/src/core/mainwindow.h index 37f2cfb52..8f42de0cc 100644 --- a/src/core/mainwindow.h +++ b/src/core/mainwindow.h @@ -369,6 +369,7 @@ signals: Song song_; Song song_playing_; QImage image_original_; + int exit_count_; }; diff --git a/src/core/tagreaderclient.cpp b/src/core/tagreaderclient.cpp index b38a3da70..4c41d41a9 100644 --- a/src/core/tagreaderclient.cpp +++ b/src/core/tagreaderclient.cpp @@ -42,6 +42,7 @@ TagReaderClient *TagReaderClient::sInstance = nullptr; TagReaderClient::TagReaderClient(QObject *parent) : QObject(parent), worker_pool_(new WorkerPool(this)) { sInstance = this; + original_thread_ = thread(); worker_pool_->SetExecutableName(kWorkerExecutableName); worker_pool_->SetWorkerCount(QThread::idealThreadCount()); @@ -50,6 +51,18 @@ TagReaderClient::TagReaderClient(QObject *parent) : QObject(parent), worker_pool void TagReaderClient::Start() { worker_pool_->Start(); } +void TagReaderClient::ExitAsync() { + metaObject()->invokeMethod(this, "Exit", Qt::QueuedConnection); +} + +void TagReaderClient::Exit() { + + assert(QThread::currentThread() == thread()); + moveToThread(original_thread_); + emit ExitFinished(); + +} + void TagReaderClient::WorkerFailedToStart() { qLog(Error) << "The" << kWorkerExecutableName << "executable was not found in the current directory or on the PATH. Strawberry will not be able to read music file tags without it."; } diff --git a/src/core/tagreaderclient.h b/src/core/tagreaderclient.h index d5d89aef6..f9ea8241e 100644 --- a/src/core/tagreaderclient.h +++ b/src/core/tagreaderclient.h @@ -36,6 +36,7 @@ #include "song.h" #include "tagreadermessages.pb.h" +class QThread; class Song; template class WorkerPool; @@ -51,6 +52,7 @@ class TagReaderClient : public QObject { static const char *kWorkerExecutableName; void Start(); + void ExitAsync(); ReplyType *ReadFile(const QString &filename); ReplyType *SaveFile(const QString &filename, const Song &metadata); @@ -67,7 +69,11 @@ class TagReaderClient : public QObject { // TODO: Make this not a singleton static TagReaderClient *Instance() { return sInstance; } + signals: + void ExitFinished(); + private slots: + void Exit(); void WorkerFailedToStart(); private: @@ -75,6 +81,7 @@ class TagReaderClient : public QObject { WorkerPool *worker_pool_; QList message_queue_; + QThread *original_thread_; }; typedef TagReaderClient::ReplyType TagReaderReply; diff --git a/src/covermanager/albumcoverloader.cpp b/src/covermanager/albumcoverloader.cpp index 336441868..87dfc6ce4 100644 --- a/src/covermanager/albumcoverloader.cpp +++ b/src/covermanager/albumcoverloader.cpp @@ -23,12 +23,12 @@ #include #include -#include -#include -#include #include -#include +#include +#include +#include #include +#include #include #include #include @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -61,13 +62,30 @@ AlbumCoverLoader::AlbumCoverLoader(QObject *parent) cover_filename_(CollectionSettingsPage::SaveCover_Hash), cover_overwrite_(false), cover_lowercase_(true), - cover_replace_spaces_(true) + cover_replace_spaces_(true), + original_thread_(nullptr) { + original_thread_ = thread(); ReloadSettings(); } +void AlbumCoverLoader::ExitAsync() { + + stop_requested_ = true; + metaObject()->invokeMethod(this, "Exit", Qt::QueuedConnection); + +} + +void AlbumCoverLoader::Exit() { + + assert(QThread::currentThread() == thread()); + moveToThread(original_thread_); + emit ExitFinished(); + +} + void AlbumCoverLoader::ReloadSettings() { QSettings s; diff --git a/src/covermanager/albumcoverloader.h b/src/covermanager/albumcoverloader.h index d3601623a..ae7a5f4ea 100644 --- a/src/covermanager/albumcoverloader.h +++ b/src/covermanager/albumcoverloader.h @@ -41,6 +41,7 @@ #include "settings/collectionsettingspage.h" #include "albumcoverloaderoptions.h" +class QThread; class Song; class NetworkAccessManager; @@ -52,6 +53,7 @@ class AlbumCoverLoader : public QObject { void ReloadSettings(); + void ExitAsync(); void Stop() { stop_requested_ = true; } static QString ImageCacheDir(const Song::Source source); @@ -68,11 +70,13 @@ class AlbumCoverLoader : public QObject { static QPixmap TryLoadPixmap(const QUrl &automatic, const QUrl &manual, const QUrl &url = QUrl()); static QImage ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image); -signals: + signals: + void ExitFinished(); void ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image); void ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &scaled, const QImage &original); protected slots: + void Exit(); void ProcessTasks(); void RemoteFetchFinished(QNetworkReply *reply, const QUrl &cover_url); @@ -128,6 +132,8 @@ signals: bool cover_lowercase_; bool cover_replace_spaces_; + QThread *original_thread_; + }; #endif // ALBUMCOVERLOADER_H diff --git a/src/internet/internetservices.cpp b/src/internet/internetservices.cpp index 1938c23ff..1a9a797ba 100644 --- a/src/internet/internetservices.cpp +++ b/src/internet/internetservices.cpp @@ -86,7 +86,6 @@ void InternetServices::Exit() { void InternetServices::ExitReceived() { InternetService *service = qobject_cast(sender()); - wait_for_exit_.removeAll(service); if (wait_for_exit_.isEmpty()) emit ExitFinished(); diff --git a/src/qobuz/qobuzservice.cpp b/src/qobuz/qobuzservice.cpp index 878aa9951..6d69a34b3 100644 --- a/src/qobuz/qobuzservice.cpp +++ b/src/qobuz/qobuzservice.cpp @@ -180,9 +180,9 @@ QobuzService::~QobuzService() { stream_url_req->deleteLater(); } - delete artists_collection_backend_; - delete albums_collection_backend_; - delete songs_collection_backend_; + artists_collection_backend_->deleteLater(); + albums_collection_backend_->deleteLater(); + songs_collection_backend_->deleteLater(); } @@ -202,8 +202,10 @@ void QobuzService::Exit() { void QobuzService::ExitReceived() { - disconnect(sender(), 0, this, 0); - wait_for_exit_.removeAll(sender()); + QObject *obj = static_cast(sender()); + disconnect(obj, 0, this, 0); + qLog(Debug) << obj << "successfully exited."; + wait_for_exit_.removeAll(obj); if (wait_for_exit_.isEmpty()) emit ExitFinished(); } diff --git a/src/subsonic/subsonicservice.cpp b/src/subsonic/subsonicservice.cpp index 71e306ff3..381415f8f 100644 --- a/src/subsonic/subsonicservice.cpp +++ b/src/subsonic/subsonicservice.cpp @@ -95,7 +95,7 @@ SubsonicService::SubsonicService(Application *app, QObject *parent) } SubsonicService::~SubsonicService() { - delete collection_backend_; + collection_backend_->deleteLater(); } void SubsonicService::Exit() { diff --git a/src/tidal/tidalservice.cpp b/src/tidal/tidalservice.cpp index 4afe00baf..85e5c4d93 100644 --- a/src/tidal/tidalservice.cpp +++ b/src/tidal/tidalservice.cpp @@ -186,9 +186,9 @@ TidalService::~TidalService() { stream_url_req->deleteLater(); } - delete artists_collection_backend_; - delete albums_collection_backend_; - delete songs_collection_backend_; + artists_collection_backend_->deleteLater(); + albums_collection_backend_->deleteLater(); + songs_collection_backend_->deleteLater(); } @@ -208,8 +208,10 @@ void TidalService::Exit() { void TidalService::ExitReceived() { - disconnect(sender(), 0, this, 0); - wait_for_exit_.removeAll(sender()); + QObject *obj = static_cast(sender()); + disconnect(obj, 0, this, 0); + qLog(Debug) << obj << "successfully exited."; + wait_for_exit_.removeAll(obj); if (wait_for_exit_.isEmpty()) emit ExitFinished(); }