feat(archive): add support for 7z and bz2 / extract rar and 7zip files protected with password

This commit is contained in:
Aaron Liu 2025-08-21 10:18:01 +08:00
parent d2ccb0b652
commit 5a1665a96a
7 changed files with 76 additions and 14 deletions

View File

@ -475,6 +475,8 @@
"defaultEncoding": "Default",
"chineseMajorEncoding": "",
"selectEncoding": "ZIP file encoding",
"password": "Password",
"passwordDescription": "If the archive file is not encrypted, please leave this field empty.",
"noEncodingSelected": "No encoding method selected",
"listingFiles": "Listing files...",
"listingFileError": "Failed to list files: {{message}}",

View File

@ -475,6 +475,8 @@
"defaultEncoding": "デフォルト",
"chineseMajorEncoding": "",
"selectEncoding": "ZIPファイルエンコード",
"password": "圧縮ファイルパスワード",
"passwordDescription": "圧縮ファイルが暗号化されていない場合、ここは空のままにしてください。",
"noEncodingSelected": "エンコード方式を選択していません",
"listingFiles": "ファイル一覧取得中...",
"listingFileError": "ファイル一覧取得中にエラーが発生しました:{{message}}",

View File

@ -475,6 +475,8 @@
"defaultEncoding": "缺省",
"chineseMajorEncoding": "简体中文常见编码",
"selectEncoding": "ZIP 文件编码",
"password": "压缩文件密码",
"passwordDescription": "如果加压缩文件未加密,此处请留空。",
"noEncodingSelected": "未选择编码方式",
"listingFiles": "列取文件中...",
"listingFileError": "列取文件时出错:{{message}}",

View File

@ -471,6 +471,8 @@
"defaultEncoding": "預設",
"chineseMajorEncoding": "簡體中文常見編碼",
"selectEncoding": "ZIP 檔案編碼",
"password": "壓縮檔案密碼",
"passwordDescription": "如果壓縮檔案未加密,此處請留空。",
"noEncodingSelected": "未選擇編碼方式",
"listingFiles": "列取檔案中...",
"listingFileError": "列取檔案時出錯:{{message}}",

View File

@ -4,6 +4,7 @@ export interface ArchiveWorkflowService {
src: string[];
dst: string;
encoding?: string;
password?: string;
}
export interface TaskListResponse {

View File

@ -11,7 +11,7 @@ import Boolset from "../../../util/boolset.ts";
import CrUri, { Filesystem } from "../../../util/uri.ts";
import { FileManagerIndex } from "../FileManager.tsx";
const supportedArchiveTypes = ["zip", "gz", "xz", "tar", "rar"];
const supportedArchiveTypes = ["zip", "gz", "xz", "tar", "rar", "7z", "bz2"];
export const canManageVersion = (file: FileResponse, bs: Boolset) => {
return (

View File

@ -1,11 +1,12 @@
import {
DialogContent,
FormControl,
Grid2,
InputAdornment,
InputLabel,
MenuItem,
Select,
Stack,
TextField,
useMediaQuery,
useTheme,
} from "@mui/material";
@ -20,11 +21,11 @@ import { FileDisplayForm } from "../../Common/Form/FileDisplayForm.tsx";
import { PathSelectorForm } from "../../Common/Form/PathSelectorForm.tsx";
import { ViewTaskAction } from "../../Common/Snackbar/snackbar.tsx";
import DraggableDialog from "../../Dialogs/DraggableDialog.tsx";
import Password from "../../Icons/Password.tsx";
import Translate from "../../Icons/Translate.tsx";
import { FileManagerIndex } from "../FileManager.tsx";
const encodings = [
"gb18030",
"ibm866",
"iso8859_2",
"iso8859_3",
@ -54,6 +55,7 @@ const encodings = [
"windows1258",
"macintoshcyrillic",
"gbk",
"gb18030",
"big5",
"eucjp",
"iso2022jp",
@ -75,13 +77,21 @@ const ExtractArchive = () => {
const [loading, setLoading] = useState(false);
const [path, setPath] = useState("");
const [encoding, setEncoding] = useState(defaultEncodingValue);
const [password, setPassword] = useState("");
const [showPassword, setShowPassword] = useState(false);
const open = useAppSelector((state) => state.globalState.extractArchiveDialogOpen);
const target = useAppSelector((state) => state.globalState.extractArchiveDialogFile);
const current = useAppSelector((state) => state.fileManager[FileManagerIndex.main].pure_path);
const showEncodingOption = useMemo(() => {
return fileExtension(target?.name ?? "") === "zip";
const ext = fileExtension(target?.name ?? "");
return ext === "zip";
}, [target?.name]);
const showPasswordOption = useMemo(() => {
const ext = fileExtension(target?.name ?? "");
return ext === "zip" || ext === "7z";
}, [target?.name]);
useEffect(() => {
@ -105,6 +115,7 @@ const ExtractArchive = () => {
src: [getFileLinkedUri(target)],
dst: path,
encoding: showEncodingOption && encoding != defaultEncodingValue ? encoding : undefined,
password: showPasswordOption && password ? password : undefined,
}),
)
.then(() => {
@ -118,7 +129,7 @@ const ExtractArchive = () => {
.finally(() => {
setLoading(false);
});
}, [target, encoding, path, showEncodingOption]);
}, [target, encoding, path, showPasswordOption, showEncodingOption, password]);
return (
<DraggableDialog
@ -136,10 +147,24 @@ const ExtractArchive = () => {
}}
>
<DialogContent sx={{ pt: 1 }}>
<Stack spacing={3}>
<Stack spacing={3} direction={isMobile ? "column" : "row"}>
{target && <FileDisplayForm file={target} label={t("modals.archiveFile")} />}
{showEncodingOption && (
<Grid2 container spacing={3}>
{target && (
<Grid2
size={{
xs: 12,
md: showEncodingOption ? 6 : 12,
}}
>
<FileDisplayForm file={target} label={t("modals.archiveFile")} />
</Grid2>
)}
{showEncodingOption && (
<Grid2
size={{
xs: 12,
md: 6,
}}
>
<FormControl variant="outlined" fullWidth>
<InputLabel>{t("modals.selectEncoding")}</InputLabel>
<Select
@ -165,12 +190,40 @@ const ExtractArchive = () => {
))}
</Select>
</FormControl>
)}
</Stack>
<Stack spacing={3} direction={isMobile ? "column" : "row"}>
</Grid2>
)}
<Grid2
size={{
xs: 12,
}}
>
<PathSelectorForm onChange={setPath} path={path} variant={"extractTo"} label={t("modals.decompressTo")} />
</Stack>
</Stack>
</Grid2>
{showPasswordOption && (
<Grid2
size={{
xs: 12,
}}
>
<TextField
slotProps={{
input: {
startAdornment: !isMobile && (
<InputAdornment position="start">
<Password />
</InputAdornment>
),
},
}}
fullWidth
placeholder={t("application:modals.passwordDescription")}
label={t("modals.password")}
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</Grid2>
)}
</Grid2>
</DialogContent>
</DraggableDialog>
);