add zip and unzip
19
README.md
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
|
||||
|
||||
### 安装
|
||||
### 📚 安装
|
||||
|
||||
[![Windows][Windows-image]][Windows-url]
|
||||
[![MacOS][MacOS-image]][MacOS-url]
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
|
||||
</div>
|
||||
|
||||
## 亮点
|
||||
## 💝 亮点
|
||||
- ***无需联网使用,全开源***
|
||||
- ***零配置,无需注册,下载即用***
|
||||
- ***零污染,无插件依赖***
|
||||
|
|
@ -49,17 +49,17 @@
|
|||
- ***可无限扩展,支持自定义应用***
|
||||
- ***golang开发后端,低资源消耗和高性能***
|
||||
|
||||
## 开发进程
|
||||
## 🚧 开发进程
|
||||
- 2024年7月,发布v1.0.0版本,发布后,项目进入第二阶段。
|
||||
|
||||
## 第二阶段目标
|
||||
## 🏭 第二阶段目标
|
||||
- [x] 完善的应用商店体系
|
||||
- [x] 完善开发文档
|
||||
- [x] 支持远程存储
|
||||
- [x] 完成企业客户端
|
||||
|
||||
|
||||
## 功能说明和预览
|
||||
## ⚡ 功能说明和预览
|
||||
|
||||
### 一、系统桌面
|
||||
- 精仿windows风格
|
||||
|
|
@ -159,7 +159,7 @@ pnpm build
|
|||
wails build
|
||||
# wails build -nsis -upx //you need install nsis and upx
|
||||
```
|
||||
## 感谢
|
||||
## ❤️ 感谢
|
||||
- [element-plus](http://element-plus.org/)
|
||||
- [vue3](https://v3.cn.vuejs.org/)
|
||||
- [wails](https://wails.io/)
|
||||
|
|
@ -168,7 +168,7 @@ wails build
|
|||
- [mind-map](https://github.com/wanglin2/mind-map)
|
||||
- [canvas-editor](https://github.com/Hufe921/canvas-editor)
|
||||
|
||||
## 关联项目
|
||||
## 💕 关联项目
|
||||
- [godooa](https://gitee.com/ruitao_admin/gdoa)
|
||||
- [gododb](https://gitee.com/ruitao_admin/gododb)
|
||||
|
||||
|
|
@ -182,3 +182,8 @@ wails build
|
|||
- 如用于商业活动或二次开发后发售,请购买相关版权
|
||||
- 不提供私下维护工作,如有bug请 [issures](https://gitee.com/ruitao_admin/gododbs) 提交
|
||||
- 请尊重作者的劳动成果
|
||||
|
||||
## 💌 支持作者
|
||||
|
||||
如果觉得不错,或者已经在使用了,希望你可以去
|
||||
<a target="_blank" href="https://gitee.com/ruitao_admin/godoos">Gitee</a> 帮我点个 ⭐ Star,这将是对我极大的鼓励与支持。
|
||||
|
Before Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 155 KiB |
|
Before Width: | Height: | Size: 100 KiB |
BIN
frontend/public/image/store/mysql.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
frontend/public/image/store/nginx.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
frontend/public/image/store/notepad.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
frontend/public/image/store/php.png
Normal file
|
After Width: | Height: | Size: 987 B |
BIN
frontend/public/image/store/phpmyadmin.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
frontend/public/image/store/redis.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
|
|
@ -1,36 +1,21 @@
|
|||
[
|
||||
{
|
||||
"name": "在线工具",
|
||||
"url": "https://tool.lu/",
|
||||
"icon": "/image/store/tools.png"
|
||||
},
|
||||
"name": "mysql5.7",
|
||||
"url": "https://gitee.com/ruitao_admin/godoos-mysql5.7/releases/download/mysql5.7/mysql-windows-5.7.7z",
|
||||
"needDownload": true,
|
||||
"needInstall": true,
|
||||
"version": "5.7.37",
|
||||
"icon": "https://img0.baidu.com/it/u=1087008535,3349487719&fm=253&fmt=auto&app=138&f=PNG?w=969&h=500",
|
||||
"checkProgress": true,
|
||||
"hasRestart": true,
|
||||
"setting": [
|
||||
{
|
||||
"name": "石墨文档",
|
||||
"url": "https://shimo.im/desktop",
|
||||
"icon": "/image/store/shimo.png"
|
||||
},
|
||||
{
|
||||
"name": "在线节拍器",
|
||||
"url": "https://metronome.tooltool.net/?utm_source=xinquji",
|
||||
"icon": "/image/store/jpq.png"
|
||||
},
|
||||
{
|
||||
"name": "查看月食",
|
||||
"url": "https://nadc.china-vo.org/eclipse/",
|
||||
"icon": "/image/store/yueshi.png"
|
||||
},
|
||||
{
|
||||
"name": "蒸 気 機",
|
||||
"url": "https://magiconch.com/vaporwave/?from=home",
|
||||
"icon": "/image/store/zqj.png"
|
||||
},
|
||||
{
|
||||
"name": "vtb 大数据",
|
||||
"url": "https://vtbs.moe/",
|
||||
"icon": "/image/store/vtb.png"
|
||||
},
|
||||
{
|
||||
"name": "更多应用",
|
||||
"desc": "更多应用敬请期待,如果您有合适的应用,可以点击开始菜单,意见反馈取得联系"
|
||||
"path": "setting.html",
|
||||
"lang":{
|
||||
"cn-zh": "设置",
|
||||
"en": "Setting"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
<button @click="uninstall?.(item)">卸载</button>
|
||||
</div>
|
||||
<div v-else>
|
||||
<button v-if="item?.url || item?.path" @click="install?.(item)">安装</button>
|
||||
<button v-if="item?.url" @click="install?.(item)">安装</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import { inject, onMounted, ref } from "vue";
|
|||
import { System } from "@/system";
|
||||
import {notifySuccess, notifyError} from "@/util/msg";
|
||||
import { t } from "@/i18n";
|
||||
//import storeList from "@/assets/store.json";
|
||||
import { getSystemKey, setSystemKey } from "@/system/config";
|
||||
import storeInitList from "@/assets/store.json";
|
||||
import { getSystemKey, setSystemKey, parseJson } from "@/system/config";
|
||||
const sys: any = inject<System>("system");
|
||||
const currentCateId = ref(0)
|
||||
const currentTitle = ref(t("store.hots"))
|
||||
|
|
@ -14,6 +14,7 @@ const categoryIcon = ['HomeFilled', 'Odometer', 'Postcard', 'TrendCharts', 'Scho
|
|||
const isready = ref(false);
|
||||
const installed = getSystemKey("intstalledPlugins");
|
||||
const apiUrl = getSystemKey("apiUrl");
|
||||
|
||||
const installedList: any = ref(installed);
|
||||
const storeList: any = ref([])
|
||||
onMounted(async() => {
|
||||
|
|
@ -22,14 +23,16 @@ onMounted(async() => {
|
|||
});
|
||||
async function getList(){
|
||||
if(currentCate.value == 'add')return;
|
||||
const storeUrl = apiUrl + '/system/storeList?cate=' + currentCate.value
|
||||
fetch(storeUrl).then(res => {
|
||||
res.json().then(data => {
|
||||
storeList.value = data
|
||||
})
|
||||
}).catch(() => {
|
||||
notifyError(t("store.errorList"))
|
||||
})
|
||||
storeList.value = storeInitList
|
||||
// const apiUrl = getSystemKey("apiUrl");
|
||||
// const storeUrl = apiUrl + '/system/storeList?cate=' + currentCate.value
|
||||
// fetch(storeUrl).then(res => {
|
||||
// res.json().then(data => {
|
||||
// storeList.value = data
|
||||
// })
|
||||
// }).catch(() => {
|
||||
// notifyError(t("store.errorList"))
|
||||
// })
|
||||
isready.value = true;
|
||||
}
|
||||
async function changeCate(index: number, item: string) {
|
||||
|
|
@ -44,8 +47,14 @@ function setCache() {
|
|||
sys.refershAppList();
|
||||
}, 1000);
|
||||
}
|
||||
function install(item: any) {
|
||||
//console.log(item)
|
||||
async function install(item: any) {
|
||||
|
||||
if(item.needDownload){
|
||||
await download(item)
|
||||
}
|
||||
if(item.needInstall){
|
||||
|
||||
}
|
||||
if(item.url){
|
||||
sys.fs.writeFile(
|
||||
`${sys._options.userLocation}Desktop/${item.name}.url`,
|
||||
|
|
@ -64,6 +73,35 @@ function uninstall(item: any) {
|
|||
delete installedList.value[installedList.value.indexOf(item.name)];
|
||||
setCache();
|
||||
}
|
||||
async function download(item: any) {
|
||||
//console.log(item)
|
||||
const completion = await fetch(apiUrl + '/store/download?url=' + item.url)
|
||||
if(!completion.ok){
|
||||
notifyError(t("store.downloadError"))
|
||||
}
|
||||
//console.log(completion)
|
||||
const reader:any = completion.body?.getReader();
|
||||
if (!reader) {
|
||||
notifyError(t("store.cantStream"));
|
||||
}
|
||||
while (true) {
|
||||
const { done, value } = await reader?.read();
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
// console.log(value)
|
||||
const json = await new TextDecoder().decode(value);
|
||||
//console.log(json)
|
||||
const res = parseJson(json)
|
||||
console.log(res)
|
||||
if(res){
|
||||
if(res.done) {
|
||||
notifySuccess(t("store.downloadSuccess"))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="outer">
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"godo/libs"
|
||||
"godo/localchat"
|
||||
"godo/progress"
|
||||
"godo/store"
|
||||
"godo/sys"
|
||||
"log"
|
||||
"net/http"
|
||||
|
|
@ -41,13 +42,16 @@ func OsStart() {
|
|||
progressRouter.HandleFunc("/list", progress.Status).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/listport", progress.ListPortsHandler).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/killport", progress.KillPortHandler).Methods(http.MethodGet)
|
||||
progressRouter.HandleFunc("/app/{name}", progress.ForwardRequest).Methods(http.MethodGet, http.MethodPost)
|
||||
progressRouter.HandleFunc("/app/{name}/{subpath:.*}", progress.ForwardRequest).Methods(http.MethodGet, http.MethodPost)
|
||||
|
||||
router.HandleFunc("/system/updateInfo", sys.GetUpdateUrlHandler).Methods(http.MethodGet)
|
||||
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("/store/list", store.GetStoreInfoHandler).Methods(http.MethodGet)
|
||||
router.HandleFunc("/store/download", store.DownloadHandler).Methods(http.MethodGet)
|
||||
router.HandleFunc("/store/install", store.InstallHandler).Methods(http.MethodGet)
|
||||
router.HandleFunc("/store/uninstall", store.UnInstallHandler).Methods(http.MethodGet)
|
||||
|
||||
router.HandleFunc("/files/info", files.HandleSystemInfo).Methods(http.MethodGet)
|
||||
router.HandleFunc("/file/read", files.HandleReadDir).Methods(http.MethodGet)
|
||||
router.HandleFunc("/file/stat", files.HandleStat).Methods(http.MethodGet)
|
||||
|
|
|
|||
165
godo/files/unzip.go
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
package files
|
||||
|
||||
import (
|
||||
"compress/bzip2"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"archive/tar"
|
||||
"archive/zip"
|
||||
)
|
||||
|
||||
// Decompress 解压文件到指定目录,支持多种压缩格式
|
||||
func Decompress(compressedFilePath, destPath string) error {
|
||||
ext := filepath.Ext(compressedFilePath)
|
||||
switch ext {
|
||||
case ".zip":
|
||||
return Unzip(compressedFilePath, destPath)
|
||||
case ".tar":
|
||||
tarFile, err := os.Open(compressedFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tarFile.Close()
|
||||
return Untar(tarFile, destPath)
|
||||
case ".gz":
|
||||
return Untargz(compressedFilePath, destPath)
|
||||
case ".bz2":
|
||||
return Untarbz2(compressedFilePath, destPath)
|
||||
default:
|
||||
return fmt.Errorf("unsupported compression format: %s", ext)
|
||||
}
|
||||
}
|
||||
|
||||
// Untar 解压.tar文件到指定目录
|
||||
func Untar(reader io.Reader, destPath string) error {
|
||||
tarReader := tar.NewReader(reader)
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
target := filepath.Join(destPath, header.Name)
|
||||
if header.Typeflag == tar.TypeDir {
|
||||
if err := os.MkdirAll(target, os.FileMode(header.Mode)); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
outFile, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR|os.O_TRUNC, os.FileMode(header.Mode))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer outFile.Close()
|
||||
|
||||
if _, err := io.Copy(outFile, tarReader); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Untargz 解压.tar.gz文件到指定目录
|
||||
func Untargz(targzFilePath, destPath string) error {
|
||||
gzFile, err := os.Open(targzFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gzFile.Close()
|
||||
|
||||
gzReader, err := gzip.NewReader(gzFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gzReader.Close()
|
||||
|
||||
return Untar(gzReader, destPath) // 传递gzReader给Untar
|
||||
}
|
||||
|
||||
// Untarbz2 解压.tar.bz2文件到指定目录
|
||||
func Untarbz2(tarbz2FilePath, destPath string) error {
|
||||
bz2File, err := os.Open(tarbz2FilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer bz2File.Close()
|
||||
|
||||
bz2Reader := bzip2.NewReader(bz2File)
|
||||
return Untar(bz2Reader, destPath) // 传递bz2Reader给Untar
|
||||
}
|
||||
|
||||
// DecompressZip 解压zip文件到指定目录
|
||||
func Unzip(zipFilePath, destPath string) error {
|
||||
r, err := zip.OpenReader(zipFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
for _, f := range r.File {
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
path := filepath.Join(destPath, f.Name)
|
||||
|
||||
if f.FileInfo().IsDir() {
|
||||
os.MkdirAll(path, f.Mode())
|
||||
continue
|
||||
}
|
||||
|
||||
os.MkdirAll(filepath.Dir(path), f.Mode())
|
||||
|
||||
outFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer outFile.Close()
|
||||
|
||||
_, err = io.Copy(outFile, rc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsSupportedCompressionFormat 判断文件后缀是否为支持的压缩格式
|
||||
func IsSupportedCompressionFormat(filename string) bool {
|
||||
supportedFormats := map[string]bool{
|
||||
".zip": true,
|
||||
".tar": true,
|
||||
".gz": true,
|
||||
".bz2": true,
|
||||
}
|
||||
|
||||
for format := range supportedFormats {
|
||||
if strings.HasSuffix(strings.ToLower(filename), format) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HandlerFile 根据文件后缀判断是否支持解压,支持则解压,否则移动文件
|
||||
func HandlerFile(filePath string, destDir string) error {
|
||||
if !IsSupportedCompressionFormat(filePath) {
|
||||
// 移动文件
|
||||
newPath := filepath.Join(destDir, filepath.Base(filePath))
|
||||
return os.Rename(filePath, newPath)
|
||||
}
|
||||
// 解压文件
|
||||
return Decompress(filePath, destDir)
|
||||
}
|
||||
219
godo/files/zip.go
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
package files
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"archive/zip"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 压缩文件到指定的目录
|
||||
func zipCompress(folderPath, zipFilePath string) error {
|
||||
zipFile, err := os.Create(zipFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer zipFile.Close()
|
||||
|
||||
zipWriter := zip.NewWriter(zipFile)
|
||||
defer zipWriter.Close()
|
||||
|
||||
info, err := os.Stat(folderPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var baseDir string
|
||||
if info.IsDir() {
|
||||
baseDir = filepath.Base(folderPath)
|
||||
}
|
||||
|
||||
filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
header, err := zip.FileInfoHeader(info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
relPath, err := filepath.Rel(folderPath, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if baseDir != "" {
|
||||
header.Name = filepath.Join(baseDir, relPath)
|
||||
} else {
|
||||
header.Name = relPath
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
header.Name += "/"
|
||||
} else {
|
||||
header.Method = zip.Deflate
|
||||
}
|
||||
|
||||
writer, err := zipWriter.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = io.Copy(writer, file)
|
||||
return err
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsSupportedCompressionFormat 判断文件后缀是否为支持的压缩格式
|
||||
func IsSupportedFormat(filename string) bool {
|
||||
supportedFormats := map[string]bool{
|
||||
".zip": true,
|
||||
".tar": true,
|
||||
".gz": true,
|
||||
".bz2": true,
|
||||
// 添加其他支持的复合格式
|
||||
}
|
||||
|
||||
for format := range supportedFormats {
|
||||
if strings.HasSuffix(strings.ToLower(filename), format) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// CompressFileOrFolder 压缩文件或文件夹到指定的压缩文件
|
||||
func CompressFileOrFolder(sourcePath, compressedFilePath string) error {
|
||||
if IsSupportedFormat(compressedFilePath) {
|
||||
return Encompress(sourcePath, compressedFilePath)
|
||||
}
|
||||
// 如果压缩文件格式不受支持,则返回错误或采取其他措施
|
||||
return fmt.Errorf("unsupported compression format for file: %s", compressedFilePath)
|
||||
}
|
||||
|
||||
// Encompress 压缩文件或文件夹到指定的压缩格式
|
||||
func Encompress(sourcePath, compressedFilePath string) error {
|
||||
switch filepath.Ext(compressedFilePath) {
|
||||
case ".zip":
|
||||
return zipCompress(sourcePath, compressedFilePath)
|
||||
case ".tar":
|
||||
return tarCompress(sourcePath, compressedFilePath)
|
||||
case ".gz":
|
||||
return tarGzCompress(sourcePath, compressedFilePath)
|
||||
default:
|
||||
return fmt.Errorf("unsupported compression format: %s", filepath.Ext(compressedFilePath))
|
||||
}
|
||||
}
|
||||
|
||||
// tarCompress 使用 tar 格式压缩文件或文件夹
|
||||
func tarCompress(folderPath, tarFilePath string) error {
|
||||
// 打开目标文件
|
||||
tarFile, err := os.Create(tarFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tarFile.Close()
|
||||
|
||||
// 创建 tar.Writer
|
||||
tarWriter := tar.NewWriter(tarFile)
|
||||
defer tarWriter.Close()
|
||||
|
||||
// 遍历文件夹并添加到 tar 文件
|
||||
err = filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
header, err := tar.FileInfoHeader(info, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tarWriter.WriteHeader(header); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = io.Copy(tarWriter, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// tarGzCompress 使用 tar.gz 格式压缩文件或文件夹
|
||||
func tarGzCompress(folderPath, tarGzFilePath string) error {
|
||||
// 打开目标文件
|
||||
gzFile, err := os.Create(tarGzFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gzFile.Close()
|
||||
|
||||
// 创建 gzip.Writer
|
||||
gzWriter := gzip.NewWriter(gzFile)
|
||||
defer gzWriter.Close()
|
||||
|
||||
// 创建 tar.Writer
|
||||
tarWriter := tar.NewWriter(gzWriter)
|
||||
defer tarWriter.Close()
|
||||
|
||||
// 遍历文件夹并添加到 tar 文件
|
||||
err = filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
header, err := tar.FileInfoHeader(info, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tarWriter.WriteHeader(header); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = io.Copy(tarWriter, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
|
@ -3,11 +3,9 @@ module godo
|
|||
go 1.22.5
|
||||
|
||||
require (
|
||||
github.com/cavaliergopher/grab/v3 v3.0.1
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/minio/selfupdate v0.6.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +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/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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
|
|
@ -12,8 +10,6 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
|||
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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ func DiscoverServers() {
|
|||
conn.Close()
|
||||
continue
|
||||
}
|
||||
log.Printf("Sending message: %+v", msg)
|
||||
//log.Printf("Sending message: %+v", msg)
|
||||
_, err = conn.Write(jsonData)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
|
|
|
|||
|
|
@ -27,10 +27,10 @@ func StartServiceDiscovery() {
|
|||
for {
|
||||
n, addr, err := conn.ReadFromUDP(buffer)
|
||||
if err != nil {
|
||||
log.Printf("Error reading from UDP: %v", err)
|
||||
log.Printf("Error reading from UDP: %v,addr:%v", err, addr)
|
||||
continue
|
||||
}
|
||||
fmt.Printf("Received message: %s from %s\n", buffer[:n], addr)
|
||||
//fmt.Printf("Received message: %s from %s\n", buffer[:n], addr)
|
||||
|
||||
var udpMsg UdpMessage
|
||||
err = json.Unmarshal(buffer[:n], &udpMsg)
|
||||
|
|
@ -38,7 +38,7 @@ func StartServiceDiscovery() {
|
|||
fmt.Printf("Error unmarshalling JSON: %v\n", err)
|
||||
continue
|
||||
}
|
||||
log.Printf("Get message: %+v", udpMsg)
|
||||
//log.Printf("Get message: %+v", udpMsg)
|
||||
OnlineUsers[udpMsg.IP] = udpMsg
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,14 @@ package progress
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
|
@ -34,37 +38,46 @@ func checkInactiveProcesses() {
|
|||
defer processesMu.RUnlock()
|
||||
|
||||
for name, p := range processes {
|
||||
// 检查进程是否还在运行
|
||||
pid := p.Cmd.Process.Pid
|
||||
_, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
p.Running = false
|
||||
// 进程已不存在,跳过后续检查
|
||||
continue
|
||||
}
|
||||
|
||||
// 如果进程还在运行,检查/ping接口
|
||||
if p.Running {
|
||||
resp, err := http.Get(p.PingURL)
|
||||
if err != nil {
|
||||
p.Running = false
|
||||
// 进程未响应
|
||||
log.Printf("Error checking ping for process %s: %v", name, err)
|
||||
continue
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
p.Running = false
|
||||
// 进程未正常响应,停止进程
|
||||
if err := p.Cmd.Process.Signal(os.Kill); err != nil {
|
||||
log.Printf("Failed to stop process %s before restart: %v", name, err)
|
||||
continue
|
||||
}
|
||||
log.Printf("Process %s (PID: %d) not found.", name, pid)
|
||||
} else {
|
||||
// 进程正常响应,更新LastPingAt并继续检查下一个进程
|
||||
log.Printf("Error finding process %s (PID: %d): %v", name, pid, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// 进程仍然运行,更新LastPing
|
||||
p.LastPing = time.Now()
|
||||
}
|
||||
}
|
||||
|
||||
func IsProcessRunning(appName string) (bool, error) {
|
||||
var cmd *exec.Cmd
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
cmd = exec.Command("powershell", "Get-Process", appName)
|
||||
case "linux":
|
||||
fallthrough
|
||||
case "darwin":
|
||||
cmd = exec.Command("pgrep", "-f", appName)
|
||||
default:
|
||||
return false, fmt.Errorf("unsupported OS: %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
var output []byte
|
||||
var err error
|
||||
if cmd.SysProcAttr == nil {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
}
|
||||
cmd.SysProcAttr.HideWindow = true // For Windows to hide the console window
|
||||
|
||||
if output, err = cmd.CombinedOutput(); err != nil {
|
||||
return false, fmt.Errorf("error checking process: %w", err)
|
||||
}
|
||||
|
||||
return len(strings.TrimSpace(string(output))) > 0, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,97 +0,0 @@
|
|||
package progress
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func ForwardRequest(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
proxyName := vars["name"] // 获取代理名称
|
||||
log.Printf("Forwarding request to proxy %s", proxyName)
|
||||
log.Printf("processes: %+v", processes)
|
||||
cmd, ok := processes[proxyName]
|
||||
if !ok || !cmd.Running {
|
||||
// Start the process again
|
||||
err := ExecuteScript(proxyName)
|
||||
if err != nil {
|
||||
respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to restart process %s: %v", proxyName, err))
|
||||
return
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
if cmd.PingURL == "" {
|
||||
respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get url %s", proxyName))
|
||||
return
|
||||
}
|
||||
// 构造目标基础URL,假设proxyName直接对应目标服务的基础路径
|
||||
targetBaseURLStr := cmd.PingURL
|
||||
targetBaseURL, err := url.Parse(targetBaseURLStr)
|
||||
if err != nil {
|
||||
respondWithError(w, http.StatusInternalServerError, "Failed to parse target base URL")
|
||||
return
|
||||
}
|
||||
// 获取请求的子路径
|
||||
subpath := r.URL.Path[len("/"+proxyName):]
|
||||
|
||||
targetURL := &url.URL{
|
||||
Scheme: targetBaseURL.Scheme,
|
||||
Host: targetBaseURL.Host,
|
||||
Path: path.Clean(path.Join(targetBaseURL.Path, subpath)), // 使用path.Clean处理路径
|
||||
RawQuery: r.URL.RawQuery, // 正确编码查询参数
|
||||
}
|
||||
log.Printf("Forwarding request to %s", targetURL)
|
||||
|
||||
// 执行重定向
|
||||
http.Redirect(w, r, targetURL.String(), http.StatusTemporaryRedirect)
|
||||
/*
|
||||
|
||||
|
||||
|
||||
// 保留原请求的路径和查询字符串,构建完整的目标URL
|
||||
// 如果没有子路径,直接使用基础URL
|
||||
// 如果有子路径,将其附加到基础URL
|
||||
|
||||
client := http.DefaultClient
|
||||
req, err := http.NewRequest(r.Method, targetURL.String(), r.Body)
|
||||
if err != nil {
|
||||
respondWithError(w, http.StatusBadRequest, "Failed to create request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
copyHeader(req.Header, r.Header)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
respondWithError(w, http.StatusServiceUnavailable, "Failed to forward request: "+err.Error())
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 将响应头复制到w.Header()
|
||||
for k, vv := range resp.Header {
|
||||
for _, v := range vv {
|
||||
w.Header().Add(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
if _, err := io.Copy(w, resp.Body); err != nil {
|
||||
log.Printf("Error copying response body: %v", err)
|
||||
}*/
|
||||
}
|
||||
|
||||
// 复制源Header到目标Header
|
||||
// func copyHeader(dst, src http.Header) {
|
||||
// for k, vv := range src {
|
||||
// for _, v := range vv {
|
||||
// dst.Add(k, v)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
|
@ -7,6 +7,6 @@ import (
|
|||
"os/exec"
|
||||
)
|
||||
|
||||
func setHideConsoleCursor(cmd *exec.Cmd) *exec.Cmd {
|
||||
func SetHideConsoleCursor(cmd *exec.Cmd) *exec.Cmd {
|
||||
return cmd
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,15 +5,4 @@ type ProcessInfo struct {
|
|||
PingPath string // 增加ping或健康检查路径
|
||||
}
|
||||
|
||||
var ProcessInfoMap = map[string]ProcessInfo{
|
||||
"model": {Port: "56711", PingPath: "ping"},
|
||||
"goconv": {Port: "56712", PingPath: "ping"},
|
||||
"file": {Port: "56713", PingPath: "ping"},
|
||||
"knowledge": {Port: "56714", PingPath: "ping"},
|
||||
//"llmcpu": {Port: "56715", PingPath: "healthz"},
|
||||
"ollama": {Port: "56715", PingPath: ""},
|
||||
"sd": {Port: "56716", PingPath: "ping"},
|
||||
"voice": {Port: "56717", PingPath: "ping"},
|
||||
"localchat": {Port: "56718", PingPath: "ping"},
|
||||
"llmgpu": {Port: "56719", PingPath: "healthz"},
|
||||
}
|
||||
var ProcessInfoMap = map[string]ProcessInfo{}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ type Process struct {
|
|||
Running bool
|
||||
ExitCode int
|
||||
Cmd *exec.Cmd
|
||||
PingURL string // 新增字段,存储每个进程的/ping URL
|
||||
Waiting bool
|
||||
LastPing time.Time
|
||||
}
|
||||
|
||||
|
|
@ -26,16 +26,13 @@ var (
|
|||
func respondWithError(w http.ResponseWriter, code int, message string) {
|
||||
http.Error(w, message, code)
|
||||
}
|
||||
func registerProcess(name, pingURL string, cmdstr *exec.Cmd) {
|
||||
func RegisterProcess(name string, cmdstr *exec.Cmd) {
|
||||
processesMu.Lock()
|
||||
defer processesMu.Unlock()
|
||||
|
||||
log.Printf("pingurl is: %s", pingURL) // 更改这里的格式化字符串
|
||||
|
||||
processes[name] = &Process{
|
||||
Name: name,
|
||||
Running: true,
|
||||
PingURL: pingURL,
|
||||
Cmd: cmdstr,
|
||||
}
|
||||
}
|
||||
|
|
@ -54,10 +51,10 @@ func Status(w http.ResponseWriter, r *http.Request) {
|
|||
if cmd.Cmd.ProcessState != nil && cmd.Cmd.ProcessState.Exited() {
|
||||
cmd.Running = false
|
||||
// 进程已经退出
|
||||
ps = append(ps, Process{Name: name, Running: false, PingURL: cmd.PingURL, LastPing: cmd.LastPing, ExitCode: cmd.Cmd.ProcessState.ExitCode()})
|
||||
ps = append(ps, Process{Name: name, Running: false, Waiting: cmd.Waiting, LastPing: cmd.LastPing, ExitCode: cmd.Cmd.ProcessState.ExitCode()})
|
||||
} else {
|
||||
// 进程仍在运行
|
||||
ps = append(ps, Process{Name: name, Running: true, PingURL: cmd.PingURL, LastPing: cmd.LastPing})
|
||||
ps = append(ps, Process{Name: name, Running: true, Waiting: cmd.Waiting, LastPing: cmd.LastPing})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,17 +2,9 @@ package progress
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"godo/libs"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func StartProcess(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -20,17 +12,6 @@ func StartProcess(w http.ResponseWriter, r *http.Request) {
|
|||
name := vars["name"]
|
||||
processesMu.Lock()
|
||||
defer processesMu.Unlock()
|
||||
cmd, ok := processes[name]
|
||||
if ok && !cmd.Running {
|
||||
err := cmd.Cmd.Start()
|
||||
if err != nil {
|
||||
respondWithError(w, http.StatusInternalServerError, err.Error())
|
||||
fmt.Fprintf(w, "failed to start process %s: %v", name, err)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
return
|
||||
}
|
||||
err := ExecuteScript(name)
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -42,25 +23,13 @@ func StartProcess(w http.ResponseWriter, r *http.Request) {
|
|||
fmt.Fprintf(w, "Process %s started.", name)
|
||||
}
|
||||
func ExecuteStartAll() error {
|
||||
log.Println("Starting all processes...")
|
||||
processesMu.Lock()
|
||||
defer processesMu.Unlock()
|
||||
|
||||
userExeDir := libs.GetRunDir()
|
||||
|
||||
userFis, err := os.ReadDir(userExeDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read user scripts directory: %w", err)
|
||||
for name, cmd := range processes {
|
||||
if err := cmd.Cmd.Start(); err != nil {
|
||||
return fmt.Errorf("failed to stop process %s: %v", name, err)
|
||||
}
|
||||
|
||||
var names []string
|
||||
for _, userFi := range userFis {
|
||||
if userFi.IsDir() && !strings.HasPrefix(userFi.Name(), ".") {
|
||||
names = append(names, userFi.Name())
|
||||
}
|
||||
}
|
||||
|
||||
err = batchExecuteScript(names)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start processes: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -74,19 +43,6 @@ func StartAll(w http.ResponseWriter, r *http.Request) {
|
|||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, "All processes started.")
|
||||
}
|
||||
func batchExecuteScript(names []string) error {
|
||||
for _, name := range names {
|
||||
// 检查processes中是否存在相同的name
|
||||
if _, exists := processes[name]; exists {
|
||||
log.Printf("Process with name %s already exists", name)
|
||||
continue
|
||||
}
|
||||
if err := ExecuteScript(name); err != nil {
|
||||
return errors.Wrapf(err, "failed to script: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExecuteScript 执行指定名称的脚本。
|
||||
// 参数:
|
||||
|
|
@ -94,51 +50,50 @@ func batchExecuteScript(names []string) error {
|
|||
// 返回值:
|
||||
// 返回可能遇到的错误,如果执行成功,则返回nil。
|
||||
func ExecuteScript(name string) error {
|
||||
// 确保name存在于ProcessInfoMap中
|
||||
info, ok := ProcessInfoMap[name]
|
||||
if !ok {
|
||||
cmd, exists := processes[name]
|
||||
if !exists {
|
||||
return fmt.Errorf("process information for '%s' not found", name)
|
||||
}
|
||||
// 根据操作系统添加.exe后缀
|
||||
binaryExt := ""
|
||||
if runtime.GOOS == "windows" {
|
||||
binaryExt = ".exe"
|
||||
if cmd.Running {
|
||||
return fmt.Errorf("process %s is already running", name)
|
||||
}
|
||||
scriptName := name + binaryExt
|
||||
exeDir := libs.GetRunDir() // 获取执行程序的目录
|
||||
scriptPath := filepath.Join(exeDir, name, scriptName) // 拼接脚本的完整路径
|
||||
// 检查脚本文件的存在性
|
||||
if !PathExists(scriptPath) {
|
||||
return fmt.Errorf("script file %s does not exist", scriptPath)
|
||||
if err := cmd.Cmd.Start(); err != nil {
|
||||
return fmt.Errorf("failed to start process %s: %v", name, err)
|
||||
}
|
||||
|
||||
// 设置并启动脚本执行命令
|
||||
var cmd *exec.Cmd
|
||||
cmd = exec.Command(scriptPath)
|
||||
if runtime.GOOS == "windows" {
|
||||
// 在Windows上,通过设置CreationFlags来隐藏窗口
|
||||
cmd = setHideConsoleCursor(cmd)
|
||||
}
|
||||
go func() {
|
||||
// 启动脚本命令并返回可能的错误
|
||||
if err := cmd.Start(); err != nil {
|
||||
log.Printf("failed to start process %s: %v", name, err)
|
||||
return
|
||||
}
|
||||
pingURL := fmt.Sprintf("http://localhost:%s/%s", info.Port, info.PingPath)
|
||||
registerProcess(name, pingURL, cmd)
|
||||
}()
|
||||
cmd.Running = true
|
||||
return nil
|
||||
}
|
||||
func PathExists(dir string) bool {
|
||||
_, err := os.Stat(dir)
|
||||
if err == nil {
|
||||
return true
|
||||
} else if os.IsNotExist(err) {
|
||||
return false
|
||||
} else if os.IsExist(err) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
// 确保name存在于ProcessInfoMap中
|
||||
// _, ok := ProcessInfoMap[name]
|
||||
// if !ok {
|
||||
// return fmt.Errorf("process information for '%s' not found", name)
|
||||
// }
|
||||
// // 根据操作系统添加.exe后缀
|
||||
// binaryExt := ""
|
||||
// if runtime.GOOS == "windows" {
|
||||
// binaryExt = ".exe"
|
||||
// }
|
||||
// scriptName := name + binaryExt
|
||||
// exeDir := libs.GetRunDir() // 获取执行程序的目录
|
||||
// scriptPath := filepath.Join(exeDir, name, scriptName) // 拼接脚本的完整路径
|
||||
// // 检查脚本文件的存在性
|
||||
// if !libs.PathExists(scriptPath) {
|
||||
// return fmt.Errorf("script file %s does not exist", scriptPath)
|
||||
// }
|
||||
|
||||
// // 设置并启动脚本执行命令
|
||||
// var cmd *exec.Cmd
|
||||
// cmd = exec.Command(scriptPath)
|
||||
// if runtime.GOOS == "windows" {
|
||||
// // 在Windows上,通过设置CreationFlags来隐藏窗口
|
||||
// cmd = SetHideConsoleCursor(cmd)
|
||||
// }
|
||||
// go func() {
|
||||
// // 启动脚本命令并返回可能的错误
|
||||
// if err := cmd.Start(); err != nil {
|
||||
// log.Printf("failed to start process %s: %v", name, err)
|
||||
// return
|
||||
// }
|
||||
// RegisterProcess(name, cmd)
|
||||
// }()
|
||||
// return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,23 +8,27 @@ import (
|
|||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func StopCmd(name string) error {
|
||||
cmd, ok := processes[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("Process not found")
|
||||
}
|
||||
if err := cmd.Cmd.Process.Kill(); err != nil {
|
||||
return fmt.Errorf("failed to kill process: %v", err)
|
||||
}
|
||||
//delete(processes, name) // 更新status,表示进程已停止
|
||||
cmd.Running = false
|
||||
return nil
|
||||
}
|
||||
func StopProcess(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
name := vars["name"]
|
||||
|
||||
cmd, ok := processes[name]
|
||||
if !ok {
|
||||
respondWithError(w, http.StatusNotFound, "Process not found")
|
||||
return
|
||||
}
|
||||
|
||||
// 停止进程并更新status
|
||||
if err := cmd.Cmd.Process.Kill(); err != nil {
|
||||
err := StopCmd(name)
|
||||
if err != nil {
|
||||
respondWithError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
//delete(processes, name) // 更新status,表示进程已停止
|
||||
cmd.Running = false
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, "Process %s stopped.", name)
|
||||
}
|
||||
|
|
@ -34,7 +38,7 @@ func StopAllHandler() error {
|
|||
|
||||
for name, cmd := range processes {
|
||||
if err := cmd.Cmd.Process.Signal(os.Interrupt); err != nil {
|
||||
return fmt.Errorf("Failed to stop process %s: %v", name, err)
|
||||
return fmt.Errorf("failed to stop process %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import (
|
|||
"syscall"
|
||||
)
|
||||
|
||||
func setHideConsoleCursor(cmd *exec.Cmd) *exec.Cmd {
|
||||
func SetHideConsoleCursor(cmd *exec.Cmd) *exec.Cmd {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
||||
return cmd
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,159 +1,156 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"godo/files"
|
||||
"godo/libs"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cavaliergopher/grab/v3"
|
||||
)
|
||||
|
||||
type ProgressReader struct {
|
||||
reader io.Reader
|
||||
total int64
|
||||
err error
|
||||
}
|
||||
|
||||
type DownloadStatus struct {
|
||||
resp *grab.Response
|
||||
cancel context.CancelFunc
|
||||
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"`
|
||||
Progress int `json:"progress"`
|
||||
Downloading bool `json:"downloading"`
|
||||
Done bool `json:"done"`
|
||||
}
|
||||
|
||||
const (
|
||||
concurrency = 6 // 并发下载数
|
||||
)
|
||||
|
||||
// var downloads = make(map[string]*grab.Response)
|
||||
var downloadsMutex sync.Mutex
|
||||
var downloadList map[string]*DownloadStatus
|
||||
|
||||
func existsInDownloadList(url string) bool {
|
||||
_, ok := downloadList[url]
|
||||
return ok
|
||||
}
|
||||
|
||||
func PauseDownload(url string) {
|
||||
downloadsMutex.Lock()
|
||||
defer downloadsMutex.Unlock()
|
||||
ds, ok := downloadList[url]
|
||||
if ds.Url == url && ok {
|
||||
if ds.cancel != nil {
|
||||
ds.cancel()
|
||||
}
|
||||
ds.resp = nil
|
||||
ds.Downloading = false
|
||||
ds.Speed = 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func ContinueDownload(url string) {
|
||||
ds, ok := downloadList[url]
|
||||
if ds.Url == url && ok {
|
||||
if !ds.Downloading && ds.resp == nil && !ds.Done {
|
||||
ds.Downloading = true
|
||||
|
||||
req, err := grab.NewRequest(ds.Path, ds.Url)
|
||||
if err != nil {
|
||||
ds.Downloading = false
|
||||
func (pr *ProgressReader) Read(p []byte) (n int, err error) {
|
||||
n, err = pr.reader.Read(p)
|
||||
pr.err = err
|
||||
pr.total += int64(n)
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ds.cancel = cancel
|
||||
req = req.WithContext(ctx)
|
||||
client := grab.NewClient()
|
||||
client.HTTPClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
MaxIdleConnsPerHost: concurrency, // 设置并发连接数
|
||||
},
|
||||
}
|
||||
//resp := grab.DefaultClient.Do(req)
|
||||
resp := client.Do(req)
|
||||
|
||||
if resp != nil && resp.HTTPResponse != nil &&
|
||||
resp.HTTPResponse.StatusCode >= 200 && resp.HTTPResponse.StatusCode < 300 {
|
||||
ds.resp = resp
|
||||
} else {
|
||||
ds.Downloading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func Download(url string) {
|
||||
chacheDir := libs.GetCacheDir()
|
||||
absPath := filepath.Join(chacheDir, filepath.Base(url))
|
||||
if !existsInDownloadList(url) {
|
||||
downloadList[url] = &DownloadStatus{
|
||||
resp: nil,
|
||||
Name: filepath.Base(url),
|
||||
Path: absPath,
|
||||
Url: url,
|
||||
Downloading: false,
|
||||
}
|
||||
}
|
||||
ContinueDownload(url)
|
||||
}
|
||||
func GetDownload(url string) *DownloadStatus {
|
||||
downloadsMutex.Lock()
|
||||
defer downloadsMutex.Unlock()
|
||||
ds, ok := downloadList[url]
|
||||
if ds.resp != nil && ok {
|
||||
ds.Current = ds.resp.BytesComplete()
|
||||
ds.Size = ds.resp.Size()
|
||||
ds.Speed = ds.resp.BytesPerSecond()
|
||||
ds.Progress = 100 * ds.resp.Progress()
|
||||
ds.Downloading = !ds.resp.IsComplete()
|
||||
ds.Done = ds.resp.Progress() == 1
|
||||
if !ds.Downloading {
|
||||
ds.resp = nil
|
||||
}
|
||||
}
|
||||
if ds.Done {
|
||||
delete(downloadList, url)
|
||||
}
|
||||
return ds
|
||||
}
|
||||
func DownloadHandler(w http.ResponseWriter, r *http.Request) {
|
||||
url := r.URL.Query().Get("url")
|
||||
Download(url)
|
||||
ticker := time.NewTicker(500 * time.Millisecond)
|
||||
log.Printf("Download url: %s", url)
|
||||
|
||||
// 获取下载目录,这里假设从请求参数中获取,如果没有则使用默认值
|
||||
downloadDir := libs.GetCacheDir()
|
||||
if downloadDir == "" {
|
||||
downloadDir = "./downloads"
|
||||
}
|
||||
|
||||
// 拼接完整的文件路径
|
||||
fileName := filepath.Base(url)
|
||||
filePath := filepath.Join(downloadDir, fileName)
|
||||
|
||||
// 开始下载
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get file: %v", err)
|
||||
http.Error(w, "Failed to get file", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 检查文件是否已存在且大小一致
|
||||
if fileInfo, err := os.Stat(filePath); err == nil {
|
||||
if fileInfo.Size() == resp.ContentLength {
|
||||
// 文件已存在且大小一致,无需下载
|
||||
runDir := libs.GetRunDir()
|
||||
err := files.HandlerFile(filePath, runDir)
|
||||
if err != nil {
|
||||
log.Printf("Error moving file: %v", err)
|
||||
}
|
||||
libs.SuccessMsg(w, "success", "File already exists and is of correct size")
|
||||
return
|
||||
} else {
|
||||
// 重新打开响应体以便后续读取
|
||||
resp.Body = http.NoBody
|
||||
resp, err = http.Get(url)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get file: %v", err)
|
||||
http.Error(w, "Failed to get file", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建文件
|
||||
file, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
log.Printf("Failed to create file: %v", err)
|
||||
http.Error(w, "Failed to create file", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// 使用ProgressReader来跟踪进度
|
||||
pr := &ProgressReader{reader: resp.Body}
|
||||
|
||||
// 启动定时器来报告进度
|
||||
ticker := time.NewTicker(200 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
flusher, ok := w.(http.Flusher)
|
||||
if !ok {
|
||||
log.Printf("Streaming unsupported")
|
||||
http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
<-ticker.C
|
||||
ds := GetDownload(url)
|
||||
jsonBytes, err := json.Marshal(ds)
|
||||
rp := &DownloadStatus{
|
||||
Name: fileName,
|
||||
Path: filePath,
|
||||
Url: url,
|
||||
Current: pr.total,
|
||||
Size: resp.ContentLength,
|
||||
Speed: 0, // 这里可以计算速度,但为了简化示例,我们暂时设为0
|
||||
Progress: int(100 * (float64(pr.total) / float64(resp.ContentLength))),
|
||||
Downloading: pr.err == nil && pr.total < resp.ContentLength,
|
||||
Done: pr.total == resp.ContentLength,
|
||||
}
|
||||
if pr.err != nil || rp.Done {
|
||||
rp.Downloading = false
|
||||
//log.Printf("Download complete: %s", filePath)
|
||||
runDir := libs.GetRunDir()
|
||||
err := files.HandlerFile(filePath, runDir)
|
||||
if err != nil {
|
||||
log.Printf("Error marshaling FileProgress to JSON: %v", err)
|
||||
continue
|
||||
log.Printf("Error moving file: %v", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
if w != nil {
|
||||
io.WriteString(w, string(jsonBytes))
|
||||
jsonBytes, err := json.Marshal(rp)
|
||||
if err != nil {
|
||||
log.Printf("Error marshaling DownloadStatus to JSON: %v", err)
|
||||
continue
|
||||
}
|
||||
w.Write(jsonBytes)
|
||||
w.Write([]byte("\n"))
|
||||
flusher.Flush()
|
||||
} else {
|
||||
log.Println("ResponseWriter is nil, cannot send progress")
|
||||
}
|
||||
if ds.Done {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// 将响应体的内容写入文件
|
||||
_, err = io.Copy(file, pr)
|
||||
if err != nil {
|
||||
log.Printf("Failed to write file: %v", err)
|
||||
http.Error(w, "Failed to write file", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
libs.SuccessMsg(w, "success", "Download complete")
|
||||
}
|
||||
|
|
|
|||
401
godo/store/install.go
Normal file
|
|
@ -0,0 +1,401 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"godo/files"
|
||||
"godo/libs"
|
||||
"godo/progress"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type StoreInfo struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
NeedDownload bool `json:"needDownload"`
|
||||
Icon string `json:"icon"`
|
||||
Setting Setting `json:"setting"`
|
||||
Config map[string]any `json:"config"`
|
||||
Cmds map[string][]Cmd `json:"cmds"`
|
||||
Install Install `json:"install"`
|
||||
Start Install `json:"start"`
|
||||
}
|
||||
type Setting struct {
|
||||
BinPath string `json:"binPath"`
|
||||
ConfPath string `json:"confPath"`
|
||||
DataDir string `json:"dataDir"`
|
||||
LogDir string `json:"logDir"`
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
Name string `json:"name"`
|
||||
Value any `json:"value"`
|
||||
}
|
||||
type Cmd struct {
|
||||
Name string `json:"name"`
|
||||
FilePath string `json:"filePath,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
BinPath string `json:"binPath,omitempty"`
|
||||
Cmds []string `json:"cmds,omitempty"`
|
||||
Waiting bool `json:"waiting"`
|
||||
Envs []Item `json:"envs"`
|
||||
}
|
||||
|
||||
type Install struct {
|
||||
Envs []Item `json:"envs"`
|
||||
Cmds []string `json:"cmds"`
|
||||
}
|
||||
|
||||
func InstallHandler(w http.ResponseWriter, r *http.Request) {
|
||||
pluginName := r.URL.Query().Get("name")
|
||||
if pluginName == "" {
|
||||
libs.ErrorMsg(w, "the app name is empty!")
|
||||
return
|
||||
}
|
||||
exeDir := libs.GetRunDir()
|
||||
exePath := filepath.Join(exeDir, pluginName)
|
||||
//log.Printf("the app path is %s", exePath)
|
||||
if !libs.PathExists(exePath) {
|
||||
libs.ErrorMsg(w, "the app path is not exists!")
|
||||
return
|
||||
}
|
||||
installFile := filepath.Join(exePath, "install.json")
|
||||
if !libs.PathExists(installFile) {
|
||||
libs.ErrorMsg(w, "the install.json is not exists!")
|
||||
return
|
||||
}
|
||||
var storeInfo StoreInfo
|
||||
content, err := os.ReadFile(installFile)
|
||||
if err != nil {
|
||||
libs.ErrorMsg(w, "cant read the install.json!")
|
||||
return
|
||||
}
|
||||
//设置 info.json
|
||||
exePath = strings.ReplaceAll(exePath, "\\", "/")
|
||||
contentBytes := []byte(strings.ReplaceAll(string(content), "{exePath}", exePath))
|
||||
//log.Printf("====content: %s", string(contentBytes))
|
||||
err = json.Unmarshal(contentBytes, &storeInfo)
|
||||
if err != nil {
|
||||
log.Printf("Error during unmarshal: %v", err)
|
||||
libs.ErrorMsg(w, "the install.json is error: "+err.Error())
|
||||
return
|
||||
}
|
||||
replacePlaceholdersInCmds(&storeInfo)
|
||||
infoFile := filepath.Join(exePath, "info.json")
|
||||
err = SaveStoreInfo(storeInfo, infoFile)
|
||||
if err != nil {
|
||||
libs.ErrorMsg(w, "save the info.json is error!")
|
||||
return
|
||||
}
|
||||
|
||||
//设置config
|
||||
if libs.PathExists(storeInfo.Setting.ConfPath + ".tpl") {
|
||||
err = SaveStoreConfig(storeInfo, exePath)
|
||||
if err != nil {
|
||||
libs.ErrorMsg(w, "save the config is error!")
|
||||
return
|
||||
}
|
||||
}
|
||||
err = InstallStore(storeInfo)
|
||||
if err != nil {
|
||||
libs.ErrorMsg(w, "install the app is error!")
|
||||
return
|
||||
}
|
||||
//复制static目录
|
||||
staticPath := filepath.Join(exePath, "static")
|
||||
if libs.PathExists(staticPath) {
|
||||
staticDir := libs.GetStaticDir()
|
||||
targetPath := filepath.Join(staticDir, pluginName)
|
||||
err = os.Rename(staticPath, targetPath)
|
||||
if err != nil {
|
||||
libs.ErrorMsg(w, "copy the static is error!")
|
||||
return
|
||||
}
|
||||
}
|
||||
libs.SuccessMsg(w, "success", "install the app success!")
|
||||
}
|
||||
func UnInstallHandler(w http.ResponseWriter, r *http.Request) {
|
||||
pluginName := r.URL.Query().Get("name")
|
||||
if pluginName == "" {
|
||||
libs.ErrorMsg(w, "the app name is empty!")
|
||||
return
|
||||
}
|
||||
err := progress.StopCmd(pluginName)
|
||||
if err != nil {
|
||||
libs.ErrorMsg(w, "stop the app is error!")
|
||||
return
|
||||
}
|
||||
exeDir := libs.GetRunDir()
|
||||
exePath := filepath.Join(exeDir, pluginName)
|
||||
//log.Printf("the app path is %s", exePath)
|
||||
if libs.PathExists(exePath) {
|
||||
err := os.RemoveAll(exePath)
|
||||
if err != nil {
|
||||
libs.ErrorMsg(w, "delete the app is error!")
|
||||
return
|
||||
}
|
||||
}
|
||||
staticDir := libs.GetStaticDir()
|
||||
staticPath := filepath.Join(staticDir, pluginName)
|
||||
if libs.PathExists(staticPath) {
|
||||
err := os.RemoveAll(staticPath)
|
||||
if err != nil {
|
||||
libs.ErrorMsg(w, "delete the static is error!")
|
||||
return
|
||||
}
|
||||
}
|
||||
libs.SuccessMsg(w, "success", "uninstall the app success!")
|
||||
|
||||
}
|
||||
func convertValueToString(value any) string {
|
||||
var strValue string
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
strValue = v
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
|
||||
strValue = fmt.Sprintf("%v", v)
|
||||
default:
|
||||
strValue = fmt.Sprintf("%v", v) // 处理其他类型,例如布尔型或更复杂的类型
|
||||
}
|
||||
return strValue
|
||||
}
|
||||
|
||||
func InstallStore(storeInfo StoreInfo) error {
|
||||
err := SetEnvs(storeInfo.Install.Envs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set install environment variable %s: %w", storeInfo.Name, err)
|
||||
}
|
||||
if len(storeInfo.Install.Cmds) > 0 {
|
||||
for _, cmdKey := range storeInfo.Install.Cmds {
|
||||
if _, ok := storeInfo.Cmds[cmdKey]; ok {
|
||||
// 如果命令存在,你可以进一步处理 cmds
|
||||
RunCmds(storeInfo, cmdKey)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func RunCmds(storeInfo StoreInfo, cmdKey string) error {
|
||||
cmds := storeInfo.Cmds[cmdKey]
|
||||
for _, cmd := range cmds {
|
||||
|
||||
if cmd.Name == "stop" {
|
||||
runStop(storeInfo)
|
||||
}
|
||||
if cmd.Name == "start" {
|
||||
runStart(storeInfo)
|
||||
}
|
||||
if cmd.Name == "restart" {
|
||||
runRestart(storeInfo)
|
||||
}
|
||||
if cmd.Name == "exec" {
|
||||
runExec(storeInfo, cmd)
|
||||
}
|
||||
if cmd.Name == "writeFile" {
|
||||
err := WriteFile(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write to file: %w", err)
|
||||
}
|
||||
}
|
||||
if cmd.Name == "deleteFile" {
|
||||
err := DeleteFile(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete file: %w", err)
|
||||
}
|
||||
}
|
||||
if cmd.Name == "unzip" {
|
||||
err := Unzip(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unzip file: %w", err)
|
||||
}
|
||||
}
|
||||
if cmd.Name == "zip" {
|
||||
err := Zip(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unzip file: %w", err)
|
||||
}
|
||||
}
|
||||
if cmd.Name == "mkdir" {
|
||||
err := MkDir(cmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create directory: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func runStop(storeInfo StoreInfo) error {
|
||||
return progress.StopCmd(storeInfo.Name)
|
||||
}
|
||||
func runStart(storeInfo StoreInfo) error {
|
||||
err := SetEnvs(storeInfo.Start.Envs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set start environment variable %s: %w", storeInfo.Name, err)
|
||||
}
|
||||
if len(storeInfo.Start.Cmds) > 0 {
|
||||
if !libs.PathExists(storeInfo.Setting.BinPath) {
|
||||
return fmt.Errorf("script file %s does not exist", storeInfo.Setting.BinPath)
|
||||
}
|
||||
cmd := exec.Command(storeInfo.Setting.BinPath, storeInfo.Start.Cmds...)
|
||||
if runtime.GOOS == "windows" {
|
||||
// 在Windows上,通过设置CreationFlags来隐藏窗口
|
||||
cmd = progress.SetHideConsoleCursor(cmd)
|
||||
}
|
||||
// 启动脚本命令并返回可能的错误
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("failed to start process %s: %w", storeInfo.Name, err)
|
||||
}
|
||||
|
||||
progress.RegisterProcess(storeInfo.Name, cmd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func runRestart(storeInfo StoreInfo) error {
|
||||
err := runStop(storeInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to stop process %s: %w", storeInfo.Name, err)
|
||||
}
|
||||
return runStart(storeInfo)
|
||||
}
|
||||
func runExec(storeInfo StoreInfo, cmdParam Cmd) error {
|
||||
err := SetEnvs(cmdParam.Envs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set start environment variable %s: %w", storeInfo.Name, err)
|
||||
}
|
||||
log.Printf("bin path:%v", cmdParam.BinPath)
|
||||
log.Printf("cmds:%v", cmdParam.Cmds)
|
||||
cmd := exec.Command(cmdParam.BinPath, cmdParam.Cmds...)
|
||||
if runtime.GOOS == "windows" {
|
||||
// 在Windows上,通过设置CreationFlags来隐藏窗口
|
||||
cmd = progress.SetHideConsoleCursor(cmd)
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("failed to run exec process %s: %w", storeInfo.Name, err)
|
||||
}
|
||||
if cmdParam.Waiting {
|
||||
if err = cmd.Wait(); err != nil {
|
||||
return fmt.Errorf("failed to wait for exec process %s: %w", storeInfo.Name, err)
|
||||
}
|
||||
}
|
||||
log.Printf("run exec process %s, name is %s", storeInfo.Name, cmdParam.Name)
|
||||
return nil
|
||||
}
|
||||
func WriteFile(cmd Cmd) error {
|
||||
if cmd.FilePath != "" {
|
||||
content := cmd.Content
|
||||
if content != "" {
|
||||
err := os.WriteFile(cmd.FilePath, []byte(content), 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write to file: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func DeleteFile(cmd Cmd) error {
|
||||
if cmd.FilePath != "" {
|
||||
err := os.Remove(cmd.FilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete file: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func MkDir(cmd Cmd) error {
|
||||
err := os.MkdirAll(cmd.FilePath, os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to make dir: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func Unzip(cmd Cmd) error {
|
||||
return files.Decompress(cmd.FilePath, cmd.Content)
|
||||
}
|
||||
func Zip(cmd Cmd) error {
|
||||
return files.Encompress(cmd.FilePath, cmd.Content)
|
||||
}
|
||||
func SetEnvs(envs []Item) error {
|
||||
if len(envs) > 0 {
|
||||
for _, item := range envs {
|
||||
strValue := convertValueToString(item.Value)
|
||||
if strValue != "" {
|
||||
err := os.Setenv(item.Name, strValue)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set environment variable %s: %w", item.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func SaveStoreInfo(storeInfo StoreInfo, infoFile string) error {
|
||||
// 使用 json.MarshalIndent 直接获取内容的字节切片
|
||||
content, err := json.MarshalIndent(storeInfo, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal reqBodies to JSON: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(infoFile, content, 0644); err != nil {
|
||||
return fmt.Errorf("failed to write to file: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func SaveStoreConfig(storeInfo StoreInfo, exePath string) error {
|
||||
content, err := os.ReadFile(storeInfo.Setting.ConfPath + ".tpl")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read tpl file: %w", err)
|
||||
}
|
||||
contentstr := strings.ReplaceAll(string(content), "{exePath}", exePath)
|
||||
contentstr = ChangeConfig(storeInfo, contentstr)
|
||||
if err := os.WriteFile(storeInfo.Setting.ConfPath, []byte(contentstr), 0644); err != nil {
|
||||
return fmt.Errorf("failed to write to file: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func ChangeConfig(storeInfo StoreInfo, contentstr string) string {
|
||||
pattern := regexp.MustCompile(`\{(\w+)\}`)
|
||||
for key, value := range storeInfo.Config {
|
||||
strValue := convertValueToString(value)
|
||||
contentstr = pattern.ReplaceAllStringFunc(contentstr, func(match string) string {
|
||||
if strings.Trim(match, "{}") == key {
|
||||
return strValue
|
||||
}
|
||||
return match
|
||||
})
|
||||
}
|
||||
return contentstr
|
||||
}
|
||||
|
||||
func replacePlaceholdersInCmds(storeInfo *StoreInfo) {
|
||||
// 创建一个正则表达式模式,用于匹配形如{key}的占位符
|
||||
pattern := regexp.MustCompile(`\{(\w+)\}`)
|
||||
|
||||
// 遍历Config中的所有键值对
|
||||
for key, value := range storeInfo.Config {
|
||||
// 遍历Cmds中的所有命令组
|
||||
for cmdGroupName, cmds := range storeInfo.Cmds {
|
||||
// 遍历每个命令组中的所有Cmd
|
||||
for i, cmd := range cmds {
|
||||
strValue := convertValueToString(value)
|
||||
// 使用正则表达式替换Content中的占位符
|
||||
storeInfo.Cmds[cmdGroupName][i].Content = pattern.ReplaceAllStringFunc(
|
||||
cmd.Content,
|
||||
func(match string) string {
|
||||
// 检查占位符是否与Config中的键匹配
|
||||
if strings.Trim(match, "{}") == key {
|
||||
return strValue
|
||||
}
|
||||
return match
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package sys
|
||||
package store
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
|
@ -61,7 +61,7 @@ func UpdateAppHandler(w http.ResponseWriter, r *http.Request) {
|
|||
defer resp.Body.Close()
|
||||
pr := &ProgressReader{reader: resp.Body}
|
||||
|
||||
ticker := time.NewTicker(250 * time.Millisecond)
|
||||
ticker := time.NewTicker(200 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
flusher, ok := w.(http.Flusher)
|
||||
if !ok {
|
||||
|
|
|
|||