display library disk usage (#976)
This commit is contained in:
@@ -723,5 +723,18 @@
|
||||
"cannotFindSourceFile": "Cannot find source file",
|
||||
"cleanUp": "Clean up",
|
||||
"cleanUpConfirmation": "Are you sure to remove resources without source file?",
|
||||
"cleanedUpSuccessfully": "Cleaned up successfully"
|
||||
"cleanedUpSuccessfully": "Cleaned up successfully",
|
||||
"libraryDescriptions": {
|
||||
"library": "Contains all files created by Enjoy while you are using the app.",
|
||||
"database": "Records all your activities and settings.",
|
||||
"audios": "Contains all audio files you added.",
|
||||
"videos": "Contains all video files you added.",
|
||||
"segments": "Contains all segments you make recording or note.",
|
||||
"speeches": "Contains all speeches created by AI TTS.",
|
||||
"recordings": "Contains all recordings you made.",
|
||||
"whisper": "Contains all whisper models you downloaded.",
|
||||
"waveforms": "Contains all waveforms decoded from audio/videos. They're for caching. It's save to delete them.",
|
||||
"logs": "Contains some logs helpful for debugging.",
|
||||
"cache": "Contains cached files. They will be cleaned up automatically."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -723,5 +723,18 @@
|
||||
"cannotFindSourceFile": "无法找到源文件,可能已经被删除",
|
||||
"cleanUp": "清理",
|
||||
"cleanUpConfirmation": "您确定要移除所有找不到源文件的资源吗?",
|
||||
"cleanedUpSuccessfully": "清理成功"
|
||||
"cleanedUpSuccessfully": "清理成功",
|
||||
"libraryDescriptions": {
|
||||
"library": "资源库总目录,包含所有您使用 Enjoy 过程产生的文件",
|
||||
"database": "数据库文件,记录您使用 Enjoy 的所有数据",
|
||||
"audios": "您添加的所有音频文件",
|
||||
"videos": "您添加的所有视频文件",
|
||||
"segments": "音频/视频文件的段落,跟读过程中产生",
|
||||
"speeches": "AI TTS 服务生成的语音文件",
|
||||
"recordings": "您的所有录音文件",
|
||||
"whisper": "Whisper 模型文件,用于语音转文本",
|
||||
"waveforms": "波形文件,用于显示音频波形。用作缓存,可以删除",
|
||||
"logs": "日志文件,帮助开发者调试问题",
|
||||
"cache": "缓存文件,会自动清理。"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,6 +384,43 @@ ${log}
|
||||
}
|
||||
);
|
||||
|
||||
ipcMain.handle("app-disk-usage", () => {
|
||||
const paths: { [key: string]: string } = {
|
||||
library: settings.libraryPath(),
|
||||
database: settings.dbPath(),
|
||||
audios: path.join(settings.userDataPath(), "audios"),
|
||||
videos: path.join(settings.userDataPath(), "videos"),
|
||||
segments: path.join(settings.userDataPath(), "segments"),
|
||||
speeches: path.join(settings.userDataPath(), "speeches"),
|
||||
recordings: path.join(settings.userDataPath(), "recordings"),
|
||||
whisper: path.join(settings.libraryPath(), "whisper"),
|
||||
waveforms: path.join(settings.libraryPath(), "waveforms"),
|
||||
logs: path.join(settings.libraryPath(), "logs"),
|
||||
cache: settings.cachePath(),
|
||||
};
|
||||
|
||||
const sizeSync = (p: string): number => {
|
||||
const stat = fs.statSync(p);
|
||||
if (stat.isFile()) return stat.size;
|
||||
else if (stat.isDirectory())
|
||||
return fs
|
||||
.readdirSync(p)
|
||||
.reduce((a, e) => a + sizeSync(path.join(p, e)), 0);
|
||||
else return 0; // can't take size of a stream/symlink/socket/
|
||||
};
|
||||
|
||||
return Object.keys(paths).map((key) => {
|
||||
const p = paths[key];
|
||||
const size = sizeSync(p);
|
||||
|
||||
return {
|
||||
name: key,
|
||||
path: p,
|
||||
size,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// Shell
|
||||
ipcMain.handle("shell-open-external", (_event, url) => {
|
||||
shell.openExternal(url);
|
||||
|
||||
@@ -47,6 +47,9 @@ contextBridge.exposeInMainWorld("__ENJOY_APP__", {
|
||||
removeCmdOutputListeners: () => {
|
||||
ipcRenderer.removeAllListeners("app-on-cmd-output");
|
||||
},
|
||||
diskUsage: () => {
|
||||
return ipcRenderer.invoke("app-disk-usage");
|
||||
},
|
||||
version,
|
||||
},
|
||||
window: {
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
import { t } from "i18next";
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
ScrollArea,
|
||||
Separator,
|
||||
} from "@renderer/components/ui";
|
||||
import {
|
||||
AppSettingsProviderContext,
|
||||
} from "@renderer/context";
|
||||
import { useContext } from "react";
|
||||
import { AppSettingsProviderContext } from "@renderer/context";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { InfoIcon } from "lucide-react";
|
||||
import { humanFileSize } from "@/utils";
|
||||
|
||||
export const LibrarySettings = () => {
|
||||
const { libraryPath, EnjoyApp } = useContext(AppSettingsProviderContext);
|
||||
@@ -25,12 +33,6 @@ export const LibrarySettings = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const openLibraryPath = async () => {
|
||||
if (libraryPath) {
|
||||
await EnjoyApp.shell.openPath(libraryPath);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-start justify-between py-4">
|
||||
<div className="">
|
||||
@@ -40,9 +42,26 @@ export const LibrarySettings = () => {
|
||||
|
||||
<div className="">
|
||||
<div className="flex items-center justify-end space-x-2 mb-2">
|
||||
<Button variant="secondary" size="sm" onClick={openLibraryPath}>
|
||||
{t("open")}
|
||||
</Button>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="secondary" size="sm">
|
||||
{t("detail")}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="h-3/5">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("usage")}</DialogTitle>
|
||||
<DialogDescription className="sr-only">
|
||||
Disk usage of Enjoy
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="h-full overflow-hidden">
|
||||
<ScrollArea className="h-full px-4">
|
||||
<DiskUsage />
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
@@ -59,3 +78,51 @@ export const LibrarySettings = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const DiskUsage = () => {
|
||||
const [usage, setUsage] = useState<DiskUsageType>([]);
|
||||
const { EnjoyApp } = useContext(AppSettingsProviderContext);
|
||||
|
||||
const openPath = async (path: string) => {
|
||||
if (path) {
|
||||
await EnjoyApp.shell.openPath(path);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
EnjoyApp.app.diskUsage().then((usage) => {
|
||||
console.log(usage);
|
||||
setUsage(usage);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="grid gap-4">
|
||||
{usage.map((item) => (
|
||||
<div key={item.name}>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center space-x-2 mb-2">
|
||||
<Badge>/{item.path.split("/").pop()}</Badge>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{humanFileSize(item.size)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
{t(`libraryDescriptions.${item.name}`)}
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => openPath(item.path)}
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
>
|
||||
{t("open")}
|
||||
</Button>
|
||||
</div>
|
||||
<Separator className="my-2" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
1
enjoy/src/types/enjoy-app.d.ts
vendored
1
enjoy/src/types/enjoy-app.d.ts
vendored
@@ -13,6 +13,7 @@ type EnjoyAppType = {
|
||||
createIssue: (title: string, body: string) => Promise<void>;
|
||||
onCmdOutput: (callback: (event, output: string) => void) => void;
|
||||
removeCmdOutputListeners: () => void;
|
||||
diskUsage: () => Promise<DiskUsageType>;
|
||||
version: string;
|
||||
};
|
||||
window: {
|
||||
|
||||
6
enjoy/src/types/index.d.ts
vendored
6
enjoy/src/types/index.d.ts
vendored
@@ -183,3 +183,9 @@ type PlatformInfo = {
|
||||
arch: string;
|
||||
version: string;
|
||||
};
|
||||
|
||||
type DiskUsageType = {
|
||||
name: string;
|
||||
path: string;
|
||||
size: number;
|
||||
}[];
|
||||
|
||||
@@ -127,3 +127,24 @@ export const convertIpaToNormal = (
|
||||
return converted;
|
||||
}
|
||||
};
|
||||
|
||||
// make size of bytes human readable
|
||||
export const humanFileSize = (bytes: number, si: boolean = false) => {
|
||||
const thresh = si ? 1000 : 1024;
|
||||
if (Math.abs(bytes) < thresh) {
|
||||
return bytes + " B";
|
||||
}
|
||||
const units = si
|
||||
? ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
|
||||
: ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
|
||||
let u = -1;
|
||||
const r = 10;
|
||||
do {
|
||||
bytes /= thresh;
|
||||
++u;
|
||||
} while (
|
||||
Math.round(Math.abs(bytes) * r) / r >= thresh &&
|
||||
u < units.length - 1
|
||||
);
|
||||
return bytes.toFixed(1) + " " + units[u];
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user