This is an automated email from the ASF dual-hosted git repository.
xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal.git
The following commit(s) were added to refs/heads/main by this push:
new 6c8b049e07 refactor: Remove not needed types in icloud (#4021)
6c8b049e07 is described below
commit 6c8b049e07e835e72cf466011ad68643b1f6fb29
Author: Xuanwo <[email protected]>
AuthorDate: Fri Jan 19 17:02:30 2024 +0800
refactor: Remove not needed types in icloud (#4021)
* Save work
Signed-off-by: Xuanwo <[email protected]>
* refactor: Remove not needed types in icloud
Signed-off-by: Xuanwo <[email protected]>
* Remove not needed clients
Signed-off-by: Xuanwo <[email protected]>
---------
Signed-off-by: Xuanwo <[email protected]>
---
Cargo.lock | 56 +++
core/Cargo.toml | 3 +-
core/src/raw/http_util/client.rs | 2 +
core/src/services/icloud/backend.rs | 16 +-
core/src/services/icloud/core.rs | 663 +++++++++++++++---------------------
5 files changed, 348 insertions(+), 392 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index f0476a220a..3ab1b0a8a0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1532,6 +1532,34 @@ dependencies = [
"unicode-segmentation",
]
+[[package]]
+name = "cookie"
+version = "0.16.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
+dependencies = [
+ "percent-encoding",
+ "time",
+ "version_check",
+]
+
+[[package]]
+name = "cookie_store"
+version = "0.16.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d606d0fba62e13cf04db20536c05cb7f13673c161cb47a47a82b9b9e7d3f1daa"
+dependencies = [
+ "cookie",
+ "idna 0.2.3",
+ "log",
+ "publicsuffix",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "time",
+ "url",
+]
+
[[package]]
name = "core-foundation"
version = "0.9.3"
@@ -3277,6 +3305,16 @@ dependencies = [
"unicode-normalization",
]
+[[package]]
+name = "idna"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
[[package]]
name = "idna"
version = "0.4.0"
@@ -5558,6 +5596,12 @@ version = "2.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94"
+[[package]]
+name = "psl-types"
+version = "2.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac"
+
[[package]]
name = "ptr_meta"
version = "0.1.4"
@@ -5578,6 +5622,16 @@ dependencies = [
"syn 1.0.109",
]
+[[package]]
+name = "publicsuffix"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457"
+dependencies = [
+ "idna 0.3.0",
+ "psl-types",
+]
+
[[package]]
name = "pulldown-cmark"
version = "0.9.3"
@@ -6014,6 +6068,8 @@ checksum =
"046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b"
dependencies = [
"base64 0.21.5",
"bytes",
+ "cookie",
+ "cookie_store",
"encoding_rs",
"futures-core",
"futures-util",
diff --git a/core/Cargo.toml b/core/Cargo.toml
index 276aa7f2f1..ec73bb4939 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -75,6 +75,7 @@ native-tls-vendored = ["reqwest/native-tls-vendored"]
# Enable path cache.
# This is an internal feature, and should not be used by users.
internal-path-cache = ["dep:moka"]
+internal-http-cookies = ["reqwest/cookies"]
# Enable all layers.
layers-all = [
@@ -154,7 +155,7 @@ services-http = []
services-huggingface = []
services-ipfs = ["dep:prost"]
services-ipmfs = []
-services-icloud = ["internal-path-cache"]
+services-icloud = ["internal-path-cache", "internal-http-cookies"]
services-koofr = []
services-libsql = ["dep:hrana-client-proto"]
services-memcached = ["dep:bb8"]
diff --git a/core/src/raw/http_util/client.rs b/core/src/raw/http_util/client.rs
index 2c5e4bc5c7..e4d47d88e8 100644
--- a/core/src/raw/http_util/client.rs
+++ b/core/src/raw/http_util/client.rs
@@ -69,6 +69,8 @@ impl HttpClient {
#[cfg(feature = "trust-dns")]
let builder = builder.trust_dns(true);
+ #[cfg(feature = "internal-http-cookies")]
+ let builder = builder.cookie_store(true);
Ok(Self {
client: builder.build().map_err(|err| {
diff --git a/core/src/services/icloud/backend.rs
b/core/src/services/icloud/backend.rs
index 34199c2e1d..52886dfa98 100644
--- a/core/src/services/icloud/backend.rs
+++ b/core/src/services/icloud/backend.rs
@@ -23,11 +23,9 @@ use std::fmt::{Debug, Formatter};
use std::sync::Arc;
use tokio::sync::Mutex;
+use super::core::*;
use crate::raw::*;
use crate::*;
-use crate::{Capability, Scheme};
-
-use super::core::{parse_error, IcloudCore, IcloudPathQuery, IcloudSigner,
SessionData};
/// Config for icloud services support.
#[derive(Default, Deserialize)]
@@ -146,6 +144,7 @@ impl IcloudBuilder {
self
}
+
/// ds_web_auth_token must be set in Session
///
/// Avoid Two Factor Authentication
@@ -158,9 +157,11 @@ impl IcloudBuilder {
self
}
- /// Enable the china origin
- /// For China, use "https://www.icloud.com.cn"
- /// For Other region, use "https://www.icloud.com"
+
+ /// Set if your apple id in China mainland.
+ ///
+ /// If in china mainland, we will connect to `https://www.icloud.com.cn`.
+ /// Otherwise, we will connect to `https://www.icloud.com`.
pub fn is_china_mainland(&mut self, is_china_mainland: bool) -> &mut Self {
self.config.is_china_mainland = is_china_mainland;
self
@@ -243,6 +244,7 @@ impl Builder for IcloudBuilder {
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));
@@ -250,7 +252,7 @@ impl Builder for IcloudBuilder {
core: Arc::new(IcloudCore {
signer: signer.clone(),
root,
- path_cache: PathCacher::new(IcloudPathQuery::new(client,
signer.clone())),
+ path_cache:
PathCacher::new(IcloudPathQuery::new(signer.clone())),
}),
})
}
diff --git a/core/src/services/icloud/core.rs b/core/src/services/icloud/core.rs
index 4e9e52f2c5..dcf6525f73 100644
--- a/core/src/services/icloud/core.rs
+++ b/core/src/services/icloud/core.rs
@@ -16,44 +16,32 @@
// under the License.
use async_trait::async_trait;
-use bytes::Buf;
-use std::collections::{BTreeMap, HashMap};
+use bytes::{Buf, Bytes};
use std::fmt::{Debug, Formatter};
use std::sync::Arc;
use http::header;
use http::header::{IF_MATCH, IF_NONE_MATCH};
-use http::Method;
use http::Request;
use http::Response;
use http::StatusCode;
-use log::debug;
use serde::{Deserialize, Serialize};
use serde_json::json;
use tokio::sync::Mutex;
-use crate::types::Result;
-use crate::{Error, ErrorKind};
-
-use crate::raw::{
- build_rooted_abs_path, get_basename, get_parent,
new_json_deserialize_error,
- parse_header_to_str, with_error_response_context, AsyncBody, HttpClient,
IncomingAsyncBody,
- OpRead, PathCacher, PathQuery,
-};
+use crate::raw::*;
+use crate::*;
static ACCOUNT_COUNTRY_HEADER: &str = "X-Apple-ID-Account-Country";
static OAUTH_STATE_HEADER: &str = "X-Apple-OAuth-State";
-
static SESSION_ID_HEADER: &str = "X-Apple-ID-Session-Id";
-
static SCNT_HEADER: &str = "scnt";
-
static SESSION_TOKEN_HEADER: &str = "X-Apple-Session-Token";
+static APPLE_RESPONSE_HEADER: &str = "X-Apple-I-Rscd";
+
static AUTH_ENDPOINT: &str = "https://idmsa.apple.com/appleauth/auth";
static SETUP_ENDPOINT: &str = "https://setup.icloud.com/setup/ws/1";
-static APPLE_RESPONSE_HEADER: &str = "X-Apple-I-Rscd";
-
const AUTH_HEADERS: [(&str, &str); 7] = [
(
// This code inspire from
@@ -72,11 +60,6 @@ const AUTH_HEADERS: [(&str, &str); 7] = [
),
];
-#[derive(Clone)]
-pub struct ServiceInfo {
- pub url: String,
-}
-
#[derive(Clone)]
pub struct SessionData {
oauth_state: String,
@@ -86,8 +69,8 @@ pub struct SessionData {
scnt: Option<String>,
account_country: Option<String>,
- cookies: BTreeMap<String, String>,
- webservices: HashMap<String, ServiceInfo>,
+ drivews_url: String,
+ docws_url: String,
}
impl SessionData {
@@ -98,8 +81,8 @@ impl SessionData {
session_token: None,
scnt: None,
account_country: None,
- cookies: BTreeMap::new(),
- webservices: HashMap::new(),
+ drivews_url: String::new(),
+ docws_url: String::new(),
}
}
}
@@ -108,13 +91,14 @@ impl SessionData {
pub struct IcloudSigner {
pub client: HttpClient,
- pub data: SessionData,
pub apple_id: String,
pub password: String,
-
+ pub is_china_mainland: bool,
pub trust_token: Option<String>,
pub ds_web_auth_token: Option<String>,
- pub is_china_mainland: bool,
+
+ pub data: SessionData,
+ pub initiated: bool,
}
impl Debug for IcloudSigner {
@@ -126,198 +110,168 @@ impl Debug for IcloudSigner {
}
impl IcloudSigner {
- pub fn get_service_info(&self, name: String) -> Option<&ServiceInfo> {
- self.data.webservices.get(&name)
+ /// Get the session data from signer.
+ pub fn session_data(&self) -> &SessionData {
+ &self.data
}
- // create_dir could use client_id
- pub fn get_client_id(&self) -> &String {
+ /// iCloud will use our oauth state as client id.
+ pub fn client_id(&self) -> &str {
&self.data.oauth_state
}
-}
-impl IcloudSigner {
- pub async fn signer(&mut self) -> Result<()> {
- let body = json!({
+ async fn init(&mut self) -> Result<()> {
+ // Sign the auth endpoint first.
+ let uri = format!("{}/signin?isRememberMeEnable=true", AUTH_ENDPOINT);
+ let body = serde_json::to_vec(&json!({
"accountName" : self.apple_id,
"password" : self.password,
"rememberMe": true,
"trustTokens": [self.trust_token.clone().unwrap()],
- })
- .to_string();
-
- let uri = format!("{}/signin?isRememberMeEnable=true", AUTH_ENDPOINT);
-
- let async_body = AsyncBody::Bytes(bytes::Bytes::from(body));
-
- let response = self.sign(Method::POST, uri, async_body).await?;
-
- let status = response.status();
-
- return match status {
- StatusCode::OK => {
- if let Some(rscd) =
response.headers().get(APPLE_RESPONSE_HEADER) {
- let status_code =
StatusCode::from_bytes(rscd.as_bytes()).unwrap();
- if status_code != StatusCode::CONFLICT {
- return Err(parse_error(response).await?);
- }
- }
- self.authenticate().await
+ }))
+ .map_err(new_json_serialize_error)?;
+
+ let mut req = Request::post(uri)
+ .header(header::CONTENT_TYPE, "application/json")
+ .body(AsyncBody::Bytes(Bytes::from(body)))
+ .map_err(new_request_build_error)?;
+ self.sign(&mut req)?;
+
+ let resp = self.client.send(req).await?;
+ if resp.status() != StatusCode::OK {
+ return Err(parse_error(resp).await?);
+ }
+ if let Some(rscd) = resp.headers().get(APPLE_RESPONSE_HEADER) {
+ let status_code = StatusCode::from_bytes(rscd.as_bytes()).unwrap();
+ if status_code != StatusCode::CONFLICT {
+ return Err(parse_error(resp).await?);
}
- _ => Err(parse_error(response).await?),
- };
- }
-
- pub async fn authenticate(&mut self) -> Result<()> {
- let body = json!({
- "accountCountryCode":
self.data.account_country.as_ref().unwrap_or(&String::new()),
-
"dsWebAuthToken":self.ds_web_auth_token.as_ref().unwrap_or(&String::new()),
- "extended_login": true,
- "trustToken":
self.trust_token.as_ref().unwrap_or(&String::new())
- })
- .to_string();
+ }
+ // Setup to get the session id.
let uri = format!("{}/accountLogin", SETUP_ENDPOINT);
+ let body = serde_json::to_vec(&json!({
+ "accountCountryCode":
self.data.account_country.clone().unwrap_or_default(),
+
"dsWebAuthToken":self.ds_web_auth_token.clone().unwrap_or_default(),
+ "extended_login": true,
+ "trustToken": self.trust_token.clone().unwrap_or_default(),
+ }))
+ .map_err(new_json_serialize_error)?;
+
+ let mut req = Request::post(uri)
+ .header(header::CONTENT_TYPE, "application/json")
+ .body(AsyncBody::Bytes(Bytes::from(body)))
+ .map_err(new_request_build_error)?;
+ self.sign(&mut req)?;
+
+ let resp = self.client.send(req).await?;
+ if resp.status() != StatusCode::OK {
+ return Err(parse_error(resp).await?);
+ }
- let async_body = AsyncBody::Bytes(bytes::Bytes::from(body));
-
- let response = self.sign(Method::POST, uri, async_body).await?;
-
- let status = response.status();
-
- match status {
- StatusCode::OK => {
- let body = &response.into_body().bytes().await?;
- let auth_info: IcloudWebservicesResponse =
-
serde_json::from_slice(body.chunk()).map_err(new_json_deserialize_error)?;
+ let bs = resp.into_body().bytes().await?;
+ let auth_info: IcloudWebservicesResponse =
+ serde_json::from_slice(&bs).map_err(new_json_deserialize_error)?;
- if let Some(drivews_url) = &auth_info.webservices.drivews.url {
- self.data.webservices.insert(
- String::from("drive"),
- ServiceInfo {
- url: drivews_url.to_string(),
- },
- );
- }
- if let Some(docws_url) = &auth_info.webservices.docws.url {
- self.data.webservices.insert(
- String::from("docw"),
- ServiceInfo {
- url: docws_url.to_string(),
- },
- );
- }
+ // Check if we have extra challenge to take.
+ if auth_info.hsa_challenge_required && !auth_info.hsa_trusted_browser {
+ return Err(Error::new(ErrorKind::Unexpected, "Apple icloud
AuthenticationFailed:Unauthorized request:Needs two-factor authentication"));
+ }
- if auth_info.hsa_challenge_required {
- if auth_info.hsa_trusted_browser {
- Ok(())
- } else {
- Err(Error::new(ErrorKind::Unexpected, "Apple icloud
AuthenticationFailed:Unauthorized request:Needs two-factor authentication"))
- }
- } else {
- Ok(())
- }
- }
- _ => Err(Error::new(
- ErrorKind::Unexpected,
- "Apple icloud AuthenticationFailed:Unauthorized:Invalid token",
- )),
+ if let Some(v) = &auth_info.webservices.drivews.url {
+ self.data.drivews_url = v.to_string();
+ }
+ if let Some(v) = &auth_info.webservices.docws.url {
+ self.data.docws_url = v.to_string();
}
+
+ Ok(())
}
-}
-impl IcloudSigner {
- pub async fn sign(
- &mut self,
- method: Method,
- uri: String,
- body: AsyncBody,
- ) -> Result<Response<IncomingAsyncBody>> {
- let mut request = Request::builder().method(method).uri(uri);
+ fn sign<T>(&mut self, req: &mut Request<T>) -> Result<()> {
+ let headers = req.headers_mut();
- request = request.header(OAUTH_STATE_HEADER,
self.data.oauth_state.clone());
+ headers.insert(
+ OAUTH_STATE_HEADER,
+ build_header_value(&self.data.oauth_state)?,
+ );
if let Some(session_id) = &self.data.session_id {
- request = request.header(SESSION_ID_HEADER, session_id);
+ headers.insert(SESSION_ID_HEADER, build_header_value(session_id)?);
}
if let Some(scnt) = &self.data.scnt {
- request = request.header(SCNT_HEADER, scnt);
+ headers.insert(SCNT_HEADER, build_header_value(scnt)?);
}
- // China region
- // ("Origin", "https://www.icloud.com.cn")
- // ("Referer", "https://www.icloud.com.cn/")
// You can get more information from
[apple.com](https://support.apple.com/en-us/111754)
if self.is_china_mainland {
- request = request.header("Origin", "https://www.icloud.com.cn");
- request = request.header("Referer", "https://www.icloud.com.cn/");
+ headers.insert(
+ header::ORIGIN,
+ build_header_value("https://www.icloud.com.cn")?,
+ );
+ headers.insert(
+ header::REFERER,
+ build_header_value("https://www.icloud.com.cn/")?,
+ );
} else {
- request = request.header("Origin", "https://www.icloud.com");
- request = request.header("Referer", "https://www.icloud.com/");
+ headers.insert(
+ header::ORIGIN,
+ build_header_value("https://www.icloud.com")?,
+ );
+ headers.insert(
+ header::REFERER,
+ build_header_value("https://www.icloud.com/")?,
+ );
}
- if !self.data.cookies.is_empty() {
- let cookies: Vec<String> = self
- .data
- .cookies
- .iter()
- .map(|(k, v)| format!("{}={}", k, v))
- .collect();
- request = request.header(header::COOKIE,
cookies.as_slice().join("; "));
+ for (key, value) in AUTH_HEADERS {
+ headers.insert(key, build_header_value(value)?);
}
- if let Some(headers) = request.headers_mut() {
- headers.insert("Content-Type",
"application/json".parse().unwrap());
- headers.insert("Accept", "*/*".parse().unwrap());
- for (key, value) in AUTH_HEADERS {
- headers.insert(key, value.parse().unwrap());
- }
+ Ok(())
+ }
+
+ /// Update signer's data after request sent out.
+ fn update(&mut self, resp: &Response<IncomingAsyncBody>) -> Result<()> {
+ if let Some(account_country) = parse_header_to_str(resp.headers(),
ACCOUNT_COUNTRY_HEADER)?
+ {
+ self.data.account_country = Some(account_country.to_string());
}
- match self.client.send(request.body(body).unwrap()).await {
- Ok(response) => {
- if let Some(account_country) =
- parse_header_to_str(response.headers(),
ACCOUNT_COUNTRY_HEADER)?
- {
- self.data.account_country =
Some(account_country.to_string());
- }
+ if let Some(session_id) = parse_header_to_str(resp.headers(),
SESSION_ID_HEADER)? {
+ self.data.session_id = Some(session_id.to_string());
+ }
+ if let Some(session_token) = parse_header_to_str(resp.headers(),
SESSION_TOKEN_HEADER)? {
+ self.data.session_token = Some(session_token.to_string());
+ }
- if let Some(session_id) =
- parse_header_to_str(response.headers(), SESSION_ID_HEADER)?
- {
- self.data.session_id = Some(session_id.to_string());
- }
- if let Some(session_token) =
- parse_header_to_str(response.headers(),
SESSION_TOKEN_HEADER)?
- {
- self.data.session_token = Some(session_token.to_string());
- }
+ if let Some(scnt) = parse_header_to_str(resp.headers(), SCNT_HEADER)? {
+ self.data.scnt = Some(scnt.to_string());
+ }
- if let Some(scnt) = parse_header_to_str(response.headers(),
SCNT_HEADER)? {
- self.data.scnt = Some(scnt.to_string());
- }
+ Ok(())
+ }
- for (key, value) in response.headers() {
- if key == header::SET_COOKIE {
- if let Some(cookie) =
value.to_str().unwrap().split(';').next() {
- if let Some((key, value)) = cookie.split_once('=')
{
- self.data
- .cookies
- .insert(String::from(key),
String::from(value));
- }
- }
- }
- }
- match response.status() {
- StatusCode::UNAUTHORIZED =>
Err(parse_error(response).await?),
- _ => Ok(response),
- }
- }
- _ => Err(Error::new(
- ErrorKind::Unexpected,
- "Apple icloud AuthenticationFailed:Unauthorized request",
- )),
+ /// Send will make sure the following things:
+ ///
+ /// - Init the signer if it's not initiated.
+ /// - Sign the request.
+ /// - Update the session data if needed.
+ pub async fn send(
+ &mut self,
+ mut req: Request<AsyncBody>,
+ ) -> Result<Response<IncomingAsyncBody>> {
+ // Init the signer first.
+ if self.initiated {
+ self.init().await?;
+ self.initiated = true;
}
+
+ self.sign(&mut req)?;
+ let resp = self.client.send(req).await?;
+ self.update(&resp)?;
+
+ Ok(resp)
}
}
@@ -335,132 +289,94 @@ impl Debug for IcloudCore {
}
}
-pub struct IcloudPathQuery {
- pub client: HttpClient,
- pub signer: Arc<Mutex<IcloudSigner>>,
-}
+impl IcloudCore {
+ // Retrieves a root within the icloud Drive.
+ // "FOLDER::com.apple.CloudDocs::root"
+ pub async fn get_root(&self, id: &str) -> Result<IcloudRoot> {
+ let mut signer = self.signer.lock().await;
-impl IcloudPathQuery {
- pub fn new(client: HttpClient, signer: Arc<Mutex<IcloudSigner>>) -> Self {
- IcloudPathQuery { client, signer }
- }
-}
+ let uri = format!(
+ "{}/retrieveItemDetailsInFolders",
+ signer.session_data().drivews_url
+ );
-#[async_trait]
-impl PathQuery for IcloudPathQuery {
- async fn root(&self) -> Result<String> {
- Ok("FOLDER::com.apple.CloudDocs::root".to_string())
+ let body = serde_json::to_vec(&json!([
+ {
+ "drivewsid": id,
+ "partialData": false
+ }
+ ]))
+ .map_err(new_json_serialize_error)?;
+
+ let req = Request::post(uri)
+ .body(AsyncBody::Bytes(Bytes::from(body)))
+ .map_err(new_request_build_error)?;
+
+ let resp = signer.send(req).await?;
+ if resp.status() != StatusCode::OK {
+ return Err(parse_error(resp).await?);
+ }
+
+ let body = resp.into_body().bytes().await?;
+ let drive_node: Vec<IcloudRoot> =
+
serde_json::from_slice(body.chunk()).map_err(new_json_deserialize_error)?;
+ Ok(drive_node[0].clone())
}
- // Retrieves the root directory within the icloud Drive.
- async fn query(&self, parent_id: &str, name: &str) ->
Result<Option<String>> {
+ pub async fn get_file(
+ &self,
+ id: &str,
+ zone: &str,
+ args: OpRead,
+ ) -> Result<Response<IncomingAsyncBody>> {
let mut signer = self.signer.lock().await;
- let drive_url = signer
- .get_service_info(String::from("drive"))
- .ok_or(Error::new(
- ErrorKind::NotFound,
- "drive service info drivews not found",
- ))?
- .clone()
- .url;
-
- let uri = format!("{}/retrieveItemDetailsInFolders", drive_url);
- let body = json!([
- {
- "drivewsid": parent_id,
- "partialData": false
- }
- ])
- .to_string();
+ let uri = format!(
+ "{}/ws/{}/download/by_id?document_id={}",
+ signer.session_data().drivews_url,
+ zone,
+ id
+ );
- let async_body = AsyncBody::Bytes(bytes::Bytes::from(body));
+ let req = Request::get(uri)
+ .body(AsyncBody::Empty)
+ .map_err(new_request_build_error)?;
- let response = signer.sign(Method::POST, uri, async_body).await?;
+ let resp = signer.send(req).await?;
+ if resp.status() != StatusCode::OK {
+ return Err(parse_error(resp).await?);
+ }
- if response.status() == StatusCode::OK {
- let body = &response.into_body().bytes().await?;
+ let body = resp.into_body().bytes().await?;
+ let object: IcloudObject =
+
serde_json::from_slice(body.chunk()).map_err(new_json_deserialize_error)?;
- let root: Vec<IcloudRoot> =
-
serde_json::from_slice(body.chunk()).map_err(new_json_deserialize_error)?;
+ let url = object.data_token.url.to_string();
- let node = &root[0];
+ let mut req = Request::get(url);
- let id = match node.items.iter().find(|it| it.name == name) {
- Some(it) => Ok(Some(it.drivewsid.clone())),
- None => Ok(None),
- }?;
- Ok(id)
- } else {
- Err(parse_error(response).await?)
+ if let Some(if_match) = args.if_match() {
+ req = req.header(IF_MATCH, if_match);
}
- }
- async fn create_dir(&self, parent_id: &str, name: &str) -> Result<String> {
- let mut signer = self.signer.lock().await;
- let client_id = signer.get_client_id();
- let drive_url = signer
- .get_service_info(String::from("drive"))
- .ok_or(Error::new(
- ErrorKind::NotFound,
- "drive service info drivews not found",
- ))?
- .url
- .clone();
-
- let uri = format!("{}/createFolders", drive_url);
- let body = json!(
- {
- "destinationDrivewsId": parent_id,
- "folders": [
- {
- "clientId": client_id,
- "name": name,
- }
- ],
- }
- )
- .to_string();
-
- let async_body = AsyncBody::Bytes(bytes::Bytes::from(body));
- let response = signer.sign(Method::POST, uri, async_body).await?;
-
- match response.status() {
- StatusCode::OK => {
- let body = &response.into_body().bytes().await?;
-
- let create_folder: IcloudCreateFolder =
-
serde_json::from_slice(body.chunk()).map_err(new_json_deserialize_error)?;
-
- Ok(create_folder.destination_drivews_id)
- }
- _ => Err(parse_error(response).await?),
+ let range = args.range();
+ if !range.is_full() {
+ req = req.header(header::RANGE, range.to_header())
+ }
+ if let Some(if_none_match) = args.if_none_match() {
+ req = req.header(IF_NONE_MATCH, if_none_match);
}
- }
-}
-
-impl IcloudCore {
- // Logs into icloud using the provided credentials.
- pub async fn login(&self) -> Result<()> {
- let mut signer = self.signer.lock().await;
- signer.signer().await
- }
+ let req = req
+ .body(AsyncBody::Empty)
+ .map_err(new_request_build_error)?;
- //Apple Drive
- pub async fn drive(&self) -> Option<DriveService> {
- let clone = self.signer.clone();
- let signer = self.signer.lock().await;
+ let resp = signer.client.send(req).await?;
- let docws = signer.get_service_info(String::from("docw")).unwrap();
- signer
- .get_service_info(String::from("drive"))
- .map(|s| DriveService::new(clone, s.url.clone(),
docws.url.clone()))
+ Ok(resp)
}
pub async fn read(&self, path: &str, args: &OpRead) ->
Result<Response<IncomingAsyncBody>> {
- self.login().await?;
-
let path = build_rooted_abs_path(&self.root, path);
let base = get_basename(&path);
@@ -469,13 +385,8 @@ impl IcloudCore {
&format!("read path not found: {}", base),
))?;
- let drive = self
- .drive()
- .await
- .expect("icloud DriveService read drive not found");
-
if let Some(docwsid) =
path_id.strip_prefix("FILE::com.apple.CloudDocs::") {
- Ok(drive
+ Ok(self
.get_file(docwsid, "com.apple.CloudDocs", args.clone())
.await?)
} else {
@@ -487,8 +398,6 @@ impl IcloudCore {
}
pub async fn stat(&self, path: &str) -> Result<IcloudItem> {
- self.login().await?;
-
let path = build_rooted_abs_path(&self.root, path);
let mut base = get_basename(&path);
@@ -503,17 +412,12 @@ impl IcloudCore {
&format!("stat path not found: {}", base),
))?;
- let drive = self
- .drive()
- .await
- .expect("icloud DriveService stat drive not found");
-
let folder_id = self.path_cache.get(parent).await?.ok_or(Error::new(
ErrorKind::NotFound,
&format!("stat path not found: {}", parent),
))?;
- let node = drive.get_root(&folder_id).await?;
+ let node = self.get_root(&folder_id).await?;
match node.items.iter().find(|it| it.drivewsid == file_id.clone()) {
Some(it) => Ok(it.clone()),
@@ -525,104 +429,95 @@ impl IcloudCore {
}
}
-pub struct DriveService {
- signer: Arc<Mutex<IcloudSigner>>,
- drive_url: String,
- docw_url: String,
+pub struct IcloudPathQuery {
+ pub signer: Arc<Mutex<IcloudSigner>>,
}
-impl DriveService {
- // Constructs an interface to an icloud Drive.
- pub fn new(
- signer: Arc<Mutex<IcloudSigner>>,
- drive_url: String,
- docw_url: String,
- ) -> DriveService {
- DriveService {
- signer,
- drive_url,
- docw_url,
- }
+impl IcloudPathQuery {
+ pub fn new(signer: Arc<Mutex<IcloudSigner>>) -> Self {
+ IcloudPathQuery { signer }
}
+}
- // Retrieves a root within the icloud Drive.
- // "FOLDER::com.apple.CloudDocs::root"
- pub async fn get_root(&self, id: &str) -> Result<IcloudRoot> {
- let uri = format!("{}/retrieveItemDetailsInFolders", self.drive_url);
-
- let body = json!([
- {
- "drivewsid": id,
- "partialData": false
- }
- ])
- .to_string();
+#[async_trait]
+impl PathQuery for IcloudPathQuery {
+ async fn root(&self) -> Result<String> {
+ Ok("FOLDER::com.apple.CloudDocs::root".to_string())
+ }
+ /// Retrieves the root directory within the icloud Drive.
+ ///
+ /// FIXME: we are reading the entire dir to find the file, this is not
efficient.
+ /// Maybe we should build a new path cache for this kind of services
instead.
+ async fn query(&self, parent_id: &str, name: &str) ->
Result<Option<String>> {
let mut signer = self.signer.lock().await;
- let async_body = AsyncBody::Bytes(bytes::Bytes::from(body));
-
- let response = signer.sign(Method::POST, uri, async_body).await?;
-
- if response.status() == StatusCode::OK {
- let body = &response.into_body().bytes().await?;
-
- let drive_node: Vec<IcloudRoot> =
-
serde_json::from_slice(body.chunk()).map_err(new_json_deserialize_error)?;
- Ok(drive_node[0].clone())
- } else {
- Err(parse_error(response).await?)
- }
- }
-
- pub async fn get_file(
- &self,
- id: &str,
- zone: &str,
- args: OpRead,
- ) -> Result<Response<IncomingAsyncBody>> {
let uri = format!(
- "{}\
- /ws/{}/download/by_id?document_id={}",
- self.docw_url, zone, id
+ "{}/retrieveItemDetailsInFolders",
+ signer.session_data().drivews_url
);
- debug!("{}", uri);
- let mut signer = self.signer.lock().await;
-
- let response = signer.sign(Method::GET, uri, AsyncBody::Empty).await?;
-
- match response.status() {
- StatusCode::OK => {
- let body = &response.into_body().bytes().await?;
- let object: IcloudObject =
-
serde_json::from_slice(body.chunk()).map_err(new_json_deserialize_error)?;
-
- let url = object.data_token.url.to_string();
+ let body = serde_json::to_vec(&json!([
+ {
+ "drivewsid": parent_id,
+ "partialData": false
+ }
+ ]))
+ .map_err(new_json_serialize_error)?;
+
+ let req = Request::post(uri)
+ .body(AsyncBody::Bytes(Bytes::from(body)))
+ .map_err(new_request_build_error)?;
+
+ let resp = signer.send(req).await?;
+ if resp.status() != StatusCode::OK {
+ return Err(parse_error(resp).await?);
+ }
- let mut request_builder =
Request::builder().method(Method::GET).uri(url);
+ let body = resp.into_body().bytes().await?;
+ let root: Vec<IcloudRoot> =
+
serde_json::from_slice(body.chunk()).map_err(new_json_deserialize_error)?;
- if let Some(if_match) = args.if_match() {
- request_builder = request_builder.header(IF_MATCH,
if_match);
- }
+ let node = &root[0];
- let range = args.range();
- if !range.is_full() {
- request_builder = request_builder.header(header::RANGE,
range.to_header())
- }
+ let id = match node.items.iter().find(|it| it.name == name) {
+ Some(it) => Ok(Some(it.drivewsid.clone())),
+ None => Ok(None),
+ }?;
+ Ok(id)
+ }
- if let Some(if_none_match) = args.if_none_match() {
- request_builder = request_builder.header(IF_NONE_MATCH,
if_none_match);
+ async fn create_dir(&self, parent_id: &str, name: &str) -> Result<String> {
+ let mut signer = self.signer.lock().await;
+ let client_id = signer.client_id();
+
+ let uri = format!("{}/createFolders",
signer.session_data().drivews_url);
+ let body = serde_json::to_vec(&json!(
+ {
+ "destinationDrivewsId": parent_id,
+ "folders": [
+ {
+ "clientId": client_id,
+ "name": name,
}
-
- let async_body =
request_builder.body(AsyncBody::Empty).unwrap();
-
- let response = signer.client.send(async_body).await?;
-
- Ok(response)
- }
- _ => Err(parse_error(response).await?),
+ ],
+ }
+ ))
+ .map_err(new_json_serialize_error)?;
+
+ let req = Request::post(uri)
+ .body(AsyncBody::Bytes(Bytes::from(body)))
+ .map_err(new_request_build_error)?;
+
+ let resp = signer.send(req).await?;
+ if resp.status() != StatusCode::OK {
+ return Err(parse_error(resp).await?);
}
+
+ let body = resp.into_body().bytes().await?;
+ let create_folder: IcloudCreateFolder =
+
serde_json::from_slice(body.chunk()).map_err(new_json_deserialize_error)?;
+ Ok(create_folder.destination_drivews_id)
}
}