opendal/services/azfile/
lister.rs
1use std::sync::Arc;
19
20use bytes::Buf;
21use http::StatusCode;
22use quick_xml::de;
23use serde::Deserialize;
24
25use super::core::AzfileCore;
26use super::error::parse_error;
27use crate::raw::*;
28use crate::*;
29
30pub struct AzfileLister {
31 core: Arc<AzfileCore>,
32 path: String,
33 limit: Option<usize>,
34}
35
36impl AzfileLister {
37 pub fn new(core: Arc<AzfileCore>, path: String, limit: Option<usize>) -> Self {
38 Self { core, path, limit }
39 }
40}
41
42impl oio::PageList for AzfileLister {
43 async fn next_page(&self, ctx: &mut oio::PageContext) -> Result<()> {
44 let resp = self
45 .core
46 .azfile_list(&self.path, &self.limit, &ctx.token)
47 .await?;
48
49 let status = resp.status();
50
51 if status != StatusCode::OK {
52 if status == StatusCode::NOT_FOUND {
53 ctx.done = true;
54 return Ok(());
55 }
56 return Err(parse_error(resp));
57 }
58
59 if ctx.token.is_empty() && !ctx.done {
61 let e = oio::Entry::new(&self.path, Metadata::new(EntryMode::DIR));
62 ctx.entries.push_back(e);
63 }
64
65 let bs = resp.into_body();
66
67 let results: EnumerationResults =
68 de::from_reader(bs.reader()).map_err(new_xml_deserialize_error)?;
69
70 if results.next_marker.is_empty() {
71 ctx.done = true;
72 } else {
73 ctx.token = results.next_marker;
74 }
75
76 for file in results.entries.file {
77 let meta = Metadata::new(EntryMode::FILE)
78 .with_etag(file.properties.etag)
79 .with_content_length(file.properties.content_length.unwrap_or(0))
80 .with_last_modified(parse_datetime_from_rfc2822(&file.properties.last_modified)?);
81 let path = self.path.clone().trim_start_matches('/').to_string() + &file.name;
82 ctx.entries.push_back(oio::Entry::new(&path, meta));
83 }
84
85 for dir in results.entries.directory {
86 let meta = Metadata::new(EntryMode::DIR)
87 .with_etag(dir.properties.etag)
88 .with_last_modified(parse_datetime_from_rfc2822(&dir.properties.last_modified)?);
89 let path = self.path.clone().trim_start_matches('/').to_string() + &dir.name + "/";
90 ctx.entries.push_back(oio::Entry::new(&path, meta));
91 }
92
93 Ok(())
94 }
95}
96
97#[derive(Debug, Deserialize, PartialEq)]
98#[serde(rename_all = "PascalCase")]
99struct EnumerationResults {
100 marker: Option<String>,
101 prefix: Option<String>,
102 max_results: Option<u32>,
103 directory_id: Option<String>,
104 entries: Entries,
105 #[serde(default)]
106 next_marker: String,
107}
108
109#[derive(Debug, Deserialize, PartialEq)]
110#[serde(rename_all = "PascalCase")]
111struct Entries {
112 #[serde(default)]
113 file: Vec<File>,
114 #[serde(default)]
115 directory: Vec<Directory>,
116}
117
118#[derive(Debug, Deserialize, PartialEq)]
119#[serde(rename_all = "PascalCase")]
120struct File {
121 #[serde(rename = "FileId")]
122 file_id: String,
123 name: String,
124 properties: Properties,
125}
126
127#[derive(Debug, Deserialize, PartialEq)]
128#[serde(rename_all = "PascalCase")]
129struct Directory {
130 #[serde(rename = "FileId")]
131 file_id: String,
132 name: String,
133 properties: Properties,
134}
135
136#[derive(Debug, Deserialize, PartialEq)]
137#[serde(rename_all = "PascalCase")]
138struct Properties {
139 #[serde(rename = "Content-Length")]
140 content_length: Option<u64>,
141 #[serde(rename = "CreationTime")]
142 creation_time: String,
143 #[serde(rename = "LastAccessTime")]
144 last_access_time: String,
145 #[serde(rename = "LastWriteTime")]
146 last_write_time: String,
147 #[serde(rename = "ChangeTime")]
148 change_time: String,
149 #[serde(rename = "Last-Modified")]
150 last_modified: String,
151 #[serde(rename = "Etag")]
152 etag: String,
153}
154
155#[cfg(test)]
156mod tests {
157 use quick_xml::de::from_str;
158
159 use super::*;
160
161 #[test]
162 fn test_parse_list_result() {
163 let xml = r#"
164<?xml version="1.0" encoding="utf-8"?>
165<EnumerationResults ServiceEndpoint="https://myaccount.file.core.windows.net/" ShareName="myshare" ShareSnapshot="date-time" DirectoryPath="directory-path">
166 <Marker>string-value</Marker>
167 <Prefix>string-value</Prefix>
168 <MaxResults>100</MaxResults>
169 <DirectoryId>directory-id</DirectoryId>
170 <Entries>
171 <File>
172 <Name>Rust By Example.pdf</Name>
173 <FileId>13835093239654252544</FileId>
174 <Properties>
175 <Content-Length>5832374</Content-Length>
176 <CreationTime>2023-09-25T12:43:05.8483527Z</CreationTime>
177 <LastAccessTime>2023-09-25T12:43:05.8483527Z</LastAccessTime>
178 <LastWriteTime>2023-09-25T12:43:08.6337775Z</LastWriteTime>
179 <ChangeTime>2023-09-25T12:43:08.6337775Z</ChangeTime>
180 <Last-Modified>Mon, 25 Sep 2023 12:43:08 GMT</Last-Modified>
181 <Etag>\"0x8DBBDC4F8AC4AEF\"</Etag>
182 </Properties>
183 </File>
184 <Directory>
185 <Name>test_list_rich_dir</Name>
186 <FileId>12105702186650959872</FileId>
187 <Properties>
188 <CreationTime>2023-10-15T12:03:40.7194774Z</CreationTime>
189 <LastAccessTime>2023-10-15T12:03:40.7194774Z</LastAccessTime>
190 <LastWriteTime>2023-10-15T12:03:40.7194774Z</LastWriteTime>
191 <ChangeTime>2023-10-15T12:03:40.7194774Z</ChangeTime>
192 <Last-Modified>Sun, 15 Oct 2023 12:03:40 GMT</Last-Modified>
193 <Etag>\"0x8DBCD76C58C3E96\"</Etag>
194 </Properties>
195 </Directory>
196 </Entries>
197 <NextMarker />
198</EnumerationResults>
199 "#;
200
201 let results: EnumerationResults = from_str(xml).unwrap();
202
203 assert_eq!(results.entries.file[0].name, "Rust By Example.pdf");
204
205 assert_eq!(
206 results.entries.file[0].properties.etag,
207 "\\\"0x8DBBDC4F8AC4AEF\\\""
208 );
209
210 assert_eq!(results.entries.directory[0].name, "test_list_rich_dir");
211
212 assert_eq!(
213 results.entries.directory[0].properties.etag,
214 "\\\"0x8DBCD76C58C3E96\\\""
215 );
216 }
217}