223 lines
6.1 KiB
TypeScript
223 lines
6.1 KiB
TypeScript
import * as path from 'path';
|
|
import * as fse from 'fs-extra';
|
|
|
|
/**
|
|
*
|
|
* separator
|
|
* If the specified filename exists, the separator will be added before the incremental value such as: file{separator}2.jpg
|
|
* The default value is '-'.
|
|
*
|
|
* mode
|
|
* The mode allows you to specify which characters to use to generate the incremental value (the string after the separator)
|
|
* The default value is 'numeric'.
|
|
* 'numeric' Using the following characters: 1234567890
|
|
* 'alpha' Using the following characters: abcdefghijklmnopqrstuvwxyz
|
|
* 'ALPHA' Using the following characters: ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
|
* 'alphanumeric' Using the following characters: 0123456789abcdefghijklmnopqrstuvwxyz
|
|
* 'ALPHANUMERIC' Using the following characters: 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
|
* 'charset' You must specify the characters you wish to use in the charset option
|
|
*
|
|
* paddingCharacter && paddingSize
|
|
* If you wish to left-pad the incremental values with a character, use this option. Here's an example :
|
|
* var uniquefilename = require('uniquefilename');
|
|
* options = {mode: 'alpha', paddingCharacter: '0', paddingSize: 3};
|
|
* uniquefilename.get('/path/to/dir/file.jpg', options, function(filename) {
|
|
* // filename might be "/path/to/dir/file.jpg",
|
|
* // "/path/to/dir/file-002.jpg", "/path/to/dir/file-045.jpg", etc...
|
|
* // depending on the files that exist on your filesystem
|
|
* });
|
|
*
|
|
* alwaysAppend
|
|
* If alwaysAppend is true filenames will include the separator and attachment from the first request.
|
|
* So instead of file.jpg, file-2.jpg you'd get file-1.jpg, file-2.jpg.
|
|
*/
|
|
export interface UniqueFileNameOption {
|
|
separator?: string;
|
|
mode?:
|
|
| 'numeric'
|
|
| 'alpha'
|
|
| 'ALPHA'
|
|
| 'alphanumeric'
|
|
| 'ALPHANUMERIC'
|
|
| 'charset';
|
|
paddingCharacter?: string;
|
|
paddingSize?: number;
|
|
alwaysAppend?: boolean;
|
|
charset?: string;
|
|
}
|
|
|
|
const charsets = {
|
|
alpha: 'abcdefghijklmnopqrstuvwxyz',
|
|
alphanumeric: '0123456789abcdefghijklmnopqrstuvwxyz',
|
|
ALPHA: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
|
ALPHANUMERIC: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
|
};
|
|
|
|
interface UniqueFile {
|
|
dir?: string;
|
|
ext?: string;
|
|
base?: string;
|
|
increment?: number;
|
|
}
|
|
|
|
export class FileUtil {
|
|
static blobToBuffer(blob: Blob): Promise<Buffer> {
|
|
if (typeof Blob === 'undefined' || !(blob instanceof Blob)) {
|
|
throw new Error('first argument must be a Blob');
|
|
}
|
|
|
|
return new Promise<Buffer>((resolve, reject) => {
|
|
const reader = new FileReader();
|
|
reader.onloadend = () => {
|
|
resolve(Buffer.from(reader.result as ArrayBuffer));
|
|
};
|
|
reader.onerror = () => {
|
|
reader.abort();
|
|
reject(reader.error);
|
|
};
|
|
reader.readAsArrayBuffer(blob);
|
|
});
|
|
}
|
|
|
|
static uniqueFileName(
|
|
filePath: string,
|
|
options?: UniqueFileNameOption
|
|
): Promise<string> {
|
|
return new Promise<string>((resolve, reject) => {
|
|
const dir = path.dirname(filePath);
|
|
const ext = path.extname(filePath);
|
|
const base = path.basename(filePath, ext);
|
|
|
|
const uniqueFile: UniqueFile = {
|
|
dir,
|
|
ext,
|
|
base
|
|
};
|
|
|
|
options = options || {};
|
|
options.separator = options.separator || '-';
|
|
options.mode = options.mode || 'numeric';
|
|
|
|
if ('numeric' !== options.mode) {
|
|
if (charsets[options.mode]) {
|
|
options.charset = charsets[options.mode];
|
|
options.mode = 'charset';
|
|
} else if (
|
|
'charset' !== options.mode ||
|
|
('charset' === options.mode && !options.charset)
|
|
) {
|
|
options.mode = 'numeric';
|
|
}
|
|
}
|
|
|
|
if (options.paddingSize && !options.paddingCharacter) {
|
|
options.paddingCharacter = '0';
|
|
}
|
|
|
|
FileUtil.uniqueFileNameProcess(
|
|
uniqueFile,
|
|
options,
|
|
(fileName: string) => {
|
|
resolve(fileName);
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
private static uniqueFileNameProcess(
|
|
uniqueFile: UniqueFile,
|
|
options: UniqueFileNameOption,
|
|
callback: (fileName: string) => void
|
|
) {
|
|
let fileName: string;
|
|
let append = '';
|
|
|
|
if (options.alwaysAppend && !uniqueFile.increment) {
|
|
uniqueFile.increment = 1;
|
|
}
|
|
|
|
if (uniqueFile.increment) {
|
|
if ('numeric' === options.mode) {
|
|
append = '' + uniqueFile.increment;
|
|
} else {
|
|
append = FileUtil.numberToString(uniqueFile.increment, options.charset);
|
|
}
|
|
|
|
if (options.paddingSize) {
|
|
while (append.length < options.paddingSize) {
|
|
append = options.paddingCharacter + append;
|
|
}
|
|
}
|
|
|
|
append = options.separator + append;
|
|
}
|
|
|
|
fileName = path.join(
|
|
uniqueFile.dir,
|
|
uniqueFile.base + append + uniqueFile.ext
|
|
);
|
|
if (fse.existsSync(fileName)) {
|
|
if (uniqueFile.increment) {
|
|
uniqueFile.increment += 1;
|
|
} else {
|
|
uniqueFile.increment = 'numeric' === options.mode ? 2 : 1;
|
|
}
|
|
return FileUtil.uniqueFileNameProcess(uniqueFile, options, callback);
|
|
} else {
|
|
return callback(fileName);
|
|
}
|
|
}
|
|
|
|
private static numberToString(nbr: number, charset: string) {
|
|
const charsetLen = charset.length;
|
|
let strLen = 0;
|
|
let strThisLen = 0;
|
|
let tmp: number;
|
|
|
|
for (let maxpower = 20; maxpower >= 0; maxpower--) {
|
|
const maxvalue = FileUtil.sumOfPowerFromOne(charsetLen, maxpower);
|
|
|
|
if (maxvalue < nbr) {
|
|
strLen = maxpower + 1;
|
|
strThisLen = maxvalue + Math.pow(charsetLen, maxpower + 1) - maxvalue;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (0 === strLen) {
|
|
return null;
|
|
}
|
|
|
|
let str = '';
|
|
while (--strLen >= 0) {
|
|
if (strLen === 0) {
|
|
str += charset.charAt(nbr - 1);
|
|
break;
|
|
}
|
|
|
|
strThisLen = Math.pow(charsetLen, strLen);
|
|
const initial = FileUtil.sumOfPowerFromOne(charsetLen, strLen - 1);
|
|
|
|
for (tmp = charsetLen; tmp >= 1; tmp--) {
|
|
if (initial + tmp * strThisLen < nbr) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
nbr -= tmp * strThisLen;
|
|
str += charset.charAt(tmp - 1);
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
private static sumOfPowerFromOne(base: number, maxpower: number) {
|
|
let value = 0;
|
|
for (let tmp = maxpower; tmp >= 1; tmp--) {
|
|
value += Math.pow(base, tmp);
|
|
}
|
|
return value;
|
|
}
|
|
}
|