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" />
### 三、内网聊天

View file

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

View file

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

View file

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 4412427 */
src: url('iconfont.woff2?t=1718176711902') format('woff2'),
url('iconfont.woff?t=1718176711902') format('woff'),
url('iconfont.ttf?t=1718176711902') format('truetype');
src: url('iconfont.woff2?t=1722214929182') format('woff2'),
url('iconfont.woff?t=1722214929182') format('woff'),
url('iconfont.ttf?t=1722214929182') format('truetype');
}
.iconfont {
@ -13,6 +13,22 @@
-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 {
content: "\e65f";
}

File diff suppressed because one or more lines are too long

View file

@ -5,6 +5,34 @@
"css_prefix_text": "icon-",
"description": "",
"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",
"name": "chat",

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,7 +1,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,
"needInstall": true,
"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

@ -64,7 +64,8 @@
</div>
</template>
<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 { useContextMenu } from '@/hook/useContextMenu.ts';
import { t, dealSystemName } from '@/i18n';
@ -228,9 +229,26 @@ function handleRightClick(mouse: MouseEvent, item: OsFileWithoutContent, index:
}
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
let menuArr = [
let menuArr: any = [
{
label: t('open'),
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) {
menuArr.push({
label: t('selected'),
@ -269,7 +330,7 @@ function handleRightClick(mouse: MouseEvent, item: OsFileWithoutContent, index:
})
}
// eslint-disable-next-line prefer-const
let extMenus = useAppMenu(item, index);
let extMenus = useAppMenu(item, sys, props);
if (extMenus && extMenus.length > 0) {
// eslint-disable-next-line prefer-spread
menuArr.push.apply(menuArr, extMenus)
@ -295,22 +356,17 @@ function handleRightClick(mouse: MouseEvent, item: OsFileWithoutContent, index:
{
label: t('delete'),
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++) {
await deleteFile(props.fileList[chosenIndexs.value[i]]);
}
chosenIndexs.value = [];
props.onRefresh();
if (item.parentPath == '/C/Users/Desktop') {
sys.refershAppList()
},
}
},
},
];
// eslint-disable-next-line prefer-spread
menuArr.push.apply(menuArr, fileMenus)

View file

@ -57,15 +57,11 @@
</div>
</template>
<script lang="ts" setup>
// import WinButton from '@/components/win/WinButton.vue';
import { ref } from "vue";
// import { useSystem } from '@/system';
// import { Dialog } from '@/dialog/Dialog';
import { t } from "@/i18n";
import { getSystemKey } from "@/system/config";
// const system = useSystem();
const items = [
// t('backup'), // '',
t("version"), // '',
];
const version = getSystemKey("version");
@ -75,58 +71,6 @@ const selectItem = (index: number) => {
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>
<style scoped>

View file

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

View file

@ -25,7 +25,7 @@ async function getList(){
if (currentCate.value == 'add') return;
storeList.value = storeInitList
// const apiUrl = getSystemKey("apiUrl");
// const storeUrl = apiUrl + '/system/storeList?cate=' + currentCate.value
// const storeUrl = apiUrl + '/store/storelist?cate=' + currentCate.value
// fetch(storeUrl).then(res => {
// res.json().then(data => {
// storeList.value = data
@ -33,8 +33,28 @@ async function getList(){
// }).catch(() => {
// notifyError(t("store.errorList"))
// })
await checkProgress()
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) {
currentCateId.value = index
currentCate.value = item
@ -53,9 +73,32 @@ async function install(item: any) {
await download(item)
}
if (item.needInstall) {
item.progress = 0
const completion = await fetch(apiUrl + '/store/install?name=' + item.name)
if (!completion.ok) {
notifyError(t("store.installError"))
return
}
if(item.url){
const res = await completion.json()
if (res.code && res.code < 0) {
notifyError(res.message)
return
}
}
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._options.userLocation}Desktop/${item.name}.url`,
`link::url::${item.url}::${item.icon}`
@ -64,10 +107,24 @@ async function install(item: any) {
notifySuccess(t("install.success"))
installedList.value.push(item.name);
await checkProgress()
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`);
notifySuccess(t("uninstall.success"))
delete installedList.value[installedList.value.indexOf(item.name)];
@ -75,6 +132,7 @@ function uninstall(item: any) {
}
async function download(item: any) {
//console.log(item)
if (item.progress) return;
const completion = await fetch(apiUrl + '/store/download?url=' + item.url)
if (!completion.ok) {
notifyError(t("store.downloadError"))
@ -95,13 +153,41 @@ async function download(item: any) {
const res = parseJson(json)
console.log(res)
if (res) {
if (res.progress) {
item.progress = res.progress
}
if (res.done) {
notifySuccess(t("store.downloadSuccess"))
item.progress = 0
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>
<template>
<div class="outer">
@ -139,7 +225,8 @@ async function download(item: any) {
</div> -->
<div class="main-app">
<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"
:pause="pauseApp" :start="startApp" :restart="restartApp" />
</div>
<AddApp v-else />

View file

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

View file

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

View file

@ -44,6 +44,7 @@ function useContextMenu() {
callback?.();
});
}
//console.log(path)
let menuArr = [
{
@ -72,10 +73,6 @@ function useContextMenu() {
label: '数据表格(Excel)',
click: () => clickFunc('xlsx', '未命名数据表格'),
},
// {
// label: '笔记(Note)',
// click: () => clickFunc('note', '未命名笔记'),
// },
{
label: '文本文件(txt)',
click: () => clickFunc('txt', '未命名文本文件'),
@ -97,36 +94,13 @@ function useContextMenu() {
label: '白板(Baiban)',
click: () => clickFunc('bb', '未命名白板'),
},
// {
// label: '工作绘图(Flow)',
// click: () => clickFunc('flow', '未命名工作绘图'),
// },
{
label: '甘特图(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: '新建报告',
// submenu: [

View file

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

View file

@ -37,6 +37,8 @@
"backup": "备份",
"import.success.reboot.soon": "导入成功,即将重启",
"import.failed": "导入失败",
"zip":"压缩",
"unzip":"解压",
"import": "导入",
"export": "导出",
"refresh": "刷新",
@ -58,6 +60,8 @@
"tips": "提示",
"file.not.exist": "文件不存在",
"file.save.success": "文件保存成功",
"file.zip.success": "文件压缩成功",
"file.unzip.success": "文件解压成功",
"new": "新建",
"back": "返回",
"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.")
}
}
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 = () => {
return {
async readdir(path: string) {
const response = await handleReadDir(path);
if (response && response.data) {
return response.data; // 假设返回的JSON包含名为"data"的字段,存储了文件内容
return response.data;
}
return [];
},
async stat(path: string) {
const response = await handleStat(path);
if (response && response.data) {
return response.data; // 假设返回的JSON包含名为"data"的字段,存储了文件内容
return response.data;
}
return false;
},
@ -207,81 +221,71 @@ export const useOsFile = () => {
const modes = osFileModeToOctal(mode)
const response = await handleChmod(path, modes);
if (response) {
return response; // 假设返回的JSON包含操作状态或其他相关信息
return response;
}
return false;
},
async exists(path: string) {
//return handleExists(path);
const response = await handleExists(path);
if (response && response.data) {
return response.data; // 假设返回的JSON包含名为"data"的字段,存储了文件内容
return response.data;
}
return false;
},
async readFile(path: string) {
// 注意handleReadFile返回的是JSON但通常readFile期望返回Buffer或字符串
// 根据你的后端API实际返回的数据类型调整此方法
const response = await handleReadFile(path);
if (response && response.data) {
return response.data; // 假设返回的JSON包含名为"data"的字段,存储了文件内容
return response.data;
}
return false;
},
async unlink(path: string) {
//return handleUnlink(path);
const response = await handleUnlink(path);
if (response) {
return response; // 假设返回的JSON包含名为"data"的字段,存储了文件内容
return response;
}
return false;
},
async rename(oldPath: string, newPath: string) {
//return handleRename(oldPath, newPath);
const response = await handleRename(oldPath, newPath);
if (response) {
return response; // 假设返回的JSON包含名为"data"的字段,存储了文件内容
return response;
}
return false;
},
async rmdir(path: string) {
//return handleRmdir(path);
const response = await handleRmdir(path);
if (response) {
return response; // 假设返回的JSON包含名为"data"的字段,存储了文件内容
return response;
}
return false;
},
async mkdir(path: string) {
//return handleMkdir(path);
const response = await handleMkdir(path);
if (response) {
return response; // 假设返回的JSON包含名为"data"的字段,存储了文件内容
return response;
}
return false;
},
async copyFile(srcPath: string, dstPath: string) {
//return handleCopyFile(srcPath, dstPath);
const response = await handleCopyFile(srcPath, dstPath);
if (response) {
return response; // 假设返回的JSON包含名为"data"的字段,存储了文件内容
return response;
}
return false;
},
async writeFile(path: string, content: string | Blob) {
//return handleWriteFile(path, content);
const response = await handleWriteFile(path, content);
if (response) {
return response; // 假设返回的JSON包含名为"data"的字段,存储了文件内容
return response;
}
return false;
},
async appendFile(path: string, content: string | Blob) {
//return handleAppendFile(path, content);
const response = await handleAppendFile(path, content);
if (response) {
return response; // 假设返回的JSON包含名为"data"的字段,存储了文件内容
return response;
}
return false;
},
@ -289,6 +293,20 @@ export const useOsFile = () => {
console.log(path, options)
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() {
return Promise.reject('fs 不支持序列化');
},

View file

@ -4,6 +4,7 @@ import { SystemOptions } from '../type/type';
import { InitSystemFile, InitUserFile } from './SystemFileConfig';
import { createInitFile } from './createInitFile';
import { OsFileMode } from './FileMode';
import JSZip from "jszip";
type DateLike = Date | string | number;
// Os文件模式枚举
class OsFileInfo {
@ -405,7 +406,7 @@ class OsFileSystem implements OsFileInterface {
* @param path
* @returns
*/
async readFile(path: string): Promise<string | null> {
async readFile(path: string): Promise<string | any> {
try {
const volume = this.checkVolumePath(path);
if (volume) {
@ -891,12 +892,13 @@ class OsFileSystem implements OsFileInterface {
const transedPath = fspath.transformPath(path);
// 获取父路径
let parentPath = fspath.dirname(transedPath);
// 如果父路径为空,设置为根路径
if (parentPath === '') parentPath = '/';
// 判断文件是否存在
const exists = await this.exists(parentPath);
// 如果文件不存在,则抛出错误
if (!exists) {
if (!exists && 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);
}
@ -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 };

View file

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

View file

@ -1,7 +1,7 @@
import { OsFileSystem, OsFileWithoutContent } from '@/system/core/FileSystem';
import { System } from '@/system';
const unknownicon = 'unknow'
import unknownicon from "@/assets/unknown.png"
export function dealExeIcon(content: string | null | undefined) {
if (!content) return unknownicon;
@ -42,19 +42,6 @@ export async function dealIcon(
const content = await system?.fs.readFile(file.path);
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 (stopCircle) {
return unknownicon;
@ -69,27 +56,4 @@ export async function dealIcon(
}
}
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/exp v0.0.0-20230522175609-2e198f4a06a1 // 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
)

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-20220811171246-fbc7d0a398ab/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.20.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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View file

@ -5,7 +5,6 @@ import (
"godo/files"
"godo/libs"
"godo/localchat"
"godo/progress"
"godo/store"
"godo/sys"
"log"
@ -27,32 +26,32 @@ func OsStart() {
router.Use(loggingMiddleware{}.Middleware)
staticDir := libs.GetStaticDir()
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") {
router.PathPrefix("/").Handler(http.StripPrefix("/", http.FileServer(http.Dir("./dist"))))
} else {
router.HandleFunc("/", progress.Ping).Methods(http.MethodGet)
router.HandleFunc("/", store.Ping).Methods(http.MethodGet)
}
progressRouter := router.PathPrefix("/progress").Subrouter()
progressRouter.HandleFunc("/start/{name}", progress.StartProcess).Methods(http.MethodGet)
progressRouter.HandleFunc("/stop/{name}", progress.StopProcess).Methods(http.MethodGet)
progressRouter.HandleFunc("/startall", progress.StartAll).Methods(http.MethodGet)
progressRouter.HandleFunc("/stopall", progress.StopAll).Methods(http.MethodGet)
progressRouter.HandleFunc("/restart/{name}", progress.ReStartProcess).Methods(http.MethodGet)
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 := router.PathPrefix("/store").Subrouter()
progressRouter.HandleFunc("/start/{name}", store.StartProcess).Methods(http.MethodGet)
progressRouter.HandleFunc("/stop/{name}", store.StopProcess).Methods(http.MethodGet)
progressRouter.HandleFunc("/startall", store.StartAll).Methods(http.MethodGet)
progressRouter.HandleFunc("/stopall", store.StopAll).Methods(http.MethodGet)
progressRouter.HandleFunc("/restart/{name}", store.ReStartProcess).Methods(http.MethodGet)
progressRouter.HandleFunc("/listporgress", store.Status).Methods(http.MethodGet)
progressRouter.HandleFunc("/listport", store.ListPortsHandler).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/update", sys.UpdateAppHandler).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/info", files.HandleSystemInfo).Methods(http.MethodGet)
router.HandleFunc("/file/read", files.HandleReadDir).Methods(http.MethodGet)
router.HandleFunc("/file/stat", files.HandleStat).Methods(http.MethodGet)
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/writefile", files.HandleWriteFile).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("/localchat/sse", localchat.SseHandler).Methods(http.MethodGet)
router.HandleFunc("/localchat/message", localchat.HandleMessage).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)
srv = &http.Server{Addr: serverAddress, Handler: router}
Serve(srv)
@ -79,7 +80,7 @@ func OsStart() {
func OsStop() {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
err := progress.StopAllHandler()
err := store.StopAllHandler()
if err != nil {
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)}
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
func parseMode(modeStr string) (os.FileMode, error) {

View file

@ -4,6 +4,7 @@ import (
"compress/bzip2"
"compress/gzip"
"fmt"
"godo/libs"
"io"
"os"
"path/filepath"
@ -48,13 +49,26 @@ func Untar(reader io.Reader, destPath string) error {
}
target := filepath.Join(destPath, header.Name)
// Ensure that all directories in the path are created.
if header.Typeflag == tar.TypeDir {
if !libs.PathExists(target) {
if err := os.MkdirAll(target, os.FileMode(header.Mode)); err != nil {
return err
}
}
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))
if err != nil {
return err
@ -114,21 +128,33 @@ func Unzip(zipFilePath, destPath string) error {
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() {
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
}
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())
if err != nil {
return err
}
defer outFile.Close()
_, err = io.Copy(outFile, rc)
if err != nil {
// Write the contents of the zip file entry to the output file.
if _, err := io.Copy(outFile, rc); err != nil {
return err
}
}
@ -155,11 +181,15 @@ func IsSupportedCompressionFormat(filename string) bool {
// HandlerFile 根据文件后缀判断是否支持解压,支持则解压,否则移动文件
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))
return os.Rename(filePath, newPath)
}
// newPath := filepath.Join(destDir, filepath.Base(filePath))
// return os.Rename(filePath, newPath)
// 解压文件
return Decompress(filePath, destDir)
}
return nil
}

View file

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

View file

@ -17,5 +17,5 @@ require (
github.com/tklauser/numcpus v0.8.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/sys v0.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.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
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 (
"context"
@ -73,7 +73,7 @@ func IsProcessRunning(appName string) (bool, error) {
if cmd.SysProcAttr == nil {
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 {
return false, fmt.Errorf("error checking process: %w", err)

View file

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

View file

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

View file

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

View file

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

View file

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

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 (
"fmt"
"log"
"net/http"
"os"
"time"
"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 {
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)
processes[name].Running = false
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
}
func StopProcess(w http.ResponseWriter, r *http.Request) {
@ -37,9 +51,10 @@ func StopAllHandler() error {
defer processesMu.Unlock()
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)
}
processes[name].Running = false
}
return nil
}
@ -49,15 +64,6 @@ func StopAll(w http.ResponseWriter, r *http.Request) {
respondWithError(w, http.StatusInternalServerError, err.Error())
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)
fmt.Fprintf(w, "All processes stopped.")
@ -70,16 +76,19 @@ func ReStartProcess(w http.ResponseWriter, r *http.Request) {
// Stop the process first
if cmd, ok := processes[name]; ok {
if err := cmd.Cmd.Process.Kill(); err != nil {
respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to stop process %s before restart: %v", name, err))
return
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))
// return
log.Printf("Failed to stop process %s before restart: %v", name, err)
}
//delete(processes, name)
cmd.Running = false
// processesMu.Lock()
// defer processesMu.Unlock()
processes[name].Running = false
} else {
respondWithError(w, http.StatusNotFound, fmt.Sprintf("Process %s not found to restart", name))
return
}
time.Sleep(time.Second * 2)
// Start the process again
err := ExecuteScript(name)
if err != nil {

View file

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