Skip to content

Commit e830b93

Browse files
committed
Add GB18030 filename encoding support
1 parent 5918f91 commit e830b93

19 files changed

Lines changed: 2118 additions & 200 deletions

application/i18n/locales/en.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,10 @@ const en: Messages = {
431431
'sftp.status.uploading': 'Uploading...',
432432
'sftp.status.ready': 'Ready',
433433
'sftp.goUp': 'Go up',
434+
'sftp.encoding.label': 'Filename Encoding',
435+
'sftp.encoding.auto': 'Auto',
436+
'sftp.encoding.utf8': 'UTF-8',
437+
'sftp.encoding.gb18030': 'GB18030',
434438
'sftp.goHome': 'Go to home',
435439
'sftp.folderName': 'Folder name',
436440
'sftp.folderName.placeholder': 'Enter folder name',
@@ -614,6 +618,8 @@ const en: Messages = {
614618
'hostDetails.sftp.sudo': 'Sudo Mode',
615619
'hostDetails.sftp.sudo.desc': 'Automatically acquire Root privileges using stored password',
616620
'hostDetails.sftp.sudo.passwordWarning': 'Sudo mode requires a password. Configure one above, or ensure the server allows passwordless sudo.',
621+
'hostDetails.sftp.encoding': 'Filename Encoding',
622+
'hostDetails.sftp.encoding.desc': 'Select the encoding used to decode and send SFTP filenames.',
617623
'hostDetails.label.placeholder': 'Label (e.g., Production Server)',
618624
'hostDetails.group.placeholder': 'Parent Group',
619625
'hostDetails.section.credentials': 'Credentials',

application/i18n/locales/zh-CN.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,10 @@ const zhCN: Messages = {
296296
'sftp.status.uploading': '上传中...',
297297
'sftp.status.ready': '就绪',
298298
'sftp.goUp': '上一级',
299+
'sftp.encoding.label': '文件名编码',
300+
'sftp.encoding.auto': '自动',
301+
'sftp.encoding.utf8': 'UTF-8',
302+
'sftp.encoding.gb18030': 'GB18030',
299303
'sftp.goHome': '返回主目录',
300304
'sftp.folderName': '文件夹名称',
301305
'sftp.folderName.placeholder': '输入文件夹名称',
@@ -374,6 +378,8 @@ const zhCN: Messages = {
374378
'hostDetails.sftp.sudo': 'Sudo 提权模式',
375379
'hostDetails.sftp.sudo.desc': '使用保存的密码自动获取 Root 权限',
376380
'hostDetails.sftp.sudo.passwordWarning': 'Sudo 模式需要密码。请在上方配置密码,或确保服务器允许免密 sudo。',
381+
'hostDetails.sftp.encoding': '文件名编码',
382+
'hostDetails.sftp.encoding.desc': '选择用于解码和发送 SFTP 文件名的编码。',
377383
'hostDetails.label.placeholder': '名称(例如:Production Server)',
378384
'hostDetails.group.placeholder': '父级 Group',
379385
'hostDetails.section.credentials': '凭据',

application/state/useSftpBackend.ts

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useCallback } from "react";
22
import { netcattyBridge } from "../../infrastructure/services/netcattyBridge";
3-
import type { RemoteFile } from "../../types";
3+
import type { RemoteFile, SftpFilenameEncoding } from "../../types";
44

55
export const useSftpBackend = () => {
66
const openSftp = useCallback(async (options: NetcattySSHOptions) => {
@@ -15,34 +15,34 @@ export const useSftpBackend = () => {
1515
return bridge.closeSftp(sftpId);
1616
}, []);
1717

18-
const listSftp = useCallback(async (sftpId: string, path: string) => {
18+
const listSftp = useCallback(async (sftpId: string, path: string, encoding?: SftpFilenameEncoding) => {
1919
const bridge = netcattyBridge.get();
2020
if (!bridge?.listSftp) throw new Error("SFTP bridge unavailable");
21-
return bridge.listSftp(sftpId, path);
21+
return bridge.listSftp(sftpId, path, encoding);
2222
}, []);
2323

24-
const readSftp = useCallback(async (sftpId: string, path: string) => {
24+
const readSftp = useCallback(async (sftpId: string, path: string, encoding?: SftpFilenameEncoding) => {
2525
const bridge = netcattyBridge.get();
2626
if (!bridge?.readSftp) throw new Error("SFTP bridge unavailable");
27-
return bridge.readSftp(sftpId, path);
27+
return bridge.readSftp(sftpId, path, encoding);
2828
}, []);
2929

30-
const readSftpBinary = useCallback(async (sftpId: string, path: string) => {
30+
const readSftpBinary = useCallback(async (sftpId: string, path: string, encoding?: SftpFilenameEncoding) => {
3131
const bridge = netcattyBridge.get();
3232
if (!bridge?.readSftpBinary) throw new Error("readSftpBinary unavailable");
33-
return bridge.readSftpBinary(sftpId, path);
33+
return bridge.readSftpBinary(sftpId, path, encoding);
3434
}, []);
3535

36-
const writeSftp = useCallback(async (sftpId: string, path: string, content: string) => {
36+
const writeSftp = useCallback(async (sftpId: string, path: string, content: string, encoding?: SftpFilenameEncoding) => {
3737
const bridge = netcattyBridge.get();
3838
if (!bridge?.writeSftp) throw new Error("SFTP bridge unavailable");
39-
return bridge.writeSftp(sftpId, path, content);
39+
return bridge.writeSftp(sftpId, path, content, encoding);
4040
}, []);
4141

42-
const writeSftpBinary = useCallback(async (sftpId: string, path: string, content: ArrayBuffer) => {
42+
const writeSftpBinary = useCallback(async (sftpId: string, path: string, content: ArrayBuffer, encoding?: SftpFilenameEncoding) => {
4343
const bridge = netcattyBridge.get();
4444
if (!bridge?.writeSftpBinary) throw new Error("writeSftpBinary unavailable");
45-
return bridge.writeSftpBinary(sftpId, path, content);
45+
return bridge.writeSftpBinary(sftpId, path, content, encoding);
4646
}, []);
4747

4848
const writeSftpBinaryWithProgress = useCallback(
@@ -51,6 +51,7 @@ export const useSftpBackend = () => {
5151
path: string,
5252
content: ArrayBuffer,
5353
transferId: string,
54+
encoding?: SftpFilenameEncoding,
5455
onProgress?: (transferred: number, total: number, speed: number) => void,
5556
onComplete?: () => void,
5657
onError?: (error: string) => void,
@@ -62,6 +63,7 @@ export const useSftpBackend = () => {
6263
path,
6364
content,
6465
transferId,
66+
encoding,
6567
onProgress,
6668
onComplete,
6769
onError,
@@ -70,34 +72,34 @@ export const useSftpBackend = () => {
7072
[],
7173
);
7274

73-
const mkdirSftp = useCallback(async (sftpId: string, path: string) => {
75+
const mkdirSftp = useCallback(async (sftpId: string, path: string, encoding?: SftpFilenameEncoding) => {
7476
const bridge = netcattyBridge.get();
7577
if (!bridge?.mkdirSftp) throw new Error("mkdirSftp unavailable");
76-
return bridge.mkdirSftp(sftpId, path);
78+
return bridge.mkdirSftp(sftpId, path, encoding);
7779
}, []);
7880

79-
const deleteSftp = useCallback(async (sftpId: string, path: string) => {
81+
const deleteSftp = useCallback(async (sftpId: string, path: string, encoding?: SftpFilenameEncoding) => {
8082
const bridge = netcattyBridge.get();
8183
if (!bridge?.deleteSftp) throw new Error("deleteSftp unavailable");
82-
return bridge.deleteSftp(sftpId, path);
84+
return bridge.deleteSftp(sftpId, path, encoding);
8385
}, []);
8486

85-
const renameSftp = useCallback(async (sftpId: string, oldPath: string, newPath: string) => {
87+
const renameSftp = useCallback(async (sftpId: string, oldPath: string, newPath: string, encoding?: SftpFilenameEncoding) => {
8688
const bridge = netcattyBridge.get();
8789
if (!bridge?.renameSftp) throw new Error("renameSftp unavailable");
88-
return bridge.renameSftp(sftpId, oldPath, newPath);
90+
return bridge.renameSftp(sftpId, oldPath, newPath, encoding);
8991
}, []);
9092

91-
const statSftp = useCallback(async (sftpId: string, path: string) => {
93+
const statSftp = useCallback(async (sftpId: string, path: string, encoding?: SftpFilenameEncoding) => {
9294
const bridge = netcattyBridge.get();
9395
if (!bridge?.statSftp) throw new Error("statSftp unavailable");
94-
return bridge.statSftp(sftpId, path);
96+
return bridge.statSftp(sftpId, path, encoding);
9597
}, []);
9698

97-
const chmodSftp = useCallback(async (sftpId: string, path: string, mode: string) => {
99+
const chmodSftp = useCallback(async (sftpId: string, path: string, mode: string, encoding?: SftpFilenameEncoding) => {
98100
const bridge = netcattyBridge.get();
99101
if (!bridge?.chmodSftp) throw new Error("chmodSftp unavailable");
100-
return bridge.chmodSftp(sftpId, path, mode);
102+
return bridge.chmodSftp(sftpId, path, mode, encoding);
101103
}, []);
102104

103105
const listLocalDir = useCallback(async (path: string): Promise<RemoteFile[]> => {
@@ -185,7 +187,7 @@ export const useSftpBackend = () => {
185187
remotePath: string,
186188
fileName: string,
187189
appPath: string,
188-
options?: { enableWatch?: boolean }
190+
options?: { enableWatch?: boolean; encoding?: SftpFilenameEncoding }
189191
): Promise<{ localTempPath: string; watchId?: string }> => {
190192
const bridge = netcattyBridge.get();
191193
if (!bridge?.downloadSftpToTemp || !bridge?.openWithApplication) {
@@ -194,7 +196,7 @@ export const useSftpBackend = () => {
194196

195197
// Download the file to temp
196198
console.log("[SFTPBackend] Downloading file to temp", { sftpId, remotePath, fileName });
197-
const tempPath = await bridge.downloadSftpToTemp(sftpId, remotePath, fileName);
199+
const tempPath = await bridge.downloadSftpToTemp(sftpId, remotePath, fileName, options?.encoding);
198200
console.log("[SFTPBackend] File downloaded to temp", { tempPath });
199201

200202
// Register temp file for cleanup when SFTP session closes (regardless of auto-sync setting)
@@ -217,7 +219,7 @@ export const useSftpBackend = () => {
217219
if (options?.enableWatch && bridge.startFileWatch) {
218220
try {
219221
console.log("[SFTPBackend] Starting file watch", { tempPath, remotePath, sftpId });
220-
const result = await bridge.startFileWatch(tempPath, remotePath, sftpId);
222+
const result = await bridge.startFileWatch(tempPath, remotePath, sftpId, options?.encoding);
221223
watchId = result.watchId;
222224
console.log("[SFTPBackend] File watch started successfully", { watchId, tempPath, remotePath });
223225
} catch (err) {
@@ -262,4 +264,3 @@ export const useSftpBackend = () => {
262264
downloadSftpToTempAndOpen,
263265
};
264266
};
265-

0 commit comments

Comments
 (0)