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 { if (typeof Blob === 'undefined' || !(blob instanceof Blob)) { throw new Error('first argument must be a Blob'); } return new Promise((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 { return new Promise((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; } }