use Qt Concurrent instead of pthread

This commit is contained in:
Martchus 2016-03-10 22:13:43 +01:00
parent 70b94fa5fa
commit eaea2e2cda
9 changed files with 357 additions and 353 deletions

View File

@ -178,7 +178,8 @@ find_package(Qt5Core REQUIRED)
find_package(Qt5Gui REQUIRED) find_package(Qt5Gui REQUIRED)
find_package(Qt5Widgets REQUIRED) find_package(Qt5Widgets REQUIRED)
find_package(Qt5LinguistTools REQUIRED) find_package(Qt5LinguistTools REQUIRED)
find_package(Qt5Script REQUIRED) find_package(Qt5Concurrent REQUIRED)
find_package(Qt5Network REQUIRED)
# select Qt module providing JavaScript (either Qt Script or Qt Qml) # select Qt module providing JavaScript (either Qt Script or Qt Qml)
if(${JS_PROVIDER} STREQUAL "auto") if(${JS_PROVIDER} STREQUAL "auto")
find_package(Qt5Script) find_package(Qt5Script)
@ -258,7 +259,7 @@ add_custom_target(translations ALL DEPENDS ${QM_FILES})
# executable and linking # executable and linking
add_executable(${META_PROJECT_NAME} ${GUI_TYPE} ${HEADER_FILES} ${SRC_FILES} ${WIDGETS_HEADER_FILES} ${WIDGETS_SRC_FILES} ${WIDGETS_UI_FILES} ${QM_FILES} ${RES_FILES} ${WINDOWS_ICON_PATH}) add_executable(${META_PROJECT_NAME} ${GUI_TYPE} ${HEADER_FILES} ${SRC_FILES} ${WIDGETS_HEADER_FILES} ${WIDGETS_SRC_FILES} ${WIDGETS_UI_FILES} ${QM_FILES} ${RES_FILES} ${WINDOWS_ICON_PATH})
target_link_libraries(${META_PROJECT_NAME} c++utilities qtutilities tagparser pthread Qt5::Core Qt5::Widgets ${JS_PROVIDER} ${WEBVIEW_PROVIDER}) target_link_libraries(${META_PROJECT_NAME} c++utilities qtutilities tagparser Qt5::Core Qt5::Widgets Qt5::Concurrent Qt5::Network ${JS_PROVIDER} ${WEBVIEW_PROVIDER})
set_target_properties(${META_PROJECT_NAME} PROPERTIES set_target_properties(${META_PROJECT_NAME} PROPERTIES
CXX_STANDARD 11 CXX_STANDARD 11
) )

View File

@ -14,6 +14,7 @@
#include <qtutilities/aboutdialog/aboutdialog.h> #include <qtutilities/aboutdialog/aboutdialog.h>
#include <qtutilities/misc/dialogutils.h> #include <qtutilities/misc/dialogutils.h>
#include <qtutilities/misc/desktoputils.h> #include <qtutilities/misc/desktoputils.h>
#include <qtutilities/misc/trylocker.h>
#include <c++utilities/conversion/stringconversion.h> #include <c++utilities/conversion/stringconversion.h>
#include <c++utilities/io/path.h> #include <c++utilities/io/path.h>
@ -30,6 +31,7 @@ using namespace Utility;
using namespace Media; using namespace Media;
using namespace Dialogs; using namespace Dialogs;
using namespace Widgets; using namespace Widgets;
using namespace ThreadingUtils;
namespace QtGui { namespace QtGui {
@ -51,7 +53,7 @@ enum LoadingResult : char
/*! /*!
* \brief Shortcut to access file operation mutex of TagEditorWidget. * \brief Shortcut to access file operation mutex of TagEditorWidget.
*/ */
std::mutex &MainWindow::fileOperationMutex() QMutex &MainWindow::fileOperationMutex()
{ {
return m_ui->tagEditorWidget->fileOperationMutex(); return m_ui->tagEditorWidget->fileOperationMutex();
} }
@ -436,28 +438,28 @@ void MainWindow::showOpenFileDlg()
*/ */
void MainWindow::saveFileInformation() void MainWindow::saveFileInformation()
{ {
if(!fileOperationMutex().try_lock()) { TryLocker<> locker(fileOperationMutex());
m_ui->statusBar->showMessage(tr("Unable to save file information because the current process hasn't been finished yet.")); if(locker) {
return; if(fileInfo().isOpen() && m_ui->tagEditorWidget->fileInfoHtml().size()) {
} const QString path = QFileDialog::getSaveFileName(this, windowTitle());
lock_guard<mutex> guard(fileOperationMutex(), adopt_lock); if(!path.isEmpty()) {
if(fileInfo().isOpen() && m_ui->tagEditorWidget->fileInfoHtml().size()) { QFile file(path);
const QString path = QFileDialog::getSaveFileName(this, windowTitle()); if(file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
if(!path.isEmpty()) { QTextStream stream(&file);
QFile file(path); stream << m_ui->tagEditorWidget->fileInfoHtml();
if(file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { file.close();
QTextStream stream(&file); if(file.error() != QFileDevice::NoError) {
stream << m_ui->tagEditorWidget->fileInfoHtml(); QMessageBox::critical(this, QApplication::applicationName(), tr("Unable to write to file.\n%1").arg(file.errorString()));
file.close(); }
if(file.error() != QFileDevice::NoError) { } else {
QMessageBox::critical(this, QApplication::applicationName(), tr("Unable to write to file.\n%1").arg(file.errorString())); QMessageBox::critical(this, QApplication::applicationName(), tr("Unable to open file."));
} }
} else {
QMessageBox::critical(this, QApplication::applicationName(), tr("Unable to open file."));
} }
} else {
QMessageBox::information(this, QApplication::applicationName(), tr("No file information available."));
} }
} else { } else {
QMessageBox::information(this, QApplication::applicationName(), tr("No file information available.")); m_ui->statusBar->showMessage(tr("Unable to save file information because the current process hasn't been finished yet."));
} }
} }

View File

@ -10,10 +10,9 @@
#include <QMainWindow> #include <QMainWindow>
#include <QByteArray> #include <QByteArray>
#include <mutex>
QT_FORWARD_DECLARE_CLASS(QFileSystemModel) QT_FORWARD_DECLARE_CLASS(QFileSystemModel)
QT_FORWARD_DECLARE_CLASS(QItemSelectionModel) QT_FORWARD_DECLARE_CLASS(QItemSelectionModel)
QT_FORWARD_DECLARE_CLASS(QMutex)
namespace Media { namespace Media {
DECLARE_ENUM(TagType, unsigned int) DECLARE_ENUM(TagType, unsigned int)
@ -73,7 +72,7 @@ private slots:
void showDbQueryWidget(); void showDbQueryWidget();
private: private:
std::mutex &fileOperationMutex(); QMutex &fileOperationMutex();
Media::MediaFileInfo &fileInfo(); Media::MediaFileInfo &fileInfo();
// UI // UI

View File

@ -20,8 +20,6 @@
#include <QClipboard> #include <QClipboard>
#include <QTextStream> #include <QTextStream>
#include <thread>
using namespace Dialogs; using namespace Dialogs;
using namespace RenamingUtility; using namespace RenamingUtility;

View File

@ -21,6 +21,8 @@
#include <tagparser/matroska/matroskatag.h> #include <tagparser/matroska/matroskatag.h>
#include <qtutilities/misc/dialogutils.h> #include <qtutilities/misc/dialogutils.h>
#include <qtutilities/misc/trylocker.h>
#include <qtutilities/misc/adoptlocker.h>
#include <qtutilities/widgets/clearlineedit.h> #include <qtutilities/widgets/clearlineedit.h>
#include <c++utilities/conversion/stringconversion.h> #include <c++utilities/conversion/stringconversion.h>
@ -35,6 +37,8 @@
#include <QFileSystemWatcher> #include <QFileSystemWatcher>
#include <QMenu> #include <QMenu>
#include <QCheckBox> #include <QCheckBox>
#include <QtConcurrent>
#include <QFutureWatcher>
#if defined(TAGEDITOR_NO_WEBVIEW) #if defined(TAGEDITOR_NO_WEBVIEW)
# error "not supported (yet)." # error "not supported (yet)."
#elif defined(TAGEDITOR_USE_WEBENGINE) #elif defined(TAGEDITOR_USE_WEBENGINE)
@ -43,7 +47,6 @@
# include <QWebView> # include <QWebView>
#endif #endif
#include <thread>
#include <functional> #include <functional>
#include <algorithm> #include <algorithm>
@ -51,6 +54,7 @@ using namespace std;
using namespace Utility; using namespace Utility;
using namespace Dialogs; using namespace Dialogs;
using namespace Widgets; using namespace Widgets;
using namespace ThreadingUtils;
using namespace Media; using namespace Media;
namespace QtGui { namespace QtGui {
@ -602,68 +606,67 @@ bool TagEditorWidget::startParsing(const QString &path, bool forceRefresh)
if(!forceRefresh && sameFile) { if(!forceRefresh && sameFile) {
return true; return true;
} }
if(!m_fileOperationMutex.try_lock()) { TryLocker<> locker(fileOperationMutex());
emit statusMessage(tr("Unable to load the selected file \"%1\" because the current process hasn't finished yet.").arg(path)); if(locker) {
return false; // clear previous results and status
} m_tags.clear();
lock_guard<mutex> guard(m_fileOperationMutex, adopt_lock); m_fileInfo.clearParsingResults();
// clear previous results and status m_fileInfo.invalidateStatus();
m_tags.clear(); m_fileInfo.invalidateNotifications();
m_fileInfo.clearParsingResults(); if(!sameFile) {
m_fileInfo.invalidateStatus(); // close last file if possibly open
m_fileInfo.invalidateNotifications(); m_fileInfo.close();
if(!sameFile) { // set path of file info
// close last file if possibly open m_currentPath = path;
m_fileInfo.close(); m_fileInfo.setPath(path.toLocal8Bit().data());
// set path of file info // update directory
m_currentPath = path; m_lastDir = m_currentDir;
m_fileInfo.setPath(path.toLocal8Bit().data()); m_currentDir = QString::fromLocal8Bit(m_fileInfo.containingDirectory().c_str());
// update directory
m_lastDir = m_currentDir;
m_currentDir = QString::fromLocal8Bit(m_fileInfo.containingDirectory().c_str());
}
// update availability of making results
m_makingResultsAvailable &= sameFile;
if(!m_makingResultsAvailable) {
m_originalNotifications.clear();
}
// show filename
m_ui->fileNameLabel->setText(QString::fromLocal8Bit(m_fileInfo.fileName().c_str()));
// define function to parse the file
auto startThread = [this, sameFile] {
m_fileOperationMutex.lock();
char result;
try {
if(sameFile) {
m_fileInfo.reopen();
}
m_fileInfo.setForceFullParse(Settings::forceFullParse());
m_fileInfo.parseEverything();
result = ParsingSuccessful;
} catch(Failure &) {
// the file has been opened; parsing notifications will be shown in the info box
result = FatalParsingError;
} catch(ios_base::failure &) {
// the file could not be opened because an IO error occured
m_fileInfo.close(); // ensure file is closed
result = IoError;
} }
// update availability of making results
m_makingResultsAvailable &= sameFile;
if(!m_makingResultsAvailable) {
m_originalNotifications.clear();
}
// show filename
m_ui->fileNameLabel->setText(QString::fromLocal8Bit(m_fileInfo.fileName().c_str()));
// define function to parse the file
auto startThread = [this, sameFile] {
m_fileOperationMutex.lock();
char result;
try {
if(sameFile) {
m_fileInfo.reopen();
}
m_fileInfo.setForceFullParse(Settings::forceFullParse());
m_fileInfo.parseEverything();
result = ParsingSuccessful;
} catch(const Failure &) {
// the file has been opened; parsing notifications will be shown in the info box
result = FatalParsingError;
} catch(const ios_base::failure &) {
// the file could not be opened because an IO error occured
m_fileInfo.close(); // ensure file is closed
result = IoError;
}
m_fileInfo.unregisterAllCallbacks();
QMetaObject::invokeMethod(this, "showFile", Qt::QueuedConnection, Q_ARG(char, result));
// showFile() will unlock the mutex!
};
m_fileInfo.unregisterAllCallbacks(); m_fileInfo.unregisterAllCallbacks();
QMetaObject::invokeMethod(this, "showFile", Qt::QueuedConnection, Q_ARG(char, result)); // perform the operation concurrently
// showFile() will unlock the mutex! QtConcurrent::run(startThread);
}; // inform user
m_fileInfo.unregisterAllCallbacks(); static const QString statusMsg(tr("The file is beeing parsed ..."));
//m_fileInfo.registerCallback(showProgress); can't show progress yet m_ui->parsingNotificationWidget->setNotificationType(NotificationType::Progress);
// use another thread to perform the operation m_ui->parsingNotificationWidget->setText(statusMsg);
std::thread thr(startThread); m_ui->parsingNotificationWidget->setVisible(true); // ensure widget is visible!
thr.detach(); emit statusMessage(statusMsg);
// inform user return true;
static const QString statusMsg(tr("The file is beeing parsed ...")); } else {
m_ui->parsingNotificationWidget->setNotificationType(NotificationType::Progress); emit statusMessage(tr("Unable to load the selected file \"%1\" because the current process hasn't finished yet.").arg(path));
m_ui->parsingNotificationWidget->setText(statusMsg); }
m_ui->parsingNotificationWidget->setVisible(true); // ensure widget is visible! return false;
emit statusMessage(statusMsg);
return true;
} }
/*! /*!
@ -671,14 +674,15 @@ bool TagEditorWidget::startParsing(const QString &path, bool forceRefresh)
*/ */
bool TagEditorWidget::reparseFile() bool TagEditorWidget::reparseFile()
{ {
if(!m_fileOperationMutex.try_lock()) {
emit statusMessage(tr("Unable to reload the file because the current process hasn't finished yet."));
return false;
}
{ {
lock_guard<mutex> guard(m_fileOperationMutex, adopt_lock); TryLocker<> locker(m_fileOperationMutex);
if(!m_fileInfo.isOpen() || m_currentPath.isEmpty()) { if(locker) {
QMessageBox::warning(this, windowTitle(), tr("Currently is not file opened.")); if(!m_fileInfo.isOpen() || m_currentPath.isEmpty()) {
QMessageBox::warning(this, windowTitle(), tr("Currently is not file opened."));
return false;
}
} else {
emit statusMessage(tr("Unable to reload the file because the current process hasn't finished yet."));
return false; return false;
} }
} }
@ -694,7 +698,7 @@ bool TagEditorWidget::reparseFile()
*/ */
void TagEditorWidget::showFile(char result) void TagEditorWidget::showFile(char result)
{ {
lock_guard<mutex> guard(m_fileOperationMutex, adopt_lock); AdoptLocker<> locker(m_fileOperationMutex);
if(result == IoError) { if(result == IoError) {
// update status // update status
updateFileStatusStatus(); updateFileStatusStatus();
@ -787,40 +791,41 @@ void TagEditorWidget::saveAndShowNextFile()
bool TagEditorWidget::applyEntriesAndSaveChangings() bool TagEditorWidget::applyEntriesAndSaveChangings()
{ {
{ {
if(!m_fileOperationMutex.try_lock()) { TryLocker<> locker(m_fileOperationMutex);
if(locker) {
m_ui->makingNotificationWidget->setNotificationType(NotificationType::Information);
m_ui->makingNotificationWidget->setNotificationSubject(NotificationSubject::Saving);
m_ui->makingNotificationWidget->setHidden(false);
m_makingResultsAvailable = true;
if(m_fileInfo.isOpen()) {
// apply titles
if(AbstractContainer *container = m_fileInfo.container()) {
if(container->supportsTitle()) {
QLayout *docTitleLayout = m_ui->docTitleWidget->layout();
for(int i = 0, count = min<int>(docTitleLayout->count() - 1, container->segmentCount()); i < count; ++i) {
container->setTitle(static_cast<ClearLineEdit *>(docTitleLayout->itemAt(i + 1)->widget())->text().toUtf8().data(), i);
}
}
}
// apply all tags
foreachTagEdit([] (TagEdit *edit) {edit->apply();});
static const QString statusMsg(tr("Saving tags ..."));
m_ui->makingNotificationWidget->setNotificationSubject(NotificationSubject::None);
m_ui->makingNotificationWidget->setNotificationType(NotificationType::Progress);
m_ui->makingNotificationWidget->setText(statusMsg);
emit statusMessage(statusMsg);
} else {
QString statusMsg(tr("No file has been opened."));
m_ui->makingNotificationWidget->setText(statusMsg);
QMessageBox::warning(this, QApplication::applicationName(), statusMsg);
return false;
}
} else {
static const QString statusMsg(tr("Unable to apply the entered tags to the file because the current process hasn't finished yet.")); static const QString statusMsg(tr("Unable to apply the entered tags to the file because the current process hasn't finished yet."));
m_ui->makingNotificationWidget->setText(statusMsg); m_ui->makingNotificationWidget->setText(statusMsg);
emit statusMessage(statusMsg); emit statusMessage(statusMsg);
return false; return false;
} }
lock_guard<mutex> guard(m_fileOperationMutex, adopt_lock);
m_ui->makingNotificationWidget->setNotificationType(NotificationType::Information);
m_ui->makingNotificationWidget->setNotificationSubject(NotificationSubject::Saving);
m_ui->makingNotificationWidget->setHidden(false);
m_makingResultsAvailable = true;
if(m_fileInfo.isOpen()) {
// apply titles
if(AbstractContainer *container = m_fileInfo.container()) {
if(container->supportsTitle()) {
QLayout *docTitleLayout = m_ui->docTitleWidget->layout();
for(int i = 0, count = min<int>(docTitleLayout->count() - 1, container->segmentCount()); i < count; ++i) {
container->setTitle(static_cast<ClearLineEdit *>(docTitleLayout->itemAt(i + 1)->widget())->text().toUtf8().data(), i);
}
}
}
// apply all tags
foreachTagEdit([] (TagEdit *edit) {edit->apply();});
static const QString statusMsg(tr("Saving tags ..."));
m_ui->makingNotificationWidget->setNotificationSubject(NotificationSubject::None);
m_ui->makingNotificationWidget->setNotificationType(NotificationType::Progress);
m_ui->makingNotificationWidget->setText(statusMsg);
emit statusMessage(statusMsg);
} else {
QString statusMsg = tr("No file has been opened.");
m_ui->makingNotificationWidget->setText(statusMsg);
QMessageBox::warning(this, QApplication::applicationName(), statusMsg);
return false;
}
} }
return startSaving(); return startSaving();
} }
@ -832,53 +837,54 @@ bool TagEditorWidget::applyEntriesAndSaveChangings()
bool TagEditorWidget::deleteAllTagsAndSave() bool TagEditorWidget::deleteAllTagsAndSave()
{ {
{ {
if(!m_fileOperationMutex.try_lock()) { TryLocker<> locker(m_fileOperationMutex);
static const QString statusMsg(tr("Unable to delete all tags from the file because the current process hasn't been finished yet.")); if(locker) {
m_ui->makingNotificationWidget->setText(statusMsg); if(Settings::askBeforeDeleting()) {
emit statusMessage(statusMsg); QMessageBox msgBox(this);
return false; msgBox.setText(tr("Do you really want to delete all tags from the file?"));
} msgBox.setIcon(QMessageBox::Warning);
lock_guard<mutex> guard(m_fileOperationMutex, adopt_lock); msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
if(Settings::askBeforeDeleting()) { msgBox.setDefaultButton(QMessageBox::No);
QMessageBox msgBox(this);
msgBox.setText(tr("Do you really want to delete all tags from the file?"));
msgBox.setIcon(QMessageBox::Warning);
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
msgBox.setDefaultButton(QMessageBox::No);
#if QT_VERSION >= 0x050200 #if QT_VERSION >= 0x050200
auto *checkBox = new QCheckBox(&msgBox); auto *checkBox = new QCheckBox(&msgBox);
checkBox->setText(tr("don't show this message again")); checkBox->setText(tr("don't show this message again"));
msgBox.setCheckBox(checkBox); msgBox.setCheckBox(checkBox);
#endif #endif
int res = msgBox.exec(); int res = msgBox.exec();
#if QT_VERSION >= 0x050200 #if QT_VERSION >= 0x050200
if(checkBox->isChecked()) { if(checkBox->isChecked()) {
Settings::askBeforeDeleting() = false; Settings::askBeforeDeleting() = false;
} }
#endif #endif
if(res != QMessageBox::Yes) { if(res != QMessageBox::Yes) {
return false; return false;
}
} }
} m_ui->makingNotificationWidget->setNotificationSubject(NotificationSubject::Saving);
m_ui->makingNotificationWidget->setNotificationSubject(NotificationSubject::Saving); m_ui->makingNotificationWidget->setNotificationType(NotificationType::Information);
m_ui->makingNotificationWidget->setNotificationType(NotificationType::Information); m_ui->makingNotificationWidget->setHidden(false);
m_ui->makingNotificationWidget->setHidden(false); m_makingResultsAvailable = true;
m_makingResultsAvailable = true; if(m_fileInfo.isOpen()) {
if(m_fileInfo.isOpen()) { if(m_fileInfo.hasAnyTag()) {
if(m_fileInfo.hasAnyTag()) { foreachTagEdit([] (TagEdit *edit) {edit->clear();});
foreachTagEdit([] (TagEdit *edit) {edit->clear();}); m_fileInfo.removeAllTags();
m_fileInfo.removeAllTags(); m_ui->makingNotificationWidget->setNotificationSubject(NotificationSubject::None);
m_ui->makingNotificationWidget->setNotificationSubject(NotificationSubject::None); m_ui->makingNotificationWidget->setNotificationType(NotificationType::Progress);
m_ui->makingNotificationWidget->setNotificationType(NotificationType::Progress); static const QString statusMsg(tr("Deleting all tags ..."));
static const QString statusMsg(tr("Deleting all tags ...")); m_ui->makingNotificationWidget->setText(statusMsg);
m_ui->makingNotificationWidget->setText(statusMsg); emit statusMessage(statusMsg);
emit statusMessage(statusMsg); } else {
m_ui->makingNotificationWidget->setText(tr("The selected file stores no tag (at least no supported), so there is nothing to delete."));
return false;
}
} else { } else {
m_ui->makingNotificationWidget->setText(tr("The selected file stores no tag (at least no supported), so there is nothing to delete.")); m_ui->makingNotificationWidget->setText(tr("No file has been opened, so no tags can be deleted."));
return false; return false;
} }
} else { } else {
m_ui->makingNotificationWidget->setText(tr("No file has been opened, so no tags can be deleted.")); static const QString statusMsg(tr("Unable to delete all tags from the file because the current process hasn't been finished yet."));
m_ui->makingNotificationWidget->setText(statusMsg);
emit statusMessage(statusMsg);
return false; return false;
} }
} }
@ -896,61 +902,61 @@ bool TagEditorWidget::deleteAllTagsAndSave()
*/ */
bool TagEditorWidget::startSaving() bool TagEditorWidget::startSaving()
{ {
if(!m_fileOperationMutex.try_lock()) { TryLocker<> locker(m_fileOperationMutex);
if(locker) {
// tags might get invalidated
m_tags.clear();
foreachTagEdit([] (TagEdit *edit) { edit->setTag(nullptr, false); });
// show abort button
m_ui->abortButton->setHidden(false);
m_ui->abortButton->setEnabled(true);
m_abortClicked = false;
// remove current path from file watcher
m_fileWatcher->removePath(m_currentPath);
// use current configuration
m_fileInfo.setForceRewrite(Settings::forceRewrite());
m_fileInfo.setTagPosition(Settings::preferredTagPosition());
m_fileInfo.setForceTagPosition(Settings::forceTagPosition());
m_fileInfo.setIndexPosition(Settings::preferredIndexPosition());
m_fileInfo.setForceIndexPosition(Settings::forceIndexPosition());
m_fileInfo.setMinPadding(Settings::minPadding());
m_fileInfo.setMaxPadding(Settings::maxPadding());
m_fileInfo.setPreferredPadding(Settings::preferredPadding());
// define functions to show the saving progress and to actually applying the changes
auto showProgress = [this] (StatusProvider &sender) -> void {
QMetaObject::invokeMethod(m_ui->makingNotificationWidget, "setPercentage", Qt::QueuedConnection, Q_ARG(int, static_cast<int>(sender.currentPercentage() * 100.0)));
if(m_abortClicked) {
QMetaObject::invokeMethod(m_ui->makingNotificationWidget, "setText", Qt::QueuedConnection, Q_ARG(QString, tr("Cancelling ...")));
m_fileInfo.tryToAbort();
} else {
QMetaObject::invokeMethod(m_ui->makingNotificationWidget, "setText", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(sender.currentStatus())));
}
};
auto startThread = [this] {
m_fileOperationMutex.lock();
bool processingError = false, ioError = false;
try {
m_fileInfo.applyChanges();
} catch(const Failure &) {
processingError = true;
} catch(const ios_base::failure &) {
ioError = true;
}
m_fileInfo.unregisterAllCallbacks();
QMetaObject::invokeMethod(this, "showSavingResult", Qt::QueuedConnection, Q_ARG(bool, processingError), Q_ARG(bool, ioError));
// showSavingResult() will unlock the mutex!
};
m_fileInfo.unregisterAllCallbacks();
m_fileInfo.registerCallback(showProgress);
// use another thread to perform the operation
QtConcurrent::run(startThread);
return true;
} else {
static const QString errorMsg(tr("Unable to start saving process because there an other process hasn't finished yet.")); static const QString errorMsg(tr("Unable to start saving process because there an other process hasn't finished yet."));
emit statusMessage(errorMsg); emit statusMessage(errorMsg);
QMessageBox::warning(this, QApplication::applicationName(), errorMsg); QMessageBox::warning(this, QApplication::applicationName(), errorMsg);
return false; return false;
} }
lock_guard<mutex> guard(m_fileOperationMutex, adopt_lock);
// tags might get invalidated
m_tags.clear();
foreachTagEdit([] (TagEdit *edit) { edit->setTag(nullptr, false); });
// show abort button
m_ui->abortButton->setHidden(false);
m_ui->abortButton->setEnabled(true);
m_abortClicked = false;
// remove current path from file watcher
m_fileWatcher->removePath(m_currentPath);
// use current configuration
m_fileInfo.setForceRewrite(Settings::forceRewrite());
m_fileInfo.setTagPosition(Settings::preferredTagPosition());
m_fileInfo.setForceTagPosition(Settings::forceTagPosition());
m_fileInfo.setIndexPosition(Settings::preferredIndexPosition());
m_fileInfo.setForceIndexPosition(Settings::forceIndexPosition());
m_fileInfo.setMinPadding(Settings::minPadding());
m_fileInfo.setMaxPadding(Settings::maxPadding());
m_fileInfo.setPreferredPadding(Settings::preferredPadding());
// define functions to show the saving progress and to actually applying the changes
auto showProgress = [this] (StatusProvider &sender) -> void {
QMetaObject::invokeMethod(m_ui->makingNotificationWidget, "setPercentage", Qt::QueuedConnection, Q_ARG(int, static_cast<int>(sender.currentPercentage() * 100.0)));
if(m_abortClicked) {
QMetaObject::invokeMethod(m_ui->makingNotificationWidget, "setText", Qt::QueuedConnection, Q_ARG(QString, tr("Cancelling ...")));
m_fileInfo.tryToAbort();
} else {
QMetaObject::invokeMethod(m_ui->makingNotificationWidget, "setText", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(sender.currentStatus())));
}
};
auto startThread = [this] {
m_fileOperationMutex.lock();
bool processingError = false, ioError = false;
try {
m_fileInfo.applyChanges();
} catch(const Failure &) {
processingError = true;
} catch(const ios_base::failure &) {
ioError = true;
}
m_fileInfo.unregisterAllCallbacks();
QMetaObject::invokeMethod(this, "showSavingResult", Qt::QueuedConnection, Q_ARG(bool, processingError), Q_ARG(bool, ioError));
// showSavingResult() will unlock the mutex!
};
m_fileInfo.unregisterAllCallbacks();
m_fileInfo.registerCallback(showProgress);
// use another thread to perform the operation
std::thread thr(startThread);
thr.detach();
return true;
} }
/*! /*!
@ -1055,18 +1061,18 @@ void TagEditorWidget::fileChangedOnDisk(const QString &path)
*/ */
void TagEditorWidget::closeFile() void TagEditorWidget::closeFile()
{ {
if(!m_fileOperationMutex.try_lock()) { TryLocker<> locker(m_fileOperationMutex);
if(locker) {
// close file
m_fileInfo.close();
// remove current path from file watcher
m_fileWatcher->removePath(m_currentPath);
// update ui
emit statusMessage("The file has been closed.");
updateFileStatusStatus();
} else {
emit statusMessage("Unable to close the file because the current process hasn't been finished yet."); emit statusMessage("Unable to close the file because the current process hasn't been finished yet.");
return;
} }
lock_guard<mutex> guard(m_fileOperationMutex, adopt_lock);
// close file
m_fileInfo.close();
// remove current path from file watcher
m_fileWatcher->removePath(m_currentPath);
// update ui
emit statusMessage("The file has been closed.");
updateFileStatusStatus();
} }
/*! /*!
@ -1119,29 +1125,28 @@ void TagEditorWidget::applySettingsFromDialog()
*/ */
void TagEditorWidget::addTag(const function<Media::Tag *(Media::MediaFileInfo &)> &createTag) void TagEditorWidget::addTag(const function<Media::Tag *(Media::MediaFileInfo &)> &createTag)
{ {
if(!m_fileOperationMutex.try_lock()) { TryLocker<> locker(m_fileOperationMutex);
emit statusMessage("Unable to add a tag because the current process hasn't been finished yet."); if(locker) {
return; if(!m_fileInfo.isOpen()) {
} emit statusMessage("Unable to add a tag because no file is opened.");
lock_guard<mutex> guard(m_fileOperationMutex, adopt_lock); return;
if(!m_fileInfo.isOpen()) { }
emit statusMessage("Unable to add a tag because no file is opened."); if(Tag *tag = createTag(m_fileInfo)) {
return; if(std::find(m_tags.cbegin(), m_tags.cend(), tag) == m_tags.cend()) {
} m_tags.push_back(tag);
if(Tag *tag = createTag(m_fileInfo)) { updateTagEditsAndAttachmentEdits(true, m_tags.size() > 1 ? PreviousValueHandling::Keep : PreviousValueHandling::Auto);
if(std::find(m_tags.cbegin(), m_tags.cend(), tag) == m_tags.cend()) { updateTagSelectionComboBox();
m_tags.push_back(tag); updateTagManagementMenu();
updateTagEditsAndAttachmentEdits(true, m_tags.size() > 1 ? PreviousValueHandling::Keep : PreviousValueHandling::Auto); updateFileStatusStatus();
updateTagSelectionComboBox(); insertTitleFromFilename();
updateTagManagementMenu(); } else {
updateFileStatusStatus(); QMessageBox::warning(this, windowTitle(), tr("A tag (with the selected target) already exists."));
insertTitleFromFilename(); }
} else { } else {
QMessageBox::warning(this, windowTitle(), tr("A tag (with the selected target) already exists.")); QMessageBox::warning(this, windowTitle(), tr("The tag can not be created."));
} }
} else { } else {
QMessageBox::warning(this, windowTitle(), tr("The tag can not be created.")); emit statusMessage("Unable to add a tag because the current process hasn't been finished yet.");
} }
} }
@ -1153,52 +1158,52 @@ void TagEditorWidget::addTag(const function<Media::Tag *(Media::MediaFileInfo &)
void TagEditorWidget::removeTag(Tag *tag) void TagEditorWidget::removeTag(Tag *tag)
{ {
if(tag) { if(tag) {
if(!m_fileOperationMutex.try_lock()) { TryLocker<> locker(m_fileOperationMutex);
emit statusMessage(tr("Unable to remove the tag because the current process hasn't been finished yet.")); if(locker) {
return; if(!m_fileInfo.isOpen()) {
} emit statusMessage(tr("Unable to remove the tag because no file is opened."));
lock_guard<mutex> guard(m_fileOperationMutex, adopt_lock); return;
if(!m_fileInfo.isOpen()) { }
emit statusMessage(tr("Unable to remove the tag because no file is opened.")); if(m_fileInfo.isOpen()) {
return; m_fileInfo.removeTag(tag);
} // remove tag from m_tags
if(m_fileInfo.isOpen()) { m_tags.erase(remove(m_tags.begin(), m_tags.end(), tag), m_tags.end());
m_fileInfo.removeTag(tag); // remove tag from all TagEdit widgets
// remove tag from m_tags vector<TagEdit *> toRemove;
m_tags.erase(remove(m_tags.begin(), m_tags.end(), tag), m_tags.end()); for(int index = 0, count = m_ui->stackedWidget->count(); index < count; ++index) {
// remove tag from all TagEdit widgets TagEdit *edit = qobject_cast<TagEdit *>(m_ui->stackedWidget->widget(index));
vector<TagEdit *> toRemove; if(edit && edit->tags().contains(tag)) {
for(int index = 0, count = m_ui->stackedWidget->count(); index < count; ++index) { QList<Tag *> tagsOfEdit = edit->tags();
TagEdit *edit = qobject_cast<TagEdit *>(m_ui->stackedWidget->widget(index)); tagsOfEdit.removeAll(tag);
if(edit && edit->tags().contains(tag)) { if(tagsOfEdit.empty()) {
QList<Tag *> tagsOfEdit = edit->tags(); // no tags left in the edit
tagsOfEdit.removeAll(tag); if(m_tags.empty()) {
if(tagsOfEdit.empty()) { // there are no other tag edits -> just disable the edit
// no tags left in the edit edit->setTag(nullptr, false);
if(m_tags.empty()) { } else {
// there are no other tag edits -> just disable the edit // there are still other tag edits -> remove the edit
edit->setTag(nullptr, false); toRemove.push_back(edit);
}
} else { } else {
// there are still other tag edits -> remove the edit // there are still tags left, reassign remaining tags (keeping the previous values)
toRemove.push_back(edit); edit->setPreviousValueHandling(PreviousValueHandling::Keep);
edit->setTags(tagsOfEdit, true);
} }
} else {
// there are still tags left, reassign remaining tags (keeping the previous values)
edit->setPreviousValueHandling(PreviousValueHandling::Keep);
edit->setTags(tagsOfEdit, true);
} }
} }
// remove TagEdit widgets
for(TagEdit *edit : toRemove) {
m_ui->tagSelectionComboBox->removeItem(m_ui->stackedWidget->indexOf(edit));
m_ui->stackedWidget->removeWidget(edit);
delete edit;
}
// update affected widgets
updateTagSelectionComboBox();
updateTagManagementMenu();
updateFileStatusStatus();
} }
// remove TagEdit widgets } else {
for(TagEdit *edit : toRemove) { emit statusMessage(tr("Unable to remove the tag because the current process hasn't been finished yet."));
m_ui->tagSelectionComboBox->removeItem(m_ui->stackedWidget->indexOf(edit));
m_ui->stackedWidget->removeWidget(edit);
delete edit;
}
// update affected widgets
updateTagSelectionComboBox();
updateTagManagementMenu();
updateFileStatusStatus();
} }
} }
} }
@ -1211,27 +1216,27 @@ void TagEditorWidget::removeTag(Tag *tag)
void TagEditorWidget::changeTarget(Tag *tag) void TagEditorWidget::changeTarget(Tag *tag)
{ {
if(tag) { if(tag) {
if(!m_fileOperationMutex.try_lock()) { TryLocker<> locker(m_fileOperationMutex);
emit statusMessage(tr("Unable to change the target because the current process hasn't been finished yet.")); if(locker) {
return; if(!m_fileInfo.isOpen()) {
} emit statusMessage(tr("Unable to change the target because no file is opened."));
lock_guard<mutex> guard(m_fileOperationMutex, adopt_lock); return;
if(!m_fileInfo.isOpen()) {
emit statusMessage(tr("Unable to change the target because no file is opened."));
return;
}
if(m_fileInfo.isOpen()) {
if(tag->supportsTarget()) {
EnterTargetDialog targetDlg(this);
targetDlg.setTarget(tag->target(), &m_fileInfo);
if(targetDlg.exec() == QDialog::Accepted) {
tag->setTarget(targetDlg.target());
updateTagSelectionComboBox();
updateTagManagementMenu();
}
} else {
QMessageBox::warning(this, windowTitle(), tr("Can not change the target of the selected tag because the tag does not support targets."));
} }
if(m_fileInfo.isOpen()) {
if(tag->supportsTarget()) {
EnterTargetDialog targetDlg(this);
targetDlg.setTarget(tag->target(), &m_fileInfo);
if(targetDlg.exec() == QDialog::Accepted) {
tag->setTarget(targetDlg.target());
updateTagSelectionComboBox();
updateTagManagementMenu();
}
} else {
QMessageBox::warning(this, windowTitle(), tr("Can not change the target of the selected tag because the tag does not support targets."));
}
}
} else {
emit statusMessage(tr("Unable to change the target because the current process hasn't been finished yet."));
} }
} }
} }

View File

@ -7,8 +7,8 @@
#include <QWidget> #include <QWidget>
#include <QByteArray> #include <QByteArray>
#include <QMutex>
#include <mutex>
#include <functional> #include <functional>
#if defined(TAGEDITOR_NO_WEBVIEW) #if defined(TAGEDITOR_NO_WEBVIEW)
@ -50,7 +50,7 @@ public:
virtual ~TagEditorWidget(); virtual ~TagEditorWidget();
public: public:
std::mutex &fileOperationMutex(); QMutex &fileOperationMutex();
const QString &currentPath() const; const QString &currentPath() const;
Media::MediaFileInfo &fileInfo(); Media::MediaFileInfo &fileInfo();
bool isTagEditShown() const; bool isTagEditShown() const;
@ -149,13 +149,13 @@ private:
bool m_makingResultsAvailable; bool m_makingResultsAvailable;
Media::NotificationList m_originalNotifications; Media::NotificationList m_originalNotifications;
bool m_abortClicked; bool m_abortClicked;
std::mutex m_fileOperationMutex; QMutex m_fileOperationMutex;
}; };
/*! /*!
* \brief Returns the mutex which is internally used for thread-synchronization. * \brief Returns the mutex which is internally used for thread-synchronization.
*/ */
inline std::mutex &TagEditorWidget::fileOperationMutex() inline QMutex &TagEditorWidget::fileOperationMutex()
{ {
return m_fileOperationMutex; return m_fileOperationMutex;
} }

View File

@ -5,12 +5,14 @@
#include <c++utilities/misc/memory.h> #include <c++utilities/misc/memory.h>
#include <qtutilities/misc/trylocker.h>
#include <QDir> #include <QDir>
#include <QStringBuilder> #include <QStringBuilder>
#include <QtConcurrent>
#include <thread>
using namespace std; using namespace std;
using namespace ThreadingUtils;
namespace RenamingUtility { namespace RenamingUtility {
@ -55,26 +57,25 @@ bool RemamingEngine::setProgram(const QString &program)
bool RemamingEngine::generatePreview(const QDir &rootDirectory, bool includeSubdirs) bool RemamingEngine::generatePreview(const QDir &rootDirectory, bool includeSubdirs)
{ {
if(!m_mutex.try_lock()) { TryLocker<> locker(m_mutex);
if(locker) {
setRootItem();
m_includeSubdirs = includeSubdirs;
m_dir = rootDirectory;
QtConcurrent::run([this] () {
{
QMutexLocker locker(&m_mutex);
m_aborted.store(false);
m_itemsProcessed = 0;
m_errorsOccured = 0;
m_newlyGeneratedRootItem = generatePreview(m_dir);
}
emit previewGenerated();
});
return true;
} else {
return false; return false;
} }
lock_guard<mutex> guard(m_mutex, adopt_lock);
setRootItem();
m_includeSubdirs = includeSubdirs;
m_dir = rootDirectory;
auto startFunc = [this] () {
{
lock_guard<mutex> guard(m_mutex);
m_aborted.store(false);
m_itemsProcessed = 0;
m_errorsOccured = 0;
m_newlyGeneratedRootItem = generatePreview(m_dir);
}
emit previewGenerated();
};
std::thread thread(startFunc);
thread.detach();
return true;
} }
bool RemamingEngine::applyChangings() bool RemamingEngine::applyChangings()
@ -82,28 +83,27 @@ bool RemamingEngine::applyChangings()
if(!m_rootItem) { if(!m_rootItem) {
return false; return false;
} }
if(!m_mutex.try_lock()) { TryLocker<> locker(m_mutex);
if(locker) {
QtConcurrent::run([this] () {
{
QMutexLocker locker(&m_mutex);
m_aborted.store(false);
m_itemsProcessed = 0;
m_errorsOccured = 0;
applyChangings(m_rootItem.get());
}
emit changingsApplied();
});
return true;
} else {
return false; return false;
} }
lock_guard<mutex> guard(m_mutex, adopt_lock);
auto startFunc = [this] () {
{
lock_guard<mutex> guard(m_mutex);
m_aborted.store(false);
m_itemsProcessed = 0;
m_errorsOccured = 0;
applyChangings(m_rootItem.get());
}
emit changingsApplied();
};
std::thread thread(startFunc);
thread.detach();
return true;
} }
bool RemamingEngine::isBusy() bool RemamingEngine::isBusy()
{ {
if(m_mutex.try_lock()) { if(m_mutex.tryLock()) {
m_mutex.unlock(); m_mutex.unlock();
return false; return false;
} else { } else {
@ -113,7 +113,7 @@ bool RemamingEngine::isBusy()
void RemamingEngine::abort() void RemamingEngine::abort()
{ {
m_aborted.store(true); m_aborted.store(1);
} }
bool RemamingEngine::isAborted() bool RemamingEngine::isAborted()
@ -123,8 +123,8 @@ bool RemamingEngine::isAborted()
bool RemamingEngine::clearPreview() bool RemamingEngine::clearPreview()
{ {
if(m_mutex.try_lock()) { TryLocker<> locker(m_mutex);
lock_guard<mutex> guard(m_mutex, adopt_lock); if(locker) {
updateModel(nullptr); updateModel(nullptr);
m_rootItem.reset(); m_rootItem.reset();
return true; return true;

View File

@ -7,7 +7,8 @@
#include <QObject> #include <QObject>
#include <QList> #include <QList>
#include <QDir> #include <QDir>
#include <QMutex>
#include <QAtomicInteger>
#if TAGEDITOR_USE_JSENGINE #if TAGEDITOR_USE_JSENGINE
# include <QJSEngine> # include <QJSEngine>
# include <QJSValue> # include <QJSValue>
@ -17,8 +18,6 @@
#endif #endif
#include <memory> #include <memory>
#include <mutex>
#include <atomic>
QT_FORWARD_DECLARE_CLASS(QFileInfo) QT_FORWARD_DECLARE_CLASS(QFileInfo)
@ -79,11 +78,11 @@ private:
std::unique_ptr<FileSystemItem> m_newlyGeneratedRootItem; std::unique_ptr<FileSystemItem> m_newlyGeneratedRootItem;
int m_itemsProcessed; int m_itemsProcessed;
int m_errorsOccured; int m_errorsOccured;
std::atomic<bool> m_aborted; QAtomicInteger<unsigned char> m_aborted;
TAGEDITOR_JS_VALUE m_program; TAGEDITOR_JS_VALUE m_program;
QDir m_dir; QDir m_dir;
bool m_includeSubdirs; bool m_includeSubdirs;
std::mutex m_mutex; QMutex m_mutex;
FileSystemItemModel *m_model; FileSystemItemModel *m_model;
FilteredFileSystemItemModel *m_currentModel; FilteredFileSystemItemModel *m_currentModel;
FilteredFileSystemItemModel *m_previewModel; FilteredFileSystemItemModel *m_previewModel;

View File

@ -15,7 +15,7 @@ VERSION = 1.4.0
# basic configuration: application # basic configuration: application
TEMPLATE = app TEMPLATE = app
QT += core gui widgets QT += core gui widgets concurrent network
# use webkitwidgets if available; otherwise use webenginewidgets # use webkitwidgets if available; otherwise use webenginewidgets
!forcewebengine:qtHaveModule(webkitwidgets) { !forcewebengine:qtHaveModule(webkitwidgets) {
QT += webkitwidgets QT += webkitwidgets