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

enhancement/Show_size_of_folder_and_files #108

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
30 changes: 30 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
Toast.fire({ icon: 'success', title: 'File(s) uploaded successfully' });
onUploadSuccess();
} catch (err) {
console.error('Upload failed', err);

Check warning on line 88 in frontend/src/App.tsx

View workflow job for this annotation

GitHub Actions / test

Unexpected console statement
const message = (err instanceof AxiosError && err.response?.data.error.message) || (err as Error).message;
Toast.fire({ icon: 'error', title: `Upload failed: ${message}` });
} finally {
Expand Down Expand Up @@ -132,12 +132,38 @@
const FileDownload = ({ url }: { url: string }) => <a style={{ textDecoration: 'none', marginLeft: 10, marginBottom: -5, color: colorLink }} href={url} title="Download file"><FaFileDownload size={22} /></a>;
const ZipDownload = ({ url, title = 'Download folder as ZIP', style }: { url: string, title?: string, style?: CSSProperties }) => <a style={{ textDecoration: 'none', color: colorLink2, verticalAlign: 'middle', ...style }} href={url} title={title}><FaFileArchive size={22} /></a>;

let sizeCounter: number = 0;

Check failure on line 135 in frontend/src/App.tsx

View workflow job for this annotation

GitHub Actions / test

Type number trivially inferred from a number literal, remove type annotation

const sizeMeter: { [key: number]: string } = {

Check failure on line 137 in frontend/src/App.tsx

View workflow job for this annotation

GitHub Actions / test

A record is preferred over an index signature
0: 'KB',
1: 'MB',
2: 'GB',
3: 'TB'

Check failure on line 141 in frontend/src/App.tsx

View workflow job for this annotation

GitHub Actions / test

Missing trailing comma
};

// Format the byte
const manageByte = (num: number): string | number => {
if (!num) return 0;
let res: number = num / 1024;

Check failure on line 147 in frontend/src/App.tsx

View workflow job for this annotation

GitHub Actions / test

'res' is never reassigned. Use 'const' instead

if (res > 1000) {
sizeCounter++;

Check failure on line 150 in frontend/src/App.tsx

View workflow job for this annotation

GitHub Actions / test

Unary operator '++' used
return manageByte(res);
} else {

Check failure on line 152 in frontend/src/App.tsx

View workflow job for this annotation

GitHub Actions / test

Unnecessary 'else' after 'return'
const value: number = sizeCounter;
sizeCounter = 0;
return res.toFixed(2) + sizeMeter[value];
}
}

Check failure on line 157 in frontend/src/App.tsx

View workflow job for this annotation

GitHub Actions / test

Missing semicolon


const FileRow = ({ path, isDir, fileName, onCheckedChange, checked }: {
path: string,
isDir: boolean,
fileName: string,
onCheckedChange?: ChangeEventHandler<HTMLInputElement>,
checked?: boolean | undefined,
size?: number | undefined,

Check failure on line 166 in frontend/src/App.tsx

View workflow job for this annotation

GitHub Actions / test

'size' PropType is defined but prop is never used
}) => {
const Icon = isDir ? FaFolder : FaFileAlt;

Expand All @@ -148,12 +174,14 @@
<>
<Link to={{ pathname: '/', search: `?p=${encodeURIComponent(path)}` }} style={linkStyle}>{fileName} {fileName === '..' && <span style={{ color: 'rgba(0,0,0,0.3)' }}>(parent dir)</span>}</Link>
<div style={{ flexGrow: 1 }} />
<span style={{ marginRight: '.2em' }} > {size && manageByte(size)} </span>

Check failure on line 177 in frontend/src/App.tsx

View workflow job for this annotation

GitHub Actions / test

A space is forbidden before closing bracket
<ZipDownload url={getDownloadUrl(path)} style={{ marginLeft: 10, marginBottom: -5 }} />
</>
) : (
<>
<a style={linkStyle} target="_blank" rel="noopener noreferrer" href={getDownloadUrl(path)}>{fileName}</a>
<div style={{ flexGrow: 1 }} />
<span style={{ marginRight: '.2em' }} > {size && manageByte(size)} </span>

Check failure on line 184 in frontend/src/App.tsx

View workflow job for this annotation

GitHub Actions / test

A space is forbidden before closing bracket
{onCheckedChange != null && <input type="checkbox" className="inputcheckbox" checked={checked} onChange={onCheckedChange} />}
<FileDownload url={getDownloadUrl(path, true)} />
</>
Expand All @@ -179,8 +207,10 @@
try {
const response = await axios.get('/api/browse', { params: { p: currentPath } });
setCurrentDirFiles(response.data);
let responseWithSize = await axios.get('/api/browse-withsize', { params: { p: currentPath } });
setCurrentDirFiles(responseWithSize.data);
} catch (err) {
console.error(err);

Check warning on line 213 in frontend/src/App.tsx

View workflow job for this annotation

GitHub Actions / test

Unexpected console statement
}
}, [currentPath]);

Expand Down Expand Up @@ -216,7 +246,7 @@

Toast.fire({ icon: 'success', title: saveAsFile ? 'Pasted text has been saved to a file on other side' : 'Pasted text has been sent to the clipboard on other side' });
} catch (err) {
console.error(err);

Check warning on line 249 in frontend/src/App.tsx

View workflow job for this annotation

GitHub Actions / test

Unexpected console statement
Toast.fire({ icon: 'error', title: 'Paste clipboard failed' });
}
}
Expand All @@ -227,7 +257,7 @@

setClipboardText(response.data);
} catch (err) {
console.error(err);

Check warning on line 260 in frontend/src/App.tsx

View workflow job for this annotation

GitHub Actions / test

Unexpected console statement
Toast.fire({ icon: 'error', title: 'Copy clipboard failed' });
}
}
Expand Down
70 changes: 70 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@

form.parse(req, async (err, _fields, { files: filesIn }) => {
if (err) {
console.error('Upload failed', err);

Check warning on line 73 in src/app.ts

View workflow job for this annotation

GitHub Actions / test

Unexpected console statement
res.status(400).send({ error: { message: err.message } });
return;
}
Expand All @@ -79,15 +79,15 @@
const files = Array.isArray(filesIn) ? filesIn : [filesIn];

// console.log(JSON.stringify({ fields, files }, null, 2));
console.log('Uploaded files to', uploadDirPath);

Check warning on line 82 in src/app.ts

View workflow job for this annotation

GitHub Actions / test

Unexpected console statement
files.forEach((f) => console.log(f.originalFilename, `(${f.size} bytes)`));

Check warning on line 83 in src/app.ts

View workflow job for this annotation

GitHub Actions / test

Unexpected console statement

await pMap(files, async (file) => {
try {
const targetPath = join(uploadDirPath, filenamify(file.originalFilename ?? 'file', { maxLength: 255 }));
if (!(await pathExists(targetPath))) await fs.rename(file.filepath, targetPath); // to prevent overwrites
} catch (err2) {
console.error(`Failed to rename ${file.originalFilename}`, err2);

Check warning on line 90 in src/app.ts

View workflow job for this annotation

GitHub Actions / test

Unexpected console statement
}
}, { concurrency: 10 });
}
Expand Down Expand Up @@ -204,7 +204,7 @@
const entryRealPath = await fs.realpath(entryAbsPath);

if (!entryRealPath.startsWith(sharedPath)) {
console.warn('Ignoring symlink pointing outside shared path', entryRealPath);

Check warning on line 207 in src/app.ts

View workflow job for this annotation

GitHub Actions / test

Unexpected console statement
return [];
}

Expand All @@ -217,7 +217,7 @@
fileName: entry.name,
}];
} catch (err) {
console.warn((err as Error).message);

Check warning on line 220 in src/app.ts

View workflow job for this annotation

GitHub Actions / test

Unexpected console statement
// https://github.com/mifi/ezshare/issues/29
return [];
}
Expand All @@ -234,6 +234,76 @@
}));


async function getFolderSize(folderPath: string): Promise<number> {
let totalSize = 0;

async function calculateSize(dirPath: string): Promise<void> {
const files = await fs.readdir(dirPath); // Async readdir
for (const file of files) {
const filePath = join(dirPath, file);
const stats = await fs.stat(filePath); // Async stat

if (stats.isDirectory()) {
await calculateSize(filePath); // Recursively get size for subdirectories
} else {
totalSize += stats.size; // Add file size
}
}
}

await calculateSize(folderPath); // Start calculation from the root folder

return totalSize;
}

app.get('/api/browse-withsize', asyncHandler(async (req, res) => {
const browseRelPath = req.query['p'] || '/';
assert(typeof browseRelPath === 'string');
const browseAbsPath = await getFileAbsPath(browseRelPath);

let readdirEntries = await fs.readdir(browseAbsPath, { withFileTypes: true });
readdirEntries = readdirEntries.sort(({ name: a }, { name: b }) => new Intl.Collator(undefined, { numeric: true }).compare(a, b));

const entries = (await pMap(readdirEntries, async (entry) => {
try {
// TODO what if a file called ".."
const entryRelPath = join(browseRelPath, entry.name);
const entryAbsPath = join(browseAbsPath, entry.name);
const entryRealPath = await fs.realpath(entryAbsPath);

if (!entryRealPath.startsWith(sharedPath)) {
console.warn('Ignoring symlink pointing outside shared path', entryRealPath);
return [];
}

const stat = await fs.lstat(entryRealPath);
const isDir = stat.isDirectory();
const size = isDir ? await getFolderSize(entryRealPath) : stat.size

return [{
path: entryRelPath,
isDir,
fileName: entry.name,
size: size
}];
} catch (err) {
console.warn((err as Error).message);
// https://github.com/mifi/ezshare/issues/29
return [];
}
}, { concurrency: 10 })).flat();

res.send({
files: [
{ path: join(browseRelPath, '..'), fileName: '..', isDir: true },
...entries,
],
cwd: browseRelPath,
sharedPath,
});
}));


app.get('/api/zip-files', asyncHandler(async (req, res) => {
const zipFileName = `${new Date().toISOString().replace(/^(\d+-\d+-\d+)T(\d+):(\d+):(\d+).*$/, '$1 $2.$3.$3')}.zip`;
const { files: filesJson } = req.query;
Expand Down
Loading