diff --git a/src/controller/storage/mount/mount.ts b/src/controller/storage/mount/mount.ts index b0df1f7..76928f6 100644 --- a/src/controller/storage/mount/mount.ts +++ b/src/controller/storage/mount/mount.ts @@ -33,6 +33,21 @@ async function reupMount(noRefreshUI?: boolean) { } } +/** + * 清除所有已挂载存储的 VFS 目录缓存并刷新挂载列表 + * 解决远程添加文件后本地不显示的问题 + */ +async function refreshMountWithVfsCache(): Promise { + try { + // 先清除所有 VFS 缓存,强制 rclone 重新读取远程目录 + await mountRepository.forgetAllVfsCache() + // 再刷新挂载列表 + await mountRepository.refreshMountList() + } catch (error) { + mountLogger.error('Failed to refresh mount with VFS cache', error as Error) + } +} + /** * 获取挂载配置 */ @@ -136,6 +151,7 @@ async function getAvailableDriveLetter(): Promise { export { reupMount, + refreshMountWithVfsCache, mountStorage, unmountStorage, addMountStorage, diff --git a/src/page/setting/components/AdvancedSettings.tsx b/src/page/setting/components/AdvancedSettings.tsx index c71a0fd..0c7f7ce 100644 --- a/src/page/setting/components/AdvancedSettings.tsx +++ b/src/page/setting/components/AdvancedSettings.tsx @@ -1,9 +1,9 @@ /** * Advanced Settings Component - * 高级设置组件(启动参数) + * 高级设置组件(网络代理、启动参数) */ -import { Button, Collapse, Form, Input, Message, Modal } from '@arco-design/web-react' +import { Button, Collapse, Form, Input, InputNumber, Message, Modal, Select } from '@arco-design/web-react' import { useTranslation } from 'react-i18next' import { nmConfig, saveNmConfig } from '../../../services/ConfigService' import { useSettingsStore } from '../../../stores/useSettingsStore' @@ -15,9 +15,99 @@ export function AdvancedSettings(): JSX.Element { const { t } = useTranslation() const { increment: incrementSettings } = useSettingsStore() + const proxy = nmConfig.settings.proxy + const proxyType = proxy?.type || 'no_proxy' + return (
+ + + + + + {proxyType !== 'no_proxy' && ( + <> + + { + if (nmConfig.settings.proxy) { + nmConfig.settings.proxy.host = value + incrementSettings() + } + }} + /> + + + { + if (nmConfig.settings.proxy) { + nmConfig.settings.proxy.port = value || undefined + incrementSettings() + } + }} + style={{ width: '12rem' }} + /> + + + { + if (nmConfig.settings.proxy) { + nmConfig.settings.proxy.username = value || undefined + incrementSettings() + } + }} + /> + + + { + if (nmConfig.settings.proxy) { + nmConfig.settings.proxy.password = value || undefined + incrementSettings() + } + }} + /> + + + )} + +
+ {t('proxy_settings_hint')} +
+
+ { await refreshMountList() } +/** + * 清除所有已挂载存储的 VFS 目录缓存 + * 用于刷新操作,强制 rclone 重新从远程读取目录列表 + */ +export async function forgetAllVfsCache(): Promise { + const mounts = rcloneInfo.mountList + if (mounts.length === 0) return + + const forgetPromises = mounts.map(async (mount) => { + try { + await rclone_api_post('/vfs/forget', { + fs: convertStoragePath(mount.storageName) || mount.storageName, + }, true) + } catch { + // 忽略 - 非关键操作 + } + }) + + await Promise.all(forgetPromises) + mountLogger.debug('VFS cache forgotten for all mounts', { count: mounts.length }) +} + /** * 执行卸载操作 * 卸载前先清理 VFS 缓存引用,卸载后清理残留临时文件 diff --git a/src/type/config.d.ts b/src/type/config.d.ts index 0cfb212..9596acf 100644 --- a/src/type/config.d.ts +++ b/src/type/config.d.ts @@ -25,6 +25,13 @@ interface NMConfig { lockOnSleep?: boolean // 休眠时锁定 idleTimeoutMinutes?: number // 空闲超时锁定(分钟),0或undefined表示禁用 } + proxy?: { + type: 'no_proxy' | 'http' | 'socks5' // 代理类型 + host?: string // 代理主机地址 + port?: number // 代理端口 + username?: string // 代理用户名(可选) + password?: string // 代理密码(可选) + } } notice?: Notice framework: { diff --git a/src/utils/rclone/process.ts b/src/utils/rclone/process.ts index 93221e5..5e7738c 100644 --- a/src/utils/rclone/process.ts +++ b/src/utils/rclone/process.ts @@ -12,6 +12,29 @@ import { netmountLogDir, rcloneConfigFile, rcloneLogFile } from '../netmountPath import { restartSidecar, startSidecarAndWait, stopSidecarGracefully } from '../sidecarService' import { parseExtraCliArgs } from '../cliArgs' +/** + * 构建代理URL + * 根据代理配置生成 rclone --http-proxy 参数值 + * 支持 HTTP 和 SOCKS5 代理,格式:protocol://[user:pass@]host:port + */ +function buildProxyUrl(proxy: { type: string; host?: string; port?: number; username?: string; password?: string }): string | undefined { + if (proxy.type === 'no_proxy' || !proxy.host || !proxy.port) { + return undefined + } + + const protocol = proxy.type === 'socks5' ? 'socks5' : 'http' + let auth = '' + if (proxy.username) { + auth = encodeURIComponent(proxy.username) + if (proxy.password) { + auth += ':' + encodeURIComponent(proxy.password) + } + auth += '@' + } + + return `${protocol}://${auth}${proxy.host}:${proxy.port}` +} + async function startRclone() { if (rcloneInfo.process.child) { await stopRclone() @@ -67,6 +90,17 @@ async function startRclone() { if (nmConfig.framework.rclone.user === '') { args.push('--rc-no-auth') } + + // 应用代理配置 + const proxy = nmConfig.settings.proxy + if (proxy && proxy.type !== 'no_proxy') { + const proxyUrl = buildProxyUrl(proxy) + if (proxyUrl) { + args.push(`--http-proxy=${proxyUrl}`) + logger.info('Rclone proxy configured', 'Rclone', { type: proxy.type, host: proxy.host }) + } + } + args.push(...parseExtraCliArgs(nmConfig.framework.rclone.extraArgs)) // 使用 Rust 端启动 sidecar,确保由主进程创建