exe自更新

This commit is contained in:
li-xiaochen 2024-11-30 20:54:48 +08:00
parent 1b4ce1966f
commit 719cb4bda2
3 changed files with 198 additions and 5 deletions

View file

@ -1,15 +1,22 @@
import json
import mimetypes
import platform
import sys
from pathlib import Path
from shutil import rmtree
from subprocess import PIPE, STDOUT, Popen
import requests
import webview
from log import logger
from python.Lib import shutil, os, subprocess
mimetypes.add_type("text/html", ".html")
mimetypes.add_type("text/css", ".css")
mimetypes.add_type("application/javascript", ".js")
version = "2024-11-28"
version = "V0.2"
config = {
"page": "init",
@ -18,6 +25,8 @@ config = {
}
config_path = Path("launcher.json")
get_new_version_url = "https://git.zhaozuohong.vip/api/v1/repos/mower-ng/launcher/releases/latest"
try:
with config_path.open("r") as f:
user_config = json.load(f)
@ -31,6 +40,41 @@ def custom_event(data):
js = f"var event = new CustomEvent('log', {{detail: {data}}}); window.dispatchEvent(event);"
window.evaluate_js(js)
def replace_restart(exename, new_exename):
script_name = "upgrade.sh" if platform.system() != "Windows" else "upgrade.bat"
script_path = os.path.join(os.getcwd(), script_name)
with open(script_path, 'w') as b:
if platform.system() == "Windows":
TempList = f"@echo off\n"
TempList += f"timeout /t 3 /nobreak\n" # 等待进程退出
TempList += f"del {exename}\n" # 删除旧程序
TempList += f"move {new_exename} {exename}\n" # 复制新版本程序
TempList += f"timeout /t 1 /nobreak\n" # 等待复制完成
TempList += f"start {exename}\n" # 启动新程序
TempList += f"exit"
else:
TempList = f"#!/bin/sh\n"
TempList += f"EXENAME={exename}\n"
TempList += f"NEW_EXENAME={new_exename}\n"
TempList += f"sleep 3\n"
TempList += f"rm \"$EXENAME\"\n"
TempList += f"mv \"$NEW_EXENAME\" \"$EXENAME\"\n"
TempList += f"sleep 1\n"
TempList += f"nohup ./{exename} &\n"
TempList += f"exit 0\n"
b.write(TempList)
if platform.system() != "Windows":
os.chmod(script_path, 0o755) # 设置脚本可执行权限
if platform.system() == "Windows":
# 不显示cmd窗口
subprocess.Popen([script_path], creationflags=subprocess.CREATE_NO_WINDOW)
else:
subprocess.Popen([script_path, exename, new_exename])
os._exit(0)
mirror_list = {
"pypi": "https://pypi.org/simple",
@ -72,9 +116,34 @@ class Api:
def set_mirror(self, mirror):
config["mirror"] = mirror
def update_self(self):
# 更新启动器本身
pass
def get_version(self):
return version
def get_new_version(self):
logger.info("获取最新版本号")
response = requests.get(get_new_version_url)
return response.json()
# 更新启动器本身
def update_self(self,download_url):
# 获取当前启动器的路径
current_path = sys.argv[0]
# 如果current_path是以py结尾,则将其转换为.exe
if current_path.endswith(".py"):
current_path = current_path[:-3] + ".exe"
temp_path = current_path + '.tmp'
logger.info(f"下载新版本: {download_url}")
response = requests.get(download_url, stream=True)
if response.status_code == 200:
with open(temp_path, 'wb') as file:
shutil.copyfileobj(response.raw, file)
else:
logger.error(f"下载新版本失败: {response.status_code}")
return f"下载新版本: {response.status_code}"
logger.info(f"替换旧版本,{temp_path} -> {current_path}")
replace_restart(current_path,temp_path)
def rm_site_packages(self):
site_packages_path = Path("./python/Lib/site-packages")
@ -111,8 +180,13 @@ class Api:
custom_event(str(e))
return "failed"
script_name = "upgrade.sh" if platform.system() != "Windows" else "upgrade.bat"
# 如果当前路径存在更新脚本,则删除
if Path(script_name).exists():
os.remove(script_name)
window = webview.create_window(f"mower-ng launcher {version}", "ui/dist/index.html", js_api=Api())
#window = webview.create_window(f"mower-ng launcher {version}", "http://localhost:5173/", js_api=Api())
webview.start()
with config_path.open("w") as f:

View file

@ -4,6 +4,7 @@ import Launch from '@/pages/Launch.vue'
import Update from '@/pages/Update.vue'
import Fix from '@/pages/Fix.vue'
import { dateZhCN, zhCN } from 'naive-ui'
import Settings from '@/pages/Settings.vue'
const loading = ref(true)
const page = ref(null)
@ -15,6 +16,14 @@ function load_config() {
})
}
async function init_version() {
version.value = await pywebview.api.get_version()
new_version.value = await pywebview.api.get_new_version()
if (new_version.value.tag_name > version.value) {
update_able.value = true
}
}
const log = ref('')
provide('log', log)
const log_ele = ref(null)
@ -28,8 +37,12 @@ watch(log, () => {
onMounted(() => {
if (window.pywebview && pywebview.api) {
load_config()
init_version()
} else {
window.addEventListener('pywebviewready', load_config)
window.addEventListener('pywebviewready', () => {
load_config()
init_version()
})
}
window.addEventListener('log', (e) => {
log.value += e.detail.log
@ -46,6 +59,15 @@ provide('running', running)
const steps = ref([])
provide('steps', steps)
const update_able = ref(false)
provide('update_able', update_able)
const version = ref('')
provide('version', version)
const new_version = ref({})
provide('new_version', new_version)
</script>
<template>
@ -65,6 +87,15 @@ provide('steps', steps)
<n-tab-pane :disabled="running" name="update" tab="更新代码"><update /></n-tab-pane>
<n-tab-pane :disabled="running" name="launch" tab="启动程序"><launch /></n-tab-pane>
<n-tab-pane :disabled="running" name="fix" tab="依赖修复"><fix /></n-tab-pane>
<n-tab-pane :disabled="running" name="settings">
<template #tab>
<n-space :wrap="false">
设置
<n-tag v-if="update_able" round type="success"></n-tag>
</n-space>
</template>
<settings />
</n-tab-pane>
</n-tabs>
</n-notification-provider>
<n-global-style />

88
ui/src/pages/Settings.vue Normal file
View file

@ -0,0 +1,88 @@
<script setup>
import { SyncCircle } from '@vicons/ionicons5'
const notification = useNotification()
const update_able = inject('update_able')
const running = inject('running')
const version = inject('version')
const new_version = inject('new_version')
const branch = ref(null)
const mirror = ref(null)
const check_running = ref(false)
const update_self_running = ref(false)
async function update_self() {
running.value = true
update_self_running.value = true
const response = await pywebview.api.update_self(new_version.value['assets'][0]['browser_download_url'])
notification['error']({
content: '错误',
meta: response,
duration: 3000
})
update_self_running.value = false
running.value = false
}
async function open_new_version_html() {
window.open(new_version.value['html_url'])
}
async function check_update() {
running.value = true
check_running.value = true
new_version.value = await pywebview.api.get_new_version()
if(new_version.value.tag_name > version.value){
update_able.value = true
notification['info']({
content: '提示',
meta: "有新版本可更新",
duration: 3000
})
}else{
update_able.value = false
notification['info']({
content: '提示',
meta: "当前已是最新版本",
duration: 3000
})
}
check_running.value = false
running.value = false
}
</script>
<template>
<n-flex vertical style="gap: 16px; height: 100%; padding: 16px; box-sizing: border-box">
<n-form label-placement="left" :show-feedback="false" label-width="auto" label-align="left">
<n-form-item label="版本">
<n-space align="center">
{{version}}
<n-button type="success" :loading="check_running" :disabled="running" @click="check_update">
<template #icon>
<n-icon :component="SyncCircle"></n-icon>
</template>
检查更新
</n-button>
</n-space>
</n-form-item>
<n-alert
style="margin: 8px 0"
type="success"
v-if="update_able"
>
<template #header>
最新版本{{new_version.name}}
<n-button style="float: right" @click="open_new_version_html">了解此版本</n-button>
</template>
<n-space>
<n-button type="success" :loading="update_self_running" :disabled="running" @click="update_self">
立即更新
</n-button>
</n-space>
</n-alert>
</n-form>
</n-flex>
</template>