tstor/plugins/qbittorrent/daemon/cleanup.go
royalcat 3c0ba3cc9f
Some checks failed
docker / build-docker (push) Failing after 56s
up
2025-06-08 06:18:23 +04:00

153 lines
4.5 KiB
Go

package daemon
import (
"context"
"errors"
"fmt"
"log/slog"
"os"
"path"
"slices"
"strings"
"git.kmsign.ru/royalcat/tstor/plugins/qbittorrent/pkg/qbittorrent"
"git.kmsign.ru/royalcat/tstor/server/pkg/rlog"
)
func (d *QBittorrentDaemon) Cleanup(ctx context.Context, run bool) ([]string, error) {
d.log.Info(ctx, "cleanup started")
torrentInfos, err := d.client.qb.Torrent().GetTorrents(ctx, &qbittorrent.TorrentOption{})
if err != nil {
d.log.Error(ctx, "failed to get torrents", rlog.Error(err))
return nil, fmt.Errorf("failed to get torrents: %w", err)
}
daemonsHashes := []string{}
for _, info := range torrentInfos {
daemonsHashes = append(daemonsHashes, info.Hash)
}
dataDirs, err := os.ReadDir(d.dataDir)
if err != nil {
d.log.Error(ctx, "failed to read data directory", slog.String("path", d.dataDir), rlog.Error(err))
return nil, fmt.Errorf("failed to read data directory: %w", err)
}
dataHashes := []string{}
for _, entry := range dataDirs {
dataHashes = append(dataHashes, entry.Name())
}
hashToDelete := make([]string, 0, 5)
for _, v := range dataHashes {
if !slices.Contains(daemonsHashes, v) {
hashToDelete = append(hashToDelete, v)
}
}
d.log.Info(ctx, "marked torrents to delete",
slog.Int("count", len(hashToDelete)),
slog.Any("infohashes", hashToDelete),
)
if !run {
d.log.Info(ctx, "dry run, skipping deletion")
return hashToDelete, nil
}
for _, hash := range hashToDelete {
d.log.Info(ctx, "deleting stale torrent data", slog.String("infohash", hash))
err := os.RemoveAll(path.Join(d.dataDir, hash))
if err != nil {
d.log.Error(ctx, "failed to delete torrent data", slog.String("infohash", hash), rlog.Error(err))
return nil, fmt.Errorf("failed to delete torrent data: %w", err)
}
}
return hashToDelete, nil
}
func (d *QBittorrentDaemon) CleanupUnregistred(ctx context.Context, run bool) ([]string, error) {
d.log.Info(ctx, "cleanup started")
err := d.sourceFilesKV.Range(ctx, func(sourcePath, hash string) error {
log := d.log.With(slog.String("sourcePath", sourcePath))
if !strings.HasSuffix(sourcePath, ".torrent") {
log.Warn(ctx, "skipping non-torrent file", slog.String("path", sourcePath))
return nil
}
if d.registeredTorrents.Contains(hash) {
log.Debug(ctx, "torrent already registered, skipping", slog.String("infohash", hash))
return nil
}
f, err := d.sourceFS.Open(ctx, sourcePath)
if err != nil {
log.Error(ctx, "failed to open source file", slog.String("path", sourcePath), rlog.Error(err))
return fmt.Errorf("failed to open source file: %w", err)
}
_, err = d.GetFS(ctx, sourcePath, f)
if err != nil {
log.Error(ctx, "failed to get filesystem for source file", slog.String("path", sourcePath), rlog.Error(err))
return fmt.Errorf("failed to get filesystem for source file: %w", err)
}
return nil
})
if err != nil {
d.log.Error(ctx, "failed to iterate source files", rlog.Error(err))
return nil, fmt.Errorf("failed to iterate source files: %w", err)
}
torrentInfos, err := d.client.qb.Torrent().GetTorrents(ctx, &qbittorrent.TorrentOption{})
if err != nil {
d.log.Error(ctx, "failed to get torrents", rlog.Error(err))
return nil, fmt.Errorf("failed to get torrents: %w", err)
}
torrentToDelete := make([]string, 0, 5)
for _, info := range torrentInfos {
if d.registeredTorrents.Contains(info.Hash) {
continue
}
d.log.Info(ctx, "torrent not found in registry", slog.String("infohash", info.Hash))
torrentToDelete = append(torrentToDelete, info.Hash)
}
d.log.Info(ctx, "marked torrents to delete",
slog.Int("count", len(torrentToDelete)),
slog.Any("infohashes", torrentToDelete),
)
if !run {
d.log.Info(ctx, "dry run, skipping deletion")
return torrentToDelete, nil
}
err = d.client.qb.Torrent().DeleteTorrents(ctx, torrentToDelete, true)
if err != nil {
d.log.Error(ctx, "failed to delete torrents", slog.Any("infohashes", torrentToDelete), rlog.Error(err))
return nil, fmt.Errorf("failed to delete torrents: %w", err)
}
d.log.Info(ctx, "torrents deleted from qbittorrent", slog.Int("count", len(torrentToDelete)))
for _, hash := range torrentToDelete {
torrentPath := path.Join(d.dataDir, hash)
_, err := os.Stat(torrentPath)
if errors.Is(err, os.ErrNotExist) {
continue
}
if err != nil {
d.log.Error(ctx, "failed to get torrent path", slog.String("path", torrentPath), rlog.Error(err))
continue
}
d.log.Warn(ctx, "leftover data for torrent detected, cleaning up", slog.String("infohash", hash), slog.String("path", torrentPath))
}
return torrentToDelete, nil
}