UNPKG

sonofs

Version:

sono nodejs distributed file system

206 lines (181 loc) 6.93 kB
const net = require('net'); const fs = require('fs'); const { parseFileName } = require('./util'); const { MAX_MEM_FILE_SIZE } = require('./consts'); const socketUtils = require('./socket-utils'); const { mimeMaps, getMime } = require('./mime'); const FileServerGetter = require('./FileServerGetter'); const UploadTemporary = require('./UploadTemporary'); class FileClient { static create(cfg) { return new FileClient(cfg); } constructor({ logger, ...cfg }) { if (!cfg.tmpDir) throw new Error('`cfg.tmpDir` can not be null!'); this.parseFileName = parseFileName; this.logger = logger || console; this.cfg = cfg; this.fileServerGetter = new FileServerGetter({ logger: this.logger, registryCfg: cfg.registry }); this.uploadTemporary = new UploadTemporary({ logger, cfg }); } async getFile(fileName) { const server = await this.fileServerGetter.getFileServer(fileName); const fileInfo = parseFileName(fileName); const { fileSize, mime } = fileInfo; return new Promise((resolve, reject) => { const client = net.createConnection({ host: server.host, port: server.port, // 以 1M/s 计算过期时间 timeout: Math.min(30000, Math.max(2000, (fileSize / (1024 * 1024)) * 1000)) }, () => { client.cork(); client.write(Buffer.from([0])); client.write(fileName, 'utf8'); client.uncork(); }) .on('timeout', () => { client.end(); }) .on('error', reject) .on('close', () => { reject(new Error('CONNECTION_CLOSED')); }); let reading = false; let allBuf; function readBuffer(chunk) { if (!reading) { const success = chunk.readUInt8(); if (success === 0) { reject(new Error('GET_FILE_FAILURE')); client.end(); return; } reading = true; allBuf = chunk; } else { allBuf = Buffer.concat([allBuf, chunk]); } if (allBuf.length >= fileSize + 1) { reading = false; const fileBuf = allBuf.slice(1); resolve({ mime: getMime(mime), buffer: fileBuf }); } } client.on('data', (chunk) => { try { readBuffer(chunk); } catch (e) { reject(e); client.end(); } }); }); } /** * 上传文件 * @param {string|number} mime 文件mime类型 * @param {*} stream 文件流 * @param {*} callback */ async upload(mime, stream) { if (typeof mime === 'string') { mime = mimeMaps[mime] || 6; } const [server, fileInfo] = await Promise.all([ this.fileServerGetter.getUploadServer(), this.uploadTemporary.read(stream) ]); let tempFilePath; const fileSize = fileInfo.fileSize; const client = net.createConnection({ host: server.host, port: server.port, // 以 1M/s 计算过期时间 timeout: Math.min(30000, Math.max(2000, (fileSize / (1024 * 1024)) * 1000)) }, () => { if (fileInfo.type === 'buffer') { // 一次性上传 const headBuf = Buffer.alloc(9); headBuf.writeUInt8(1); headBuf.writeUInt32BE(fileSize + 9, 1); headBuf.writeUInt8(mime, 5); headBuf.writeUIntBE(fileSize, 6, 3); const buf = fileInfo.buffer; client.cork(); client.write(headBuf); client.write(buf); client.uncork(); } else { // 分片上传 let rangeSize = MAX_MEM_FILE_SIZE; let offset = 0; tempFilePath = fileInfo.path; fs.open(fileInfo.path, 'r', (err, fd) => { if (err) { return client.end(); } for (; offset < fileSize; offset += rangeSize) { let bufSize = Math.min(rangeSize, fileSize - offset); const headBuf = Buffer.alloc(17); headBuf.writeUInt8(2); headBuf.writeUInt32BE(17 + bufSize, 1); headBuf.writeUInt8(mime, 5); headBuf.writeUIntBE(fileSize, 6, 4); headBuf.writeUIntBE(offset, 10, 4); headBuf.writeUIntBE(bufSize, 14, 3); let readBuf = Buffer.alloc(bufSize); fs.read(fd, readBuf, 0, bufSize, offset, (err, bytesRead, buf) => { if (err) return client.end(); client.write(headBuf); client.write(buf); }); } }); } }) .on('timeout', () => { this.logger.info('timeout'); client.end(); }); return new Promise((resolve, reject) => { client .on('error', reject) .on('close', () => { tempFilePath && fs.unlink(tempFilePath, () => { }); reject(new Error('CONNECTION_CLOSED')); }); let success = true; let fileRangeSize = 0; socketUtils.onReceiveBlock(client, (type, buf) => { try { success &= type == 1; fileRangeSize += buf.readUInt32BE(); console.log('upload complete - type:', type, 'fileRangeSize:', fileRangeSize, "fileSize:", fileSize); if (fileRangeSize === fileSize) { if (!success) { reject(new Error('UPLOAD_FAILURE')); } else { resolve(buf.toString('utf8', 4)); } client.end(); } } catch (e) { this.logger.error(e); reject(e); client.end(); } }); }); } } module.exports = FileClient;