diff --git a/src/resources/docs/shortcuts_en.md b/src/resources/docs/shortcuts_en.md
index 46a0a035..0942ac2f 100644
--- a/src/resources/docs/shortcuts_en.md
+++ b/src/resources/docs/shortcuts_en.md
@@ -124,6 +124,8 @@ Toggle single panel or two panels mode.
Toggle the Tools panel.
- `F`
Popup the opened notes list of current split window. Within this list, pressing the sequence number in front of each note could jump to that note.
+- `A`
+Popup the attachments list of current note.
- `X`
Close current tab.
- `J`
diff --git a/src/resources/docs/shortcuts_zh.md b/src/resources/docs/shortcuts_zh.md
index 4fd11406..926c0aba 100644
--- a/src/resources/docs/shortcuts_zh.md
+++ b/src/resources/docs/shortcuts_zh.md
@@ -125,6 +125,8 @@ size=8
打开或关闭工具面板。
- `F`
打开当前分割窗口的笔记列表。在该列表中,可以直接按笔记对应的序号实现跳转。
+- `A`
+打开当前笔记的附件列表。
- `X`
关闭当前标签页。
- `J`
diff --git a/src/vattachmentlist.cpp b/src/vattachmentlist.cpp
index f559753b..695df7bc 100644
--- a/src/vattachmentlist.cpp
+++ b/src/vattachmentlist.cpp
@@ -62,7 +62,8 @@ void VAttachmentList::setupUI()
tr("Fail to clear attachments of note %2.")
.arg(g_config->c_dataTextStyle)
.arg(m_file->getName()),
- tr("Please maintain the configureation file manually."),
+ tr("Please check the attachments folder and "
+ "maintain the configuration file manually."),
QMessageBox::Ok,
QMessageBox::Ok,
g_vnote->getMainWindow());
@@ -344,7 +345,8 @@ void VAttachmentList::deleteSelectedItems()
tr("Fail to delete attachments of note %2.")
.arg(g_config->c_dataTextStyle)
.arg(m_file->getName()),
- tr("Please maintain the configureation file manually."),
+ tr("Please check the attachments folder and "
+ "maintain the configuration file manually."),
QMessageBox::Ok,
QMessageBox::Ok,
g_vnote->getMainWindow());
@@ -566,6 +568,8 @@ bool VAttachmentList::handleDropEvent(QDropEvent *p_event)
void VAttachmentList::handleAboutToShow()
{
updateContent();
+
+ checkAttachments();
}
void VAttachmentList::updateButtonState() const
@@ -586,3 +590,65 @@ void VAttachmentList::updateButtonState() const
btn->setBubbleNumber(numOfAttachments);
}
+
+void VAttachmentList::checkAttachments()
+{
+ if (!m_file) {
+ return;
+ }
+
+ QVector missingAttas = m_file->checkAttachments();
+ if (missingAttas.isEmpty()) {
+ return;
+ }
+
+ QVector items;
+ for (auto const & atta : missingAttas) {
+ items.push_back(ConfirmItemInfo(atta,
+ atta,
+ "",
+ NULL));
+ }
+
+ QString text = tr("VNote detects that these attachments of note "
+ "%2 are missing in disk. "
+ "Would you like to remove them from the note?")
+ .arg(g_config->c_dataTextStyle)
+ .arg(m_file->getName());
+
+ QString info = tr("Click \"Cancel\" to leave them untouched.");
+
+ VConfirmDeletionDialog dialog(tr("Confirm Deleting Attachments"),
+ text,
+ info,
+ items,
+ false,
+ false,
+ false,
+ g_vnote->getMainWindow());
+ if (dialog.exec()) {
+ items = dialog.getConfirmedItems();
+
+ QVector names;
+ for (auto const & item : items) {
+ names.push_back(item.m_name);
+ }
+
+ if (!m_file->deleteAttachments(names, true)) {
+ VUtils::showMessage(QMessageBox::Warning,
+ tr("Warning"),
+ tr("Fail to delete attachments of note %2.")
+ .arg(g_config->c_dataTextStyle)
+ .arg(m_file->getName()),
+ tr("Please check the attachments folder and "
+ "maintain the configuration file manually."),
+ QMessageBox::Ok,
+ QMessageBox::Ok,
+ g_vnote->getMainWindow());
+ }
+
+ updateButtonState();
+
+ updateContent();
+ }
+}
diff --git a/src/vattachmentlist.h b/src/vattachmentlist.h
index 9ed2be3a..52a45964 100644
--- a/src/vattachmentlist.h
+++ b/src/vattachmentlist.h
@@ -62,6 +62,9 @@ private:
// Update the state of VButtonWithWidget.
void updateButtonState() const;
+ // Check if there are attachments that do not exist in disk.
+ void checkAttachments();
+
QPushButton *m_addBtn;
QPushButton *m_clearBtn;
QPushButton *m_locateBtn;
diff --git a/src/vnotefile.cpp b/src/vnotefile.cpp
index 743bdbe7..0cbb46f8 100644
--- a/src/vnotefile.cpp
+++ b/src/vnotefile.cpp
@@ -268,7 +268,7 @@ QString VNoteFile::fetchAttachmentFolderPath()
return folderPath;
}
-bool VNoteFile::deleteAttachments()
+bool VNoteFile::deleteAttachments(bool p_omitMissing)
{
if (m_attachments.isEmpty()) {
return true;
@@ -279,10 +279,11 @@ bool VNoteFile::deleteAttachments()
attas.push_back(m_attachments[i].m_name);
}
- return deleteAttachments(attas);
+ return deleteAttachments(attas, p_omitMissing);
}
-bool VNoteFile::deleteAttachments(const QVector &p_names)
+bool VNoteFile::deleteAttachments(const QVector &p_names,
+ bool p_omitMissing)
{
if (p_names.isEmpty()) {
return true;
@@ -298,7 +299,15 @@ bool VNoteFile::deleteAttachments(const QVector &p_names)
}
m_attachments.remove(idx);
- if (!VUtils::deleteFile(getNotebook(), dir.filePath(p_names[i]), false)) {
+
+ QString filePath = dir.filePath(p_names[i]);
+ if (p_omitMissing
+ && !QFileInfo::exists(filePath)) {
+ // The attachment file does not exist. We skip it to avoid error.
+ continue;
+ }
+
+ if (!VUtils::deleteFile(getNotebook(), filePath, false)) {
ret = false;
qWarning() << "fail to delete attachment" << p_names[i]
<< "for note" << m_name;
@@ -386,6 +395,21 @@ bool VNoteFile::renameAttachment(const QString &p_oldName, const QString &p_newN
return true;
}
+QVector VNoteFile::checkAttachments()
+{
+ QVector missing;
+
+ QDir dir(fetchAttachmentFolderPath());
+ for (auto const & atta : m_attachments) {
+ QString file = dir.filePath(atta.m_name);
+ if (!QFileInfo::exists(file)) {
+ missing.push_back(atta.m_name);
+ }
+ }
+
+ return missing;
+}
+
bool VNoteFile::deleteFile(VNoteFile *p_file, QString *p_errMsg)
{
Q_ASSERT(!p_file->isOpened());
diff --git a/src/vnotefile.h b/src/vnotefile.h
index 92e444f1..96d3a344 100644
--- a/src/vnotefile.h
+++ b/src/vnotefile.h
@@ -86,10 +86,13 @@ public:
QString fetchAttachmentFolderPath();
// Delete all the attachments.
- bool deleteAttachments();
+ // @p_omitMissing: omit the error if the attachment file does not exist.
+ bool deleteAttachments(bool p_omitMissing = false);
// Delete attachments specified by @p_names.
- bool deleteAttachments(const QVector &p_names);
+ // @p_omitMissing: omit the error if the attachment file does not exist.
+ bool deleteAttachments(const QVector &p_names,
+ bool p_omitMissing = false);
// Reorder attachments in m_attachments by index.
bool sortAttachments(const QVector &p_sortedIdx);
@@ -101,6 +104,10 @@ public:
// Rename attachment @p_oldName to @p_newName.
bool renameAttachment(const QString &p_oldName, const QString &p_newName);
+ // Check if all the attachment files still exist.
+ // Return the missing attachments' names.
+ QVector checkAttachments();
+
// Create a VNoteFile from @p_json Json object.
static VNoteFile *fromJson(VDirectory *p_directory,
const QJsonObject &p_json,