mirror of
https://github.com/edu4rdshl/rusnapshot.git
synced 2026-07-17 23:24:55 +00:00
Initial commit.
This commit is contained in:
parent
3699578552
commit
882bff2d2e
14 changed files with 2113 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/target
|
||||
19
.vscode/launch.json
vendored
Normal file
19
.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug",
|
||||
"program": "${workspaceRoot}/target/debug/${workspaceRootFolderName}",
|
||||
"args": "--cr -p root -d snapshots -t /mnt/defvol/_snapshots/ -s /mnt/defvol/_active/root/",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"sourceLanguages": [
|
||||
"rust"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
1520
Cargo.lock
generated
Normal file
1520
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
18
Cargo.toml
Normal file
18
Cargo.toml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
[package]
|
||||
name = "rusnapshot"
|
||||
version = "0.1.0"
|
||||
authors = ["edu4rdshl"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "2.33.3", features = ["yaml"] }
|
||||
reqwest = { version = "0.11.3", features = ["default-tls"] }
|
||||
openssl = { version = "0.10.30", features = ["vendored"] }
|
||||
config = { version = "0.11.0", features = ["yaml", "json", "toml", "hjson", "ini"] }
|
||||
failure = "0.1.8"
|
||||
sqlite = "0.26.0"
|
||||
md5 = "0.7.0"
|
||||
chrono = "0.4.19"
|
||||
prettytable-rs = "0.8.0"
|
||||
13
examples/config.toml
Normal file
13
examples/config.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Example configuration file for rustnapshot
|
||||
|
||||
# Snapshots directory
|
||||
dest_dir = "/mnt/defvol/_snapshots/"
|
||||
# Directory to be snapshoted
|
||||
source_dir = "/mnt/defvol/_active/root/"
|
||||
# SQLite database file PATH, created if no exists, recommended to be in the snapshots dir
|
||||
database_file = "/mnt/defvol/_snapshots/rustnapshot.sqlite"
|
||||
# Prefix for the snapshots, they will be saved in the format prefix-$current_date
|
||||
snapshot_prefix = "root"
|
||||
# Last snapshots to keep. If prefix is specified then only the snapshots with that prefix
|
||||
# are deleted
|
||||
keep_only = "2"
|
||||
123
src/args.rs
Normal file
123
src/args.rs
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
use {
|
||||
crate::structs::Args,
|
||||
clap::{load_yaml, value_t, App},
|
||||
std::{collections::HashMap, path::Path},
|
||||
};
|
||||
|
||||
pub fn get_args() -> Args {
|
||||
let yaml = load_yaml!("cli.yml");
|
||||
let matches = App::from_yaml(yaml)
|
||||
.version(clap::crate_version!())
|
||||
.get_matches();
|
||||
let settings: HashMap<String, String> =
|
||||
return_settings(&matches, &mut config::Config::default());
|
||||
Args {
|
||||
create_snapshot: matches.is_present("create-snapshot"),
|
||||
delete_snapshot: matches.is_present("delete-snapshot"),
|
||||
list_snapshots: matches.is_present("list-snapshots"),
|
||||
clean_snapshots: matches.is_present("clean-snapshots") || matches.is_present("keep-only"),
|
||||
restore_snapshot: matches.is_present("restore-snapshot"),
|
||||
dest_dir: if matches.is_present("dest-dir") {
|
||||
value_t!(matches, "dest-dir", String).unwrap_or_else(|_| String::new())
|
||||
} else {
|
||||
return_value_or_default(&settings, "dest_dir", String::new())
|
||||
},
|
||||
source_dir: if matches.is_present("source-dir") {
|
||||
value_t!(matches, "source-dir", String).unwrap_or_else(|_| String::new())
|
||||
} else {
|
||||
return_value_or_default(&settings, "source_dir", String::new())
|
||||
},
|
||||
database_file: if matches.is_present("database-file") {
|
||||
value_t!(matches, "database-file", String)
|
||||
.unwrap_or_else(|_| String::from("rusnapshot.sqlite"))
|
||||
} else {
|
||||
return_value_or_default(
|
||||
&settings,
|
||||
"database_file",
|
||||
String::from("rusnapshot.sqlite"),
|
||||
)
|
||||
},
|
||||
snapshot_name: String::new(),
|
||||
snapshot_id: value_t!(matches, "snapshot-id", String).unwrap_or_else(|_| String::new()),
|
||||
snapshot_prefix: if matches.is_present("snapshot-prefix") {
|
||||
value_t!(matches, "snapshot-prefix", String)
|
||||
.unwrap_or_else(|_| String::from("snapshot"))
|
||||
} else {
|
||||
return_value_or_default(&settings, "snapshot_prefix", String::from("snapshot"))
|
||||
},
|
||||
keep_only: if matches.is_present("keep-only") {
|
||||
value_t!(matches, "keep-only", usize).unwrap_or_else(|_| 0)
|
||||
} else {
|
||||
return_value_or_default(&settings, "keep_only", 0.to_string())
|
||||
.parse()
|
||||
.unwrap_or_default()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn return_settings(
|
||||
matches: &clap::ArgMatches,
|
||||
settings: &mut config::Config,
|
||||
) -> HashMap<String, String> {
|
||||
if matches.is_present("config-file") {
|
||||
match settings.merge(config::File::with_name(
|
||||
&value_t!(matches, "config-file", String).unwrap(),
|
||||
)) {
|
||||
Ok(settings) => match settings.merge(config::Environment::with_prefix("RUSNAPSHOT")) {
|
||||
Ok(settings) => settings
|
||||
.clone()
|
||||
.try_into::<HashMap<String, String>>()
|
||||
.unwrap(),
|
||||
Err(e) => {
|
||||
eprintln!("Error merging environment variables into settings: {}", e);
|
||||
std::process::exit(1)
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("Error reading config file: {}", e);
|
||||
std::process::exit(1)
|
||||
}
|
||||
}
|
||||
} else if Path::new("rusnapshot.toml").exists()
|
||||
|| Path::new("rusnapshot.json").exists()
|
||||
|| Path::new("rusnapshot.hjson").exists()
|
||||
|| Path::new("rusnapshot.ini").exists()
|
||||
|| Path::new("rusnapshot.yml").exists()
|
||||
{
|
||||
match settings.merge(config::File::with_name("rusnapshot")) {
|
||||
Ok(settings) => match settings.merge(config::Environment::with_prefix("RUSNAPSHOT")) {
|
||||
Ok(settings) => settings
|
||||
.clone()
|
||||
.try_into::<HashMap<String, String>>()
|
||||
.unwrap(),
|
||||
Err(e) => {
|
||||
eprintln!("Error merging environment variables into settings: {}", e);
|
||||
std::process::exit(1)
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("Error reading config file: {}", e);
|
||||
std::process::exit(1)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match settings.merge(config::Environment::with_prefix("RUSNAPSHOT")) {
|
||||
Ok(settings) => settings
|
||||
.clone()
|
||||
.try_into::<HashMap<String, String>>()
|
||||
.unwrap(),
|
||||
Err(e) => {
|
||||
eprintln!("Error merging environment variables into settings: {}", e);
|
||||
std::process::exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn return_value_or_default(
|
||||
settings: &HashMap<String, String>,
|
||||
value: &str,
|
||||
default_value: String,
|
||||
) -> String {
|
||||
settings.get(value).unwrap_or(&default_value).to_string()
|
||||
}
|
||||
88
src/cli.yml
Normal file
88
src/cli.yml
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
name: Rusnapshot
|
||||
author: Eduard Tolosa <edu4rdshl@protonmail.com>
|
||||
settings:
|
||||
- ArgRequiredElseHelp
|
||||
- StrictUtf8
|
||||
about: Simple and handy btrfs snapshoting tool.
|
||||
args:
|
||||
- config-file:
|
||||
help: Path to configuration file.
|
||||
short: c
|
||||
long: config
|
||||
takes_value: true
|
||||
multiple: false
|
||||
|
||||
- dest-dir:
|
||||
help: Directory where snapshots should be saved.
|
||||
long: to
|
||||
takes_value: true
|
||||
multiple: false
|
||||
|
||||
- source-dir:
|
||||
help: Directory from where snapshots should be created.
|
||||
long: from
|
||||
takes_value: true
|
||||
multiple: false
|
||||
|
||||
- snapshot-id:
|
||||
help: Snapshot id or name to work with.
|
||||
long: id
|
||||
takes_value: true
|
||||
multiple: false
|
||||
|
||||
- database-file:
|
||||
help: Path to the SQLite database file.
|
||||
short: d
|
||||
long: dfile
|
||||
takes_value: true
|
||||
multiple: false
|
||||
|
||||
- snapshot-prefix:
|
||||
help: "Prefix for the snapshot name. Default: snapshot"
|
||||
short: p
|
||||
long: prefix
|
||||
takes_value: true
|
||||
multiple: false
|
||||
|
||||
- create-snapshot:
|
||||
help: Create an snapshot.
|
||||
long: cr
|
||||
takes_value: false
|
||||
multiple: false
|
||||
|
||||
- clean-snapshots:
|
||||
help: "Enable snapshots cleaning, will keep only the last X snapshots specified with -k/--keep. WARNING: if -k/--keep is not specified, IT WILL DELETE ALL THE SNAPSHOTS."
|
||||
long: clean
|
||||
takes_value: false
|
||||
multiple: false
|
||||
|
||||
- delete-snapshot:
|
||||
help: Delete an snapshot.
|
||||
long: del
|
||||
takes_value: false
|
||||
multiple: false
|
||||
requires:
|
||||
- snapshot-id
|
||||
|
||||
- restore-snapshot:
|
||||
help: Restore an specific snapshot.
|
||||
short: r
|
||||
long: restore
|
||||
takes_value: false
|
||||
multiple: false
|
||||
requires:
|
||||
- snapshot-id
|
||||
|
||||
- list-snapshots:
|
||||
help: List the snapshots tracked in the database.
|
||||
short: l
|
||||
long: list
|
||||
takes_value: false
|
||||
multiple: false
|
||||
|
||||
- keep-only:
|
||||
help: Keep only the last X items.
|
||||
short: k
|
||||
long: keep
|
||||
takes_value: true
|
||||
multiple: false
|
||||
108
src/controller.rs
Normal file
108
src/controller.rs
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
use {
|
||||
crate::{database, errors::*, operations, structs::Args},
|
||||
chrono::Utc,
|
||||
md5,
|
||||
prettytable::{cell, row, Table},
|
||||
};
|
||||
|
||||
pub fn manage_creation(args: &mut Args) -> Result<()> {
|
||||
let connection = sqlite::open(&args.database_file)?;
|
||||
args.snapshot_name = format!(
|
||||
"{}-{}",
|
||||
args.snapshot_prefix,
|
||||
Utc::now().format("%Y-%m-%d-%H-%M-%S")
|
||||
);
|
||||
args.snapshot_id = format!("{:?}", md5::compute(&args.snapshot_name));
|
||||
if operations::take_snapshot(args) {
|
||||
database::commit_to_database(&connection, args)?
|
||||
}
|
||||
drop(connection);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn manage_deletion(args: &mut Args) -> Result<()> {
|
||||
let connection = sqlite::open(&args.database_file)?;
|
||||
let snapshot_data = database::return_snapshot_data(&connection, args)?;
|
||||
if !snapshot_data.snap_id.is_empty() {
|
||||
args.snapshot_name = snapshot_data.destination + &snapshot_data.name;
|
||||
} else {
|
||||
eprintln!(
|
||||
"Snapshot ID {} does not returned any data. Please double check the ID.",
|
||||
args.snapshot_id
|
||||
)
|
||||
}
|
||||
if !args.snapshot_name.is_empty() && operations::del_snapshot(args) {
|
||||
database::delete_from_database(&connection, args)?
|
||||
}
|
||||
drop(connection);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn manage_restoring(args: &mut Args) -> Result<()> {
|
||||
let connection = sqlite::open(&args.database_file)?;
|
||||
let snapshot_data = database::return_snapshot_data(&connection, args)?;
|
||||
if !snapshot_data.snap_id.is_empty() {
|
||||
args.snapshot_name = snapshot_data.destination + &snapshot_data.name;
|
||||
args.source_dir = snapshot_data.source;
|
||||
} else {
|
||||
eprintln!(
|
||||
"Snapshot ID {} does not returned any data. Please double check the ID.",
|
||||
args.snapshot_id
|
||||
)
|
||||
}
|
||||
println!(
|
||||
"ID: {}, NAME: {}, Rest Point: {}",
|
||||
args.snapshot_id, args.snapshot_name, args.source_dir
|
||||
);
|
||||
if !args.snapshot_name.is_empty() && operations::restore_snapshot(args) {
|
||||
println!(
|
||||
"The snapshot with ID {} was successfully restored to {}",
|
||||
args.snapshot_id, args.source_dir
|
||||
)
|
||||
}
|
||||
drop(connection);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn manage_listing(args: &mut Args) -> Result<()> {
|
||||
let connection = sqlite::open(&args.database_file)?;
|
||||
let snaps_data = database::return_all_data(&connection)?;
|
||||
|
||||
let mut table = Table::new();
|
||||
table.set_titles(row![
|
||||
bcFg => "NAME",
|
||||
"ID",
|
||||
"SOURCE DIR",
|
||||
"DESTINATION DIR",
|
||||
"DATE"
|
||||
]);
|
||||
|
||||
for data in &snaps_data {
|
||||
table.add_row(row![ d =>
|
||||
data.name,
|
||||
data.snap_id,
|
||||
data.source,
|
||||
data.destination,
|
||||
data.date,
|
||||
]);
|
||||
}
|
||||
table.printstd();
|
||||
drop(connection);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn keep_only_x(args: &mut Args) -> Result<()> {
|
||||
let connection = sqlite::open(&args.database_file)?;
|
||||
let snaps_data = database::return_only_x_items(&connection, args)?;
|
||||
for data in &snaps_data {
|
||||
args.snapshot_name = data.destination.clone() + &data.name;
|
||||
args.snapshot_id = data.snap_id.clone();
|
||||
manage_deletion(args)?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
98
src/database.rs
Normal file
98
src/database.rs
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
use {
|
||||
crate::{
|
||||
errors::*,
|
||||
structs::{Args, Database},
|
||||
},
|
||||
sqlite::{Connection, State},
|
||||
};
|
||||
|
||||
pub fn test_database(connection_str: &str) -> Result<()> {
|
||||
let connection = sqlite::open(&connection_str)?;
|
||||
drop(connection);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn setup_initial_database(connection: &Connection) -> Result<()> {
|
||||
connection.execute("CREATE TABLE IF NOT EXISTS snapshots (name TEXT NOT NULL, snap_id TEXT NOT NULL, source TEXT NOT NULL, destination TEXT NOT NULL, date TEXT DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY(name, snap_id))")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn commit_to_database(connection: &Connection, args: &Args) -> Result<()> {
|
||||
let statement = format!(
|
||||
"INSERT INTO snapshots (name, snap_id, source, destination) VALUES ('{}', '{}', '{}', '{}')",
|
||||
args.snapshot_name, args.snapshot_id, args.source_dir, args.dest_dir
|
||||
);
|
||||
connection.execute(&statement)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_from_database(connection: &Connection, args: &Args) -> Result<()> {
|
||||
let statement = format!(
|
||||
"DELETE FROM snapshots WHERE name = '{}' OR snap_id = '{}'",
|
||||
args.snapshot_id, args.snapshot_id
|
||||
);
|
||||
connection.execute(&statement)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn return_snapshot_data(connection: &Connection, args: &Args) -> Result<Database> {
|
||||
let mut statement = connection.prepare(format!(
|
||||
"SELECT * FROM snapshots WHERE name = '{}' OR snap_id = '{}'",
|
||||
args.snapshot_id, args.snapshot_id
|
||||
))?;
|
||||
let mut snap_data = Database::default();
|
||||
|
||||
while let State::Row = statement.next()? {
|
||||
snap_data.name = statement.read::<String>(0).unwrap();
|
||||
snap_data.snap_id = statement.read::<String>(1).unwrap();
|
||||
snap_data.source = statement.read::<String>(2).unwrap();
|
||||
snap_data.destination = statement.read::<String>(3).unwrap();
|
||||
snap_data.date = statement.read::<String>(4).unwrap();
|
||||
}
|
||||
|
||||
Ok(snap_data)
|
||||
}
|
||||
|
||||
pub fn return_all_data(connection: &Connection) -> Result<Vec<Database>> {
|
||||
let mut snapshots_data: Vec<Database> = Vec::new();
|
||||
|
||||
let mut statement = connection.prepare("SELECT * FROM snapshots ORDER BY date DESC")?;
|
||||
|
||||
while let State::Row = statement.next()? {
|
||||
let mut db_struct = Database::default();
|
||||
|
||||
db_struct.name = statement.read::<String>(0).unwrap();
|
||||
db_struct.snap_id = statement.read::<String>(1).unwrap();
|
||||
db_struct.source = statement.read::<String>(2).unwrap();
|
||||
db_struct.destination = statement.read::<String>(3).unwrap();
|
||||
db_struct.date = statement.read::<String>(4).unwrap();
|
||||
|
||||
snapshots_data.push(db_struct);
|
||||
}
|
||||
|
||||
Ok(snapshots_data)
|
||||
}
|
||||
|
||||
pub fn return_only_x_items(connection: &Connection, args: &Args) -> Result<Vec<Database>> {
|
||||
let mut snapshots_data: Vec<Database> = Vec::new();
|
||||
|
||||
let mut statement = connection.prepare(&format!("SELECT name,snap_id,source,destination,date FROM (SELECT row_number() over(ORDER BY date DESC) n,* from snapshots) WHERE n > {} AND name LIKE '{}%'", args.keep_only, args.snapshot_prefix))?;
|
||||
|
||||
while let State::Row = statement.next()? {
|
||||
let mut db_struct = Database::default();
|
||||
|
||||
db_struct.name = statement.read::<String>(0).unwrap();
|
||||
db_struct.snap_id = statement.read::<String>(1).unwrap();
|
||||
db_struct.source = statement.read::<String>(2).unwrap();
|
||||
db_struct.destination = statement.read::<String>(3).unwrap();
|
||||
db_struct.date = statement.read::<String>(4).unwrap();
|
||||
|
||||
snapshots_data.push(db_struct);
|
||||
}
|
||||
|
||||
Ok(snapshots_data)
|
||||
}
|
||||
2
src/errors.rs
Normal file
2
src/errors.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
pub use failure::{Error, ResultExt};
|
||||
pub type Result<T> = ::std::result::Result<T, Error>;
|
||||
6
src/lib.rs
Normal file
6
src/lib.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
pub mod args;
|
||||
pub mod controller;
|
||||
pub mod database;
|
||||
pub mod errors;
|
||||
mod operations;
|
||||
mod structs;
|
||||
37
src/main.rs
Normal file
37
src/main.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
use {
|
||||
rusnapshot::{args, controller, database, errors::*},
|
||||
sqlite,
|
||||
};
|
||||
|
||||
fn run() -> Result<()> {
|
||||
let mut arguments = args::get_args();
|
||||
database::test_database(&arguments.database_file)?;
|
||||
database::setup_initial_database(&sqlite::open(&arguments.database_file)?)?;
|
||||
|
||||
if arguments.create_snapshot {
|
||||
controller::manage_creation(&mut arguments)?
|
||||
}
|
||||
if arguments.delete_snapshot {
|
||||
controller::manage_deletion(&mut arguments)?
|
||||
}
|
||||
if arguments.list_snapshots {
|
||||
controller::manage_listing(&mut arguments)?
|
||||
}
|
||||
if arguments.clean_snapshots {
|
||||
controller::keep_only_x(&mut arguments)?
|
||||
}
|
||||
if arguments.restore_snapshot {
|
||||
controller::manage_restoring(&mut arguments)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Err(err) = run() {
|
||||
eprintln!("\nError: {}", err);
|
||||
for cause in err.iter_chain().skip(1) {
|
||||
eprintln!("Error description: {}", cause);
|
||||
}
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
45
src/operations.rs
Normal file
45
src/operations.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
use {
|
||||
crate::structs::Args,
|
||||
std::{path::Path, process::Command},
|
||||
};
|
||||
|
||||
pub fn take_snapshot(args: &Args) -> bool {
|
||||
Command::new("btrfs")
|
||||
.args(&[
|
||||
"subvolume",
|
||||
"snapshot",
|
||||
"-r",
|
||||
&args.source_dir,
|
||||
&format!("{}/{}", args.dest_dir, args.snapshot_name),
|
||||
])
|
||||
.status()
|
||||
.expect("Error while taking the snapshot.")
|
||||
.success()
|
||||
}
|
||||
|
||||
pub fn del_snapshot(args: &Args) -> bool {
|
||||
Command::new("btrfs")
|
||||
.args(&["subvolume", "delete", &args.snapshot_name])
|
||||
.status()
|
||||
.expect("Error deleting the snapshot.")
|
||||
.success()
|
||||
}
|
||||
|
||||
pub fn restore_snapshot(args: &Args) -> bool {
|
||||
(!Path::new(&args.source_dir).exists()
|
||||
|| Command::new("btrfs")
|
||||
.args(&["subvolume", "delete", &args.source_dir])
|
||||
.status()
|
||||
.expect("Error deleting the subvolume.")
|
||||
.success())
|
||||
&& Command::new("btrfs")
|
||||
.args(&[
|
||||
"subvolume",
|
||||
"snapshot",
|
||||
&args.snapshot_name,
|
||||
&args.source_dir,
|
||||
])
|
||||
.status()
|
||||
.expect("Error restoring the snapshot.")
|
||||
.success()
|
||||
}
|
||||
35
src/structs.rs
Normal file
35
src/structs.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#[derive(Clone, Debug)]
|
||||
pub struct Args {
|
||||
pub create_snapshot: bool,
|
||||
pub delete_snapshot: bool,
|
||||
pub list_snapshots: bool,
|
||||
pub clean_snapshots: bool,
|
||||
pub restore_snapshot: bool,
|
||||
pub dest_dir: String,
|
||||
pub source_dir: String,
|
||||
pub database_file: String,
|
||||
pub snapshot_name: String,
|
||||
pub snapshot_id: String,
|
||||
pub snapshot_prefix: String,
|
||||
pub keep_only: usize,
|
||||
}
|
||||
|
||||
pub struct Database {
|
||||
pub name: String,
|
||||
pub snap_id: String,
|
||||
pub source: String,
|
||||
pub destination: String,
|
||||
pub date: String,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
pub fn default() -> Database {
|
||||
Database {
|
||||
name: String::new(),
|
||||
snap_id: String::new(),
|
||||
source: String::new(),
|
||||
destination: String::new(),
|
||||
date: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue