Refactor GUI

* Make error handling more clear
* Get rid of useless mutex (sync is done
  by QMetaObject::invokeMethod() already)
* Improve details
This commit is contained in:
Martchus 2016-11-23 21:46:33 +01:00
parent 1dca8b168a
commit d3558cfefe
6 changed files with 354 additions and 371 deletions

View File

@ -40,11 +40,11 @@ void QueryResultsModel::setFetchingCover(bool fetchingCover)
m_fetchingCover = fetchingCover; m_fetchingCover = fetchingCover;
} }
#define returnValue(field) return qstringToTagValue(res.field, TagTextEncoding::Utf16LittleEndian)
void QueryResultsModel::abort() void QueryResultsModel::abort()
{} {}
#define returnValue(field) return qstringToTagValue(res.field, TagTextEncoding::Utf16LittleEndian)
TagValue QueryResultsModel::fieldValue(int row, KnownField knownField) const TagValue QueryResultsModel::fieldValue(int row, KnownField knownField) const
{ {
if(row < m_results.size()) { if(row < m_results.size()) {

View File

@ -2,6 +2,7 @@
#define DBQUERY_H #define DBQUERY_H
#include <c++utilities/application/global.h> #include <c++utilities/application/global.h>
#include <c++utilities/conversion/types.h>
#include <QAbstractTableModel> #include <QAbstractTableModel>
#include <QNetworkReply> #include <QNetworkReply>
@ -29,9 +30,9 @@ struct SongDescription
QString artist; QString artist;
QString year; QString year;
QString genre; QString genre;
unsigned int track; int32 track;
unsigned int totalTracks; int32 totalTracks;
unsigned int disk; int32 disk;
QByteArray cover; QByteArray cover;
QString lyrics; QString lyrics;
}; };

View File

@ -55,9 +55,9 @@ enum LoadingResult : char
/*! /*!
* \brief Shortcut to access file operation mutex of TagEditorWidget. * \brief Shortcut to access file operation mutex of TagEditorWidget.
*/ */
QMutex &MainWindow::fileOperationMutex() bool MainWindow::fileOperationOngoing() const
{ {
return m_ui->tagEditorWidget->fileOperationMutex(); return m_ui->tagEditorWidget->fileOperationOngoing();
} }
/*! /*!
@ -503,39 +503,41 @@ void MainWindow::showSaveAsDlg()
*/ */
void MainWindow::saveFileInformation() void MainWindow::saveFileInformation()
{ {
TryLocker<> locker(fileOperationMutex()); if(fileOperationOngoing()) {
if(locker) {
if(fileInfo().isOpen()) {
const QByteArray htmlData =
#ifndef TAGEDITOR_NO_WEBVIEW
!m_ui->tagEditorWidget->fileInfoHtml().isEmpty() ?
m_ui->tagEditorWidget->fileInfoHtml() :
#endif
HtmlInfo::generateInfo(fileInfo(), m_ui->tagEditorWidget->originalNotifications());
if(!htmlData.isEmpty()) {
const QString path = QFileDialog::getSaveFileName(this, tr("Save file information - ") + QCoreApplication::applicationName());
if(!path.isEmpty()) {
QFile file(path);
if(file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
QTextStream stream(&file);
stream.setCodec(QTextCodec::codecForName("UTF-8"));
stream << htmlData;
file.close();
if(file.error() != QFileDevice::NoError) {
QMessageBox::critical(this, QApplication::applicationName(), tr("Unable to write to file.\n%1").arg(file.errorString()));
}
} else {
QMessageBox::critical(this, QApplication::applicationName(), tr("Unable to open file."));
}
}
} else {
QMessageBox::information(this, QApplication::applicationName(), tr("No file information available."));
}
} else {
QMessageBox::information(this, QApplication::applicationName(), tr("No file is opened."));
}
} else {
m_ui->statusBar->showMessage(tr("Unable to save file information because the current process hasn't been finished yet.")); m_ui->statusBar->showMessage(tr("Unable to save file information because the current process hasn't been finished yet."));
return;
}
if(!fileInfo().isOpen()) {
QMessageBox::information(this, QApplication::applicationName(), tr("No file is opened."));
return;
}
const QByteArray htmlData =
#ifndef TAGEDITOR_NO_WEBVIEW
!m_ui->tagEditorWidget->fileInfoHtml().isEmpty() ?
m_ui->tagEditorWidget->fileInfoHtml() :
#endif
HtmlInfo::generateInfo(fileInfo(), m_ui->tagEditorWidget->originalNotifications());
if(htmlData.isEmpty()) {
QMessageBox::information(this, QApplication::applicationName(), tr("No file information available."));
return;
}
const QString path = QFileDialog::getSaveFileName(this, tr("Save file information - ") + QCoreApplication::applicationName());
if(path.isEmpty()) {
QMessageBox::critical(this, QApplication::applicationName(), tr("Unable to open file."));
return;
}
QFile file(path);
if(file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
QTextStream stream(&file);
stream.setCodec(QTextCodec::codecForName("UTF-8"));
stream << htmlData;
file.close();
if(file.error() != QFileDevice::NoError) {
QMessageBox::critical(this, QApplication::applicationName(), tr("Unable to write to file.\n%1").arg(file.errorString()));
}
} }
} }

View File

@ -12,7 +12,6 @@
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_CLASS(TagType, unsigned int); DECLARE_ENUM_CLASS(TagType, unsigned int);
@ -80,7 +79,7 @@ private slots:
void toggleDbQueryWidget(); void toggleDbQueryWidget();
private: private:
QMutex &fileOperationMutex(); bool fileOperationOngoing() const;
Media::MediaFileInfo &fileInfo(); Media::MediaFileInfo &fileInfo();
// UI // UI

View File

@ -22,8 +22,6 @@
#include <tagparser/ogg/oggcontainer.h> #include <tagparser/ogg/oggcontainer.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>
@ -56,7 +54,6 @@ using namespace std::placeholders;
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;
using namespace Models; using namespace Models;
@ -90,7 +87,8 @@ TagEditorWidget::TagEditorWidget(QWidget *parent) :
m_infoTreeView(nullptr), m_infoTreeView(nullptr),
m_nextFileAfterSaving(false), m_nextFileAfterSaving(false),
m_makingResultsAvailable(false), m_makingResultsAvailable(false),
m_abortClicked(false) m_abortClicked(false),
m_fileOperationOngoing(false)
{ {
// setup UI // setup UI
m_ui->setupUi(this); m_ui->setupUi(this);
@ -723,82 +721,80 @@ bool TagEditorWidget::startParsing(const QString &path, bool forceRefresh)
if(!forceRefresh && sameFile) { if(!forceRefresh && sameFile) {
return true; return true;
} }
TryLocker<> locker(fileOperationMutex()); if(m_fileOperationOngoing) {
if(locker) { emit statusMessage(tr("Unable to load the selected file \"%1\" because the current process hasn't finished yet.").arg(path));
// clear previous results and status return false;
m_tags.clear(); }
m_fileInfo.clearParsingResults();
m_fileInfo.invalidateStatus(); // clear previous results and status
m_fileInfo.invalidateNotifications(); m_tags.clear();
if(!sameFile) { m_fileInfo.clearParsingResults();
// close last file if possibly open m_fileInfo.invalidateStatus();
m_fileInfo.close(); m_fileInfo.invalidateNotifications();
// set path of file info if(!sameFile) {
m_currentPath = path; // close last file if possibly open
m_fileInfo.setSaveFilePath(string()); 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.setSaveFilePath(string());
m_currentDir = QString::fromLocal8Bit(m_fileInfo.containingDirectory().c_str()); m_fileInfo.setPath(path.toLocal8Bit().data());
} // update directory
// update availability of making results m_lastDir = m_currentDir;
m_makingResultsAvailable &= sameFile; m_currentDir = QString::fromLocal8Bit(m_fileInfo.containingDirectory().c_str());
if(!m_makingResultsAvailable) { }
m_originalNotifications.clear(); // update availability of making results
} m_makingResultsAvailable &= sameFile;
// show filename if(!m_makingResultsAvailable) {
m_ui->fileNameLabel->setText(QString::fromLocal8Bit(m_fileInfo.fileName().c_str())); m_originalNotifications.clear();
// define function to parse the file }
auto startThread = [this] { // show filename
m_fileOperationMutex.lock(); m_ui->fileNameLabel->setText(QString::fromLocal8Bit(m_fileInfo.fileName().c_str()));
char result; // define function to parse the file
try { // credits for this nesting go to GCC regression 66145 auto startThread = [this] {
char result;
try { // credits for this nesting go to GCC regression 66145
try {
try { try {
try { // try to open with write access
// try to open with write access m_fileInfo.reopen(false);
m_fileInfo.reopen(false);
} catch(...) {
::IoUtilities::catchIoFailure();
// try to open read-only if opening with write access failed
m_fileInfo.reopen(true);
}
m_fileInfo.setForceFullParse(Settings::values().editor.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(...) { } catch(...) {
::IoUtilities::catchIoFailure(); ::IoUtilities::catchIoFailure();
// the file could not be opened because an IO error occured // try to open read-only if opening with write access failed
m_fileInfo.close(); // ensure file is closed m_fileInfo.reopen(true);
result = IoError;
} }
} catch(const exception &e) { m_fileInfo.setForceFullParse(Settings::values().editor.forceFullParse);
m_fileInfo.addNotification(Media::NotificationType::Critical, string("Something completely unexpected happened: ") + e.what(), "parsing"); m_fileInfo.parseEverything();
result = ParsingSuccessful;
} catch(const Failure &) {
// the file has been opened; parsing notifications will be shown in the info box
result = FatalParsingError; result = FatalParsingError;
} catch(...) { } catch(...) {
m_fileInfo.addNotification(Media::NotificationType::Critical, "Something completely unexpected happened", "parsing"); ::IoUtilities::catchIoFailure();
result = FatalParsingError; // the file could not be opened because an IO error occured
m_fileInfo.close(); // ensure file is closed
result = IoError;
} }
m_fileInfo.unregisterAllCallbacks(); } catch(const exception &e) {
QMetaObject::invokeMethod(this, "showFile", Qt::QueuedConnection, Q_ARG(char, result)); m_fileInfo.addNotification(Media::NotificationType::Critical, string("Something completely unexpected happened: ") + e.what(), "parsing");
// showFile() will unlock the mutex! result = FatalParsingError;
}; } catch(...) {
m_fileInfo.addNotification(Media::NotificationType::Critical, "Something completely unexpected happened", "parsing");
result = FatalParsingError;
}
m_fileInfo.unregisterAllCallbacks(); m_fileInfo.unregisterAllCallbacks();
// perform the operation concurrently QMetaObject::invokeMethod(this, "showFile", Qt::QueuedConnection, Q_ARG(char, result));
QtConcurrent::run(startThread); };
// inform user m_fileInfo.unregisterAllCallbacks();
static const QString statusMsg(tr("The file is beeing parsed ...")); m_fileOperationOngoing = true;
m_ui->parsingNotificationWidget->setNotificationType(NotificationType::Progress); // perform the operation concurrently
m_ui->parsingNotificationWidget->setText(statusMsg); QtConcurrent::run(startThread);
m_ui->parsingNotificationWidget->setVisible(true); // ensure widget is visible! // inform user
emit statusMessage(statusMsg); static const QString statusMsg(tr("The file is beeing parsed ..."));
return true; m_ui->parsingNotificationWidget->setNotificationType(NotificationType::Progress);
} else { m_ui->parsingNotificationWidget->setText(statusMsg);
emit statusMessage(tr("Unable to load the selected file \"%1\" because the current process hasn't finished yet.").arg(path)); m_ui->parsingNotificationWidget->setVisible(true); // ensure widget is visible!
} emit statusMessage(statusMsg);
return false; return true;
} }
/*! /*!
@ -806,17 +802,13 @@ bool TagEditorWidget::startParsing(const QString &path, bool forceRefresh)
*/ */
bool TagEditorWidget::reparseFile() bool TagEditorWidget::reparseFile()
{ {
{ if(m_fileOperationOngoing) {
TryLocker<> locker(m_fileOperationMutex); emit statusMessage(tr("Unable to reload the file because the current process hasn't finished yet."));
if(locker) { return false;
if(!m_fileInfo.isOpen() || m_currentPath.isEmpty()) { }
QMessageBox::warning(this, windowTitle(), tr("Currently is not file opened.")); if(!m_fileInfo.isOpen() || m_currentPath.isEmpty()) {
return false; 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 startParsing(m_currentPath, true); return startParsing(m_currentPath, true);
} }
@ -826,11 +818,10 @@ bool TagEditorWidget::reparseFile()
* This private slot is invoked from the thread which performed the * This private slot is invoked from the thread which performed the
* parsing operation using Qt::QueuedConnection. * parsing operation using Qt::QueuedConnection.
* \param result Specifies whether the file could be load sucessfully. * \param result Specifies whether the file could be load sucessfully.
* \remarks Expects m_fileOperationMutex to be locked!
*/ */
void TagEditorWidget::showFile(char result) void TagEditorWidget::showFile(char result)
{ {
AdoptLocker<> locker(m_fileOperationMutex); m_fileOperationOngoing = false;
if(result == IoError) { if(result == IoError) {
// update status // update status
updateFileStatusStatus(); updateFileStatusStatus();
@ -941,43 +932,39 @@ void TagEditorWidget::saveAndShowNextFile()
*/ */
bool TagEditorWidget::applyEntriesAndSaveChangings() bool TagEditorWidget::applyEntriesAndSaveChangings()
{ {
{ if(m_fileOperationOngoing) {
TryLocker<> locker(m_fileOperationMutex); static const QString statusMsg(tr("Unable to apply the entered tags to the file because the current process hasn't finished yet."));
if(locker) { emit statusMessage(statusMsg);
m_ui->makingNotificationWidget->setNotificationType(NotificationType::Information); return false;
m_ui->makingNotificationWidget->setNotificationSubject(NotificationSubject::Saving); }
m_ui->makingNotificationWidget->setHidden(false);
m_makingResultsAvailable = true; m_ui->makingNotificationWidget->setNotificationType(NotificationType::Information);
if(m_fileInfo.isOpen()) { m_ui->makingNotificationWidget->setNotificationSubject(NotificationSubject::Saving);
// apply titles m_ui->makingNotificationWidget->setHidden(false);
if(AbstractContainer *container = m_fileInfo.container()) {
if(container->supportsTitle()) { if(!m_fileInfo.isOpen()) {
QLayout *docTitleLayout = m_ui->docTitleWidget->layout(); m_ui->makingNotificationWidget->setText(tr("No file has been opened, so tags can not be saved."));
for(int i = 0, count = min<int>(docTitleLayout->count() - 1, container->segmentCount()); i < count; ++i) { return false;
container->setTitle(static_cast<ClearLineEdit *>(docTitleLayout->itemAt(i + 1)->widget())->text().toUtf8().data(), i); }
}
} m_makingResultsAvailable = true;
}
// apply all tags // apply titles
foreachTagEdit([] (TagEdit *edit) {edit->apply();}); if(AbstractContainer *container = m_fileInfo.container()) {
static const QString statusMsg(tr("Saving tags ...")); if(container->supportsTitle()) {
m_ui->makingNotificationWidget->setNotificationSubject(NotificationSubject::None); QLayout *docTitleLayout = m_ui->docTitleWidget->layout();
m_ui->makingNotificationWidget->setNotificationType(NotificationType::Progress); for(int i = 0, count = min<int>(docTitleLayout->count() - 1, container->segmentCount()); i < count; ++i) {
m_ui->makingNotificationWidget->setText(statusMsg); container->setTitle(static_cast<ClearLineEdit *>(docTitleLayout->itemAt(i + 1)->widget())->text().toUtf8().data(), i);
emit statusMessage(statusMsg);
} else {
static const 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."));
m_ui->makingNotificationWidget->setText(statusMsg);
emit statusMessage(statusMsg);
return false;
} }
} }
// 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);
return startSaving(); return startSaving();
} }
@ -987,148 +974,148 @@ bool TagEditorWidget::applyEntriesAndSaveChangings()
*/ */
bool TagEditorWidget::deleteAllTagsAndSave() bool TagEditorWidget::deleteAllTagsAndSave()
{ {
{ if(m_fileOperationOngoing) {
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) { emit statusMessage(statusMsg);
if(Settings::values().editor.askBeforeDeleting) { return false;
QMessageBox msgBox(this); }
msgBox.setText(tr("Do you really want to delete all tags from the file?"));
msgBox.setIcon(QMessageBox::Warning); m_ui->makingNotificationWidget->setNotificationSubject(NotificationSubject::Saving);
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); m_ui->makingNotificationWidget->setNotificationType(NotificationType::Information);
msgBox.setDefaultButton(QMessageBox::No); m_ui->makingNotificationWidget->setHidden(false);
if(!m_fileInfo.isOpen()) {
m_ui->makingNotificationWidget->setText(tr("No file has been opened, so no tags can be deleted."));
return false;
}
if(!m_fileInfo.hasAnyTag()) {
m_ui->makingNotificationWidget->setText(tr("The selected file has no tag (at least no supported), so there is nothing to delete."));
return false;
}
if(Settings::values().editor.askBeforeDeleting) {
m_ui->makingNotificationWidget->setText(tr("Do you really want to delete all tags from the file?"));
QMessageBox msgBox(this);
msgBox.setText(m_ui->makingNotificationWidget->text());
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::values().editor.askBeforeDeleting = false; Settings::values().editor.askBeforeDeleting = false;
} }
#endif #endif
if(res != QMessageBox::Yes) { if(res != QMessageBox::Yes) {
return false; m_ui->makingNotificationWidget->setText(tr("Deletion aborted."));
}
}
m_ui->makingNotificationWidget->setNotificationSubject(NotificationSubject::Saving);
m_ui->makingNotificationWidget->setNotificationType(NotificationType::Information);
m_ui->makingNotificationWidget->setHidden(false);
m_makingResultsAvailable = true;
if(m_fileInfo.isOpen()) {
if(m_fileInfo.hasAnyTag()) {
foreachTagEdit([] (TagEdit *edit) {edit->clear();});
m_fileInfo.removeAllTags();
m_ui->makingNotificationWidget->setNotificationSubject(NotificationSubject::None);
m_ui->makingNotificationWidget->setNotificationType(NotificationType::Progress);
static const QString statusMsg(tr("Deleting all tags ..."));
m_ui->makingNotificationWidget->setText(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 {
m_ui->makingNotificationWidget->setText(tr("No file has been opened, so no tags can be deleted."));
return false;
}
} else {
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;
} }
} }
m_makingResultsAvailable = true;
foreachTagEdit([] (TagEdit *edit) {edit->clear();});
m_fileInfo.removeAllTags();
m_ui->makingNotificationWidget->setNotificationSubject(NotificationSubject::None);
m_ui->makingNotificationWidget->setNotificationType(NotificationType::Progress);
static const QString statusMsg(tr("Deleting all tags ..."));
m_ui->makingNotificationWidget->setText(statusMsg);
emit statusMessage(statusMsg);
return startSaving(); return startSaving();
} }
/*! /*!
* \brief Starts saving. This method is called by applyEntriesAndSaveChangings() and deleteAllTagsAndSave(). * \brief Starts saving. This method is called by applyEntriesAndSaveChangings() and deleteAllTagsAndSave().
*
* The actual process is performed in another thread. * The actual process is performed in another thread.
*
* \remarks Will start a new thread to perform the operation. Then showSavingResult() is called * \remarks Will start a new thread to perform the operation. Then showSavingResult() is called
* using Qt::QueuedConnection in the main thread. m_fileOperationMutex will remain locked when saving is * using Qt::QueuedConnection in the main thread.
* finished and will be unlocked in showSavingResult(). This way any method which might be called after
* the operation thread ends and before the invokation of showSavingResult() will see a locked mutex and
* hence not mutate the current file.
*/ */
bool TagEditorWidget::startSaving() bool TagEditorWidget::startSaving()
{ {
TryLocker<> locker(m_fileOperationMutex); if(m_fileOperationOngoing) {
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
const auto &settings = Settings::values().tagPocessing.fileLayout;
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 {
try {
m_fileInfo.applyChanges();
} catch(const Failure &) {
processingError = true;
} catch(...) {
::IoUtilities::catchIoFailure();
ioError = true;
}
} catch(const exception &e) {
m_fileInfo.addNotification(Media::NotificationType::Critical, string("Something completely unexpected happened: ") + e.what(), "making");
processingError = true;
} catch(...) {
m_fileInfo.addNotification(Media::NotificationType::Critical, "Something completely unexpected happened", "making");
processingError = 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;
} }
// 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
const auto &settings = Settings::values().tagPocessing.fileLayout;
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] {
bool processingError = false, ioError = false;
try {
try {
m_fileInfo.applyChanges();
} catch(const Failure &) {
processingError = true;
} catch(...) {
::IoUtilities::catchIoFailure();
ioError = true;
}
} catch(const exception &e) {
m_fileInfo.addNotification(Media::NotificationType::Critical, string("Something completely unexpected happened: ") + e.what(), "making");
processingError = true;
} catch(...) {
m_fileInfo.addNotification(Media::NotificationType::Critical, "Something completely unexpected happened", "making");
processingError = true;
}
m_fileInfo.unregisterAllCallbacks();
QMetaObject::invokeMethod(this, "showSavingResult", Qt::QueuedConnection, Q_ARG(bool, processingError), Q_ARG(bool, ioError));
};
m_fileInfo.unregisterAllCallbacks();
m_fileInfo.registerCallback(showProgress);
m_fileOperationOngoing = true;
// use another thread to perform the operation
QtConcurrent::run(startThread);
return true;
} }
/*! /*!
* \brief Shows the saving results. * \brief Shows the saving results.
*
* This private slot is invoked from the thread which performed the * This private slot is invoked from the thread which performed the
* saving operation using Qt::QueuedConnection. * saving operation using Qt::QueuedConnection.
*
* \param sucess Specifies whether the file could be saved sucessfully. * \param sucess Specifies whether the file could be saved sucessfully.
* \remarks Expects m_fileOperationMutex to be locked!
*/ */
void TagEditorWidget::showSavingResult(bool processingError, bool ioError) void TagEditorWidget::showSavingResult(bool processingError, bool ioError)
{ {
m_fileOperationOngoing = false;
m_ui->abortButton->setHidden(true); m_ui->abortButton->setHidden(true);
m_ui->makingNotificationWidget->setNotificationType(NotificationType::TaskComplete); m_ui->makingNotificationWidget->setNotificationType(NotificationType::TaskComplete);
m_ui->makingNotificationWidget->setNotificationSubject(NotificationSubject::Saving); m_ui->makingNotificationWidget->setNotificationSubject(NotificationSubject::Saving);
@ -1166,7 +1153,6 @@ void TagEditorWidget::showSavingResult(bool processingError, bool ioError)
} }
m_ui->makingNotificationWidget->setText(statusMsg); m_ui->makingNotificationWidget->setText(statusMsg);
emit statusMessage(statusMsg); emit statusMessage(statusMsg);
m_fileOperationMutex.unlock();
// let the main window know that the current file has been saved // let the main window know that the current file has been saved
// -> the main window will ensure the current file is still selected // -> the main window will ensure the current file is still selected
emit currentPathChanged(m_currentPath); emit currentPathChanged(m_currentPath);
@ -1193,7 +1179,6 @@ void TagEditorWidget::showSavingResult(bool processingError, bool ioError)
// -> reset "save as path" in any case after fatal error // -> reset "save as path" in any case after fatal error
m_fileInfo.setSaveFilePath(string()); m_fileInfo.setSaveFilePath(string());
m_fileOperationMutex.unlock();
startParsing(m_currentPath, true); startParsing(m_currentPath, true);
} }
} }
@ -1230,18 +1215,18 @@ void TagEditorWidget::fileChangedOnDisk(const QString &path)
*/ */
void TagEditorWidget::closeFile() void TagEditorWidget::closeFile()
{ {
TryLocker<> locker(m_fileOperationMutex); if(m_fileOperationOngoing) {
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;
} }
// 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();
} }
/*! /*!
@ -1300,28 +1285,29 @@ void TagEditorWidget::applySettingsFromDialog()
*/ */
void TagEditorWidget::addTag(const function<Media::Tag *(Media::MediaFileInfo &)> &createTag) void TagEditorWidget::addTag(const function<Media::Tag *(Media::MediaFileInfo &)> &createTag)
{ {
TryLocker<> locker(m_fileOperationMutex); if(m_fileOperationOngoing) {
if(locker) { emit statusMessage("Unable to add a tag because the current process hasn't been finished yet.");
if(!m_fileInfo.isOpen()) { return;
emit statusMessage("Unable to add a tag because no file is opened."); }
if(!m_fileInfo.isOpen()) {
emit statusMessage("Unable to add a tag because no file is opened.");
return;
}
if(Tag *tag = createTag(m_fileInfo)) {
if(std::find(m_tags.cbegin(), m_tags.cend(), tag) != m_tags.cend()) {
QMessageBox::warning(this, windowTitle(), tr("A tag (with the selected target) already exists."));
return; return;
} }
if(Tag *tag = createTag(m_fileInfo)) {
if(std::find(m_tags.cbegin(), m_tags.cend(), tag) == m_tags.cend()) { m_tags.push_back(tag);
m_tags.push_back(tag); updateTagEditsAndAttachmentEdits(true, m_tags.size() > 1 ? PreviousValueHandling::Keep : PreviousValueHandling::Auto);
updateTagEditsAndAttachmentEdits(true, m_tags.size() > 1 ? PreviousValueHandling::Keep : PreviousValueHandling::Auto); updateTagSelectionComboBox();
updateTagSelectionComboBox(); updateTagManagementMenu();
updateTagManagementMenu(); updateFileStatusStatus();
updateFileStatusStatus(); insertTitleFromFilename();
insertTitleFromFilename();
} else {
QMessageBox::warning(this, windowTitle(), tr("A tag (with the selected target) already exists."));
}
} else {
QMessageBox::warning(this, windowTitle(), tr("The tag can not be created."));
}
} else { } else {
emit statusMessage("Unable to add a tag because the current process hasn't been finished yet."); QMessageBox::warning(this, windowTitle(), tr("The tag can not be created."));
} }
} }
@ -1333,53 +1319,51 @@ void TagEditorWidget::addTag(const function<Media::Tag *(Media::MediaFileInfo &)
void TagEditorWidget::removeTag(Tag *tag) void TagEditorWidget::removeTag(Tag *tag)
{ {
if(tag) { if(tag) {
TryLocker<> locker(m_fileOperationMutex); if(m_fileOperationOngoing) {
if(locker) {
if(!m_fileInfo.isOpen()) {
emit statusMessage(tr("Unable to remove the tag because no file is opened."));
return;
}
if(m_fileInfo.isOpen()) {
m_fileInfo.removeTag(tag);
// remove tag from m_tags
m_tags.erase(remove(m_tags.begin(), m_tags.end(), tag), m_tags.end());
// remove tag from all TagEdit widgets
vector<TagEdit *> toRemove;
for(int index = 0, count = m_ui->stackedWidget->count(); index < count; ++index) {
TagEdit *edit = qobject_cast<TagEdit *>(m_ui->stackedWidget->widget(index));
if(edit && edit->tags().contains(tag)) {
QList<Tag *> tagsOfEdit = edit->tags();
tagsOfEdit.removeAll(tag);
if(tagsOfEdit.empty()) {
// no tags left in the edit
if(m_tags.empty()) {
// there are no other tag edits -> just disable the edit
edit->setTag(nullptr, false);
} else {
// there are still other tag edits -> remove the edit
toRemove.push_back(edit);
}
} 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();
}
} else {
emit statusMessage(tr("Unable to remove the tag because the current process hasn't been finished yet.")); emit statusMessage(tr("Unable to remove the tag because the current process hasn't been finished yet."));
return;
} }
if(!m_fileInfo.isOpen()) {
emit statusMessage(tr("Unable to remove the tag because no file is opened."));
return;
}
m_fileInfo.removeTag(tag);
// remove tag from m_tags
m_tags.erase(remove(m_tags.begin(), m_tags.end(), tag), m_tags.end());
// remove tag from all TagEdit widgets
vector<TagEdit *> toRemove;
for(int index = 0, count = m_ui->stackedWidget->count(); index < count; ++index) {
TagEdit *edit = qobject_cast<TagEdit *>(m_ui->stackedWidget->widget(index));
if(edit && edit->tags().contains(tag)) {
QList<Tag *> tagsOfEdit = edit->tags();
tagsOfEdit.removeAll(tag);
if(tagsOfEdit.empty()) {
// no tags left in the edit
if(m_tags.empty()) {
// there are no other tag edits -> just disable the edit
edit->setTag(nullptr, false);
} else {
// there are still other tag edits -> remove the edit
toRemove.push_back(edit);
}
} 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();
} }
} }
@ -1391,27 +1375,25 @@ void TagEditorWidget::removeTag(Tag *tag)
void TagEditorWidget::changeTarget(Tag *tag) void TagEditorWidget::changeTarget(Tag *tag)
{ {
if(tag) { if(tag) {
TryLocker<> locker(m_fileOperationMutex); if(m_fileOperationOngoing) {
if(locker) {
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."));
}
}
} else {
emit statusMessage(tr("Unable to change the target because the current process hasn't been finished yet.")); emit statusMessage(tr("Unable to change the target because the current process hasn't been finished yet."));
return;
}
if(!m_fileInfo.isOpen()) {
emit statusMessage(tr("Unable to change the target because no file is opened."));
return;
}
if(!tag->supportsTarget()) {
QMessageBox::warning(this, windowTitle(), tr("Can not change the target of the selected tag because the tag does not support targets."));
return;
}
EnterTargetDialog targetDlg(this);
targetDlg.setTarget(tag->target(), &m_fileInfo);
if(targetDlg.exec() == QDialog::Accepted) {
tag->setTarget(targetDlg.target());
updateTagSelectionComboBox();
updateTagManagementMenu();
} }
} }
} }

View File

@ -7,7 +7,6 @@
#include <QWidget> #include <QWidget>
#include <QByteArray> #include <QByteArray>
#include <QMutex>
#include <functional> #include <functional>
@ -55,7 +54,7 @@ public:
~TagEditorWidget(); ~TagEditorWidget();
public: public:
QMutex &fileOperationMutex(); bool fileOperationOngoing() const;
const QString &currentPath() const; const QString &currentPath() const;
const QString &currentDir() const; const QString &currentDir() const;
Media::MediaFileInfo &fileInfo(); Media::MediaFileInfo &fileInfo();
@ -151,15 +150,15 @@ private:
bool m_makingResultsAvailable; bool m_makingResultsAvailable;
Media::NotificationList m_originalNotifications; Media::NotificationList m_originalNotifications;
bool m_abortClicked; bool m_abortClicked;
QMutex m_fileOperationMutex; bool m_fileOperationOngoing;
}; };
/*! /*!
* \brief Returns the mutex which is internally used for thread-synchronization. * \brief Returns the mutex which is internally used for thread-synchronization.
*/ */
inline QMutex &TagEditorWidget::fileOperationMutex() inline bool TagEditorWidget::fileOperationOngoing() const
{ {
return m_fileOperationMutex; return m_fileOperationOngoing;
} }
/*! /*!