perf: 客户端自动下载新版本更新

This commit is contained in:
kuaifan 2022-01-10 19:27:43 +08:00
parent 92793b8ff8
commit 79a94d25bd
7 changed files with 267 additions and 98 deletions

111
electron/main.js vendored
View File

@ -1,47 +1,23 @@
const fs = require('fs')
const os = require("os");
const path = require('path')
const XLSX = require('xlsx');
const {app, BrowserWindow, ipcMain, dialog} = require('electron')
const utils = require('./utils');
const log = require("electron-log");
let mainWindow = null,
subWindow = [],
downloadList = [],
willQuitApp = false,
inheritClose = false,
devloadCachePath = path.resolve(__dirname, ".devload"),
devloadUrl = "";
if (fs.existsSync(devloadCachePath)) {
devloadUrl = fs.readFileSync(devloadCachePath, 'utf8')
}
function runNum(str, fixed) {
let _s = Number(str);
if (_s + "" === "NaN") {
_s = 0;
}
if (/^[0-9]*[1-9][0-9]*$/.test(fixed)) {
_s = _s.toFixed(fixed);
let rs = _s.indexOf('.');
if (rs < 0) {
_s += ".";
for (let i = 0; i < fixed; i++) {
_s += "0";
}
}
}
return _s;
}
function randomString(len) {
len = len || 32;
let $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678oOLl9gqVvUuI1';
let maxPos = $chars.length;
let pwd = '';
for (let i = 0; i < len; i++) {
pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
}
return pwd;
}
function createMainWindow() {
mainWindow = new BrowserWindow({
width: 1280,
@ -54,7 +30,7 @@ function createMainWindow() {
contextIsolation: false
}
})
mainWindow.webContents.setUserAgent(mainWindow.webContents.getUserAgent() + " MainTaskWindow/1.0");
mainWindow.webContents.setUserAgent(mainWindow.webContents.getUserAgent() + " MainTaskWindow/" + process.platform + "/" + os.arch() + "/1.0");
if (devloadUrl) {
mainWindow.loadURL(devloadUrl).then(r => {
@ -86,6 +62,42 @@ function createMainWindow() {
}
}
})
mainWindow.webContents.session.on('will-download', (event, item) => {
item.setSavePath(path.join(app.getPath('temp'), item.getFilename()));
item.on('done', (event, state) => {
try {
const info = {
state,
name: item.getFilename(),
url: item.getURL(),
chain: item.getURLChain(),
savePath: item.getSavePath(),
mimeType: item.getMimeType(),
totalBytes: item.getTotalBytes(),
};
mainWindow.webContents.send("downloadDone", info)
//
if (info.state == "completed") {
// 下载完成
info.chain.some(url => {
let download = downloadList.find(item => item.url == url)
if (download) {
download.status = "completed"
download.info = info
}
})
} else {
// 下载失败
info.chain.some(url => {
downloadList = downloadList.filter(item => item.url != url)
})
}
} catch (e) {
//
}
})
})
}
function createSubWindow(args) {
@ -100,7 +112,7 @@ function createSubWindow(args) {
}
}
let name = args.name || "auto_" + randomString(6);
let name = args.name || "auto_" + utils.randomString(6);
let item = subWindow.find(item => item.name == name);
let browser = item ? item.browser : null;
if (browser) {
@ -139,7 +151,7 @@ function createSubWindow(args) {
})
subWindow.push({ name, browser })
}
browser.webContents.setUserAgent(browser.webContents.getUserAgent() + " SubTaskWindow/1.0" + (args.userAgent ? (" " + args.userAgent) : ""));
browser.webContents.setUserAgent(browser.webContents.getUserAgent() + " SubTaskWindow/" + process.platform + "/" + os.arch() + "/1.0" + (args.userAgent ? (" " + args.userAgent) : ""));
if (devloadUrl) {
browser.loadURL(devloadUrl + '#' + (args.hash || args.path)).then(r => {
@ -177,6 +189,39 @@ ipcMain.on('inheritClose', (event) => {
event.returnValue = "ok"
})
ipcMain.on('downloadURL', (event, args) => {
const download = downloadList.find(({url}) => url == args.url);
if (download) {
if (download.status == "completed") {
if (fs.existsSync(download.info.savePath)) {
log.warn("已下载完成", args)
mainWindow.webContents.send("downloadDone", download.info)
} else {
log.info("开始重新下载", args)
download.status = "progressing"
mainWindow.webContents.downloadURL(args.url);
}
} else {
log.warn("已在下载列表中", args)
}
} else {
log.info("开始下载", args)
downloadList.push(Object.assign(args, { status: "progressing" }))
mainWindow.webContents.downloadURL(args.url);
}
event.returnValue = "ok"
})
ipcMain.on('openFile', (event, args) => {
utils.openFile(args.path)
event.returnValue = "ok"
})
ipcMain.on('windowQuit', (event) => {
event.returnValue = "ok"
app.quit();
})
ipcMain.on('windowRouter', (event, args) => {
createSubWindow(args)
event.returnValue = "ok"
@ -278,7 +323,7 @@ ipcMain.on('setDockBadge', (event, args) => {
// Mac only
return;
}
if (runNum(args) > 0) {
if (utils.runNum(args) > 0) {
app.dock.setBadge(String(args))
} else {
app.dock.setBadge("")

View File

@ -25,7 +25,7 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/kuaifan/dootask.git"
"url": "https://github.com/kuaifan/dootask.git"
},
"devDependencies": {
"@electron-forge/cli": "^6.0.0-beta.61",
@ -36,11 +36,12 @@
"dmg-license": "^1.0.10",
"dotenv": "^10.0.0",
"electron": "^16.0.5",
"electron-builder": "^22.14.5",
"electron-log": "^4.4.3"
"electron-builder": "^22.14.5"
},
"dependencies": {
"axios": "^0.24.0",
"electron-squirrel-startup": "^1.0.0",
"electron-log": "^4.4.3",
"fs-extra": "^10.0.0",
"xlsx": "^0.17.2"
},

106
electron/utils.js vendored Normal file
View File

@ -0,0 +1,106 @@
const fs = require("fs");
const {shell} = require("electron");
module.exports = {
/**
* 随机数字
* @param str
* @param fixed
* @returns {number}
*/
runNum(str, fixed) {
let _s = Number(str);
if (_s + "" === "NaN") {
_s = 0;
}
if (/^[0-9]*[1-9][0-9]*$/.test(fixed)) {
_s = _s.toFixed(fixed);
let rs = _s.indexOf('.');
if (rs < 0) {
_s += ".";
for (let i = 0; i < fixed; i++) {
_s += "0";
}
}
}
return _s;
},
/**
* 随机字符串
* @param len
* @returns {string}
*/
randomString(len) {
len = len || 32;
let $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678oOLl9gqVvUuI1';
let maxPos = $chars.length;
let pwd = '';
for (let i = 0; i < len; i++) {
pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
}
return pwd;
},
/**
* 字符串包含
* @param string
* @param find
* @param lower
* @returns {boolean}
*/
strExists(string, find, lower = false) {
string += "";
find += "";
if (lower !== true) {
string = string.toLowerCase();
find = find.toLowerCase();
}
return (string.indexOf(find) !== -1);
},
/**
* 字符串是否左边包含
* @param string
* @param find
* @param lower
* @returns {boolean}
*/
leftExists(string, find, lower = false) {
string += "";
find += "";
if (lower !== true) {
string = string.toLowerCase();
find = find.toLowerCase();
}
return (string.substring(0, find.length) === find);
},
/**
* 删除左边字符串
* @param string
* @param find
* @param lower
* @returns {string}
*/
leftDelete(string, find, lower = false) {
string += "";
find += "";
if (this.leftExists(string, find, lower)) {
string = string.substring(find.length)
}
return string ? string : '';
},
/**
* 打开文件
* @param path
*/
openFile(path) {
if (!fs.existsSync(path)) {
return
}
shell.openPath(path).then(() => {
})
},
}

View File

@ -6,15 +6,17 @@
</keep-alive>
</transition>
<Spinner/>
<AppDown/>
</div>
</template>
<script>
import Spinner from "./components/Spinner";
import AppDown from "./components/AppDown";
import {mapState} from "vuex";
export default {
components: {Spinner},
components: {AppDown, Spinner},
data() {
return {

View File

@ -1,9 +1,9 @@
<template>
<div v-if="repoStatus && !$store.state.windowMax768" class="common-app-down">
<div v-if="$Electron" class="common-app-down-link" @click="openExternal(repoData.html_url)">
<div v-if="status && !$store.state.windowMax768" class="common-app-down">
<div v-if="$Electron" class="common-app-down-link" @click="installApplication">
<Icon type="md-download"/> {{$L(repoTitle)}}
</div>
<a v-else class="common-app-down-link" :href="repoData.html_url" target="_blank">
<a v-else class="common-app-down-link" :href="releases.html_url" target="_blank">
<Icon type="md-download"/> {{$L(repoTitle)}}
</a>
</div>
@ -12,9 +12,9 @@
<script>
import Vue from 'vue'
import MarkdownPreview from "./MDEditor/components/preview";
import axios from "axios";
Vue.component('MarkdownPreview', MarkdownPreview)
import axios from "axios";
import { Notification } from 'element-ui';
export default {
@ -23,34 +23,19 @@ export default {
return {
repoName: 'kuaifan/dootask',
repoData: {},
repoStatus: 0, // 0 12
status: 0, // 0 12
releases: {},
downInfo: {}
}
},
mounted() {
this.getReleases();
},
computed: {
repoTitle() {
return this.repoStatus == 2 ? '更新客户端' : '客户端下载';
}
},
watch: {
repoData: {
handler(data) {
if (!data.tag_name) {
this.repoStatus = 0;
return;
}
if (!this.$Electron) {
//
this.repoStatus = 1;
return;
}
//
let currentVersion = window.systemInformation.version;
let latestVersion = $A.leftDelete(data.tag_name.toLowerCase(), "v")
if (this.compareVersion(latestVersion, currentVersion) === 1) {
//
//
if (this.$Electron) {
this.$Electron.ipcRenderer.on('downloadDone', (event, args) => {
if (args.name == this.repoData.name) {
this.downInfo = args;
const h = this.$createElement;
window.__appNotification && window.__appNotification.close();
window.__appNotification = Notification({
@ -59,7 +44,7 @@ export default {
position: "bottom-right",
customClass: "common-app-down-notification",
onClose: () => {
this.repoStatus = 2;
this.status = 2;
},
message: h('span', [
h('span', [
@ -68,19 +53,19 @@ export default {
props: {
color: 'volcano'
}
}, data.tag_name)
}, this.releases.tag_name)
]),
h('MarkdownPreview', {
class: 'common-app-down-body',
props: {
initialValue: data.body
initialValue: this.releases.body
}
}),
h('div', {
class: 'common-app-down-link',
on: {
click: () => {
this.openExternal(data.html_url);
this.installApplication();
}
},
}, [
@ -92,20 +77,25 @@ export default {
marginRight: '5px'
}
}),
h('span', this.$L('立即升级'))
h('span', this.$L('立即安装'))
]),
])
});
}
},
deep: true
})
}
},
computed: {
repoTitle() {
return this.status == 2 ? '更新客户端' : '客户端下载';
}
},
methods: {
getReleases() {
let appdown = $A.getStorageJson("cacheAppdown");
if (appdown.time && appdown.time + 3600 > Math.round(new Date().getTime() / 1000)) {
this.chackReleases(appdown.data)
let cache = $A.getStorageJson("cacheAppdown");
if (cache.time && cache.time + 3600 > Math.round(new Date().getTime() / 1000)) {
this.releases = cache.data;
this.chackReleases()
return;
}
;(() => {
@ -117,23 +107,52 @@ export default {
time: Math.round(new Date().getTime() / 1000),
data: data
});
this.chackReleases(data)
this.releases = data;
this.chackReleases()
}
});
})();
},
chackReleases(data) {
let hostname = window.location.hostname;
if (hostname == '127.0.0.1') {
hostname = "www.dootask.com"
chackReleases() {
let hostName = window.location.hostname;
if (hostName == '127.0.0.1') {
hostName = "www.dootask.com"
}
let assets = data.assets || [];
let asset = assets.find(({browser_download_url}) => {
return $A.strExists(browser_download_url, hostname)
});
if (asset) {
this.repoData = data;
if (this.$Electron) {
//
let match = (window.navigator.userAgent + "").match(/\s+(Main|Sub)TaskWindow\/(.*?)\/(.*?)\//)
if (!match) {
return;
}
let artifactName = null;
if (match[2] === 'darwin') {
artifactName = `${hostName}-${this.releases.tag_name}-mac-${match[3]}.pkg`;
} else if (match[2] === 'win32') {
artifactName = `${hostName}-${this.releases.tag_name}-win-${match[3]}.exe`;
} else {
return;
}
this.repoData = (this.releases.assets || []).find(({name}) => name == artifactName);
if (!this.repoData) {
return;
}
let currentVersion = window.systemInformation.version;
let latestVersion = $A.leftDelete(this.releases.tag_name.toLowerCase(), "v")
if (this.compareVersion(latestVersion, currentVersion) === 1) {
//
console.log("有新版本");
this.$Electron.ipcRenderer.send('downloadURL', {
url: this.repoData.browser_download_url
});
}
} else {
//
this.repoData = (this.releases.assets || []).find(({name}) => $A.strExists(name, hostName));
if (this.repoData) {
console.log("有客户端");
this.status = 1;
}
}
},
@ -181,12 +200,14 @@ export default {
return 0;
},
openExternal(url) {
try {
this.$Electron.shell.openExternal(url);
} catch (e) {
window.location.href = url;
installApplication() {
if (!this.$Electron) {
return;
}
this.$Electron.ipcRenderer.send('openFile', {
path: this.downInfo.savePath
});
this.$Electron.ipcRenderer.send('windowQuit');
}
}
};

View File

@ -44,17 +44,14 @@
</div>
<div class="login-right-bottom">
<Button v-if="$Electron" icon="ios-globe-outline" type="primary" @click="onServerUrlInput">{{$L('自定义服务器')}}</Button>
<AppDown/>
</div>
</div>
</template>
<script>
import AppDown from "../components/AppDown";
import {mapState} from "vuex";
export default {
components: {AppDown},
data() {
return {
loadIng: 0,

View File

@ -70,18 +70,15 @@
</ul>
</template>
</div>
<AppDown/>
</div>
</template>
<script>
import {mapGetters, mapState} from "vuex";
import AppDown from "../../components/AppDown";
import {Store} from "le5le-store";
import TaskMenu from "./components/TaskMenu";
export default {
components: {TaskMenu, AppDown},
components: {TaskMenu},
data() {
return {
nowTime: $A.Time(),