mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-28 11:16:37 +08:00
feat: 实现下载完成后自动触发智能分类整理
- 添加 AutoAfterDownload 配置字段到 OrganizerConfig - 设置默认值为 false - 修改 DownloadService 结构,添加 organizer 和 prevStates 字段 - 修改 NewDownloadService 构造函数,接受 organizer 参数 - 修改 service.go 初始化顺序,将 organizer 创建移到 downloads 之前 - 修改 poll() 方法,检测下载完成并触发整理 - 实现 onTorrentComplete() 方法,根据 savePath 查找 Media 并整理
This commit is contained in:
@@ -133,8 +133,9 @@ type AIConfig struct {
|
||||
|
||||
// OrganizerConfig 配置媒体文件智能分类整理。
|
||||
type OrganizerConfig struct {
|
||||
SmartClassify bool `mapstructure:"smart_classify"`
|
||||
Categories map[string]string `mapstructure:"categories"`
|
||||
SmartClassify bool `mapstructure:"smart_classify"`
|
||||
AutoAfterDownload bool `mapstructure:"auto_after_download"`
|
||||
Categories map[string]string `mapstructure:"categories"`
|
||||
}
|
||||
|
||||
// FlareSolverrConfig 配置 FlareSolverr 服务(用于绕过 Cloudflare/WAF)。
|
||||
@@ -234,6 +235,7 @@ func setDefaults(v *viper.Viper) {
|
||||
v.SetDefault("flaresolverr.timeout", 60)
|
||||
|
||||
v.SetDefault("organizer.smart_classify", false)
|
||||
v.SetDefault("organizer.auto_after_download", false)
|
||||
v.SetDefault("organizer.categories.chinese_movie", "华语电影")
|
||||
v.SetDefault("organizer.categories.foreign_movie", "外语电影")
|
||||
v.SetDefault("organizer.categories.euus_movie", "欧美电影")
|
||||
|
||||
@@ -29,24 +29,28 @@ import (
|
||||
|
||||
// DownloadService is the single download orchestrator.
|
||||
type DownloadService struct {
|
||||
log *zap.Logger
|
||||
repo *repository.Container
|
||||
hub *Hub
|
||||
qb *QBitClient
|
||||
log *zap.Logger
|
||||
repo *repository.Container
|
||||
hub *Hub
|
||||
qb *QBitClient
|
||||
organizer *OrganizerService
|
||||
|
||||
mu sync.Mutex
|
||||
stopCh chan struct{}
|
||||
pollOnce sync.Once
|
||||
mu sync.Mutex
|
||||
stopCh chan struct{}
|
||||
pollOnce sync.Once
|
||||
prevStates map[string]bool // hash -> wasCompleted
|
||||
}
|
||||
|
||||
// NewDownloadService is the constructor.
|
||||
func NewDownloadService(log *zap.Logger, repo *repository.Container, hub *Hub) *DownloadService {
|
||||
func NewDownloadService(log *zap.Logger, repo *repository.Container, hub *Hub, organizer *OrganizerService) *DownloadService {
|
||||
return &DownloadService{
|
||||
log: log,
|
||||
repo: repo,
|
||||
hub: hub,
|
||||
qb: NewQBitClient(log, QBitConfig{}),
|
||||
stopCh: make(chan struct{}),
|
||||
log: log,
|
||||
repo: repo,
|
||||
hub: hub,
|
||||
qb: NewQBitClient(log, QBitConfig{}),
|
||||
organizer: organizer,
|
||||
prevStates: make(map[string]bool),
|
||||
stopCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +135,10 @@ func (d *DownloadService) Delete(ctx context.Context, hash string, withFiles boo
|
||||
func (d *DownloadService) poll(ctx context.Context) {
|
||||
t := time.NewTicker(5 * time.Second)
|
||||
defer t.Stop()
|
||||
// prevStates tracks previous completion states to detect "just finished"
|
||||
if d.prevStates == nil {
|
||||
d.prevStates = make(map[string]bool)
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
@@ -143,6 +151,46 @@ func (d *DownloadService) poll(ctx context.Context) {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// Detect completed downloads and trigger organize
|
||||
for _, t := range live {
|
||||
hash := t.Hash
|
||||
complete := t.Progress >= 1.0
|
||||
if complete && !d.prevStates[hash] {
|
||||
// Just completed: trigger organize
|
||||
go d.onTorrentComplete(ctx, hash, t.SavePath)
|
||||
}
|
||||
d.prevStates[hash] = complete
|
||||
}
|
||||
d.hub.Publish("download", map[string]any{"torrents": live})
|
||||
}
|
||||
}
|
||||
|
||||
// onTorrentComplete handles a torrent that just finished downloading.
|
||||
// It tries to find the associated Media record and trigger organize.
|
||||
func (d *DownloadService) onTorrentComplete(ctx context.Context, hash string, savePath string) {
|
||||
if d.organizer == nil || savePath == "" {
|
||||
return
|
||||
}
|
||||
// Check if auto-organize after download is enabled
|
||||
autoOrganize := d.organizer.isSmartClassifyEnabled(ctx)
|
||||
// Also check dedicated config key
|
||||
if v, err := d.repo.Setting.Get(ctx, "organizer.auto_after_download"); err == nil {
|
||||
autoOrganize = autoOrganize || v == "true" || v == "1" || v == "on"
|
||||
}
|
||||
if !autoOrganize {
|
||||
d.log.Info("download completed, auto-organize disabled", zap.String("hash", hash))
|
||||
return
|
||||
}
|
||||
d.log.Info("download completed, triggering organize", zap.String("hash", hash), zap.String("save_path", savePath))
|
||||
// Find Media record by path prefix
|
||||
var medias []model.Media
|
||||
if err := d.repo.DB.WithContext(ctx).Where("path LIKE ?", savePath+"%").Find(&medias).Error; err != nil {
|
||||
d.log.Error("find media by path", zap.Error(err))
|
||||
return
|
||||
}
|
||||
for i := range medias {
|
||||
if _, err := d.organizer.OrganizeMedia(ctx, medias[i].ID); err != nil {
|
||||
d.log.Error("organize media", zap.String("media_id", medias[i].ID), zap.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,10 +88,11 @@ func New(cfg *config.Config, log *zap.Logger, repos *repository.Container) *Cont
|
||||
thetvdb := NewTheTVDBProvider(cfg, log)
|
||||
fanart := NewFanartProvider(cfg, log)
|
||||
scraper := NewScraperService(cfg, log, repos, tmdb, bangumi, thetvdb, fanart, hub)
|
||||
organizer := NewOrganizerService(cfg, log, repos)
|
||||
downloads := NewDownloadService(log, repos, hub, organizer)
|
||||
discover := NewDiscoverService(log, tmdb)
|
||||
transcoder := NewTranscoderService(cfg, log, repos, hub)
|
||||
scanner := NewScannerService(cfg, log, repos, hub, probe, scraper)
|
||||
downloads := NewDownloadService(log, repos, hub)
|
||||
subscription := NewSubscriptionService(log, repos, downloads, hub)
|
||||
watcher := NewWatcherService(log, repos, scanner)
|
||||
nfo := NewNFOService(log, repos)
|
||||
@@ -109,7 +110,6 @@ func New(cfg *config.Config, log *zap.Logger, repos *repository.Container) *Cont
|
||||
storageCfg := NewStorageConfigService(log, repos, crypto)
|
||||
downloadClients := NewDownloadClientService(log, repos)
|
||||
assistant := NewAssistantService(log, repos, ai)
|
||||
organizer := NewOrganizerService(cfg, log, repos)
|
||||
douban := NewDoubanProvider(cfg, log)
|
||||
scheduler := NewSchedulerService(log, repos, scanner, transcoder, hub, cfg.Cache.CacheDir)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user