Skip to content

Commit 0bdcfa6

Browse files
authored
Merge pull request #1899 from ehhc/deleting_of_attachment_link
Deleting of attachments -> fixes #1828 and fixes #740
2 parents 55b8488 + ffc3fb7 commit 0bdcfa6

File tree

3 files changed

+100
-1
lines changed

3 files changed

+100
-1
lines changed

browser/components/CodeEditor.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ export default class CodeEditor extends React.Component {
3636
el = el.parentNode
3737
}
3838
this.props.onBlur != null && this.props.onBlur(e)
39+
40+
const {storageKey, noteKey} = this.props
41+
attachmentManagement.deleteAttachmentsNotPresentInNote(this.editor.getValue(), storageKey, noteKey)
3942
}
4043
this.pasteHandler = (editor, e) => this.handlePaste(editor, e)
4144
this.loadStyleHandler = (e) => {

browser/main/lib/dataApi/attachmentManagement.js

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem
157157
/**
158158
* @description Returns all attachment paths of the given markdown
159159
* @param {String} markdownContent content in which the attachment paths should be found
160-
* @returns {String[]} Array of the relativ paths (starting with :storage) of the attachments of the given markdown
160+
* @returns {String[]} Array of the relative paths (starting with :storage) of the attachments of the given markdown
161161
*/
162162
function getAttachmentsInContent (markdownContent) {
163163
const preparedInput = markdownContent.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep)
@@ -190,6 +190,49 @@ function removeStorageAndNoteReferences (input, noteKey) {
190190
return input.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep).replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + noteKey, 'g'), DESTINATION_FOLDER)
191191
}
192192

193+
/**
194+
* @description Deletes all attachments stored in the attachment folder of the give not that are not referenced in the markdownContent
195+
* @param markdownContent Content of the note. All unreferenced notes will be deleted
196+
* @param storageKey StorageKey of the current note. Is used to determine the belonging attachment folder.
197+
* @param noteKey NoteKey of the current note. Is used to determine the belonging attachment folder.
198+
*/
199+
function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey) {
200+
const targetStorage = findStorage.findStorage(storageKey)
201+
const attachmentFolder = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
202+
const attachmentsInNote = getAttachmentsInContent(markdownContent)
203+
const attachmentsInNoteOnlyFileNames = []
204+
if (attachmentsInNote) {
205+
for (let i = 0; i < attachmentsInNote.length; i++) {
206+
attachmentsInNoteOnlyFileNames.push(attachmentsInNote[i].replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + noteKey + escapeStringRegexp(path.sep), 'g'), ''))
207+
}
208+
}
209+
210+
if (fs.existsSync(attachmentFolder)) {
211+
fs.readdir(attachmentFolder, (err, files) => {
212+
if (err) {
213+
console.error("Error reading directory '" + attachmentFolder + "'. Error:")
214+
console.error(err)
215+
return
216+
}
217+
files.forEach(file => {
218+
if (!attachmentsInNoteOnlyFileNames.includes(file)) {
219+
const absolutePathOfFile = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey, file)
220+
fs.unlink(absolutePathOfFile, (err) => {
221+
if (err) {
222+
console.error("Could not delete '%s'", absolutePathOfFile)
223+
console.error(err)
224+
return
225+
}
226+
console.info("File '" + absolutePathOfFile + "' deleted because it was not included in the content of the note")
227+
})
228+
}
229+
})
230+
})
231+
} else {
232+
console.info("Attachment folder ('" + attachmentFolder + "') did not exist..")
233+
}
234+
}
235+
193236
module.exports = {
194237
copyAttachment,
195238
fixLocalURLS,
@@ -199,6 +242,7 @@ module.exports = {
199242
getAttachmentsInContent,
200243
getAbsolutePathsOfAttachmentsInContent,
201244
removeStorageAndNoteReferences,
245+
deleteAttachmentsNotPresentInNote,
202246
STORAGE_FOLDER_PLACEHOLDER,
203247
DESTINATION_FOLDER
204248
}

tests/dataApi/attachmentManagement.test.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,3 +260,55 @@ it('should remove the all ":storage" and noteKey references', function () {
260260
const actual = systemUnderTest.removeStorageAndNoteReferences(testInput, noteKey)
261261
expect(actual).toEqual(expectedOutput)
262262
})
263+
264+
it('should test that deleteAttachmentsNotPresentInNote deletes all unreferenced attachments ', function () {
265+
const dummyStorage = {path: 'dummyStoragePath'}
266+
const noteKey = 'noteKey'
267+
const storageKey = 'storageKey'
268+
const markdownContent = ''
269+
const dummyFilesInFolder = ['file1.txt', 'file2.pdf', 'file3.jpg']
270+
const attachmentFolderPath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, noteKey)
271+
272+
findStorage.findStorage = jest.fn(() => dummyStorage)
273+
fs.existsSync = jest.fn(() => true)
274+
fs.readdir = jest.fn((paht, callback) => callback(undefined, dummyFilesInFolder))
275+
fs.unlink = jest.fn()
276+
277+
systemUnderTest.deleteAttachmentsNotPresentInNote(markdownContent, storageKey, noteKey)
278+
expect(fs.existsSync).toHaveBeenLastCalledWith(attachmentFolderPath)
279+
expect(fs.readdir).toHaveBeenCalledTimes(1)
280+
expect(fs.readdir.mock.calls[0][0]).toBe(attachmentFolderPath)
281+
282+
expect(fs.unlink).toHaveBeenCalledTimes(dummyFilesInFolder.length)
283+
const fsUnlinkCallArguments = []
284+
for (let i = 0; i < dummyFilesInFolder.length; i++) {
285+
fsUnlinkCallArguments.push(fs.unlink.mock.calls[i][0])
286+
}
287+
288+
dummyFilesInFolder.forEach(function (file) {
289+
expect(fsUnlinkCallArguments.includes(path.join(attachmentFolderPath, file))).toBe(true)
290+
})
291+
})
292+
293+
it('should test that deleteAttachmentsNotPresentInNote does not delete referenced attachments', function () {
294+
const dummyStorage = {path: 'dummyStoragePath'}
295+
const noteKey = 'noteKey'
296+
const storageKey = 'storageKey'
297+
const dummyFilesInFolder = ['file1.txt', 'file2.pdf', 'file3.jpg']
298+
const markdownContent = systemUnderTest.generateAttachmentMarkdown('fileLabel', path.join(systemUnderTest.STORAGE_FOLDER_PLACEHOLDER, noteKey, dummyFilesInFolder[0]), false)
299+
const attachmentFolderPath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, noteKey)
300+
301+
findStorage.findStorage = jest.fn(() => dummyStorage)
302+
fs.existsSync = jest.fn(() => true)
303+
fs.readdir = jest.fn((paht, callback) => callback(undefined, dummyFilesInFolder))
304+
fs.unlink = jest.fn()
305+
306+
systemUnderTest.deleteAttachmentsNotPresentInNote(markdownContent, storageKey, noteKey)
307+
308+
expect(fs.unlink).toHaveBeenCalledTimes(dummyFilesInFolder.length - 1)
309+
const fsUnlinkCallArguments = []
310+
for (let i = 0; i < dummyFilesInFolder.length - 1; i++) {
311+
fsUnlinkCallArguments.push(fs.unlink.mock.calls[i][0])
312+
}
313+
expect(fsUnlinkCallArguments.includes(path.join(attachmentFolderPath, dummyFilesInFolder[0]))).toBe(false)
314+
})

0 commit comments

Comments
 (0)