Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow user to view attachments and clear unused attachments #1

Merged
merged 4 commits into from
Aug 28, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions browser/main/lib/dataApi/attachmentManagement.js
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,53 @@ function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey
}
}

/**
* @description Get all existing attachments related to a specific note
including their status (in use or not) and their path. Return null if there're no attachment related to note or specified parametters are invalid
* @param storageKey StorageKey of the current note
* @param noteKey NoteKey of the currentNote
* @param linkText Text that was pasted
* @return {Promise<Array<{path: String, isInUse: bool}>>} Promise returning the
list of attachments with their properties */
function getAttachmentsPathAndStatus (markdownContent, storageKey, noteKey) {
if (storageKey == null || noteKey == null || markdownContent == null) {
return
}
const targetStorage = findStorage.findStorage(storageKey)
const attachmentFolder = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
const attachmentsInNote = getAttachmentsInMarkdownContent(markdownContent)
const attachmentsInNoteOnlyFileNames = []
if (attachmentsInNote) {
for (let i = 0; i < attachmentsInNote.length; i++) {
attachmentsInNoteOnlyFileNames.push(attachmentsInNote[i].replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + noteKey + escapeStringRegexp(path.sep), 'g'), ''))
}
}
if (fs.existsSync(attachmentFolder)) {
return new Promise((resolve, reject) => {
fs.readdir(attachmentFolder, (err, files) => {
if (err) {
console.error('Error reading directory "' + attachmentFolder + '". Error:')
console.error(err)
reject(err)
return
}
const attachments = []
for (const file of files) {
const absolutePathOfFile = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey, file)
if (!attachmentsInNoteOnlyFileNames.includes(file)) {
attachments.push({ path: absolutePathOfFile, isInUse: false })
} else {
attachments.push({ path: absolutePathOfFile, isInUse: true })
}
}
resolve(attachments)
})
})
} else {
return null
}
}

/**
* Clones the attachments of a given note.
* 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.
Expand Down Expand Up @@ -728,6 +775,7 @@ module.exports = {
removeStorageAndNoteReferences,
deleteAttachmentFolder,
deleteAttachmentsNotPresentInNote,
getAttachmentsPathAndStatus,
moveAttachments,
cloneAttachments,
isAttachmentLink,
Expand Down
97 changes: 96 additions & 1 deletion browser/main/modals/PreferencesModal/StoragesTab.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './StoragesTab.styl'
import dataApi from 'browser/main/lib/dataApi'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
import StorageItem from './StorageItem'
import i18n from 'browser/lib/i18n'
import fs from 'fs'

const electron = require('electron')
const { shell, remote } = electron
Expand All @@ -25,6 +27,20 @@ function browseFolder () {
})
}

function humanFileSize (bytes) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self: Move this into util file

const threshold = 1000
if (Math.abs(bytes) < threshold) {
return bytes + ' B'
}
var units = ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
var u = -1
do {
bytes /= threshold
++u
} while (Math.abs(bytes) >= threshold && u < units.length - 1)
return bytes.toFixed(1) + ' ' + units[u]
}

class StoragesTab extends React.Component {
constructor (props) {
super(props)
Expand All @@ -35,8 +51,29 @@ class StoragesTab extends React.Component {
name: 'Unnamed',
type: 'FILESYSTEM',
path: ''
}
},
attachments: []
}
this.loadAttachmentStorage()
}

loadAttachmentStorage () {
const promises = []
this.props.data.noteMap.map(note => {
const promise = attachmentManagement.getAttachmentsPathAndStatus(
note.content,
note.storage,
note.key
)
if (promise) promises.push(promise)
})

Promise.all(promises)
.then(data => {
const result = data.reduce((acc, curr) => acc.concat(curr), [])
this.setState({attachments: result})
})
.catch(console.error)
}

handleAddStorageButton (e) {
Expand All @@ -57,8 +94,53 @@ class StoragesTab extends React.Component {
e.preventDefault()
}

removeAllAttachments (attachments) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this method should also be in the attachmentManagement-file..?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And a test for it is required

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm gonna put it in attachmentManagement, maybe I should change the getAttachments function return types to compatible with this. Well I plan to do that anyway, let's do it.

const promises = []
for (const attachment of attachments) {
for (const file of attachment) {
const promise = new Promise((resolve, reject) => {
fs.unlink(file, (err) => {
if (err) {
console.error('Could not delete "%s"', file)
console.error(err)
reject(err)
return
}
resolve()
})
})
promises.push(promise)
}
}
Promise.all(promises)
.then(() => this.loadAttachmentStorage())
.catch(console.error)
}

renderList () {
const { data, boundingBox } = this.props
const { attachments } = this.state

const unusedAttachments = attachments.filter(attachment => !attachment.isInUse)
const inUseAttachments = attachments.filter(attachment => attachment.isInUse)

const totalUnusedAttachments = unusedAttachments.length
const totalInuseAttachments = inUseAttachments.length
const totalAttachments = totalUnusedAttachments + totalInuseAttachments

const totalUnusedAttachmentsSize = unusedAttachments
.reduce((acc, curr) => {
const stats = fs.statSync(curr.path)
const fileSizeInBytes = stats.size
return acc + fileSizeInBytes
}, 0)
const totalInuseAttachmentsSize = inUseAttachments
.reduce((acc, curr) => {
const stats = fs.statSync(curr.path)
const fileSizeInBytes = stats.size
return acc + fileSizeInBytes
}, 0)
const totalAttachmentsSize = totalUnusedAttachmentsSize + totalInuseAttachmentsSize

if (!boundingBox) { return null }
const storageList = data.storageMap.map((storage) => {
Expand All @@ -82,6 +164,19 @@ class StoragesTab extends React.Component {
<i className='fa fa-plus' /> {i18n.__('Add Storage Location')}
</button>
</div>
<div styleName='header'>{i18n.__('Attachment storage')}</div>
<p styleName='list-attachment-label'>
Unused attachments size: {humanFileSize(totalUnusedAttachmentsSize)} ({totalUnusedAttachments} items)
</p>
<p styleName='list-attachment-label'>
In use attachments size: {humanFileSize(totalInuseAttachmentsSize)} ({totalInuseAttachments} items)
</p>
<p styleName='list-attachment-label'>
Total attachments size: {humanFileSize(totalAttachmentsSize)} ({totalAttachments} items)
</p>
<button styleName='list-attachement-clear-button' onClick={() => this.removeAllAttachments(unusedAttachments)}>
{i18n.__('Clear unused attachments')}
</button>
</div>
)
}
Expand Down
23 changes: 20 additions & 3 deletions browser/main/modals/PreferencesModal/StoragesTab.styl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@
colorDefaultButton()
font-size $tab--button-font-size
border-radius 2px
.list-attachment-label
margin-bottom 10px
color $ui-text-color
.list-attachement-clear-button
height 30px
border none
border-top-right-radius 2px
border-bottom-right-radius 2px
colorPrimaryButton()
vertical-align middle
padding 0 20px

.addStorage
margin-bottom 15px
Expand Down Expand Up @@ -154,8 +165,8 @@ body[data-theme="dark"]
.addStorage-body-control-cancelButton
colorDarkDefaultButton()
border-color $ui-dark-borderColor


.list-attachement-clear-button
colorDarkPrimaryButton()

body[data-theme="solarized-dark"]
.root
Expand Down Expand Up @@ -194,6 +205,8 @@ body[data-theme="solarized-dark"]
.addStorage-body-control-cancelButton
colorDarkDefaultButton()
border-color $ui-solarized-dark-borderColor
.list-attachement-clear-button
colorSolarizedDarkPrimaryButton()

body[data-theme="monokai"]
.root
Expand Down Expand Up @@ -232,6 +245,8 @@ body[data-theme="monokai"]
.addStorage-body-control-cancelButton
colorDarkDefaultButton()
border-color $ui-monokai-borderColor
.list-attachement-clear-button
colorMonokaiPrimaryButton()

body[data-theme="dracula"]
.root
Expand Down Expand Up @@ -269,4 +284,6 @@ body[data-theme="dracula"]
colorDarkPrimaryButton()
.addStorage-body-control-cancelButton
colorDarkDefaultButton()
border-color $ui-dracula-borderColor
border-color $ui-dracula-borderColor
.list-attachement-clear-button
colorDraculaPrimaryButton()