Compare commits

...

2 Commits

Author SHA1 Message Date
grimhilt
d442bf689c feat(clone): read url 2024-09-15 18:16:46 +02:00
grimhilt
3af7a00b0f feat(reqprops): deserialize response 2024-09-15 16:16:38 +02:00
9 changed files with 233 additions and 20 deletions

View File

@ -4,3 +4,4 @@ pub mod status;
pub mod reset;
pub mod push;
pub mod test;
pub mod clone;

118
src/commands/clone.rs Normal file
View File

@ -0,0 +1,118 @@
use crate::config::config::Config;
use regex::Regex;
pub struct CloneArgs {
pub remote: String,
pub depth: Option<String>,
}
pub fn exec(args: CloneArgs, config: Config) {
get_url_props(&args.remote);
}
struct UrlProps<'a> {
is_secure: bool,
domain: &'a str,
path: &'a str,
}
impl UrlProps<'_> {
fn new() -> Self {
UrlProps {
is_secure: true,
domain: "",
path: "",
}
}
}
pub fn get_url_props(url: &str) -> UrlProps {
let mut url_props = UrlProps::new();
// Match protocol and domain
let re = Regex::new(r"((?<protocol>https?)://)?(?<domain>[^/]*)").unwrap();
let captures = re.captures(url).expect("fatal: invalid url");
// Assume is secure
let protocol = captures.name("protocol").map_or("https", |m| m.as_str());
url_props.is_secure = protocol == "https";
url_props.domain = captures
.name("domain")
.map(|m| m.as_str())
.expect("fatal: domain not found");
// Get rest of url
let end_of_domain_idx = captures
.name("domain")
.expect("Already unwraped before")
.end();
let rest_of_url = &url[end_of_domain_idx..];
// Try webdav url
if let Some(rest_of_url) = rest_of_url.strip_prefix("/remote.php/dav/files") {
let re = Regex::new(r"[^\/]*(?<path>.*)").unwrap();
url_props.path = re
.captures(rest_of_url)
.expect("fatal: invalid webdav url")
.name("path")
.map_or("/", |m| m.as_str());
return url_props;
}
// Try 'dir' argument
let re = Regex::new(r"\?dir=(?<path>[^&]*)").unwrap();
if let Some(captures) = re.captures(rest_of_url) {
url_props.path = captures.name("path").map_or("/", |m| m.as_str());
return url_props;
}
// Try path next to domain
if rest_of_url.chars().nth(0).expect("fatal: invalid url") == '/' {
url_props.path = rest_of_url;
return url_props;
}
panic!("fatal: invalid url (cannot found path)");
}
#[cfg(test)]
mod tests {
use super::*;
const DOMAIN: &str = "nextcloud.com";
const SUBDOMAIN: &str = "nextcloud.example.com";
fn compare_url_props(url_to_test: &str, is_secure: bool, domain: &str) {
let path = "/foo/bar";
let url_props = get_url_props(url_to_test);
assert_eq!(url_props.is_secure, is_secure);
assert_eq!(url_props.domain, domain);
assert_eq!(url_props.path, path);
}
#[test]
fn get_url_props_from_browser_test() {
compare_url_props(
"https://nextcloud.com/apps/files/?dir=/foo/bar&fileid=166666",
true,
DOMAIN,
);
compare_url_props(
"https://nextcloud.com/apps/files/files/625?dir=/foo/bar",
true,
DOMAIN,
);
}
#[test]
fn get_url_props_direct_url_test() {
compare_url_props("https://nextcloud.example.com/foo/bar", true, SUBDOMAIN);
compare_url_props("http://nextcloud.example.com/foo/bar", false, SUBDOMAIN);
compare_url_props("nextcloud.example.com/foo/bar", true, SUBDOMAIN);
}
#[test]
fn get_url_props_with_port_test() {
compare_url_props("localhost:8080/foo/bar", true, "localhost:8080");
}
}

View File

@ -18,9 +18,12 @@ pub fn exec(args: TestArgs, config: Config) {
// println!("{:?}", config);
// Ok(())
let req = ReqProps::new("")
tokio::runtime::Runtime::new().unwrap().block_on(async {
let mut req = ReqProps::new("")
.set_config(&config)
.get_properties(vec![Props::CreationDate, Props::GetLastModified])
.send();
dbg!(req);
.get_properties(vec![Props::CreationDate, Props::LastModified]);
req.send().await;
});
// dbg!(req);
}

View File

@ -19,6 +19,7 @@ fn main() {
subcommands::reset::create(),
subcommands::push::create(),
subcommands::test::create(),
subcommands::clone::create(),
]);
// .setting(clap::AppSettings::SubcommandRequiredElseHelp);
@ -31,6 +32,7 @@ fn main() {
Some(("reset", args)) => subcommands::reset::handler(args),
Some(("push", args)) => subcommands::push::handler(args),
Some(("test", args)) => subcommands::test::handler(args),
Some(("clone", args)) => subcommands::clone::handler(args),
Some((_, _)) => {}
None => {}
};

View File

@ -1,15 +1,17 @@
use crate::services::service::Service;
use crate::store::object::Obj;
use crate::config::config::Config;
use serde::Deserialize;
use serde_xml_rs::from_str;
pub enum Props {
CreationDate,
GetLastModified,
GetETag,
GetContentType,
LastModified,
ETag,
ContentType,
RessourceType,
GetContentLength,
GetContentLanguage,
ContentLength,
ContentLanguage,
DisplayName,
FileId,
Permissions,
@ -23,11 +25,41 @@ pub enum Props {
ContainedFileCountm,
}
#[derive(Deserialize, Debug)]
struct Propstat {
#[serde(rename = "prop")]
prop: Prop,
}
#[derive(Deserialize, Debug)]
struct Prop {
#[serde(rename = "getlastmodified")]
last_modified: Option<String>,
#[serde(rename = "getcontentlength")]
content_length: Option<u64>,
}
#[derive(Deserialize, Debug)]
struct Response {
#[serde(rename = "href")]
href: String,
#[serde(rename = "propstat")]
propstat: Propstat,
}
#[derive(Deserialize, Debug)]
struct Multistatus {
#[serde(rename = "response")]
responses: Vec<Response>,
}
impl From<&Props> for &str {
fn from(variant: &Props) -> Self {
match variant {
Props::CreationDate => "<d:creationdate />",
Props::GetLastModified => "<d:getlastmodified />",
Props::LastModified => "<d:getlastmodified />",
_ => todo!("Props conversion not implemented"),
}
}
@ -73,8 +105,18 @@ impl ReqProps {
xml
}
pub fn send(&mut self) {
self.service.send();
pub async fn send(&mut self) {
dbg!("send");
let res = self.service.send().await;
dbg!("Sent");
let text = res.unwrap().text().await.unwrap();
self.parse(&text);
}
fn parse (&self, xml: &str) {
dbg!("Parsing");
let multistatus: Multistatus = from_str(&xml).unwrap();
dbg!(multistatus);
}
}

View File

@ -1,5 +1,5 @@
use crate::config::config::Config;
use reqwest::blocking::{Client, ClientBuilder};
use reqwest::{Client, ClientBuilder};
use reqwest::{header::HeaderMap, Method, Url};
const USER_AGENT: &str = "Nextsync";
@ -65,9 +65,10 @@ impl Service {
self
}
pub fn send(&mut self) {
pub async fn send(&mut self) -> Result<reqwest::Response, reqwest::Error> {
let mut url = self
.config.clone()
.config
.clone()
.expect("A config must be provided to service")
.get_nsconfig()
.get_remote("origin")
@ -75,11 +76,11 @@ impl Service {
.expect("An url must be set on the remote");
url.push_str(&self.url.clone().unwrap());
dbg!(self
.client
self.client
.build()
.request(self.method.clone().expect("Method must be set"), url,)
.request(self.method.clone().expect("Method must be set"), url)
.bearer_auth("rK5ud2NmrR8p586Th7v272HRgUcZcEKIEluOGjzQQRj7gWMMAISFTiJcFnnmnNiu2VVlENks")
.send());
.send()
.await
}
}

View File

@ -4,3 +4,4 @@ pub mod reset;
pub mod status;
pub mod push;
pub mod test;
pub mod clone;

45
src/subcommands/clone.rs Normal file
View File

@ -0,0 +1,45 @@
use clap::{Arg, Command, ArgMatches};
use crate::commands;
use crate::config::config::Config;
pub fn create() -> Command {
// let remote_desc = sized_str(&format!("The repository to clone from. See the NEXTSYNC URLS section below for more information on specifying repositories."));
// let depth_desc = sized_str(&format!("Depth of the recursive fetch of object properties. This value should be lower when there are a lot of files per directory and higher when there are a lot of subdirectories with fewer files. (Default: {})", clone::DEPTH));
Command::new("clone")
.arg(
Arg::new("remote")
.required(true)
.num_args(1)
.value_name("REMOTE")
//.help(_desc)
)
.arg(
Arg::new("depth")
.short('d')
.long("depth")
.required(false)
.num_args(1)
//.help(&depth_desc)
)
.arg(
Arg::new("directory")
.required(false)
.num_args(1)
.value_name("DIRECTORY")
)
.about("Clone a repository into a new directory")
.after_help("NEXTSYNC URLS\nThe following syntaxes may be used:\n\t- user@host.xz/path/to/repo\n\t- http[s]://host.xz/apps/files/?dir=/path/to/repo&fileid=111111\n\t- [http[s]://]host.xz/remote.php/dav/files/user/path/to/repo\n")
}
pub fn handler(args: &ArgMatches) {
if let Some(val) = args.get_one::<String>("directory") {
// global::global::set_dir_path(String::from(val.to_string()));
}
if let Some(remote) = args.get_one::<String>("remote") {
commands::clone::exec(commands::clone::CloneArgs {
remote: remote.to_string(),
depth: args.get_one::<String>("depth").cloned(),
}, Config::new());
}
}

View File

@ -26,7 +26,7 @@ fn compare_vect(vec1: Vec<Obj>, vec2: Vec<&str>, config: &Config) {
}
fn status_expected(config: &Config, staged: Vec<&str>, not_staged: Vec<&str>) {
let res = get_obj_changes(&DEFAULT_STATUS_ARG, config);
let res = get_obj_changes(config);
assert_eq!(res.staged.len(), staged.len());
assert_eq!(res.not_staged.len(), not_staged.len());