diff --git a/src/commands/clone.rs b/src/commands/clone.rs index 308c34b..8f0282c 100644 --- a/src/commands/clone.rs +++ b/src/commands/clone.rs @@ -20,7 +20,9 @@ pub async fn exec(args: CloneArgs, config: Config) { .set_path(url_props.path.to_string()) // .set_depth(args.depth) .get_properties(vec![]) - .enumerate().await else { todo!()}; + .enumerate().await else { todo!() }; + dbg!(&files); + dbg!(&folders); for folder in folders { // create folder diff --git a/src/services/enumerator.rs b/src/services/enumerator.rs index 102f02c..e4c5604 100644 --- a/src/services/enumerator.rs +++ b/src/services/enumerator.rs @@ -2,15 +2,16 @@ use super::{ req_props::{Props, ReqProps, Response}, service::Service, }; +use crate::utils::path; use std::sync::Arc; -use tokio::sync::Mutex; +use tokio::{task, sync::{mpsc::UnboundedSender, Mutex}}; -pub const DEFAULT_DEPTH: usize = 3; +pub const DEFAULT_DEPTH: u16 = 2; pub struct Enumerator<'a> { service: &'a Service, path: String, - depth: usize, + depth: u16, properties: Vec, } @@ -29,7 +30,7 @@ impl<'a> Enumerator<'a> { self } - pub fn set_depth(mut self, depth: usize) -> Self { + pub fn set_depth(mut self, depth: u16) -> Self { self.depth = depth; self } @@ -44,43 +45,34 @@ impl<'a> Enumerator<'a> { let files = Arc::new(Mutex::new(Vec::new())); let folders = Arc::new(Mutex::new(Vec::new())); let service = Arc::from(self.service.clone()); + let tasks_active = Arc::new(Mutex::new(0)); tx.send(self.path.clone()); let mut handles = vec![]; - while let Some(path) = rx.recv().await { - let tx_clone = tx.clone(); - let files_clone = Arc::clone(&files); - let folders_clone = Arc::clone(&folders); - let service_clone = Arc::clone(&service); - let properties = self.properties.clone(); - - let handle = tokio::task::spawn_blocking(move || async move { - let res = ReqProps::new(&service_clone) - .set_path(path) - // .set_depth(self.depth) - .get_properties(properties) - .send() - .await - .unwrap(); - - for obj in res.responses { - if obj.is_dir() { - todo!("Deal to have good href"); - tx_clone.send(obj.href.clone()); - folders_clone.lock().await.push(obj); - } else { - files_clone.lock().await.push(obj); - } - } - }); - handles.push(handle); + loop { + dbg!(*tasks_active.lock().await); + if let Ok(path) = rx.try_recv() { + handles.push(enumerator_task(EnumeratorTask{ + path, + depth: self.depth.clone(), + tx: tx.clone(), + files: Arc::clone(&files), + folders: Arc::clone(&folders), + properties: self.properties.clone(), + service: self.service, + tasks_active: Arc::clone(&tasks_active), + })); + } else if *tasks_active.lock().await <= 0 { + dbg!("brek"); + break; + } } // Wait for all tasks to complete for handle in handles { - handle.await; + let _ = handle.await; } Ok(( @@ -89,3 +81,46 @@ impl<'a> Enumerator<'a> { )) } } + +struct EnumeratorTask<'a> { + path: String, + depth: u16, + tx: UnboundedSender, + files: Arc>>, + folders: Arc>>, + properties: Vec, + service: &'a Service, + tasks_active: Arc>, +} + +async fn enumerator_task<'a, 'b>(data: EnumeratorTask<'a, 'b>) -> task::JoinHandle<()> { + let current_depth = path::get_depth(&data.path); + *data.tasks_active.lock().await += 1; + + tokio::task::spawn(async move { + let res = ReqProps::new(data.service) + .set_path(data.path.clone()) + // .set_depth(self.depth) + .get_properties(data.properties) + .send() + .await + .unwrap(); + + dbg!(&res); + for obj in res.responses { + if obj.is_dir() { + // Avoid enumerating the same folder multiple times + if obj.abs_path() != data.path { + // depth deeper than current + self.depth + if obj.path_depth() > current_depth + data.depth { + data.tx.send(obj.abs_path().to_owned()).unwrap(); + } + data.folders.lock().await.push(obj); + } + } else { + data.files.lock().await.push(obj); + } + } + *data.tasks_active.lock().await -= 1; + }) +} diff --git a/src/services/req_props.rs b/src/services/req_props.rs index 76ce6c0..1234d08 100644 --- a/src/services/req_props.rs +++ b/src/services/req_props.rs @@ -1,5 +1,6 @@ use crate::services::service::{Request, Service}; use crate::store::object::Obj; +use crate::utils::path; use serde::Deserialize; use serde_xml_rs::from_str; @@ -49,7 +50,27 @@ pub struct Response { impl Response { pub fn is_dir(&self) -> bool { - todo!("is dir reponse") + self.href.ends_with("/") + } + + pub fn abs_path(&self) -> &str { + let path = self + .href + .strip_prefix("/remote.php/dav/files") + .expect(&format!( + "Unexpected result when requesting props. Cannot strip from {}", + self.href + )); + + if path.ends_with('/') { + path.strip_suffix('/').expect("Checked before") + } else { + path + } + } + + pub fn path_depth(&self) -> u16 { + path::get_depth(self.abs_path()) } } diff --git a/src/services/service.rs b/src/services/service.rs index 26e5dc9..6029bbf 100644 --- a/src/services/service.rs +++ b/src/services/service.rs @@ -1,6 +1,6 @@ use crate::commands::clone::UrlProps; use reqwest::{header::HeaderMap, Method, Url}; -use reqwest::{Client, ClientBuilder}; +use reqwest::{Client, ClientBuilder, RequestBuilder}; const USER_AGENT: &str = "Nextsync"; @@ -36,23 +36,31 @@ impl ClientConfig { #[derive(Clone)] pub struct Service { + /// http[s]://host.xz/remote.php/dav/files url_base: String, } impl From<&UrlProps<'_>> for Service { fn from(url_props: &UrlProps) -> Self { - todo!("Auth"); let mut url_base = if url_props.is_secure { String::from("https://") } else { String::from("http://") }; url_base.push_str(url_props.domain); + url_base.push_str("/remote.php/dav/files"); Service { url_base } } } +impl Service { + fn authenticate(&self, request: RequestBuilder) -> RequestBuilder { + request + .bearer_auth("rK5ud2NmrR8p586Th7v272HRgUcZcEKIEluOGjzQQRj7gWMMAISFTiJcFnnmnNiu2VVlENks") + } +} + pub struct Request<'a> { service: &'a Service, client: ClientConfig, @@ -81,7 +89,17 @@ impl<'a> Request<'a> { } pub async fn send(&mut self) -> Result { - todo!() + self.service + .authenticate(self.client.build().request( + self.method.clone().expect("Method must be set"), + { + let mut url = self.service.url_base.clone(); + url.push_str(&self.url.clone().expect("An url must be set")); + url + }, + )) + .send() + .await // let mut url = self // .config // .clone() @@ -95,7 +113,6 @@ impl<'a> Request<'a> { // self.client // .build() // .request(self.method.clone().expect("Method must be set"), url) - // .bearer_auth("rK5ud2NmrR8p586Th7v272HRgUcZcEKIEluOGjzQQRj7gWMMAISFTiJcFnnmnNiu2VVlENks") // .send() // .await } diff --git a/src/subcommands/clone.rs b/src/subcommands/clone.rs index 13aafe5..eec0b0c 100644 --- a/src/subcommands/clone.rs +++ b/src/subcommands/clone.rs @@ -1,4 +1,4 @@ -use clap::{Arg, ArgMatches, Command}; +use clap::{Arg, ArgAction, ArgMatches, Command}; use crate::commands; use crate::config::config::Config; @@ -22,8 +22,9 @@ pub fn create() -> Command { ) .arg( Arg::new("force_insecure") - .long("force-insecure") .short('f') + .long("force-insecure") + .action(ArgAction::SetTrue) .help("Force the connection to nextcloud to be in http (not https)") ) .arg( @@ -48,7 +49,7 @@ pub async fn handler(args: &ArgMatches) { commands::clone::CloneArgs { remote: remote.to_string(), depth: args.get_one::("depth").cloned(), - force_insecure: args.contains_id("force_insecure"), + force_insecure: *args.get_one::("force_insecure").unwrap(), }, Config::from(args.get_one::("directory")), ) diff --git a/src/utils/path.rs b/src/utils/path.rs index 06ef1e1..bd6577a 100644 --- a/src/utils/path.rs +++ b/src/utils/path.rs @@ -54,3 +54,8 @@ pub fn normalize_path>(path: P) -> PathBuf { } normalized } + +/// Calculate depth of a path +pub fn get_depth(path: &str) -> u16 { + path.split("/").count() as u16 +}