Skip to content

Conversation

@BlackDark
Copy link
Member

@BlackDark BlackDark commented Dec 26, 2025

Summary by Sourcery

Add configuration-driven remote path mapping synchronization for download clients across all supported *Arr applications, including validation, diffing, and dry-run support, while exposing new client APIs and wiring them into the main sync pipeline.

New Features:

  • Introduce remote path mapping configuration and syncing for download clients, including create, update, and delete operations per *Arr instance.
  • Expose remote path mapping CRUD methods on all *Arr client wrappers to interact with their respective RemotePathMapping API endpoints.
  • Add support for specifying and merging remote path mappings and deletion behavior in instance download client configuration.
  • Document remote path mappings in the configuration docs and provide example YAML usage in the sample config.

Enhancements:

  • Refactor unified client usage by adding a helper to access specific client instances directly, simplifying consumers like config and metadata syncers.
  • Improve tests by updating mocks to use the new specific client accessor and by seeding default download client config values for download client sync tests.

Documentation:

  • Extend configuration documentation with a new section explaining remote path mappings, configuration keys, and behavior, marked as available since v1.20.0.
  • Add an AGENTS.md guide to help AI agents and contributors understand project structure, conventions, and workflows.

Tests:

  • Add a new remote path syncer test suite scaffold and adjust existing metadata, root folder, and download client config tests to work with the new client access pattern.

@sourcery-ai
Copy link

sourcery-ai bot commented Dec 26, 2025

Reviewer's Guide

Adds support for synchronizing remote path mappings for download clients across all *Arr types, wiring it into the main pipeline and configuration system, while refactoring unified client usage to expose specific clients directly and extending clients/tests/docs accordingly.

Sequence diagram for remote path mappings sync in main pipeline

sequenceDiagram
  participant Pipeline as pipeline
  participant Syncer as syncRemotePaths
  participant Env as getEnvs
  participant UC as getSpecificClient
  participant Client as ArrClient
  participant Server as ArrServer

  Pipeline->>Syncer: syncRemotePaths(arrType, mergedConfigInstance, serverCache)
  Syncer->>Syncer: validateRemotePathConfig(remote_paths)

  Syncer->>UC: getSpecificClient<ArrClient>()
  UC-->>Syncer: Concrete client (RadarrClient|SonarrClient|...)
  Syncer->>Client: getRemotePathMappings()
  Client->>Server: vXRemotepathmappingList
  Server-->>Client: RemotePathMappingResource[]
  Client-->>Syncer: serverMappings

  Syncer->>Syncer: calculateDiff(configRemotePaths, serverMappings)

  Syncer->>Env: getEnvs()
  Env-->>Syncer: { DRY_RUN }
  alt DRY_RUN is true
    Syncer->>Syncer: compute counts (create, update, delete)
    Syncer-->>Pipeline: RemotePathSyncResult (no API mutations)
  else DRY_RUN is false
    loop For each config in diff.toCreate
      Syncer->>Client: createRemotePathMapping(mapping)
      Client->>Server: vXRemotepathmappingCreate
      alt Server reports already exists
        Client-->>Syncer: error "RemotePath already exists"
        Syncer->>Syncer: find existing mapping by host+remotePath
        Syncer->>Client: updateRemotePathMapping(id, mapping)
        Client->>Server: vXRemotepathmappingUpdate
      else Created successfully
        Client-->>Syncer: created mapping
      end
    end

    loop For each item in diff.toUpdate
      Syncer->>Client: updateRemotePathMapping(id, mapping)
      Client->>Server: vXRemotepathmappingUpdate
      Server-->>Client: updated mapping
      Client-->>Syncer: updated mapping
    end

    loop For each item in diff.toDelete
      Syncer->>Client: deleteRemotePathMapping(id)
      Client->>Server: vXRemotepathmappingDelete
      Server-->>Client: 204
      Client-->>Syncer: success
    end

    Syncer-->>Pipeline: RemotePathSyncResult(created, updated, deleted, unchanged)
  end
Loading

Updated class diagram for unified client and remote path client methods

classDiagram
  class UnifiedClient {
    +api IArrClient
    +UnifiedClient(type ArrType, baseUrl string, apiKey string)
    +getLanguages()
    +getQualityProfiles()
    +createQualityProfile(profile any)
    +updateQualityProfile(id string, profile any)
    +deleteQualityProfile(id string)
    +getCustomFormats()
    +createCustomFormat(format any)
    +updateCustomFormat(id string, format any)
    +deleteCustomFormat(id string)
  }

  class RadarrClient {
    +RadarrClient(baseUrl string, apiKey string)
    +getDownloadClientConfig() Promise~any~
    +updateDownloadClientConfig(id string, config any) Promise~any~
    +getRemotePathMappings() Promise~RemotePathMappingResource[]~
    +createRemotePathMapping(mapping RemotePathMappingResource) Promise~RemotePathMappingResource~
    +updateRemotePathMapping(id string, mapping RemotePathMappingResource) Promise~RemotePathMappingResource~
    +deleteRemotePathMapping(id string) Promise~void~
  }

  class SonarrClient {
    +SonarrClient(baseUrl string, apiKey string)
    +getDownloadClientConfig() Promise~any~
    +updateDownloadClientConfig(id string, config any) Promise~any~
    +getRemotePathMappings() Promise~RemotePathMappingResource[]~
    +createRemotePathMapping(mapping RemotePathMappingResource) Promise~RemotePathMappingResource~
    +updateRemotePathMapping(id string, mapping RemotePathMappingResource) Promise~RemotePathMappingResource~
    +deleteRemotePathMapping(id string) Promise~void~
  }

  class LidarrClient {
    +LidarrClient(baseUrl string, apiKey string)
    +getDownloadClientConfig() Promise~any~
    +updateDownloadClientConfig(id string, config any) Promise~any~
    +getRemotePathMappings() Promise~RemotePathMappingResource[]~
    +createRemotePathMapping(mapping RemotePathMappingResource) Promise~RemotePathMappingResource~
    +updateRemotePathMapping(id string, mapping RemotePathMappingResource) Promise~RemotePathMappingResource~
    +deleteRemotePathMapping(id string) Promise~void~
  }

  class ReadarrClient {
    +ReadarrClient(baseUrl string, apiKey string)
    +getDownloadClientConfig() Promise~any~
    +updateDownloadClientConfig(id string, config any) Promise~any~
    +getRemotePathMappings() Promise~RemotePathMappingResource[]~
    +createRemotePathMapping(mapping RemotePathMappingResource) Promise~RemotePathMappingResource~
    +updateRemotePathMapping(id string, mapping RemotePathMappingResource) Promise~RemotePathMappingResource~
    +deleteRemotePathMapping(id string) Promise~void~
  }

  class WhisparrClient {
    +WhisparrClient(baseUrl string, apiKey string)
    +getDownloadClientConfig() Promise~any~
    +updateDownloadClientConfig(id string, config any) Promise~any~
    +getRemotePathMappings() Promise~RemotePathMappingResource[]~
    +createRemotePathMapping(mapping RemotePathMappingResource) Promise~RemotePathMappingResource~
    +updateRemotePathMapping(id string, mapping RemotePathMappingResource) Promise~RemotePathMappingResource~
    +deleteRemotePathMapping(id string) Promise~void~
  }

  class getUnifiedClient {
    +getUnifiedClient() UnifiedClient
  }

  class getSpecificClientFn {
    +getSpecificClient~T~() T
  }

  class RemotePathMappingResource {
    +id number
    +host string
    +remotePath string
    +localPath string
  }

  class InputConfigRemotePath {
    +host string
    +remote_path string
    +local_path string
  }

  class RemotePathSyncResult {
    +created number
    +updated number
    +deleted number
    +unchanged number
    +arrType ArrType
  }

  UnifiedClient --> IArrClient
  getUnifiedClient ..> UnifiedClient
  getSpecificClientFn ..> RadarrClient
  getSpecificClientFn ..> SonarrClient
  getSpecificClientFn ..> LidarrClient
  getSpecificClientFn ..> ReadarrClient
  getSpecificClientFn ..> WhisparrClient

  RadarrClient ..> RemotePathMappingResource
  SonarrClient ..> RemotePathMappingResource
  LidarrClient ..> RemotePathMappingResource
  ReadarrClient ..> RemotePathMappingResource
  WhisparrClient ..> RemotePathMappingResource

  InputConfigRemotePath ..> RemotePathMappingResource
  RemotePathSyncResult ..> ArrType
Loading

Flow diagram for remote path diff calculation and sync decisions

flowchart TD
  A["Start syncRemotePaths"] --> B["Read remote_paths and delete_unmanaged_remote_paths from config"]
  B --> C{remote_paths is undefined?}
  C -->|Yes| D["Skip management and return zero counts"]
  C -->|No| E{remote_paths length is 0 and delete_unmanaged_remote_paths is false?}
  E -->|Yes| D
  E -->|No| F["validateRemotePathConfig(remote_paths)"]

  F --> G["Fetch serverMappings via client.getRemotePathMappings"]
  G --> H["calculateDiff(configs, serverMappings)"]

  H --> I{Any create, update or delete?}
  I -->|No| J["Log already up to date, return unchanged count"]
  I -->|Yes| K{DRY_RUN is true?}

  K -->|Yes| L["Log planned create/update/delete counts"]
  L --> M["Return RemotePathSyncResult with counts"]

  K -->|No| N["Apply creates, updates, deletes"]
  N --> O["On create error: check for 'already exists' and fallback to update"]
  O --> P["Accumulate created, updated, deleted counts"]
  P --> Q["Log success summary"]
  Q --> M
Loading

File-Level Changes

Change Details Files
Introduce remote path mapping sync feature and configuration, integrated into the main pipeline.
  • Add RemotePath types, validation schema, and diff result structures for remote path mappings.
  • Implement remotePathSyncer to validate config, compute create/update/delete diffs against server mappings, and perform CRUD with dry-run support and detailed logging.
  • Wire remote path sync into the main pipeline in index.ts, gated by download_clients.remote_paths and delete_unmanaged_remote_paths flags.
  • Extend merged config logic to merge remote_paths and delete_unmanaged_remote_paths from instance configs into templates.
  • Update config type definitions to include download_clients.remote_paths and delete_unmanaged_remote_paths as experimental options.
src/remotePaths/remotePath.types.ts
src/remotePaths/remotePathSyncer.ts
src/types/config.types.ts
src/config.ts
src/index.ts
Extend per-*Arr clients with remote path mapping CRUD operations.
  • Add getRemotePathMappings, createRemotePathMapping, updateRemotePathMapping, and deleteRemotePathMapping methods to each *Arr client.
  • Wire these methods to the appropriate generated API endpoints for v1/v3 RemotePathMapping resources.
src/clients/radarr-client.ts
src/clients/sonarr-client.ts
src/clients/lidarr-client.ts
src/clients/readarr-client.ts
src/clients/whisparr-client.ts
Refactor unified client usage to expose specific clients via a standalone helper and adjust consumers/tests.
  • Add getSpecificClient helper that returns the underlying concrete *Arr client from the UnifiedClient.
  • Remove UnifiedClient.getSpecificClient instance method and associated unused type state.
  • Update downloadClientConfigSyncer and metadata/root folder sync classes to use the new getSpecificClient helper instead of calling getUnifiedClient().getSpecificClient().
  • Adjust Vitest mocks in downloadClientConfigSyncer and metadata/root folder tests to mock getSpecificClient and the unified client shape with an api property.
src/clients/unified-client.ts
src/downloadClientConfig/downloadClientConfigSyncer.ts
src/metadataProfiles/metadataProfileLidarr.ts
src/metadataProfiles/metadataProfileReadarr.ts
src/rootFolder/rootFolderLidarr.ts
src/downloadClientConfig/downloadClientConfigSyncer.test.ts
src/metadataProfiles/metadataProfileLidarr.test.ts
src/metadataProfiles/metadataProfileReadarr.test.ts
src/rootFolder/rootFolderLidarr.test.ts
Document and exemplify the new remote path mapping configuration for users.
  • Add docs section describing download client remote path mappings, key behaviors, and constraints.
  • Update example full config YAML for Sonarr and Radarr to demonstrate remote_paths usage alongside download client config.
docs/docs/configuration/config-file.md
examples/full/config/config.yml
Add contributor-facing guide for AI agents and local tooling settings placeholder.
  • Introduce AGENTS.md to describe project architecture, dev workflow, and conventions for automated agents or new contributors.
  • Add a local settings JSON placeholder under .claude for tool configuration.
AGENTS.md
.claude/settings.local.json

Possibly linked issues

  • #N/A: PR directly implements the requested remote path mapping support for download_clients, including config, sync logic, and docs.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions
Copy link

Doc preview deployed to: https://363f788b.configarr-preview.pages.dev

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants