Files
MeBox/internal/service/download_manager_svc.go
T

254 lines
6.5 KiB
Go

// Package service — 下载管理器,管理多个下载客户端适配器。
//
// DownloadManager 提供多客户端分发能力,支持运行时热插拔。
// 调用方通过 GetDefault() 或 GetClient(id) 获取适配器来执行下载操作。
package service
import (
"context"
"encoding/json"
"errors"
"sync"
"go.uber.org/zap"
"github.com/ShukeBta/MediaStationGo/internal/model"
"github.com/ShukeBta/MediaStationGo/internal/repository"
)
// DownloadManager 管理多个下载客户端适配器实例。
type DownloadManager struct {
log *zap.Logger
repo *repository.Container
crypto *CryptoService
mu sync.RWMutex
clients map[string]DownloadAdapter // clientID -> adapter
configs map[string]DownloadClientConfig
}
// NewDownloadManager 创建新的下载管理器。
func NewDownloadManager(log *zap.Logger, repo *repository.Container, crypto *CryptoService) *DownloadManager {
return &DownloadManager{
log: log,
repo: repo,
crypto: crypto,
clients: make(map[string]DownloadAdapter),
configs: make(map[string]DownloadClientConfig),
}
}
// LoadAll 从数据库加载所有已启用的客户端并初始化适配器。
func (m *DownloadManager) LoadAll(ctx context.Context) error {
dbClients, err := m.repo.DownloadClient.ListEnabled(ctx)
if err != nil {
return err
}
m.mu.Lock()
defer m.mu.Unlock()
// 清空现有
m.clients = make(map[string]DownloadAdapter, len(dbClients))
m.configs = make(map[string]DownloadClientConfig, len(dbClients))
for _, dc := range dbClients {
cfg, err := m.buildConfig(&dc)
if err != nil {
m.log.Warn("failed to build config for download client",
zap.String("id", dc.ID),
zap.String("name", dc.Name),
zap.Error(err),
)
continue
}
adapter := AdapterFactory(dc.Type)
if adapter == nil {
m.log.Warn("unknown download client type",
zap.String("type", dc.Type),
zap.String("id", dc.ID),
)
continue
}
if initErr := adapter.Initialize(ctx, cfg); initErr != nil {
m.log.Warn("failed to initialize download client",
zap.String("id", dc.ID),
zap.String("name", dc.Name),
zap.Error(initErr),
)
continue
}
m.clients[dc.ID] = adapter
m.configs[dc.ID] = cfg
m.log.Info("download client initialized",
zap.String("id", dc.ID),
zap.String("name", dc.Name),
zap.String("type", dc.Type),
)
}
return nil
}
// GetDefault 返回默认下载客户端适配器。
// 如果没有设置默认客户端,返回第一个可用的客户端。
func (m *DownloadManager) GetDefault() (string, DownloadAdapter, error) {
m.mu.RLock()
defer m.mu.RUnlock()
// 首先找默认的
defaultClient, err := m.repo.DownloadClient.FindDefault(context.Background())
if err != nil {
return "", nil, err
}
if defaultClient != nil {
if adapter, ok := m.clients[defaultClient.ID]; ok {
return defaultClient.ID, adapter, nil
}
}
// 返回第一个可用的
for id, adapter := range m.clients {
return id, adapter, nil
}
return "", nil, errors.New("no download client available")
}
// GetClient 返回指定 ID 的下载客户端适配器。
func (m *DownloadManager) GetClient(id string) (DownloadAdapter, error) {
m.mu.RLock()
defer m.mu.RUnlock()
adapter, ok := m.clients[id]
if !ok {
return nil, errors.New("download client not found or not initialized")
}
return adapter, nil
}
// AddClient 动态添加并初始化一个下载客户端。
func (m *DownloadManager) AddClient(ctx context.Context, dc *model.DownloadClient) error {
cfg, err := m.buildConfig(dc)
if err != nil {
return err
}
adapter := AdapterFactory(dc.Type)
if adapter == nil {
return errors.New("unknown download client type: " + dc.Type)
}
if err := adapter.Initialize(ctx, cfg); err != nil {
return err
}
m.mu.Lock()
defer m.mu.Unlock()
m.clients[dc.ID] = adapter
m.configs[dc.ID] = cfg
return nil
}
// RemoveClient 移除一个下载客户端(停止适配器,不删除数据库记录)。
func (m *DownloadManager) RemoveClient(id string) {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.clients, id)
delete(m.configs, id)
}
// UpdateClient 更新已有客户端的配置并重新初始化。
func (m *DownloadManager) UpdateClient(ctx context.Context, dc *model.DownloadClient) error {
m.RemoveClient(dc.ID)
return m.AddClient(ctx, dc)
}
// TestConnection 测试客户端连接。
func (m *DownloadManager) TestConnection(ctx context.Context, dc *model.DownloadClient) error {
cfg, err := m.buildConfig(dc)
if err != nil {
return err
}
adapter := AdapterFactory(dc.Type)
if adapter == nil {
return errors.New("unknown download client type: " + dc.Type)
}
return adapter.Initialize(ctx, cfg)
}
// ListAll 获取所有已加载客户端的种子列表。
func (m *DownloadManager) ListAll(ctx context.Context, filter string) (map[string][]TorrentInfo, error) {
m.mu.RLock()
ids := make([]string, 0, len(m.clients))
for id := range m.clients {
ids = append(ids, id)
}
adapters := make([]DownloadAdapter, 0, len(m.clients))
for _, id := range ids {
adapters = append(adapters, m.clients[id])
}
m.mu.RUnlock()
result := make(map[string][]TorrentInfo)
for i, id := range ids {
list, err := adapters[i].List(ctx, filter)
if err != nil {
m.log.Warn("failed to list torrents from client",
zap.String("id", id),
zap.Error(err),
)
continue
}
result[id] = list
}
return result, nil
}
// GetAdapterTypes 返回支持的下载客户端类型列表。
func (m *DownloadManager) GetAdapterTypes() []AdapterTypeInfo {
return []AdapterTypeInfo{
{Type: "qbittorrent", Name: "qBittorrent", Description: "qBittorrent WebUI API (v2)"},
{Type: "transmission", Name: "Transmission", Description: "Transmission RPC API"},
{Type: "aria2", Name: "Aria2", Description: "Aria2 JSON-RPC API"},
}
}
// AdapterTypeInfo 描述下载客户端类型信息。
type AdapterTypeInfo struct {
Type string `json:"type"`
Name string `json:"name"`
Description string `json:"description"`
}
// buildConfig 从数据库模型构建适配器配置。
func (m *DownloadManager) buildConfig(dc *model.DownloadClient) (DownloadClientConfig, error) {
password := dc.Password
if m.crypto != nil && password != "" {
password = m.crypto.Decrypt(password)
}
cfg := DownloadClientConfig{
Host: dc.Host,
Username: dc.Username,
Password: password,
}
// 解析 Extra JSON 配置
if dc.Extra != "" {
extraStr := dc.Extra
if m.crypto != nil {
extraStr = m.crypto.Decrypt(extraStr)
}
var extra map[string]string
if err := json.Unmarshal([]byte(extraStr), &extra); err == nil {
cfg.Extra = extra
}
}
return cfg, nil
}