From 30004ebd8b67fb09a2235b33b3a8eb845181fbde Mon Sep 17 00:00:00 2001 From: grimhilt Date: Thu, 20 Jul 2023 00:59:57 +0200 Subject: [PATCH] remote-diff draft --- src/commands.rs | 1 + src/commands/remote_diff.rs | 94 +++++++++++++++++++++++++++++++++++++ src/main.rs | 31 ++++++++++++ src/services/api.rs | 8 +++- src/services/req_props.rs | 15 ++++++ src/store/head.rs | 11 +++-- src/store/object.rs | 73 ++++++++++++++++++++++++++++ 7 files changed, 228 insertions(+), 5 deletions(-) create mode 100644 src/commands/remote_diff.rs diff --git a/src/commands.rs b/src/commands.rs index c346a7e..2439b8c 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -5,3 +5,4 @@ pub mod reset; pub mod clone; pub mod push; pub mod config; +pub mod remote_diff; diff --git a/src/commands/remote_diff.rs b/src/commands/remote_diff.rs new file mode 100644 index 0000000..bd5b14c --- /dev/null +++ b/src/commands/remote_diff.rs @@ -0,0 +1,94 @@ +use crate::services::api::ApiError; +use crate::services::req_props::{ReqProps, ObjProps}; +use crate::store::object::Object; +use crate::utils::path; +use std::fs::canonicalize; +use std::path::PathBuf; + +pub struct RemoteDiffArgs { + pub path: Option, +} + +pub fn remote_diff(args: RemoteDiffArgs) { + let path = { + if let Some(path) = args.path { + let mut cur = path::current().unwrap(); + cur.push(path); + let canonic = canonicalize(cur).ok().unwrap(); + dbg!(&canonic); + let ok = canonic.strip_prefix(path::repo_root()); + dbg!(&ok); + + PathBuf::from("/") + } else { + PathBuf::from("/") + } + }; + + let mut folders: Vec = vec![ObjProps { + contentlength: None, + href: None, + lastmodified: None, + relative_s: Some(path.to_str().unwrap().to_owned()), + }]; + let mut files: Vec = vec![]; + + while folders.len() > 0 { + let folder = folders.pop().unwrap(); + + let res = ReqProps::new() + .set_url(&folder.relative_s.unwrap()) + .gethref() + .getlastmodified() + .send_req_multiple(); + + let objs = match res { + Ok(o) => o, + Err(ApiError::IncorrectRequest(err)) => { + eprintln!("fatal: {}", err.status()); + std::process::exit(1); + }, + Err(ApiError::EmptyError(_)) => { + eprintln!("Failed to get body"); + vec![] + } + Err(ApiError::RequestError(err)) => { + eprintln!("fatal: {}", err); + std::process::exit(1); + }, + Err(ApiError::Unexpected(_)) => todo!() + }; + + + + let mut iter = objs.iter(); + // todo opti store date of root + let root = iter.next(); + + for obj in iter { + let mut o = Object::new(&obj.clone().relative_s.unwrap()); + let exist = o.exists(); + + let should_pull = { + if exist { + o.read() + .is_older(obj.lastmodified.unwrap().timestamp()) + } else { + true + } + }; + + if should_pull { + println!("should pull {}", obj.clone().relative_s.unwrap()); + if obj.href.clone().unwrap().chars().last().unwrap() == '/' { + folders.push(obj.clone()); + } else { + files.push(obj.clone()); + } + } + + } + + } +} + diff --git a/src/main.rs b/src/main.rs index 016b2c6..69836e3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,8 @@ use clap::{App, Arg, SubCommand}; use textwrap::{fill, Options}; + use crate::commands::add::AddArgs; +use crate::commands::remote_diff::RemoteDiffArgs; mod commands; mod utils; @@ -96,6 +98,20 @@ fn main() { .value_name("VALUE") ) ) + .subcommand( + SubCommand::with_name("remote-diff") + .arg( + Arg::with_name("path") + .required(false) + .takes_value(true) + .value_name("PATH") + .help("The path to pull."), + ) + .about("Fetch new and modifed files from the nextcloud server.") + ) + .subcommand( + SubCommand::with_name("test") + ) .get_matches(); if let Some(matches) = matches.subcommand_matches("init") { @@ -134,6 +150,21 @@ fn main() { } } } + } else if let Some(matches) = matches.subcommand_matches("remote-diff") { + commands::remote_diff::remote_diff(RemoteDiffArgs { + path: { + if let Some(mut path) = matches.values_of("path") { + match path.next() { + Some(p) => Some(String::from(p)), + None => None, + } + } else { + None + } + }, + }); + } else if let Some(_) = matches.subcommand_matches("test") { + } } diff --git a/src/services/api.rs b/src/services/api.rs index 39cc8e4..cf551b3 100644 --- a/src/services/api.rs +++ b/src/services/api.rs @@ -63,7 +63,9 @@ impl ApiBuilder { url.push_str(username.unwrap()); url.push_str(&root); url.push_str("/"); - url.push_str(path); + if path != "/" { + url.push_str(path); + } self.request = Some(self.client.request(method, url)); self } @@ -75,7 +77,9 @@ impl ApiBuilder { url.push_str(&api_props.username); url.push_str(&api_props.root); url.push_str("/"); - url.push_str(p); + if p != "/" { + url.push_str(p); + } self.request = Some(self.client.request(meth, url)); self } diff --git a/src/services/req_props.rs b/src/services/req_props.rs index dbde423..02135cc 100644 --- a/src/services/req_props.rs +++ b/src/services/req_props.rs @@ -3,6 +3,8 @@ use chrono::{Utc, DateTime}; use reqwest::{Method, Response, Error}; use xml::reader::{EventReader, XmlEvent}; use reqwest::header::HeaderValue; +use crate::commands::clone::get_url_props; +use crate::commands::config; use crate::utils::time::parse_timestamp; use crate::utils::api::{get_relative_s, ApiProps}; use crate::services::api::{ApiBuilder, ApiError}; @@ -55,6 +57,19 @@ impl ReqProps { } pub fn set_url(&mut self, url: &str) -> &mut ReqProps { + let remote = match config::get("remote") { + Some(r) => r, + None => { + eprintln!("fatal: unable to find a remote"); + std::process::exit(1); + } + }; + let (host, username, root) = get_url_props(&remote); + self.api_props = Some(ApiProps { + host, + username: username.unwrap().to_owned(), + root: root.to_owned(), + }); self.api_builder.build_request(Method::from_bytes(b"PROPFIND").unwrap(), url); self } diff --git a/src/store/head.rs b/src/store/head.rs index 0736d14..8754fb8 100644 --- a/src/store/head.rs +++ b/src/store/head.rs @@ -1,10 +1,16 @@ use std::fs::OpenOptions; +use std::path::PathBuf; use std::io::{self, Write}; use crate::utils::{read, path}; -pub fn add_line(line: String) -> io::Result<()> { +pub fn path() -> PathBuf { let mut root = path::nextsync(); root.push("HEAD"); + root +} + +pub fn add_line(line: String) -> io::Result<()> { + let root = path(); let mut file = OpenOptions::new() .read(true) @@ -17,8 +23,7 @@ pub fn add_line(line: String) -> io::Result<()> { } pub fn rm_line(line: &str) -> io::Result<()> { - let mut root = path::nextsync(); - root.push("HEAD"); + let root = path(); read::rm_line(root, line)?; Ok(()) } diff --git a/src/store/object.rs b/src/store/object.rs index 10ce3da..fa9e959 100644 --- a/src/store/object.rs +++ b/src/store/object.rs @@ -4,11 +4,84 @@ use std::fs::{self, OpenOptions}; use crypto::sha1::Sha1; use crypto::digest::Digest; use std::io::{Seek, SeekFrom, Read}; +use crate::utils::time::parse_timestamp; +use crate::store::head; use crate::utils::{read, path}; pub mod tree; pub mod blob; +pub struct Object { + path: PathBuf, + hash: String, + obj_p: PathBuf, + ts: Option +} + +impl Object { + pub fn new(path: &str) -> Object { + let path = match path.chars().next_back() == "/".chars().next() { + true => { + let mut new = path.chars(); + new.next_back(); + new.as_str() + }, + false => path, + }; + if path == "" { + return Object { + path: PathBuf::from("/"), + hash: String::from(""), + obj_p: head::path(), + ts: None, + } + } + + let mut hasher = Sha1::new(); + hasher.input_str(path); + let hash = hasher.result_str(); + + let (dir, res) = hash.split_at(2); + + let mut obj_p = path::objects(); + obj_p.push(dir); + obj_p.push(res); + + Object { + path: PathBuf::from(path), + hash, + obj_p, + ts: None, + } + } + + pub fn read(&mut self) -> &mut Object { + match read::read_lines(&self.obj_p) { + Ok(mut reader) => { + if let Some(Ok(line)) = reader.next() { + let mut data = line.rsplit(' '); + if data.clone().count() >= 2 { + self.ts = Some(data.next().unwrap().parse::().unwrap()) + } + } + }, + Err(err) => { + eprintln!("error reading object {}: {}", self.obj_p.display(), err); + }, + }; + self + } + + pub fn exists(&mut self) -> bool { + self.obj_p.exists() + } + + pub fn is_older(&mut self, ts: i64) -> bool { + // todo be aware of the diff of ts format + ts > self.ts.expect("Should be read before used") / 1000 + } +} + /// Returns (line, hash, name) /// /// # Examples