feat(status): started status without nsobject fully implemented
This commit is contained in:
parent
fb57bd6565
commit
c7bde2c8d5
@ -1,2 +1,3 @@
|
||||
pub mod add;
|
||||
pub mod init;
|
||||
pub mod status;
|
||||
|
||||
162
src/commands/status.rs
Normal file
162
src/commands/status.rs
Normal file
@ -0,0 +1,162 @@
|
||||
use crate::config::config::Config;
|
||||
use crate::store::{
|
||||
nsobject::NsObject,
|
||||
object::{Obj, ObjStatus},
|
||||
};
|
||||
use crate::utils::path;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use threadpool::ThreadPool;
|
||||
|
||||
pub struct StatusArgs {
|
||||
pub nostyle: bool,
|
||||
}
|
||||
|
||||
struct ObjStatuses {
|
||||
created: Arc<Mutex<HashMap<String, Obj>>>,
|
||||
modified: Arc<Mutex<HashMap<String, Obj>>>,
|
||||
deleted: Arc<Mutex<HashMap<String, Obj>>>,
|
||||
}
|
||||
|
||||
impl ObjStatuses {
|
||||
fn new() -> Self {
|
||||
ObjStatuses {
|
||||
created: Arc::new(Mutex::new(HashMap::new())),
|
||||
modified: Arc::new(Mutex::new(HashMap::new())),
|
||||
deleted: Arc::new(Mutex::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
fn push_created(&self, key: String, value: Obj) {
|
||||
self.created.lock().unwrap().insert(key, value);
|
||||
}
|
||||
|
||||
fn push_modified(&self, key: String, value: Obj) {
|
||||
self.modified.lock().unwrap().insert(key, value);
|
||||
}
|
||||
|
||||
fn push_deleted(&self, key: String, value: Obj) {
|
||||
self.deleted.lock().unwrap().insert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exec(args: StatusArgs, config: Config) {
|
||||
// use root of repo if no custom path has been set by the command
|
||||
let root = if config.is_custom_execution_path {
|
||||
config.execution_path.clone()
|
||||
} else {
|
||||
config.get_root_unsafe().to_path_buf()
|
||||
};
|
||||
|
||||
let pool = ThreadPool::new(4);
|
||||
let repo_root = config.get_root_unsafe().clone();
|
||||
let res = Arc::new(ObjStatuses::new());
|
||||
|
||||
let pool_clone = pool.clone();
|
||||
let res_clone = Arc::clone(&res);
|
||||
pool.execute(move || {
|
||||
compare_dir(pool_clone, &repo_root, &root, res_clone);
|
||||
});
|
||||
|
||||
pool.join();
|
||||
|
||||
for entry in res.created.lock().unwrap().iter() {
|
||||
println!("created: {}", entry.0);
|
||||
}
|
||||
for entry in res.modified.lock().unwrap().iter() {
|
||||
println!("modified: {}", entry.0);
|
||||
}
|
||||
for entry in res.deleted.lock().unwrap().iter() {
|
||||
println!("deleted: {}", entry.0);
|
||||
}
|
||||
// find moved and copied
|
||||
}
|
||||
|
||||
///
|
||||
///
|
||||
/// * `pool`:
|
||||
/// * `root_path`: path of the repo's root
|
||||
/// * `path`: path we should analyze
|
||||
/// * `res`: the struct in which we should store the response
|
||||
fn compare_dir(pool: ThreadPool, root_path: &PathBuf, path: &PathBuf, res: Arc<ObjStatuses>) {
|
||||
let entries = match fs::read_dir(path) {
|
||||
Ok(entries) => entries,
|
||||
Err(err) => {
|
||||
eprintln!("Failed to read {} ({err})", path.display());
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut local_files = HashMap::new();
|
||||
let mut local_dirs = HashMap::new();
|
||||
|
||||
// Read locals files and folders
|
||||
for entry in entries {
|
||||
let entry = match entry {
|
||||
Ok(entry) => entry,
|
||||
Err(err) => {
|
||||
eprintln!("Failed to read entry {err}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let repo_relative_entry = path::to_repo_relative(&entry.path(), root_path);
|
||||
let local_obj = Obj::from_local_path(&repo_relative_entry);
|
||||
|
||||
if entry.path().is_dir() {
|
||||
if entry.path().ends_with(".nextsync") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if local_obj.is_new() {
|
||||
// TODO: opti move files in new directory
|
||||
res.push_created(local_obj.cpy_path(), local_obj);
|
||||
} else {
|
||||
local_dirs.insert(local_obj.cpy_path(), local_obj);
|
||||
|
||||
// Analyze sub folder
|
||||
let pool_clone = pool.clone();
|
||||
let root_path_clone = root_path.clone();
|
||||
let res_clone = Arc::clone(&res);
|
||||
pool.execute(move || {
|
||||
compare_dir(pool_clone, &root_path_clone, &entry.path(), res_clone);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if local_obj.is_new() {
|
||||
res.push_created(local_obj.cpy_path(), local_obj);
|
||||
} else {
|
||||
local_files.insert(local_obj.cpy_path(), local_obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read ns objects to find deleted
|
||||
let entries = NsObject::from_local_path(&path);
|
||||
for entry in entries.iter() {
|
||||
if entry.is_file() {
|
||||
match local_files.get(entry.get_obj_path().to_str().unwrap()) {
|
||||
None => res.push_deleted(path::to_string(entry.get_obj_path()), {
|
||||
let mut obj = entry.get_local_obj();
|
||||
obj.set_status(ObjStatus::Deleted);
|
||||
obj
|
||||
}),
|
||||
Some(local_obj) if local_obj != entry => {
|
||||
res.push_modified(local_obj.cpy_path(), local_obj.clone())
|
||||
}
|
||||
Some(_) => (),
|
||||
}
|
||||
} else {
|
||||
match local_dirs.get(entry.get_obj_path().to_str().unwrap()) {
|
||||
None => res.push_deleted(path::to_string(entry.get_obj_path()), {
|
||||
let mut obj = entry.get_local_obj();
|
||||
obj.set_status(ObjStatus::Deleted);
|
||||
obj
|
||||
}),
|
||||
Some(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,7 @@ use std::sync::OnceLock;
|
||||
/// * `root`: path of the repo
|
||||
pub struct Config {
|
||||
pub execution_path: PathBuf,
|
||||
pub is_custom_execution_path: bool,
|
||||
root: OnceLock<Option<PathBuf>>,
|
||||
}
|
||||
|
||||
@ -15,6 +16,7 @@ impl Config {
|
||||
pub fn new() -> Self {
|
||||
Config {
|
||||
execution_path: PathBuf::from(env::current_dir().unwrap()),
|
||||
is_custom_execution_path: false,
|
||||
root: OnceLock::new(),
|
||||
}
|
||||
}
|
||||
@ -23,6 +25,7 @@ impl Config {
|
||||
match exec_path {
|
||||
Some(path) => Config {
|
||||
execution_path: PathBuf::from(path),
|
||||
is_custom_execution_path: true,
|
||||
root: OnceLock::new(),
|
||||
},
|
||||
None => Config::new(),
|
||||
|
||||
@ -4,13 +4,18 @@ mod commands;
|
||||
mod config;
|
||||
mod store;
|
||||
mod subcommands;
|
||||
mod utils;
|
||||
|
||||
fn main() {
|
||||
let app = Command::new("Nextsync")
|
||||
.version("1.0")
|
||||
.author("grimhilt")
|
||||
.about("A git-line command line tool to interact with nextcloud")
|
||||
.subcommands([subcommands::init::create(), subcommands::add::create()]);
|
||||
.subcommands([
|
||||
subcommands::init::create(),
|
||||
subcommands::add::create(),
|
||||
subcommands::status::create(),
|
||||
]);
|
||||
// .setting(clap::AppSettings::SubcommandRequiredElseHelp);
|
||||
|
||||
let matches = app.get_matches();
|
||||
@ -18,6 +23,7 @@ fn main() {
|
||||
match matches.subcommand() {
|
||||
Some(("init", args)) => subcommands::init::handler(args),
|
||||
Some(("add", args)) => subcommands::add::handler(args),
|
||||
Some(("status", args)) => subcommands::status::handler(args),
|
||||
Some((_, _)) => {}
|
||||
None => {}
|
||||
};
|
||||
|
||||
@ -1,2 +1,4 @@
|
||||
pub mod ignorer;
|
||||
pub mod nsignore;
|
||||
pub mod object;
|
||||
pub mod nsobject;
|
||||
|
||||
90
src/store/nsobject.rs
Normal file
90
src/store/nsobject.rs
Normal file
@ -0,0 +1,90 @@
|
||||
use crate::store::object::{ObjType, Obj, ObjMetadata};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
type NsObjectChilds = Vec<Box<NsObject>>;
|
||||
|
||||
pub struct NsObject {
|
||||
pub obj_type: ObjType,
|
||||
obj_path: OnceLock<PathBuf>,
|
||||
childs: OnceLock<NsObjectChilds>,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl NsObject {
|
||||
pub fn from_local_path(path: &PathBuf) -> Self {
|
||||
NsObject {
|
||||
obj_type: ObjType::Obj,
|
||||
obj_path: OnceLock::from(path.to_path_buf()),
|
||||
childs: OnceLock::new(),
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_hash(hash: &str) -> Self {
|
||||
NsObject {
|
||||
obj_type: ObjType::Obj,
|
||||
obj_path: OnceLock::new(),
|
||||
childs: OnceLock::new(),
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_childs(&self) -> &NsObjectChilds {
|
||||
self.childs.get_or_init(|| {
|
||||
if self.obj_type != ObjType::Tree {
|
||||
Vec::new()
|
||||
} else {
|
||||
todo!()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_obj_path(&self) -> &PathBuf {
|
||||
self.obj_path.get_or_init(|| {
|
||||
todo!()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_file(&self) -> bool {
|
||||
self.obj_type == ObjType::Blob
|
||||
}
|
||||
|
||||
pub fn is_dir(&self) -> bool {
|
||||
self.obj_type == ObjType::Tree
|
||||
}
|
||||
|
||||
pub fn get_local_obj(&self) -> Obj {
|
||||
let mut obj = Obj::from_local_path(self.get_obj_path());
|
||||
obj.set_type(self.obj_type.clone());
|
||||
obj
|
||||
}
|
||||
|
||||
pub fn get_metadata(&self) -> Option<ObjMetadata> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> NsObjectIter<'_> {
|
||||
NsObjectIter {
|
||||
nsobject: self,
|
||||
index: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NsObjectIter<'a> {
|
||||
nsobject: &'a NsObject,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for NsObjectIter<'a> {
|
||||
type Item = &'a NsObject;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.index += 1;
|
||||
match self.nsobject.get_childs().get(self.index - 1) {
|
||||
None => None,
|
||||
Some(obj) => Some(&**obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
135
src/store/object.rs
Normal file
135
src/store/object.rs
Normal file
@ -0,0 +1,135 @@
|
||||
use crate::store::nsobject::NsObject;
|
||||
use crate::utils::path;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::OnceLock;
|
||||
use std::time::SystemTime;
|
||||
|
||||
const MAX_SIZE_TO_USE_HASH: u64 = 12 * 1024 * 1024;
|
||||
|
||||
pub struct ObjMetadata {
|
||||
size: u64,
|
||||
modified: Option<SystemTime>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub enum ObjType {
|
||||
Obj,
|
||||
Blob,
|
||||
Tree,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub enum ObjStatus {
|
||||
Undefined,
|
||||
Created,
|
||||
Moved,
|
||||
Copied,
|
||||
Deleted,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Obj {
|
||||
obj_type: ObjType,
|
||||
status: OnceLock<ObjStatus>,
|
||||
/// path of the object from root
|
||||
obj_path: PathBuf,
|
||||
}
|
||||
|
||||
impl Obj {
|
||||
pub fn from_local_path(path: &PathBuf) -> Self {
|
||||
// todo set state
|
||||
Obj {
|
||||
obj_type: ObjType::Obj,
|
||||
status: OnceLock::new(),
|
||||
obj_path: path.to_path_buf(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_status(&self) -> &ObjStatus {
|
||||
self.status.get_or_init(|| {
|
||||
// read path
|
||||
ObjStatus::Created
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_status(&mut self, status: ObjStatus) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn set_type(&mut self, obj_type: ObjType) {
|
||||
self.obj_type = obj_type;
|
||||
}
|
||||
|
||||
pub fn is_new(&self) -> bool {
|
||||
self.get_status() == &ObjStatus::Created
|
||||
}
|
||||
|
||||
pub fn cpy_path(&self) -> String {
|
||||
path::to_string(&self.obj_path)
|
||||
}
|
||||
|
||||
pub fn get_metadata(&self) -> Option<ObjMetadata> {
|
||||
let metadata = match fs::metadata(&self.obj_path) {
|
||||
Ok(m) => m,
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
"Failed to read metadata of {} ({})",
|
||||
self.obj_path.display(),
|
||||
err
|
||||
);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
Some(ObjMetadata {
|
||||
size: metadata.len(),
|
||||
modified: metadata.modified().ok(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<NsObject> for Obj {
|
||||
fn eq(&self, other: &NsObject) -> bool {
|
||||
if self.obj_type != other.obj_type {
|
||||
eprintln!("Trying to compare different obj type");
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.obj_type == ObjType::Tree {
|
||||
panic!("fatal: cannot comparing folders");
|
||||
}
|
||||
|
||||
let obj_metadata = self.get_metadata();
|
||||
let nsobj_metadata = other.get_metadata();
|
||||
|
||||
// If not metadata exist considere it is not the same
|
||||
if obj_metadata.is_none() || nsobj_metadata.is_none() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let obj_metadata = obj_metadata.unwrap();
|
||||
let nsobj_metadata = nsobj_metadata.unwrap();
|
||||
|
||||
// if we have modified and it is the same it must be the same file
|
||||
if obj_metadata.modified.is_some() && nsobj_metadata.modified.is_some() {
|
||||
if obj_metadata.modified.unwrap() == nsobj_metadata.modified.unwrap() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// if size is same considere comparing hash if file is small
|
||||
if obj_metadata.size == obj_metadata.size {
|
||||
if obj_metadata.size < MAX_SIZE_TO_USE_HASH
|
||||
&& nsobj_metadata.size < MAX_SIZE_TO_USE_HASH
|
||||
{
|
||||
todo!()
|
||||
// return self.get_file_hash() == other.get_file_hash();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,2 +1,3 @@
|
||||
pub mod add;
|
||||
pub mod init;
|
||||
pub mod status;
|
||||
|
||||
25
src/subcommands/status.rs
Normal file
25
src/subcommands/status.rs
Normal file
@ -0,0 +1,25 @@
|
||||
use clap::{Arg, ArgMatches, Command};
|
||||
|
||||
use crate::commands;
|
||||
use crate::commands::status::StatusArgs;
|
||||
use crate::config::config::Config;
|
||||
|
||||
pub fn create() -> Command {
|
||||
Command::new("status")
|
||||
.arg(Arg::new("directory").num_args(1).value_name("DIRECTORY"))
|
||||
.arg(
|
||||
Arg::new("nostyle")
|
||||
.long("nostyle")
|
||||
.help("Status with minium information and style"),
|
||||
)
|
||||
.about("Show the working tree status")
|
||||
}
|
||||
|
||||
pub fn handler(args: &ArgMatches) {
|
||||
commands::status::exec(
|
||||
StatusArgs {
|
||||
nostyle: args.contains_id("nostyle"),
|
||||
},
|
||||
Config::from(args.get_one::<String>("directory")),
|
||||
);
|
||||
}
|
||||
1
src/utils.rs
Normal file
1
src/utils.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod path;
|
||||
9
src/utils/path.rs
Normal file
9
src/utils/path.rs
Normal file
@ -0,0 +1,9 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub fn to_repo_relative(path: &PathBuf, root: &PathBuf) -> PathBuf {
|
||||
path.strip_prefix(root).unwrap().to_path_buf()
|
||||
}
|
||||
|
||||
pub fn to_string(path: &PathBuf) -> String {
|
||||
path.to_str().unwrap().to_string()
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user