feat(push): save objects
This commit is contained in:
parent
8f636b4bf7
commit
a69a71d843
@ -2,3 +2,4 @@ pub mod add;
|
||||
pub mod init;
|
||||
pub mod status;
|
||||
pub mod reset;
|
||||
pub mod push;
|
||||
|
@ -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
20
src/commands/push.rs
Normal 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();
|
||||
}
|
@ -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
|
||||
|
@ -1,7 +1,6 @@
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::OnceLock;
|
||||
use crate::store::structs;
|
||||
|
||||
///
|
||||
/// # Parameters
|
||||
|
@ -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 => {}
|
||||
};
|
||||
|
@ -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(())
|
||||
|
@ -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>>;
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
13
src/subcommands/push.rs
Normal 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());
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
||||
use clap::{ArgMatches, Command};
|
||||
|
||||
use crate::commands;
|
||||
use crate::commands::reset::ResetArgs;
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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]
|
||||
|
Loading…
Reference in New Issue
Block a user