opendal/raw/oio/list/flat_list.rs
1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements. See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership. The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License. You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied. See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use crate::raw::*;
19use crate::*;
20
21/// FlatLister will walk dir in bottom up way:
22///
23/// - List nested dir first
24/// - Go back into parent dirs one by one
25///
26/// Given the following file tree:
27///
28/// ```txt
29/// .
30/// ├── dir_x/
31/// │ ├── dir_y/
32/// │ │ ├── dir_z/
33/// │ │ └── file_c
34/// │ └── file_b
35/// └── file_a
36/// ```
37///
38/// ToFlatLister will output entries like:
39///
40/// ```txt
41/// dir_x/dir_y/dir_z/file_c
42/// dir_x/dir_y/dir_z/
43/// dir_x/dir_y/file_b
44/// dir_x/dir_y/
45/// dir_x/file_a
46/// dir_x/
47/// ```
48///
49/// # Note
50///
51/// There is no guarantee about the order between files and dirs at the same level.
52/// We only make sure the nested dirs will show up before parent dirs.
53///
54/// Especially, for storage services that can't return dirs first, ToFlatLister
55/// may output parent dirs' files before nested dirs, this is expected because files
56/// always output directly while listing.
57pub struct FlatLister<A: Access, L> {
58 acc: A,
59
60 next_dir: Option<oio::Entry>,
61 active_lister: Vec<(Option<oio::Entry>, L)>,
62}
63
64/// # Safety
65///
66/// wasm32 is a special target that we only have one event-loop for this FlatLister.
67unsafe impl<A: Access, L> Send for FlatLister<A, L> {}
68/// # Safety
69///
70/// We will only take `&mut Self` reference for FsLister.
71unsafe impl<A: Access, L> Sync for FlatLister<A, L> {}
72
73impl<A, L> FlatLister<A, L>
74where
75 A: Access,
76{
77 /// Create a new flat lister
78 pub fn new(acc: A, path: &str) -> FlatLister<A, L> {
79 FlatLister {
80 acc,
81 next_dir: Some(oio::Entry::new(path, Metadata::new(EntryMode::DIR))),
82 active_lister: vec![],
83 }
84 }
85}
86
87impl<A, L> oio::List for FlatLister<A, L>
88where
89 A: Access<Lister = L>,
90 L: oio::List,
91{
92 async fn next(&mut self) -> Result<Option<oio::Entry>> {
93 loop {
94 if let Some(de) = self.next_dir.take() {
95 let (_, mut l) = self.acc.list(de.path(), OpList::new()).await?;
96 if let Some(v) = l.next().await? {
97 self.active_lister.push((Some(de.clone()), l));
98
99 if v.mode().is_dir() {
100 // should not loop itself again
101 if v.path() != de.path() {
102 self.next_dir = Some(v);
103 continue;
104 }
105 } else {
106 return Ok(Some(v));
107 }
108 }
109 }
110
111 let (de, lister) = match self.active_lister.last_mut() {
112 Some((de, lister)) => (de, lister),
113 None => return Ok(None),
114 };
115
116 match lister.next().await? {
117 Some(v) if v.mode().is_dir() => {
118 // should not loop itself again
119 if v.path() != de.as_ref().expect("de should not be none here").path() {
120 self.next_dir = Some(v);
121 continue;
122 }
123 }
124 Some(v) => return Ok(Some(v)),
125 None => match de.take() {
126 Some(de) => {
127 return Ok(Some(de));
128 }
129 None => {
130 let _ = self.active_lister.pop();
131 continue;
132 }
133 },
134 }
135 }
136 }
137}