nextsync-rust/src/store/object/blob.rs
2023-08-25 16:09:28 +02:00

387 lines
11 KiB
Rust

use std::io::{self, Read};
use std::fs::{self, File};
use std::io::Write;
use std::fs::OpenOptions;
use std::path::PathBuf;
use std::time::SystemTime;
use crypto::sha1::Sha1;
use crypto::digest::Digest;
use crate::commands::status::{LocalObj, State};
use crate::utils::path::path_buf_to_string;
use crate::utils::{path, read};
use crate::store::head;
use crate::store::object::{update_dates, add_node, rm_node};
const HASH_EMPTY: &str = "d41d8cd98f00b204e9800998ecf8427e";
pub struct Blob {
r_path: PathBuf, // relative path
a_path: PathBuf, // absolute path
hash: String, // hash of relative path
file_hash: Option<String>,
obj_p: PathBuf, // path of the object file
data: Vec<String>, // content of the blob
}
impl Blob {
pub fn new(r_path: PathBuf) -> Blob {
let mut hasher = Sha1::new();
hasher.input_str(r_path.to_str().unwrap());
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);
let root = path::repo_root();
let a_path = root.join(r_path.clone());
Blob {
r_path,
a_path,
hash,
file_hash: None,
obj_p,
data: vec![],
}
}
fn get_line_filename(&mut self) -> (String, String) {
let file_name = self.r_path.file_name().unwrap().to_str().unwrap().to_owned();
let mut line = String::from("blob");
line.push_str(" ");
line.push_str(&self.hash);
line.push_str(" ");
line.push_str(&file_name);
(line, file_name)
}
fn get_file_hash(&mut self) -> String {
if self.file_hash.is_none() {
let bytes = std::fs::read(self.a_path.clone()).unwrap();
let hash = md5::compute(&bytes);
self.file_hash = Some(format!("{:x}", hash))
}
self.file_hash.clone().unwrap()
}
fn create_blob_ref(&mut self, file_name: String, ts_remote: &str) -> io::Result<()> {
let metadata = fs::metadata(self.a_path.clone())?;
let secs = metadata
.modified()
.unwrap()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
let mut content = file_name.clone();
content.push_str(" ");
content.push_str(ts_remote);
content.push_str(" ");
content.push_str(&metadata.len().to_string());
content.push_str(" ");
content.push_str(&secs.to_string());
content.push_str(" ");
content.push_str(&self.get_file_hash());
let binding = self.obj_p.clone();
let child = binding.file_name();
self.obj_p.pop();
if !self.obj_p.clone().exists() {
fs::create_dir_all(self.obj_p.clone())?;
}
self.obj_p.push(child.unwrap().to_str().unwrap());
let mut file = OpenOptions::new()
.create_new(true)
.write(true)
.open(self.obj_p.clone())?;
writeln!(file, "{}", &content)?;
Ok(())
}
fn get_file_ref(&mut self) -> PathBuf {
let mut refs_p = path::refs();
let file_hash = self.get_file_hash().clone();
let (dir, res) = file_hash.split_at(2);
refs_p.push(dir);
if !refs_p.exists() {
fs::create_dir_all(refs_p.clone());
}
refs_p.push(res);
refs_p
}
// create a file in .nextsync/refs with the hash of this blob that
// redirect to the relative path
fn create_hash_ref(&mut self) -> io::Result<()> {
// todo check if the file has been modified for moved and copy
let refs_p = self.get_file_ref();
let mut file = OpenOptions::new()
.create(true)
.write(true)
.open(refs_p)?;
// todo deal with duplicate content
writeln!(file, "{}", self.r_path.clone().to_str().unwrap())?;
Ok(())
}
pub fn get_all_identical_blobs(&mut self) -> Vec<String> {
// an empty file is a new file not the copy of another empty file
if self.get_file_hash() == HASH_EMPTY {
return vec![];
}
let refs_p = self.get_file_ref();
let mut blobs: Vec<String> = vec![];
if let Ok(lines) = read::read_lines(refs_p) {
for line in lines {
if let Ok(l) = line {
blobs.push(l.clone());
}
}
}
blobs
}
pub fn create(&mut self, ts_remote: &str, up_parent: bool) -> io::Result<()> {
let (line, file_name) = self.get_line_filename();
// add blob reference to parent
if self.r_path.iter().count() == 1 {
head::add_line(line)?;
} else {
add_node(self.r_path.parent().unwrap(), &line)?;
}
if let Err(err) = self.create_blob_ref(file_name.clone(), ts_remote.clone()) {
eprintln!("err: saving blob ref of {}: {}", self.r_path.clone().display(), err);
}
if let Err(err) = self.create_hash_ref() {
eprintln!("err: saving hash ref of {}: {}", self.r_path.clone().display(), err);
}
// update date for all parent
if up_parent {
if let Err(err) = update_dates(self.r_path.clone(), ts_remote) {
eprintln!("err: updating parent date of {}: {}", self.r_path.clone().display(), err);
}
}
Ok(())
}
pub fn rm(&mut self) -> io::Result<()> {
let (line, _) = self.get_line_filename();
// remove blob reference to parent
if self.r_path.iter().count() == 1 {
head::rm_line(&line)?;
} else {
rm_node(self.r_path.parent().unwrap(), &line)?;
}
// remove blob object
fs::remove_file(self.obj_p.clone())?;
Ok(())
}
pub fn update(&mut self, ts_remote: &str) -> io::Result<()> {
// remove old hash ref
let mut refs_p = path::refs();
let binding = self.saved_hash();
let (dir, res) = binding.split_at(2);
refs_p.push(dir);
refs_p.push(res);
if let Err(err) = fs::remove_file(refs_p) {
eprintln!("err: removing hash ref of {}: {}", self.r_path.clone().display(), err);
}
// creating new hash ref
if let Err(err) = self.create_hash_ref() {
eprintln!("err: saving hash ref of {}: {}", self.r_path.clone().display(), err);
}
// updating content of blob's ref
let metadata = fs::metadata(self.a_path.clone())?;
let secs = metadata
.modified()
.unwrap()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
let mut content = self.saved_filename();
content.push_str(" ");
content.push_str(ts_remote);
content.push_str(" ");
content.push_str(&metadata.len().to_string());
content.push_str(" ");
content.push_str(&secs.to_string());
content.push_str(" ");
content.push_str(&self.get_file_hash());
let mut file = OpenOptions::new()
.write(true)
.open(self.obj_p.clone())?;
writeln!(file, "{}", &content)?;
Ok(())
}
pub fn read_data(&mut self) {
if self.data.len() == 0 {
if let Ok(mut file) = File::open(self.obj_p.clone()) {
let mut buffer = String::new();
let _ = file.read_to_string(&mut buffer);
let data = buffer.rsplit(' ').collect::<Vec<_>>();
for e in data {
self.data.push(String::from(e));
}
self.data.reverse();
if let Some(last) = self.data.last_mut() {
if last.ends_with("\n") {
last.pop();
}
}
}
}
}
fn saved_filename(&mut self) -> String {
self.read_data();
if self.data.len() >= 1 {
self.data[0].clone()
} else {
String::new()
}
}
pub fn saved_remote_ts(&mut self) -> String {
self.read_data();
if self.data.len() >= 2 {
self.data[1].clone()
} else {
String::new()
}
}
fn saved_local_size(&mut self) -> String {
self.read_data();
if self.data.len() >= 3 {
self.data[2].clone()
} else {
String::new()
}
}
fn saved_local_ts(&mut self) -> u64 {
self.read_data();
if self.data.len() >= 4 {
self.data[3].parse::<u64>().unwrap()
} else {
0
}
}
fn saved_hash(&mut self) -> String {
self.read_data();
if self.data.len() >= 5 {
self.data[4].clone()
} else {
String::new()
}
}
fn has_same_size(&mut self) -> bool {
let metadata = match fs::metadata(self.a_path.clone()) {
Ok(m) => m,
Err(_) => return true,
};
if self.saved_local_size() == String::new() { return true; }
metadata.len().to_string() == self.saved_local_size()
}
fn is_newer(&mut self) -> bool {
let metadata = match fs::metadata(self.a_path.clone()) {
Ok(m) => m,
Err(_) => return true,
};
let secs = metadata
.modified()
.unwrap()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
if self.saved_local_ts() == 0 { return true; }
secs > self.saved_local_ts()
}
fn has_same_hash(&mut self) -> bool {
if self.saved_hash() == String::new() { return false; }
let file_hash = self.get_file_hash().clone();
self.saved_hash() == file_hash
}
pub fn has_change(&mut self) -> bool {
!self.has_same_size() || (self.is_newer() && !self.has_same_hash())
}
pub fn get_local_obj(&mut self) -> LocalObj {
let mut path_from = None;
let state = {
let has_obj_ref = self.obj_p.clone().exists();
let blob_exists = self.a_path.clone().exists();
if has_obj_ref && !blob_exists {
State::Deleted
} else if !has_obj_ref && blob_exists {
let identical_blobs = self.get_all_identical_blobs();
if identical_blobs.len() != 0 {
let identical_blob = Blob::new(identical_blobs[0].clone().into())
.get_local_obj();
if identical_blob.state == State::Deleted {
path_from = Some(identical_blob.path);
State::Moved
} else if identical_blob.state == State::Default {
path_from = Some(identical_blob.path);
State::Copied
} else {
State::New
}
} else {
State::New
}
} else if !has_obj_ref && !blob_exists {
State::Default
} else if self.has_change() {
State::Modified
} else {
State::Default
}
};
LocalObj {
otype: String::from("blob"),
name: path_buf_to_string(self.r_path.clone()),
path: self.r_path.clone(),
path_from,
state
}
}
}