import * as path from 'path' ; import * as fse from 'fs-extra' ; import { calculateFileMd5 , calculateMd5 , IFileInfo , listDir , mergePartFile , wait , } from './util' ; import { FolderExistException , Md5Exception , NotFoundException , } from './exception' ; const DEAFULT_TEMP_FILE_LOCATION = path . join ( __dirname , './upload_file' ) ; const DEAFULT_MERGED_FILE_LOCATION = path . join ( __dirname , './merged_file' ) ; const DEFAULT_OPTIONS = { tempFileLocation : DEAFULT_TEMP_FILE_LOCATION , mergedFileLocation : DEAFULT_MERGED_FILE_LOCATION , } ; export interface IFileUploaderOptions { tempFileLocation : string ; mergedFileLocation : string ; } export interface IUploadPartInfo { path : string ; index : number ; md5 : string ; } export interface IMergedFileInfo { path : string ; md5 : string ; } export class FileUploaderServer { private fileUploaderOptions : IFileUploaderOptions ; constructor ( options ) { this . fileUploaderOptions = Object . assign ( DEFAULT_OPTIONS , options ) ; } public getOptions ( ) { return this . fileUploaderOptions ; } param fileName returns public async initFilePartUpload ( fileName ) { const { tempFileLocation } = this . fileUploaderOptions ; await fse . ensureDir ( tempFileLocation ) ; const uploadId = calculateMd5 ( `${ fileName }-${ Date . now ( ) }` ) ; const uploadFolderPath = path . join ( tempFileLocation , uploadId ) ; const uploadFolderExist = fse . existsSync ( uploadFolderPath ) ; if ( uploadFolderExist ) { throw new FolderExistException ( 'found same upload folder, maybe you meet hash collision' ) ; } await fse . mkdir ( uploadFolderPath ) ; return uploadId ; } param uploadId param partIndex param partFile returns public async uploadPartFile ( uploadId , partIndex , partFile ) { const uploadFolderPath = await this . getUploadFolder ( uploadId ) ; const partFileMd5 = calculateMd5 ( partFile ) ; const partFileLocation = path . join ( uploadFolderPath , `${ partIndex }|${ partFileMd5 }.part` ) ; await fse . writeFile ( partFileLocation , partFile ) ; return partFileMd5 ; } param uploadId returns public async listUploadedPartFile ( uploadId ) { const uploadFolderPath = await this . getUploadFolder ( uploadId ) ; const dirList = await listDir ( uploadFolderPath ) ; const uploadPartInfo = dirList . map ( ( item ) => { const [ index , md5 ] = item . name . replace ( /\.part$/ , '' ) . split ( '|' ) ; return { path : item . path , index : parseInt ( index ) , md5 , } ; } ) ; return uploadPartInfo ; } param uploadId param deleteFolder async cancelFilePartUpload ( uploadId , deleteFolder = false ) { const uploadFolderPath = await this . getUploadFolder ( uploadId ) ; if ( deleteFolder ) { await fse . remove ( uploadFolderPath ) ; } else { await fse . rename ( uploadFolderPath , `${ uploadFolderPath }[removed]` ) ; } } param uploadId param fileName param md5 returns async finishFilePartUpload ( uploadId , fileName , md5 ) { const { mergedFileLocation } = this . fileUploaderOptions ; await fse . ensureDir ( mergedFileLocation ) ; const uploadFolderPath = await this . getUploadFolder ( uploadId ) ; const dirList = await listDir ( uploadFolderPath ) ; const files = dirList . filter ( ( item ) => item . path . endsWith ( '.part' ) ) ; const mergedFileDirLocation = path . join ( mergedFileLocation , md5 ) ; await fse . ensureDir ( mergedFileDirLocation ) ; const mergedFilePath = path . join ( mergedFileDirLocation , fileName ) ; await mergePartFile ( files , mergedFilePath ) ; await wait ( 1000 ) ; const mergedFileMd5 = await calculateFileMd5 ( mergedFilePath ) ; if ( mergedFileMd5 !== md5 ) { throw new Md5Exception ( 'md5 checked failed' ) ; } return { path : mergedFilePath , md5 , } ; } param uploadId returns private async getUploadFolder ( uploadId ) { const { tempFileLocation } = this . fileUploaderOptions ; await fse . ensureDir ( tempFileLocation ) ; const uploadFolderPath = path . join ( tempFileLocation , uploadId ) ; const uploadFolderExist = fse . existsSync ( uploadFolderPath ) ; if ( ! uploadFolderExist ) { throw new NotFoundException ( 'not found upload folder' ) ; } return uploadFolderPath ; } }