1use anyhow::{anyhow, Result};
3use banyan::store::{BlockWriter, ReadOnlyStore};
4use futures::prelude::*;
5use libipld::Cid;
6use serde::{de::IgnoredAny, de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
7use std::{convert::TryInto, fmt, str::FromStr};
8
9use crate::tags::Sha256Digest;
10
11pub fn block_get(key: &Cid) -> Result<Box<[u8]>> {
12 let url = reqwest::Url::parse_with_params(
13 "http://localhost:5001/api/v0/block/get",
14 &[("arg", format!("{}", key))],
15 )?;
16 let client = reqwest::blocking::Client::new();
17 let data: Vec<u8> = client.post(url).send()?.bytes()?.to_vec();
18 Ok(data.into())
19}
20
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct Base64Blob(pub Vec<u8>);
23
24impl Serialize for Base64Blob {
25 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
26 where
27 S: Serializer,
28 {
29 serializer.serialize_str(base64::encode(&self.0).as_ref())
30 }
31}
32
33impl<'de> Deserialize<'de> for Base64Blob {
34 fn deserialize<D>(deserializer: D) -> Result<Base64Blob, D::Error>
35 where
36 D: Deserializer<'de>,
37 {
38 struct MyVisitor();
39
40 impl<'de> Visitor<'de> for MyVisitor {
41 type Value = Base64Blob;
42
43 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
44 formatter.write_str("string")
45 }
46
47 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
48 where
49 E: serde::de::Error,
50 {
51 base64::decode(v).map(Base64Blob).map_err(|err| {
52 serde::de::Error::custom(format!("Error decoding base64 string: {}", err))
53 })
54 }
55 }
56
57 deserializer.deserialize_any(MyVisitor())
58 }
59}
60
61#[derive(Debug, Clone, Deserialize)]
62struct IpfsPubsubEventIo {
63 data: Base64Blob,
66 #[serde(rename = "from")]
67 _from: IgnoredAny,
68 #[serde(rename = "seqno")]
69 _seqno: IgnoredAny,
70 #[serde(rename = "topicIDs")]
71 _topic_ids: IgnoredAny,
72}
73
74pub fn pubsub_sub(topic: &str) -> Result<impl Stream<Item = reqwest::Result<Vec<u8>>>> {
75 let url = reqwest::Url::parse_with_params(
76 "http://localhost:5001/api/v0/pubsub/sub",
77 &[("arg", topic)],
78 )?;
79 let client = reqwest::Client::new();
80 let data = client
81 .post(url)
82 .send()
83 .map(|response| match response {
84 Ok(response) => response
85 .bytes_stream()
86 .map_ok(|bytes| {
87 let data: IpfsPubsubEventIo = serde_json::from_slice(&bytes).unwrap();
88 data.data.0
89 })
90 .left_stream(),
91 Err(cause) => futures::stream::once(future::err(cause)).right_stream(),
92 })
93 .flatten_stream();
94 Ok(data)
95}
96
97pub async fn pubsub_pub(topic: &str, data: &[u8]) -> Result<()> {
98 use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
99 let topic = percent_encode(topic.as_bytes(), NON_ALPHANUMERIC).to_string();
100 let data = percent_encode(data, NON_ALPHANUMERIC).to_string();
101 let url = reqwest::Url::parse(&format!(
102 "http://localhost:5001/api/v0/pubsub/pub?arg={}&arg={}",
103 topic, data
104 ))?;
105 let client = reqwest::Client::new();
106 let _ = client.post(url).send().await?.bytes().await?.to_vec();
107 Ok(())
108}
109
110fn format_codec(codec: u64) -> Result<&'static str> {
111 match codec {
112 0x71 => Ok("cbor"),
113 0x70 => Ok("protobuf"),
114 0x55 => Ok("raw"),
115 _ => Err(anyhow!("unsupported codec {}", codec)),
116 }
117}
118
119pub fn block_put(data: &[u8], codec: u64, pin: bool) -> Result<Cid> {
120 let url = reqwest::Url::parse_with_params(
121 "http://localhost:5001/api/v0/block/put",
122 &[("format", format_codec(codec)?), ("pin", &pin.to_string())],
123 )?;
124 let client = reqwest::blocking::Client::new();
125 let form = reqwest::blocking::multipart::Form::new().part(
126 "file",
127 reqwest::blocking::multipart::Part::bytes(data.to_vec()),
128 );
129 let res: IpfsBlockPutResponseIo = client.post(url).multipart(form).send()?.json()?;
130 let cid = Cid::from_str(&res.key)?;
131 Ok(cid)
132}
133
134#[derive(Clone)]
135pub struct IpfsStore;
136
137impl IpfsStore {
138 pub fn new() -> anyhow::Result<Self> {
139 Ok(Self)
140 }
141}
142
143impl ReadOnlyStore<Sha256Digest> for IpfsStore {
144 fn get(&self, link: &Sha256Digest) -> Result<Box<[u8]>> {
145 let cid: Cid = (*link).into();
146 std::thread::spawn(move || crate::ipfs::block_get(&cid))
147 .join()
148 .map_err(|_| anyhow!("join error!"))?
149 }
150}
151
152impl BlockWriter<Sha256Digest> for IpfsStore {
153 fn put(&mut self, data: Vec<u8>) -> Result<Sha256Digest> {
154 let cid = std::thread::spawn(move || crate::ipfs::block_put(&data, 0x71, false))
155 .join()
156 .map_err(|_| anyhow!("join error!"))??;
157 assert!(cid.hash().code() == 0x12);
158 assert!(cid.hash().digest().len() == 32);
159 cid.try_into()
160 }
161}
162
163#[derive(Deserialize)]
164struct IpfsBlockPutResponseIo {
165 #[serde(rename = "Key")]
166 key: String,
167}