add update
This commit is contained in:
parent
86e49a26aa
commit
382fca2364
19 changed files with 346 additions and 248 deletions
|
|
@ -14,7 +14,6 @@ import (
|
||||||
// App struct
|
// App struct
|
||||||
type App struct {
|
type App struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
exDir string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewApp creates a new App application struct
|
// NewApp creates a new App application struct
|
||||||
|
|
|
||||||
102
app/update.go
102
app/update.go
|
|
@ -1,102 +0,0 @@
|
||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/zip"
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/minio/selfupdate"
|
|
||||||
wruntime "github.com/wailsapp/wails/v2/pkg/runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ProgressReader struct {
|
|
||||||
reader io.Reader
|
|
||||||
total int64
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
type DownloadStatus struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Path string `json:"path"`
|
|
||||||
Url string `json:"url"`
|
|
||||||
Transferred int64 `json:"transferred"`
|
|
||||||
Size int64 `json:"size"`
|
|
||||||
Speed float64 `json:"speed"`
|
|
||||||
Progress float64 `json:"progress"`
|
|
||||||
Downloading bool `json:"downloading"`
|
|
||||||
Done bool `json:"done"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pr *ProgressReader) Read(p []byte) (n int, err error) {
|
|
||||||
n, err = pr.reader.Read(p)
|
|
||||||
pr.err = err
|
|
||||||
pr.total += int64(n)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
func (a *App) UpdateApp(url string) (broken bool, err error) {
|
|
||||||
resp, err := http.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
pr := &ProgressReader{reader: resp.Body}
|
|
||||||
|
|
||||||
ticker := time.NewTicker(250 * time.Millisecond)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
// update progress
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
<-ticker.C
|
|
||||||
wruntime.EventsEmit(a.ctx, "updateApp", &DownloadStatus{
|
|
||||||
Name: filepath.Base(url),
|
|
||||||
Path: "",
|
|
||||||
Url: url,
|
|
||||||
Transferred: pr.total,
|
|
||||||
Size: resp.ContentLength,
|
|
||||||
Speed: 0,
|
|
||||||
Progress: 100 * (float64(pr.total) / float64(resp.ContentLength)),
|
|
||||||
Downloading: pr.err == nil && pr.total < resp.ContentLength,
|
|
||||||
Done: pr.total == resp.ContentLength,
|
|
||||||
})
|
|
||||||
if pr.err != nil || pr.total == resp.ContentLength {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
var updateFile io.Reader = pr
|
|
||||||
// extract macos binary from zip
|
|
||||||
if strings.HasSuffix(url, ".zip") && runtime.GOOS == "darwin" {
|
|
||||||
zipBytes, err := io.ReadAll(pr)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
archive, err := zip.NewReader(bytes.NewReader(zipBytes), int64(len(zipBytes)))
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
file, err := archive.Open("godoos.app/Contents/MacOS/godoos")
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
updateFile = file
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply update
|
|
||||||
err = selfupdate.Apply(updateFile, selfupdate.Options{})
|
|
||||||
if err != nil {
|
|
||||||
if rerr := selfupdate.RollbackError(err); rerr != nil {
|
|
||||||
return true, rerr
|
|
||||||
}
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
// restart app
|
|
||||||
a.RestartApp()
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-item" v-if="config.storeType === 'local'">
|
<div class="setting-item" v-if="config.storeType === 'local'">
|
||||||
<label>存储地址</label>
|
<label>存储地址</label>
|
||||||
<el-input v-model="config.storePath" @click="selectFile" />
|
<el-input v-model="config.storePath" @click="selectFile()" />
|
||||||
</div>
|
</div>
|
||||||
<template v-if="config.storeType === 'net'">
|
<template v-if="config.storeType === 'net'">
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ export const getSystemConfig = (ifset = false) => {
|
||||||
// 初始化配置对象的各项属性,若本地存储中已存在则不进行覆盖
|
// 初始化配置对象的各项属性,若本地存储中已存在则不进行覆盖
|
||||||
if (!config.version) {
|
if (!config.version) {
|
||||||
config.version = '1.0.0';
|
config.version = '1.0.0';
|
||||||
|
//config.version = '0.0.9';
|
||||||
}
|
}
|
||||||
if (!config.isFirstRun) {
|
if (!config.isFirstRun) {
|
||||||
config.isFirstRun = false;
|
config.isFirstRun = false;
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,8 @@ import { Tray, TrayOptions } from './menu/Tary';
|
||||||
import { InitSystemFile, InitUserFile } from './core/SystemFileConfig';
|
import { InitSystemFile, InitUserFile } from './core/SystemFileConfig';
|
||||||
import { createInitFile } from './core/createInitFile';
|
import { createInitFile } from './core/createInitFile';
|
||||||
import { getSystemConfig, getSystemKey, setSystemKey, setSystemConfig, clearSystemConfig } from './config'
|
import { getSystemConfig, getSystemKey, setSystemKey, setSystemConfig, clearSystemConfig } from './config'
|
||||||
|
import { checkUpdate } from '@/util/update';
|
||||||
|
import { RestartApp } from '@/util/goutil';
|
||||||
|
|
||||||
export type OsPlugin = (system: System) => void;
|
export type OsPlugin = (system: System) => void;
|
||||||
export type FileOpener = {
|
export type FileOpener = {
|
||||||
|
|
@ -71,8 +72,6 @@ export class System {
|
||||||
this._eventer = this.initEvent();
|
this._eventer = this.initEvent();
|
||||||
this.firstRun();
|
this.firstRun();
|
||||||
this.initSystem();
|
this.initSystem();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -112,11 +111,17 @@ export class System {
|
||||||
this.initBackground(); // 初始化壁纸
|
this.initBackground(); // 初始化壁纸
|
||||||
this.emit('start');
|
this.emit('start');
|
||||||
|
|
||||||
// setTimeout(() => {
|
setTimeout(() => {
|
||||||
// if (this._rootState.magnet?.length < 1) {
|
if (this._rootState.magnet?.length < 1) {
|
||||||
// this.refershAppList()
|
this.refershAppList()
|
||||||
// }
|
}
|
||||||
// }, 6000);
|
checkUpdate();
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this._rootState.magnet?.length < 1) {
|
||||||
|
this.recover()
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
}, 6000);
|
||||||
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|
@ -382,7 +387,7 @@ export class System {
|
||||||
}
|
}
|
||||||
reboot() {
|
reboot() {
|
||||||
this._rootState.state = SystemStateEnum.close;
|
this._rootState.state = SystemStateEnum.close;
|
||||||
window.location.reload();
|
RestartApp();
|
||||||
}
|
}
|
||||||
recover() {
|
recover() {
|
||||||
clearSystemConfig()
|
clearSystemConfig()
|
||||||
|
|
@ -390,7 +395,7 @@ export class System {
|
||||||
this.fs.removeFileSystem().then(() => {
|
this.fs.removeFileSystem().then(() => {
|
||||||
window.indexedDB.deleteDatabase("GodoDatabase");
|
window.indexedDB.deleteDatabase("GodoDatabase");
|
||||||
|
|
||||||
window.location.reload();
|
RestartApp();
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ class Dialog {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
setProgress,
|
setProgress,
|
||||||
|
dialogwin
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
public static showMessageBox(option: {
|
public static showMessageBox(option: {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,16 @@
|
||||||
export async function OpenDirDialog(){
|
export async function OpenDirDialog(){
|
||||||
if((window as any).go) {
|
if((window as any).go) {
|
||||||
//(window as any).go.OpenDirDialog();
|
return (window as any)['go']['app']['App']['OpenDirDialog']();
|
||||||
return (window as any)['go']['main']['App']['OpenDirDialog']();
|
|
||||||
}else {
|
}else {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function RestartApp(){
|
||||||
|
if(!(window as any).go){
|
||||||
|
window.location.reload();
|
||||||
|
}else{
|
||||||
|
return (window as any)['go']['app']['App']['RestartApp']();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,114 +1,62 @@
|
||||||
//import { EventsOff, EventsOn } from '~/runtime';
|
import { getSystemConfig,setSystemKey,parseJson } from '@/system/config'
|
||||||
//import manifest from '../../package.json';
|
import { RestartApp } from './goutil';
|
||||||
import {isWindowsOS,getSystemConfig} from '@/system/config'
|
import { Dialog } from '@/system';
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
export async function checkUpdate() {
|
export async function checkUpdate() {
|
||||||
if(!(window as any).go) return;
|
|
||||||
const config = getSystemConfig();
|
const config = getSystemConfig();
|
||||||
const updateGiteeUrl = `https://gitee.com/api/v5/repos/ruitao_admin/godoos/releases/`
|
const updateGiteeUrl = `${config.apiUrl}/system/updateInfo`
|
||||||
const releaseRes = await fetch(updateGiteeUrl)
|
const releaseRes = await fetch(updateGiteeUrl)
|
||||||
if(!releaseRes.ok) return;
|
if (!releaseRes.ok) return;
|
||||||
const releaseData = await releaseRes.json()
|
const releaseData = await releaseRes.json()
|
||||||
const versionTag = releaseData.tag_name;
|
const versionTag = releaseData.version;
|
||||||
if(!versionTag) return;
|
if (!versionTag) return;
|
||||||
if (versionTag.replace('v', '') <= config.version) return;
|
if (versionTag <= config.version) return;
|
||||||
const verifyUrl = `${updateGiteeUrl}tags/${versionTag}`;
|
const updateUrl = releaseData.url
|
||||||
const verRes = await fetch(verifyUrl);
|
if (!updateUrl || updateUrl == '') return;
|
||||||
if(!verRes.ok) return;
|
const dialogRes: any = await Dialog.showMessageBox({
|
||||||
const verData = await verRes.json()
|
title: '更新提示',
|
||||||
if(!verData.assets || verData.assets.length <= 0) return;
|
message: `发现新版本:${versionTag},是否更新?`
|
||||||
const appName = "godoos"+ versionTag + (isWindowsOS() ? '.exe' : '');
|
})
|
||||||
const updateUrl = `${updateGiteeUrl}download/${versionTag}/${appName}`;
|
//console.log(dialogRes)
|
||||||
console.log(updateUrl)
|
if (dialogRes.response !== -1) {
|
||||||
// fetch(`${updateGiteeUrl}latest`).then((r) => {
|
return;
|
||||||
// if (r.ok) {
|
|
||||||
// r.json().then((data) => {
|
|
||||||
// if (data.tag_name) {
|
|
||||||
// const versionTag = data.tag_name;
|
|
||||||
// console.log(versionTag)
|
|
||||||
// if (versionTag.replace('v', '') > manifest.version) {
|
|
||||||
// const verifyUrl = `${updateGiteeUrl}tags/${versionTag}`;
|
|
||||||
// }
|
|
||||||
/*
|
|
||||||
if (versionTag.replace('v', '') > manifest.version) {
|
|
||||||
const verifyUrl = `${updateGiteeUrl}tags/${versionTag}`;
|
|
||||||
|
|
||||||
fetch(verifyUrl).then((r) => {
|
|
||||||
if (r.ok) {
|
|
||||||
r.json().then((data) => {
|
|
||||||
if (data.assets && data.assets.length > 0) {
|
|
||||||
const asset = data.assets.find((a: any) => a.name.toLowerCase().includes(commonStore.platform.toLowerCase().replace('darwin', 'macos')));
|
|
||||||
if (asset) {
|
|
||||||
const updateUrl = `${updateGiteeUrl}download/${versionTag}/${asset.name}`;
|
|
||||||
toastWithButton(t('New Version Available') + ': ' + versionTag, t('Update'), () => {
|
|
||||||
DeleteFile('cache.json');
|
|
||||||
const progressId = 'update_app';
|
|
||||||
const progressEvent = 'updateApp';
|
|
||||||
const updateProgress = (ds: DownloadStatus | null) => {
|
|
||||||
const content =
|
|
||||||
t('Downloading update, please wait. If it is not completed, please manually download the program from GitHub and replace the original program.')
|
|
||||||
+ (ds ? ` (${ds.progress.toFixed(2)}% ${bytesToReadable(ds.transferred)}/${bytesToReadable(ds.size)})` : '');
|
|
||||||
const options: ToastOptions = {
|
|
||||||
type: 'info',
|
|
||||||
position: 'bottom-left',
|
|
||||||
autoClose: false,
|
|
||||||
toastId: progressId,
|
|
||||||
hideProgressBar: false,
|
|
||||||
progress: ds ? ds.progress / 100 : 0
|
|
||||||
};
|
|
||||||
if (toast.isActive(progressId))
|
|
||||||
toast.update(progressId, {
|
|
||||||
render: content,
|
|
||||||
...options
|
|
||||||
});
|
|
||||||
else
|
|
||||||
toast(content, options);
|
|
||||||
};
|
|
||||||
updateProgress(null);
|
|
||||||
EventsOn(progressEvent, updateProgress);
|
|
||||||
UpdateApp(updateUrl).then(() => {
|
|
||||||
toast(t('Update completed, please restart the program.'), {
|
|
||||||
type: 'success',
|
|
||||||
position: 'bottom-left',
|
|
||||||
autoClose: false
|
|
||||||
}
|
}
|
||||||
);
|
const { setProgress,dialogwin } = Dialog.showProcessDialog({
|
||||||
}).catch((e) => {
|
message: '正在更新',
|
||||||
toast(t('Update Error') + ' - ' + (e.message || e), {
|
});
|
||||||
|
const upUrl = `${config.apiUrl}/system/update?url=${updateUrl}`
|
||||||
|
const upRes = await fetch(upUrl)
|
||||||
|
if (!upRes.ok) return;
|
||||||
|
const reader: any = upRes.body?.getReader();
|
||||||
|
if (!reader) {
|
||||||
|
ElMessage({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
position: 'bottom-left',
|
message: "the system has not stream!"
|
||||||
autoClose: false
|
})
|
||||||
});
|
}
|
||||||
}).finally(() => {
|
while (true) {
|
||||||
toast.dismiss(progressId);
|
const { done, value } = await reader.read();
|
||||||
EventsOff(progressEvent);
|
if (done) {
|
||||||
});
|
reader.releaseLock();
|
||||||
}, {
|
break;
|
||||||
autoClose: false,
|
}
|
||||||
position: 'bottom-left'
|
const rawjson = new TextDecoder().decode(value);
|
||||||
});
|
const json = parseJson(rawjson);
|
||||||
|
console.log(json)
|
||||||
|
if(json){
|
||||||
|
if(json.progress){
|
||||||
|
setProgress(json.progress)
|
||||||
|
}
|
||||||
|
if(json.updateCompleted){
|
||||||
|
dialogwin.close()
|
||||||
|
ElMessage({
|
||||||
|
type: 'success',
|
||||||
|
message: "update completed!"
|
||||||
|
})
|
||||||
|
setSystemKey('version',versionTag)
|
||||||
|
RestartApp()
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw new Error('Verify response was not ok.');
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
} else {
|
|
||||||
if (notifyEvenLatest) {
|
|
||||||
toast(t('This is the latest version'), { type: 'success', position: 'bottom-left', autoClose: 2000 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
// } else {
|
|
||||||
// throw new Error('Invalid response.');
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// } else {
|
|
||||||
// throw new Error('Network response was not ok.');
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// ).catch((e) => {
|
|
||||||
// //toast(t('Updates Check Error') + ' - ' + (e.message || e), { type: 'error', position: 'bottom-left' });
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
||||||
2
frontend/wailsjs/go/app/App.d.ts
vendored
2
frontend/wailsjs/go/app/App.d.ts
vendored
|
|
@ -4,5 +4,3 @@
|
||||||
export function OpenDirDialog():Promise<string>;
|
export function OpenDirDialog():Promise<string>;
|
||||||
|
|
||||||
export function RestartApp():Promise<void>;
|
export function RestartApp():Promise<void>;
|
||||||
|
|
||||||
export function UpdateApp(arg1:string):Promise<boolean>;
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,3 @@ export function OpenDirDialog() {
|
||||||
export function RestartApp() {
|
export function RestartApp() {
|
||||||
return window['go']['app']['App']['RestartApp']();
|
return window['go']['app']['App']['RestartApp']();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function UpdateApp(arg1) {
|
|
||||||
return window['go']['app']['App']['UpdateApp'](arg1);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
6
go.mod
6
go.mod
|
|
@ -2,15 +2,13 @@ module godoos
|
||||||
|
|
||||||
go 1.22.5
|
go 1.22.5
|
||||||
|
|
||||||
require (
|
require github.com/wailsapp/wails/v2 v2.9.1
|
||||||
github.com/minio/selfupdate v0.6.0
|
|
||||||
github.com/wailsapp/wails/v2 v2.9.1
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
aead.dev/minisign v0.2.0 // indirect
|
aead.dev/minisign v0.2.0 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
github.com/gorilla/mux v1.8.1 // indirect
|
github.com/gorilla/mux v1.8.1 // indirect
|
||||||
|
github.com/minio/selfupdate v0.6.0 // indirect
|
||||||
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
|
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||||
github.com/tklauser/numcpus v0.8.0 // indirect
|
github.com/tklauser/numcpus v0.8.0 // indirect
|
||||||
|
|
|
||||||
2
godo/.gitignore
vendored
Normal file
2
godo/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
tmp
|
||||||
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"godo/libs"
|
"godo/libs"
|
||||||
"godo/localchat"
|
"godo/localchat"
|
||||||
"godo/progress"
|
"godo/progress"
|
||||||
|
"godo/sys"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -38,8 +39,11 @@ func OsStart() {
|
||||||
progressRouter.HandleFunc("/app/{name}/{subpath:.*}", progress.ForwardRequest).Methods(http.MethodGet, http.MethodPost)
|
progressRouter.HandleFunc("/app/{name}/{subpath:.*}", progress.ForwardRequest).Methods(http.MethodGet, http.MethodPost)
|
||||||
router.HandleFunc("/ping", progress.Ping).Methods(http.MethodGet)
|
router.HandleFunc("/ping", progress.Ping).Methods(http.MethodGet)
|
||||||
router.HandleFunc("/", progress.Ping).Methods(http.MethodGet)
|
router.HandleFunc("/", progress.Ping).Methods(http.MethodGet)
|
||||||
router.HandleFunc("/system/info", files.HandleSystemInfo).Methods(http.MethodGet)
|
router.HandleFunc("/system/updateInfo", sys.GetUpdateUrlHandler).Methods(http.MethodGet)
|
||||||
router.HandleFunc("/system/setting", HandleSetConfig).Methods(http.MethodPost)
|
router.HandleFunc("/system/update", sys.UpdateAppHandler).Methods(http.MethodGet)
|
||||||
|
router.HandleFunc("/system/storeList", sys.GetStoreInfoHandler).Methods(http.MethodGet)
|
||||||
|
router.HandleFunc("/system/setting", sys.HandleSetConfig).Methods(http.MethodPost)
|
||||||
|
router.HandleFunc("/files/info", files.HandleSystemInfo).Methods(http.MethodGet)
|
||||||
router.HandleFunc("/file/read", files.HandleReadDir).Methods(http.MethodGet)
|
router.HandleFunc("/file/read", files.HandleReadDir).Methods(http.MethodGet)
|
||||||
router.HandleFunc("/file/stat", files.HandleStat).Methods(http.MethodGet)
|
router.HandleFunc("/file/stat", files.HandleStat).Methods(http.MethodGet)
|
||||||
router.HandleFunc("/file/chmod", files.HandleChmod).Methods(http.MethodPost)
|
router.HandleFunc("/file/chmod", files.HandleChmod).Methods(http.MethodPost)
|
||||||
|
|
|
||||||
|
|
@ -73,10 +73,10 @@ func corsMiddleware() mux.MiddlewareFunc {
|
||||||
w.Header().Set("Access-Control-Allow-Headers", allowHeaders)
|
w.Header().Set("Access-Control-Allow-Headers", allowHeaders)
|
||||||
|
|
||||||
// 如果是预检请求(OPTIONS),直接返回 200 OK
|
// 如果是预检请求(OPTIONS),直接返回 200 OK
|
||||||
// if r.Method == http.MethodOptions {
|
if r.Method == http.MethodOptions {
|
||||||
// w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
// return
|
return
|
||||||
// }
|
}
|
||||||
|
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,21 @@ module godo
|
||||||
go 1.22.5
|
go 1.22.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/cavaliergopher/grab/v3 v3.0.1
|
||||||
github.com/fsnotify/fsnotify v1.7.0
|
github.com/fsnotify/fsnotify v1.7.0
|
||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
|
github.com/minio/selfupdate v0.6.0
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/cavaliergopher/grab/v3 v3.0.1 // indirect
|
aead.dev/minisign v0.2.0 // indirect
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
github.com/stretchr/testify v1.9.0 // indirect
|
github.com/stretchr/testify v1.9.0 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||||
github.com/tklauser/numcpus v0.8.0 // indirect
|
github.com/tklauser/numcpus v0.8.0 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
|
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b // indirect
|
||||||
golang.org/x/sys v0.19.0 // indirect
|
golang.org/x/sys v0.19.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
21
godo/go.sum
21
godo/go.sum
|
|
@ -1,3 +1,5 @@
|
||||||
|
aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk=
|
||||||
|
aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ=
|
||||||
github.com/cavaliergopher/grab/v3 v3.0.1 h1:4z7TkBfmPjmLAAmkkAZNX/6QJ1nNFdv3SdIHXju0Fr4=
|
github.com/cavaliergopher/grab/v3 v3.0.1 h1:4z7TkBfmPjmLAAmkkAZNX/6QJ1nNFdv3SdIHXju0Fr4=
|
||||||
github.com/cavaliergopher/grab/v3 v3.0.1/go.mod h1:1U/KNnD+Ft6JJiYoYBAimKH2XrYptb8Kl3DFGmsjpq4=
|
github.com/cavaliergopher/grab/v3 v3.0.1/go.mod h1:1U/KNnD+Ft6JJiYoYBAimKH2XrYptb8Kl3DFGmsjpq4=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
|
@ -8,6 +10,8 @@ github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||||
|
github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU=
|
||||||
|
github.com/minio/selfupdate v0.6.0/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
|
@ -22,8 +26,25 @@ github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYg
|
||||||
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
|
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
|
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b h1:QAqMVf3pSa6eeTsuklijukjXBlj7Es2QQplab+/RbQ4=
|
||||||
|
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package cmd
|
package sys
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
41
godo/sys/store.go
Normal file
41
godo/sys/store.go
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
package sys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"godo/libs"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetStoreInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
cate := r.URL.Query().Get("cate")
|
||||||
|
os := runtime.GOOS
|
||||||
|
arch := runtime.GOARCH
|
||||||
|
if cate == "" {
|
||||||
|
libs.ErrorMsg(w, "cate is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pluginUrl := "https://gitee.com/ruitao_admin/godoos-image/raw/master/store/" + os + "/" + arch + "/" + cate + ".json"
|
||||||
|
res, err := http.Get(pluginUrl)
|
||||||
|
if err != nil {
|
||||||
|
libs.ErrorMsg(w, err.Error())
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
if res.StatusCode == 200 {
|
||||||
|
body, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
libs.ErrorMsg(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var info interface{}
|
||||||
|
err = json.Unmarshal(body, &info)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error unmarshalling JSON:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(info)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
175
godo/sys/update.go
Normal file
175
godo/sys/update.go
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
package sys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"godo/libs"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/selfupdate"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OSInfo struct {
|
||||||
|
Amd64 string `json:"amd64"`
|
||||||
|
Arm64 string `json:"arm64"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VersionInfo struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Changelog string `json:"changelog"`
|
||||||
|
Windows OSInfo `json:"windows"`
|
||||||
|
Linux OSInfo `json:"linux"`
|
||||||
|
Darwin OSInfo `json:"darwin"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProgressReader struct {
|
||||||
|
reader io.Reader
|
||||||
|
total int64
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
type DownloadStatus struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
Current int64 `json:"current"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
Speed float64 `json:"speed"`
|
||||||
|
Progress float64 `json:"progress"`
|
||||||
|
Downloading bool `json:"downloading"`
|
||||||
|
Done bool `json:"done"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pr *ProgressReader) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = pr.reader.Read(p)
|
||||||
|
pr.err = err
|
||||||
|
pr.total += int64(n)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func UpdateAppHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
url := r.URL.Query().Get("url")
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
pr := &ProgressReader{reader: resp.Body}
|
||||||
|
|
||||||
|
ticker := time.NewTicker(250 * time.Millisecond)
|
||||||
|
defer ticker.Stop()
|
||||||
|
flusher, ok := w.(http.Flusher)
|
||||||
|
if !ok {
|
||||||
|
log.Printf("Streaming unsupported")
|
||||||
|
http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// update progress
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
<-ticker.C
|
||||||
|
rp := &DownloadStatus{
|
||||||
|
Name: filepath.Base(url),
|
||||||
|
Path: "",
|
||||||
|
Url: url,
|
||||||
|
Current: pr.total,
|
||||||
|
Size: resp.ContentLength,
|
||||||
|
Speed: 0,
|
||||||
|
Progress: 100 * (float64(pr.total) / float64(resp.ContentLength)),
|
||||||
|
Downloading: pr.err == nil && pr.total < resp.ContentLength,
|
||||||
|
Done: pr.total == resp.ContentLength,
|
||||||
|
}
|
||||||
|
if pr.err != nil || pr.total == resp.ContentLength {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if w != nil {
|
||||||
|
jsonBytes, err := json.Marshal(rp)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error marshaling FileProgress to JSON: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
io.WriteString(w, string(jsonBytes))
|
||||||
|
w.Write([]byte("\n"))
|
||||||
|
flusher.Flush()
|
||||||
|
} else {
|
||||||
|
log.Println("ResponseWriter is nil, cannot send progress")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var updateFile io.Reader = pr
|
||||||
|
// apply update
|
||||||
|
err = selfupdate.Apply(updateFile, selfupdate.Options{})
|
||||||
|
if err != nil {
|
||||||
|
if rerr := selfupdate.RollbackError(err); rerr != nil {
|
||||||
|
http.Error(w, "update error:"+rerr.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 更新完成后发送响应给前端
|
||||||
|
json.NewEncoder(w).Encode(map[string]bool{"updateCompleted": true})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUpdateUrlHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
updateUrl := "https://gitee.com/ruitao_admin/godoos-image/raw/master/version/version.json"
|
||||||
|
res, err := http.Get(updateUrl)
|
||||||
|
if err != nil {
|
||||||
|
libs.ErrorMsg(w, err.Error())
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
if res.StatusCode == 200 {
|
||||||
|
body, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
libs.ErrorMsg(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var info VersionInfo
|
||||||
|
err = json.Unmarshal(body, &info)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error unmarshalling JSON:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//log.Printf("info: %v", info)
|
||||||
|
// 根据操作系统和架构获取路径
|
||||||
|
path := getPathForOSAndArch(&info)
|
||||||
|
// 将结果以 JSON 格式返回给前端
|
||||||
|
response := map[string]string{"url": path, "version": info.Version}
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据操作系统和架构获取路径
|
||||||
|
func getPathForOSAndArch(info *VersionInfo) string {
|
||||||
|
os := runtime.GOOS
|
||||||
|
arch := runtime.GOARCH
|
||||||
|
switch os {
|
||||||
|
case "windows":
|
||||||
|
if arch == "amd64" {
|
||||||
|
return info.Windows.Amd64
|
||||||
|
} else if arch == "arm64" {
|
||||||
|
return info.Windows.Arm64
|
||||||
|
}
|
||||||
|
case "linux":
|
||||||
|
if arch == "amd64" {
|
||||||
|
return info.Linux.Amd64
|
||||||
|
} else if arch == "arm64" {
|
||||||
|
return info.Linux.Arm64
|
||||||
|
}
|
||||||
|
case "darwin":
|
||||||
|
if arch == "amd64" {
|
||||||
|
return info.Darwin.Amd64
|
||||||
|
} else if arch == "arm64" {
|
||||||
|
return info.Darwin.Arm64
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue