feat(push): save objects

This commit is contained in:
grimhilt 2024-09-12 23:22:20 +02:00
parent 8f636b4bf7
commit a69a71d843
16 changed files with 230 additions and 68 deletions

View File

@ -2,3 +2,4 @@ pub mod add;
pub mod init;
pub mod status;
pub mod reset;
pub mod push;

View File

@ -2,7 +2,7 @@ use crate::config::config::Config;
use crate::store::{
ignorer::Ignorer,
indexer::Indexer,
nsobject::{self, NsObject},
nsobject::NsObject,
structs::{self, to_obj_path},
};
use crate::utils::path::to_repo_relative;

20
src/commands/push.rs Normal file
View File

@ -0,0 +1,20 @@
use crate::config::config::Config;
use crate::store::object::Obj;
use crate::store::{indexer::Indexer, structs};
pub struct PushArgs {}
pub fn exec(args: PushArgs, config: Config) {
structs::init(config.get_root());
let mut indexer = Indexer::new(config.get_root_unsafe());
let _ = indexer.load();
for indexed_obj in indexer.get_indexed_objs() {
let local_obj = Obj::from_local_path(&indexed_obj.path);
local_obj.save().unwrap();
}
indexer.clear();
let _ = indexer.save();
}

View File

@ -1,8 +1,7 @@
use crate::config::config::Config;
use crate::store::indexer::Indexer;
pub struct ResetArgs {
}
pub struct ResetArgs {}
pub fn exec(args: ResetArgs, config: Config) {
// Init ignorer

View File

@ -1,7 +1,6 @@
use std::env;
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
use crate::store::structs;
///
/// # Parameters

View File

@ -16,6 +16,7 @@ fn main() {
subcommands::add::create(),
subcommands::status::create(),
subcommands::reset::create(),
subcommands::push::create(),
]);
// .setting(clap::AppSettings::SubcommandRequiredElseHelp);
@ -26,6 +27,7 @@ fn main() {
Some(("add", args)) => subcommands::add::handler(args),
Some(("status", args)) => subcommands::status::handler(args),
Some(("reset", args)) => subcommands::reset::handler(args),
Some(("push", args)) => subcommands::push::handler(args),
Some((_, _)) => {}
None => {}
};

View File

@ -1,9 +1,11 @@
use crate::store::object::{Obj, ObjType};
use crate::store::{
object::{Obj, ObjType},
structs::{ObjPath, to_obj_path},
};
use crate::utils::path::normalize_path;
use std::cmp::Ordering;
use std::fs::File;
use std::io::{self, Write};
use std::io::{BufRead, BufReader};
use std::io::{self, Write, BufRead, BufReader};
use std::path::PathBuf;
// Custom sorting function to handle paths hierarchically
@ -28,8 +30,8 @@ fn sort_paths_hierarchically(paths: &mut Vec<PathBuf>) {
#[derive(Debug)]
pub struct IndexedObj {
obj_type: ObjType,
pub path: PathBuf,
pub obj_type: ObjType,
pub path: ObjPath,
}
pub struct Indexer {
@ -64,7 +66,7 @@ impl Indexer {
self.indexed_objs.push(IndexedObj {
obj_type: ObjType::try_from(line[0]).unwrap(),
path,
path: to_obj_path(&path),
});
}
@ -92,7 +94,6 @@ impl Indexer {
}
pub fn is_staged(&self, obj: &Obj) -> bool {
dbg!(obj);
// self.indexed_objs.iter().position(|o| &o.obj_type == obj.get_obj_type() && &o.path == obj.get_obj_path()).is_some()
self.indexed_objs
.iter()
@ -110,12 +111,12 @@ impl Indexer {
fn index_obj(&mut self, path: PathBuf, obj_type: ObjType) {
let mut path = path;
path = normalize_path(path); // normalize path (/foo/./bar => /foo/bar)
// change path to be relative to repo's root
// change path to be relative to repo's root
path = path.strip_prefix(&self.repo_root).unwrap().to_path_buf();
let indexed_obj = IndexedObj {
obj_type,
path: path.clone(),
path: to_obj_path(&path),
};
if self.is_indexed(&indexed_obj) {
@ -138,12 +139,13 @@ impl Indexer {
for obj in self.indexed_objs.iter() {
// write obj_type
file.write_all(&[obj.obj_type.clone().into()])?;
file.write(&[obj.obj_type.clone().into()])?;
// write path
file.write_all(obj.path.to_str().unwrap().as_bytes())?;
file.write(obj.path.to_str().unwrap().as_bytes())?;
file.write_all(b"\n")?;
file.write(b"\n")?;
file.flush()?;
}
Ok(())

View File

@ -2,7 +2,6 @@ use crate::store::{
object::{Obj, ObjMetadata, ObjType},
structs::{NsObjPath, ObjPath},
};
use std::path::PathBuf;
use std::sync::OnceLock;
type NsObjectChilds = Vec<Box<NsObject>>;

View File

@ -1,10 +1,14 @@
use crate::store::{nsobject::NsObject, structs::ObjPath};
use crate::store::{
nsobject::NsObject,
structs::{NsObjPath, ObjPath},
};
use crate::utils::path;
use std::env;
use std::fs;
use std::fs::{self, File, OpenOptions};
use std::io::{self, Write};
use std::path::PathBuf;
use std::sync::OnceLock;
use std::time::SystemTime;
use std::time::{SystemTime, UNIX_EPOCH};
const MAX_SIZE_TO_USE_HASH: u64 = 12 * 1024 * 1024;
@ -60,11 +64,21 @@ pub struct Obj {
impl Obj {
pub fn from_local_path(path: &ObjPath) -> Self {
// todo set state
let obj_path = path.clone();
Obj {
obj_type: ObjType::Obj,
obj_type: {
if path.abs().exists() {
if path.abs().is_dir() {
ObjType::Tree
} else {
ObjType::Blob
}
} else {
ObjType::Obj
}
},
status: OnceLock::new(),
obj_path: path.clone(),
obj_path,
}
}
@ -92,7 +106,7 @@ impl Obj {
&self.obj_type
}
pub fn get_obj_path(&self) -> &PathBuf {
pub fn get_obj_path(&self) -> &ObjPath {
&self.obj_path
}
@ -126,6 +140,83 @@ impl Obj {
path::to_string(&self.obj_path)
}
fn get_nsobj_path(&self) -> NsObjPath {
NsObjPath::from(&self.obj_path)
}
pub fn save(&self) -> io::Result<()> {
let nsobj_path = self.get_nsobj_path();
let mut file = {
if nsobj_path.exists() {
todo!("Edition of object")
} else {
let mut nsobj_dir = nsobj_path.clone();
nsobj_dir.pop();
if !nsobj_dir.exists() {
std::fs::create_dir_all(nsobj_dir)?;
}
File::create(&nsobj_path)?
}
};
// Write type
file.write(&[self.obj_type.clone().into()])?;
if self.obj_type == ObjType::Blob {
if let Some(metadata) = self.get_metadata() {
// Write size
file.write(&metadata.size.to_le_bytes())?;
// Write modified
file.write(
&metadata
.modified
.expect(&format!(
"Expect 'modified' in metadata of {} to save obj",
self.cpy_path()
))
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
.to_le_bytes(),
)?;
} else {
todo!("Cannot load metadata")
}
}
file.write_all(b"\n")?;
// Write Path
file.write_all(self.obj_path.to_str().unwrap().as_bytes())?;
file.write_all(b"\n")?;
file.flush()?;
// Save itself inside its parent
let mut parent_path = self.obj_path.clone();
parent_path.pop();
let parent_obj = Obj::from_local_path(&parent_path);
parent_obj.add_child(&self)?;
Ok(())
}
fn add_child(&self, child: &Obj) -> io::Result<()> {
let mut file = OpenOptions::new()
.write(true)
.create(true)
.append(true)
.open(self.get_nsobj_path())?;
let child_type = &[child.get_obj_type().clone().into()];
let child_path = child.obj_path.to_str().unwrap().as_bytes();
file.write(child_type)?;
file.write(child_path)?;
file.write(b"\n")?;
file.flush()?;
Ok(())
}
pub fn get_metadata(&self) -> Option<ObjMetadata> {
let metadata = match fs::metadata(&*self.obj_path) {
Ok(m) => m,

View File

@ -2,7 +2,7 @@ use crate::utils::{path, tests};
use crypto::digest::Digest;
use crypto::sha1::Sha1;
use std::ops::{Deref, DerefMut};
use std::path::PathBuf;
use std::path::{PathBuf, Path};
use std::sync::{LazyLock, Mutex, OnceLock};
// for tests only as it is mutable
@ -39,9 +39,30 @@ pub fn get_repo_root() -> PathBuf {
}
}
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, Clone)]
pub struct ObjPath {
path: PathBuf,
abs: OnceLock<PathBuf>,
}
impl ObjPath {
pub fn abs(&self) -> &PathBuf {
self.abs.get_or_init(|| {
let mut path = get_repo_root();
path.push(self.path.clone());
path
})
}
}
impl PartialEq for ObjPath {
fn eq(&self, other: &Self) -> bool {
if self.abs.get().is_some() && other.abs.get().is_some() {
self.abs() == other.abs()
} else {
self.path == other.path
}
}
}
impl Deref for ObjPath {
@ -57,14 +78,21 @@ impl DerefMut for ObjPath {
}
}
impl AsRef<Path> for ObjPath {
fn as_ref(&self) -> &Path {
&self.path
}
}
pub fn to_obj_path(path: &PathBuf) -> ObjPath {
ObjPath { path: path.clone() }
ObjPath { path: path.clone(), abs: OnceLock::new() }
}
impl Into<ObjPath> for &PathBuf {
fn into(self) -> ObjPath {
ObjPath {
path: path::to_repo_relative(self),
abs: OnceLock::from(self.clone()),
}
}
}
@ -73,6 +101,7 @@ impl Into<ObjPath> for PathBuf {
fn into(self) -> ObjPath {
ObjPath {
path: path::to_repo_relative(&self),
abs: OnceLock::from(self.clone()),
}
}
}
@ -95,11 +124,19 @@ impl DerefMut for NsObjPath {
}
}
impl AsRef<Path> for NsObjPath {
fn as_ref(&self) -> &Path {
&self.path
}
}
impl From<&str> for NsObjPath {
fn from(hash: &str) -> Self {
let (dir, res) = hash.split_at(2);
let mut ns_obj_path = get_repo_root();
ns_obj_path.push(".nextsync");
ns_obj_path.push("objects");
ns_obj_path.push(dir);
ns_obj_path.push(res);
@ -109,6 +146,16 @@ impl From<&str> for NsObjPath {
impl From<&ObjPath> for NsObjPath {
fn from(obj_path: &ObjPath) -> Self {
// NsObjPath of root is the HEAD file
if path::get_level(obj_path) == 0 {
let mut path = get_repo_root();
path.push(".nextsync");
path.push("HEAD");
return NsObjPath {
path
};
}
let mut hasher = Sha1::new();
hasher.input_str(
obj_path

View File

@ -2,3 +2,4 @@ pub mod add;
pub mod init;
pub mod reset;
pub mod status;
pub mod push;

13
src/subcommands/push.rs Normal file
View File

@ -0,0 +1,13 @@
use clap::{ArgMatches, Command};
use crate::commands;
use crate::commands::push::PushArgs;
use crate::config::config::Config;
pub fn create() -> Command {
Command::new("push").about("Push changes on nextcloud")
}
pub fn handler(args: &ArgMatches) {
commands::push::exec(PushArgs {}, Config::new());
}

View File

@ -1,4 +1,4 @@
use clap::{Arg, ArgAction, ArgMatches, Command};
use clap::{ArgMatches, Command};
use crate::commands;
use crate::commands::reset::ResetArgs;

View File

@ -1,7 +1,7 @@
mod common;
use common::client::ClientTest;
use nextsync::store::indexer::Indexer;
use nextsync::store::{structs::to_obj_path, indexer::Indexer};
use nextsync::commands::status::{get_obj_changes, StatusArgs};
use nextsync::config::config::Config;
use std::io;
@ -17,7 +17,7 @@ fn indexed_expected(indexer: &Indexer, expected: Vec<&str>) {
for obj in expected {
assert!(objs
.iter()
.position(|e| { e.path == PathBuf::from(obj) })
.position(|e| { e.path == to_obj_path(&PathBuf::from(obj)) })
.is_some());
}
}

View File

@ -1,8 +1,7 @@
use nextsync::config::config::Config;
use std::fs::OpenOptions;
use rand::{distributions::Alphanumeric, Rng};
use std::env;
use std::fs::{self, File};
use std::fs::{self, File, OpenOptions};
use std::io::{self, Write};
use std::path::PathBuf;
use std::process::{Command, Output};
@ -136,11 +135,12 @@ impl ClientTest {
pub fn add_ignore_rule(&self, rule: &str) {
let mut nsignore_path = self.volume.clone();
nsignore_path.push_str("/.nsignore");
let mut file = OpenOptions::new()
.write(true)
.create(true)
.append(true)
.open(nsignore_path).unwrap();
let mut file = OpenOptions::new()
.write(true)
.create(true)
.append(true)
.open(nsignore_path)
.unwrap();
let _ = writeln!(file, "{rule}").unwrap();
}

View File

@ -1,6 +1,7 @@
mod common;
use common::client::ClientTest;
use nextsync::store::object::Obj;
use nextsync::commands::status::{get_obj_changes, StatusArgs};
use nextsync::config::config::Config;
use std::io;
@ -8,43 +9,30 @@ use std::path::PathBuf;
const DEFAULT_STATUS_ARG: StatusArgs = StatusArgs { nostyle: false };
fn compare_vect(vec1: Vec<Obj>, vec2: Vec<&str>, config: &Config) {
for obj in vec2 {
assert!(
vec1.iter()
.position(|e| {
e.get_env_relative_path(config.get_root_unsafe()) == PathBuf::from(obj)
})
.is_some(),
"{:?}",
vec1.iter()
.map(|e| { e.get_env_relative_path(config.get_root_unsafe()) })
.collect::<Vec<PathBuf>>()
);
}
}
fn status_expected(config: &Config, staged: Vec<&str>, not_staged: Vec<&str>) {
let res = get_obj_changes(&DEFAULT_STATUS_ARG, config);
assert_eq!(res.staged.len(), staged.len());
assert_eq!(res.not_staged.len(), not_staged.len());
for obj in staged {
assert!(
res.staged
.iter()
.position(|e| {
e.get_env_relative_path(config.get_root_unsafe()) == PathBuf::from(obj)
})
.is_some(),
"{:?}",
res.staged
.iter()
.map(|e| { e.get_env_relative_path(config.get_root_unsafe()) })
.collect::<Vec<PathBuf>>()
);
}
for obj in not_staged {
assert!(
res.not_staged
.iter()
.position(|e| {
e.get_env_relative_path(config.get_root_unsafe()) == PathBuf::from(obj)
})
.is_some(),
"{:?}",
res.not_staged
.iter()
.map(|e| { e.get_env_relative_path(config.get_root_unsafe()) })
.collect::<Vec<PathBuf>>()
);
}
compare_vect(res.staged, staged, &config);
compare_vect(res.not_staged, not_staged, &config);
}
#[test]