Skip to content

Commit

Permalink
Merge pull request #16 from atjn/asyncify
Browse files Browse the repository at this point in the history
Upgrade to promise-based API
  • Loading branch information
alessioalex authored Apr 15, 2021
2 parents 9034cb0 + ad19bc2 commit bd3d41f
Show file tree
Hide file tree
Showing 15 changed files with 3,947 additions and 371 deletions.
40 changes: 40 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Test

on: [push, pull_request]

jobs:

test:

strategy:
fail-fast: false
matrix:
node-version: [14.x]
os: [ubuntu-latest, windows-latest, macOS-latest]
include:
- node-version: 15.x
os: ubuntu-latest

runs-on: ${{ matrix.os }}

steps:

- name: Checkout code
uses: actions/checkout@v2

- name: Set up Node ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}

- name: Use npm cache
uses: actions/cache@v2
with:
path: node_modules
key: ${{ runner.os }}-node${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}

- name: Install dependencies
run: npm install

- name: Test code
run: npm test
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules
.nyc_output
119 changes: 106 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,129 @@
# get-folder-size

Get the size of a folder by iterating through its sub-files and folders.
Get the size of a folder by iterating through its sub-files and -folders.

## Usage
| :warning: | Version 3 of this package completely redesigns the API to use ES6 promises and bumps minimum node support to version 14. If you need to use the old callback syntax, or if you need to support an older version of node, keep your dependency pinned to version ^2 ([v2 API reference](https://github.com/alessioalex/get-folder-size/tree/v2.0.1)). |
|-----------|----------------------------------------------------------------------------------------|

## Basic usage

If you don't care about the details and just want a quick implementation, you can use:

```js
getFolderSize(folder, [regexIgnorePattern], callback)
getFolderSize.loose('/path/to/folder');
```

Example:

```js
const getSize = require('get-folder-size');
import getFolderSize from 'get-folder-size';

const myFolder = '/path/to/my/folder';

const size = await getFolderSize.loose(myFolder);
console.log(`The folder is ${size} bytes large`);
console.log(`That is the same as ${(size / 1000 / 1000).toFixed(2)} MB`);
```

## Methods

When reading the size of a folder, read errors can randomly occur for a number of reasons, especially if a different process is altering files in the same folder at the same time. There are three different ways to call this package, depending on how you want to handle those errors:

### `getFolderSize(path, [options]): object`
The default method will return an object with the size of the folder and a list of encountered errors:

```js
{
size: 1435,
errors: [
Error{} ...
]
}
```

If no errors were encountered, `errors` will be `null`. If errors were encountered, `size` will likely be smaller than the real folder size.

This method is great if you want to implement custom logic based on the errors that were encountered.

### `getFolderSize.loose(path, [options]): number`
The `loose` method will return the folder size directly and ignore any errors it encounters, which means the returned folder size could be smaller than the real folder size.

This method is great if the precise size isn't too important, for example when used only to display the folder size to the user.

getSize(myFolder, (err, size) => {
if (err) { throw err; }
### `getFolderSize.strict(path, [options]): number`
The `strict` method will return the folder size directly, but throw an error if it encounters any read errors.

console.log(size + ' bytes');
console.log((size / 1024 / 1024).toFixed(2) + ' MB');
});
This method is great if you need a very accurate number. You will have to implement some sort of error handling to use it reliably.

## Options

Any of the three methods can also take an `options` object:

```js
getFolderSize(
'/path/to/folder',
{
ignore: /pattern/,
fs: customFS,
}
)
```

The `ignore` option takes a regex pattern. Any file or folder with a path that matches the pattern will not be counted in the total folder size.

The `fs` option allows you to pass a different filesystem handler, such as [memfs](https://github.com/streamich/memfs), that will be used to read the folder size. The filesystem handler must incorporate `lstat` and `readdir` promise functions.

## CLI tool

You can run this module from your command line:

```bash
npm i -g get-folder-size
get-folder-size --folder=/my/folder --ignore=node_modules
get-folder-size --folder="/my/folder" --ignore="node_modules"
```
The optional `ignore` statement takes a regex pattern.

## FAQ

### I don't care if I have a file or folder, I just want to get the size.

If a file is passed to `get-folder-size`, it will simply return the size of the file. This means you can use it as a catch-all to get the size of any element in the filesystem.

Example:

```js
import getItemSize from 'get-folder-size';

for(const path of [
'/path/to/small/file.txt',
'/path/to/small/folder/',
'/path/to/large/file.js',
'/path/to/large/folder/',
]){
console.log(await getItemSize.strict(path));
}

## Size vs Size on disk
// Console:
// 273
// 402
// 348614
// 674362319

```
### Does it return actual size or size on disk?

This module calculates the actual folder size, and not the size on disk. [Read about the difference here.](https://web.archive.org/web/20140712235443/https://stackoverflow.com/questions/15470787/please-help-me-understand-size-vs-size-on-disk)

### How do I use it?

This is a Node module. If you are not sure what that means, please check out one of the many great tutorials online, like [nodejs.dev](https://nodejs.dev/learn/introduction-to-nodejs).

When you have Node set up, you can install `get-folder-size` from your command line with this command:

```bash
npm install get-folder-size
```

[This module calculates the actual file size, and not the size on disk.](https://web.archive.org/web/20140712235443/https://stackoverflow.com/questions/15470787/please-help-me-understand-size-vs-size-on-disk)
You can now import it into your JavaScript files, or you can use its command line interface (CLI).

## License

Expand Down
16 changes: 8 additions & 8 deletions bin/get-folder-size → bin/get-folder-size.js
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
#!/usr/bin/env node

const getSize = require('../');
const argv = require('gar')(process.argv.slice(2));
const path = require('path');
import getFolderSize from '../index.js';
import gar from 'gar';
import path from 'path';

const argv = gar(process.argv.slice(2));

// --folder or -f or last argument passed
const folder = argv.folder || argv.f || argv._[argv._.length - 1];

Expand All @@ -15,8 +18,5 @@ if (!folder) {

const ignore = argv.ignore ? new RegExp(argv.ignore) : null;

getSize(path.resolve(folder), ignore, (err, bytes) => {
if (err) { throw err; }

console.log((bytes / 1024 / 1024).toFixed(2) + ' Mb');
});
const size = await getFolderSize.strict(path.resolve(folder), {ignore});
console.log((size / 1000 / 1000).toFixed(2) + ' MB');
11 changes: 5 additions & 6 deletions example.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
'use strict';

const path = require('path');
const getSize = require('./');
import path from 'path';
import getFolderSize from './index.js';

if (!process.env.FOLDER) {
throw new Error('FOLDER env var needed');
}

getSize(path.resolve(process.env.FOLDER), (err, size) => {
if (err) { throw err; }
getFolderSize.strict(path.resolve(process.env.FOLDER)).then(size => {

console.log(size + ' bytes');
console.log((size / 1024 / 1024).toFixed(2) + ' Mb');
console.log((size / 1000 / 1000).toFixed(2) + ' MB');

});
125 changes: 72 additions & 53 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,66 +1,85 @@
'use strict';

const fs = require('fs');
const path = require('path');
const eachAsync = require('tiny-each-async');
import { join as joinPaths } from 'path';

function readSizeRecursive(seen, item, ignoreRegEx, callback) {
let cb;
let ignoreRegExp;
/**
* Returns an object containing the size of the folder and a list of errors encountered while traversing the folder.
*
* If any errors are returned, the returned folder size is likely smaller than the real folder size.
*
* @param {string} itemPath - Path of the folder.
* @param {object} [options] - Options.
* @param {object} [options.ignore] - If a file's path matches this regex object, its size is not counted.
* @param {object} [options.fs] - The filesystem that should be used. Uses node fs by default.
*
* @returns {Promise<{size: number, errors: Array<Error> | null}>} - An object containing the size of the folder in bytes and a list of encountered errors.
*/
export default async function getFolderSize (itemPath, options) { return await core(itemPath, options, {errors: true}) }

if (!callback) {
cb = ignoreRegEx;
ignoreRegExp = null;
} else {
cb = callback;
ignoreRegExp = ignoreRegEx;
}
/**
* Returns the size of the folder. If any errors are encountered while traversing the folder, they are silently ignored.
*
* The returned folder size might be smaller than the real folder size. It is impossible to know for sure, since errors are ignored.
*
* @param {string} itemPath - Path of the folder.
* @param {object} [options] - Options.
* @param {object} [options.ignore] - If a file's path matches this regex object, its size is not counted.
* @param {object} [options.fs] - The filesystem that should be used. Uses node fs by default.
*
* @returns {Promise<number>} - The size of the folder in bytes.
*/
getFolderSize.loose = async (itemPath, options) => await core(itemPath, options);

fs.lstat(item, function lstat(e, stats) {
let total = !e ? (stats.size || 0) : 0;
/**
* Returns the size of the folder. If any errors are encountered while traversing the folder, this method will throw an error.
*
* Because errors will otherwise make this method fail, the returned folder size will always be accurate.
*
* @param {string} itemPath - Path of the folder.
* @param {object} [options] - Options.
* @param {object} [options.ignore] - If a file's path matches this regex object, its size is not counted.
* @param {object} [options.fs] - The filesystem that should be used. Uses node fs by default.
*
* @returns {Promise<number>} - The size of the folder in bytes.
*/
getFolderSize.strict = async (itemPath, options) => await core(itemPath, options, {strict: true});

if (stats) {
if (seen.has(stats.ino)) { return cb(null, 0); }

seen.add(stats.ino);
}

if (!e && stats.isDirectory()) {
fs.readdir(item, (err, list) => {
if (err) { return cb(err); }
async function core (rootItemPath, options = {}, returnType = {}) {
const fs = options.fs || await import('fs/promises');

const fileSizes = new Map();
const errors = [];

await processItem(rootItemPath);

eachAsync(
list,
5000,
(dirItem, next) => {
readSizeRecursive(
seen,
path.join(item, dirItem),
ignoreRegExp,
(error, size) => {
if (!error) { total += size; }
async function processItem(itemPath) {
if(options.ignore?.test(itemPath)) return;

next(error);
}
);
},
(finalErr) => {
cb(finalErr, total);
}
);
});
} else {
if (ignoreRegExp && ignoreRegExp.test(item)) {
total = 0;
}
const stats = returnType.strict ? await fs.lstat(itemPath) : await fs.lstat(itemPath).catch(error => errors.push(error));
if(typeof stats !== 'object') return;
fileSizes.set(stats.ino, stats.size);

cb(e, total);
if(stats.isDirectory()) {
const directoryItems = returnType.strict ? await fs.readdir(itemPath) : await fs.readdir(itemPath).catch(error => errors.push(error));
if(typeof directoryItems !== 'object') return;
await Promise.all(
directoryItems.map(directoryItem =>
processItem(joinPaths(itemPath, directoryItem))
)
);
}
});
}
}

module.exports = (...args) => {
args.unshift(new Set());
const folderSize = Array.from(fileSizes.values()).reduce((total, fileSize) => total + fileSize, 0);

if (returnType.errors) {
return {
size: folderSize,
errors: errors.length > 0 ? errors : null,
}
} else {
return folderSize;
}

return readSizeRecursive(...args);
};
}
Loading

0 comments on commit bd3d41f

Please sign in to comment.