1use std::fmt::Debug;
19use std::sync::Arc;
20
21use http::StatusCode;
22use log::debug;
23use prost::Message;
24
25use super::reader::*;
26use crate::IPFS_SCHEME;
27use crate::config::IpfsConfig;
28use crate::core::IpfsCore;
29use crate::core::parse_error;
30use crate::ipld::PBNode;
31use opendal_core::raw::*;
32use opendal_core::*;
33
34#[doc = include_str!("docs.md")]
36#[derive(Default)]
37pub struct IpfsBuilder {
38 pub(super) config: IpfsConfig,
39}
40
41impl Debug for IpfsBuilder {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 f.debug_struct("IpfsBuilder")
44 .field("config", &self.config)
45 .finish()
46 }
47}
48
49impl IpfsBuilder {
50 pub fn root(mut self, root: &str) -> Self {
58 self.config.root = if root.is_empty() {
59 None
60 } else {
61 Some(root.to_string())
62 };
63
64 self
65 }
66
67 pub fn endpoint(mut self, endpoint: &str) -> Self {
79 if !endpoint.is_empty() {
80 self.config.endpoint = Some(endpoint.trim_end_matches('/').to_string());
82 }
83
84 self
85 }
86}
87
88impl Builder for IpfsBuilder {
89 type Config = IpfsConfig;
90
91 fn build(self) -> Result<impl Service> {
92 debug!("backend build started: {:?}", self);
93
94 let root = normalize_root(&self.config.root.unwrap_or_default());
95 if !root.starts_with("/ipfs/") && !root.starts_with("/ipns/") {
96 return Err(Error::new(
97 ErrorKind::ConfigInvalid,
98 "root must start with /ipfs/ or /ipns/",
99 )
100 .with_context("service", IPFS_SCHEME)
101 .with_context("root", &root));
102 }
103 debug!("backend use root {root}");
104
105 let endpoint = match &self.config.endpoint {
106 Some(endpoint) => Ok(endpoint.clone()),
107 None => Err(Error::new(ErrorKind::ConfigInvalid, "endpoint is empty")
108 .with_context("service", IPFS_SCHEME)
109 .with_context("root", &root)),
110 }?;
111 debug!("backend use endpoint {}", endpoint);
112
113 let info = ServiceInfo::new(IPFS_SCHEME, &root, "");
114 let capability = Capability {
115 stat: true,
116
117 read: true,
118 read_with_suffix: true,
119
120 list: true,
121
122 shared: true,
123
124 ..Default::default()
125 };
126
127 let accessor_info = info;
128 let core = Arc::new(IpfsCore {
129 info: accessor_info,
130 capability,
131 root,
132 endpoint,
133 });
134
135 Ok(IpfsBackend { core })
136 }
137}
138
139#[derive(Clone, Debug)]
141pub struct IpfsBackend {
142 pub(crate) core: Arc<IpfsCore>,
143}
144
145impl Service for IpfsBackend {
146 type Reader = oio::StreamReader<IpfsReader>;
147 type Writer = ();
148 type Lister = oio::PageLister<DirStream>;
149 type Deleter = ();
150 type Copier = ();
151
152 fn info(&self) -> ServiceInfo {
153 self.core.info.clone()
154 }
155
156 fn capability(&self) -> Capability {
157 self.core.capability
158 }
159
160 async fn create_dir(
161 &self,
162 _ctx: &OperationContext,
163 _path: &str,
164 _args: OpCreateDir,
165 ) -> Result<RpCreateDir> {
166 Err(Error::new(
167 ErrorKind::Unsupported,
168 "operation is not supported",
169 ))
170 }
171
172 async fn stat(&self, ctx: &OperationContext, path: &str, _: OpStat) -> Result<RpStat> {
173 let metadata = self.core.ipfs_stat(ctx, path).await?;
174 Ok(RpStat::new(metadata))
175 }
176
177 fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
178 let output: oio::StreamReader<IpfsReader> = {
179 Ok(oio::StreamReader::new(IpfsReader::new(
180 self.clone(),
181 ctx.clone(),
182 path,
183 args,
184 )))
185 }?;
186
187 Ok(output)
188 }
189
190 fn write(&self, _ctx: &OperationContext, _path: &str, _args: OpWrite) -> Result<Self::Writer> {
191 Err(Error::new(
192 ErrorKind::Unsupported,
193 "operation is not supported",
194 ))
195 }
196
197 fn delete(&self, _ctx: &OperationContext) -> Result<Self::Deleter> {
198 Err(Error::new(
199 ErrorKind::Unsupported,
200 "operation is not supported",
201 ))
202 }
203
204 fn list(&self, ctx: &OperationContext, path: &str, _: OpList) -> Result<Self::Lister> {
205 let output: oio::PageLister<DirStream> = {
206 let l = DirStream::new(self.core.clone(), ctx.clone(), path);
207 Ok(oio::PageLister::new(l))
208 }?;
209
210 Ok(output)
211 }
212
213 fn copy(
214 &self,
215 _ctx: &OperationContext,
216 _from: &str,
217 _to: &str,
218 _args: OpCopy,
219 _opts: OpCopier,
220 ) -> Result<Self::Copier> {
221 Err(Error::new(
222 ErrorKind::Unsupported,
223 "operation is not supported",
224 ))
225 }
226
227 async fn rename(
228 &self,
229 _ctx: &OperationContext,
230 _from: &str,
231 _to: &str,
232 _args: OpRename,
233 ) -> Result<RpRename> {
234 Err(Error::new(
235 ErrorKind::Unsupported,
236 "operation is not supported",
237 ))
238 }
239
240 async fn presign(
241 &self,
242 _ctx: &OperationContext,
243 _path: &str,
244 _args: OpPresign,
245 ) -> Result<RpPresign> {
246 Err(Error::new(
247 ErrorKind::Unsupported,
248 "operation is not supported",
249 ))
250 }
251}
252
253pub struct DirStream {
254 core: Arc<IpfsCore>,
255 ctx: OperationContext,
256 path: String,
257}
258
259impl DirStream {
260 fn new(core: Arc<IpfsCore>, ctx: OperationContext, path: &str) -> Self {
261 Self {
262 core,
263 ctx,
264 path: path.to_string(),
265 }
266 }
267}
268
269impl oio::PageList for DirStream {
270 async fn next_page(&self, ctx: &mut oio::PageContext) -> Result<()> {
271 let resp = self.core.ipfs_list(&self.ctx, &self.path).await?;
272
273 if resp.status() != StatusCode::OK {
274 return Err(parse_error(resp));
275 }
276
277 let bs = resp.into_body();
278 let pb_node = PBNode::decode(bs).map_err(|e| {
279 Error::new(ErrorKind::Unexpected, "deserialize protobuf from response").set_source(e)
280 })?;
281
282 let names = pb_node
283 .links
284 .into_iter()
285 .map(|v| v.name.unwrap())
286 .collect::<Vec<String>>();
287
288 for mut name in names {
289 let meta = self.core.ipfs_stat(&self.ctx, &name).await?;
290
291 if meta.mode().is_dir() {
292 name += "/";
293 }
294
295 ctx.entries.push_back(oio::Entry::new(&name, meta))
296 }
297
298 ctx.done = true;
299 Ok(())
300 }
301}