1#[cfg(not(target_arch = "wasm32"))]
7use std::path::PathBuf;
8
9use anyhow::Error;
10use bytes::Bytes;
11use futures::{
12 StreamExt,
13 stream::{self, BoxStream},
14};
15use ipld_core::cid::Cid;
16use ll::file::FileReadFailed;
17pub use rust_unixfs as ll;
18
19mod add;
20mod cat;
21mod get;
22mod ls;
23mod prefetch;
24pub use add::UnixfsAdd;
25pub use cat::{StartingPoint, UnixfsCat};
26pub use get::UnixfsGet;
27pub use ls::{Entry, UnixfsLs};
28
29use crate::{
30 Ipfs, IpfsPath,
31 dag::{ResolveError, UnexpectedResolved},
32};
33
34pub struct IpfsUnixfs {
35 ipfs: Ipfs,
36}
37
38pub enum AddOpt {
39 #[cfg(not(target_arch = "wasm32"))]
40 Path(PathBuf),
41 Stream(BoxStream<'static, std::io::Result<Bytes>>),
42 StreamWithName(String, BoxStream<'static, std::io::Result<Bytes>>),
43}
44
45#[cfg(not(target_arch = "wasm32"))]
46impl From<&str> for AddOpt {
47 fn from(value: &str) -> Self {
48 AddOpt::Path(PathBuf::from(value))
49 }
50}
51
52#[cfg(not(target_arch = "wasm32"))]
53impl From<String> for AddOpt {
54 fn from(value: String) -> Self {
55 AddOpt::Path(PathBuf::from(value))
56 }
57}
58
59#[cfg(not(target_arch = "wasm32"))]
60impl From<&std::path::Path> for AddOpt {
61 fn from(path: &std::path::Path) -> Self {
62 AddOpt::Path(path.to_path_buf())
63 }
64}
65
66#[cfg(not(target_arch = "wasm32"))]
67impl From<PathBuf> for AddOpt {
68 fn from(path: PathBuf) -> Self {
69 AddOpt::Path(path)
70 }
71}
72
73impl From<Vec<u8>> for AddOpt {
74 fn from(bytes: Vec<u8>) -> Self {
75 let bytes: Bytes = bytes.into();
76 Self::from(bytes)
77 }
78}
79
80impl From<&'static [u8]> for AddOpt {
81 fn from(bytes: &'static [u8]) -> Self {
82 let bytes: Bytes = bytes.into();
83 Self::from(bytes)
84 }
85}
86
87impl From<(String, Vec<u8>)> for AddOpt {
88 fn from((name, bytes): (String, Vec<u8>)) -> Self {
89 let bytes: Bytes = bytes.into();
90 Self::from((name, bytes))
91 }
92}
93
94impl From<(String, &'static [u8])> for AddOpt {
95 fn from((name, bytes): (String, &'static [u8])) -> Self {
96 let bytes: Bytes = bytes.into();
97 Self::from((name, bytes))
98 }
99}
100
101impl From<Bytes> for AddOpt {
102 fn from(bytes: Bytes) -> Self {
103 let stream = stream::once(async { Ok::<_, std::io::Error>(bytes) }).boxed();
104 AddOpt::Stream(stream)
105 }
106}
107
108impl From<(String, Bytes)> for AddOpt {
109 fn from((name, bytes): (String, Bytes)) -> Self {
110 let stream = stream::once(async { Ok::<_, std::io::Error>(bytes) }).boxed();
111 Self::from((name, stream))
112 }
113}
114
115impl From<BoxStream<'static, std::io::Result<Bytes>>> for AddOpt {
116 fn from(stream: BoxStream<'static, std::io::Result<Bytes>>) -> Self {
117 AddOpt::Stream(stream)
118 }
119}
120
121impl From<(String, BoxStream<'static, std::io::Result<Bytes>>)> for AddOpt {
122 fn from((name, stream): (String, BoxStream<'static, std::io::Result<Bytes>>)) -> Self {
123 AddOpt::StreamWithName(name, stream)
124 }
125}
126
127impl From<BoxStream<'static, std::io::Result<Vec<u8>>>> for AddOpt {
128 fn from(stream: BoxStream<'static, std::io::Result<Vec<u8>>>) -> Self {
129 AddOpt::Stream(stream.map(|result| result.map(|data| data.into())).boxed())
130 }
131}
132
133impl From<(String, BoxStream<'static, std::io::Result<Vec<u8>>>)> for AddOpt {
134 fn from((name, stream): (String, BoxStream<'static, std::io::Result<Vec<u8>>>)) -> Self {
135 let stream = stream.map(|result| result.map(|data| data.into())).boxed();
136 AddOpt::StreamWithName(name, stream)
137 }
138}
139
140impl IpfsUnixfs {
141 pub fn new(ipfs: Ipfs) -> Self {
142 Self { ipfs }
143 }
144
145 pub fn cat(&self, starting_point: impl Into<StartingPoint>) -> UnixfsCat {
149 UnixfsCat::with_ipfs(&self.ipfs, starting_point)
150 }
151
152 pub fn add(&self, item: impl Into<AddOpt>) -> UnixfsAdd {
154 let item = item.into();
155 match item {
156 #[cfg(not(target_arch = "wasm32"))]
157 AddOpt::Path(path) => UnixfsAdd::with_ipfs(&self.ipfs, path),
158 AddOpt::Stream(stream) => UnixfsAdd::with_ipfs(
159 &self.ipfs,
160 add::AddOpt::Stream {
161 name: None,
162 total: None,
163 stream,
164 },
165 ),
166 AddOpt::StreamWithName(name, stream) => UnixfsAdd::with_ipfs(
167 &self.ipfs,
168 add::AddOpt::Stream {
169 name: Some(name),
170 total: None,
171 stream,
172 },
173 ),
174 }
175 }
176
177 pub fn get(&self, path: impl Into<IpfsPath>, dest: impl AsRef<std::path::Path>) -> UnixfsGet {
181 UnixfsGet::with_ipfs(&self.ipfs, path, dest)
182 }
183
184 pub fn ls(&self, path: impl Into<IpfsPath>) -> UnixfsLs {
186 UnixfsLs::with_ipfs(&self.ipfs, path)
187 }
188}
189
190#[derive(Debug)]
191pub enum UnixfsStatus {
192 ProgressStatus {
193 written: usize,
194 total_size: Option<usize>,
195 },
196 CompletedStatus {
197 path: IpfsPath,
198 written: usize,
199 total_size: Option<usize>,
200 },
201 FailedStatus {
202 written: usize,
203 total_size: Option<usize>,
204 error: Error,
205 },
206}
207
208#[derive(Debug, thiserror::Error)]
210pub enum TraversalFailed {
211 #[error("path resolving failed")]
213 Resolving(#[source] ResolveError),
214
215 #[error("path resolved to unexpected")]
218 Path(#[source] UnexpectedResolved),
219
220 #[error("loading of {} failed", .0)]
222 Loading(Cid, #[source] Error),
223
224 #[error("data exceeded max length")]
225 MaxLengthExceeded { size: usize, length: usize },
226 #[error("Timeout while resolving {path}")]
227 Timeout { path: IpfsPath },
228
229 #[error("walk failed on {}", .0)]
231 Walking(Cid, #[source] FileReadFailed),
232
233 #[error(transparent)]
234 Io(std::io::Error),
235}
236
237#[cfg(test)]
238mod tests {
239 #[test]
240 fn test_file_cid() {
241 let content = "\u{8}\u{2}\u{12}\u{12}Here is some data\n\u{18}\u{12}";
246
247 let mut adder = rust_unixfs::file::adder::FileAdder::builder()
249 .with_cid_version(ipld_core::cid::Version::V0)
250 .build();
251 let (mut blocks, consumed) = adder.push(content.as_bytes());
252 assert_eq!(consumed, content.len(), "should had consumed all content");
253 assert_eq!(
254 blocks.next(),
255 None,
256 "should not had produced any blocks yet"
257 );
258
259 let mut blocks = adder.finish();
260
261 let (cid, _block) = blocks.next().unwrap();
262 assert_eq!(blocks.next(), None, "should had been the last");
263
264 assert_eq!(
265 "QmQZE72h2Vdm3F5gWr9RLuzSw3rUJEkKedWEa8t8XVygT5",
266 cid.to_string(),
267 "matches cid from go-ipfs 0.6.0"
268 );
269 }
270}