diff --git a/ipfs-api-prelude/src/api.rs b/ipfs-api-prelude/src/api.rs index 964cd78..6fd2ae8 100644 --- a/ipfs-api-prelude/src/api.rs +++ b/ipfs-api-prelude/src/api.rs @@ -10,7 +10,7 @@ use crate::{read::LineDecoder, request, response, Backend, BoxStream}; use async_trait::async_trait; use bytes::Bytes; use common_multipart_rfc7578::client::multipart; -use futures::{future, FutureExt, TryStreamExt, AsyncRead}; +use futures::{future, AsyncRead, FutureExt, TryStreamExt}; use std::{ fs::File, io::{Cursor, Read}, @@ -80,7 +80,8 @@ pub trait IpfsApi: Backend { where R: 'static + AsyncRead + Send + Sync + Unpin, { - self.add_async_with_options(data, request::Add::default()).await + self.add_async_with_options(data, request::Add::default()) + .await } /// Add a file to IPFS with options. @@ -2004,6 +2005,143 @@ pub trait IpfsApi: Backend { // TODO /pin/verify + /// Pin object to remote pinning service. + /// + /// - `service`: Name of the remote pinning service to use (mandatory). + /// + /// - `name`: An optional name for the pin. + /// + /// - `background`: Add to the queue on the remote service and + /// return immediately (does not wait for pinned status). + /// + /// # Examples + /// + /// ```no_run + /// use ipfs-api-backend-hyper::{IpfsApi, IpfsClient}; + /// + /// let client = IpfsClient::default(); + /// let res = client.pin_remote_add( + /// "QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", + /// "pinata", + /// Some("pin_task_name"), + /// true + /// ); + /// ``` + /// + async fn pin_remote_add( + &self, + key: &str, + service: &str, + name: Option<&str>, + background: bool, + ) -> Result { + self.request( + request::PinRemoteAdd { + key, + service: Some(service), + name, + background: Some(background), + }, + None, + ) + .await + } + + /// Returns a list objects pinned to remote pinning service. + /// + /// - `service`: Name of the remote pinning service to use (mandatory). + /// + /// - `name`: Return pins with names that contain the value provided (case-sensitive, exact match). + /// + /// - `cid`: Return pins for the specified CIDs + /// + /// - `status`: Return pins with the specified statuses (queued,pinning,pinned,failed), default [pinned]. + /// + /// # Examples + /// + /// ```no_run + /// use ipfs-api-backend-hyper::{IpfsApi, IpfsClient}; + /// use ipfs_api_prelude::request::PinStatus; + /// + /// let client = IpfsClient::default(); + /// let res = client.pin_remote_ls("pinata", None, None, None); + /// + /// let status = vec![PinStatus::Pinning, PinStatus::Pinned]; + /// let res = client.pin_remote_ls( + /// "pinata", + /// None, + /// None, + /// Some(&status) + /// ); + /// ``` + /// + async fn pin_remote_ls( + &self, + service: &str, + name: Option<&str>, + cid: Option<&[&str]>, + status: Option<&[request::PinStatus]>, + ) -> Result, Self::Error> { + let req = self.build_base_request( + request::PinRemoteLs { + service: Some(service), + name, + cid: cid.map(|cid| cid.into()), + status, + }, + None, + )?; + self.request_stream_json(req).try_collect().await + } + + /// Remove pins from remote pinning service. + /// + /// - `service`: Name of the remote pinning service to use (mandatory). + /// + /// - `name`: Remove pins with names that contain provided value (case-sensitive, exact match). + /// + /// - `cid`: Remove pins for the specified CIDs. + /// + /// - `status`: Remove pins with the specified statuses (queued,pinning,pinned,failed) + /// + /// - `force`: Allow removal of multiple pins matching the query without additional confirmation. + /// + /// # Examples + /// + /// ```no_run + /// use ipfs_api::{IpfsApi, IpfsClient}; + /// + /// let client = IpfsClient::default(); + /// let res = client.pin_remote_rm( + /// "pinata", + /// None, + /// None, + /// None, + /// true + /// ); + /// ``` + /// + async fn pin_remote_rm( + &self, + service: &str, + name: Option<&str>, + cid: Option<&[&str]>, + status: Option<&[request::PinStatus]>, + force: bool, + ) -> Result { + self.request_string( + request::PinRemoteRm { + service: Some(service), + name, + cid: cid.map(|cid| cid.into()), + status, + force: Some(force), + }, + None, + ) + .await + } + /// Pings a peer. /// /// ```no_run diff --git a/ipfs-api-prelude/src/request/pin.rs b/ipfs-api-prelude/src/request/pin.rs index 3e4815d..69347db 100644 --- a/ipfs-api-prelude/src/request/pin.rs +++ b/ipfs-api-prelude/src/request/pin.rs @@ -46,3 +46,133 @@ pub struct PinRm<'a> { impl<'a> ApiRequest for PinRm<'a> { const PATH: &'static str = "/pin/rm"; } + +#[derive(Serialize)] +pub struct PinRemoteAdd<'a> { + #[serde(rename = "arg")] + pub key: &'a str, + + pub service: Option<&'a str>, + pub name: Option<&'a str>, + pub background: Option, +} + +impl<'a> ApiRequest for PinRemoteAdd<'a> { + const PATH: &'static str = "/pin/remote/add"; +} + +#[derive(Serialize)] +pub struct PinRemoteLs<'a> { + pub service: Option<&'a str>, + pub name: Option<&'a str>, + pub cid: Option>, + #[serde( + skip_serializing_if = "Option::is_none", + serialize_with = "serialize_pin_status" + )] + pub status: Option<&'a [PinStatus]>, +} + +impl<'a> ApiRequest for PinRemoteLs<'a> { + const PATH: &'static str = "/pin/remote/ls"; +} + +#[derive(Serialize)] +pub struct PinRemoteRm<'a> { + pub service: Option<&'a str>, + pub name: Option<&'a str>, + pub cid: Option>, + #[serde( + skip_serializing_if = "Option::is_none", + serialize_with = "serialize_pin_status" + )] + pub status: Option<&'a [PinStatus]>, + pub force: Option, +} + +impl<'a> ApiRequest for PinRemoteRm<'a> { + const PATH: &'static str = "/pin/remote/rm"; +} + +pub struct Cids<'a>(&'a [&'a str]); + +impl<'a> From<&'a [&'a str]> for Cids<'a> { + fn from(data: &'a [&'a str]) -> Self { + Self(data) + } +} + +impl<'a> Serialize for Cids<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let cids = self.0.join(","); + serializer.serialize_str(&cids) + } +} + +#[derive(Serialize)] +pub enum PinStatus { + Queued, + Pinning, + Pinned, + Failed, +} + +impl std::fmt::Display for PinStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let pin_status = match self { + PinStatus::Failed => "failed", + PinStatus::Queued => "queued", + PinStatus::Pinning => "pinning", + PinStatus::Pinned => "pinned", + }; + write!(f, "{pin_status}") + } +} + +fn serialize_pin_status( + pin_status: &Option<&[PinStatus]>, + serializer: S, +) -> Result +where + S: serde::Serializer, +{ + let pin_status = pin_status.unwrap(); + let pin_status = pin_status + .iter() + .map(|item| item.to_string()) + .collect::>() + .join(","); + serializer.serialize_str(&format!("[{pin_status}]")) +} + +#[cfg(test)] +mod tests { + use super::*; + + serialize_url_test!( + test_serializes_pin_remote_rm, + PinRemoteRm { + service: Some("Pinata"), + name: None, + cid: Some((&vec!["bafybeiaq3hspbuvhvg7nlxjjvsnzit6m6hevrjwedoj4jbx6uycgkkexni"] as &[&str]).into()), + status: Some(&vec![PinStatus::Pinned, PinStatus::Pinning]), + force: Some(true) + }, + "service=Pinata&cid=bafybeiaq3hspbuvhvg7nlxjjvsnzit6m6hevrjwedoj4jbx6uycgkkexni&status=%5Bpinned%2Cpinning%5D&force=true" + ); + + serialize_url_test!( + test_serializes_pin_remote_rm_multi_cid, + PinRemoteRm { + service: Some("Pinata"), + name: None, + cid: Some((&vec!["bafybeiaq3hspbuvhvg7nlxjjvsnzit6m6hevrjwedoj4jbx6uycgkkexni", "QmfWC6JwVxmjVQfPpSiTsxFaSBdPTtFCd1B4aqMqRgaeMU"] as &[&str]).into()), + status:None, + force: None + }, + "service=Pinata&cid=bafybeiaq3hspbuvhvg7nlxjjvsnzit6m6hevrjwedoj4jbx6uycgkkexni%2CQmfWC6JwVxmjVQfPpSiTsxFaSBdPTtFCd1B4aqMqRgaeMU" + ); +} diff --git a/ipfs-api-prelude/src/response/pin.rs b/ipfs-api-prelude/src/response/pin.rs index 3a303ba..ad756b5 100644 --- a/ipfs-api-prelude/src/response/pin.rs +++ b/ipfs-api-prelude/src/response/pin.rs @@ -40,6 +40,22 @@ pub struct PinRmResponse { pub pins: Vec, } +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct PinRemoteAddResponse { + pub cid: String, + pub name: String, + pub status: String, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct PinRemoteLsResponse { + pub cid: String, + pub name: String, + pub status: String, +} + #[cfg(test)] mod tests { deserialize_test!(v0_pin_ls_0, PinLsResponse);