Merge branch 'master' of https://gitee.com/bnu_mixly/mixly3
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -36,7 +36,7 @@ except Exception as e:
|
||||
'''IMU-Sensor'''
|
||||
try :
|
||||
import qmi8658
|
||||
onboard_imu = qmi8658.QMI8658(onboard_i2c, 0x6A)
|
||||
onboard_imu = qmi8658.QMI8658(onboard_i2c)
|
||||
except Exception as e:
|
||||
print("Warning: Failed to communicate with QMI8658 (IMU) or",e)
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -171,7 +171,6 @@ export const turtle_setxy = {
|
||||
};
|
||||
|
||||
export const turtle_pos_shape = {
|
||||
|
||||
init: function () {
|
||||
this.setColour(TURTLE_HUE);
|
||||
var pos_shape =
|
||||
@@ -200,6 +199,18 @@ export const turtle_pos_shape = {
|
||||
}
|
||||
};
|
||||
|
||||
export const turtle_stamp = {
|
||||
init: function () {
|
||||
this.setColour(TURTLE_HUE);
|
||||
this.appendValueInput('TUR')
|
||||
.setCheck('Turtle')
|
||||
this.appendDummyInput()
|
||||
.appendField(Blockly.Msg.TURTLE_STAMP);
|
||||
this.setInputsInline(true);
|
||||
this.setPreviousStatement(true);
|
||||
this.setNextStatement(true);
|
||||
}
|
||||
};
|
||||
|
||||
export const turtle_clear = {
|
||||
init: function () {
|
||||
@@ -211,8 +222,6 @@ export const turtle_clear = {
|
||||
this.setColour(TURTLE_HUE);
|
||||
this.appendDummyInput("")
|
||||
.appendField(new Blockly.FieldDropdown(clear_reset), 'DIR')
|
||||
|
||||
|
||||
this.setInputsInline(true);
|
||||
this.setPreviousStatement(true);
|
||||
this.setNextStatement(true);
|
||||
|
||||
@@ -75,6 +75,13 @@ export const turtle_pos_shape = function (_, generator) {
|
||||
return [code, generator.ORDER_ATOMIC];
|
||||
}
|
||||
|
||||
export const turtle_stamp = function (_, generator) {
|
||||
generator.definitions_.import_turtle = "import turtle";
|
||||
var varName = generator.valueToCode(this, 'TUR', generator.ORDER_ASSIGNMENT) || '0';
|
||||
var code = varName + '.stamp()\n';
|
||||
return code;
|
||||
}
|
||||
|
||||
export const turtle_clear = function (_, generator) {
|
||||
generator.definitions_.import_turtle = "import turtle";
|
||||
var varName = generator.valueToCode(this, 'TUR', generator.ORDER_ASSIGNMENT) || '0';
|
||||
|
||||
@@ -1814,6 +1814,13 @@
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="turtle_stamp">
|
||||
<value name="TUR">
|
||||
<shadow type="variables_get">
|
||||
<field name="VAR">tina</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="turtle_clear">
|
||||
<value name="TUR">
|
||||
<shadow type="variables_get">
|
||||
|
||||
@@ -1777,6 +1777,13 @@
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="turtle_stamp">
|
||||
<value name="TUR">
|
||||
<shadow type="variables_get">
|
||||
<field name="VAR">tina</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="turtle_clear">
|
||||
<value name="TUR">
|
||||
<shadow type="variables_get">
|
||||
|
||||
@@ -1445,6 +1445,13 @@
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="turtle_stamp">
|
||||
<value name="TUR">
|
||||
<shadow type="variables_get">
|
||||
<field name="VAR">tina</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="turtle_clear">
|
||||
<value name="TUR">
|
||||
<shadow type="variables_get">
|
||||
|
||||
@@ -1776,6 +1776,7 @@ En.TURTLE_POS_SHAPE = "Get the turtle currently";
|
||||
En.TURTLE_POS = "Get the current position of the turtle";
|
||||
En.TURTLE_SHAPE = "shape";
|
||||
En.TURTLE_HEADING = "Heading";
|
||||
En.TURTLE_STAMP = "Stamp";
|
||||
En.MIXLY_TOOLTIP_TURTEL_POS = 'Get the turtle\'s current position and return a tuple containing the current position x and y coordinates';
|
||||
En.MIXLY_TOOLTIP_TURTEL_SHAPE = 'Get the turtle\'s current shape and return a string';
|
||||
En.MIXLY_TOOLTIP_TURTEL_GET_SPEED = "get the turtles current speed and return an integer;";
|
||||
|
||||
@@ -1789,6 +1789,7 @@ ZhHans.TURTLE_POS_SHAPE = "获取海龟当前";
|
||||
ZhHans.TURTLE_POS = "位置";
|
||||
ZhHans.TURTLE_SHAPE = "形状";
|
||||
ZhHans.TURTLE_HEADING = "朝向";
|
||||
ZhHans.TURTLE_STAMP = "留下印记";
|
||||
ZhHans.MIXLY_TOOLTIP_TURTEL_POS = '获取海龟当前位置,返回一个包含当前位置x坐标和y坐标的元组';
|
||||
ZhHans.MIXLY_TOOLTIP_TURTEL_SHAPE = '获取海龟当前形状,返回一个字符串';
|
||||
ZhHans.MIXLY_TOOLTIP_TURTEL_GET_SPEED = '获取海龟当前速度,返回一个整数';
|
||||
|
||||
@@ -1789,6 +1789,7 @@ ZhHant.TURTLE_POS_SHAPE = "獲取海龜當前";
|
||||
ZhHant.TURTLE_POS = "位置";
|
||||
ZhHant.TURTLE_SHAPE = "形狀";
|
||||
ZhHant.TURTLE_HEADING = "朝向";
|
||||
ZhHant.TURTLE_STAMP = "留下印記";
|
||||
ZhHant.MIXLY_TOOLTIP_TURTEL_POS = '獲取海龜當前位置,返回一個包含當前位置x座標和y座標的元組';
|
||||
ZhHant.MIXLY_TOOLTIP_TURTEL_SHAPE = '獲取海龜當前形狀,返回一個字符串';
|
||||
ZhHant.MIXLY_TOOLTIP_TURTEL_GET_SPEED = '獲取海龜當前速度,返回一個整數';
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<script type="text/javascript" src="./common/modules/web-modules/lazyload.js"></script>
|
||||
<script type="text/javascript" src="./common/blockly-core/base.js"></script>
|
||||
<script type="text/javascript" src="./common/main.js?view=home"></script>
|
||||
<link rel="stylesheet" type="text/css" href="./common/ui/fomantic-ui/semantic.min.css" />
|
||||
<link rel="stylesheet" href="./files/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="./files/owl.carousel.min.css">
|
||||
<link rel="stylesheet" href="./files/slicknav.min.css">
|
||||
|
||||
@@ -31,7 +31,7 @@ Config.init = () => {
|
||||
const boardPageConfig = Url.getConfig();
|
||||
Config.BOARD_PAGE = boardPageConfig ?? {};
|
||||
console.log('Config.BOARD_PAGE:', Config.BOARD_PAGE);
|
||||
document.title = Config.SOFTWARE.version ?? 'Mixly 2.0';
|
||||
document.title = Config.SOFTWARE.version ?? 'Mixly 3.0';
|
||||
|
||||
Config.USER = {
|
||||
...Config.USER,
|
||||
|
||||
@@ -5,6 +5,7 @@ goog.require('ace.ExtLanguageTools');
|
||||
goog.require('layui');
|
||||
goog.require('store');
|
||||
goog.require('$.select2');
|
||||
goog.require('$.fomanticUI');
|
||||
goog.require('Mixly.XML');
|
||||
goog.require('Mixly.LayerExt');
|
||||
goog.require('Mixly.Msg');
|
||||
@@ -167,13 +168,14 @@ Setting.onclick = () => {
|
||||
Setting.addOnchangeOptionListener = () => {
|
||||
element.on('tab(setting-menu-filter)', function(data) {
|
||||
const { index } = data;
|
||||
if (index === 1) {
|
||||
const type = $(data.elem.prevObject).data('type');
|
||||
if (type === 'import-board') {
|
||||
if (data.index !== Setting.nowIndex) {
|
||||
goog.isElectron && BoardManager.onclickImportBoards();
|
||||
} else {
|
||||
layui.table.resize('cloud-boards-table');
|
||||
}
|
||||
} else if (index === 2) {
|
||||
} else if (type === 'ws-update') {
|
||||
if (data.index !== Setting.nowIndex) {
|
||||
$('#setting-menu-update').loading({
|
||||
background: USER.theme === 'dark' ? '#807b7b' : '#fff',
|
||||
@@ -189,6 +191,26 @@ Setting.addOnchangeOptionListener = () => {
|
||||
args: [ SOFTWARE.configUrl ]
|
||||
});
|
||||
}
|
||||
} else if (type === 'nw-update') {
|
||||
fetch('/api/check-update', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((result) => {
|
||||
const {
|
||||
localVersion,
|
||||
cloudVersion,
|
||||
needsUpdate,
|
||||
cloudFile
|
||||
} = result;
|
||||
Setting.refreshUpdateMenuStatus(localVersion, cloudVersion, needsUpdate, cloudFile);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error)
|
||||
});
|
||||
}
|
||||
Setting.nowIndex = index;
|
||||
});
|
||||
@@ -229,58 +251,65 @@ Setting.configMenuGetValue = (obj) => {
|
||||
return config;
|
||||
}
|
||||
|
||||
Setting.refreshUpdateMenuStatus = (config) => {
|
||||
console.log(config);
|
||||
const {
|
||||
serverVersion
|
||||
} = config;
|
||||
let $serverDiv = $('#setting-menu-update-server');
|
||||
let $btnDiv = $('#setting-menu-update > div:nth-child(2)');
|
||||
Setting.refreshUpdateMenuStatus = (localVersion, cloudVersion, needsUpdate, url) => {
|
||||
const $serverDiv = $('#setting-menu-update-server');
|
||||
const $btnDiv = $('#setting-menu-update > div:nth-child(2)');
|
||||
const $button = $btnDiv.children('button');
|
||||
$button.removeClass('layui-btn-disabled');
|
||||
$button.addClass('self-adaption-btn');
|
||||
const $mixlyProgress = $serverDiv.find('.mixly-progress');
|
||||
$serverDiv.find('span').css('display', 'none');
|
||||
let needUpdateServer = false;
|
||||
if (serverVersion && serverVersion !== SOFTWARE.serverVersion) {
|
||||
$serverDiv.find('span[value="obsolete"]').css('display', 'inline-block');
|
||||
needUpdateServer = true;
|
||||
$serverDiv.find('text').text(`${SOFTWARE.serverVersion} → ${serverVersion}`);
|
||||
$mixlyProgress.hide();
|
||||
$mixlyProgress.find('.progress').show();
|
||||
$mixlyProgress.removeClass('swinging indeterminate');
|
||||
if (needsUpdate) {
|
||||
$serverDiv.find('span.obsolete').css('display', 'inline-block');
|
||||
$serverDiv.find('text').text(`${localVersion} → ${cloudVersion}`);
|
||||
|
||||
} else {
|
||||
$serverDiv.find('span[value="latest"]').css('display', 'inline-block');
|
||||
$serverDiv.find('text').text(SOFTWARE.serverVersion);
|
||||
$serverDiv.find('span.latest').css('display', 'inline-block');
|
||||
$serverDiv.find('text').text(localVersion);
|
||||
}
|
||||
if (needUpdateServer) {
|
||||
if (needsUpdate) {
|
||||
$btnDiv.css('display', 'flex');
|
||||
$btnDiv.children('button').off().click((event) => {
|
||||
LayerExt.open({
|
||||
title: Msg.getLang('PROGRESS'),
|
||||
id: 'setting-menu-update-layer',
|
||||
shade: LayerExt.SHADE_ALL,
|
||||
area: ['40%', '60%'],
|
||||
max: ['800px', '300px'],
|
||||
min: ['500px', '100px'],
|
||||
success: (layero, index) => {
|
||||
$('#setting-menu-update-layer').css('overflow', 'hidden');
|
||||
layero.find('.layui-layer-setwin').css('display', 'none');
|
||||
Setting.ace = Setting.createAceEditor('setting-menu-update-layer');
|
||||
Setting.ace.resize();
|
||||
const { Socket } = Mixly.WebSocket;
|
||||
Socket.sendCommand({
|
||||
obj: 'Socket',
|
||||
func: 'updateSW',
|
||||
args: []
|
||||
$button.off().one('click', (event) => {
|
||||
$button.addClass('layui-btn-disabled');
|
||||
$button.removeClass('self-adaption-btn');
|
||||
const eventSource = new EventSource(`/api/download?url=${encodeURIComponent(url)}&cloudVersion=${cloudVersion}`);
|
||||
$mixlyProgress.show();
|
||||
eventSource.onmessage = function(event) {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.type === 'progress') {
|
||||
$mixlyProgress.progress({
|
||||
percent: data.progress
|
||||
});
|
||||
},
|
||||
resizing: (layero) => {
|
||||
Setting.ace.resize();
|
||||
},
|
||||
end: () => {
|
||||
} else if (data.type === 'unzip') {
|
||||
$mixlyProgress.addClass('swinging indeterminate');
|
||||
$mixlyProgress.progress({
|
||||
percent: 100
|
||||
});
|
||||
$mixlyProgress.find('.progress').hide();
|
||||
layer.msg('解压中...', { time: 1000 });
|
||||
} else if (data.type === 'complete') {
|
||||
$mixlyProgress.removeClass('swinging indeterminate');
|
||||
layer.msg('更新完成!5秒后自动刷新...', { time: 1000 });
|
||||
eventSource.close();
|
||||
setTimeout(function(){
|
||||
window.location.reload();
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
};
|
||||
eventSource.onerror = function(error) {
|
||||
layer.msg('下载失败!5秒后自动刷新...', { time: 1000 });
|
||||
setTimeout(function(){
|
||||
window.location.reload();
|
||||
}, 5000);
|
||||
eventSource.close();
|
||||
};
|
||||
});
|
||||
} else {
|
||||
$btnDiv.css('display', 'none');
|
||||
}
|
||||
setTimeout(() => {
|
||||
$('#setting-menu-update').loading('destroy');
|
||||
}, 500);
|
||||
}
|
||||
|
||||
Setting.showUpdateMessage = (data) => {
|
||||
|
||||
@@ -21,6 +21,9 @@ if (Env.hasSocketServer) {
|
||||
if (env === 'electron' && !goog.isElectron) {
|
||||
env = 'web';
|
||||
}
|
||||
if (typeof nw === 'object') {
|
||||
env = 'nw';
|
||||
}
|
||||
|
||||
XML.TEMPLATE_CONFIG = [
|
||||
{
|
||||
|
||||
@@ -89,6 +89,7 @@
|
||||
"layui",
|
||||
"store",
|
||||
"$.select2",
|
||||
"$.fomanticUI",
|
||||
"Mixly.XML",
|
||||
"Mixly.LayerExt",
|
||||
"Mixly.Msg",
|
||||
|
||||
@@ -167,22 +167,52 @@
|
||||
#setting-menu-update .layui-panel {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
#setting-menu-update .latest,
|
||||
#setting-menu-update .obsolete {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#setting-menu-update-server .mixly-progress {
|
||||
display: none;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
#setting-menu-update-server .bar {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
#setting-menu-update-server .bar > .progress {
|
||||
/*display: none;*/
|
||||
height: 20px;
|
||||
margin-bottom: 10px;
|
||||
overflow: hidden;
|
||||
border-radius: 1px;
|
||||
-webkit-box-shadow: unset;
|
||||
box-shadow: unset;
|
||||
background-color: unset;
|
||||
}
|
||||
</style>
|
||||
<div id="setting-menu" class="layui-layer-wrap">
|
||||
<div id="setting-menu-top">
|
||||
<div id="setting-menu-left">
|
||||
<ul id="setting-menu-options" class="layui-nav layui-nav-tree layui-bg-cyan layui-inline"
|
||||
lay-filter="setting-menu-filter">
|
||||
<li class="layui-nav-item layui-this" lay-id="0">
|
||||
<li class="layui-nav-item layui-this" lay-id="0" data-type="config">
|
||||
<a m-id="0">{{ d.personalise }}</a>
|
||||
</li>
|
||||
{{# if(d.env === 'electron'){ }}
|
||||
<li class="layui-nav-item" lay-id="1">
|
||||
<li class="layui-nav-item" lay-id="1" data-type="import-board">
|
||||
<a m-id="1" href="javascript:;">{{ d.importBoard }}</a>
|
||||
</li>
|
||||
{{# } }}
|
||||
{{# if(d.env === 'web-socket'){ }}
|
||||
<li class="layui-nav-item" lay-id="2">
|
||||
<li class="layui-nav-item" lay-id="2" data-type="ws-update">
|
||||
<a m-id="2">{{ d.checkForUpdates }}</a>
|
||||
</li>
|
||||
{{# } }}
|
||||
{{# if(d.env === 'nw'){ }}
|
||||
<li class="layui-nav-item" lay-id="3" data-type="nw-update">
|
||||
<a m-id="2">{{ d.checkForUpdates }}</a>
|
||||
</li>
|
||||
{{# } }}
|
||||
@@ -341,19 +371,24 @@
|
||||
<div class="layui-hide" id="import-board-page" lay-filter="import-board-page-filter"></div>
|
||||
</div>
|
||||
{{# } }}
|
||||
{{# if(d.env === 'web-socket'){ }}
|
||||
{{# if(d.env === 'web-socket' || d.env === 'nw'){ }}
|
||||
<div class="menu-body">
|
||||
<div id="setting-menu-update" class="setting-menu-info">
|
||||
<div>
|
||||
<div id="setting-menu-update-server" class="layui-card layui-panel">
|
||||
<div class="layui-card-header">
|
||||
<label>{{ d.server }}</label>
|
||||
<span class="layui-badge layui-bg-green" value="latest">{{ d.latest }}</span>
|
||||
<span class="layui-badge" value="obsolete">{{ d.obsolete }}</span>
|
||||
<span class="latest layui-badge layui-bg-green" value="latest">{{ d.latest }}</span>
|
||||
<span class="obsolete layui-badge" value="obsolete">{{ d.obsolete }}</span>
|
||||
</div>
|
||||
<div class="layui-card-body" class="">
|
||||
<label>{{ d.version }}: </label>
|
||||
<text>1.0.0</text>
|
||||
<text></text>
|
||||
<div class="mixly-progress ui teal progress">
|
||||
<div class="bar">
|
||||
<div class="progress"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
"author": "Mixly",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"adm-zip": "^0.5.16",
|
||||
"axios": "^0.27.2",
|
||||
"commander": "^11.1.0",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"css-loader": "^6.8.1",
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
"nwjs-builder-phoenix": "^1.15.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"adm-zip": "^0.5.16",
|
||||
"axios": "^0.27.2",
|
||||
"fs": "^0.0.1-security",
|
||||
"http": "^0.0.1-security",
|
||||
"path": "^0.12.7",
|
||||
|
||||
@@ -23,11 +23,15 @@ if (options.type === 'special') {
|
||||
options.type = path.basename(process.cwd());
|
||||
}
|
||||
|
||||
let targetIndex = 0;
|
||||
|
||||
const generateHash = (inputString) => {
|
||||
const hash = crypto.createHash('sha256');
|
||||
hash.update(inputString);
|
||||
return hash.digest('hex');
|
||||
}
|
||||
// const hash = crypto.createHash('sha256');
|
||||
// hash.update(inputString);
|
||||
// return hash.digest('hex');
|
||||
targetIndex += 1;
|
||||
return String(targetIndex);
|
||||
};
|
||||
|
||||
const getExamples = (dirPath, convertExample = false) => {
|
||||
let examples = {};
|
||||
|
||||
276
static-server/api.js
Normal file
276
static-server/api.js
Normal file
@@ -0,0 +1,276 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const express = require('express');
|
||||
const axios = require('axios');
|
||||
const AdmZip = require('adm-zip');
|
||||
|
||||
|
||||
class AsyncAdmZip {
|
||||
constructor(zipPath) {
|
||||
this.zipPath = zipPath;
|
||||
this.zip = new AdmZip(zipPath);
|
||||
}
|
||||
|
||||
// 异步解压到目录
|
||||
async extractAllTo(targetPath, overwrite = true) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
// 确保目标目录存在
|
||||
fs.promises.mkdir(targetPath, { recursive: true })
|
||||
.then(() => {
|
||||
this.zip.extractAllTo(targetPath, overwrite);
|
||||
resolve({
|
||||
success: true,
|
||||
targetPath,
|
||||
fileCount: this.zip.getEntries().length
|
||||
});
|
||||
})
|
||||
.catch(reject);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 异步解压单个文件
|
||||
async extractEntry(entryName, targetPath, overwrite = true) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const entry = this.zip.getEntry(entryName);
|
||||
if (!entry) {
|
||||
reject(new Error(`条目不存在: ${entryName}`));
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.isDirectory) {
|
||||
reject(new Error(`条目是目录: ${entryName}`));
|
||||
return;
|
||||
}
|
||||
|
||||
// 确保目标目录存在
|
||||
const targetDir = path.dirname(targetPath);
|
||||
fs.promises.mkdir(targetDir, { recursive: true })
|
||||
.then(() => {
|
||||
this.zip.extractEntryTo(entry, targetDir, false, overwrite);
|
||||
resolve({
|
||||
success: true,
|
||||
entryName,
|
||||
targetPath
|
||||
});
|
||||
})
|
||||
.catch(reject);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 异步获取zip文件信息
|
||||
async getZipInfo() {
|
||||
return new Promise((resolve) => {
|
||||
const entries = this.zip.getEntries();
|
||||
const info = {
|
||||
fileCount: entries.length,
|
||||
totalSize: entries.reduce((sum, entry) => sum + entry.header.size, 0),
|
||||
entries: entries.map(entry => ({
|
||||
name: entry.entryName,
|
||||
size: entry.header.size,
|
||||
isDirectory: entry.isDirectory,
|
||||
compressedSize: entry.header.compressedSize
|
||||
}))
|
||||
};
|
||||
resolve(info);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
|
||||
const TEMP_FOLDER_PATH = path.resolve(__dirname, '../temp');;
|
||||
const VERSION_FILE = path.resolve(__dirname, '../version.json');
|
||||
|
||||
function getLocalVersion() {
|
||||
try {
|
||||
if (fs.existsSync(VERSION_FILE)) {
|
||||
const data = fs.readFileSync(VERSION_FILE, 'utf8');
|
||||
return data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('读取版本文件失败:', error);
|
||||
}
|
||||
return '2025.09.06';
|
||||
}
|
||||
|
||||
function saveVersionInfo(version) {
|
||||
fs.writeFileSync(VERSION_FILE, version);
|
||||
}
|
||||
|
||||
async function getCloudVersion() {
|
||||
try {
|
||||
const response = await axios.get('http://update.mixly.cn/index.php');
|
||||
return response.data.mixly.all;
|
||||
} catch (error) {
|
||||
console.error('获取云端版本信息失败:', error);
|
||||
return {
|
||||
file: 'mixly.zip',
|
||||
version: '2025.09.06'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function checkUpdate() {
|
||||
try {
|
||||
const localVersion = getLocalVersion();
|
||||
const cloudVersions = await getCloudVersion();
|
||||
const cloudVersion = cloudVersions['version'];
|
||||
const cloudFile = 'http://update.mixly.cn/download.php?file=' + cloudVersions['file']
|
||||
return {
|
||||
needsUpdate: localVersion !== cloudVersion,
|
||||
localVersion: localVersion,
|
||||
cloudVersion: cloudVersion,
|
||||
cloudFile: cloudFile,
|
||||
error: ''
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
needsUpdate: false,
|
||||
localVersion: '',
|
||||
cloudVersion: '',
|
||||
cloudFile: '',
|
||||
error: error.message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查更新接口
|
||||
router.post('/check-update', async (req, res) => {
|
||||
const updateInfo = await checkUpdate();
|
||||
res.json(updateInfo);
|
||||
});
|
||||
|
||||
// 下载进度返回
|
||||
function deleteFolderRecursive(dirPath) {
|
||||
if (fs.existsSync(dirPath)) {
|
||||
fs.readdirSync(dirPath).forEach(file => {
|
||||
const curPath = path.join(dirPath, file);
|
||||
if (fs.lstatSync(curPath).isDirectory()) {
|
||||
deleteFolderRecursive(curPath);
|
||||
} else {
|
||||
fs.unlinkSync(curPath);
|
||||
}
|
||||
});
|
||||
fs.rmdirSync(dirPath);
|
||||
}
|
||||
}
|
||||
|
||||
router.get('/download', async (req, res) => {
|
||||
try {
|
||||
const { url, cloudVersion } = req.query;
|
||||
|
||||
// 清理临时文件夹
|
||||
if (fs.existsSync(TEMP_FOLDER_PATH)) {
|
||||
deleteFolderRecursive(TEMP_FOLDER_PATH);
|
||||
}
|
||||
fs.mkdirSync(TEMP_FOLDER_PATH, { recursive: true });
|
||||
|
||||
const filePath = path.resolve(TEMP_FOLDER_PATH, 'mixly.zip');
|
||||
const fileStream = fs.createWriteStream(filePath);
|
||||
|
||||
// 设置 SSE 响应头
|
||||
res.setHeader('Content-Type', 'text/event-stream');
|
||||
res.setHeader('Cache-Control', 'no-cache');
|
||||
res.setHeader('Connection', 'keep-alive');
|
||||
res.flushHeaders();
|
||||
|
||||
// 发起下载请求 - 添加 NW.js 特定配置
|
||||
const response = await axios({
|
||||
method: 'GET',
|
||||
url: url,
|
||||
responseType: 'stream',
|
||||
timeout: 60000,
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
||||
},
|
||||
adapter: require('axios/lib/adapters/http')
|
||||
});
|
||||
|
||||
const totalSize = parseInt(response.headers['content-length'], 10) || 0;
|
||||
|
||||
let downloadedSize = 0;
|
||||
let lastProgress = 0;
|
||||
|
||||
// 发送进度信息
|
||||
const sendProgress = (progress) => {
|
||||
if (progress !== lastProgress) {
|
||||
const data = JSON.stringify({ type: 'progress', progress });
|
||||
res.write(`data: ${data}\n\n`);
|
||||
lastProgress = progress;
|
||||
}
|
||||
};
|
||||
|
||||
// 管道流处理
|
||||
response.data.pipe(fileStream);
|
||||
|
||||
// 进度监控
|
||||
response.data.on('data', (chunk) => {
|
||||
downloadedSize += chunk.length;
|
||||
if (totalSize > 0) {
|
||||
const progress = Math.round((downloadedSize / totalSize) * 100);
|
||||
sendProgress(progress);
|
||||
}
|
||||
});
|
||||
|
||||
// 文件流完成
|
||||
fileStream.on('finish', async () => {
|
||||
console.log('文件下载完成,开始解压');
|
||||
|
||||
// 发送解压信息
|
||||
res.write(`data: ${JSON.stringify({ type: 'unzip' })}\n\n`);
|
||||
|
||||
try {
|
||||
const asyncZip = new AsyncAdmZip(filePath);
|
||||
await asyncZip.extractAllTo(path.resolve(__dirname, '../'));
|
||||
|
||||
// 保存版本信息
|
||||
saveVersionInfo(cloudVersion);
|
||||
|
||||
// 发送完成信息
|
||||
res.write(`data: ${JSON.stringify({ type: 'complete', version: cloudVersion })}\n\n`);
|
||||
|
||||
// 清理临时文件
|
||||
if (fs.existsSync(TEMP_FOLDER_PATH)) {
|
||||
deleteFolderRecursive(TEMP_FOLDER_PATH);
|
||||
}
|
||||
|
||||
res.end();
|
||||
} catch (error) {
|
||||
console.error('解压失败:', error);
|
||||
res.write(`data: ${JSON.stringify({ type: 'error', message: '解压失败' })}\n\n`);
|
||||
res.end();
|
||||
}
|
||||
});
|
||||
|
||||
// 错误处理
|
||||
response.data.on('error', (error) => {
|
||||
console.error('下载流错误:', error);
|
||||
res.write(`data: ${JSON.stringify({ type: 'error', message: '下载流错误' })}\n\n`);
|
||||
res.end();
|
||||
});
|
||||
|
||||
fileStream.on('error', (error) => {
|
||||
console.error('文件流错误:', error);
|
||||
res.write(`data: ${JSON.stringify({ type: 'error', message: '文件保存错误' })}\n\n`);
|
||||
res.end();
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('下载过程错误:', error);
|
||||
res.write(`data: ${JSON.stringify({ type: 'error', message: '下载失败: ' + error.message })}\n\n`);
|
||||
res.end();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,12 +1,14 @@
|
||||
const http = require('http');
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const apiRoutes = require('./api.js');
|
||||
|
||||
const StaticServer = {};
|
||||
|
||||
StaticServer.run = (port) => {
|
||||
const app = express();
|
||||
app.use(express.static(path.resolve(__dirname, '../')));
|
||||
app.use('/api/', apiRoutes);
|
||||
const httpServer = http.createServer(app);
|
||||
httpServer.listen(port);
|
||||
console.log('Static服务器正在运行 [端口 - ' + port + ', http]...');
|
||||
|
||||
Reference in New Issue
Block a user