Skip to content

Commit

Permalink
dvs add R-centric output
Browse files Browse the repository at this point in the history
  • Loading branch information
jenna-a2ai committed Apr 1, 2024
1 parent b8d5033 commit f461e96
Show file tree
Hide file tree
Showing 14 changed files with 201 additions and 108 deletions.
3 changes: 0 additions & 3 deletions dvs.yaml

This file was deleted.

14 changes: 7 additions & 7 deletions src/helpers/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::fs::File;
use std::io::Result;
use std::io::{self, Read};

pub fn hash_file_with_blake3(file_path: &PathBuf) -> io::Result<String> {
pub fn hash_file_with_blake3(file_path: &PathBuf) -> io::Result<Option<String>> {
let file = File::open(file_path)?;

let mmap = match maybe_memmap_file(&file) {
Expand All @@ -17,10 +17,10 @@ pub fn hash_file_with_blake3(file_path: &PathBuf) -> io::Result<String> {
};
let mut hasher = Hasher::new();
hasher.update_rayon(&mmap);
Ok(hasher.finalize().to_string())
Ok(Some(hasher.finalize().to_string()))
}

fn hash_file_with_blake3_direct(file_path: &PathBuf) -> io::Result<String> {
fn hash_file_with_blake3_direct(file_path: &PathBuf) -> io::Result<Option<String>> {
let mut file = File::open(file_path)?;

let mut hasher = Hasher::new();
Expand All @@ -35,7 +35,7 @@ fn hash_file_with_blake3_direct(file_path: &PathBuf) -> io::Result<String> {
}

let hash_result = hasher.finalize();
Ok(hash_result.to_string())
Ok(Some(hash_result.to_string()))
}

// Mmap a file, if it looks like a good idea. Return None in cases where we
Expand Down Expand Up @@ -70,17 +70,17 @@ fn maybe_memmap_file(file: &File) -> Result<Option<memmap2::Mmap>> {
})
}

pub fn get_file_hash(path: &PathBuf) -> String {
pub fn get_file_hash(path: &PathBuf) -> Option<String> {
// TODO: get cache if possible

let hash =match hash_file_with_blake3(&path) {
Ok(hash) => hash,
Err(_) => String::from(""), // swallowing error - return empty string if not hashable
Err(_) => None,
};

// TODO: cache bytes

return hash.to_string();
return hash;
}

pub fn get_storage_path(storage_dir: &PathBuf, file_hash: &String) -> PathBuf {
Expand Down
212 changes: 159 additions & 53 deletions src/library/add.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,38 @@
use std::path::PathBuf;
use std::os::unix::fs::PermissionsExt;
use serde::Serialize;
use file_owner::{Group, PathExt};
use std::{fs, u32};
use anyhow::{anyhow, Context, Result};
use anyhow::{Context, Result};
use crate::helpers::hash;
use crate::helpers::copy;
use crate::helpers::file;
use crate::helpers::ignore;
use crate::helpers::config;
use crate::helpers::repo;

pub fn dvs_add(files: &Vec<String>, message: &String) -> Result<()> {
#[derive(Clone, PartialEq, Serialize)]
enum Outcome {
Success,
AlreadyPresent,
Error
}

#[derive(Clone, PartialEq, Serialize)]
pub struct AddedFile {
path: PathBuf,
hash: Option<String>,
outcome: Outcome,
error: Option<String>,
size: Option<u64>,
}

pub fn dvs_add(files: &Vec<String>, message: &String) -> Result<Vec<AddedFile>> {
// Get git root
let git_dir = repo::get_nearest_repo_dir(&PathBuf::from(".")).with_context(|| "could not find git repo root - make sure you're in an active git repository")?;

// load the config
let conf = config::read(&git_dir)?;
let conf = config::read(&git_dir).with_context(|| "could not load configuration file - no dvs.yaml in directory - be sure to initiate devious")?;

let mut queued_paths: Vec<PathBuf> = Vec::new();

Expand Down Expand Up @@ -48,65 +65,167 @@ pub fn dvs_add(files: &Vec<String>, message: &String) -> Result<()> {
queued_paths.push(file);
} // for


// add each file in queued_paths to storage
for file in &queued_paths {
add(file, &conf, &message)?;
}
let added_files = queued_paths.into_iter().map(|file| {
add(&file, &conf, &message)
}).collect::<Vec<AddedFile>>();

if queued_paths.is_empty() {
// json warning: no files were queued
}

Ok(())
return Ok(added_files)
} // run_add_cmd

fn add(local_path: &PathBuf, conf: &config::Config, message: &String) -> Result<String> {
fn add(local_path: &PathBuf, conf: &config::Config, message: &String) -> AddedFile {
// set error to None by default
let mut error: Option<String> = None;

// get file hash
let file_hash = hash::hash_file_with_blake3(local_path).with_context(|| format!("could not hash file"))?;
let file_hash = hash::get_file_hash(&local_path);
if file_hash.is_none() && error.is_none() {
error = Some(String::from("could not hash file"));
}

// get file size
let file_size = get_file_size(&local_path);
if file_size.is_none() && error.is_none() {
error = Some(String::from("unable to get size of file"));
}

// get user name
let user_name = get_user_name(&local_path);
if user_name.is_none() && error.is_none() {
error = Some(String::from("could not get name of file owner"));
}

// check group
let group: Option<Group> = match Group::from_name(&conf.group) {
Ok(group) => Some(group),
Err(_) => {
if error.is_none() {error = Some(String::from("group not found"));}
None
}
};

// now see if file can be added
let storage_dir_abs: Option<PathBuf> = match conf.storage_dir.canonicalize() {
Ok(path) => Some(path),
Err(_) => {
if error.is_none() {error = Some(String::from("could not find storage directory"))}
None
}
};

// get storage path
let storage_dir_abs = conf.storage_dir.canonicalize().with_context(|| format!("could not find storage directory: {}", conf.storage_dir.display()))?;
let dest_path = hash::get_storage_path(&storage_dir_abs, &file_hash);
if error.is_some() {
return AddedFile{
path: local_path.clone(),
hash: file_hash,
outcome: Outcome::Error,
error: error,
size: file_size
};
}

// check if group exists again
let group = Group::from_name(&conf.group).with_context(|| format!("group not found: {}", conf.group))?;
// can safely unwrap storage_dir_abs and file_hash
let file_hash_value = file_hash.clone().unwrap();
let dest_path = hash::get_storage_path(&storage_dir_abs.unwrap(), &file_hash_value);

// Copy the file to the storage directory if it's not already there
let mut outcome: Outcome = Outcome::Success;
if !dest_path.exists() {
// copy
copy::copy(&local_path, &dest_path).with_context(|| format!("could not copy {} to storage directory: {}", local_path.display(), dest_path.display()))?;
copy_file_to_storage_directory(local_path, &dest_path, &conf.permissions, &group.unwrap());
}
else {
println!("{} already exists in storage directory", local_path.display())
outcome = Outcome::AlreadyPresent;
}

// set permissions
set_permissions(&conf.permissions, &dest_path)?;

// set group ownership
dest_path.set_group(group).with_context(|| format!("unable to set group: {}", group))?;

// get file size
let file_size = get_file_size(&local_path)?;

// get user name
let user_name = get_user_name(&local_path)?;

// create + write metadata file
// create metadata
let metadata = file::Metadata{
file_hash: file_hash.clone(),
file_size,
file_hash: file_hash_value,
file_size: file_size.unwrap(),
time_stamp: chrono::offset::Local::now().to_string(),
message: message.clone(),
saved_by: user_name
saved_by: user_name.unwrap()
};

// write metadata file
match file::save(&metadata, &local_path) {
Ok(_) => {},
Err(_) => if error.is_none() {error = Some(String::from("could not save metadata file"))}
};
file::save(&metadata, &local_path).with_context(|| format!("could not save metadata into file"))?;

// Add file to gitignore
ignore::add_gitignore_entry(local_path).with_context(|| format!("could not add .gitignore entry"))?;
match ignore::add_gitignore_entry(local_path) {
Ok(_) => {},
Err(_) => {
if error.is_none() {error = Some(String::from("could not add .gitignore entry"))}
}
};

return Ok(file_hash);
if error.is_some() {outcome = Outcome::Error}

return AddedFile {
path: local_path.clone(),
hash: file_hash.clone(),
outcome,
error,
size: file_size
}
}


fn get_file_size(local_path: &PathBuf) -> Option<u64> {
match local_path.metadata() {
Ok(data) => return Some(data.len()),
Err(_) => return None,
};
}


fn get_user_name(local_path: &PathBuf) -> Option<String> {
let owner = match local_path.owner().with_context(|| format!("")) {
Ok(owner) => owner,
Err(_) => return None,
};
match owner.name() {
Ok(name) => return Some(name.unwrap()),
Err(_) => return None,
};
}


fn copy_file_to_storage_directory(local_path: &PathBuf, dest_path: &PathBuf, mode: &u32, group: &Group) -> Option<String> {
let mut error = None;
match copy::copy(&local_path, &dest_path) {
Ok(_) => {
// set permissions
match set_permissions(&mode, &dest_path) {
Ok(_) => {},
Err(_) => {
// set error
if error.is_none() {error = Some(String::from("could not set file permissions"))}
// delete copied file
fs::remove_file(&dest_path)
.expect(format!("could not set permissions after copying {} to {}: error deleting copied file. Delete {} manually.", local_path.display(), dest_path.display(), dest_path.display()).as_str());
}
};

// set group ownership
match dest_path.set_group(group.clone()) {
Ok(_) => {},
Err(_) => {
// set error
if error.is_none() {error = Some(String::from("could not set file group ownership"))}
// delete copied file
fs::remove_file(&dest_path)
.expect(format!("could not set group ownership after copying {} to {}: error deleting copied file. Delete {} manually.", local_path.display(), dest_path.display(), dest_path.display()).as_str());

}
};
} // Ok, could copy
Err(_) => {
if error.is_none() {error = Some(String::from("could not copy file to storage directory"))}
}
};
return error
}

fn set_permissions(mode: &u32, dest_path: &PathBuf) -> Result<()> {
Expand All @@ -115,17 +234,4 @@ fn set_permissions(mode: &u32, dest_path: &PathBuf) -> Result<()> {
let new_permissions = fs::Permissions::from_mode(*mode);
fs::set_permissions(&dest_path, new_permissions).with_context(|| format!("unable to set permissions: {}", mode))?;
Ok(())
}

fn get_file_size(local_path: &PathBuf) -> Result<u64> {
let local_path_data = local_path.metadata().with_context(|| format!("unable to get size of file: {}", local_path.display()))?;
return Ok(local_path_data.len());
}

fn get_user_name(local_path: &PathBuf) -> Result<String> {
let owner = local_path.owner().with_context(|| format!(""))?;
match owner.name() {
Ok(name) => return Ok(name.unwrap()),
Err(e) => return Err(anyhow!("could not get name of file owner: {e}")),
};
}
2 changes: 1 addition & 1 deletion src/library/get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub fn get(local_path: &PathBuf, storage_dir: &PathBuf) -> Result<()> {
let metadata_hash = metadata.file_hash;

// check if up-to-date file is already present locally
if !local_path.exists() || local_hash == String::from("") || metadata_hash == String::from("") || local_hash != metadata_hash {
if !local_path.exists() || local_hash.is_none() || metadata_hash == String::from("") || local_hash.unwrap() != metadata_hash {
copy::copy(&storage_path, &local_path).with_context(|| format!("could not copy {} from storage directory: {}", local_path.display(), storage_dir.display()))?;
}
else {
Expand Down
13 changes: 8 additions & 5 deletions src/library/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,14 @@ pub fn dvs_status(files: &Vec<String>) -> Result<Vec<JsonFileResult>> {
if !path.exists() {status = String::from("not-present")}
else {
// get whether file was hashable and file hash
let file_hash = hash::get_file_hash(&path); // will return "" if not hashable, which won't match metadata.file_hash
if file_hash == metadata.file_hash {
status = String::from("up-to-date")
}
// else, the file exists, but the hash isn't up to date, so still with default: out-of-date
match hash::get_file_hash(&path) {
Some(file_hash) => {
if file_hash == metadata.file_hash {
status = String::from("up-to-date")
}
}
None => (),
};
}

// assemble info into JsonFileResult
Expand Down
Loading

0 comments on commit f461e96

Please sign in to comment.