diff --git a/share/demo.kdbx b/share/demo.kdbx index f78524633..35fd79ce1 100644 Binary files a/share/demo.kdbx and b/share/demo.kdbx differ diff --git a/src/core/EntryAttachments.cpp b/src/core/EntryAttachments.cpp index be8135f5f..1afb337ab 100644 --- a/src/core/EntryAttachments.cpp +++ b/src/core/EntryAttachments.cpp @@ -18,14 +18,25 @@ #include "EntryAttachments.h" #include "core/Global.h" +#include "crypto/Random.h" +#include +#include +#include #include +#include +#include EntryAttachments::EntryAttachments(QObject* parent) : ModifiableObject(parent) { } +EntryAttachments::~EntryAttachments() +{ + clear(); +} + QList EntryAttachments::keys() const { return m_attachments.keys(); @@ -82,6 +93,10 @@ void EntryAttachments::remove(const QString& key) m_attachments.remove(key); + if (m_openedAttachments.contains(key)) { + disconnectAndEraseExternalFile(m_openedAttachments.value(key)); + } + emit removed(key); emitModified(); } @@ -92,20 +107,17 @@ void EntryAttachments::remove(const QStringList& keys) return; } + bool emitStatus = modifiedSignalEnabled(); + setEmitModified(false); + bool isModified = false; for (const QString& key : keys) { - if (!m_attachments.contains(key)) { - Q_ASSERT_X( - false, "EntryAttachments::remove", qPrintable(QString("Can't find attachment for key %1").arg(key))); - continue; - } - - isModified = true; - emit aboutToBeRemoved(key); - m_attachments.remove(key); - emit removed(key); + isModified |= m_attachments.contains(key); + remove(key); } + setEmitModified(emitStatus); + if (isModified) { emitModified(); } @@ -133,15 +145,47 @@ void EntryAttachments::clear() m_attachments.clear(); + const auto externalPath = m_openedAttachments.values(); + for (auto& path : externalPath) { + disconnectAndEraseExternalFile(path); + } + emit reset(); emitModified(); } +void EntryAttachments::disconnectAndEraseExternalFile(const QString& path) +{ + if (m_openedAttachmentsInverse.contains(path)) { + m_attachmentFileWatchers.value(path)->stop(); + m_attachmentFileWatchers.remove(path); + + m_openedAttachments.remove(m_openedAttachmentsInverse.value(path)); + m_openedAttachmentsInverse.remove(path); + } + + QFile f(path); + if (f.open(QFile::ReadWrite)) { + qint64 blocks = f.size() / 128 + 1; + for (qint64 i = 0; i < blocks; ++i) { + f.write(randomGen()->randomArray(128)); + } + f.close(); + } + f.remove(); +} + void EntryAttachments::copyDataFrom(const EntryAttachments* other) { if (*this != *other) { emit aboutToBeReset(); + // Reset all externally opened files + const auto externalPath = m_openedAttachments.values(); + for (auto& path : externalPath) { + disconnectAndEraseExternalFile(path); + } + m_attachments = other->m_attachments; emit reset(); @@ -167,3 +211,54 @@ int EntryAttachments::attachmentsSize() const } return size; } + +bool EntryAttachments::openAttachment(const QString& key, QString* errorMessage) +{ + if (!m_openedAttachments.contains(key)) { + const QByteArray attachmentData = value(key); + auto ext = key.contains(".") ? "." + key.split(".").last() : ""; + +#ifdef KEEPASSXC_DIST_SNAP + const QString tmpFileTemplate = + QString("%1/XXXXXXXXXXXX%2").arg(QProcessEnvironment::systemEnvironment().value("SNAP_USER_DATA"), ext); +#else + const QString tmpFileTemplate = QDir::temp().absoluteFilePath(QString("XXXXXXXXXXXX").append(ext)); +#endif + + QTemporaryFile tmpFile(tmpFileTemplate); + + const bool saveOk = tmpFile.open() && tmpFile.setPermissions(QFile::ReadOwner | QFile::WriteOwner) + && tmpFile.write(attachmentData) == attachmentData.size() && tmpFile.flush(); + + if (!saveOk && errorMessage) { + *errorMessage = tr("%1 - %2").arg(key, tmpFile.errorString()); + return false; + } + + tmpFile.close(); + tmpFile.setAutoRemove(false); + m_openedAttachments.insert(key, tmpFile.fileName()); + m_openedAttachmentsInverse.insert(tmpFile.fileName(), key); + + auto watcher = QSharedPointer::create(); + watcher->start(tmpFile.fileName(), 5); + connect(watcher.data(), &FileWatcher::fileChanged, this, &EntryAttachments::attachmentFileModified); + m_attachmentFileWatchers.insert(tmpFile.fileName(), watcher); + } + + const bool openOk = QDesktopServices::openUrl(QUrl::fromLocalFile(m_openedAttachments.value(key))); + if (!openOk && errorMessage) { + *errorMessage = tr("Cannot open file \"%1\"").arg(key); + return false; + } + + return true; +} + +void EntryAttachments::attachmentFileModified(const QString& path) +{ + auto it = m_openedAttachmentsInverse.find(path); + if (it != m_openedAttachmentsInverse.end()) { + emit valueModifiedExternally(it.value(), path); + } +} diff --git a/src/core/EntryAttachments.h b/src/core/EntryAttachments.h index d23681b90..1fc35df89 100644 --- a/src/core/EntryAttachments.h +++ b/src/core/EntryAttachments.h @@ -18,10 +18,13 @@ #ifndef KEEPASSX_ENTRYATTACHMENTS_H #define KEEPASSX_ENTRYATTACHMENTS_H +#include "core/FileWatcher.h" +#include "core/ModifiableObject.h" + +#include #include #include - -#include "core/ModifiableObject.h" +#include class QStringList; @@ -31,6 +34,7 @@ class EntryAttachments : public ModifiableObject public: explicit EntryAttachments(QObject* parent = nullptr); + virtual ~EntryAttachments(); QList keys() const; bool hasKey(const QString& key) const; QSet values() const; @@ -45,9 +49,11 @@ public: bool operator==(const EntryAttachments& other) const; bool operator!=(const EntryAttachments& other) const; int attachmentsSize() const; + bool openAttachment(const QString& key, QString* errorMessage = nullptr); signals: void keyModified(const QString& key); + void valueModifiedExternally(const QString& key, const QString& path); void aboutToBeAdded(const QString& key); void added(const QString& key); void aboutToBeRemoved(const QString& key); @@ -55,8 +61,16 @@ signals: void aboutToBeReset(); void reset(); +private slots: + void attachmentFileModified(const QString& path); + private: + void disconnectAndEraseExternalFile(const QString& path); + QMap m_attachments; + QHash m_openedAttachments; + QHash m_openedAttachmentsInverse; + QHash> m_attachmentFileWatchers; }; #endif // KEEPASSX_ENTRYATTACHMENTS_H diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 86dbea417..9daae9f88 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -329,6 +329,7 @@ void DatabaseWidget::clearAllWidgets() m_editEntryWidget->clear(); m_historyEditEntryWidget->clear(); m_editGroupWidget->clear(); + m_previewView->clear(); } void DatabaseWidget::emitCurrentModeChanged() diff --git a/src/gui/EntryPreviewWidget.cpp b/src/gui/EntryPreviewWidget.cpp index b7d783e6d..74fec6af5 100644 --- a/src/gui/EntryPreviewWidget.cpp +++ b/src/gui/EntryPreviewWidget.cpp @@ -94,6 +94,14 @@ EntryPreviewWidget::~EntryPreviewWidget() { } +void EntryPreviewWidget::clear() +{ + hide(); + m_currentEntry = nullptr; + m_currentGroup = nullptr; + m_ui->entryAttachmentsWidget->unlinkAttachments(); +} + void EntryPreviewWidget::setEntry(Entry* selectedEntry) { if (!selectedEntry) { @@ -339,7 +347,7 @@ void EntryPreviewWidget::updateEntryAdvancedTab() m_ui->entryAttributesTable->horizontalHeader()->setStretchLastSection(true); m_ui->entryAttributesTable->resizeColumnsToContents(); m_ui->entryAttributesTable->resizeRowsToContents(); - m_ui->entryAttachmentsWidget->setEntryAttachments(m_currentEntry->attachments()); + m_ui->entryAttachmentsWidget->linkAttachments(m_currentEntry->attachments()); } void EntryPreviewWidget::updateEntryAutotypeTab() diff --git a/src/gui/EntryPreviewWidget.h b/src/gui/EntryPreviewWidget.h index 335ef26e7..8a5b0c09f 100644 --- a/src/gui/EntryPreviewWidget.h +++ b/src/gui/EntryPreviewWidget.h @@ -40,6 +40,7 @@ public slots: void setEntry(Entry* selectedEntry); void setGroup(Group* selectedGroup); void setDatabaseMode(DatabaseWidget::Mode mode); + void clear(); signals: void errorOccurred(const QString& error); diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index 5687351b5..9a70b2118 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -66,6 +66,7 @@ EditEntryWidget::EditEntryWidget(QWidget* parent) , m_sshAgentUi(new Ui::EditEntryWidgetSSHAgent()) , m_historyUi(new Ui::EditEntryWidgetHistory()) , m_browserUi(new Ui::EditEntryWidgetBrowser()) + , m_attachments(new EntryAttachments()) , m_customData(new CustomData()) , m_mainWidget(new QScrollArea()) , m_advancedWidget(new QWidget()) @@ -537,7 +538,7 @@ void EditEntryWidget::setupSSHAgent() connect(m_sshAgentUi->decryptButton, &QPushButton::clicked, this, &EditEntryWidget::decryptPrivateKey); connect(m_sshAgentUi->copyToClipboardButton, &QPushButton::clicked, this, &EditEntryWidget::copyPublicKey); - connect(m_advancedUi->attachmentsWidget->entryAttachments(), &EntryAttachments::modified, + connect(m_attachments.data(), &EntryAttachments::modified, this, &EditEntryWidget::updateSSHAgentAttachments); // clang-format on @@ -576,7 +577,7 @@ void EditEntryWidget::updateSSHAgentAttachments() { // detect if KeeAgent.settings was removed by hand and reset settings if (m_entry && KeeAgentSettings::inEntryAttachments(m_entry->attachments()) - && !KeeAgentSettings::inEntryAttachments(m_advancedUi->attachmentsWidget->entryAttachments())) { + && !KeeAgentSettings::inEntryAttachments(m_attachments.data())) { m_sshAgentSettings.reset(); setSSHAgentSettings(); } @@ -584,8 +585,7 @@ void EditEntryWidget::updateSSHAgentAttachments() m_sshAgentUi->attachmentComboBox->clear(); m_sshAgentUi->attachmentComboBox->addItem(""); - auto attachments = m_advancedUi->attachmentsWidget->entryAttachments(); - for (const QString& fileName : attachments->keys()) { + for (const QString& fileName : m_attachments->keys()) { if (fileName == "KeeAgent.settings") { continue; } @@ -698,7 +698,7 @@ bool EditEntryWidget::getOpenSSHKey(OpenSSHKey& key, bool decrypt) if (!settings.toOpenSSHKey(m_mainUi->usernameComboBox->lineEdit()->text(), m_mainUi->passwordEdit->text(), m_db->filePath(), - m_advancedUi->attachmentsWidget->entryAttachments(), + m_attachments.data(), key, decrypt)) { showMessage(settings.errorString(), MessageWidget::Error); @@ -828,6 +828,7 @@ void EditEntryWidget::loadEntry(Entry* entry, void EditEntryWidget::setForms(Entry* entry, bool restore) { + m_attachments->copyDataFrom(entry->attachments()); m_customData->copyDataFrom(entry->customData()); m_mainUi->titleEdit->setReadOnly(m_history); @@ -888,7 +889,7 @@ void EditEntryWidget::setForms(Entry* entry, bool restore) m_mainUi->notesEdit->setPlainText(entry->notes()); - m_advancedUi->attachmentsWidget->setEntryAttachments(entry->attachments()); + m_advancedUi->attachmentsWidget->linkAttachments(m_attachments.data()); m_entryAttributes->copyCustomKeysFrom(entry->attributes()); if (m_attributesModel->rowCount() != 0) { @@ -1090,7 +1091,6 @@ bool EditEntryWidget::commitEntry() } m_historyModel->setEntries(m_entry->historyItems()); - m_advancedUi->attachmentsWidget->setEntryAttachments(m_entry->attachments()); showMessage(tr("Entry updated successfully."), MessageWidget::Positive); setModified(false); @@ -1110,7 +1110,7 @@ void EditEntryWidget::updateEntryData(Entry* entry) const QRegularExpression newLineRegex("(?:\r?\n|\r)"); entry->attributes()->copyCustomKeysFrom(m_entryAttributes); - entry->attachments()->copyDataFrom(m_advancedUi->attachmentsWidget->entryAttachments()); + entry->attachments()->copyDataFrom(m_attachments.data()); entry->customData()->copyDataFrom(m_customData.data()); entry->setTitle(m_mainUi->titleEdit->text().replace(newLineRegex, " ")); entry->setUsername(m_mainUi->usernameComboBox->lineEdit()->text().replace(newLineRegex, " ")); @@ -1212,7 +1212,8 @@ void EditEntryWidget::clear() m_mainUi->notesEdit->clear(); m_entryAttributes->clear(); - m_advancedUi->attachmentsWidget->clearAttachments(); + m_attachments->clear(); + m_customData->clear(); m_autoTypeAssoc->clear(); m_historyModel->clear(); m_iconsWidget->reset(); diff --git a/src/gui/entry/EditEntryWidget.h b/src/gui/entry/EditEntryWidget.h index 47975ac6d..4616e1af5 100644 --- a/src/gui/entry/EditEntryWidget.h +++ b/src/gui/entry/EditEntryWidget.h @@ -35,6 +35,7 @@ class EditWidgetIcons; class EditWidgetProperties; class Entry; class EntryAttributes; +class EntryAttachments; class EntryAttributesModel; class EntryHistoryModel; class QButtonGroup; @@ -171,6 +172,7 @@ private: const QScopedPointer m_sshAgentUi; const QScopedPointer m_historyUi; const QScopedPointer m_browserUi; + const QScopedPointer m_attachments; const QScopedPointer m_customData; QScrollArea* const m_mainWidget; diff --git a/src/gui/entry/EntryAttachmentsModel.h b/src/gui/entry/EntryAttachmentsModel.h index 486ee059f..d155f25ca 100644 --- a/src/gui/entry/EntryAttachmentsModel.h +++ b/src/gui/entry/EntryAttachmentsModel.h @@ -19,6 +19,7 @@ #define KEEPASSX_ENTRYATTACHMENTSMODEL_H #include +#include class EntryAttachments; @@ -55,7 +56,7 @@ private slots: void setReadOnly(bool readOnly); private: - EntryAttachments* m_entryAttachments; + QPointer m_entryAttachments; QStringList m_headers; bool m_readOnly = false; }; diff --git a/src/gui/entry/EntryAttachmentsWidget.cpp b/src/gui/entry/EntryAttachmentsWidget.cpp index 5d1e9240a..bab041d88 100644 --- a/src/gui/entry/EntryAttachmentsWidget.cpp +++ b/src/gui/entry/EntryAttachmentsWidget.cpp @@ -1,14 +1,30 @@ +/* + * Copyright (C) 2021 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "EntryAttachmentsWidget.h" #include "ui_EntryAttachmentsWidget.h" -#include #include #include #include +#include #include #include "EntryAttachmentsModel.h" -#include "config-keepassx.h" #include "core/Config.h" #include "core/EntryAttachments.h" #include "core/Tools.h" @@ -18,7 +34,7 @@ EntryAttachmentsWidget::EntryAttachmentsWidget(QWidget* parent) : QWidget(parent) , m_ui(new Ui::EntryAttachmentsWidget) - , m_entryAttachments(new EntryAttachments(this)) + , m_entryAttachments(nullptr) , m_attachmentsModel(new EntryAttachmentsModel(this)) , m_readOnly(false) , m_buttonsVisible(true) @@ -29,7 +45,6 @@ EntryAttachmentsWidget::EntryAttachmentsWidget(QWidget* parent) m_ui->attachmentsView->viewport()->setAcceptDrops(true); m_ui->attachmentsView->viewport()->installEventFilter(this); - m_attachmentsModel->setEntryAttachments(m_entryAttachments); m_ui->attachmentsView->setModel(m_attachmentsModel); m_ui->attachmentsView->verticalHeader()->hide(); m_ui->attachmentsView->horizontalHeader()->setStretchLastSection(true); @@ -63,7 +78,7 @@ EntryAttachmentsWidget::~EntryAttachmentsWidget() { } -const EntryAttachments* EntryAttachmentsWidget::entryAttachments() const +const EntryAttachments* EntryAttachmentsWidget::attachments() const { return m_entryAttachments; } @@ -78,15 +93,29 @@ bool EntryAttachmentsWidget::isButtonsVisible() const return m_buttonsVisible; } -void EntryAttachmentsWidget::setEntryAttachments(const EntryAttachments* attachments) +void EntryAttachmentsWidget::linkAttachments(EntryAttachments* attachments) { - Q_ASSERT(attachments != nullptr); - m_entryAttachments->copyDataFrom(attachments); + unlinkAttachments(); + + m_entryAttachments = attachments; + m_attachmentsModel->setEntryAttachments(m_entryAttachments); + + if (m_entryAttachments) { + connect(m_entryAttachments, + SIGNAL(valueModifiedExternally(QString, QString)), + this, + SLOT(attachmentModifiedExternally(QString, QString))); + connect(m_entryAttachments, SIGNAL(modified()), this, SIGNAL(widgetUpdated())); + } } -void EntryAttachmentsWidget::clearAttachments() +void EntryAttachmentsWidget::unlinkAttachments() { - m_entryAttachments->clear(); + if (m_entryAttachments) { + m_entryAttachments->disconnect(this); + m_entryAttachments = nullptr; + m_attachmentsModel->setEntryAttachments(nullptr); + } } void EntryAttachmentsWidget::setReadOnly(bool readOnly) @@ -109,25 +138,9 @@ void EntryAttachmentsWidget::setButtonsVisible(bool buttonsVisible) emit buttonsVisibleChanged(m_buttonsVisible); } -QByteArray EntryAttachmentsWidget::getAttachment(const QString& name) -{ - return m_entryAttachments->value(name); -} - -void EntryAttachmentsWidget::setAttachment(const QString& name, const QByteArray& value) -{ - m_entryAttachments->set(name, value); -} - -void EntryAttachmentsWidget::removeAttachment(const QString& name) -{ - if (!isReadOnly() && m_entryAttachments->hasKey(name)) { - m_entryAttachments->remove(name); - } -} - void EntryAttachmentsWidget::insertAttachments() { + Q_ASSERT(m_entryAttachments); Q_ASSERT(!isReadOnly()); if (isReadOnly()) { return; @@ -153,6 +166,7 @@ void EntryAttachmentsWidget::insertAttachments() void EntryAttachmentsWidget::removeSelectedAttachments() { + Q_ASSERT(m_entryAttachments); Q_ASSERT(!isReadOnly()); if (isReadOnly()) { return; @@ -181,11 +195,14 @@ void EntryAttachmentsWidget::removeSelectedAttachments() void EntryAttachmentsWidget::renameSelectedAttachments() { + Q_ASSERT(m_entryAttachments); m_ui->attachmentsView->edit(m_ui->attachmentsView->selectionModel()->selectedIndexes().first()); } void EntryAttachmentsWidget::saveSelectedAttachments() { + Q_ASSERT(m_entryAttachments); + const QModelIndexList indexes = m_ui->attachmentsView->selectionModel()->selectedRows(0); if (indexes.isEmpty()) { return; @@ -253,7 +270,7 @@ void EntryAttachmentsWidget::openAttachment(const QModelIndex& index) } QString errorMessage; - if (!openAttachment(index, errorMessage)) { + if (!m_entryAttachments->openAttachment(m_attachmentsModel->keyByIndex(index), &errorMessage)) { errorOccurred(tr("Unable to open attachment:\n%1").arg(errorMessage)); } } @@ -268,7 +285,7 @@ void EntryAttachmentsWidget::openSelectedAttachments() QStringList errors; for (const QModelIndex& index : indexes) { QString errorMessage; - if (!openAttachment(index, errorMessage)) { + if (!m_entryAttachments->openAttachment(m_attachmentsModel->keyByIndex(index), &errorMessage)) { const QString filename = m_attachmentsModel->keyByIndex(index); errors.append(QString("%1 - %2").arg(filename, errorMessage)); }; @@ -324,39 +341,6 @@ bool EntryAttachmentsWidget::insertAttachments(const QStringList& filenames, QSt return errors.isEmpty(); } -bool EntryAttachmentsWidget::openAttachment(const QModelIndex& index, QString& errorMessage) -{ - const QString filename = m_attachmentsModel->keyByIndex(index); - const QByteArray attachmentData = m_entryAttachments->value(filename); - - // tmp file will be removed once the database (or the application) has been closed -#ifdef KEEPASSXC_DIST_SNAP - const QString tmpFileTemplate = - QString("%1/XXXXXX.%2").arg(QProcessEnvironment::systemEnvironment().value("SNAP_USER_DATA"), filename); -#else - const QString tmpFileTemplate = QDir::temp().absoluteFilePath(QString("XXXXXX.").append(filename)); -#endif - - QScopedPointer tmpFile(new QTemporaryFile(tmpFileTemplate, this)); - - const bool saveOk = tmpFile->open() && tmpFile->write(attachmentData) == attachmentData.size() && tmpFile->flush(); - if (!saveOk) { - errorMessage = QString("%1 - %2").arg(filename, tmpFile->errorString()); - return false; - } - - tmpFile->close(); - const bool openOk = QDesktopServices::openUrl(QUrl::fromLocalFile(tmpFile->fileName())); - if (!openOk) { - errorMessage = QString("Can't open file \"%1\"").arg(filename); - return false; - } - - // take ownership of the tmpFile pointer - tmpFile.take(); - return true; -} - QStringList EntryAttachmentsWidget::confirmLargeAttachments(const QStringList& filenames) { const QString confirmation(tr("%1 is a big file (%2 MB).\nYour database may get very large and reduce " @@ -421,3 +405,34 @@ bool EntryAttachmentsWidget::eventFilter(QObject* watched, QEvent* e) return QWidget::eventFilter(watched, e); } + +void EntryAttachmentsWidget::attachmentModifiedExternally(const QString& key, const QString& filePath) +{ + if (m_pendingChanges.contains(filePath)) { + return; + } + + m_pendingChanges << filePath; + + auto result = MessageBox::question( + this, + tr("Attachment modified"), + tr("The attachment '%1' was modified.\nDo you want to save the changes to your database?").arg(key), + MessageBox::Save | MessageBox::Discard, + MessageBox::Save); + + if (result == MessageBox::Save) { + QFile f(filePath); + if (f.open(QFile::ReadOnly)) { + m_entryAttachments->set(key, f.readAll()); + f.close(); + emit widgetUpdated(); + } else { + MessageBox::critical(this, + tr("Saving attachment failed"), + tr("Saving updated attachment failed.\nError: %1").arg(f.errorString())); + } + } + + m_pendingChanges.removeAll(filePath); +} diff --git a/src/gui/entry/EntryAttachmentsWidget.h b/src/gui/entry/EntryAttachmentsWidget.h index b959d7c71..53d5076e2 100644 --- a/src/gui/entry/EntryAttachmentsWidget.h +++ b/src/gui/entry/EntryAttachmentsWidget.h @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2021 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #ifndef ENTRYATTACHMENTSWIDGET_H #define ENTRYATTACHMENTSWIDGET_H @@ -22,17 +39,13 @@ public: explicit EntryAttachmentsWidget(QWidget* parent = nullptr); ~EntryAttachmentsWidget(); - const EntryAttachments* entryAttachments() const; + const EntryAttachments* attachments() const; bool isReadOnly() const; bool isButtonsVisible() const; - QByteArray getAttachment(const QString& name); - void setAttachment(const QString& name, const QByteArray& value); - void removeAttachment(const QString& name); - public slots: - void setEntryAttachments(const EntryAttachments* attachments); - void clearAttachments(); + void linkAttachments(EntryAttachments* attachments); + void unlinkAttachments(); void setReadOnly(bool readOnly); void setButtonsVisible(bool isButtonsVisible); @@ -51,10 +64,10 @@ private slots: void openSelectedAttachments(); void updateButtonsVisible(); void updateButtonsEnabled(); + void attachmentModifiedExternally(const QString& key, const QString& filePath); private: bool insertAttachments(const QStringList& fileNames, QString& errorMessage); - bool openAttachment(const QModelIndex& index, QString& errorMessage); QStringList confirmLargeAttachments(const QStringList& filenames); @@ -63,6 +76,7 @@ private: QScopedPointer m_ui; QPointer m_entryAttachments; QPointer m_attachmentsModel; + QStringList m_pendingChanges; bool m_readOnly; bool m_buttonsVisible; };