Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 0 additions & 72 deletions cmd/migrate_nzbdav/main.go

This file was deleted.

22 changes: 0 additions & 22 deletions conductor/paginated-history-search.md

This file was deleted.

31 changes: 0 additions & 31 deletions conductor/reliable-blocklist.md

This file was deleted.

5 changes: 1 addition & 4 deletions internal/api/arrs_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,6 @@ type ArrsWebhookRequest struct {
EpisodeFile struct {
Path string `json:"path"`
} `json:"episodeFile"`
Download struct {
ID string `json:"id"`
} `json:"download"`
DeletedFiles ArrsDeletedFiles `json:"deletedFiles,omitempty"`
}

Expand Down Expand Up @@ -452,7 +449,7 @@ func (s *Server) handleArrsWebhook(c *fiber.Ctx) error {

// Add to health check (pending status) with high priority (Next) to ensure it's processed right away
cfg := s.configManager.GetConfigGetter()()
err := s.healthRepo.AddFileToHealthCheckWithMetadata(c.Context(), normalizedPath, &path, cfg.GetMaxRetries(), cfg.GetMaxRepairRetries(), sourceNzb, req.Download.ID, database.HealthPriorityNext, releaseDate)
err := s.healthRepo.AddFileToHealthCheckWithMetadata(c.Context(), normalizedPath, &path, cfg.GetMaxRetries(), cfg.GetMaxRepairRetries(), sourceNzb, database.HealthPriorityNext, releaseDate)
if err != nil {
slog.ErrorContext(c.Context(), "Failed to add webhook file to health check", "path", normalizedPath, "error", err)
} else {
Expand Down
6 changes: 3 additions & 3 deletions internal/api/health_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ func (s *Server) handleRepairHealth(c *fiber.Ctx) error {
}

// Trigger rescan with the resolved path
err = s.arrsService.TriggerFileRescan(ctx, pathForRescan, item.FilePath, "")
err = s.arrsService.TriggerFileRescan(ctx, pathForRescan, item.FilePath)
if err != nil {
// Check if this is a "no ARR instance found" error
if strings.Contains(err.Error(), "no ARR instance found") {
Expand Down Expand Up @@ -546,7 +546,7 @@ func (s *Server) handleRepairHealthBulk(c *fiber.Ctx) error {
}

// Trigger rescan
err = s.arrsService.TriggerFileRescan(ctx, pathForRescan, item.FilePath, "")
err = s.arrsService.TriggerFileRescan(ctx, pathForRescan, item.FilePath)
if err != nil {
failedCount++
errors[filePath] = fmt.Sprintf("Failed to trigger repair: %v", err)
Expand Down Expand Up @@ -879,7 +879,7 @@ func (s *Server) handleAddHealthCheck(c *fiber.Ctx) error {
}

// Add file to health database
err := s.healthRepo.AddFileToHealthCheck(c.Context(), req.FilePath, req.LibraryPath, maxRetries, cfg.GetMaxRepairRetries(), req.SourceNzb, "", req.Priority)
err := s.healthRepo.AddFileToHealthCheck(c.Context(), req.FilePath, req.LibraryPath, maxRetries, cfg.GetMaxRepairRetries(), req.SourceNzb, req.Priority)
if err != nil {
return RespondInternalError(c, "Failed to add file for health check", err.Error())
}
Expand Down
132 changes: 50 additions & 82 deletions internal/arrs/scanner/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,9 +297,9 @@ func (m *Manager) sonarrHasFile(ctx context.Context, client *sonarr.Sonarr, inst
}

// TriggerFileRescan triggers a rescan for a specific file path through the appropriate ARR instance
func (m *Manager) TriggerFileRescan(ctx context.Context, pathForRescan string, relativePath string, downloadID string) error {
func (m *Manager) TriggerFileRescan(ctx context.Context, pathForRescan string, relativePath string) error {
res, err, _ := m.sf.Do(fmt.Sprintf("rescan:%s", pathForRescan), func() (interface{}, error) {
slog.InfoContext(ctx, "Triggering ARR rescan", "path", pathForRescan, "relative_path", relativePath, "download_id", downloadID)
slog.InfoContext(ctx, "Triggering ARR rescan", "path", pathForRescan, "relative_path", relativePath)

// Find which ARR instance manages this file path
instanceType, instanceName, err := m.findInstanceForFilePath(ctx, pathForRescan, relativePath)
Expand All @@ -325,14 +325,14 @@ func (m *Manager) TriggerFileRescan(ctx context.Context, pathForRescan string, r
if err != nil {
return nil, fmt.Errorf("failed to create Radarr client: %w", err)
}
return nil, m.triggerRadarrRescanByPath(ctx, client, pathForRescan, relativePath, instanceName, downloadID)
return nil, m.triggerRadarrRescanByPath(ctx, client, pathForRescan, relativePath, instanceName)

case "sonarr":
client, err := m.clients.GetOrCreateSonarrClient(instanceName, instanceConfig.URL, instanceConfig.APIKey)
if err != nil {
return nil, fmt.Errorf("failed to create Sonarr client: %w", err)
}
return nil, m.triggerSonarrRescanByPath(ctx, client, pathForRescan, relativePath, instanceName, downloadID)
return nil, m.triggerSonarrRescanByPath(ctx, client, pathForRescan, relativePath, instanceName)

case "lidarr", "readarr", "whisparr":
// For now, we only support RefreshMonitoredDownloads for these
Expand Down Expand Up @@ -492,12 +492,11 @@ func (m *Manager) TriggerDownloadScan(ctx context.Context, instanceType string)
}

// triggerRadarrRescanByPath triggers a rescan in Radarr for the given file path
func (m *Manager) triggerRadarrRescanByPath(ctx context.Context, client *radarr.Radarr, filePath, relativePath, instanceName string, downloadID string) error {
func (m *Manager) triggerRadarrRescanByPath(ctx context.Context, client *radarr.Radarr, filePath, relativePath, instanceName string) error {
slog.InfoContext(ctx, "Searching Radarr for matching movie",
"instance", instanceName,
"file_path", filePath,
"relative_path", relativePath,
"download_id", downloadID)
"relative_path", relativePath)

// Get all movies to find the one with matching file path
movies, err := m.data.GetMovies(ctx, client, instanceName)
Expand Down Expand Up @@ -574,7 +573,7 @@ func (m *Manager) triggerRadarrRescanByPath(ctx context.Context, client *radarr.
// But we can still trigger search
if targetMovie.HasFile && targetMovie.MovieFile != nil {
// Try to blocklist the release associated with this file
if err := m.blocklistRadarrMovieFile(ctx, client, targetMovie.ID, targetMovie.MovieFile.ID, downloadID); err != nil {
if err := m.blocklistRadarrMovieFile(ctx, client, targetMovie.ID, targetMovie.MovieFile.ID); err != nil {
slog.WarnContext(ctx, "Failed to blocklist Radarr release", "error", err)
}

Expand Down Expand Up @@ -612,7 +611,7 @@ func (m *Manager) triggerRadarrRescanByPath(ctx context.Context, client *radarr.
}

// triggerSonarrRescanByPath triggers a rescan in Sonarr for the given file path
func (m *Manager) triggerSonarrRescanByPath(ctx context.Context, client *sonarr.Sonarr, filePath, relativePath, instanceName string, downloadID string) error {
func (m *Manager) triggerSonarrRescanByPath(ctx context.Context, client *sonarr.Sonarr, filePath, relativePath, instanceName string) error {
cfg := m.configGetter()

// Get library directory from health config
Expand All @@ -625,8 +624,7 @@ func (m *Manager) triggerSonarrRescanByPath(ctx context.Context, client *sonarr.
"instance", instanceName,
"file_path", filePath,
"relative_path", relativePath,
"library_dir", libraryDir,
"download_id", downloadID)
"library_dir", libraryDir)

// Get all series to find the one that contains this file path
series, err := m.data.GetSeries(ctx, client, instanceName)
Expand Down Expand Up @@ -740,7 +738,7 @@ func (m *Manager) triggerSonarrRescanByPath(ctx context.Context, client *sonarr.
"episode_file_id", targetEpisodeFile.ID)

// Try to blocklist the release associated with this file
if err := m.blocklistSonarrEpisodeFile(ctx, client, targetSeries.ID, targetEpisodeFile.ID, downloadID); err != nil {
if err := m.blocklistSonarrEpisodeFile(ctx, client, targetSeries.ID, targetEpisodeFile.ID); err != nil {
slog.WarnContext(ctx, "Failed to blocklist Sonarr release", "error", err)
}

Expand Down Expand Up @@ -845,50 +843,35 @@ func (m *Manager) failSonarrQueueItemByPath(ctx context.Context, client *sonarr.
}

// blocklistRadarrMovieFile finds the history event for the given file and marks it as failed (blocklisting the release)
func (m *Manager) blocklistRadarrMovieFile(ctx context.Context, client *radarr.Radarr, movieID int64, fileID int64, knownDownloadID string) error {
slog.DebugContext(ctx, "Attempting to find and blocklist release for movie file", "movie_id", movieID, "file_id", fileID, "known_download_id", knownDownloadID)

var downloadID string = knownDownloadID
var history *radarr.History
func (m *Manager) blocklistRadarrMovieFile(ctx context.Context, client *radarr.Radarr, movieID int64, fileID int64) error {
slog.DebugContext(ctx, "Attempting to find and blocklist release for movie file", "movie_id", movieID, "file_id", fileID)

if downloadID == "" {
// Fetch history for this specific movie
req := &starr.PageReq{PageSize: 1000, SortKey: "date", SortDir: starr.SortDescend}
req.Set("movieId", strconv.FormatInt(movieID, 10))
// Fetch history for this specific movie
req := &starr.PageReq{PageSize: 100, SortKey: "date", SortDir: starr.SortDescend}
req.Set("movieId", strconv.FormatInt(movieID, 10))

var err error
history, err = client.GetHistoryPageContext(ctx, req)
if err != nil {
return fmt.Errorf("failed to fetch Radarr history: %w", err)
}
history, err := client.GetHistoryPageContext(ctx, req)
if err != nil {
return fmt.Errorf("failed to fetch Radarr history: %w", err)
}

targetFileID := strconv.FormatInt(fileID, 10)
targetFileID := strconv.FormatInt(fileID, 10)
var downloadID string

// 1. Find the import event to get the downloadId
for _, record := range history.Records {
if record.Data.FileID == targetFileID && (record.EventType == "movieFileImported" || record.EventType == "downloadFolderImported") {
downloadID = record.DownloadID
break
}
}

if downloadID == "" {
slog.WarnContext(ctx, "Could not find import event in Radarr history for file", "movie_id", movieID, "file_id", fileID)
return nil
// 1. Find the import event to get the downloadId
for _, record := range history.Records {
if record.Data.FileID == targetFileID && (record.EventType == "movieFileImported" || record.EventType == "downloadFolderImported") {
downloadID = record.DownloadID
break
}
}

// 2. Find the original grab event using the downloadId
if history == nil {
req := &starr.PageReq{PageSize: 100, SortKey: "date", SortDir: starr.SortDescend}
req.Set("movieId", strconv.FormatInt(movieID, 10))
var err error
history, err = client.GetHistoryPageContext(ctx, req)
if err != nil {
return fmt.Errorf("failed to fetch Radarr history: %w", err)
}
if downloadID == "" {
slog.WarnContext(ctx, "Could not find import event in Radarr history for file", "movie_id", movieID, "file_id", fileID)
return nil
}

// 2. Find the original grab event using the downloadId
for _, record := range history.Records {
if record.DownloadID == downloadID && record.EventType == "grabbed" {
slog.InfoContext(ctx, "Found grabbed history record, marking as failed to blocklist release",
Expand All @@ -905,50 +888,35 @@ func (m *Manager) blocklistRadarrMovieFile(ctx context.Context, client *radarr.R
}

// blocklistSonarrEpisodeFile finds the grabbed history event for the given file and marks it as failed (blocklisting the release)
func (m *Manager) blocklistSonarrEpisodeFile(ctx context.Context, client *sonarr.Sonarr, seriesID int64, fileID int64, knownDownloadID string) error {
slog.DebugContext(ctx, "Attempting to find and blocklist release for episode file", "series_id", seriesID, "file_id", fileID, "known_download_id", knownDownloadID)
func (m *Manager) blocklistSonarrEpisodeFile(ctx context.Context, client *sonarr.Sonarr, seriesID int64, fileID int64) error {
slog.DebugContext(ctx, "Attempting to find and blocklist release for episode file", "series_id", seriesID, "file_id", fileID)

var downloadID string = knownDownloadID
var history *sonarr.History
// Fetch history for this specific series
req := &starr.PageReq{PageSize: 100, SortKey: "date", SortDir: starr.SortDescend}
req.Set("seriesId", strconv.FormatInt(seriesID, 10))

if downloadID == "" {
// Fetch history for this specific series
req := &starr.PageReq{PageSize: 1000, SortKey: "date", SortDir: starr.SortDescend}
req.Set("seriesId", strconv.FormatInt(seriesID, 10))

var err error
history, err = client.GetHistoryPageContext(ctx, req)
if err != nil {
return fmt.Errorf("failed to fetch Sonarr history: %w", err)
}

targetFileID := strconv.FormatInt(fileID, 10)
history, err := client.GetHistoryPageContext(ctx, req)
if err != nil {
return fmt.Errorf("failed to fetch Sonarr history: %w", err)
}

// 1. Find the import event to get the downloadId
for _, record := range history.Records {
if record.Data.FileID == targetFileID && record.EventType == "downloadFolderImported" {
downloadID = record.DownloadID
break
}
}
targetFileID := strconv.FormatInt(fileID, 10)
var downloadID string

if downloadID == "" {
slog.WarnContext(ctx, "Could not find import event in Sonarr history for file", "series_id", seriesID, "file_id", fileID)
return nil
// 1. Find the import event to get the downloadId
for _, record := range history.Records {
if record.Data.FileID == targetFileID && record.EventType == "downloadFolderImported" {
downloadID = record.DownloadID
break
}
}

// 2. Find the original grab event using the downloadId
if history == nil {
req := &starr.PageReq{PageSize: 100, SortKey: "date", SortDir: starr.SortDescend}
req.Set("seriesId", strconv.FormatInt(seriesID, 10))
var err error
history, err = client.GetHistoryPageContext(ctx, req)
if err != nil {
return fmt.Errorf("failed to fetch Sonarr history: %w", err)
}
if downloadID == "" {
slog.WarnContext(ctx, "Could not find import event in Sonarr history for file", "series_id", seriesID, "file_id", fileID)
return nil
}

// 2. Find the original grab event using the downloadId
for _, record := range history.Records {
if record.DownloadID == downloadID && record.EventType == "grabbed" {
slog.InfoContext(ctx, "Found grabbed history record, marking as failed to blocklist release",
Expand Down
Loading
Loading