2020-07-02 16:20:45 +02:00
|
|
|
use crate::Data;
|
|
|
|
use crate::error::Error;
|
2020-07-28 14:41:49 +02:00
|
|
|
use crate::helpers::compression;
|
2020-07-02 16:20:45 +02:00
|
|
|
|
|
|
|
use log::error;
|
2020-07-28 14:41:49 +02:00
|
|
|
use std::fs::create_dir_all;
|
2020-07-02 16:20:45 +02:00
|
|
|
use std::path::Path;
|
|
|
|
use std::thread;
|
2021-02-09 11:08:30 +01:00
|
|
|
use std::time::Duration;
|
2020-07-02 16:20:45 +02:00
|
|
|
|
|
|
|
pub fn load_snapshot(
|
|
|
|
db_path: &str,
|
|
|
|
snapshot_path: &Path,
|
|
|
|
ignore_snapshot_if_db_exists: bool,
|
|
|
|
ignore_missing_snapshot: bool
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
let db_path = Path::new(db_path);
|
|
|
|
|
|
|
|
if !db_path.exists() && snapshot_path.exists() {
|
2020-07-28 14:41:49 +02:00
|
|
|
compression::from_tar_gz(snapshot_path, db_path)
|
2020-07-02 16:20:45 +02:00
|
|
|
} else if db_path.exists() && !ignore_snapshot_if_db_exists {
|
2020-10-15 13:38:29 +02:00
|
|
|
Err(Error::Internal(format!("database already exists at {:?}, try to delete it or rename it", db_path.canonicalize().unwrap_or(db_path.into()))))
|
2020-07-02 16:20:45 +02:00
|
|
|
} else if !snapshot_path.exists() && !ignore_missing_snapshot {
|
2020-10-15 13:38:29 +02:00
|
|
|
Err(Error::Internal(format!("snapshot doesn't exist at {:?}", snapshot_path.canonicalize().unwrap_or(snapshot_path.into()))))
|
2020-07-02 16:20:45 +02:00
|
|
|
} else {
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-26 11:34:37 +01:00
|
|
|
pub fn create_snapshot(data: &Data, snapshot_dir: impl AsRef<Path>, snapshot_name: impl AsRef<str>) -> Result<(), Error> {
|
|
|
|
create_dir_all(&snapshot_dir)?;
|
|
|
|
let tmp_dir = tempfile::tempdir_in(&snapshot_dir)?;
|
2020-07-02 16:20:45 +02:00
|
|
|
|
|
|
|
data.db.copy_and_compact_to_path(tmp_dir.path())?;
|
|
|
|
|
2021-03-26 11:34:37 +01:00
|
|
|
let temp_snapshot_file = tempfile::NamedTempFile::new_in(&snapshot_dir)?;
|
|
|
|
|
|
|
|
compression::to_tar_gz(tmp_dir.path(), temp_snapshot_file.path())
|
|
|
|
.map_err(|e| Error::Internal(format!("something went wrong during snapshot compression: {}", e)))?;
|
|
|
|
|
|
|
|
let snapshot_path = snapshot_dir.as_ref().join(snapshot_name.as_ref());
|
|
|
|
|
|
|
|
temp_snapshot_file.persist(snapshot_path).map_err(|e| Error::Internal(e.to_string()))?;
|
|
|
|
|
|
|
|
Ok(())
|
2020-07-02 16:20:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn schedule_snapshot(data: Data, snapshot_dir: &Path, time_gap_s: u64) -> Result<(), Error> {
|
2021-02-09 11:08:30 +01:00
|
|
|
if snapshot_dir.file_name().is_none() {
|
2020-07-02 16:20:45 +02:00
|
|
|
return Err(Error::Internal("invalid snapshot file path".to_string()));
|
|
|
|
}
|
|
|
|
let db_name = Path::new(&data.db_path).file_name().ok_or_else(|| Error::Internal("invalid database name".to_string()))?;
|
|
|
|
create_dir_all(snapshot_dir)?;
|
2021-03-26 11:34:37 +01:00
|
|
|
let snapshot_name = format!("{}.snapshot", db_name.to_str().unwrap_or("data.ms"));
|
|
|
|
let snapshot_dir = snapshot_dir.to_owned();
|
2021-02-09 11:08:30 +01:00
|
|
|
|
|
|
|
thread::spawn(move || loop {
|
2021-03-26 11:34:37 +01:00
|
|
|
if let Err(e) = create_snapshot(&data, &snapshot_dir, &snapshot_name) {
|
2020-07-02 16:20:45 +02:00
|
|
|
error!("Unsuccessful snapshot creation: {}", e);
|
|
|
|
}
|
2020-10-15 16:11:02 +02:00
|
|
|
thread::sleep(Duration::from_secs(time_gap_s));
|
2020-07-02 16:20:45 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
use std::io::prelude::*;
|
|
|
|
use std::fs;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_pack_unpack() {
|
2021-03-29 13:25:59 +02:00
|
|
|
let tempdir = tempfile::tempdir().unwrap();
|
2020-07-02 16:20:45 +02:00
|
|
|
|
|
|
|
let test_dir = tempdir.path();
|
|
|
|
let src_dir = test_dir.join("src");
|
|
|
|
let dest_dir = test_dir.join("complex/destination/path/");
|
2020-10-15 12:18:15 +02:00
|
|
|
let archive_path = test_dir.join("archive.snapshot");
|
2020-07-02 16:20:45 +02:00
|
|
|
|
|
|
|
let file_1_relative = Path::new("file1.txt");
|
2020-10-13 11:17:02 +02:00
|
|
|
let subdir_relative = Path::new("subdir/");
|
|
|
|
let file_2_relative = Path::new("subdir/file2.txt");
|
2021-02-09 11:08:30 +01:00
|
|
|
|
2020-10-13 11:17:02 +02:00
|
|
|
create_dir_all(src_dir.join(subdir_relative)).unwrap();
|
2020-07-28 14:41:49 +02:00
|
|
|
fs::File::create(src_dir.join(file_1_relative)).unwrap().write_all(b"Hello_file_1").unwrap();
|
|
|
|
fs::File::create(src_dir.join(file_2_relative)).unwrap().write_all(b"Hello_file_2").unwrap();
|
2020-07-02 16:20:45 +02:00
|
|
|
|
2021-02-09 11:08:30 +01:00
|
|
|
|
2020-07-28 14:41:49 +02:00
|
|
|
assert!(compression::to_tar_gz(&src_dir, &archive_path).is_ok());
|
2020-07-02 16:20:45 +02:00
|
|
|
assert!(archive_path.exists());
|
|
|
|
assert!(load_snapshot(&dest_dir.to_str().unwrap(), &archive_path, false, false).is_ok());
|
|
|
|
|
|
|
|
assert!(dest_dir.exists());
|
|
|
|
assert!(dest_dir.join(file_1_relative).exists());
|
2020-10-13 11:17:02 +02:00
|
|
|
assert!(dest_dir.join(subdir_relative).exists());
|
2020-07-02 16:20:45 +02:00
|
|
|
assert!(dest_dir.join(file_2_relative).exists());
|
|
|
|
|
|
|
|
let contents = fs::read_to_string(dest_dir.join(file_1_relative)).unwrap();
|
|
|
|
assert_eq!(contents, "Hello_file_1");
|
2021-02-09 11:08:30 +01:00
|
|
|
|
2020-07-02 16:20:45 +02:00
|
|
|
let contents = fs::read_to_string(dest_dir.join(file_2_relative)).unwrap();
|
|
|
|
assert_eq!(contents, "Hello_file_2");
|
|
|
|
}
|
|
|
|
}
|