internetarchive-rs 0.1.4

Async Rust client for Internet Archive item metadata, search, uploads, metadata updates, and downloads.
Documentation
use std::future::Future;
use std::path::Path;

use client_uploader_traits::{
    ClientContext, CreatePublication, CreatePublicationRequest, DownloadNamedPublicFile,
    ExistingFileConflictPolicy, ExistingFileConflictPolicyKind, ListResourceFiles,
    MaybeAuthenticatedClient, PublicationOutcome, ReadPublicResource, RepositoryFile,
    RepositoryRecord, SearchPublicResources, SearchResultsLike, UpdatePublication,
    UpdatePublicationRequest, UploadSourceKind, UploadSpecLike,
};

use crate::downloads::ResolvedDownload;
use crate::endpoint::Endpoint;
use crate::error::InternetArchiveError;
use crate::metadata::ItemMetadata;
use crate::model::{Item, ItemFile, SearchDocument, SearchResponse, SearchResultPage};
use crate::poll::PollOptions;
use crate::search::SearchQuery;
use crate::upload::{FileConflictPolicy, UploadSource, UploadSpec};
use crate::workflow::{PublishOutcome, PublishRequest};
use crate::{InternetArchiveClient, ItemIdentifier};

fn into_create_publish_request(
    request: CreatePublicationRequest<ItemIdentifier, ItemMetadata, UploadSpec>,
) -> PublishRequest {
    PublishRequest::new(request.target, request.metadata, request.uploads)
}

fn into_update_publish_request(
    request: UpdatePublicationRequest<ItemIdentifier, ItemMetadata, FileConflictPolicy, UploadSpec>,
) -> PublishRequest {
    let mut publish_request =
        PublishRequest::new(request.resource_id, request.metadata, request.uploads);
    publish_request.conflict_policy = request.policy;
    publish_request
}

impl ClientContext for InternetArchiveClient {
    type Endpoint = Endpoint;
    type PollOptions = PollOptions;
    type Error = InternetArchiveError;

    fn endpoint(&self) -> &Self::Endpoint {
        self.endpoint()
    }

    fn poll_options(&self) -> &Self::PollOptions {
        self.poll_options()
    }

    fn request_timeout(&self) -> Option<std::time::Duration> {
        self.request_timeout()
    }

    fn connect_timeout(&self) -> Option<std::time::Duration> {
        self.connect_timeout()
    }
}

impl MaybeAuthenticatedClient for InternetArchiveClient {
    fn has_auth(&self) -> bool {
        self.has_auth()
    }
}

impl UploadSpecLike for UploadSpec {
    fn filename(&self) -> &str {
        &self.filename
    }

    fn source_kind(&self) -> UploadSourceKind {
        match &self.source {
            UploadSource::Path(_) => UploadSourceKind::Path,
            UploadSource::Bytes(_) => UploadSourceKind::Bytes,
        }
    }

    fn content_length(&self) -> Option<u64> {
        match &self.source {
            UploadSource::Path(path) => std::fs::metadata(path).ok().map(|metadata| metadata.len()),
            UploadSource::Bytes(bytes) => u64::try_from(bytes.len()).ok(),
        }
    }

    fn content_type(&self) -> Option<&str> {
        Some(self.content_type.as_ref())
    }
}

impl RepositoryFile for ItemFile {
    type Id = String;

    fn file_id(&self) -> Option<Self::Id> {
        None
    }

    fn file_name(&self) -> &str {
        &self.name
    }

    fn size_bytes(&self) -> Option<u64> {
        self.size
    }

    fn checksum(&self) -> Option<&str> {
        self.md5
            .as_deref()
            .or(self.sha1.as_deref())
            .or(self.crc32.as_deref())
    }
}

impl RepositoryRecord for Item {
    type Id = ItemIdentifier;
    type File = ItemFile;

    fn resource_id(&self) -> Option<Self::Id> {
        self.identifier()
    }

    fn title(&self) -> Option<&str> {
        self.metadata.title()
    }

    fn files(&self) -> &[Self::File] {
        &self.files
    }
}

impl SearchResultsLike for SearchResultPage {
    type Item = SearchDocument;

    fn items(&self) -> &[Self::Item] {
        &self.docs
    }

    fn total_hits(&self) -> Option<u64> {
        Some(self.num_found)
    }
}

impl SearchResultsLike for SearchResponse {
    type Item = SearchDocument;

    fn items(&self) -> &[Self::Item] {
        &self.response.docs
    }

    fn total_hits(&self) -> Option<u64> {
        Some(self.response.num_found)
    }
}

impl PublicationOutcome for PublishOutcome {
    type PublicResource = Item;

    fn public_resource(&self) -> &Self::PublicResource {
        &self.item
    }

    fn created(&self) -> Option<bool> {
        Some(self.created)
    }
}

impl ExistingFileConflictPolicy for FileConflictPolicy {
    fn kind(&self) -> ExistingFileConflictPolicyKind {
        match self {
            Self::Error => ExistingFileConflictPolicyKind::Error,
            Self::Skip => ExistingFileConflictPolicyKind::Skip,
            Self::Overwrite => ExistingFileConflictPolicyKind::Overwrite,
            Self::OverwriteKeepingHistory => {
                ExistingFileConflictPolicyKind::OverwriteKeepingHistory
            }
        }
    }
}

impl ReadPublicResource for InternetArchiveClient {
    type ResourceId = ItemIdentifier;
    type Resource = Item;

    fn get_public_resource(
        &self,
        id: &Self::ResourceId,
    ) -> impl Future<Output = Result<Self::Resource, Self::Error>> {
        self.get_item(id)
    }
}

impl SearchPublicResources for InternetArchiveClient {
    type Query = SearchQuery;
    type SearchResults = SearchResponse;

    fn search_public_resources(
        &self,
        query: &Self::Query,
    ) -> impl Future<Output = Result<Self::SearchResults, Self::Error>> {
        self.search(query)
    }
}

impl ListResourceFiles for InternetArchiveClient {
    type ResourceId = ItemIdentifier;
    type File = ItemFile;

    async fn list_resource_files(
        &self,
        id: &Self::ResourceId,
    ) -> Result<Vec<Self::File>, Self::Error> {
        Ok(self.get_item(id).await?.files)
    }
}

impl DownloadNamedPublicFile for InternetArchiveClient {
    type ResourceId = ItemIdentifier;
    type Download = ResolvedDownload;

    async fn download_named_public_file_to_path(
        &self,
        id: &Self::ResourceId,
        name: &str,
        path: &Path,
    ) -> Result<Self::Download, Self::Error> {
        self.download_to_path(id, name, path).await?;
        self.resolve_download(id, name)
    }
}

impl CreatePublication for InternetArchiveClient {
    type CreateTarget = ItemIdentifier;
    type Metadata = ItemMetadata;
    type Upload = UploadSpec;
    type Output = PublishOutcome;

    fn create_publication(
        &self,
        request: CreatePublicationRequest<Self::CreateTarget, Self::Metadata, Self::Upload>,
    ) -> impl Future<Output = Result<Self::Output, Self::Error>> {
        self.publish_item(into_create_publish_request(request))
    }
}

impl UpdatePublication for InternetArchiveClient {
    type ResourceId = ItemIdentifier;
    type Metadata = ItemMetadata;
    type FilePolicy = FileConflictPolicy;
    type Upload = UploadSpec;
    type Output = PublishOutcome;

    fn update_publication(
        &self,
        request: UpdatePublicationRequest<
            Self::ResourceId,
            Self::Metadata,
            Self::FilePolicy,
            Self::Upload,
        >,
    ) -> impl Future<Output = Result<Self::Output, Self::Error>> {
        self.upsert_item(into_update_publish_request(request))
    }
}