use std::fmt::Debug;
use std::fmt::Formatter;
use std::sync::Arc;
use http::Response;
use http::StatusCode;
use tokio::sync::Mutex;
use super::core::*;
use crate::raw::*;
use crate::services::IcloudConfig;
use crate::*;
impl Configurator for IcloudConfig {
type Builder = IcloudBuilder;
fn into_builder(self) -> Self::Builder {
IcloudBuilder {
config: self,
http_client: None,
}
}
}
#[doc = include_str!("docs.md")]
#[derive(Default)]
pub struct IcloudBuilder {
pub config: IcloudConfig,
pub http_client: Option<HttpClient>,
}
impl Debug for IcloudBuilder {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut d = f.debug_struct("IcloudBuilder");
d.field("config", &self.config);
d.finish_non_exhaustive()
}
}
impl IcloudBuilder {
pub fn root(mut self, root: &str) -> Self {
self.config.root = if root.is_empty() {
None
} else {
Some(root.to_string())
};
self
}
pub fn apple_id(mut self, apple_id: &str) -> Self {
self.config.apple_id = if apple_id.is_empty() {
None
} else {
Some(apple_id.to_string())
};
self
}
pub fn password(mut self, password: &str) -> Self {
self.config.password = if password.is_empty() {
None
} else {
Some(password.to_string())
};
self
}
pub fn trust_token(mut self, trust_token: &str) -> Self {
self.config.trust_token = if trust_token.is_empty() {
None
} else {
Some(trust_token.to_string())
};
self
}
pub fn ds_web_auth_token(mut self, ds_web_auth_token: &str) -> Self {
self.config.ds_web_auth_token = if ds_web_auth_token.is_empty() {
None
} else {
Some(ds_web_auth_token.to_string())
};
self
}
pub fn is_china_mainland(mut self, is_china_mainland: bool) -> Self {
self.config.is_china_mainland = is_china_mainland;
self
}
pub fn http_client(mut self, client: HttpClient) -> Self {
self.http_client = Some(client);
self
}
}
impl Builder for IcloudBuilder {
const SCHEME: Scheme = Scheme::Icloud;
type Config = IcloudConfig;
fn build(self) -> Result<impl Access> {
let root = normalize_root(&self.config.root.unwrap_or_default());
let apple_id = match &self.config.apple_id {
Some(apple_id) => Ok(apple_id.clone()),
None => Err(Error::new(ErrorKind::ConfigInvalid, "apple_id is empty")
.with_operation("Builder::build")
.with_context("service", Scheme::Icloud)),
}?;
let password = match &self.config.password {
Some(password) => Ok(password.clone()),
None => Err(Error::new(ErrorKind::ConfigInvalid, "password is empty")
.with_operation("Builder::build")
.with_context("service", Scheme::Icloud)),
}?;
let ds_web_auth_token = match &self.config.ds_web_auth_token {
Some(ds_web_auth_token) => Ok(ds_web_auth_token.clone()),
None => Err(
Error::new(ErrorKind::ConfigInvalid, "ds_web_auth_token is empty")
.with_operation("Builder::build")
.with_context("service", Scheme::Icloud),
),
}?;
let trust_token = match &self.config.trust_token {
Some(trust_token) => Ok(trust_token.clone()),
None => Err(Error::new(ErrorKind::ConfigInvalid, "trust_token is empty")
.with_operation("Builder::build")
.with_context("service", Scheme::Icloud)),
}?;
let client = if let Some(client) = self.http_client {
client
} else {
HttpClient::new().map_err(|err| {
err.with_operation("Builder::build")
.with_context("service", Scheme::Icloud)
})?
};
let session_data = SessionData::new();
let signer = IcloudSigner {
client: client.clone(),
data: session_data,
apple_id,
password,
trust_token: Some(trust_token),
ds_web_auth_token: Some(ds_web_auth_token),
is_china_mainland: self.config.is_china_mainland,
initiated: false,
};
let signer = Arc::new(Mutex::new(signer));
Ok(IcloudBackend {
core: Arc::new(IcloudCore {
signer: signer.clone(),
root,
path_cache: PathCacher::new(IcloudPathQuery::new(signer.clone())),
}),
})
}
}
#[derive(Debug, Clone)]
pub struct IcloudBackend {
core: Arc<IcloudCore>,
}
impl Access for IcloudBackend {
type Reader = HttpBody;
type Writer = ();
type Lister = ();
type Deleter = ();
type BlockingReader = ();
type BlockingWriter = ();
type BlockingLister = ();
type BlockingDeleter = ();
fn info(&self) -> Arc<AccessorInfo> {
let mut ma = AccessorInfo::default();
ma.set_scheme(Scheme::Icloud)
.set_root(&self.core.root)
.set_native_capability(Capability {
stat: true,
read: true,
shared: true,
..Default::default()
});
ma.into()
}
async fn stat(&self, path: &str, _: OpStat) -> Result<RpStat> {
if path == "/" {
return Ok(RpStat::new(Metadata::new(EntryMode::DIR)));
}
let node = self.core.stat(path).await?;
let mut meta = Metadata::new(match node.type_field.as_str() {
"FOLDER" => EntryMode::DIR,
_ => EntryMode::FILE,
});
if meta.mode() == EntryMode::DIR || path.ends_with('/') {
return Ok(RpStat::new(Metadata::new(EntryMode::DIR)));
}
meta = meta.with_content_length(node.size);
let last_modified = parse_datetime_from_rfc3339(&node.date_modified)?;
meta = meta.with_last_modified(last_modified);
Ok(RpStat::new(meta))
}
async fn read(&self, path: &str, args: OpRead) -> Result<(RpRead, Self::Reader)> {
let resp = self.core.read(path, args.range(), &args).await?;
let status = resp.status();
match status {
StatusCode::OK | StatusCode::PARTIAL_CONTENT => Ok((RpRead::new(), resp.into_body())),
_ => {
let (part, mut body) = resp.into_parts();
let buf = body.to_buffer().await?;
Err(parse_error(Response::from_parts(part, buf)))
}
}
}
}