1#![doc = include_str!("../README.md")]
19#![cfg_attr(docsrs, feature(doc_cfg))]
20#![cfg_attr(docsrs, doc(auto_cfg))]
21#![deny(missing_docs)]
22use std::collections::HashSet;
23use std::sync::Arc;
24use std::vec::IntoIter;
25
26use opendal_core::raw::*;
27use opendal_core::*;
28
29#[derive(Clone, Debug, Default)]
56pub struct ImmutableIndexLayer {
57 vec: Vec<String>,
58}
59
60impl ImmutableIndexLayer {
61 pub fn new() -> Self {
63 Self::default()
64 }
65}
66
67impl ImmutableIndexLayer {
68 pub fn insert(&mut self, key: String) {
70 self.vec.push(key);
71 }
72
73 pub fn extend_iter<I>(&mut self, iter: I)
75 where
76 I: IntoIterator<Item = String>,
77 {
78 self.vec.extend(iter);
79 }
80}
81
82impl Layer for ImmutableIndexLayer {
83 fn apply_service(&self, inner: Servicer) -> Servicer {
84 Arc::new(self.layer(inner))
85 }
86}
87
88impl ImmutableIndexLayer {
89 fn layer(&self, inner: Servicer) -> ImmutableIndexService {
90 ImmutableIndexService {
91 inner,
92 vec: self.vec.clone(),
93 }
94 }
95}
96
97#[doc(hidden)]
98#[derive(Debug)]
99pub struct ImmutableIndexService {
100 inner: Servicer,
101 vec: Vec<String>,
102}
103
104impl ImmutableIndexService {
105 fn children_flat(&self, path: &str) -> Vec<String> {
106 self.vec
107 .iter()
108 .filter(|v| v.starts_with(path) && v.as_str() != path)
109 .cloned()
110 .collect()
111 }
112
113 fn children_hierarchy(&self, path: &str) -> Vec<String> {
114 let mut res = HashSet::new();
115
116 for i in self.vec.iter() {
117 if !i.starts_with(path) {
119 continue;
120 }
121
122 if i == path {
124 continue;
125 }
126
127 match i[path.len()..].find('/') {
128 None => {
130 res.insert(i.to_string());
131 }
132 Some(idx) => {
133 let dir_idx = idx + 1 + path.len();
135
136 if dir_idx == i.len() {
137 res.insert(i.to_string());
139 } else {
140 res.insert(i[..dir_idx].to_string());
143 }
144 }
145 }
146 }
147
148 res.into_iter().collect()
149 }
150}
151
152impl Service for ImmutableIndexService {
153 type Reader = oio::Reader;
154 type Writer = oio::Writer;
155 type Lister = ImmutableDir;
156 type Deleter = oio::Deleter;
157 type Copier = oio::Copier;
158 type Composer = oio::Composer;
159
160 fn info(&self) -> ServiceInfo {
161 self.inner.info()
162 }
163
164 fn capability(&self) -> Capability {
165 let mut capability = self.inner.capability();
166 capability.list = true;
167 capability.list_with_recursive = true;
168 capability
169 }
170
171 fn compose(&self, ctx: &OperationContext, to: &str, args: OpCompose) -> Result<Self::Composer> {
172 self.inner.compose(ctx, to, args)
173 }
174
175 async fn create_dir(
176 &self,
177 ctx: &OperationContext,
178 path: &str,
179 args: OpCreateDir,
180 ) -> Result<RpCreateDir> {
181 self.inner.create_dir(ctx, path, args).await
182 }
183
184 async fn stat(&self, ctx: &OperationContext, path: &str, args: OpStat) -> Result<RpStat> {
185 self.inner.stat(ctx, path, args).await
186 }
187
188 fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
189 self.inner.read(ctx, path, args)
190 }
191
192 fn write(&self, ctx: &OperationContext, path: &str, args: OpWrite) -> Result<Self::Writer> {
193 self.inner.write(ctx, path, args)
194 }
195
196 fn copy(
197 &self,
198 ctx: &OperationContext,
199 from: &str,
200 to: &str,
201 args: OpCopy,
202 ) -> Result<Self::Copier> {
203 self.inner.copy(ctx, from, to, args)
204 }
205
206 fn list(&self, _ctx: &OperationContext, path: &str, args: OpList) -> Result<Self::Lister> {
207 let mut path = path;
208 if path == "/" {
209 path = ""
210 }
211
212 let idx = if args.recursive() {
213 self.children_flat(path)
214 } else {
215 self.children_hierarchy(path)
216 };
217
218 Ok(ImmutableDir::new(idx))
219 }
220
221 fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
222 self.inner.delete(ctx)
223 }
224
225 async fn rename(
226 &self,
227 ctx: &OperationContext,
228 from: &str,
229 to: &str,
230 args: OpRename,
231 ) -> Result<RpRename> {
232 self.inner.rename(ctx, from, to, args).await
233 }
234
235 async fn restore(
236 &self,
237 ctx: &OperationContext,
238 path: &str,
239 args: OpRestore,
240 ) -> Result<RpRestore> {
241 self.inner.restore(ctx, path, args).await
242 }
243
244 async fn presign(
245 &self,
246 ctx: &OperationContext,
247 path: &str,
248 args: OpPresign,
249 ) -> Result<RpPresign> {
250 self.inner.presign(ctx, path, args).await
251 }
252}
253
254#[doc(hidden)]
255pub struct ImmutableDir {
256 idx: IntoIter<String>,
257}
258
259impl ImmutableDir {
260 fn new(idx: Vec<String>) -> Self {
261 Self {
262 idx: idx.into_iter(),
263 }
264 }
265
266 fn inner_next(&mut self) -> Option<oio::Entry> {
267 self.idx.next().map(|v| {
268 let metadata = if v.ends_with('/') {
269 MetadataBuilder::dir()
270 } else {
271 MetadataBuilder::unknown()
272 };
273 oio::Entry::with(v, metadata.build())
274 })
275 }
276}
277
278impl oio::List for ImmutableDir {
279 async fn next(&mut self) -> Result<Option<oio::Entry>> {
280 Ok(self.inner_next())
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use std::collections::HashMap;
287 use std::sync::Arc;
288
289 use super::*;
290 use futures::TryStreamExt;
291 use log::debug;
292 use logforth::append::Testing;
293 use logforth::filter::rustlog::RustLogFilterBuilder;
294 use logforth::layout::TextLayout;
295
296 #[derive(Debug)]
297 struct MockService;
298
299 impl Service for MockService {
300 type Reader = ();
301 type Writer = ();
302 type Lister = ();
303 type Deleter = ();
304 type Copier = ();
305 type Composer = ();
306
307 fn info(&self) -> ServiceInfo {
308 ServiceInfo::with_scheme("mock")
309 }
310
311 fn capability(&self) -> Capability {
312 Capability {
313 list: true,
314 list_with_recursive: true,
315 ..Default::default()
316 }
317 }
318
319 async fn create_dir(
320 &self,
321 _: &OperationContext,
322 _: &str,
323 _: OpCreateDir,
324 ) -> Result<RpCreateDir> {
325 Err(Error::new(
326 ErrorKind::Unsupported,
327 "operation is not supported",
328 ))
329 }
330
331 async fn stat(&self, _: &OperationContext, _: &str, _: OpStat) -> Result<RpStat> {
332 Err(Error::new(
333 ErrorKind::Unsupported,
334 "operation is not supported",
335 ))
336 }
337
338 fn read(&self, _ctx: &OperationContext, _: &str, _: OpRead) -> Result<Self::Reader> {
339 Err(Error::new(
340 ErrorKind::Unsupported,
341 "operation is not supported",
342 ))
343 }
344
345 fn write(&self, _ctx: &OperationContext, _: &str, _: OpWrite) -> Result<Self::Writer> {
346 Err(Error::new(
347 ErrorKind::Unsupported,
348 "operation is not supported",
349 ))
350 }
351
352 fn delete(&self, _ctx: &OperationContext) -> Result<Self::Deleter> {
353 Err(Error::new(
354 ErrorKind::Unsupported,
355 "operation is not supported",
356 ))
357 }
358
359 fn list(&self, _ctx: &OperationContext, _: &str, _: OpList) -> Result<Self::Lister> {
360 Err(Error::new(
361 ErrorKind::Unsupported,
362 "operation is not supported",
363 ))
364 }
365
366 fn copy(&self, _: &OperationContext, _: &str, _: &str, _: OpCopy) -> Result<Self::Copier> {
367 Err(Error::new(
368 ErrorKind::Unsupported,
369 "operation is not supported",
370 ))
371 }
372
373 async fn rename(
374 &self,
375 _: &OperationContext,
376 _: &str,
377 _: &str,
378 _: OpRename,
379 ) -> Result<RpRename> {
380 Err(Error::new(
381 ErrorKind::Unsupported,
382 "operation is not supported",
383 ))
384 }
385
386 async fn presign(&self, _: &OperationContext, _: &str, _: OpPresign) -> Result<RpPresign> {
387 Err(Error::new(
388 ErrorKind::Unsupported,
389 "operation is not supported",
390 ))
391 }
392 }
393
394 fn build_operator(layer: ImmutableIndexLayer) -> Operator {
395 Operator::from_parts(OperationContext::default(), Arc::new(MockService)).layer(layer)
396 }
397
398 fn setup() {
399 let _ = logforth::starter_log::builder()
400 .dispatch(|d| {
401 d.filter(RustLogFilterBuilder::from_default_env().build())
402 .append(Testing::default().with_layout(TextLayout::default()))
403 })
404 .try_apply();
405 }
406
407 #[tokio::test]
408 async fn test_list() -> Result<()> {
409 setup();
410
411 let mut iil = ImmutableIndexLayer::default();
412 for i in ["file", "dir/", "dir/file", "dir_without_prefix/file"] {
413 iil.insert(i.to_string())
414 }
415
416 let op = build_operator(iil);
417
418 let mut map = HashMap::new();
419 let mut set = HashSet::new();
420 let mut ds = op.lister("").await?;
421 while let Some(entry) = ds.try_next().await? {
422 debug!("got entry: {}", entry.path());
423 assert!(
424 set.insert(entry.path().to_string()),
425 "duplicated value: {}",
426 entry.path()
427 );
428 map.insert(entry.path().to_string(), entry.metadata().mode());
429 }
430
431 assert_eq!(map["file"], EntryMode::Unknown);
432 assert_eq!(map["dir/"], EntryMode::DIR);
433 assert_eq!(map["dir_without_prefix/"], EntryMode::DIR);
434 Ok(())
435 }
436
437 #[tokio::test]
438 async fn test_scan() -> Result<()> {
439 setup();
440
441 let mut iil = ImmutableIndexLayer::default();
442 for i in ["file", "dir/", "dir/file", "dir_without_prefix/file"] {
443 iil.insert(i.to_string())
444 }
445
446 let op = build_operator(iil);
447
448 let mut ds = op.lister_with("/").recursive(true).await?;
449 let mut set = HashSet::new();
450 let mut map = HashMap::new();
451 while let Some(entry) = ds.try_next().await? {
452 debug!("got entry: {}", entry.path());
453 assert!(
454 set.insert(entry.path().to_string()),
455 "duplicated value: {}",
456 entry.path()
457 );
458 map.insert(entry.path().to_string(), entry.metadata().mode());
459 }
460
461 debug!("current files: {map:?}");
462
463 assert_eq!(map["file"], EntryMode::Unknown);
464 assert_eq!(map["dir/"], EntryMode::DIR);
465 assert_eq!(map["dir_without_prefix/file"], EntryMode::Unknown);
466 Ok(())
467 }
468
469 #[tokio::test]
470 async fn test_list_dir() -> Result<()> {
471 setup();
472
473 let mut iil = ImmutableIndexLayer::default();
474 for i in [
475 "dataset/stateful/ontime_2007_200.csv",
476 "dataset/stateful/ontime_2008_200.csv",
477 "dataset/stateful/ontime_2009_200.csv",
478 ] {
479 iil.insert(i.to_string())
480 }
481
482 let op = build_operator(iil);
483
484 let mut map = HashMap::new();
486 let mut set = HashSet::new();
487 let mut ds = op.lister("/").await?;
488 while let Some(entry) = ds.try_next().await? {
489 assert!(
490 set.insert(entry.path().to_string()),
491 "duplicated value: {}",
492 entry.path()
493 );
494 map.insert(entry.path().to_string(), entry.metadata().mode());
495 }
496
497 assert_eq!(map.len(), 1);
498 assert_eq!(map["dataset/"], EntryMode::DIR);
499
500 let mut map = HashMap::new();
502 let mut set = HashSet::new();
503 let mut ds = op.lister("dataset/stateful/").await?;
504 while let Some(entry) = ds.try_next().await? {
505 assert!(
506 set.insert(entry.path().to_string()),
507 "duplicated value: {}",
508 entry.path()
509 );
510 map.insert(entry.path().to_string(), entry.metadata().mode());
511 }
512
513 assert_eq!(
514 map["dataset/stateful/ontime_2007_200.csv"],
515 EntryMode::Unknown
516 );
517 assert_eq!(
518 map["dataset/stateful/ontime_2008_200.csv"],
519 EntryMode::Unknown
520 );
521 assert_eq!(
522 map["dataset/stateful/ontime_2009_200.csv"],
523 EntryMode::Unknown
524 );
525 Ok(())
526 }
527
528 #[tokio::test]
529 async fn test_walk_top_down_dir() -> Result<()> {
530 setup();
531
532 let mut iil = ImmutableIndexLayer::default();
533 for i in [
534 "dataset/stateful/ontime_2007_200.csv",
535 "dataset/stateful/ontime_2008_200.csv",
536 "dataset/stateful/ontime_2009_200.csv",
537 ] {
538 iil.insert(i.to_string())
539 }
540
541 let op = build_operator(iil);
542
543 let mut ds = op.lister_with("/").recursive(true).await?;
544
545 let mut map = HashMap::new();
546 let mut set = HashSet::new();
547 while let Some(entry) = ds.try_next().await? {
548 assert!(
549 set.insert(entry.path().to_string()),
550 "duplicated value: {}",
551 entry.path()
552 );
553 map.insert(entry.path().to_string(), entry.metadata().mode());
554 }
555
556 debug!("current files: {map:?}");
557
558 assert_eq!(
559 map["dataset/stateful/ontime_2007_200.csv"],
560 EntryMode::Unknown
561 );
562 assert_eq!(
563 map["dataset/stateful/ontime_2008_200.csv"],
564 EntryMode::Unknown
565 );
566 assert_eq!(
567 map["dataset/stateful/ontime_2009_200.csv"],
568 EntryMode::Unknown
569 );
570 Ok(())
571 }
572}