Skip to content

Commit c7d33fb

Browse files
ZeroX-DGRokt33r
authored andcommitted
Allow user to view attachments and clear unused attachments
1 parent cf324d9 commit c7d33fb

File tree

3 files changed

+168
-4
lines changed

3 files changed

+168
-4
lines changed

browser/main/lib/dataApi/attachmentManagement.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,52 @@ function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey
624624
}
625625
}
626626

627+
function getAttachments (markdownContent, storageKey, noteKey) {
628+
if (storageKey == null || noteKey == null || markdownContent == null) {
629+
return
630+
}
631+
const targetStorage = findStorage.findStorage(storageKey)
632+
const attachmentFolder = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
633+
const attachmentsInNote = getAttachmentsInMarkdownContent(markdownContent)
634+
const attachmentsInNoteOnlyFileNames = []
635+
if (attachmentsInNote) {
636+
for (let i = 0; i < attachmentsInNote.length; i++) {
637+
attachmentsInNoteOnlyFileNames.push(attachmentsInNote[i].replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + noteKey + escapeStringRegexp(path.sep), 'g'), ''))
638+
}
639+
}
640+
if (fs.existsSync(attachmentFolder)) {
641+
return new Promise((resolve, reject) => {
642+
fs.readdir(attachmentFolder, (err, files) => {
643+
if (err) {
644+
console.error('Error reading directory "' + attachmentFolder + '". Error:')
645+
console.error(err)
646+
reject(err)
647+
return
648+
}
649+
const attachmentsNotInNotePaths = []
650+
const attachmentsInNotePaths = []
651+
const allAttachments = []
652+
for (const file of files) {
653+
const absolutePathOfFile = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey, file)
654+
if (!attachmentsInNoteOnlyFileNames.includes(file)) {
655+
attachmentsNotInNotePaths.push(absolutePathOfFile)
656+
} else {
657+
attachmentsInNotePaths.push(absolutePathOfFile)
658+
}
659+
allAttachments.push(absolutePathOfFile)
660+
}
661+
resolve({
662+
allAttachments,
663+
attachmentsNotInNotePaths,
664+
attachmentsInNotePaths
665+
})
666+
})
667+
})
668+
} else {
669+
return null
670+
}
671+
}
672+
627673
/**
628674
* Clones the attachments of a given note.
629675
* Copies the attachments to their new destination and updates the content of the new note so that the attachment-links again point to the correct destination.
@@ -728,6 +774,7 @@ module.exports = {
728774
removeStorageAndNoteReferences,
729775
deleteAttachmentFolder,
730776
deleteAttachmentsNotPresentInNote,
777+
getAttachments,
731778
moveAttachments,
732779
cloneAttachments,
733780
isAttachmentLink,

browser/main/modals/PreferencesModal/StoragesTab.js

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import React from 'react'
33
import CSSModules from 'browser/lib/CSSModules'
44
import styles from './StoragesTab.styl'
55
import dataApi from 'browser/main/lib/dataApi'
6+
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
67
import StorageItem from './StorageItem'
78
import i18n from 'browser/lib/i18n'
9+
import fs from 'fs'
810

911
const electron = require('electron')
1012
const { shell, remote } = electron
@@ -25,6 +27,20 @@ function browseFolder () {
2527
})
2628
}
2729

30+
function humanFileSize (bytes) {
31+
const threshold = 1000
32+
if (Math.abs(bytes) < threshold) {
33+
return bytes + ' B'
34+
}
35+
var units = ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
36+
var u = -1
37+
do {
38+
bytes /= threshold
39+
++u
40+
} while (Math.abs(bytes) >= threshold && u < units.length - 1)
41+
return bytes.toFixed(1) + ' ' + units[u]
42+
}
43+
2844
class StoragesTab extends React.Component {
2945
constructor (props) {
3046
super(props)
@@ -35,8 +51,26 @@ class StoragesTab extends React.Component {
3551
name: 'Unnamed',
3652
type: 'FILESYSTEM',
3753
path: ''
38-
}
54+
},
55+
attachments: []
3956
}
57+
this.loadAttachmentStorage()
58+
}
59+
60+
loadAttachmentStorage () {
61+
const promises = []
62+
this.props.data.noteMap.map(note => {
63+
const promise = attachmentManagement.getAttachments(
64+
note.content,
65+
note.storage,
66+
note.key
67+
)
68+
if (promise) promises.push(promise)
69+
})
70+
71+
Promise.all(promises)
72+
.then(data => this.setState({attachments: data}))
73+
.catch(console.error)
4074
}
4175

4276
handleAddStorageButton (e) {
@@ -57,8 +91,61 @@ class StoragesTab extends React.Component {
5791
e.preventDefault()
5892
}
5993

94+
removeAllAttachments (attachments) {
95+
const promises = []
96+
for (const attachment of attachments) {
97+
for (const file of attachment) {
98+
const promise = new Promise((resolve, reject) => {
99+
fs.unlink(file, (err) => {
100+
if (err) {
101+
console.error('Could not delete "%s"', file)
102+
console.error(err)
103+
reject(err)
104+
return
105+
}
106+
resolve()
107+
})
108+
})
109+
promises.push(promise)
110+
}
111+
}
112+
Promise.all(promises)
113+
.then(() => this.loadAttachmentStorage())
114+
.catch(console.error)
115+
}
116+
60117
renderList () {
61118
const { data, boundingBox } = this.props
119+
const { attachments } = this.state
120+
121+
const totalUnusedAttachments = attachments
122+
.reduce((acc, curr) => acc + curr.attachmentsNotInNotePaths.length, 0)
123+
const totalInuseAttachments = attachments
124+
.reduce((acc, curr) => acc + curr.attachmentsInNotePaths.length, 0)
125+
const totalAttachments = totalUnusedAttachments + totalInuseAttachments
126+
127+
const unusedAttachments = attachments.reduce((acc, curr) => {
128+
acc.push(curr.attachmentsNotInNotePaths)
129+
return acc
130+
}, [])
131+
132+
const totalUnusedAttachmentsSize = attachments
133+
.reduce((acc, curr) => {
134+
return acc + curr.attachmentsNotInNotePaths.reduce((racc, rcurr) => {
135+
const stats = fs.statSync(rcurr)
136+
const fileSizeInBytes = stats.size
137+
return racc + fileSizeInBytes
138+
}, 0)
139+
}, 0)
140+
const totalInuseAttachmentsSize = attachments
141+
.reduce((acc, curr) => {
142+
return acc + curr.attachmentsInNotePaths.reduce((racc, rcurr) => {
143+
const stats = fs.statSync(rcurr)
144+
const fileSizeInBytes = stats.size
145+
return racc + fileSizeInBytes
146+
}, 0)
147+
}, 0)
148+
const totalAttachmentsSize = totalUnusedAttachmentsSize + totalInuseAttachmentsSize
62149

63150
if (!boundingBox) { return null }
64151
const storageList = data.storageMap.map((storage) => {
@@ -82,6 +169,19 @@ class StoragesTab extends React.Component {
82169
<i className='fa fa-plus' /> {i18n.__('Add Storage Location')}
83170
</button>
84171
</div>
172+
<div styleName='header'>{i18n.__('Attachment storage')}</div>
173+
<p styleName='list-attachment-label'>
174+
Unused attachments size: {humanFileSize(totalUnusedAttachmentsSize)} ({totalUnusedAttachments} items)
175+
</p>
176+
<p styleName='list-attachment-label'>
177+
In use attachments size: {humanFileSize(totalInuseAttachmentsSize)} ({totalInuseAttachments} items)
178+
</p>
179+
<p styleName='list-attachment-label'>
180+
Total attachments size: {humanFileSize(totalAttachmentsSize)} ({totalAttachments} items)
181+
</p>
182+
<button styleName='list-attachement-clear-button' onClick={() => this.removeAllAttachments(unusedAttachments)}>
183+
{i18n.__('Clear unused attachments')}
184+
</button>
85185
</div>
86186
)
87187
}

browser/main/modals/PreferencesModal/StoragesTab.styl

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,17 @@
3333
colorDefaultButton()
3434
font-size $tab--button-font-size
3535
border-radius 2px
36+
.list-attachment-label
37+
margin-bottom 10px
38+
color $ui-text-color
39+
.list-attachement-clear-button
40+
height 30px
41+
border none
42+
border-top-right-radius 2px
43+
border-bottom-right-radius 2px
44+
colorPrimaryButton()
45+
vertical-align middle
46+
padding 0 20px
3647

3748
.addStorage
3849
margin-bottom 15px
@@ -154,8 +165,8 @@ body[data-theme="dark"]
154165
.addStorage-body-control-cancelButton
155166
colorDarkDefaultButton()
156167
border-color $ui-dark-borderColor
157-
158-
168+
.list-attachement-clear-button
169+
colorDarkPrimaryButton()
159170

160171
body[data-theme="solarized-dark"]
161172
.root
@@ -194,6 +205,8 @@ body[data-theme="solarized-dark"]
194205
.addStorage-body-control-cancelButton
195206
colorDarkDefaultButton()
196207
border-color $ui-solarized-dark-borderColor
208+
.list-attachement-clear-button
209+
colorSolarizedDarkPrimaryButton()
197210

198211
body[data-theme="monokai"]
199212
.root
@@ -232,6 +245,8 @@ body[data-theme="monokai"]
232245
.addStorage-body-control-cancelButton
233246
colorDarkDefaultButton()
234247
border-color $ui-monokai-borderColor
248+
.list-attachement-clear-button
249+
colorMonokaiPrimaryButton()
235250

236251
body[data-theme="dracula"]
237252
.root
@@ -269,4 +284,6 @@ body[data-theme="dracula"]
269284
colorDarkPrimaryButton()
270285
.addStorage-body-control-cancelButton
271286
colorDarkDefaultButton()
272-
border-color $ui-dracula-borderColor
287+
border-color $ui-dracula-borderColor
288+
.list-attachement-clear-button
289+
colorDraculaPrimaryButton()

0 commit comments

Comments
 (0)