清理qBittorrent接入残留:删除死schema分组与前端下载类型,下载器保存目录键改为downloader.savepath并兼容旧键

This commit is contained in:
truewhile
2026-09-05 18:08:52 +08:00
parent 030ed5f325
commit 28551c7883
16 changed files with 31 additions and 88 deletions
+1 -1
View File
@@ -55,7 +55,7 @@ services:
bind: bind:
create_host_path: false create_host_path: false
# 下载目录。需要和 qBittorrent 保存路径保持一致。 # 下载目录。需要和下载器保存路径保持一致。
# NAS 示例:source: /vol1/1000/Downloads # NAS 示例:source: /vol1/1000/Downloads
# Windows Docker Desktop 示例:source: D:/Downloads # Windows Docker Desktop 示例:source: D:/Downloads
- type: bind - type: bind
+1 -1
View File
@@ -69,7 +69,7 @@ services:
MEBOX_LOGGING_MAX_AGE_DAYS: "30" MEBOX_LOGGING_MAX_AGE_DAYS: "30"
extra_hosts: extra_hosts:
# 容器访问宿主机服务用,例如 qBittorrent: http://host.docker.internal:8085 # 容器访问宿主机服务(如下载器等)用: http://host.docker.internal:8085
- "host.docker.internal:host-gateway" - "host.docker.internal:host-gateway"
healthcheck: healthcheck:
+1 -1
View File
@@ -50,7 +50,7 @@ services:
bind: bind:
create_host_path: false create_host_path: false
# 下载目录。需要和 qBittorrent 保存路径保持一致。 # 下载目录。需要和下载器保存路径保持一致。
# NAS 示例:source: /vol1/1000/Downloads # NAS 示例:source: /vol1/1000/Downloads
# Windows Docker Desktop 示例:source: D:/Downloads # Windows Docker Desktop 示例:source: D:/Downloads
- type: bind - type: bind
+1 -1
View File
@@ -29,7 +29,7 @@ services:
ports: ports:
- "18080:8080" - "18080:8080"
# 让容器可以访问宿主机上的 qBittorrent。 # 让容器可以访问宿主机上的服务(如下载器)。
# qB 地址通常可填:http://host.docker.internal:8085 # qB 地址通常可填:http://host.docker.internal:8085
extra_hosts: extra_hosts:
- "host.docker.internal:host-gateway" - "host.docker.internal:host-gateway"
-10
View File
@@ -116,16 +116,6 @@ func schemaHandler(_ *service.Container) gin.HandlerFunc {
{"key": "adult.pin", "type": "text"}, {"key": "adult.pin", "type": "text"},
}, },
}, },
{
"key": "qbittorrent",
"label": "qBittorrent",
"items": []gin.H{
{"key": "qbittorrent.url", "type": "text"},
{"key": "qbittorrent.username", "type": "text"},
{"key": "qbittorrent.password", "type": "text"},
{"key": "qbittorrent.savepath", "type": "text"},
},
},
{ {
"key": "system-update", "key": "system-update",
"label": "系统更新", "label": "系统更新",
+1 -1
View File
@@ -1,6 +1,6 @@
// Package service — AES-GCM crypto helper for at-rest secrets. // Package service — AES-GCM crypto helper for at-rest secrets.
// //
// Sensitive fields (third-party API keys, qBittorrent passwords, …) are // Sensitive fields (third-party API keys, service passwords, …) are
// stored in SQLite. We encrypt them with AES-256-GCM keyed off the JWT // stored in SQLite. We encrypt them with AES-256-GCM keyed off the JWT
// secret so a stolen DB file alone is not enough to recover the // secret so a stolen DB file alone is not enough to recover the
// plaintext credentials. // plaintext credentials.
+5 -1
View File
@@ -77,7 +77,11 @@ func (s *FileManagerService) allowedRoots() (map[string]string, error) {
} }
addSetting("organize-source", "organize.source_dir") addSetting("organize-source", "organize.source_dir")
addSetting("organize-target", "organize.target_dir") addSetting("organize-target", "organize.target_dir")
addSetting("qb-savepath", "qbittorrent.savepath") addSetting("downloader-savepath", "downloader.savepath")
// 兼容历史键名 qbittorrent.savepath:旧版本把下载器保存目录存在该键下
if value, err := s.repo.Setting.Get(context.Background(), "downloader.savepath"); err != nil || strings.TrimSpace(value) == "" {
addSetting("downloader-savepath", "qbittorrent.savepath")
}
} }
if s.repo != nil && s.repo.Library != nil { if s.repo != nil && s.repo.Library != nil {
libs, err := s.repo.Library.List(context.Background()) libs, err := s.repo.Library.List(context.Background())
+4 -3
View File
@@ -196,9 +196,10 @@ func TestFileManagerIncludesConfiguredOrganizeRoots(t *testing.T) {
got[root.Label] = root.Path got[root.Label] = root.Path
} }
for label, want := range map[string]string{ for label, want := range map[string]string{
"organize-source": filepath.Clean(sourceDir), "organize-source": filepath.Clean(sourceDir),
"organize-target": filepath.Clean(targetDir), "organize-target": filepath.Clean(targetDir),
"qb-savepath": filepath.Clean(qbDir), // 旧键 qbittorrent.savepath 写入应经兼容回退落在 downloader-savepath 下
"downloader-savepath": filepath.Clean(qbDir),
} { } {
if got[label] != want { if got[label] != want {
t.Fatalf("root %s = %q, want %q; roots=%#v", label, got[label], want, listing.Roots) t.Fatalf("root %s = %q, want %q; roots=%#v", label, got[label], want, listing.Roots)
+1 -1
View File
@@ -136,7 +136,7 @@ func TestResolveAccessibleMappedPathMapsEmbeddedHostDownloadMarker(t *testing.T)
} }
t.Setenv("MEBOX_DOWNLOAD_CONTAINER_DIR", containerDownloads) t.Setenv("MEBOX_DOWNLOAD_CONTAINER_DIR", containerDownloads)
got, _, err := resolveAccessibleMappedPath("/vol1/1000/Docker/qbittorrent/downloads/国产剧") got, _, err := resolveAccessibleMappedPath("/vol1/1000/nas/downloads/国产剧")
if err != nil { if err != nil {
t.Fatalf("resolveAccessibleMappedPath() error = %v", err) t.Fatalf("resolveAccessibleMappedPath() error = %v", err)
} }
@@ -40,7 +40,7 @@ func (o *OrganizerService) OrganizeSourceCandidates(ctx context.Context) []Organ
out = append(out, OrganizeSourceCandidate{Label: label, Path: clean, Kind: kind}) out = append(out, OrganizeSourceCandidate{Label: label, Path: clean, Kind: kind})
} }
add("默认整理源", o.settingValue(ctx, "organize.source_dir"), "source") add("默认整理源", o.settingValue(ctx, "organize.source_dir"), "source")
add("下载器保存目录", o.settingValue(ctx, "qbittorrent.savepath"), "download") add("下载器保存目录", o.downloaderSavepath(ctx), "download")
add("下载目录", envOrDefault("MEBOX_DOWNLOAD_CONTAINER_DIR", "/downloads"), "download") add("下载目录", envOrDefault("MEBOX_DOWNLOAD_CONTAINER_DIR", "/downloads"), "download")
add("媒体目录", envOrDefault("MEBOX_MEDIA_CONTAINER_DIR", "/media"), "media") add("媒体目录", envOrDefault("MEBOX_MEDIA_CONTAINER_DIR", "/media"), "media")
return out return out
@@ -56,8 +56,17 @@ func (o *OrganizerService) settingValue(ctx context.Context, key string) string
return "" return ""
} }
// downloaderSavepath 返回下载器保存目录,优先新键 downloader.savepath,
// 兼容历史键名 qbittorrent.savepath(旧版本部署已写入的配置不丢失)。
func (o *OrganizerService) downloaderSavepath(ctx context.Context) string {
if v := o.settingValue(ctx, "downloader.savepath"); v != "" {
return v
}
return o.settingValue(ctx, "qbittorrent.savepath")
}
// defaultSourceRoot resolves the source root for a directory organize: // defaultSourceRoot resolves the source root for a directory organize:
// explicit override → organize.source_dir setting → qB default save path → // explicit override → organize.source_dir setting → downloader save path →
// download container dir. // download container dir.
func (o *OrganizerService) defaultSourceRoot(ctx context.Context, override string) string { func (o *OrganizerService) defaultSourceRoot(ctx context.Context, override string) string {
if r := strings.TrimSpace(override); r != "" { if r := strings.TrimSpace(override); r != "" {
@@ -66,7 +75,7 @@ func (o *OrganizerService) defaultSourceRoot(ctx context.Context, override strin
if v := o.settingValue(ctx, "organize.source_dir"); v != "" { if v := o.settingValue(ctx, "organize.source_dir"); v != "" {
return v return v
} }
if v := o.settingValue(ctx, "qbittorrent.savepath"); v != "" { if v := o.downloaderSavepath(ctx); v != "" {
return v return v
} }
return envOrDefault("MEBOX_DOWNLOAD_CONTAINER_DIR", "/downloads") return envOrDefault("MEBOX_DOWNLOAD_CONTAINER_DIR", "/downloads")
+1 -1
View File
@@ -54,7 +54,7 @@ func (o *OrganizerService) resolveTransferMode(ctx context.Context, override Tra
} }
} }
if mode == TransferMove && o.keepSeedingEnabled(ctx) { if mode == TransferMove && o.keepSeedingEnabled(ctx) {
// 移动会删除源文件导致 qBittorrent 停止做种;保种开启时改用硬链接 // 移动会删除源文件导致下载器停止做种;保种开启时改用硬链接
// 既规范命名又保留源文件继续做种上传。硬链接失败时会报错,避免静默 // 既规范命名又保留源文件继续做种上传。硬链接失败时会报错,避免静默
// 退化复制后占用双份磁盘空间。 // 退化复制后占用双份磁盘空间。
return TransferHardlink return TransferHardlink
+1 -1
View File
@@ -7,7 +7,7 @@ import (
) )
// translateClientPath 将下载客户端报告的路径转换为容器内可访问的路径。 // translateClientPath 将下载客户端报告的路径转换为容器内可访问的路径。
// 常见场景:qBittorrent在另一个容器,报告的路径是其容器内路径,需要映射到当前容器。 // 常见场景:下载器在另一个容器,报告的路径是其容器内路径,需要映射到当前容器。
func translateClientPath(clientPath string, mappings map[string]string) string { func translateClientPath(clientPath string, mappings map[string]string) string {
if clientPath == "" { if clientPath == "" {
return "" return ""
+1 -1
View File
@@ -27,7 +27,7 @@ func NewExternalHTTPClient(timeout time.Duration) *http.Client {
} }
// NewInternalHTTPClient builds an HTTP client for LAN / Docker-internal // NewInternalHTTPClient builds an HTTP client for LAN / Docker-internal
// services such as qBittorrent, Transmission and Aria2. These endpoints are // services such as downloaders and other local tools. These endpoints are
// usually 127.0.0.1, host.docker.internal, 172.17.0.1 or a NAS LAN IP; sending // usually 127.0.0.1, host.docker.internal, 172.17.0.1 or a NAS LAN IP; sending
// them through HTTP_PROXY/SOCKS proxies makes local WebUI logins hang or fail. // them through HTTP_PROXY/SOCKS proxies makes local WebUI logins hang or fail.
func NewInternalHTTPClient(timeout time.Duration) *http.Client { func NewInternalHTTPClient(timeout time.Duration) *http.Client {
+1 -1
View File
@@ -7,7 +7,7 @@
// hardlink 硬链接(同盘零额外占用,保留源文件;做种不受影响) // hardlink 硬链接(同盘零额外占用,保留源文件;做种不受影响)
// symlink 软链接(保留源文件,指向源) // symlink 软链接(保留源文件,指向源)
// //
// 除 move 外,其余方式都保留源文件,因此 qBittorrent 等下载器仍能在原 // 除 move 外,其余方式都保留源文件,因此下载器仍能在原
// 路径找到数据继续做种上传。 // 路径找到数据继续做种上传。
package service package service
-60
View File
@@ -1,60 +0,0 @@
export interface DownloadTask {
id: string
source: string
download_client_id?: string
external_id?: string
title: string
poster_url?: string
backdrop_url?: string
overview?: string
save_path: string
status: string
progress: number
state?: string
dlspeed?: number
upspeed?: number
num_seeds?: number
num_leechs?: number
size?: number
downloaded?: number
created_at: string
updated_at: string
}
export interface QBitTorrent {
hash: string
client_id: string
source: 'qbittorrent' | 'transmission' | 'aria2'
name: string
title: string
poster_url?: string
backdrop_url?: string
overview?: string
state: string
progress: number
dlspeed: number
upspeed: number
num_seeds: number
num_leechs: number
size: number
downloaded: number
save_path: string
}
export interface DownloadClient {
id: string
name: string
type: 'qbittorrent' | 'transmission' | 'aria2'
host: string
username: string
is_default: boolean
enabled: boolean
created_at: string
updated_at: string
}
export interface DownloadClientTypeInfo {
type: string
name: string
description: string
}
-1
View File
@@ -2,7 +2,6 @@
export * from './api' export * from './api'
export * from './auth' export * from './auth'
export * from './downloads'
export * from './events' export * from './events'
export * from './history' export * from './history'
export * from './library' export * from './library'