add progress

This commit is contained in:
godo 2024-07-30 08:12:52 +08:00
parent 389cffec76
commit d8839f9138
41 changed files with 905 additions and 444 deletions

View file

@ -71,6 +71,7 @@
- 文件拖拽上传 - 文件拖拽上传
- 文件搜索 - 文件搜索
- 原生文件存储 - 原生文件存储
- 直接压缩/解压文件夹本地存储支持zip/tar/gz/bz2
<img src="./docs/img/file.png" width="600" /> <img src="./docs/img/file.png" width="600" />
### 三、内网聊天 ### 三、内网聊天

View file

@ -52,6 +52,7 @@ declare module 'vue' {
ElInput: typeof import('element-plus/es')['ElInput'] ElInput: typeof import('element-plus/es')['ElInput']
ElOption: typeof import('element-plus/es')['ElOption'] ElOption: typeof import('element-plus/es')['ElOption']
ElPopover: typeof import('element-plus/es')['ElPopover'] ElPopover: typeof import('element-plus/es')['ElPopover']
ElProgress: typeof import('element-plus/es')['ElProgress']
ElRow: typeof import('element-plus/es')['ElRow'] ElRow: typeof import('element-plus/es')['ElRow']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect'] ElSelect: typeof import('element-plus/es')['ElSelect']

View file

@ -54,6 +54,30 @@
<div class="content unicode" style="display: block;"> <div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box"> <ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe623;</span>
<div class="name">zip</div>
<div class="code-name">&amp;#xe623;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe8b0;</span>
<div class="name">gz</div>
<div class="code-name">&amp;#xe8b0;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe620;</span>
<div class="name">tar</div>
<div class="code-name">&amp;#xe620;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe65e;</span>
<div class="name">bz2</div>
<div class="code-name">&amp;#xe65e;</div>
</li>
<li class="dib"> <li class="dib">
<span class="icon iconfont">&#xe65f;</span> <span class="icon iconfont">&#xe65f;</span>
<div class="name">chat</div> <div class="name">chat</div>
@ -672,9 +696,9 @@
<pre><code class="language-css" <pre><code class="language-css"
>@font-face { >@font-face {
font-family: 'iconfont'; font-family: 'iconfont';
src: url('iconfont.woff2?t=1718176711902') format('woff2'), src: url('iconfont.woff2?t=1722214929182') format('woff2'),
url('iconfont.woff?t=1718176711902') format('woff'), url('iconfont.woff?t=1722214929182') format('woff'),
url('iconfont.ttf?t=1718176711902') format('truetype'); url('iconfont.ttf?t=1722214929182') format('truetype');
} }
</code></pre> </code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3> <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@ -700,6 +724,42 @@
<div class="content font-class"> <div class="content font-class">
<ul class="icon_lists dib-box"> <ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont icon-zip"></span>
<div class="name">
zip
</div>
<div class="code-name">.icon-zip
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-gz"></span>
<div class="name">
gz
</div>
<div class="code-name">.icon-gz
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-tar"></span>
<div class="name">
tar
</div>
<div class="code-name">.icon-tar
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-bz2"></span>
<div class="name">
bz2
</div>
<div class="code-name">.icon-bz2
</div>
</li>
<li class="dib"> <li class="dib">
<span class="icon iconfont icon-chat"></span> <span class="icon iconfont icon-chat"></span>
<div class="name"> <div class="name">
@ -1627,6 +1687,38 @@
<div class="content symbol"> <div class="content symbol">
<ul class="icon_lists dib-box"> <ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-zip"></use>
</svg>
<div class="name">zip</div>
<div class="code-name">#icon-zip</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-gz"></use>
</svg>
<div class="name">gz</div>
<div class="code-name">#icon-gz</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-tar"></use>
</svg>
<div class="name">tar</div>
<div class="code-name">#icon-tar</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-bz2"></use>
</svg>
<div class="name">bz2</div>
<div class="code-name">#icon-bz2</div>
</li>
<li class="dib"> <li class="dib">
<svg class="icon svg-icon" aria-hidden="true"> <svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-chat"></use> <use xlink:href="#icon-chat"></use>

View file

@ -1,8 +1,8 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 4412427 */ font-family: "iconfont"; /* Project id 4412427 */
src: url('iconfont.woff2?t=1718176711902') format('woff2'), src: url('iconfont.woff2?t=1722214929182') format('woff2'),
url('iconfont.woff?t=1718176711902') format('woff'), url('iconfont.woff?t=1722214929182') format('woff'),
url('iconfont.ttf?t=1718176711902') format('truetype'); url('iconfont.ttf?t=1722214929182') format('truetype');
} }
.iconfont { .iconfont {
@ -13,6 +13,22 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-zip:before {
content: "\e623";
}
.icon-gz:before {
content: "\e8b0";
}
.icon-tar:before {
content: "\e620";
}
.icon-bz2:before {
content: "\e65e";
}
.icon-chat:before { .icon-chat:before {
content: "\e65f"; content: "\e65f";
} }

File diff suppressed because one or more lines are too long

View file

@ -5,6 +5,34 @@
"css_prefix_text": "icon-", "css_prefix_text": "icon-",
"description": "", "description": "",
"glyphs": [ "glyphs": [
{
"icon_id": "30862364",
"name": "zip",
"font_class": "zip",
"unicode": "e623",
"unicode_decimal": 58915
},
{
"icon_id": "10135229",
"name": "gz",
"font_class": "gz",
"unicode": "e8b0",
"unicode_decimal": 59568
},
{
"icon_id": "21942534",
"name": "tar",
"font_class": "tar",
"unicode": "e620",
"unicode_decimal": 58912
},
{
"icon_id": "40271559",
"name": "bz2",
"font_class": "bz2",
"unicode": "e65e",
"unicode_decimal": 58974
},
{ {
"icon_id": "9442749", "icon_id": "9442749",
"name": "chat", "name": "chat",

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,7 +1,7 @@
[ [
{ {
"name": "mysql5.7", "name": "mysql5.7",
"url": "https://gitee.com/ruitao_admin/godoos-mysql5.7/releases/download/mysql5.7/mysql-windows-5.7.7z", "url": "https://gitee.com/ruitao_admin/godoos-mysql5.7/releases/download/mysql5.7/mysql5.7.zip",
"needDownload": true, "needDownload": true,
"needInstall": true, "needInstall": true,
"version": "5.7.37", "version": "5.7.37",
@ -17,5 +17,35 @@
} }
} }
] ]
},
{
"name": "nginx",
"url": "https://gitee.com/ruitao_admin/godoos-mysql5.7/releases/download/mysql5.7andredis5.0/nginx.zip",
"needDownload": true,
"needInstall": true,
"version": "1.25.3",
"icon": "https://p9.itc.cn/q_70/images03/20211130/a01ca555bc16455cbb2b50aed44fb64f.jpeg",
"checkProgress": true,
"hasRestart": true
},
{
"name": "redis5.0",
"url": "https://gitee.com/ruitao_admin/godoos-mysql5.7/releases/download/mysql5.7andredis5.0/redis5.0.zip",
"needDownload": true,
"needInstall": true,
"version": "5.0.0",
"icon": "https://img1.baidu.com/it/u=4217735613,2025794445&fm=253&fmt=auto&app=138&f=JPEG?w=293&h=247",
"checkProgress": true,
"hasRestart": true
},
{
"name": "Notepad3",
"url": "https://gitee.com/ruitao_admin/godoos-mysql5.7/releases/download/mysql5.7andredis5.0/Notepad3.zip",
"needDownload": true,
"needInstall": true,
"version": "3.0.0",
"icon": "https://www.waerfa.com/images/qfinx.png",
"checkProgress": true,
"hasRestart": true
} }
] ]

View file

@ -27,16 +27,16 @@
'mode-detail': mode === 'detail', 'mode-detail': mode === 'detail',
'drag-over': hoverIndex === index, 'drag-over': hoverIndex === index,
}" :style="{ }" :style="{
'--theme-color': theme === 'light' ? '#ffffff6b' : '#3bdbff3d', '--theme-color': theme === 'light' ? '#ffffff6b' : '#3bdbff3d',
}" v-for="(item, index) in fileList" :key="item.path" @dblclick="handleOnOpen(item)" }" v-for="(item, index) in fileList" :key="item.path" @dblclick="handleOnOpen(item)"
@touchstart.passive="doubleTouch($event, item)" @contextmenu.stop.prevent="handleRightClick($event, item, index)" @touchstart.passive="doubleTouch($event, item)" @contextmenu.stop.prevent="handleRightClick($event, item, index)"
@drop="hadnleDrop($event, item.path)" @dragenter.prevent="handleDragEnter(index)" @dragover.prevent @drop="hadnleDrop($event, item.path)" @dragenter.prevent="handleDragEnter(index)" @dragover.prevent
@dragleave="handleDragLeave()" @dragstart.stop="startDragApp($event, item)" @click="handleClick(index)" @dragleave="handleDragLeave()" @dragstart.stop="startDragApp($event, item)" @click="handleClick(index)"
@mousedown.stop :ref="(ref: any) => { @mousedown.stop :ref="(ref: any) => {
if (ref) { if (ref) {
appPositions[index] = markRaw(ref as Element); appPositions[index] = markRaw(ref as Element);
}
} }
}
"> ">
<div class="file-item_img"> <div class="file-item_img">
<FileIcon :file="item" /> <FileIcon :file="item" />
@ -64,7 +64,8 @@
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useSystem, basename, dirname, join, OsFileWithoutContent } from '@/system/index.ts'; import { useSystem, basename, dirname, join, OsFileWithoutContent, Notify } from '@/system/index.ts';
import { getSystemKey } from '@/system/config'
import { emitEvent, mountEvent } from '@/system/event'; import { emitEvent, mountEvent } from '@/system/event';
import { useContextMenu } from '@/hook/useContextMenu.ts'; import { useContextMenu } from '@/hook/useContextMenu.ts';
import { t, dealSystemName } from '@/i18n'; import { t, dealSystemName } from '@/i18n';
@ -228,9 +229,26 @@ function handleRightClick(mouse: MouseEvent, item: OsFileWithoutContent, index:
} }
const ext = item.name.split(".").pop(); const ext = item.name.split(".").pop();
const zipSucess = (res: any) => {
if (!res || res.code < 0) {
new Notify({
title: t('tips'),
content: t('error'),
});
} else {
props.onRefresh();
new Notify({
title: t('tips'),
content: t('file.zip.success'),
});
if (item.parentPath == '/C/Users/Desktop') {
sys.refershAppList()
}
}
};
// eslint-disable-next-line prefer-const // eslint-disable-next-line prefer-const
let menuArr = [ let menuArr: any = [
{ {
label: t('open'), label: t('open'),
click: () => { click: () => {
@ -247,6 +265,49 @@ function handleRightClick(mouse: MouseEvent, item: OsFileWithoutContent, index:
// }, // },
]; ];
if (item.isDirectory) {
if (getSystemKey('storeType') == 'local') {
menuArr.push({
label: t('zip'),
submenu: [
{
label: 'zip',
click: () => {
sys.fs.zip(item.path, 'zip').then((res: any) => {
zipSucess(res)
});
},
},
{
label: 'tar',
click: () => {
sys.fs.zip(item.path, 'tar').then((res: any) => {
zipSucess(res)
});
},
},
{
label: 'gz',
click: () => {
sys.fs.zip(item.path, 'gz').then((res: any) => {
zipSucess(res)
});
},
},
],
})
} else {
menuArr.push({
label: t('zip'),
click: () => {
sys.fs.zip(item.path, 'zip').then((res: any) => {
zipSucess(res)
});
},
})
}
}
if (choose.ifShow) { if (choose.ifShow) {
menuArr.push({ menuArr.push({
label: t('selected'), label: t('selected'),
@ -269,7 +330,7 @@ function handleRightClick(mouse: MouseEvent, item: OsFileWithoutContent, index:
}) })
} }
// eslint-disable-next-line prefer-const // eslint-disable-next-line prefer-const
let extMenus = useAppMenu(item, index); let extMenus = useAppMenu(item, sys, props);
if (extMenus && extMenus.length > 0) { if (extMenus && extMenus.length > 0) {
// eslint-disable-next-line prefer-spread // eslint-disable-next-line prefer-spread
menuArr.push.apply(menuArr, extMenus) menuArr.push.apply(menuArr, extMenus)
@ -295,22 +356,17 @@ function handleRightClick(mouse: MouseEvent, item: OsFileWithoutContent, index:
{ {
label: t('delete'), label: t('delete'),
click: async () => { click: async () => {
// await Promise.all(
// chosenIndexs.value.map((index) => {
// const item = props.fileList[index];
// deleteFile(item);
// props.onRefresh();
// return true;
// })
// );
for (let i = 0; i < chosenIndexs.value.length; i++) { for (let i = 0; i < chosenIndexs.value.length; i++) {
await deleteFile(props.fileList[chosenIndexs.value[i]]); await deleteFile(props.fileList[chosenIndexs.value[i]]);
} }
chosenIndexs.value = []; chosenIndexs.value = [];
props.onRefresh(); props.onRefresh();
sys.refershAppList() if (item.parentPath == '/C/Users/Desktop') {
sys.refershAppList()
}
}, },
} },
]; ];
// eslint-disable-next-line prefer-spread // eslint-disable-next-line prefer-spread
menuArr.push.apply(menuArr, fileMenus) menuArr.push.apply(menuArr, fileMenus)

View file

@ -57,15 +57,11 @@
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
// import WinButton from '@/components/win/WinButton.vue';
import { ref } from "vue"; import { ref } from "vue";
// import { useSystem } from '@/system';
// import { Dialog } from '@/dialog/Dialog';
import { t } from "@/i18n"; import { t } from "@/i18n";
import { getSystemKey } from "@/system/config"; import { getSystemKey } from "@/system/config";
// const system = useSystem(); // const system = useSystem();
const items = [ const items = [
// t('backup'), // '',
t("version"), // '', t("version"), // '',
]; ];
const version = getSystemKey("version"); const version = getSystemKey("version");
@ -75,58 +71,6 @@ const selectItem = (index: number) => {
activeIndex.value = index; activeIndex.value = index;
}; };
// async function handleClick(num: number) {
// if (num === 0) {
// //
// const cfg = await system?.serializeState();
// try {
// await navigator.clipboard.writeText(cfg!);
// Dialog.showMessageBox({
// title: t('export.config'),
// message: t('export.success.saved.to.clipboard'),
// type: 'info',
// buttons: [t('confirm')],
// });
// } catch (err) {
// Dialog.showMessageBox({
// title: t('export.config'),
// message: t('export.failed'),
// type: 'error',
// buttons: [t('confirm')],
// });
// }
// } else if (num === 1) {
// //
// try {
// const req = await Dialog.showMessageBox({
// title: t('import.config'),
// // message: ',?',
// message: t('import.config.will.cover.existing.files.continue'),
// type: 'warning',
// buttons: [t('confirm'), t('cancel')],
// });
// if (req.response === 1) return;
// await system?.deserializeState(inputConfig.value);
// setTimeout(() => {
// system?.reboot();
// }, 10000);
// await Dialog.showMessageBox({
// title: t('import.success'),
// message: t('import.success.reboot.soon'),
// type: 'warning',
// buttons: [t('confirm')],
// });
// system?.reboot();
// } catch (err) {
// Dialog.showMessageBox({
// title: t('import.config'),
// message: t('import.failed'),
// type: 'error',
// buttons: [t('confirm')],
// });
// }
// }
// }
</script> </script>
<style scoped> <style scoped>

View file

@ -1,24 +1,28 @@
<template> <template>
<div class="app-item"> <div class="app-item">
<img <img v-if="item?.icon" draggable="false" class="app-img" :src="item?.icon" alt="" />
v-if="item?.icon"
draggable="false"
class="app-img"
:src="item?.icon"
alt=""
/>
<div class="app-content"> <div class="app-content">
<div class="app-title">{{ item?.name }}</div> <div class="app-title">{{ item?.name }}</div>
<div class="app-desc">{{ item?.desc ?? item?.name }}</div> <div class="app-desc">
<el-progress :text-inside="true" style="margin-top: 3px;" v-if="item?.progress" :stroke-width="20"
:percentage="item?.progress" />
</div>
<div class="app-button"> <div class="app-button">
<div
v-if="installedList!.includes(item?.name)" <template v-if="installedList!.includes(item?.name)">
> <div v-if="item!.checkProgress">
<button @click="uninstall?.(item)">卸载</button> <button @click="pause?.(item)" v-if="item?.isRuning">暂停</button>
</div> <button @click="start?.(item)" v-else>启动</button>
<div v-else> </div>
<button v-if="item?.url" @click="install?.(item)">安装</button> <div v-if="item!.hasRestart && item?.isRuning">
</div> <button @click="restart?.(item)">重启</button>
</div>
<button @click="uninstall?.(item)" v-if="!item?.isRuning">卸载</button>
</template>
<template v-else>
<button v-if="item?.url" @click="install?.(item)" :disabled="item?.progress">安装</button>
</template>
</div> </div>
</div> </div>
</div> </div>
@ -29,6 +33,9 @@ defineProps({
installedList: Array, installedList: Array,
install: Function, install: Function,
uninstall: Function, uninstall: Function,
pause: Function,
start: Function,
restart: Function
}); });
</script> </script>
<style scoped> <style scoped>
@ -53,6 +60,7 @@ defineProps({
border: 1px solid #cccccc7b; border: 1px solid #cccccc7b;
user-select: none; user-select: none;
} }
.app-content { .app-content {
margin-left: 10px; margin-left: 10px;
display: flex; display: flex;
@ -62,32 +70,39 @@ defineProps({
height: 80px; height: 80px;
width: 100%; width: 100%;
} }
.app-title { .app-title {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
padding-bottom: 6px; padding-bottom: 6px;
user-select: none; user-select: none;
} }
.app-desc { .app-desc {
font-size: 12px; font-size: 12px;
color: #666; color: #666;
user-select: none; user-select: none;
} }
.app-item:hover { .app-item:hover {
box-shadow: 0 0 20px 2px #dddddd5b; box-shadow: 0 0 20px 2px #dddddd5b;
transform: translateY(-2px); transform: translateY(-2px);
} }
.app-item:active { .app-item:active {
box-shadow: 0 0 0px #ccc; box-shadow: 0 0 0px #ccc;
transform: translateY(0px); transform: translateY(0px);
} }
.app-button { .app-button {
width: 100%; width: 100%;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: flex-end; justify-content: flex-end;
align-items: center; align-items: center;
gap: 10px;
} }
.app-button button { .app-button button {
width: 60px; width: 60px;
height: 30px; height: 30px;
@ -97,10 +112,12 @@ defineProps({
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all 0.2s;
} }
.app-button button:hover { .app-button button:hover {
box-shadow: 0 0 10px #ccc; box-shadow: 0 0 10px #ccc;
/* transform: translateY(-2px); */ /* transform: translateY(-2px); */
} }
.app-button button:active { .app-button button:active {
box-shadow: 0 0 0px #ccc; box-shadow: 0 0 0px #ccc;
transform: translateY(0px); transform: translateY(0px);

View file

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { inject, onMounted, ref } from "vue"; import { inject, onMounted, ref } from "vue";
import { System } from "@/system"; import { System } from "@/system";
import {notifySuccess, notifyError} from "@/util/msg"; import { notifySuccess, notifyError } from "@/util/msg";
import { t } from "@/i18n"; import { t } from "@/i18n";
import storeInitList from "@/assets/store.json"; import storeInitList from "@/assets/store.json";
import { getSystemKey, setSystemKey, parseJson } from "@/system/config"; import { getSystemKey, setSystemKey, parseJson } from "@/system/config";
@ -17,15 +17,15 @@ const apiUrl = getSystemKey("apiUrl");
const installedList: any = ref(installed); const installedList: any = ref(installed);
const storeList: any = ref([]) const storeList: any = ref([])
onMounted(async() => { onMounted(async () => {
await getList(); await getList();
}); });
async function getList(){ async function getList() {
if(currentCate.value == 'add')return; if (currentCate.value == 'add') return;
storeList.value = storeInitList storeList.value = storeInitList
// const apiUrl = getSystemKey("apiUrl"); // const apiUrl = getSystemKey("apiUrl");
// const storeUrl = apiUrl + '/system/storeList?cate=' + currentCate.value // const storeUrl = apiUrl + '/store/storelist?cate=' + currentCate.value
// fetch(storeUrl).then(res => { // fetch(storeUrl).then(res => {
// res.json().then(data => { // res.json().then(data => {
// storeList.value = data // storeList.value = data
@ -33,8 +33,28 @@ async function getList(){
// }).catch(() => { // }).catch(() => {
// notifyError(t("store.errorList")) // notifyError(t("store.errorList"))
// }) // })
await checkProgress()
isready.value = true; isready.value = true;
} }
async function checkProgress() {
const completion: any = await fetch(apiUrl + '/store/listporgress')
if (!completion.ok) {
return
}
let res:any = await completion.json()
if(!res || res.length < 1){
res = []
}
storeList.value.forEach((item: any, index: number) => {
const pitem: any = res.find((i: any) => i.name == item.name)
console.log(pitem)
if (pitem) {
storeList.value[index].isRuning = pitem.running
}else{
storeList.value[index].isRuning = false
}
})
}
async function changeCate(index: number, item: string) { async function changeCate(index: number, item: string) {
currentCateId.value = index currentCateId.value = index
currentCate.value = item currentCate.value = item
@ -48,26 +68,63 @@ function setCache() {
}, 1000); }, 1000);
} }
async function install(item: any) { async function install(item: any) {
if(item.needDownload){ if (item.needDownload) {
await download(item) await download(item)
} }
if(item.needInstall){ if (item.needInstall) {
item.progress = 0
const completion = await fetch(apiUrl + '/store/install?name=' + item.name)
if (!completion.ok) {
notifyError(t("store.installError"))
return
}
const res = await completion.json()
if (res.code && res.code < 0) {
notifyError(res.message)
return
}
} }
if(item.url){ if(item.isOut){
item.progress = 0
const completion = await fetch(apiUrl + '/store/installOut?url=' + item.url)
if (!completion.ok) {
notifyError(t("store.installError"))
return
}
const res = await completion.json()
if (res.code && res.code < 0) {
notifyError(res.message)
return
}
}
if (item.isWeb) {
sys.fs.writeFile( sys.fs.writeFile(
`${sys._options.userLocation}Desktop/${item.name}.url`, `${sys._options.userLocation}Desktop/${item.name}.url`,
`link::url::${item.url}::${item.icon}` `link::url::${item.url}::${item.icon}`
); );
} }
notifySuccess(t("install.success")) notifySuccess(t("install.success"))
installedList.value.push(item.name); installedList.value.push(item.name);
await checkProgress()
setCache(); setCache();
} }
function uninstall(item: any) { async function uninstall(item: any) {
if (item.needInstall) {
item.progress = 0
const completion = await fetch(apiUrl + '/store/uninstall?name=' + item.name)
if (!completion.ok) {
notifyError(t("store.installError"))
return
}
const res = await completion.json()
if (res.code && res.code < 0) {
notifyError(res.message)
return
}
}
sys.fs.unlink(`${sys._options.userLocation}Desktop/${item.name}.url`); sys.fs.unlink(`${sys._options.userLocation}Desktop/${item.name}.url`);
notifySuccess(t("uninstall.success")) notifySuccess(t("uninstall.success"))
delete installedList.value[installedList.value.indexOf(item.name)]; delete installedList.value[installedList.value.indexOf(item.name)];
@ -75,12 +132,13 @@ function uninstall(item: any) {
} }
async function download(item: any) { async function download(item: any) {
//console.log(item) //console.log(item)
if (item.progress) return;
const completion = await fetch(apiUrl + '/store/download?url=' + item.url) const completion = await fetch(apiUrl + '/store/download?url=' + item.url)
if(!completion.ok){ if (!completion.ok) {
notifyError(t("store.downloadError")) notifyError(t("store.downloadError"))
} }
//console.log(completion) //console.log(completion)
const reader:any = completion.body?.getReader(); const reader: any = completion.body?.getReader();
if (!reader) { if (!reader) {
notifyError(t("store.cantStream")); notifyError(t("store.cantStream"));
} }
@ -89,19 +147,47 @@ async function download(item: any) {
if (done) { if (done) {
break; break;
} }
// console.log(value) // console.log(value)
const json = await new TextDecoder().decode(value); const json = await new TextDecoder().decode(value);
//console.log(json) //console.log(json)
const res = parseJson(json) const res = parseJson(json)
console.log(res) console.log(res)
if(res){ if (res) {
if(res.done) { if (res.progress) {
item.progress = res.progress
}
if (res.done) {
notifySuccess(t("store.downloadSuccess")) notifySuccess(t("store.downloadSuccess"))
item.progress = 0
break; break;
} }
} }
} }
} }
async function pauseApp(item: any) {
const res:any = await fetch(apiUrl + '/store/stop/' + item.name)
if(!res.ok){
const msg = await res.text()
notifyError(msg)
return
}
setTimeout(async() => {
await checkProgress()
}, 1000)
}
async function restartApp(item: any) {
await fetch(apiUrl + '/store/restart/' + item.name)
setTimeout(async() => {
await checkProgress()
}, 1000)
}
async function startApp(item: any) {
await fetch(apiUrl + '/store/start/' + item.name)
setTimeout(async() => {
await checkProgress()
}, 1000)
}
</script> </script>
<template> <template>
<div class="outer"> <div class="outer">
@ -138,10 +224,11 @@ async function download(item: any) {
</div> </div>
</div> --> </div> -->
<div class="main-app"> <div class="main-app">
<div v-for="item in storeList" v-if="currentCate != 'add'" class="store-item" :key="item.name"> <div v-for="item in storeList" v-if="currentCate != 'add'" class="store-item" :key="item.name">
<AppItem :item="item" :installed-list="installedList" :install="install" :uninstall="uninstall" /> <AppItem :item="item" :installed-list="installedList" :install="install" :uninstall="uninstall"
</div> :pause="pauseApp" :start="startApp" :restart="restartApp" />
<AddApp v-else/> </div>
<AddApp v-else />
</div> </div>
</div> </div>

View file

@ -13,7 +13,7 @@ const router = useRouter();
const props = defineProps<{ const props = defineProps<{
win: any; win: any;
}>(); }>();
const win = ref(props.win)
if (props.win.path) { if (props.win.path) {
router.push(props.win.path); router.push(props.win.path);
} }

View file

@ -1,45 +1,54 @@
import { OsFileWithoutContent } from '@/system/core/FileSystem'; import { OsFileWithoutContent, BrowserWindow, Notify } from '@/system';
import { BrowserWindow } from '@/system/window/BrowserWindow'; import { t } from "@/i18n"
export function useAppMenu(item: OsFileWithoutContent, _: number) { export function useAppMenu(item: OsFileWithoutContent, sys: any, props: any) {
let menuArr: any = []; let menuArr: any = [];
const ext:any = item.name.split(".").pop(); const ext: any = item.name.split(".").pop();
const picExt = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'tiff']; const picExt = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'tiff'];
if (picExt.includes(ext)) { if (picExt.includes(ext)) {
menuArr.push({ menuArr.push({
label: '编辑', label: t('piceditor'),
click: () => { click: () => {
const photoWindow = new BrowserWindow({ const photoWindow = new BrowserWindow({
width: 800, width: 800,
height: 600, height: 600,
icon: "picedit", icon: "picedit",
center: true, center: true,
title: '图像绘图', title: t('piceditor'),
url: "/picedit/index.html", url: "/picedit/index.html",
config: item config: item
}); });
photoWindow.show() photoWindow.show()
}, },
},) },)
} }
// const fileExt = ['txt', 'html', 'json', 'xml', 'css', 'js','vue','go','php','java','py'] const zipExt = ['zip', 'tar', 'gz', 'bz2']
// if (fileExt.includes(ext)) { const unzipSucess = (res: any) => {
// menuArr.push({ //console.log(res)
// label: '文本编辑', if (!res || res.code < 0) {
// click: () => { new Notify({
// const textWindow = new BrowserWindow({ title: t('tips'),
// width: 800, content: t('error'),
// height: 600, });
// icon: "editorbt", } else {
// center: true, props.onRefresh();
// title: '文件编辑', new Notify({
// url: "/text/index.html", title: t('tips'),
// config: item content: t('file.unzip.success'),
// }); });
// textWindow.show() }
// },
// },)
// }
};
if (zipExt.includes(ext)) {
menuArr.push({
label: t('unzip'),
click: () => {
//console.log(item.path)
sys.fs.unzip(item.path).then((res: any) => {
unzipSucess(res)
})
},
},)
}
return menuArr; return menuArr;
} }
//export { useAppMenu }; //export { useAppMenu };

View file

@ -44,6 +44,7 @@ function useContextMenu() {
callback?.(); callback?.();
}); });
} }
//console.log(path) //console.log(path)
let menuArr = [ let menuArr = [
{ {
@ -72,10 +73,6 @@ function useContextMenu() {
label: '数据表格(Excel)', label: '数据表格(Excel)',
click: () => clickFunc('xlsx', '未命名数据表格'), click: () => clickFunc('xlsx', '未命名数据表格'),
}, },
// {
// label: '笔记(Note)',
// click: () => clickFunc('note', '未命名笔记'),
// },
{ {
label: '文本文件(txt)', label: '文本文件(txt)',
click: () => clickFunc('txt', '未命名文本文件'), click: () => clickFunc('txt', '未命名文本文件'),
@ -97,36 +94,13 @@ function useContextMenu() {
label: '白板(Baiban)', label: '白板(Baiban)',
click: () => clickFunc('bb', '未命名白板'), click: () => clickFunc('bb', '未命名白板'),
}, },
// {
// label: '工作绘图(Flow)',
// click: () => clickFunc('flow', '未命名工作绘图'),
// },
{ {
label: '甘特图(Gant)', label: '甘特图(Gant)',
click: () => clickFunc('gant', '未命名项目'), click: () => clickFunc('gant', '未命名项目'),
}, },
// {
// label: '原型图(Proto)',
// click: () => clickFunc('proto', '原型图'),
// },
// {
// label: '流程图(Flow)',
// click: () => clickFunc('mind', '流程图'),
// },
// {
// label: '泳道图(Uml)',
// click: () => clickFunc('uml', '泳道图'),
// },
// {
// label: '用例图(Case)',
// click: () => clickFunc('case', '泳道图'),
// },
// {
// label: '顺序图(Seq)',
// click: () => clickFunc('seq', '顺序图'),
// },
], ],
}, },
// { // {
// label: '新建报告', // label: '新建报告',
// submenu: [ // submenu: [

View file

@ -59,6 +59,8 @@
"tips": "Tips", "tips": "Tips",
"file.not.exist": "File Not Exist", "file.not.exist": "File Not Exist",
"file.save.success": "File Save Success", "file.save.success": "File Save Success",
"file.zip.success": "File Zip Success",
"file.unzip.success": "File Unzip Success",
"new": "New", "new": "New",
"back": "Back", "back": "Back",
"path.not.exist": "Path Not Exist", "path.not.exist": "Path Not Exist",

View file

@ -37,6 +37,8 @@
"backup": "备份", "backup": "备份",
"import.success.reboot.soon": "导入成功,即将重启", "import.success.reboot.soon": "导入成功,即将重启",
"import.failed": "导入失败", "import.failed": "导入失败",
"zip":"压缩",
"unzip":"解压",
"import": "导入", "import": "导入",
"export": "导出", "export": "导出",
"refresh": "刷新", "refresh": "刷新",
@ -58,6 +60,8 @@
"tips": "提示", "tips": "提示",
"file.not.exist": "文件不存在", "file.not.exist": "文件不存在",
"file.save.success": "文件保存成功", "file.save.success": "文件保存成功",
"file.zip.success": "文件压缩成功",
"file.unzip.success": "文件解压成功",
"new": "新建", "new": "新建",
"back": "返回", "back": "返回",
"path.not.exist": "路径不存在", "path.not.exist": "路径不存在",

View file

@ -187,19 +187,33 @@ export function handleWatch(path: string, callback: any, errback: any) {
errback("Your browser does not support server-sent events.") errback("Your browser does not support server-sent events.")
} }
} }
export async function handleZip(path: string, ext: string): Promise<any> {
const res = await fetch(`${API_BASE_URL}/zip?path=${encodeURIComponent(path)}&ext=${ext}`);
if (!res.ok) {
return false;
}
return await res.json();
}
export async function handleUnZip(path: string): Promise<any> {
const res = await fetch(`${API_BASE_URL}/unzip?path=${encodeURIComponent(path)}`);
if (!res.ok) {
return false;
}
return await res.json();
}
export const useOsFile = () => { export const useOsFile = () => {
return { return {
async readdir(path: string) { async readdir(path: string) {
const response = await handleReadDir(path); const response = await handleReadDir(path);
if (response && response.data) { if (response && response.data) {
return response.data; // 假设返回的JSON包含名为"data"的字段,存储了文件内容 return response.data;
} }
return []; return [];
}, },
async stat(path: string) { async stat(path: string) {
const response = await handleStat(path); const response = await handleStat(path);
if (response && response.data) { if (response && response.data) {
return response.data; // 假设返回的JSON包含名为"data"的字段,存储了文件内容 return response.data;
} }
return false; return false;
}, },
@ -207,81 +221,71 @@ export const useOsFile = () => {
const modes = osFileModeToOctal(mode) const modes = osFileModeToOctal(mode)
const response = await handleChmod(path, modes); const response = await handleChmod(path, modes);
if (response) { if (response) {
return response; // 假设返回的JSON包含操作状态或其他相关信息 return response;
} }
return false; return false;
}, },
async exists(path: string) { async exists(path: string) {
//return handleExists(path);
const response = await handleExists(path); const response = await handleExists(path);
if (response && response.data) { if (response && response.data) {
return response.data; // 假设返回的JSON包含名为"data"的字段,存储了文件内容 return response.data;
} }
return false; return false;
}, },
async readFile(path: string) { async readFile(path: string) {
// 注意handleReadFile返回的是JSON但通常readFile期望返回Buffer或字符串 // 注意handleReadFile返回的是JSON但通常readFile期望返回Buffer或字符串
// 根据你的后端API实际返回的数据类型调整此方法
const response = await handleReadFile(path); const response = await handleReadFile(path);
if (response && response.data) { if (response && response.data) {
return response.data; // 假设返回的JSON包含名为"data"的字段,存储了文件内容 return response.data;
} }
return false; return false;
}, },
async unlink(path: string) { async unlink(path: string) {
//return handleUnlink(path);
const response = await handleUnlink(path); const response = await handleUnlink(path);
if (response) { if (response) {
return response; // 假设返回的JSON包含名为"data"的字段,存储了文件内容 return response;
} }
return false; return false;
}, },
async rename(oldPath: string, newPath: string) { async rename(oldPath: string, newPath: string) {
//return handleRename(oldPath, newPath);
const response = await handleRename(oldPath, newPath); const response = await handleRename(oldPath, newPath);
if (response) { if (response) {
return response; // 假设返回的JSON包含名为"data"的字段,存储了文件内容 return response;
} }
return false; return false;
}, },
async rmdir(path: string) { async rmdir(path: string) {
//return handleRmdir(path);
const response = await handleRmdir(path); const response = await handleRmdir(path);
if (response) { if (response) {
return response; // 假设返回的JSON包含名为"data"的字段,存储了文件内容 return response;
} }
return false; return false;
}, },
async mkdir(path: string) { async mkdir(path: string) {
//return handleMkdir(path);
const response = await handleMkdir(path); const response = await handleMkdir(path);
if (response) { if (response) {
return response; // 假设返回的JSON包含名为"data"的字段,存储了文件内容 return response;
} }
return false; return false;
}, },
async copyFile(srcPath: string, dstPath: string) { async copyFile(srcPath: string, dstPath: string) {
//return handleCopyFile(srcPath, dstPath);
const response = await handleCopyFile(srcPath, dstPath); const response = await handleCopyFile(srcPath, dstPath);
if (response) { if (response) {
return response; // 假设返回的JSON包含名为"data"的字段,存储了文件内容 return response;
} }
return false; return false;
}, },
async writeFile(path: string, content: string | Blob) { async writeFile(path: string, content: string | Blob) {
//return handleWriteFile(path, content);
const response = await handleWriteFile(path, content); const response = await handleWriteFile(path, content);
if (response) { if (response) {
return response; // 假设返回的JSON包含名为"data"的字段,存储了文件内容 return response;
} }
return false; return false;
}, },
async appendFile(path: string, content: string | Blob) { async appendFile(path: string, content: string | Blob) {
//return handleAppendFile(path, content);
const response = await handleAppendFile(path, content); const response = await handleAppendFile(path, content);
if (response) { if (response) {
return response; // 假设返回的JSON包含名为"data"的字段,存储了文件内容 return response;
} }
return false; return false;
}, },
@ -289,6 +293,20 @@ export const useOsFile = () => {
console.log(path, options) console.log(path, options)
return true; return true;
}, },
async zip(path: string, ext: string) {
const response = await handleZip(path, ext);
if (response) {
return response;
}
return false;
},
async unzip(path: string) {
const response = await handleUnZip(path);
if (response) {
return response;
}
return false;
},
serializeFileSystem() { serializeFileSystem() {
return Promise.reject('fs 不支持序列化'); return Promise.reject('fs 不支持序列化');
}, },

View file

@ -4,6 +4,7 @@ import { SystemOptions } from '../type/type';
import { InitSystemFile, InitUserFile } from './SystemFileConfig'; import { InitSystemFile, InitUserFile } from './SystemFileConfig';
import { createInitFile } from './createInitFile'; import { createInitFile } from './createInitFile';
import { OsFileMode } from './FileMode'; import { OsFileMode } from './FileMode';
import JSZip from "jszip";
type DateLike = Date | string | number; type DateLike = Date | string | number;
// Os文件模式枚举 // Os文件模式枚举
class OsFileInfo { class OsFileInfo {
@ -405,7 +406,7 @@ class OsFileSystem implements OsFileInterface {
* @param path * @param path
* @returns * @returns
*/ */
async readFile(path: string): Promise<string | null> { async readFile(path: string): Promise<string | any> {
try { try {
const volume = this.checkVolumePath(path); const volume = this.checkVolumePath(path);
if (volume) { if (volume) {
@ -891,12 +892,13 @@ class OsFileSystem implements OsFileInterface {
const transedPath = fspath.transformPath(path); const transedPath = fspath.transformPath(path);
// 获取父路径 // 获取父路径
let parentPath = fspath.dirname(transedPath); let parentPath = fspath.dirname(transedPath);
// 如果父路径为空,设置为根路径 // 如果父路径为空,设置为根路径
if (parentPath === '') parentPath = '/'; if (parentPath === '') parentPath = '/';
// 判断文件是否存在 // 判断文件是否存在
const exists = await this.exists(parentPath); const exists = await this.exists(parentPath);
// 如果文件不存在,则抛出错误 // 如果文件不存在,则抛出错误
if (!exists) { if (!exists && path != parentPath) {
this.onerror('Cannot create directory to a non-exist path:' + parentPath); this.onerror('Cannot create directory to a non-exist path:' + parentPath);
return Promise.reject('Cannot create directory to a non-exist path:' + parentPath); return Promise.reject('Cannot create directory to a non-exist path:' + parentPath);
} }
@ -1072,6 +1074,75 @@ class OsFileSystem implements OsFileInterface {
}; };
}); });
} }
async zip(path: string, ext?: string) {
ext = 'zip';
const ziplibs = new JSZip();
const that = this;
async function dfsPackage(path: string, ziplibs: JSZip) {
const dir = await that.readdir(path);
for (let i = 0; i < dir.length; i++) {
const item = dir[i];
const stat = await that.stat(item.path);
if (stat) {
if (stat.isDirectory) {
await dfsPackage(item.path, ziplibs);
} else {
const content = await that.readFile(item.path);
try {
atob(content || "");
ziplibs.file(item.path, content || "");
} catch (error) {
ziplibs.file(item.path, content || "");
}
}
}
}
}
await dfsPackage(path, ziplibs);
const content = await ziplibs.generateAsync({ type: "blob" })
const targetPath = path + "." + ext;
await that.writeFile(targetPath, content);
return { code: 0 }
}
async unzip(path: string) {
const stat = await this.stat(path);
if (stat?.isDirectory) {
return { code: -1 }
}
const that = this;
try {
const content = await this.readFile(path);
const ziplibs = new JSZip();
const unziped = await ziplibs.loadAsync(content);
const targetPath = path.substring(0, path.lastIndexOf("."))
if (!await that.exists(targetPath)) {
await that.mkdir(targetPath)
}
const unzipArray: Array<JSZip.JSZipObject> = [];
unziped.forEach((_, zipEntry) => {
unzipArray.push(zipEntry);
});
for (let i = 0; i < unzipArray.length; i++) {
const zipEntry = unzipArray[i];
if (zipEntry.dir) {
await this.mkdir(zipEntry.name);
} else {
let fileC: any = await zipEntry.async("string");
if (!fileC.startsWith("link::")) {
fileC = await zipEntry.async("arraybuffer");
}
this.writeFile(zipEntry.name, fileC);
}
}
return { code: 0 }
} catch (error) {
console.log(error)
return { code: -1 }
}
}
} }
export { OsFile, OsFileInfo, OsFileSystem }; export { OsFile, OsFileInfo, OsFileSystem };

View file

@ -168,7 +168,7 @@ export function initBuiltinFileOpener(system: System) {
height: 600, height: 600,
center: true, center: true,
title: t("computer"), title: t("computer"),
content: "MyComputer", content: "Computer",
//icon: myComputerLogoIcon, //icon: myComputerLogoIcon,
icon: "computer", icon: "computer",
config: { config: {

View file

@ -1,7 +1,7 @@
import { OsFileSystem, OsFileWithoutContent } from '@/system/core/FileSystem'; import { OsFileSystem, OsFileWithoutContent } from '@/system/core/FileSystem';
import { System } from '@/system'; import { System } from '@/system';
const unknownicon = 'unknow' import unknownicon from "@/assets/unknown.png"
export function dealExeIcon(content: string | null | undefined) { export function dealExeIcon(content: string | null | undefined) {
if (!content) return unknownicon; if (!content) return unknownicon;
@ -42,19 +42,6 @@ export async function dealIcon(
const content = await system?.fs.readFile(file.path); const content = await system?.fs.readFile(file.path);
return dealExeIcon(content); return dealExeIcon(content);
} }
// if (ext === '.png') {
// if (system.fs instanceof OsFileSystem) {
// if (system.fs.checkVolumePath(file.path)) {
// return imageicon;
// } else {
// // 只有当非自定义文件系统和非自定义卷才直接展示icon
// const content = await system.fs.readFile(file.path);
// return 'data:image/png;base64,' + content || unknownicon;
// }
// } else {
// return imageicon;
// }
// }
if (ext === 'ln') { if (ext === 'ln') {
if (stopCircle) { if (stopCircle) {
return unknownicon; return unknownicon;
@ -69,27 +56,4 @@ export async function dealIcon(
} }
} }
return file.ext; return file.ext;
// const exts = ['mp3','mp4','txt','js','json','xml','svg','html','css','bmp','gif','jpg','png','tiff','webp']
// if(exts.includes(file.ext)){
// return '/image/ext/' + file.ext + '.svg'
// }
// if (ext === '.mp3') return audioicon;
// if (ext === '.mp4') return videoicon;
// if (ext === '.txt') return txtIcon;
// if (ext === '.js') return jsIcon;
// if (ext === '.json') return jsonIcon;
// if (ext === '.xml') return xmlIcon;
// if (ext === '.svg') return svgIcon;
// if (ext === '.html') return htmlIcon;
// if (ext === '.css') return cssIcon;
// if (ext === '.bmp') return bmpIcon;
// if (ext === '.gif') return gifIcon;
// if (ext === '.jpg') return jpgIcon;
// if (ext === '.png') return pngIcon;
// if (ext === '.tiff') return tiffIcon;
// if (ext === '.webp') return webpIcon;
//return system.getOpener(ext)?.icon || unknownicon;
} }

2
go.mod
View file

@ -42,7 +42,7 @@ require (
golang.org/x/crypto v0.23.0 // indirect golang.org/x/crypto v0.23.0 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/net v0.25.0 // indirect golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.15.0 // indirect golang.org/x/text v0.15.0 // indirect
) )

4
go.sum
View file

@ -105,8 +105,8 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.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-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View file

@ -5,7 +5,6 @@ import (
"godo/files" "godo/files"
"godo/libs" "godo/libs"
"godo/localchat" "godo/localchat"
"godo/progress"
"godo/store" "godo/store"
"godo/sys" "godo/sys"
"log" "log"
@ -27,32 +26,32 @@ func OsStart() {
router.Use(loggingMiddleware{}.Middleware) router.Use(loggingMiddleware{}.Middleware)
staticDir := libs.GetStaticDir() staticDir := libs.GetStaticDir()
router.PathPrefix("/static").Handler(http.StripPrefix("/static", http.FileServer(http.Dir(staticDir)))) router.PathPrefix("/static").Handler(http.StripPrefix("/static", http.FileServer(http.Dir(staticDir))))
router.HandleFunc("/ping", progress.Ping).Methods(http.MethodGet) router.HandleFunc("/ping", store.Ping).Methods(http.MethodGet)
if libs.PathExists("./dist") { if libs.PathExists("./dist") {
router.PathPrefix("/").Handler(http.StripPrefix("/", http.FileServer(http.Dir("./dist")))) router.PathPrefix("/").Handler(http.StripPrefix("/", http.FileServer(http.Dir("./dist"))))
} else { } else {
router.HandleFunc("/", progress.Ping).Methods(http.MethodGet) router.HandleFunc("/", store.Ping).Methods(http.MethodGet)
} }
progressRouter := router.PathPrefix("/progress").Subrouter() progressRouter := router.PathPrefix("/store").Subrouter()
progressRouter.HandleFunc("/start/{name}", progress.StartProcess).Methods(http.MethodGet) progressRouter.HandleFunc("/start/{name}", store.StartProcess).Methods(http.MethodGet)
progressRouter.HandleFunc("/stop/{name}", progress.StopProcess).Methods(http.MethodGet) progressRouter.HandleFunc("/stop/{name}", store.StopProcess).Methods(http.MethodGet)
progressRouter.HandleFunc("/startall", progress.StartAll).Methods(http.MethodGet) progressRouter.HandleFunc("/startall", store.StartAll).Methods(http.MethodGet)
progressRouter.HandleFunc("/stopall", progress.StopAll).Methods(http.MethodGet) progressRouter.HandleFunc("/stopall", store.StopAll).Methods(http.MethodGet)
progressRouter.HandleFunc("/restart/{name}", progress.ReStartProcess).Methods(http.MethodGet) progressRouter.HandleFunc("/restart/{name}", store.ReStartProcess).Methods(http.MethodGet)
progressRouter.HandleFunc("/list", progress.Status).Methods(http.MethodGet) progressRouter.HandleFunc("/listporgress", store.Status).Methods(http.MethodGet)
progressRouter.HandleFunc("/listport", progress.ListPortsHandler).Methods(http.MethodGet) progressRouter.HandleFunc("/listport", store.ListPortsHandler).Methods(http.MethodGet)
progressRouter.HandleFunc("/killport", progress.KillPortHandler).Methods(http.MethodGet) progressRouter.HandleFunc("/killport", store.KillPortHandler).Methods(http.MethodGet)
progressRouter.HandleFunc("/storelist", store.GetStoreInfoHandler).Methods(http.MethodGet)
progressRouter.HandleFunc("/download", store.DownloadHandler).Methods(http.MethodGet)
progressRouter.HandleFunc("/install", store.InstallHandler).Methods(http.MethodGet)
progressRouter.HandleFunc("/installOut", store.RunOutHandler).Methods(http.MethodGet)
progressRouter.HandleFunc("/uninstall", store.UnInstallHandler).Methods(http.MethodGet)
router.HandleFunc("/system/updateInfo", sys.GetUpdateUrlHandler).Methods(http.MethodGet) router.HandleFunc("/system/updateInfo", sys.GetUpdateUrlHandler).Methods(http.MethodGet)
router.HandleFunc("/system/update", sys.UpdateAppHandler).Methods(http.MethodGet) router.HandleFunc("/system/update", sys.UpdateAppHandler).Methods(http.MethodGet)
router.HandleFunc("/system/setting", sys.HandleSetConfig).Methods(http.MethodPost) router.HandleFunc("/system/setting", sys.HandleSetConfig).Methods(http.MethodPost)
router.HandleFunc("/store/list", store.GetStoreInfoHandler).Methods(http.MethodGet) router.HandleFunc("/file/info", files.HandleSystemInfo).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/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)
@ -66,12 +65,14 @@ func OsStart() {
router.HandleFunc("/file/copyfile", files.HandleCopyFile).Methods(http.MethodGet) router.HandleFunc("/file/copyfile", files.HandleCopyFile).Methods(http.MethodGet)
router.HandleFunc("/file/writefile", files.HandleWriteFile).Methods(http.MethodPost) router.HandleFunc("/file/writefile", files.HandleWriteFile).Methods(http.MethodPost)
router.HandleFunc("/file/appendfile", files.HandleAppendFile).Methods(http.MethodPost) router.HandleFunc("/file/appendfile", files.HandleAppendFile).Methods(http.MethodPost)
router.HandleFunc("/file/zip", files.HandleZip).Methods(http.MethodGet)
router.HandleFunc("/file/unzip", files.HandleUnZip).Methods(http.MethodGet)
router.HandleFunc("/file/watch", files.WatchHandler).Methods(http.MethodGet) router.HandleFunc("/file/watch", files.WatchHandler).Methods(http.MethodGet)
router.HandleFunc("/localchat/sse", localchat.SseHandler).Methods(http.MethodGet) router.HandleFunc("/localchat/sse", localchat.SseHandler).Methods(http.MethodGet)
router.HandleFunc("/localchat/message", localchat.HandleMessage).Methods(http.MethodPost) router.HandleFunc("/localchat/message", localchat.HandleMessage).Methods(http.MethodPost)
router.HandleFunc("/localchat/upload", localchat.MultiUploadHandler).Methods(http.MethodPost) router.HandleFunc("/localchat/upload", localchat.MultiUploadHandler).Methods(http.MethodPost)
go progress.CheckActive(context.Background()) go store.CheckActive(context.Background())
log.Printf("Listening on port: %v", serverAddress) log.Printf("Listening on port: %v", serverAddress)
srv = &http.Server{Addr: serverAddress, Handler: router} srv = &http.Server{Addr: serverAddress, Handler: router}
Serve(srv) Serve(srv)
@ -79,7 +80,7 @@ func OsStart() {
func OsStop() { func OsStop() {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel() defer cancel()
err := progress.StopAllHandler() err := store.StopAllHandler()
if err != nil { if err != nil {
log.Fatalf("Servers forced to shutdown error: %v", err) log.Fatalf("Servers forced to shutdown error: %v", err)
} }

View file

@ -398,6 +398,58 @@ func HandleChmod(w http.ResponseWriter, r *http.Request) {
res := libs.APIResponse{Message: fmt.Sprintf("Permissions of file '%s' successfully changed to %o.", path, mode)} res := libs.APIResponse{Message: fmt.Sprintf("Permissions of file '%s' successfully changed to %o.", path, mode)}
json.NewEncoder(w).Encode(res) json.NewEncoder(w).Encode(res)
} }
func HandleZip(w http.ResponseWriter, r *http.Request) {
path := r.URL.Query().Get("path")
ext := r.URL.Query().Get("ext")
if ext == "" {
ext = "zip"
}
if err := validateFilePath(path); err != nil {
libs.HTTPError(w, http.StatusBadRequest, err.Error())
return
}
basePath, err := libs.GetOsDir()
if err != nil {
libs.HTTPError(w, http.StatusInternalServerError, err.Error())
return
}
sourcePath := filepath.Join(basePath, path)
compressedFilePath := filepath.Join(basePath, path+"."+ext)
err = CompressFileOrFolder(sourcePath, compressedFilePath)
if err != nil {
libs.HTTPError(w, http.StatusConflict, err.Error())
return
}
res := libs.APIResponse{Message: fmt.Sprintf("success zip %s.%s.", path, ext)}
json.NewEncoder(w).Encode(res)
}
func HandleUnZip(w http.ResponseWriter, r *http.Request) {
path := r.URL.Query().Get("path")
if err := validateFilePath(path); err != nil {
libs.ErrorMsg(w, err.Error())
return
}
basePath, err := libs.GetOsDir()
if err != nil {
libs.ErrorMsg(w, err.Error())
return
}
zipFilePath := filepath.Join(basePath, path)
destPath := filepath.Dir(zipFilePath)
err = Decompress(zipFilePath, destPath)
if err != nil {
libs.ErrorMsg(w, err.Error())
return
}
res := libs.APIResponse{Message: fmt.Sprintf("success unzip %s.", path)}
json.NewEncoder(w).Encode(res)
}
// parseMode converts a string representation of a mode to an os.FileMode // parseMode converts a string representation of a mode to an os.FileMode
func parseMode(modeStr string) (os.FileMode, error) { func parseMode(modeStr string) (os.FileMode, error) {

View file

@ -4,6 +4,7 @@ import (
"compress/bzip2" "compress/bzip2"
"compress/gzip" "compress/gzip"
"fmt" "fmt"
"godo/libs"
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
@ -48,13 +49,26 @@ func Untar(reader io.Reader, destPath string) error {
} }
target := filepath.Join(destPath, header.Name) target := filepath.Join(destPath, header.Name)
// Ensure that all directories in the path are created.
if header.Typeflag == tar.TypeDir { if header.Typeflag == tar.TypeDir {
if err := os.MkdirAll(target, os.FileMode(header.Mode)); err != nil { if !libs.PathExists(target) {
return err if err := os.MkdirAll(target, os.FileMode(header.Mode)); err != nil {
return err
}
} }
continue continue
} }
// If it's a regular file, ensure its parent directories exist.
if !libs.PathExists(target) {
if err := os.MkdirAll(filepath.Dir(target), os.ModePerm); err != nil {
return err
}
}
// Create the file and write the contents from the tar archive.
outFile, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR|os.O_TRUNC, os.FileMode(header.Mode)) outFile, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR|os.O_TRUNC, os.FileMode(header.Mode))
if err != nil { if err != nil {
return err return err
@ -114,21 +128,33 @@ func Unzip(zipFilePath, destPath string) error {
path := filepath.Join(destPath, f.Name) path := filepath.Join(destPath, f.Name)
// Ensure parent directory exists before creating the file or directory.
if !libs.PathExists(filepath.Dir(path)) {
if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
return err
}
}
if f.FileInfo().IsDir() { if f.FileInfo().IsDir() {
os.MkdirAll(path, f.Mode()) // Create the directory itself if it doesn't exist yet.
if !libs.PathExists(path) {
if err := os.Mkdir(path, f.Mode()); err != nil {
return err
}
}
continue continue
} }
os.MkdirAll(filepath.Dir(path), f.Mode()) // Open the file for writing, truncating if it already exists.
outFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) outFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil { if err != nil {
return err return err
} }
defer outFile.Close() defer outFile.Close()
_, err = io.Copy(outFile, rc) // Write the contents of the zip file entry to the output file.
if err != nil { if _, err := io.Copy(outFile, rc); err != nil {
return err return err
} }
} }
@ -155,11 +181,15 @@ func IsSupportedCompressionFormat(filename string) bool {
// HandlerFile 根据文件后缀判断是否支持解压,支持则解压,否则移动文件 // HandlerFile 根据文件后缀判断是否支持解压,支持则解压,否则移动文件
func HandlerFile(filePath string, destDir string) error { func HandlerFile(filePath string, destDir string) error {
if !IsSupportedCompressionFormat(filePath) { // log.Printf("HandlerFile: %s", filePath)
// log.Printf("destDir: %s", destDir)
if IsSupportedCompressionFormat(filePath) {
// 移动文件 // 移动文件
newPath := filepath.Join(destDir, filepath.Base(filePath)) // newPath := filepath.Join(destDir, filepath.Base(filePath))
return os.Rename(filePath, newPath) // return os.Rename(filePath, newPath)
// 解压文件
return Decompress(filePath, destDir)
} }
// 解压文件 return nil
return Decompress(filePath, destDir)
} }

View file

@ -87,8 +87,6 @@ func IsSupportedFormat(filename string) bool {
".zip": true, ".zip": true,
".tar": true, ".tar": true,
".gz": true, ".gz": true,
".bz2": true,
// 添加其他支持的复合格式
} }
for format := range supportedFormats { for format := range supportedFormats {

View file

@ -17,5 +17,5 @@ require (
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/crypto v0.0.0-20211209193657-4570a0811e8b // indirect
golang.org/x/sys v0.19.0 // indirect golang.org/x/sys v0.22.0 // indirect
) )

View file

@ -37,6 +37,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.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-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View file

@ -1,99 +0,0 @@
package progress
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
)
func StartProcess(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
processesMu.Lock()
defer processesMu.Unlock()
err := ExecuteScript(name)
if err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Process %s started.", name)
}
func ExecuteStartAll() error {
processesMu.Lock()
defer processesMu.Unlock()
for name, cmd := range processes {
if err := cmd.Cmd.Start(); err != nil {
return fmt.Errorf("failed to stop process %s: %v", name, err)
}
}
return nil
}
func StartAll(w http.ResponseWriter, r *http.Request) {
if err := ExecuteStartAll(); err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "All processes started.")
}
// ExecuteScript 执行指定名称的脚本。
// 参数:
// name - 脚本的名称。
// 返回值:
// 返回可能遇到的错误如果执行成功则返回nil。
func ExecuteScript(name string) error {
cmd, exists := processes[name]
if !exists {
return fmt.Errorf("process information for '%s' not found", name)
}
if cmd.Running {
return fmt.Errorf("process %s is already running", name)
}
if err := cmd.Cmd.Start(); err != nil {
return fmt.Errorf("failed to start process %s: %v", name, err)
}
cmd.Running = true
return nil
// 确保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
}

View file

@ -1,4 +1,4 @@
package progress package store
import ( import (
"context" "context"
@ -73,7 +73,7 @@ func IsProcessRunning(appName string) (bool, error) {
if cmd.SysProcAttr == nil { if cmd.SysProcAttr == nil {
cmd.SysProcAttr = &syscall.SysProcAttr{} cmd.SysProcAttr = &syscall.SysProcAttr{}
} }
cmd.SysProcAttr.HideWindow = true // For Windows to hide the console window //cmd.SysProcAttr.HideWindow = true // For Windows to hide the console window
if output, err = cmd.CombinedOutput(); err != nil { if output, err = cmd.CombinedOutput(); err != nil {
return false, fmt.Errorf("error checking process: %w", err) return false, fmt.Errorf("error checking process: %w", err)

View file

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"godo/files" "godo/files"
"godo/libs" "godo/libs"
"godo/progress"
"log" "log"
"net/http" "net/http"
"os" "os"
@ -14,6 +13,7 @@ import (
"regexp" "regexp"
"runtime" "runtime"
"strings" "strings"
"time"
) )
type StoreInfo struct { type StoreInfo struct {
@ -44,7 +44,8 @@ type Cmd struct {
Content string `json:"content,omitempty"` Content string `json:"content,omitempty"`
BinPath string `json:"binPath,omitempty"` BinPath string `json:"binPath,omitempty"`
Cmds []string `json:"cmds,omitempty"` Cmds []string `json:"cmds,omitempty"`
Waiting bool `json:"waiting"` Waiting int `json:"waiting"`
Kill bool `json:"kill"`
Envs []Item `json:"envs"` Envs []Item `json:"envs"`
} }
@ -61,7 +62,7 @@ func InstallHandler(w http.ResponseWriter, r *http.Request) {
} }
exeDir := libs.GetRunDir() exeDir := libs.GetRunDir()
exePath := filepath.Join(exeDir, pluginName) exePath := filepath.Join(exeDir, pluginName)
//log.Printf("the app path is %s", exePath) log.Printf("the app path is %s", exePath)
if !libs.PathExists(exePath) { if !libs.PathExists(exePath) {
libs.ErrorMsg(w, "the app path is not exists!") libs.ErrorMsg(w, "the app path is not exists!")
return return
@ -115,8 +116,9 @@ func InstallHandler(w http.ResponseWriter, r *http.Request) {
targetPath := filepath.Join(staticDir, pluginName) targetPath := filepath.Join(staticDir, pluginName)
err = os.Rename(staticPath, targetPath) err = os.Rename(staticPath, targetPath)
if err != nil { if err != nil {
libs.ErrorMsg(w, "copy the static is error!") // libs.ErrorMsg(w, "copy the static is error!")
return // return
log.Printf("copy the static is error! %v", err)
} }
} }
libs.SuccessMsg(w, "success", "install the app success!") libs.SuccessMsg(w, "success", "install the app success!")
@ -127,10 +129,12 @@ func UnInstallHandler(w http.ResponseWriter, r *http.Request) {
libs.ErrorMsg(w, "the app name is empty!") libs.ErrorMsg(w, "the app name is empty!")
return return
} }
err := progress.StopCmd(pluginName) log.Printf("uninstall the app %s", pluginName)
err := StopCmd(pluginName)
if err != nil { if err != nil {
libs.ErrorMsg(w, "stop the app is error!") // libs.ErrorMsg(w, "stop the app is error!")
return // return
log.Printf("stop the app is error! %s", err)
} }
exeDir := libs.GetRunDir() exeDir := libs.GetRunDir()
exePath := filepath.Join(exeDir, pluginName) exePath := filepath.Join(exeDir, pluginName)
@ -233,29 +237,35 @@ func RunCmds(storeInfo StoreInfo, cmdKey string) error {
return nil return nil
} }
func runStop(storeInfo StoreInfo) error { func runStop(storeInfo StoreInfo) error {
return progress.StopCmd(storeInfo.Name) return StopCmd(storeInfo.Name)
} }
func runStart(storeInfo StoreInfo) error { func runStart(storeInfo StoreInfo) error {
err := SetEnvs(storeInfo.Start.Envs) err := SetEnvs(storeInfo.Start.Envs)
if err != nil { if err != nil {
return fmt.Errorf("failed to set start environment variable %s: %w", storeInfo.Name, err) 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) {
if !libs.PathExists(storeInfo.Setting.BinPath) { return fmt.Errorf("script file %s does not exist", 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)
} }
var cmd *exec.Cmd
if len(storeInfo.Start.Cmds) > 0 {
cmd = exec.Command(storeInfo.Setting.BinPath, storeInfo.Start.Cmds...)
} else {
cmd = exec.Command(storeInfo.Setting.BinPath)
}
if runtime.GOOS == "windows" {
// 在Windows上通过设置CreationFlags来隐藏窗口
cmd = SetHideConsoleCursor(cmd)
}
// 启动脚本命令并返回可能的错误
go func(cmd *exec.Cmd) {
if err := cmd.Start(); err != nil {
log.Println("Error starting script:", err)
return
}
RegisterProcess(storeInfo.Name, cmd)
}(cmd)
return nil return nil
} }
func runRestart(storeInfo StoreInfo) error { func runRestart(storeInfo StoreInfo) error {
@ -275,15 +285,21 @@ func runExec(storeInfo StoreInfo, cmdParam Cmd) error {
cmd := exec.Command(cmdParam.BinPath, cmdParam.Cmds...) cmd := exec.Command(cmdParam.BinPath, cmdParam.Cmds...)
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
// 在Windows上通过设置CreationFlags来隐藏窗口 // 在Windows上通过设置CreationFlags来隐藏窗口
cmd = progress.SetHideConsoleCursor(cmd) cmd = SetHideConsoleCursor(cmd)
} }
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
return fmt.Errorf("failed to run exec process %s: %w", storeInfo.Name, err) return fmt.Errorf("failed to run exec process %s: %w", storeInfo.Name, err)
} }
if cmdParam.Waiting { if cmdParam.Waiting > 0 {
if err = cmd.Wait(); err != nil { if err = cmd.Wait(); err != nil {
return fmt.Errorf("failed to wait for exec process %s: %w", storeInfo.Name, err) return fmt.Errorf("failed to wait for exec process %s: %w", storeInfo.Name, err)
} }
time.Sleep(time.Second * time.Duration(cmdParam.Waiting))
}
if cmdParam.Kill {
if err := cmd.Process.Kill(); err != nil {
return fmt.Errorf("failed to kill process: %v", err)
}
} }
log.Printf("run exec process %s, name is %s", storeInfo.Name, cmdParam.Name) log.Printf("run exec process %s, name is %s", storeInfo.Name, cmdParam.Name)
return nil return nil

View file

@ -1,9 +1,10 @@
//go:build !windows //go:build !windows
package progress package store
import ( import (
// 导入包 // 导入包
"os/exec" "os/exec"
) )

View file

@ -1,4 +1,4 @@
package progress package store
type ProcessInfo struct { type ProcessInfo struct {
Port string Port string

View file

@ -1,4 +1,4 @@
package progress package store
import ( import (
"encoding/json" "encoding/json"

View file

@ -1,4 +1,4 @@
package progress package store
import ( import (
"encoding/json" "encoding/json"
@ -10,12 +10,12 @@ import (
) )
type Process struct { type Process struct {
Name string Name string `json:"name"`
Running bool Running bool `json:"running"`
ExitCode int ExitCode int `json:"exitCode"`
Cmd *exec.Cmd Cmd *exec.Cmd `json:"cmd"`
Waiting bool Waiting bool `json:"waiting"`
LastPing time.Time LastPing time.Time `json:"lastPing"`
} }
var ( var (
@ -29,7 +29,6 @@ func respondWithError(w http.ResponseWriter, code int, message string) {
func RegisterProcess(name string, cmdstr *exec.Cmd) { func RegisterProcess(name string, cmdstr *exec.Cmd) {
processesMu.Lock() processesMu.Lock()
defer processesMu.Unlock() defer processesMu.Unlock()
processes[name] = &Process{ processes[name] = &Process{
Name: name, Name: name,
Running: true, Running: true,
@ -43,19 +42,20 @@ func GetCmd(name string) *Process {
return processes[name] return processes[name]
} }
func Status(w http.ResponseWriter, r *http.Request) { func Status(w http.ResponseWriter, r *http.Request) {
processesMu.RLock() // processesMu.RLock()
defer processesMu.RUnlock() // defer processesMu.RUnlock()
var ps []Process var ps []Process
for name, cmd := range processes { for name, cmd := range processes {
if cmd.Cmd.ProcessState != nil && cmd.Cmd.ProcessState.Exited() { // if cmd.Cmd.ProcessState != nil && cmd.Cmd.ProcessState.Exited() {
cmd.Running = false // cmd.Running = false
// 进程已经退出 // // 进程已经退出
ps = append(ps, Process{Name: name, Running: false, Waiting: cmd.Waiting, 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 { // } else {
// 进程仍在运行 // // 进程仍在运行
ps = append(ps, Process{Name: name, Running: true, Waiting: cmd.Waiting, LastPing: cmd.LastPing}) // ps = append(ps, Process{Name: name, Running: true, Waiting: cmd.Waiting, LastPing: cmd.LastPing})
} // }
ps = append(ps, Process{Name: name, Running: cmd.Running, Waiting: cmd.Waiting, LastPing: cmd.LastPing, ExitCode: cmd.Cmd.ProcessState.ExitCode()})
} }
jsonBytes, err := json.MarshalIndent(ps, "", " ") jsonBytes, err := json.MarshalIndent(ps, "", " ")

136
godo/store/start.go Normal file
View file

@ -0,0 +1,136 @@
package store
import (
"encoding/json"
"fmt"
"godo/libs"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"github.com/gorilla/mux"
)
func StartProcess(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
processesMu.Lock()
defer processesMu.Unlock()
err := ExecuteScript(name)
if err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Process %s started.", name)
}
func ExecuteStartAll() error {
processesMu.Lock()
defer processesMu.Unlock()
for name, cmd := range processes {
if err := cmd.Cmd.Start(); err != nil {
return fmt.Errorf("failed to stop process %s: %v", name, err)
}
}
return nil
}
func StartAll(w http.ResponseWriter, r *http.Request) {
if err := ExecuteStartAll(); err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "All processes started.")
}
func GetStoreInfo(name string) (StoreInfo, error) {
var storeInfo StoreInfo
exeDir := libs.GetRunDir()
infoPath := filepath.Join(exeDir, name, "info.json")
if !libs.PathExists(infoPath) {
return storeInfo, fmt.Errorf("process information for '%s' not found", name)
}
content, err := os.ReadFile(infoPath)
if err != nil {
return storeInfo, fmt.Errorf("failed to read info.json: %v", err)
}
if err := json.Unmarshal(content, &storeInfo); err != nil {
return storeInfo, fmt.Errorf("failed to unmarshal info.json: %v", err)
}
scriptPath := storeInfo.Setting.BinPath
if !libs.PathExists(scriptPath) {
return storeInfo, fmt.Errorf("script file '%s' not found", scriptPath)
}
return storeInfo, nil
}
// ExecuteScript 执行指定名称的脚本。
// 参数:
// name - 脚本的名称。
// 返回值:
// 返回可能遇到的错误如果执行成功则返回nil。
func ExecuteScript(name string) error {
cmd, exists := processes[name]
if !exists {
log.Printf("process information for '%s' not found", name)
//return fmt.Errorf("process information for '%s' not found", name)
storeInfo, err := GetStoreInfo(name)
if err != nil {
return err
}
err = runStart(storeInfo)
if err != nil {
return fmt.Errorf("failed to run script: %v", err)
}
return nil
}
if cmd.Running {
return nil
}
// if err := cmd.Cmd.Start(); err != nil {
// log.Printf("failed to start process %s: %v", name, err)
// return nil
// }
processes[name].Running = true
go func() {
if err := cmd.Cmd.Start(); err != nil {
log.Printf("failed to start process %s: %v", name, err)
return
}
// processesMu.Lock()
// defer processesMu.Unlock()
processes[name].Cmd = cmd.Cmd
}()
return nil
}
func RunOutHandler(w http.ResponseWriter, r *http.Request) {
url := r.URL.Query().Get("url")
fileName := filepath.Base(url)
cacheDir := libs.GetCacheDir()
filePath := filepath.Join(cacheDir, fileName)
if !libs.PathExists(filePath) {
libs.ErrorMsg(w, "file not found")
return
}
cmd := exec.Command(filePath)
if err := cmd.Start(); err != nil {
libs.ErrorMsg(w, fmt.Sprintf("start error: %v", err))
return
}
// 如果 cmd.Start() 成功,返回成功消息
libs.SuccessMsg(w, "", "start success")
}

View file

@ -1,23 +1,37 @@
package progress package store
import ( import (
"fmt" "fmt"
"log"
"net/http" "net/http"
"os" "time"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/shirou/gopsutil/process"
) )
func KillByPid(pid int) error {
p, err := process.NewProcess(int32(pid))
if err != nil {
return fmt.Errorf("failed to create process object: %w", err)
}
// 杀死进程及其子进程
if err := p.Kill(); err != nil {
return fmt.Errorf("failed to kill process: %w", err)
}
return nil
}
func StopCmd(name string) error { func StopCmd(name string) error {
cmd, ok := processes[name] cmd, ok := processes[name]
if !ok { if !ok {
return fmt.Errorf("Process not found") return fmt.Errorf("Process not found")
} }
if err := cmd.Cmd.Process.Kill(); err != nil { processes[name].Running = false
return fmt.Errorf("failed to kill process: %v", err) err := KillByPid(cmd.Cmd.Process.Pid)
if err != nil {
return fmt.Errorf("failed to kill process: %w", err)
} }
//delete(processes, name) // 更新status表示进程已停止
cmd.Running = false
return nil return nil
} }
func StopProcess(w http.ResponseWriter, r *http.Request) { func StopProcess(w http.ResponseWriter, r *http.Request) {
@ -37,9 +51,10 @@ func StopAllHandler() error {
defer processesMu.Unlock() defer processesMu.Unlock()
for name, cmd := range processes { for name, cmd := range processes {
if err := cmd.Cmd.Process.Signal(os.Interrupt); err != nil { if err := KillByPid(cmd.Cmd.Process.Pid); err != nil {
return fmt.Errorf("failed to stop process %s: %v", name, err) return fmt.Errorf("failed to stop process %s: %v", name, err)
} }
processes[name].Running = false
} }
return nil return nil
} }
@ -49,15 +64,6 @@ func StopAll(w http.ResponseWriter, r *http.Request) {
respondWithError(w, http.StatusInternalServerError, err.Error()) respondWithError(w, http.StatusInternalServerError, err.Error())
return return
} }
// processesMu.Lock()
// defer processesMu.Unlock()
// for name, cmd := range processes {
// if err := cmd.Cmd.Process.Signal(os.Interrupt); err != nil {
// respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to stop process %s: %v", name, err))
// return
// }
// }
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "All processes stopped.") fmt.Fprintf(w, "All processes stopped.")
@ -70,16 +76,19 @@ func ReStartProcess(w http.ResponseWriter, r *http.Request) {
// Stop the process first // Stop the process first
if cmd, ok := processes[name]; ok { if cmd, ok := processes[name]; ok {
if err := cmd.Cmd.Process.Kill(); err != nil { if err := KillByPid(cmd.Cmd.Process.Pid); err != nil {
respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to stop process %s before restart: %v", name, err)) // respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to stop process %s before restart: %v", name, err))
return // return
log.Printf("Failed to stop process %s before restart: %v", name, err)
} }
//delete(processes, name) // processesMu.Lock()
cmd.Running = false // defer processesMu.Unlock()
processes[name].Running = false
} else { } else {
respondWithError(w, http.StatusNotFound, fmt.Sprintf("Process %s not found to restart", name)) respondWithError(w, http.StatusNotFound, fmt.Sprintf("Process %s not found to restart", name))
return return
} }
time.Sleep(time.Second * 2)
// Start the process again // Start the process again
err := ExecuteScript(name) err := ExecuteScript(name)
if err != nil { if err != nil {

View file

@ -1,10 +1,11 @@
//go:build windows //go:build windows
// +build windows // +build windows
package progress package store
import ( import (
// 导入包 // 导入包
"os/exec" "os/exec"
"syscall" "syscall"
) )