修改打包方式为onedir,自更新 #15

Merged
li-xiaochen merged 5 commits from dev into main 2024-12-01 20:09:23 +08:00
6 changed files with 227 additions and 32 deletions

8
.gitignore vendored
View file

@ -162,4 +162,10 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear # and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/ .idea/
launcher.iml
# Mower-ng
git/
python/
mower-ng/

View file

@ -9,5 +9,11 @@
前端运行 `npm run build` 生成 `ui/dist`,之后安装 PyInstaller,运行 前端运行 `npm run build` 生成 `ui/dist`,之后安装 PyInstaller,运行
```bash ```bash
pyinstaller -w -F --add-data ui/dist:ui/dist launcher.py pyinstaller -w --add-data ui/dist:ui/dist launcher.py
```
在dist文件夹生成launcher文件夹,切到dist文件夹下运行
```bash
tar -cf launcher.tar launcher
``` ```

View file

@ -3,13 +3,18 @@ import mimetypes
from pathlib import Path from pathlib import Path
from shutil import rmtree from shutil import rmtree
from subprocess import PIPE, STDOUT, Popen from subprocess import PIPE, STDOUT, Popen
import requests
import webview import webview
from log import logger
from python.Lib import shutil, os, subprocess
mimetypes.add_type("text/html", ".html") mimetypes.add_type("text/html", ".html")
mimetypes.add_type("text/css", ".css") mimetypes.add_type("text/css", ".css")
mimetypes.add_type("application/javascript", ".js") mimetypes.add_type("application/javascript", ".js")
version = "2024-11-28" version = "v0.2"
config = { config = {
"page": "init", "page": "init",
@ -18,6 +23,10 @@ config = {
} }
config_path = Path("launcher.json") config_path = Path("launcher.json")
get_new_version_url = "https://git.zhaozuohong.vip/api/v1/repos/mower-ng/launcher/releases/latest"
upgrade_script_name = "upgrade.bat"
try: try:
with config_path.open("r") as f: with config_path.open("r") as f:
user_config = json.load(f) user_config = json.load(f)
@ -39,7 +48,6 @@ mirror_list = {
"sjtu": "https://mirror.sjtu.edu.cn/pypi/web/simple", "sjtu": "https://mirror.sjtu.edu.cn/pypi/web/simple",
} }
command_list = { command_list = {
"lfs": "git\\bin\\git lfs install", "lfs": "git\\bin\\git lfs install",
"ensurepip": "python\\python -m ensurepip --default-pip", "ensurepip": "python\\python -m ensurepip --default-pip",
@ -72,9 +80,45 @@ class Api:
def set_mirror(self, mirror): def set_mirror(self, mirror):
config["mirror"] = mirror config["mirror"] = mirror
def update_self(self): def get_version(self):
# 更新启动器本身 return version
pass
def get_new_version(self):
logger.info("获取最新版本号")
response = requests.get(get_new_version_url)
return response.json()
# 更新启动器本身
def update_self(self, download_url):
# 下载压缩包的全路径
download_path = os.path.join(os.getcwd(), os.path.basename(download_url))
logger.info(f"下载新版本: {download_url}{download_path}")
response = requests.get(download_url, stream=True)
if response.status_code == 200:
with open(download_path, 'wb') as file:
shutil.copyfileobj(response.raw, file)
logger.info("下载完成")
else:
logger.error(f"下载新版本失败: {response.status_code}")
return f"下载新版本失败: {response.status_code}"
script_path = os.path.join(os.getcwd(), upgrade_script_name)
folder_path = os.path.join(os.getcwd(), "_internal")
exe_path = os.path.join(os.getcwd(), "launcher.exe")
with open(script_path, 'w') as b:
TempList = f"@echo off\n"
TempList += f"timeout /t 3 /nobreak\n" # 等待进程退出
TempList += f"rmdir {folder_path}\n" # 删除_internal
TempList += f"del {exe_path}\n" # 删除exe
TempList += f"tar -xf {download_path} -C ..\n" # 解压压缩包
TempList += f"timeout /t 1 /nobreak\n" # 等待解压
TempList += f"start {exe_path}\n" # 启动新程序
TempList += f"del {download_path}\n" # 删除压缩包
TempList += f"exit"
b.write(TempList)
# 不显示cmd窗口
subprocess.Popen([script_path], creationflags=subprocess.CREATE_NO_WINDOW)
os._exit(0)
def rm_site_packages(self): def rm_site_packages(self):
site_packages_path = Path("./python/Lib/site-packages") site_packages_path = Path("./python/Lib/site-packages")
@ -97,7 +141,7 @@ class Api:
custom_event(command + "\n") custom_event(command + "\n")
try: try:
with Popen( with Popen(
command, stdout=PIPE, stderr=STDOUT, shell=True, cwd=cwd, bufsize=0 command, stdout=PIPE, stderr=STDOUT, shell=True, cwd=cwd, bufsize=0
) as p: ) as p:
for data in p.stdout: for data in p.stdout:
try: try:
@ -112,7 +156,12 @@ class Api:
return "failed" return "failed"
# 如果当前路径存在更新脚本,则删除
if Path(upgrade_script_name).exists():
os.remove(upgrade_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}", "ui/dist/index.html", js_api=Api())
# window = webview.create_window(f"mower-ng launcher {version}", "http://localhost:5173/", js_api=Api())
webview.start() webview.start()
with config_path.open("w") as f: 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 Update from '@/pages/Update.vue'
import Fix from '@/pages/Fix.vue' import Fix from '@/pages/Fix.vue'
import { dateZhCN, zhCN } from 'naive-ui' import { dateZhCN, zhCN } from 'naive-ui'
import Settings from '@/pages/Settings.vue'
const loading = ref(true) const loading = ref(true)
const page = ref(null) 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('') const log = ref('')
provide('log', log) provide('log', log)
const log_ele = ref(null) const log_ele = ref(null)
@ -28,8 +37,12 @@ watch(log, () => {
onMounted(() => { onMounted(() => {
if (window.pywebview && pywebview.api) { if (window.pywebview && pywebview.api) {
load_config() load_config()
init_version()
} else { } else {
window.addEventListener('pywebviewready', load_config) window.addEventListener('pywebviewready', () => {
load_config()
init_version()
})
} }
window.addEventListener('log', (e) => { window.addEventListener('log', (e) => {
log.value += e.detail.log log.value += e.detail.log
@ -46,6 +59,15 @@ provide('running', running)
const steps = ref([]) const steps = ref([])
provide('steps', steps) 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> </script>
<template> <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="update" tab="更新代码"><update /></n-tab-pane>
<n-tab-pane :disabled="running" name="launch" tab="启动程序"><launch /></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="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-tabs>
</n-notification-provider> </n-notification-provider>
<n-global-style /> <n-global-style />

View file

@ -3,39 +3,48 @@ const running = inject('running')
const notification = useNotification() const notification = useNotification()
async function rm_site_packages_and_python_scripts() { async function rm_site_packages_and_python_scripts() {
running.value = true running.value = true
notification['info']({ notification['info']({
content: '提示', content: '提示',
meta: '开始移除site-packages和python/Script目录', meta: '开始移除site-packages和python/Script目录',
duration: 3000 duration: 3000
}) })
const response = await pywebview.api.rm_site_packages() const response = await pywebview.api.rm_site_packages()
notification['info']({ notification['info']({
content: '提示', content: '提示',
meta: response, meta: response,
duration: 3000 duration: 3000
}) })
const response2 = await pywebview.api.rm_python_scripts() const response2 = await pywebview.api.rm_python_scripts()
notification['info']({ notification['info']({
content: '提示', content: '提示',
meta: response2, meta: response2,
duration: 3000 duration: 3000
}) })
running.value = false running.value = false
} }
</script> </script>
<template> <template>
<n-flex vertical style=" <n-flex
vertical
style="
gap: 16px; gap: 16px;
height: 100%; height: 100%;
padding: 16px; padding: 16px;
box-sizing: border-box; box-sizing: border-box;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
"> "
<n-button class="fix-btn" type="error" secondary size="large" @click="rm_site_packages_and_python_scripts"> >
移除 site-packages python/Scripts 目录 <n-button
class="fix-btn"
type="error"
secondary
size="large"
@click="rm_site_packages_and_python_scripts"
>
移除 site-packages python/Scripts 目录
</n-button> </n-button>
</n-flex> </n-flex>
</template> </template>

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

@ -0,0 +1,94 @@
<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 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.tag_name} ${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>