diff --git a/internal/arrs/scanner/manager.go b/internal/arrs/scanner/manager.go index 55c676d1..7fd12f3a 100644 --- a/internal/arrs/scanner/manager.go +++ b/internal/arrs/scanner/manager.go @@ -445,8 +445,6 @@ func (m *Manager) triggerSonarrRescanByPath(ctx context.Context, client *sonarr. libraryDir := "" if cfg.Health.LibraryDir != nil && *cfg.Health.LibraryDir != "" { libraryDir = *cfg.Health.LibraryDir - } else { - return fmt.Errorf("Health.LibraryDir is not configured") } slog.DebugContext(ctx, "Triggering Sonarr rescan/re-download by path", diff --git a/internal/errors/errors.go b/internal/errors/errors.go index e2c9d022..8c911783 100644 --- a/internal/errors/errors.go +++ b/internal/errors/errors.go @@ -64,18 +64,6 @@ func IsNonRetryable(err error) bool { // Sentinel errors for common non-retryable conditions. var ( - // ErrNoRetryable is kept for backward compatibility with existing code. - ErrNoRetryable = &NonRetryableError{ - message: "no retryable errors found", - cause: nil, - } - - // ErrNoVideoFiles indicates that an import contains no video files. - ErrNoVideoFiles = &NonRetryableError{ - message: "import contains no video files", - cause: nil, - } - // ErrFallbackNotConfigured indicates that SABnzbd fallback is not enabled or configured. ErrFallbackNotConfigured = &NonRetryableError{ message: "SABnzbd fallback not configured", diff --git a/internal/importer/archive/common.go b/internal/importer/archive/common.go index f8343109..80b4ca18 100644 --- a/internal/importer/archive/common.go +++ b/internal/importer/archive/common.go @@ -1,8 +1,6 @@ package archive -import "strconv" - -// parseInt safely converts string to int +// ParseInt safely converts string to int func ParseInt(s string) int { num := 0 for _, r := range s { @@ -14,8 +12,3 @@ func ParseInt(s string) int { } return num } - -// FormatInt converts an integer to a string -func FormatInt(n int) string { - return strconv.Itoa(n) -} diff --git a/internal/importer/errors.go b/internal/importer/errors.go index 3e3cecbc..e6074729 100644 --- a/internal/importer/errors.go +++ b/internal/importer/errors.go @@ -19,12 +19,6 @@ var ( // IsNonRetryable checks if an error is non-retryable. IsNonRetryable = sharedErrors.IsNonRetryable - // ErrNoRetryable is kept for backward compatibility with existing code. - ErrNoRetryable = sharedErrors.ErrNoRetryable - - // ErrNoVideoFiles indicates that an import contains no video files. - ErrNoVideoFiles = sharedErrors.ErrNoVideoFiles - // ErrFallbackNotConfigured indicates that SABnzbd fallback is not enabled or configured. ErrFallbackNotConfigured = sharedErrors.ErrFallbackNotConfigured ) diff --git a/internal/importer/interfaces.go b/internal/importer/interfaces.go index 8bcf0c06..d924f735 100644 --- a/internal/importer/interfaces.go +++ b/internal/importer/interfaces.go @@ -20,10 +20,6 @@ type QueueManager interface { IsPaused() bool // IsRunning returns whether the queue manager is active IsRunning() bool - // GetWorkerCount returns the current number of workers - GetWorkerCount() int - // UpdateWorkerCount changes the number of active workers - UpdateWorkerCount(count int) error // CancelProcessing cancels processing of a specific item CancelProcessing(itemID int64) error // ProcessItemInBackground starts processing a specific item in the background @@ -56,8 +52,6 @@ type QueueOperations interface { AddToQueue(ctx context.Context, filePath string, relativePath *string, category *string, priority *database.QueuePriority) (*database.ImportQueueItem, error) // GetQueueStats returns queue statistics GetQueueStats(ctx context.Context) (*database.QueueStats, error) - // GetStats returns comprehensive service statistics - GetStats(ctx context.Context) (*ServiceStats, error) } // SymlinkCreator handles symlink creation for imported files @@ -127,8 +121,6 @@ type ImportService interface { NzbDavImporter QueueOperations - // Database returns the underlying database connection - Database() *database.DB // Close releases all resources Close() error // SetRcloneClient sets the rclone client for VFS notifications diff --git a/internal/importer/queue/manager.go b/internal/importer/queue/manager.go index a0dbf86b..d0fc5f31 100644 --- a/internal/importer/queue/manager.go +++ b/internal/importer/queue/manager.go @@ -162,13 +162,6 @@ func (m *Manager) IsRunning() bool { return m.running } -// GetWorkerCount returns the current worker count -func (m *Manager) GetWorkerCount() int { - m.mu.RLock() - defer m.mu.RUnlock() - return m.config.Workers -} - // CancelProcessing cancels processing for a specific item func (m *Manager) CancelProcessing(itemID int64) error { m.cancelMu.RLock() diff --git a/internal/importer/service.go b/internal/importer/service.go index 9046185e..902c00ef 100644 --- a/internal/importer/service.go +++ b/internal/importer/service.go @@ -155,7 +155,6 @@ type Service struct { paused bool ctx context.Context cancel context.CancelFunc - wg sync.WaitGroup // Cancellation tracking for processing items cancelFuncs map[int64]context.CancelFunc @@ -401,11 +400,6 @@ func (s *Service) SetArrsService(service *arrs.Service) { } } -// Database returns the database instance for processing -func (s *Service) Database() *database.DB { - return s.database -} - // GetQueueStats returns current queue statistics from database func (s *Service) GetQueueStats(ctx context.Context) (*database.QueueStats, error) { return s.database.Repository.GetQueueStats(ctx) @@ -744,61 +738,6 @@ func (s *Service) handleProcessingFailure(ctx context.Context, item *database.Im } } - -// ServiceStats holds statistics about the service -type ServiceStats struct { - IsRunning bool `json:"is_running"` - Workers int `json:"workers"` - QueueStats *database.QueueStats `json:"queue_stats,omitempty"` - ScanInfo ScanInfo `json:"scan_info"` -} - -// GetStats returns service statistics -func (s *Service) GetStats(ctx context.Context) (*ServiceStats, error) { - stats := &ServiceStats{ - IsRunning: s.IsRunning(), - Workers: s.config.Workers, - ScanInfo: s.GetScanStatus(), - } - - // Add queue statistics - queueStats, err := s.GetQueueStats(ctx) - if err != nil { - s.log.WarnContext(ctx, "Failed to get queue stats", "error", err) - } else { - stats.QueueStats = queueStats - } - - return stats, nil -} - -// UpdateWorkerCount updates the worker count configuration (requires service restart to take effect) -// Dynamic worker scaling is not supported - changes only apply on next service restart -func (s *Service) UpdateWorkerCount(count int) error { - if count <= 0 { - return fmt.Errorf("worker count must be greater than 0") - } - - s.mu.Lock() - defer s.mu.Unlock() - - s.log.InfoContext(s.ctx, "Queue worker count update requested - restart required to take effect", - "current_count", s.config.Workers, - "requested_count", count, - "running", s.running) - - // Configuration update is handled at the config manager level - // Changes only take effect on service restart - return nil -} - -// GetWorkerCount returns the current configured worker count -func (s *Service) GetWorkerCount() int { - s.mu.RLock() - defer s.mu.RUnlock() - return s.config.Workers -} - // CancelProcessing cancels a processing queue item by cancelling its context func (s *Service) CancelProcessing(itemID int64) error { return s.queueManager.CancelProcessing(itemID) diff --git a/internal/importer/utils/file_extensions.go b/internal/importer/utils/file_extensions.go index d859e14c..65cceb22 100644 --- a/internal/importer/utils/file_extensions.go +++ b/internal/importer/utils/file_extensions.go @@ -1,14 +1,10 @@ package utils import ( - "bufio" - "os" "path/filepath" "regexp" "slices" "strings" - - "github.com/gabriel-vasile/mimetype" ) // This file provides helpers translated from https://github.com/sabnzbd/sabnzbd/blob/develop/sabnzbd/utils/file_extension.py for detecting @@ -75,37 +71,6 @@ var allExt = func() []string { return out }() -// AllExtensions returns the combined list of known extensions (dot-prefixed). -// If you need to add user-defined extensions, pass them to AllExtensionsWith. -func AllExtensions() []string { return allExt } - -// AllExtensionsWith returns combined list plus user-defined dot- or non-dot-prefixed extensions. -func AllExtensionsWith(extra []string) []string { - if len(extra) == 0 { - return allExt - } - set := map[string]struct{}{} - for _, e := range allExt { - set[e] = struct{}{} - } - for _, e := range extra { - e = strings.ToLower(e) - if e == "" { - continue - } - if !strings.HasPrefix(e, ".") { - e = "." + e - } - set[e] = struct{}{} - } - out := make([]string, 0, len(set)) - for k := range set { - out = append(out, k) - } - slices.Sort(out) - return out -} - // HasPopularExtension reports whether file_path has a popular extension (case-insensitive) // or matches known RAR or 7zip patterns (e.g., .rar, .r00, .partXX.rar, .7z, .7z.001). func HasPopularExtension(filePath string) bool { @@ -123,82 +88,3 @@ func HasPopularExtension(filePath string) bool { base := filepath.Base(filePath) return rarPattern.MatchString(strings.ToLower(base)) || sevenZipPattern.MatchString(strings.ToLower(base)) } - -// AllPossibleExtensions attempts to detect the file's extension(s). -// Unlike Python's puremagic (which may return multiple candidates), we -// typically have one strong match via signature-based detection. -// The returned extensions are dot-prefixed and lowercase. -func AllPossibleExtensions(filePath string) []string { - // Try MIME-based detection - if mt, err := mimetype.DetectFile(filePath); err == nil && mt != nil { - if ext := strings.ToLower(mt.Extension()); ext != "" { - return []string{ext} - } - } - return nil -} - -// WhatIsMostLikelyExtension returns the most likely extension (dot-prefixed) for file_path. -// Logic mirrors the Python version: -// 1) If the start of the file is valid UTF-8 text, check for NZB clues, else return .txt -// 2) Otherwise, use signature detection and prefer a popular extension if it matches -// 3) Fallback to the first detected extension or empty string if none. -func WhatIsMostLikelyExtension(filePath string) string { - // 1) Quick text/NZB check on the first ~200 bytes - if ext, ok := sniffTextOrNZB(filePath, 200); ok { - return ext - } - - // 2) signature detection - candidates := AllPossibleExtensions(filePath) - if len(candidates) == 0 { - return "" - } - // Prefer popular extension - all := allExt - for _, cand := range candidates { - if slices.Contains(all, strings.ToLower(cand)) { - return strings.ToLower(cand) - } - } - // 3) fallback to first - return strings.ToLower(candidates[0]) -} - -// sniffTextOrNZB reads up to n bytes and checks if it's valid UTF-8 text and -// whether it contains NZB markers. Returns (ext, true) when determined. -func sniffTextOrNZB(filePath string, n int) (string, bool) { - f, err := os.Open(filePath) - if err != nil { - return "", false - } - defer f.Close() - - r := bufio.NewReader(f) - buf, _ := r.Peek(n) - // If valid UTF-8, treat as text - if !isLikelyUTF8(buf) { - return "", false - } - lower := strings.ToLower(string(buf)) - if strings.Contains(lower, "!doctype nzb public") || strings.Contains(lower, "= 0 { - return false - } - return true -} diff --git a/internal/metadata/proto/metadata.pb.go b/internal/metadata/proto/metadata.pb.go index 40bb0855..4ac1d460 100644 --- a/internal/metadata/proto/metadata.pb.go +++ b/internal/metadata/proto/metadata.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.9 -// protoc v3.21.12 +// protoc-gen-go v1.36.11 +// protoc v6.33.2 // source: internal/metadata/proto/metadata.proto package __ @@ -270,7 +270,7 @@ type FileMetadata struct { AesIv []byte `protobuf:"bytes,11,opt,name=aes_iv,json=aesIv,proto3" json:"aes_iv,omitempty"` // AES initialization vector (for AES-encrypted archives) ReleaseDate int64 `protobuf:"varint,12,opt,name=release_date,json=releaseDate,proto3" json:"release_date,omitempty"` // Unix timestamp of the original Usenet post release date Par2Files []*Par2FileReference `protobuf:"bytes,13,rep,name=par2_files,json=par2Files,proto3" json:"par2_files,omitempty"` // Associated PAR2 repair files - NzbdavId string `protobuf:"bytes,14,opt,name=nzbdav_id,json=nzbdavId,proto3" json:"nzbdav_id,omitempty"` // Original ID from nzbdav (for backward compatibility) + NzbdavId string `protobuf:"bytes,14,opt,name=nzbdav_id,json=nzbdavId,proto3" json:"nzbdav_id,omitempty"` // ID to maintain compatibility with nzbdav unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } diff --git a/internal/metadata/proto/metadata.proto b/internal/metadata/proto/metadata.proto index faa738c9..891b9325 100644 --- a/internal/metadata/proto/metadata.proto +++ b/internal/metadata/proto/metadata.proto @@ -47,7 +47,8 @@ message FileMetadata { repeated SegmentData segment_data = 9; // Segment information (lazy-loaded) bytes aes_key = 10; // AES encryption key (for AES-encrypted archives) bytes aes_iv = 11; // AES initialization vector (for AES-encrypted archives) - int64 release_date = 12; // Unix timestamp of the original Usenet post release date - repeated Par2FileReference par2_files = 13; // Associated PAR2 repair files + int64 release_date = 12; // Unix timestamp of the original Usenet post release date + repeated Par2FileReference par2_files = 13; // Associated PAR2 repair files + string nzbdav_id = 14; // ID to maintain compatibility with nzbdav } \ No newline at end of file