opendal/services/icloud/
backend.rs1use std::fmt::Debug;
19use std::fmt::Formatter;
20use std::sync::Arc;
21
22use http::Response;
23use http::StatusCode;
24use tokio::sync::Mutex;
25
26use super::core::*;
27use crate::raw::*;
28use crate::services::IcloudConfig;
29use crate::*;
30
31impl Configurator for IcloudConfig {
32 type Builder = IcloudBuilder;
33
34 #[allow(deprecated)]
35 fn into_builder(self) -> Self::Builder {
36 IcloudBuilder {
37 config: self,
38 http_client: None,
39 }
40 }
41}
42
43#[doc = include_str!("docs.md")]
45#[derive(Default)]
46pub struct IcloudBuilder {
47 pub config: IcloudConfig,
49 #[deprecated(since = "0.53.0", note = "Use `Operator::update_http_client` instead")]
56 pub http_client: Option<HttpClient>,
57}
58
59impl Debug for IcloudBuilder {
60 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
61 let mut d = f.debug_struct("IcloudBuilder");
62
63 d.field("config", &self.config);
64 d.finish_non_exhaustive()
65 }
66}
67
68impl IcloudBuilder {
69 pub fn root(mut self, root: &str) -> Self {
73 self.config.root = if root.is_empty() {
74 None
75 } else {
76 Some(root.to_string())
77 };
78
79 self
80 }
81
82 pub fn apple_id(mut self, apple_id: &str) -> Self {
86 self.config.apple_id = if apple_id.is_empty() {
87 None
88 } else {
89 Some(apple_id.to_string())
90 };
91
92 self
93 }
94
95 pub fn password(mut self, password: &str) -> Self {
99 self.config.password = if password.is_empty() {
100 None
101 } else {
102 Some(password.to_string())
103 };
104
105 self
106 }
107
108 pub fn trust_token(mut self, trust_token: &str) -> Self {
112 self.config.trust_token = if trust_token.is_empty() {
113 None
114 } else {
115 Some(trust_token.to_string())
116 };
117
118 self
119 }
120
121 pub fn ds_web_auth_token(mut self, ds_web_auth_token: &str) -> Self {
125 self.config.ds_web_auth_token = if ds_web_auth_token.is_empty() {
126 None
127 } else {
128 Some(ds_web_auth_token.to_string())
129 };
130
131 self
132 }
133
134 pub fn is_china_mainland(mut self, is_china_mainland: bool) -> Self {
139 self.config.is_china_mainland = is_china_mainland;
140 self
141 }
142
143 #[deprecated(since = "0.53.0", note = "Use `Operator::update_http_client` instead")]
150 #[allow(deprecated)]
151 pub fn http_client(mut self, client: HttpClient) -> Self {
152 self.http_client = Some(client);
153 self
154 }
155}
156
157impl Builder for IcloudBuilder {
158 const SCHEME: Scheme = Scheme::Icloud;
159 type Config = IcloudConfig;
160
161 fn build(self) -> Result<impl Access> {
162 let root = normalize_root(&self.config.root.unwrap_or_default());
163
164 let apple_id = match &self.config.apple_id {
165 Some(apple_id) => Ok(apple_id.clone()),
166 None => Err(Error::new(ErrorKind::ConfigInvalid, "apple_id is empty")
167 .with_operation("Builder::build")
168 .with_context("service", Scheme::Icloud)),
169 }?;
170
171 let password = match &self.config.password {
172 Some(password) => Ok(password.clone()),
173 None => Err(Error::new(ErrorKind::ConfigInvalid, "password is empty")
174 .with_operation("Builder::build")
175 .with_context("service", Scheme::Icloud)),
176 }?;
177
178 let ds_web_auth_token = match &self.config.ds_web_auth_token {
179 Some(ds_web_auth_token) => Ok(ds_web_auth_token.clone()),
180 None => Err(
181 Error::new(ErrorKind::ConfigInvalid, "ds_web_auth_token is empty")
182 .with_operation("Builder::build")
183 .with_context("service", Scheme::Icloud),
184 ),
185 }?;
186
187 let trust_token = match &self.config.trust_token {
188 Some(trust_token) => Ok(trust_token.clone()),
189 None => Err(Error::new(ErrorKind::ConfigInvalid, "trust_token is empty")
190 .with_operation("Builder::build")
191 .with_context("service", Scheme::Icloud)),
192 }?;
193
194 let session_data = SessionData::new();
195
196 let info = AccessorInfo::default();
197 info.set_scheme(Scheme::Icloud)
198 .set_root(&root)
199 .set_native_capability(Capability {
200 stat: true,
201 stat_has_content_length: true,
202 stat_has_last_modified: true,
203
204 read: true,
205
206 shared: true,
207 ..Default::default()
208 });
209
210 #[allow(deprecated)]
212 if let Some(client) = self.http_client {
213 info.update_http_client(|_| client);
214 }
215
216 let accessor_info = Arc::new(info);
217
218 let signer = IcloudSigner {
219 info: accessor_info.clone(),
220 data: session_data,
221 apple_id,
222 password,
223 trust_token: Some(trust_token),
224 ds_web_auth_token: Some(ds_web_auth_token),
225 is_china_mainland: self.config.is_china_mainland,
226 initiated: false,
227 };
228
229 let signer = Arc::new(Mutex::new(signer));
230 Ok(IcloudBackend {
231 core: Arc::new(IcloudCore {
232 info: accessor_info,
233 signer: signer.clone(),
234 root,
235 path_cache: PathCacher::new(IcloudPathQuery::new(signer.clone())),
236 }),
237 })
238 }
239}
240
241#[derive(Debug, Clone)]
242pub struct IcloudBackend {
243 core: Arc<IcloudCore>,
244}
245
246impl Access for IcloudBackend {
247 type Reader = HttpBody;
248 type Writer = ();
249 type Lister = ();
250 type Deleter = ();
251 type BlockingReader = ();
252 type BlockingWriter = ();
253 type BlockingLister = ();
254 type BlockingDeleter = ();
255
256 fn info(&self) -> Arc<AccessorInfo> {
257 self.core.info.clone()
258 }
259
260 async fn stat(&self, path: &str, _: OpStat) -> Result<RpStat> {
261 if path == "/" {
263 return Ok(RpStat::new(Metadata::new(EntryMode::DIR)));
264 }
265
266 let node = self.core.stat(path).await?;
267
268 let mut meta = Metadata::new(match node.type_field.as_str() {
269 "FOLDER" => EntryMode::DIR,
270 _ => EntryMode::FILE,
271 });
272
273 if meta.mode() == EntryMode::DIR || path.ends_with('/') {
274 return Ok(RpStat::new(Metadata::new(EntryMode::DIR)));
275 }
276
277 meta = meta.with_content_length(node.size);
278
279 let last_modified = parse_datetime_from_rfc3339(&node.date_modified)?;
280 meta = meta.with_last_modified(last_modified);
281
282 Ok(RpStat::new(meta))
283 }
284
285 async fn read(&self, path: &str, args: OpRead) -> Result<(RpRead, Self::Reader)> {
286 let resp = self.core.read(path, args.range(), &args).await?;
287
288 let status = resp.status();
289 match status {
290 StatusCode::OK | StatusCode::PARTIAL_CONTENT => Ok((RpRead::new(), resp.into_body())),
291 _ => {
292 let (part, mut body) = resp.into_parts();
293 let buf = body.to_buffer().await?;
294 Err(parse_error(Response::from_parts(part, buf)))
295 }
296 }
297 }
298}