mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-07 22:06:38 +08:00
refactor site service by responsibility
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MediaStationGo/internal/model"
|
||||
)
|
||||
|
||||
// SearchResult is one torrent returned by a site adapter search.
|
||||
type SearchResult struct {
|
||||
SiteName string `json:"site_name"`
|
||||
SiteID string `json:"site_id"`
|
||||
Title string `json:"title"`
|
||||
Subtitle string `json:"subtitle,omitempty"`
|
||||
TorrentURL string `json:"torrent_url"`
|
||||
DownloadURL string `json:"download_url"`
|
||||
Category string `json:"category,omitempty"`
|
||||
SearchKeyword string `json:"search_keyword,omitempty"`
|
||||
Size int64 `json:"size"`
|
||||
Seeders int `json:"seeders"`
|
||||
Leechers int `json:"leechers"`
|
||||
Free bool `json:"free"`
|
||||
}
|
||||
|
||||
// Search fans out a keyword query to every enabled site and returns
|
||||
// merged results sorted by seeders descending.
|
||||
// Uses concurrent search with sync.WaitGroup for performance.
|
||||
func (s *SiteService) Search(ctx context.Context, keyword string) ([]SearchResult, error) {
|
||||
if strings.TrimSpace(keyword) == "" {
|
||||
return []SearchResult{}, nil
|
||||
}
|
||||
sites, err := s.List(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
wg sync.WaitGroup
|
||||
enabledCount int
|
||||
failedCount int
|
||||
failureErrs []error
|
||||
failures []string
|
||||
results []SearchResult
|
||||
)
|
||||
|
||||
for i := range sites {
|
||||
if !sites[i].Enabled {
|
||||
continue
|
||||
}
|
||||
enabledCount++
|
||||
wg.Add(1)
|
||||
go func(site model.Site) {
|
||||
defer wg.Done()
|
||||
|
||||
adapter := NewSiteAdapter(&site)
|
||||
if adapter == nil {
|
||||
mu.Lock()
|
||||
failedCount++
|
||||
err := fmt.Errorf("%s: unsupported site type %s", site.Name, site.Type)
|
||||
failureErrs = append(failureErrs, err)
|
||||
failures = append(failures, err.Error())
|
||||
mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
cfg := s.siteModelToConfig(&site)
|
||||
|
||||
// Use site timeout or default 30s
|
||||
timeout := time.Duration(site.Timeout) * time.Second
|
||||
if timeout <= 0 {
|
||||
timeout = 30 * time.Second
|
||||
}
|
||||
ctxWithTimeout, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
|
||||
result, err := adapter.Search(ctxWithTimeout, cfg, keyword, 1)
|
||||
if err != nil {
|
||||
mu.Lock()
|
||||
failedCount++
|
||||
failureErr := fmt.Errorf("%s: %w", site.Name, err)
|
||||
failureErrs = append(failureErrs, failureErr)
|
||||
failures = append(failures, failureErr.Error())
|
||||
mu.Unlock()
|
||||
s.log.Warn("site search failed",
|
||||
zap.String("site", site.Name),
|
||||
zap.String("type", site.Type),
|
||||
zap.String("url", site.URL),
|
||||
zap.String("keyword", keyword),
|
||||
zap.Duration("timeout", timeout),
|
||||
zap.Error(err))
|
||||
return
|
||||
}
|
||||
if result == nil {
|
||||
return
|
||||
}
|
||||
items := result.Items
|
||||
if items == nil {
|
||||
items = []TorrentItem{}
|
||||
}
|
||||
for _, item := range items {
|
||||
mu.Lock()
|
||||
results = append(results, SearchResult{
|
||||
SiteName: site.Name,
|
||||
SiteID: site.ID,
|
||||
Title: item.Title,
|
||||
Subtitle: item.Subtitle,
|
||||
TorrentURL: item.DetailURL,
|
||||
DownloadURL: item.DownloadURL,
|
||||
Category: item.Category,
|
||||
SearchKeyword: keyword,
|
||||
Size: item.Size,
|
||||
Seeders: item.Seeders,
|
||||
Leechers: item.Leechers,
|
||||
Free: item.Free,
|
||||
})
|
||||
mu.Unlock()
|
||||
}
|
||||
}(sites[i])
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// Ensure results is never nil (return [] instead of null in JSON)
|
||||
if results == nil {
|
||||
results = []SearchResult{}
|
||||
}
|
||||
|
||||
// Sort by seeders desc.
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Seeders > results[j].Seeders
|
||||
})
|
||||
if s.log != nil {
|
||||
s.log.Info("site search completed",
|
||||
zap.String("keyword", keyword),
|
||||
zap.Int("enabled_sites", enabledCount),
|
||||
zap.Int("failed_sites", failedCount),
|
||||
zap.Int("results_count", len(results)))
|
||||
}
|
||||
if enabledCount > 0 && failedCount >= enabledCount && len(results) == 0 {
|
||||
if len(failureErrs) > 0 {
|
||||
return results, fmt.Errorf("all enabled sites failed while searching %q: %w", keyword, errors.Join(failureErrs...))
|
||||
}
|
||||
return results, fmt.Errorf("all enabled sites failed while searching %q: %s", keyword, strings.Join(failures, "; "))
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
Reference in New Issue
Block a user