1use std::fmt::{Debug, Formatter};
10use std::path::{Path, PathBuf};
11use std::time::SystemTime;
12
13use async_trait::async_trait;
14use bytes::{Buf, Bytes};
15use futures::TryStreamExt;
16use ipfs_api_backend_hyper::request::{FilesLs, FilesRead, FilesWrite};
17use ipfs_api_backend_hyper::response::{FilesEntry, FilesStatResponse};
18use ipfs_api_backend_hyper::{Error, IpfsApi, IpfsClient, TryFromUri};
19
20#[async_trait]
22pub trait PeerApi: Send + Sync + Debug {
23 async fn cp(&self, path: &str, dest: &str) -> Result<(), Error>;
25
26 async fn flush(&self, path: &str) -> Result<(), Error>;
28
29 async fn ls(&self, path: &str) -> Result<Vec<PeerEntry>, Error>;
31
32 async fn mkdir(&self, path: &str) -> Result<PeerEntry, Error>;
34
35 async fn mv(&self, path: &str, dest: &str) -> Result<(), Error>;
37
38 async fn read(&self, path: &str, offset: usize, count: usize) -> Result<Bytes, Error>;
40
41 async fn rm(&self, path: &str) -> Result<(), Error>;
43
44 async fn stat(&self, path: &str) -> Result<PeerEntry, Error>;
46
47 async fn write(
49 &self,
50 path: &str,
51 offset: usize,
52 truncate: bool,
53 data: Bytes,
54 ) -> Result<(), Error>;
55}
56
57#[derive(Debug, Clone)]
59pub struct PeerEntry {
60 pub path: String,
62
63 pub crtime: SystemTime,
65
66 pub mtime: SystemTime,
68
69 pub is_dir: bool,
71
72 pub size: usize,
74}
75
76impl PeerEntry {
77 fn new_dir(path: &str) -> Self {
78 Self {
79 path: path.to_string(),
80 crtime: SystemTime::now(),
81 mtime: SystemTime::now(),
82 is_dir: true,
83 size: 0,
84 }
85 }
86
87 fn from_stat(path: &str, stat: &FilesStatResponse) -> Self {
88 Self {
89 path: path.to_string(),
90 crtime: SystemTime::now(),
91 mtime: SystemTime::now(),
92 is_dir: stat.typ == "directory",
93 size: stat.size as usize,
94 }
95 }
96
97 fn from_entry(path: &str, entry: &FilesEntry) -> Self {
98 Self {
99 path: path.to_string(),
100 crtime: SystemTime::now(),
101 mtime: SystemTime::now(),
102 is_dir: entry.typ == 1,
103 size: entry.size as usize,
104 }
105 }
106}
107
108pub struct BaseApi {
115 ipfs: IpfsClient,
116}
117
118impl BaseApi {
119 pub fn new() -> Box<BaseApi> {
121 BaseApi::from_ipfs_client(IpfsClient::default())
122 }
123
124 pub fn from_uri(uri: &str) -> Box<BaseApi> {
126 BaseApi::from_ipfs_client(IpfsClient::from_str(uri).unwrap())
127 }
128
129 pub fn from_ipfs_client(ipfs: IpfsClient) -> Box<BaseApi> {
131 Box::new(BaseApi { ipfs })
132 }
133}
134
135impl Debug for BaseApi {
136 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
137 write!(f, "BasicApi")
138 }
139}
140
141#[async_trait]
142impl PeerApi for BaseApi {
143 async fn cp(&self, path: &str, dest: &str) -> Result<(), Error> {
144 let path = normalize_path(path);
145 let dest = normalize_path(dest);
146 self.ipfs.files_cp(&path, &dest).await
147 }
148
149 async fn flush(&self, path: &str) -> Result<(), Error> {
150 let path = normalize_path(path);
151 self.ipfs.files_flush(Some(&path)).await
152 }
153
154 async fn ls(&self, path: &str) -> Result<Vec<PeerEntry>, Error> {
155 let path = normalize_path(path);
156 let req = FilesLs {
157 path: Some(&path),
158 long: Some(true),
159 ..Default::default()
160 };
161 let res = self.ipfs.files_ls_with_options(req).await?;
162 Ok(res
163 .entries
164 .iter()
165 .map(|e| PeerEntry::from_entry(&concat_path(&path, &e.name), e))
166 .collect())
167 }
168
169 async fn mkdir(&self, path: &str) -> Result<PeerEntry, Error> {
170 let path = normalize_path(path);
171 self.ipfs.files_mkdir(&path, false).await?;
172 Ok(PeerEntry::new_dir(&path))
173 }
174
175 async fn mv(&self, path: &str, dest: &str) -> Result<(), Error> {
176 let path = normalize_path(path);
177 let dest = normalize_path(dest);
178 self.ipfs.files_mv(&path, &dest).await
179 }
180
181 async fn read(&self, path: &str, offset: usize, count: usize) -> Result<Bytes, Error> {
182 let path = normalize_path(path);
183 let req = FilesRead {
184 path: &path,
185 offset: Some(offset as i64),
186 count: Some(count as i64),
187 };
188 let data = self
189 .ipfs
190 .files_read_with_options(req)
191 .map_ok(|chunk| chunk.to_vec())
192 .try_concat()
193 .await?;
194 Ok(Bytes::copy_from_slice(&data))
195 }
196
197 async fn rm(&self, path: &str) -> Result<(), Error> {
198 let path = normalize_path(path);
199 self.ipfs.files_rm(&path, true).await
200 }
201
202 async fn stat(&self, path: &str) -> Result<PeerEntry, Error> {
203 let path = normalize_path(path);
204 let stat = self.ipfs.files_stat(&path).await?;
205 Ok(PeerEntry::from_stat(&path, &stat))
206 }
207
208 async fn write(
209 &self,
210 path: &str,
211 offset: usize,
212 truncate: bool,
213 data: Bytes,
214 ) -> Result<(), Error> {
215 let path = normalize_path(path);
216 let req = FilesWrite {
217 path: &path,
218 offset: Some(offset as i64),
219 create: Some(true),
220 truncate: Some(truncate),
221 flush: Some(false),
222 ..Default::default()
223 };
224 self.ipfs.files_write_with_options(req, data.reader()).await
225 }
226}
227
228#[inline]
229fn concat_path(p1: &str, p2: &str) -> String {
230 pb_to_string(Path::new(p1).join(Path::new(p2)))
231}
232
233#[inline]
234fn pb_to_string(path: PathBuf) -> String {
235 path.into_os_string().into_string().unwrap()
236}
237
238#[inline]
239fn normalize_path(path: &str) -> String {
240 let mut path = path.to_string();
241 if path.len() > 1 && path.ends_with('/') {
242 path.pop();
243 }
244 path
245}