diff --git a/app/containers/HomePage/components/FileExplorer.jsx b/app/containers/HomePage/components/FileExplorer.jsx index 26bc7df8..a1f370d7 100644 --- a/app/containers/HomePage/components/FileExplorer.jsx +++ b/app/containers/HomePage/components/FileExplorer.jsx @@ -95,7 +95,7 @@ import { redditShareUrl, twitterShareUrl, } from '../../../templates/socialMediaShareBtns'; -import { baseName, pathInfo, pathUp, sanitizePath } from '../../../utils/files'; +import { baseName, pathInfo, pathUp, sanitizePath, calculateFolderSize } from '../../../utils/files'; import { DEVICE_TYPE, FILE_EXPLORER_VIEW_TYPE, @@ -202,6 +202,7 @@ class FileExplorer extends Component { }, }, directoryGeneratedTime: Date.now(), + folderSizes: {}, // Add state for folder sizes }; this.state = { @@ -258,6 +259,7 @@ class FileExplorer extends Component { if (nextDirectoryNodes !== prevDirectoryNodes) { this._handleDirectoryGeneratedTime(); + this.calculateFolderSizes(nextDirectoryNodes); // Calculate folder sizes when directory changes } if (nextShowDirectoriesFirst !== showDirectoriesFirst) { @@ -1985,6 +1987,19 @@ class FileExplorer extends Component { }); }; + calculateFolderSizes = async (nodes) => { + const folderSizes = {}; + + for (const node of nodes) { + if (node.isFolder) { + const size = await calculateFolderSize(node.path); + folderSizes[node.path] = size; + } + } + + this.setState({ folderSizes }); + }; + render() { const { classes: styles, @@ -1999,7 +2014,7 @@ class FileExplorer extends Component { isStatusBarEnabled, fileTransferClipboard, } = this.props; - const { toggleDialog, togglePasteConfirmDialog, directoryGeneratedTime } = + const { toggleDialog, togglePasteConfirmDialog, directoryGeneratedTime, folderSizes } = this.state; const { rename, newFolder } = toggleDialog; const togglePasteDialog = @@ -2181,6 +2196,7 @@ class FileExplorer extends Component { this._handleFocussedFileExplorerDeviceType } onAcceleratorActivation={this._handleAcceleratorActivation} + folderSizes={folderSizes} // Pass folder sizes to FileExplorerBodyRender /> ; diff --git a/app/containers/HomePage/components/FileExplorerTableBodyListRender.jsx b/app/containers/HomePage/components/FileExplorerTableBodyListRender.jsx index 7a56b7a2..fe73445c 100644 --- a/app/containers/HomePage/components/FileExplorerTableBodyListRender.jsx +++ b/app/containers/HomePage/components/FileExplorerTableBodyListRender.jsx @@ -8,10 +8,10 @@ import classNames from 'classnames'; import { niceBytes, springTruncate } from '../../../utils/funcs'; import { FILE_EXPLORER_TABLE_TRUNCATE_MAX_CHARS } from '../../../constants'; import { styles } from '../styles/FileExplorerTableBodyListRender'; -// eslint-disable-next-line import/no-relative-packages import prettyFileIcons from '../../../vendors/pretty-file-icons'; import { imgsrc } from '../../../utils/imgsrc'; import { appDateFormat } from '../../../utils/date'; +import { calculateFolderSize } from '../../../utils/files'; class FileExplorerTableBodyListRender extends PureComponent { RenderFileIcon = () => { @@ -56,6 +56,7 @@ class FileExplorerTableBodyListRender extends PureComponent { onContextMenuClick, onTableClick, onTableDoubleClick, + folderSizes, } = this.props; const { RenderFileIcon, RenderFolderIcon } = this; @@ -65,6 +66,8 @@ class FileExplorerTableBodyListRender extends PureComponent { FILE_EXPLORER_TABLE_TRUNCATE_MAX_CHARS ); + const folderSize = item.isFolder ? folderSizes[item.path] : null; + return ( - {item.isFolder ? `--` : `${niceBytes(item.size)}`} + {item.isFolder ? (folderSize ? niceBytes(folderSize) : '--') : `${niceBytes(item.size)}`} )} {hideColList.indexOf('dateAdded') < 0 && ( diff --git a/app/containers/HomePage/components/FileExplorerTableFooterStatusBarRender.jsx b/app/containers/HomePage/components/FileExplorerTableFooterStatusBarRender.jsx index 9a226094..2daaa322 100644 --- a/app/containers/HomePage/components/FileExplorerTableFooterStatusBarRender.jsx +++ b/app/containers/HomePage/components/FileExplorerTableFooterStatusBarRender.jsx @@ -4,9 +4,10 @@ import { faMobile, faLaptop } from '@fortawesome/free-solid-svg-icons'; import { withStyles } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; import { styles } from '../styles/FileExplorerTableFooterStatusBarRender'; -import { getPluralText } from '../../../utils/funcs'; +import { getPluralText, niceBytes } from '../../../utils/funcs'; import { DEVICE_TYPE } from '../../../enums'; import { DEVICES_LABEL } from '../../../constants'; +import { calculateFolderSize } from '../../../utils/files'; class FileExplorerTableFooterStatusBarRender extends PureComponent { getDirectoryListStats = () => { @@ -14,12 +15,14 @@ class FileExplorerTableFooterStatusBarRender extends PureComponent { let directories = 0; let files = 0; + let totalSize = 0; (directoryLists.nodes || []).map((a) => { if (a.isFolder) { directories += 1; } else { files += 1; + totalSize += a.size; } return a; @@ -27,15 +30,28 @@ class FileExplorerTableFooterStatusBarRender extends PureComponent { const total = directories + files; - return { total, directories, files }; + return { total, directories, files, totalSize }; }; - getSelectedDirectoryStats = () => { + getSelectedDirectoryStats = async () => { const { directoryLists } = this.props; - const total = directoryLists.queue.selected.length; + const selected = directoryLists.queue.selected; + const total = selected.length; + let totalSize = 0; + + for (const path of selected) { + const node = directoryLists.nodes.find((n) => n.path === path); + if (node) { + if (node.isFolder) { + totalSize += await calculateFolderSize(node.path); + } else { + totalSize += node.size; + } + } + } - return { total }; + return { total, totalSize }; }; RenderDeviceName = () => { @@ -69,8 +85,8 @@ class FileExplorerTableFooterStatusBarRender extends PureComponent { render() { const { classes: styles, fileTransferClipboard } = this.props; - const { directories, files, total } = this.getDirectoryListStats(); - const { total: selectedTotal } = this.getSelectedDirectoryStats(); + const { directories, files, total, totalSize } = this.getDirectoryListStats(); + const { total: selectedTotal, totalSize: selectedTotalSize } = this.getSelectedDirectoryStats(); const fileTransferClipboardLength = fileTransferClipboard.queue.length; const { RenderDeviceName } = this; @@ -80,7 +96,7 @@ class FileExplorerTableFooterStatusBarRender extends PureComponent { {selectedTotal > 0 ? ( - {`${selectedTotal} of ${total} selected`} + {`${selectedTotal} of ${total} selected, ${niceBytes(selectedTotalSize)}`} ) : ( {`${total} ${getPluralText( 'item', @@ -89,7 +105,7 @@ class FileExplorerTableFooterStatusBarRender extends PureComponent { 'directory', directories, 'directories' - )}, ${files} ${getPluralText('file', files)})`} + )}, ${files} ${getPluralText('file', files)}, ${niceBytes(totalSize)})`} )} {`, ${fileTransferClipboardLength} ${getPluralText( 'item', diff --git a/app/utils/files.js b/app/utils/files.js index d501dc77..e7ee8c0a 100644 --- a/app/utils/files.js +++ b/app/utils/files.js @@ -1,6 +1,7 @@ import { join, parse } from 'path'; import { homedir as homedirOs } from 'os'; import { APP_BUNDLE_ID } from '../constants/meta'; +import fs from 'fs'; const homeDir = homedirOs(); @@ -52,3 +53,44 @@ export const getExtension = (fileName, isFolder) => { export const pathInfo = (filePath) => { return parse(filePath); }; + +export const calculateFolderSize = (folderPath) => { + return new Promise((resolve, reject) => { + let totalSize = 0; + + fs.readdir(folderPath, (err, files) => { + if (err) { + return reject(err); + } + + let pending = files.length; + if (!pending) { + return resolve(totalSize); + } + + files.forEach((file) => { + const filePath = join(folderPath, file); + + fs.stat(filePath, (err, stats) => { + if (err) { + return reject(err); + } + + if (stats.isDirectory()) { + calculateFolderSize(filePath).then((size) => { + totalSize += size; + if (!--pending) { + resolve(totalSize); + } + }).catch(reject); + } else { + totalSize += stats.size; + if (!--pending) { + resolve(totalSize); + } + } + }); + }); + }); + }); +};