游戏热更新插件
This commit is contained in:
155
packages/update-manager/js/FileUploader.js
Normal file
155
packages/update-manager/js/FileUploader.js
Normal file
@@ -0,0 +1,155 @@
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
const FileUtil = require('./FileUtils');
|
||||
const Client = require("ftp");
|
||||
|
||||
class FileUploader {
|
||||
ftps = {};
|
||||
maxClients = 3;
|
||||
options = {};
|
||||
queue = [];
|
||||
|
||||
setOption(options, maxThread) {
|
||||
this.options = options;
|
||||
this.maxClients = maxThread || 1;
|
||||
}
|
||||
|
||||
prepare() {
|
||||
const currLen = Object.keys(this.ftps).length;
|
||||
logger.info('准备创建上传线程:', this.maxClients, currLen);
|
||||
if (currLen < this.maxClients) {
|
||||
for (let i = this.maxClients - Object.keys(this.ftps).length; i > 0; i--) {
|
||||
let name = "Thread-" + Math.random();
|
||||
this.ftps[name] = new UploadThread(this.options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
for (let id in this.ftps) {
|
||||
this.ftps[id].destroy();
|
||||
delete this.ftps[id];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件、目录到ftp服务器。
|
||||
* @param source
|
||||
* @param dst
|
||||
*/
|
||||
upload(source, dst) {
|
||||
const st = fs.statSync(source);
|
||||
if (st.isFile()) {
|
||||
this.queue.push({src: source, dst: dst})
|
||||
} else if (st.isDirectory()) {
|
||||
const files = FileUtil.listDirsAndFiles(source);
|
||||
const startIndex = (source.endsWith('\\') || source.endsWith('/')) ? source.length - 1 : source.length;
|
||||
const remoteStr = (dst.endsWith('\\') || dst.endsWith('/')) ? dst.substring(0, dst.length - 1) : dst;
|
||||
for (let i of files.files) {
|
||||
//bugfix: 这里目标文件路径必须是 linux文件路径.否则ftp会做为普通文件直接上传,而不包含目录.
|
||||
this.queue.push({src: i, dst: (remoteStr + i.substring(startIndex)).replace(/\\/g, '/')});
|
||||
}
|
||||
}
|
||||
this.checkUpload();
|
||||
}
|
||||
|
||||
async checkUpload() {
|
||||
// 准备上传线程.
|
||||
this.prepare();
|
||||
let once = null;
|
||||
while ((once = this.queue.shift()) != null) {
|
||||
const readyFtps = Object.values(this.ftps);
|
||||
const index = Math.floor(Math.random() * readyFtps.length);
|
||||
readyFtps[index].addTask(once);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class UploadThread {
|
||||
ftp = null;
|
||||
queue = [];
|
||||
ready = false;
|
||||
stopped = false;
|
||||
options = null;
|
||||
|
||||
constructor(options) {
|
||||
this.options = options;
|
||||
this.ftp = this.newConnection();
|
||||
this.run();
|
||||
}
|
||||
|
||||
addTask(once) {
|
||||
this.queue.push(once);
|
||||
if (this.stopped) {
|
||||
this.ftp = this.newConnection();
|
||||
}
|
||||
}
|
||||
|
||||
newConnection() {
|
||||
this.ready = false;
|
||||
const ftp = new Client();
|
||||
ftp.on('ready', () => {
|
||||
logger.log('ftp ready');
|
||||
this.ready = true;
|
||||
});
|
||||
ftp.on('close', () => {
|
||||
logger.log('ftp client has close');
|
||||
this.ready = false;
|
||||
});
|
||||
ftp.on('end', () => {
|
||||
logger.log('ftp client has end');
|
||||
this.ready = false;
|
||||
});
|
||||
ftp.on('error', (err) => {
|
||||
logger.log('ftp client has an error : ', JSON.stringify(err));
|
||||
this.ready = false;
|
||||
});
|
||||
ftp.connect(this.options);
|
||||
return ftp;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.stopped = true;
|
||||
}
|
||||
|
||||
async sleep(ms) {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
// 定时循环.直到destroy()执行.
|
||||
async run() {
|
||||
while (!this.stopped) {
|
||||
if (!this.ready || this.queue.length <= 0) {
|
||||
await this.sleep(500);
|
||||
continue;
|
||||
}
|
||||
const once = this.queue[0];
|
||||
const err = await this.uploadOnce(this.ftp, once);
|
||||
if (!err) {
|
||||
this.queue.shift();
|
||||
logger.info('上传完成:', once.src);
|
||||
continue;
|
||||
} else {
|
||||
logger.warn('上传失败,准备重试:', once.src);
|
||||
this.ftp.end();
|
||||
// 上传失败有可能是线程强制结束导致的上传失败.
|
||||
if (!this.stopped) {
|
||||
this.ftp = this.newConnection();
|
||||
}
|
||||
}
|
||||
}
|
||||
this.ftp.end();
|
||||
}
|
||||
|
||||
uploadOnce(ftp, once) {
|
||||
return new Promise(resolve => {
|
||||
ftp.put(once.src, once.dst, false, resolve)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function () {
|
||||
return new FileUploader();
|
||||
}
|
75
packages/update-manager/js/FileUtils.js
Normal file
75
packages/update-manager/js/FileUtils.js
Normal file
@@ -0,0 +1,75 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
// console adapter.
|
||||
global.logger = global.logger || {};
|
||||
logger.log = (Editor && Editor.log) || console.log;
|
||||
logger.info = (Editor && Editor.info) || console.info;
|
||||
logger.warn = (Editor && Editor.warn) || console.warn;
|
||||
logger.error = (Editor && Editor.error) || console.error;
|
||||
logger.success = (Editor && Editor.success) || (Editor && Editor.info) || console.log;
|
||||
|
||||
class FileUtil {
|
||||
/*
|
||||
* 获取window上的文件目录以及文件列表信息
|
||||
* @param { String } localDir 本地路径
|
||||
* @return { files, dirs }
|
||||
* */
|
||||
listDirsAndFiles(localDir) {
|
||||
const dirs = []
|
||||
const files = []
|
||||
const dir = fs.readdirSync(localDir)
|
||||
for (let i = 0; i < dir.length; i++) {
|
||||
const p = path.join(localDir, dir[i])
|
||||
const stat = fs.statSync(p)
|
||||
if (stat.isDirectory()) {
|
||||
dirs.push(p)
|
||||
const children = this.listDirsAndFiles(p)
|
||||
dirs.push(...children.dirs)
|
||||
files.push(...children.files)
|
||||
} else {
|
||||
files.push(p)
|
||||
}
|
||||
}
|
||||
return {
|
||||
files,
|
||||
dirs
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝文件或目录。
|
||||
* @param src
|
||||
* @param dst
|
||||
*/
|
||||
copy(src, dst) {
|
||||
const st = fs.statSync(src);
|
||||
if (st.isFile()) {
|
||||
var readable = fs.createReadStream(src);//创建读取流
|
||||
var writable = fs.createWriteStream(dst);//创建写入流
|
||||
readable.pipe(writable);
|
||||
return;
|
||||
} else if (!st.isDirectory()) {
|
||||
return;
|
||||
}
|
||||
if (!fs.existsSync(dst)) {
|
||||
fs.mkdirSync(dst, {recursive: true});
|
||||
}
|
||||
//读取目录
|
||||
const paths = fs.readdirSync(src);
|
||||
for (let path of paths) {
|
||||
this.copy(src + '/' + path, dst + '/' + path);
|
||||
}
|
||||
};
|
||||
|
||||
write(filePath, content) {
|
||||
const pathInfo = path.parse(filePath);
|
||||
// console.log('写文件:', filePath, pathInfo.dir)
|
||||
if (!fs.existsSync(pathInfo.dir)) {
|
||||
fs.mkdirSync(pathInfo.dir, {recursive: true});
|
||||
}
|
||||
fs.writeFileSync(filePath, content, 'utf-8');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new FileUtil();
|
6
packages/update-manager/js/vue.2.5.16.js
Normal file
6
packages/update-manager/js/vue.2.5.16.js
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user