mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-08 14:26:37 +08:00
refactor: split modules and harden scraping workflows
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MediaStationGo/internal/model"
|
||||
)
|
||||
|
||||
// completedTorrentCatchupWindow 限定重启补整理只覆盖最近完成的种子,
|
||||
// 防止每次启动都把全部历史种子重新过一遍整理流程。
|
||||
const completedTorrentCatchupWindow = 24 * time.Hour
|
||||
|
||||
const completedTorrentCatchupSettingPrefix = "download.auto_organized."
|
||||
const completedTorrentNotifySettingPrefix = "download.completed_notified."
|
||||
|
||||
func (d *DownloadService) downloadAutoOrganizeEnabled(ctx context.Context) bool {
|
||||
if d == nil || d.repo == nil || d.repo.Setting == nil {
|
||||
return false
|
||||
}
|
||||
if v, err := d.repo.Setting.Get(ctx, "organizer.auto_after_download"); err == nil && parseBoolSetting(v, false) {
|
||||
return true
|
||||
}
|
||||
if v, err := d.repo.Setting.Get(ctx, "organize.auto"); err == nil && parseBoolSetting(v, false) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// recentlyCompletedTorrent 报告该种子是否在补整理时间窗内完成。
|
||||
// qBittorrent 未提供 completion_on 时保守地返回 false。
|
||||
func recentlyCompletedTorrent(torrent QBitTorrent, now time.Time) bool {
|
||||
if torrent.CompletionOn <= 0 {
|
||||
return false
|
||||
}
|
||||
completed := time.Unix(torrent.CompletionOn, 0)
|
||||
return now.Sub(completed) <= completedTorrentCatchupWindow
|
||||
}
|
||||
|
||||
func (d *DownloadService) completedTorrentCatchupRecorded(ctx context.Context, torrent QBitTorrent) bool {
|
||||
if d == nil || d.repo == nil || d.repo.Setting == nil {
|
||||
return false
|
||||
}
|
||||
key := completedTorrentCatchupSettingKey(torrent)
|
||||
if key == "" {
|
||||
return false
|
||||
}
|
||||
value, err := d.repo.Setting.Get(ctx, key)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return parseBoolSetting(value, false)
|
||||
}
|
||||
|
||||
func (d *DownloadService) markCompletedTorrentCatchupRecorded(ctx context.Context, torrent QBitTorrent) {
|
||||
if d == nil || d.repo == nil || d.repo.Setting == nil {
|
||||
return
|
||||
}
|
||||
key := completedTorrentCatchupSettingKey(torrent)
|
||||
if key == "" {
|
||||
return
|
||||
}
|
||||
if err := d.repo.Setting.Set(ctx, key, "true"); err != nil && d.log != nil {
|
||||
d.log.Debug("mark completed torrent catchup failed",
|
||||
zap.String("hash", torrent.Hash),
|
||||
zap.String("name", torrent.Name),
|
||||
zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func completedTorrentCatchupSettingKey(torrent QBitTorrent) string {
|
||||
key := completedTorrentQueueKey(torrent)
|
||||
if key == "" {
|
||||
return ""
|
||||
}
|
||||
sum := sha1.Sum([]byte(key))
|
||||
return completedTorrentCatchupSettingPrefix + fmt.Sprintf("%x", sum[:])
|
||||
}
|
||||
|
||||
func (d *DownloadService) completedTorrentNotified(ctx context.Context, torrent QBitTorrent) bool {
|
||||
if d == nil || d.repo == nil || d.repo.Setting == nil {
|
||||
return false
|
||||
}
|
||||
key := completedTorrentNotifySettingKey(torrent)
|
||||
if key == "" {
|
||||
return false
|
||||
}
|
||||
value, err := d.repo.Setting.Get(ctx, key)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return parseBoolSetting(value, false)
|
||||
}
|
||||
|
||||
func (d *DownloadService) markCompletedTorrentNotified(ctx context.Context, torrent QBitTorrent) {
|
||||
if d == nil || d.repo == nil || d.repo.Setting == nil {
|
||||
return
|
||||
}
|
||||
key := completedTorrentNotifySettingKey(torrent)
|
||||
if key == "" {
|
||||
return
|
||||
}
|
||||
if err := d.repo.Setting.Set(ctx, key, "true"); err != nil && d.log != nil {
|
||||
d.log.Debug("mark completed torrent notification failed",
|
||||
zap.String("hash", torrent.Hash),
|
||||
zap.String("name", torrent.Name),
|
||||
zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func completedTorrentNotifySettingKey(torrent QBitTorrent) string {
|
||||
key := completedTorrentQueueKey(torrent)
|
||||
if key == "" {
|
||||
return ""
|
||||
}
|
||||
sum := sha1.Sum([]byte(key))
|
||||
return completedTorrentNotifySettingPrefix + fmt.Sprintf("%x", sum[:])
|
||||
}
|
||||
|
||||
func completedTorrentQueueKey(torrent QBitTorrent) string {
|
||||
hash := strings.ToLower(strings.TrimSpace(torrent.Hash))
|
||||
if hash != "" {
|
||||
return hash
|
||||
}
|
||||
parts := []string{torrent.Name, torrent.ContentPath, torrent.SavePath}
|
||||
for i := range parts {
|
||||
parts[i] = strings.TrimSpace(parts[i])
|
||||
}
|
||||
key := strings.Join(parts, "|")
|
||||
if strings.Trim(key, "|") == "" {
|
||||
return ""
|
||||
}
|
||||
return strings.ToLower(key)
|
||||
}
|
||||
|
||||
func (d *DownloadService) syncDownloadTaskProgress(ctx context.Context, torrent QBitTorrent, taskByKey map[string]model.DownloadTask) {
|
||||
if d == nil || d.repo == nil || d.repo.DB == nil || strings.TrimSpace(torrent.Name) == "" {
|
||||
return
|
||||
}
|
||||
matched, ok := findMatchingTaskByTorrentIdentity(torrent.Name, taskByKey)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
status := torrent.State
|
||||
if torrent.Progress >= 1 {
|
||||
status = "completed"
|
||||
}
|
||||
if strings.TrimSpace(status) == "" {
|
||||
status = matched.Status
|
||||
}
|
||||
updates := map[string]any{}
|
||||
if math.Abs(float64(matched.Progress-torrent.Progress)) > 0.0001 {
|
||||
updates["progress"] = torrent.Progress
|
||||
}
|
||||
if status != "" && status != matched.Status {
|
||||
updates["status"] = status
|
||||
}
|
||||
if len(updates) == 0 {
|
||||
return
|
||||
}
|
||||
_ = d.repo.DB.WithContext(ctx).Model(&model.DownloadTask{}).Where("id = ?", matched.ID).Updates(updates).Error
|
||||
}
|
||||
|
||||
func tasksByIdentity(rows []model.DownloadTask) map[string]model.DownloadTask {
|
||||
out := make(map[string]model.DownloadTask, len(rows))
|
||||
for _, row := range rows {
|
||||
key := downloadTaskIdentityKey(row.Title)
|
||||
if key != "" {
|
||||
out[key] = row
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func tasksByTorrentIdentity(rows []model.DownloadTask) map[string]model.DownloadTask {
|
||||
out := make(map[string]model.DownloadTask, len(rows))
|
||||
for _, row := range rows {
|
||||
key := normalizeTorrentName(row.Title)
|
||||
if key != "" {
|
||||
out[key] = row
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func findMatchingTaskByIdentity(title string, taskByKey map[string]model.DownloadTask) (model.DownloadTask, bool) {
|
||||
key := downloadTaskIdentityKey(title)
|
||||
if key == "" {
|
||||
return model.DownloadTask{}, false
|
||||
}
|
||||
if row, ok := taskByKey[key]; ok {
|
||||
return row, true
|
||||
}
|
||||
for currentKey, row := range taskByKey {
|
||||
if strings.Contains(key, currentKey) || strings.Contains(currentKey, key) {
|
||||
return row, true
|
||||
}
|
||||
}
|
||||
return model.DownloadTask{}, false
|
||||
}
|
||||
|
||||
func findMatchingTaskByTorrentIdentity(title string, taskByKey map[string]model.DownloadTask) (model.DownloadTask, bool) {
|
||||
key := normalizeTorrentName(title)
|
||||
if key == "" {
|
||||
return model.DownloadTask{}, false
|
||||
}
|
||||
if row, ok := taskByKey[key]; ok {
|
||||
return row, true
|
||||
}
|
||||
for currentKey, row := range taskByKey {
|
||||
if strings.Contains(key, currentKey) || strings.Contains(currentKey, key) {
|
||||
return row, true
|
||||
}
|
||||
}
|
||||
return model.DownloadTask{}, false
|
||||
}
|
||||
|
||||
func downloadTaskNeedsCompletion(task model.DownloadTask) bool {
|
||||
if task.Progress < 1 {
|
||||
return true
|
||||
}
|
||||
return strings.ToLower(strings.TrimSpace(task.Status)) != "completed"
|
||||
}
|
||||
Reference in New Issue
Block a user