Skip to content

Commit

Permalink
added options to use exclusions
Browse files Browse the repository at this point in the history
  • Loading branch information
kinire98 committed Feb 6, 2024
1 parent d7f689e commit da0d94e
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 28 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ license = "MIT"
clap = { version = "4.4.18", features = ["derive"] }
color-eyre = "0.6.2"
fs_extra = "1.3.0"
glob = "0.3.1"
serde = { version = "1.0.196", features = ["derive"] }
serde_json = "1.0.113"
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@ As a user: make simple backups with the most recent copy of the files and access
3. Get the times of modification of each file in the directory of target directory. (DONE)
4. Compare both. If the origin directory time is greater than the target directory time that means that there were some changes, so it's neccessary to make a backup. (DONE)
5. If a directory or a file doesn't exist in the target directory it will be created. (DONE)
6. If a directory or a file no longer exists in the origin directory two options: if nothing is addressed for this, it will just leave it there or you can delete it.
7. All of this will be created in the most recent directory of the backup directory.

The directory structure will be like this
The directory structure is intended to be something like this (is up to you to maintain a correct file structure):
```
|
\_ backup_dir
Expand All @@ -29,10 +27,11 @@ A backup app that will store the most recent version of you files.
You indicate a directory and inside that directory a copy will be created. If you don't say the opposite it will be in the same directory.
You can tell the application to create a new directory so you can store the history of your file.
## Maybes
- Make an option to exclude directories
- An option to store the configuration in a configuration file, so you don't have to indicate the paths or the exclusions all the time. (Maybe with a [tui](https://docs.rs/tui/latest/tui)). It will also be useful to show progress
- An option to store the configuration in a configuration file, so you don't have to indicate the paths or the exclusions all the time. (Maybe with a [tui](https://docs.rs/tui/latest/tui)). It will also be useful to show progress. As the project progress I see this option more viable to not bloat the program with lots of arguments
- Multithreading support
## TODOS
- Exclusions and configurations stored in files
- Add behaviour for Windows when file or dir doesn't exist
- Change transfer of ownership to reference passing to avoid clones and improve performance time and memory wise
- Some test, especially a integration one
- Some tests, especially a integration one
- Look for unwraps and change them for proper error handling. If can't be done, use expect.
4 changes: 2 additions & 2 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ fn main() {
std::fs::write(
&path,
format!(
"{} {:?}:{:?}, {:?}:{:?} {}",
"{} {:?}:{:?}, {:?}:{:?} {}\n",
"{", "origin", "", "target", "", "}"
),
)
.unwrap();
}
let path = path.parent().unwrap().join("exclusions.json");
if !path.exists() {
std::fs::write(&path, "[]").unwrap();
std::fs::write(&path, "[]\n").unwrap();
}
}
}
68 changes: 61 additions & 7 deletions src/config/exclusions.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,38 @@
use crate::error::Result;
use crate::error::*;
use glob::Pattern;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

#[derive(Serialize, Deserialize, Debug)]
struct Exclusions(Vec<String>);

pub fn is_excluded(path: &PathBuf) -> Result<bool> {
let exclusions_file = std::env::current_exe()
let exclusions_file = std::env::current_dir()
.unwrap()
.join("config")
.join("exclusions.json");
let file_contents = std::fs::read_to_string(&exclusions_file).unwrap();
let deserialized: Exclusions = serde_json::from_str(&file_contents).unwrap();
let mut iter = deserialized
.0
.iter()
.filter(|file| **file == path.display().to_string());
let mut iter = deserialized.0.iter().filter(|file| {
Pattern::new(file.as_str())
.unwrap()
.matches(path.display().to_string().as_str())
});
Ok(iter.next().is_some())
}
pub fn add_exclusion(path: &PathBuf) -> Result<()> {
let exclusions_file = std::env::current_exe()
// TODO Dont add exclusion if it already exists
let exclusions_file = std::env::current_dir()
.unwrap()
.join("config")
.join("exclusions.json");
let file_contents = std::fs::read_to_string(&exclusions_file).unwrap();
let mut deserialized: Exclusions = serde_json::from_str(&file_contents).unwrap();
if Pattern::new(path.display().to_string().as_str()).is_err() {
return Err(Error {
kind: ErrorKind::InvalidPattern(path.display().to_string()),
});
}
deserialized.0.push(path.display().to_string());
std::fs::write(
exclusions_file,
Expand All @@ -33,3 +41,49 @@ pub fn add_exclusion(path: &PathBuf) -> Result<()> {
.unwrap();
Ok(())
}
pub fn list_exclusions() -> Result<()> {
let exclusions_file = std::env::current_dir()
.map_err(|_| Error {
kind: ErrorKind::FSError,
})?
.join("config")
.join("exclusions.json");
let mut counter = 1;
serde_json::from_str::<Exclusions>(&std::fs::read_to_string(exclusions_file).map_err(
|_| Error {
kind: ErrorKind::FSError,
},
)?)
.map_err(|_| Error {
kind: ErrorKind::JSONParsingError("exclusions".to_string()),
})?
.0
.iter()
.for_each(|exclusion| {
println!("{} -> {}", counter, exclusion);
counter += 1;
});
Ok(())
}
pub fn remove_exclusion(pattern: &PathBuf) -> Result<String> {
let exclusions_file = std::env::current_dir()
.map_err(|_| Error {
kind: ErrorKind::FSError,
})?
.join("config")
.join("exclusions.json");
let binding = serde_json::from_str::<Exclusions>(
&std::fs::read_to_string(exclusions_file).map_err(|_| Error {
kind: ErrorKind::FSError,
})?,
)
.map_err(|_| Error {
kind: ErrorKind::JSONParsingError("exclusions".to_string()),
})?;
let rest_of_values = binding
.0
.iter()
.filter(|stored_pattern| *stored_pattern != &pattern.display().to_string());
println!("{:?}", rest_of_values);
Ok(String::new())
}
8 changes: 6 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{error, fmt::Debug, fmt::Display};
use std::{error, fmt::{Debug, Display}};

pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
Expand All @@ -9,6 +9,8 @@ pub struct Error {
pub enum ErrorKind {
DirectoryDoesNotExist(String),
InvalidOption(String),
InvalidPattern(String),
JSONParsingError(String),
TargetDirMTimeHigherThanOriginDir,
FSError,
OperationAbortedByUser,
Expand All @@ -29,7 +31,9 @@ impl Display for Error {
write!(f,
"The directory where you want to store the backup has a modification time lower than the directory of origin for the backup.\n This means that you modified some data in thetarget directory after the last time you changed some data in the directory of origin."
)
}
},
ErrorKind::InvalidPattern(invalid_pattern) => write!(f, "The file pattern you introduced to exclude: \n{}\nis not valid", invalid_pattern),
ErrorKind::JSONParsingError(exclusions_or_configuration) => write!(f, "There was a problem parsing the file with the {}", exclusions_or_configuration),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ pub use app::backup;

pub use sys::should_be_backed;

mod config;
pub mod config;
71 changes: 61 additions & 10 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,22 @@ use klone::error::*;
#[command(author, version, about)]
struct Args {
/// The directory where the files to backup are
origin_dir: PathBuf,
origin_dir: Option<PathBuf>,
/// The directory where you want to store the backup
target_dir: PathBuf,
target_dir: Option<PathBuf>,
/// Indicates if a new directory should be created
#[arg(short, long)]
#[arg(short = 'n', long)]
new: bool,
/// Add an exclusion so the files that match with it won't be backed up. Be cautious: this
/// exclusions are global
#[arg(short = 'e', long)]
exclude: bool,
/// Show all the exclusions you added
#[arg(short = 'l', long)]
list_exclusions: bool,
/// Remove a previously added exclusion.
#[arg(short = 'r', long)]
remove_exclusion: bool,
}

// 1. Check if the paths are valid.
Expand All @@ -31,10 +41,48 @@ fn main() -> Result<()> {
env::set_var("RUST_BACKTRACE", "full");
//env::set_var("COLORBT_SHOW_HIDDEN", "1");
println!("{:?}", args);
match args.origin_dir.try_exists() {
// ! This is gonna change when added defaults
match (
args.exclude,
args.list_exclusions,
args.remove_exclusion,
args.origin_dir.is_some(), // If this call returns false,
args.target_dir.is_some(), // it's imposible that this returns true
) {
// Start backup
(false, false, false, true, true) => backup_option(args)?,
// Start backup without valid paths provided
(false, false, false, _, _) => Err(Error { kind: ErrorKind::InvalidOption("You must specify two valid paths if you don't provide arguments for exclusions of defaults".to_string()) })?,
// Add exclusion
(true, false, false, true, false) => klone::config::exclusions::add_exclusion(
&args.origin_dir.expect("This message should never appear"),
)?,
// Add exclusion without a pattern to store
(true, false, false, false, _) => Err(Error { kind: ErrorKind::InvalidOption("You must provide a pattern to add to the stored exclusions".to_string()) })?,
// Add exclusion with conflicting arguments
(true, _, _, _, _) => Err(Error { kind: ErrorKind::InvalidOption("Conflicting arguments".to_string()) })?,
// List exclusions
(false, true, false, false, false) => klone::config::exclusions::list_exclusions()?,
// List exclusions with conflicting arguments
(_, true, _, _, _) => Err(Error { kind: ErrorKind::InvalidOption("Conflicting arguments".to_string()) })?,
// Delete exclusion
(false, false, true, true, false) => {
let deleted_exclusion = klone::config::exclusions::remove_exclusion(&args.origin_dir.expect("This message should never appear"))?;
println!("You deleted the following exclusion: \n {}", deleted_exclusion);
},
// Delete exclusion without a pattern
(false, false, true, false, _) => Err(Error { kind: ErrorKind::InvalidOption("You must provide a pattern to delete from the stored exclusions".to_string()) })?,
// Delete exclusion with conflicting arguments
(_, _, true, true, _) => Err(Error { kind: ErrorKind::InvalidOption("Conflicting arguments".to_string()) })?,
}
Ok(())
}

fn backup_option(args: Args) -> Result<()> {
match args.origin_dir.as_ref().unwrap().try_exists() {
Ok(exists) => {
if !exists {
let path = args.origin_dir.display().to_string();
let path = args.origin_dir.as_ref().unwrap().display().to_string();
return Err(Error {
kind: ErrorKind::DirectoryDoesNotExist(path),
});
Expand All @@ -46,7 +94,7 @@ fn main() -> Result<()> {
})
}
}
match args.target_dir.try_exists() {
match args.target_dir.as_ref().unwrap().try_exists() {
Ok(exists) => match (args.new, exists) {
// The target directory exists and it is told to create it.
// Aborts because it can be the case that there is a backup or other data in that
Expand All @@ -57,7 +105,7 @@ fn main() -> Result<()> {
})
}
// The directory doesn't exist and the program is told to create it
(true, false) => create_dir(args.target_dir.to_str().unwrap())?,
(true, false) => create_dir(args.target_dir.as_ref().unwrap())?,
// The directory doesn't exist and the user hasn't asked to create it
// It prompts if the user wants to create it.
// If so, and there aren't any errors the execution continues as normal
Expand All @@ -72,7 +120,7 @@ fn main() -> Result<()> {
println!("{:?}", input);
match input.as_str() {
"y" | "Y" | "" => {
create_dir(args.target_dir.to_str().unwrap())?;
create_dir(args.target_dir.as_ref().unwrap())?;
}
"n" | "N" => {
return Err(Error {
Expand Down Expand Up @@ -100,11 +148,14 @@ fn main() -> Result<()> {
}

// In this point we have two directories we know for a fact that exist
klone::backup(args.origin_dir, args.target_dir)?;
klone::backup(args.origin_dir.unwrap(), args.target_dir.unwrap())?;
Ok(())
}

fn create_dir(path: &str) -> Result<()> {
fn create_dir<T>(path: T) -> Result<()>
where
T: AsRef<std::path::Path>,
{
match fs::create_dir_all(path) {
Ok(_) => Ok(()),
Err(_) => Err(Error {
Expand Down

0 comments on commit da0d94e

Please sign in to comment.