nako-metadata-scraper 0.1.0-alpha.2

Official Nako metadata scraper Addon Sidecar.
Documentation
use std::sync::Arc;

use nako_addon_protocol::{AddonResourceRequest, AddonResourceResponse};

use crate::{
    nako_runtime::{NakoRuntimeClient, NakoRuntimeTransport},
    providers::MetadataProvider,
};

use super::{
    MetadataQuery, MetadataScrapeOutcome, ProviderExternalIdCapability, ProviderFieldPolicy,
    ProviderRunPolicy, QueryExternalIdAlias, artwork, orchestration, response, scrape, writeback,
};

#[derive(Clone)]
pub struct MetadataScrapeRuntime<T = crate::nako_runtime::ReqwestNakoRuntimeTransport>
where
    T: NakoRuntimeTransport,
{
    default_language: String,
    external_id_aliases: Arc<Vec<QueryExternalIdAlias>>,
    external_id_capabilities: Arc<Vec<ProviderExternalIdCapability>>,
    default_provider_field_policy: Arc<ProviderFieldPolicy>,
    default_provider_run_policy: Arc<ProviderRunPolicy>,
    providers: Arc<Vec<Box<dyn MetadataProvider>>>,
    nako_runtime: Option<NakoRuntimeClient<T>>,
}

impl<T> MetadataScrapeRuntime<T>
where
    T: NakoRuntimeTransport,
{
    #[must_use]
    pub fn new(
        default_language: impl Into<String>,
        providers: Vec<Box<dyn MetadataProvider>>,
        nako_runtime: Option<NakoRuntimeClient<T>>,
    ) -> Self {
        Self::with_external_id_aliases(default_language, Vec::new(), providers, nako_runtime)
    }

    #[must_use]
    pub fn with_external_id_aliases(
        default_language: impl Into<String>,
        external_id_aliases: Vec<QueryExternalIdAlias>,
        providers: Vec<Box<dyn MetadataProvider>>,
        nako_runtime: Option<NakoRuntimeClient<T>>,
    ) -> Self {
        Self::with_external_id_aliases_and_provider_field_policy(
            default_language,
            external_id_aliases,
            ProviderFieldPolicy::default(),
            providers,
            nako_runtime,
        )
    }

    #[must_use]
    pub fn with_external_id_aliases_and_provider_field_policy(
        default_language: impl Into<String>,
        external_id_aliases: Vec<QueryExternalIdAlias>,
        default_provider_field_policy: ProviderFieldPolicy,
        providers: Vec<Box<dyn MetadataProvider>>,
        nako_runtime: Option<NakoRuntimeClient<T>>,
    ) -> Self {
        Self {
            default_language: default_language.into(),
            external_id_aliases: Arc::new(external_id_aliases),
            external_id_capabilities: Arc::new(Vec::new()),
            default_provider_field_policy: Arc::new(default_provider_field_policy),
            default_provider_run_policy: Arc::new(ProviderRunPolicy::default()),
            providers: Arc::new(providers),
            nako_runtime,
        }
    }

    #[must_use]
    pub fn with_external_id_capabilities(
        default_language: impl Into<String>,
        external_id_capabilities: Vec<ProviderExternalIdCapability>,
        providers: Vec<Box<dyn MetadataProvider>>,
        nako_runtime: Option<NakoRuntimeClient<T>>,
    ) -> Self {
        Self::with_external_id_capabilities_and_provider_field_policy(
            default_language,
            external_id_capabilities,
            ProviderFieldPolicy::default(),
            providers,
            nako_runtime,
        )
    }

    #[must_use]
    pub fn with_external_id_capabilities_and_provider_field_policy(
        default_language: impl Into<String>,
        external_id_capabilities: Vec<ProviderExternalIdCapability>,
        default_provider_field_policy: ProviderFieldPolicy,
        providers: Vec<Box<dyn MetadataProvider>>,
        nako_runtime: Option<NakoRuntimeClient<T>>,
    ) -> Self {
        Self::with_external_id_capabilities_field_policy_and_run_policy(
            default_language,
            external_id_capabilities,
            default_provider_field_policy,
            ProviderRunPolicy::default(),
            providers,
            nako_runtime,
        )
    }

    #[must_use]
    pub(crate) fn with_external_id_capabilities_field_policy_and_run_policy(
        default_language: impl Into<String>,
        external_id_capabilities: Vec<ProviderExternalIdCapability>,
        default_provider_field_policy: ProviderFieldPolicy,
        default_provider_run_policy: ProviderRunPolicy,
        providers: Vec<Box<dyn MetadataProvider>>,
        nako_runtime: Option<NakoRuntimeClient<T>>,
    ) -> Self {
        Self {
            default_language: default_language.into(),
            external_id_aliases: Arc::new(Vec::new()),
            external_id_capabilities: Arc::new(external_id_capabilities),
            default_provider_field_policy: Arc::new(default_provider_field_policy),
            default_provider_run_policy: Arc::new(default_provider_run_policy),
            providers: Arc::new(providers),
            nako_runtime,
        }
    }

    pub async fn scrape(&self, request: AddonResourceRequest) -> AddonResourceResponse {
        let outcome = self
            .scrape_outcome(&request.request_id, &request.payload)
            .await;
        response::metadata_response(request, outcome)
    }

    pub(crate) async fn scrape_outcome(
        &self,
        request_id: &str,
        payload: &serde_json::Value,
    ) -> MetadataScrapeOutcome {
        let provider_run_policy = self.provider_run_policy_from_payload(payload);
        self.scrape_outcome_with_provider_run_policy(request_id, payload, provider_run_policy)
            .await
    }

    pub(crate) fn provider_run_policy_from_payload(
        &self,
        payload: &serde_json::Value,
    ) -> ProviderRunPolicy {
        ProviderRunPolicy::from_payload_or_default(
            payload,
            self.default_provider_run_policy.as_ref(),
        )
    }

    pub(crate) async fn scrape_outcome_with_provider_run_policy(
        &self,
        request_id: &str,
        payload: &serde_json::Value,
        provider_run_policy: ProviderRunPolicy,
    ) -> MetadataScrapeOutcome {
        let query = if self.external_id_capabilities.is_empty() {
            MetadataQuery::from_payload_with_external_id_aliases(
                payload,
                &self.default_language,
                self.external_id_aliases.as_ref().as_slice(),
            )
        } else {
            MetadataQuery::from_payload_with_external_id_capabilities(
                payload,
                &self.default_language,
                self.external_id_capabilities.as_ref().as_slice(),
            )
        };
        let writeback_request = writeback::metadata_writeback_input_from_payload(payload);
        let artwork_writeback_request = artwork::artwork_writeback_input_from_payload(payload);
        let provider_field_policy = ProviderFieldPolicy::from_payload_or_default(
            payload,
            self.default_provider_field_policy.as_ref(),
        );
        let suggestions = orchestration::suggest_candidates(
            self.providers.as_ref().as_slice(),
            &query,
            self.external_id_capabilities.as_ref().as_slice(),
            &provider_field_policy,
            &provider_run_policy,
        )
        .await;
        let selected_candidate = suggestions.candidates.first().cloned();
        let writeback_result = writeback::maybe_submit_metadata_writeback(
            self.nako_runtime.as_ref(),
            request_id,
            &query,
            selected_candidate.as_ref(),
            writeback_request,
        )
        .await;
        let artwork_writeback_result = writeback::maybe_submit_artwork_writeback(
            self.nako_runtime.as_ref(),
            request_id,
            &query,
            &suggestions.candidates,
            artwork_writeback_request,
        )
        .await;

        MetadataScrapeOutcome {
            av: scrape::av_facts_for_outcome(payload, &query),
            query,
            candidates: suggestions.candidates,
            provider_execution: suggestions.execution,
            writeback_result,
            artwork_writeback_result,
        }
    }
}