Format with cargo fmt

This commit is contained in:
Jacob Hinkle 2022-11-22 09:16:03 -05:00
parent 55c97a2740
commit 1badaeefb1
6 changed files with 193 additions and 160 deletions

View File

@ -112,8 +112,10 @@ pub enum FindDatasetError {
NoPathsProvided, NoPathsProvided,
#[error("Something went wrong when parsing paths")] #[error("Something went wrong when parsing paths")]
PathError(std::io::Error), PathError(std::io::Error),
#[error("None of the search paths belongs to an existing dataset. Returned path is the nearest #[error(
common ancestor of all searched paths (on same filesystem)")] "None of the search paths belongs to an existing dataset. Returned path is the nearest
common ancestor of all searched paths (on same filesystem)"
)]
NoDataset(PathBuf), NoDataset(PathBuf),
#[error("Some, but not all, search paths do not reside in an existing dataset")] #[error("Some, but not all, search paths do not reside in an existing dataset")]
SomeNotInDataset, SomeNotInDataset,

View File

@ -10,7 +10,7 @@ use std::io::{Error as IOError, Read};
/// Some simple namespaces to use for deriving UUID v5 keys /// Some simple namespaces to use for deriving UUID v5 keys
const NAMESPACE_MACHINE: Uuid = Uuid::from_bytes([1; 16]); const NAMESPACE_MACHINE: Uuid = Uuid::from_bytes([1; 16]);
#[derive(Clone,Debug)] #[derive(Clone, Debug)]
pub struct Machine { pub struct Machine {
pub key: Uuid, pub key: Uuid,
pub machine_id: String, pub machine_id: String,
@ -47,7 +47,7 @@ impl Machine {
} }
} }
#[derive(Clone,Debug)] #[derive(Clone, Debug)]
pub struct User { pub struct User {
pub key: Uuid, pub key: Uuid,
pub machine: Machine, pub machine: Machine,
@ -85,7 +85,7 @@ impl User {
} }
} }
#[derive(Clone,Debug)] #[derive(Clone, Debug)]
pub struct Environment { pub struct Environment {
pub key: Uuid, pub key: Uuid,
pub user: User, pub user: User,
@ -97,25 +97,17 @@ pub struct Environment {
} }
impl Environment { impl Environment {
pub fn current(addl_info: Option<String>) -> Self { pub fn current(addl_info: Option<String>) -> Self {
let lor_result: Result<String, IOError> = ( let lor_result: Result<String, IOError> = (|| {
|| { let mut s = String::new();
let mut s = String::new(); let mut f = File::open("/etc/os-release")?;
let mut f = File::open("/etc/os-release")?; f.read_to_string(&mut s)?;
f.read_to_string(&mut s)?; Ok(s)
Ok(s) })();
})();
let lor = lor_result.unwrap_or_else(|_| "Unknown".to_string()); let lor = lor_result.unwrap_or_else(|_| "Unknown".to_string());
let user = User::current(); let user = User::current();
let os_release = sys_info::os_release().unwrap_or_else(|_| "Unknown".to_string()); let os_release = sys_info::os_release().unwrap_or_else(|_| "Unknown".to_string());
//let env_vars = HashMap::from_iter(env::vars()); //let env_vars = HashMap::from_iter(env::vars());
let key = Uuid::new_v5( let key = Uuid::new_v5(&user.key, format!("{}\n{}", os_release, lor,).as_bytes());
&user.key,
format!(
"{}\n{}",
os_release,
lor,
).as_bytes(),
);
Environment { Environment {
key, key,
user, user,

140
src/fs.rs
View File

@ -4,7 +4,7 @@ use jwalk::{Error as JWalkError, WalkDir};
use log; use log;
use rayon::prelude::*; use rayon::prelude::*;
use ring::digest::{Context, SHA256}; use ring::digest::{Context, SHA256};
use rusqlite::{Error as RSError, Result as RSResult, ToSql, Transaction, types as rstypes}; use rusqlite::{types as rstypes, Error as RSError, Result as RSResult, ToSql, Transaction};
use thiserror::Error; use thiserror::Error;
use uuid::Uuid; use uuid::Uuid;
@ -17,10 +17,9 @@ use std::time::{Instant, SystemTime};
use crate::timing::persistent_stamp; use crate::timing::persistent_stamp;
#[derive(Copy, Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
pub enum FileType { pub enum FileType {
Other = 0, // char/block devices, FIFOs, sockets... Other = 0, // char/block devices, FIFOs, sockets...
Regular = 1, Regular = 1,
Directory = 2, Directory = 2,
Symlink = 3, Symlink = 3,
@ -65,7 +64,7 @@ pub enum RecordError {
TooBigDepthJump, TooBigDepthJump,
} }
#[derive(Copy,Clone,Debug,From)] #[derive(Copy, Clone, Debug, From)]
pub struct Hash256([u8; 32]); pub struct Hash256([u8; 32]);
impl fmt::LowerHex for Hash256 { impl fmt::LowerHex for Hash256 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -114,7 +113,6 @@ fn buffered_hash256<R: Read>(mut reader: R) -> IOResult<(Hash256, usize)> {
Ok((hash.into(), num_bytes)) Ok((hash.into(), num_bytes))
} }
/// Walk the directory while sorting by filename, maintaining a stack of hashes (keys) for /// Walk the directory while sorting by filename, maintaining a stack of hashes (keys) for
/// directories and insert entries into current_files table as we pass over them. This means /// directories and insert entries into current_files table as we pass over them. This means
/// computing the sha256 key, storing it along with parent, filetype, and symlink_target. /// computing the sha256 key, storing it along with parent, filetype, and symlink_target.
@ -122,7 +120,8 @@ fn walk_and_insert(tx: &Transaction, p: &Path, path_key: Hash256) -> Result<(),
let mut insert_stmt = tx.prepare( let mut insert_stmt = tx.prepare(
"INSERT OR IGNORE INTO current_files "INSERT OR IGNORE INTO current_files
(sha256, name, filepath, parent, version_uuid, filetype, symlink_target, recorded_time) (sha256, name, filepath, parent, version_uuid, filetype, symlink_target, recorded_time)
VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)")?; VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
)?;
let mut dirstack: Vec<Option<Hash256>> = vec![]; let mut dirstack: Vec<Option<Hash256>> = vec![];
let mut parent: Option<Hash256> = None; let mut parent: Option<Hash256> = None;
let mut lastkey: Option<Hash256> = None; let mut lastkey: Option<Hash256> = None;
@ -133,15 +132,18 @@ fn walk_and_insert(tx: &Transaction, p: &Path, path_key: Hash256) -> Result<(),
log::trace!("walk_and_insert {:?}", p); log::trace!("walk_and_insert {:?}", p);
for entry_result in WalkDir::new(p) for entry_result in WalkDir::new(p)
.follow_links(false) .follow_links(false)
.skip_hidden(true) .skip_hidden(true)
.sort(true) { .sort(true)
{
let entry = entry_result?; let entry = entry_result?;
let path = entry.path(); let path = entry.path();
let pathstr = path.to_str() let pathstr = path
.to_str()
.expect("Path will be convertable to a UTF-8 string"); .expect("Path will be convertable to a UTF-8 string");
let filetype = FileType::from(entry.file_type()); let filetype = FileType::from(entry.file_type());
let filename = path.file_name() let filename = path
.file_name()
.expect("Path will end in a named component (unlike / or foo/..)") .expect("Path will end in a named component (unlike / or foo/..)")
.to_str() .to_str()
.expect("File name will be convertable to a UTF-8 string"); .expect("File name will be convertable to a UTF-8 string");
@ -149,11 +151,13 @@ fn walk_and_insert(tx: &Transaction, p: &Path, path_key: Hash256) -> Result<(),
let symlink_target = if filetype == FileType::Symlink { let symlink_target = if filetype == FileType::Symlink {
Some( Some(
std::fs::read_link(&path)? std::fs::read_link(&path)?
.to_str() .to_str()
.expect("Symlink target will be convertable to a UTF-8 string") .expect("Symlink target will be convertable to a UTF-8 string")
.to_string() .to_string(),
) )
} else { None }; } else {
None
};
if entry.depth > depth { if entry.depth > depth {
// descending into directory // descending into directory
@ -178,7 +182,8 @@ fn walk_and_insert(tx: &Transaction, p: &Path, path_key: Hash256) -> Result<(),
Some(Hash256(b)) => { Some(Hash256(b)) => {
ctx.update(&b[..]); ctx.update(&b[..]);
} }
None => { // Root. hash is the hash of the uuid for this dataset None => {
// Root. hash is the hash of the uuid for this dataset
ctx.update(&path_key.0); ctx.update(&path_key.0);
} }
} }
@ -190,15 +195,15 @@ fn walk_and_insert(tx: &Transaction, p: &Path, path_key: Hash256) -> Result<(),
let ver_uuid = Uuid::new_v4(); let ver_uuid = Uuid::new_v4();
insert_stmt.execute(( insert_stmt.execute((
thiskey.0, thiskey.0,
filename, filename,
pathstr, pathstr,
parent, parent,
ver_uuid.as_bytes(), ver_uuid.as_bytes(),
filetype as u8, filetype as u8,
symlink_target, symlink_target,
persistent_stamp(Instant::now(), start_instant, start_systemtime), persistent_stamp(Instant::now(), start_instant, start_systemtime),
))?; ))?;
lastkey = Some(thiskey); lastkey = Some(thiskey);
} }
Ok(()) Ok(())
@ -210,31 +215,37 @@ fn insert_file_content_hashes(tx: &Transaction) -> Result<(), RecordError> {
log::trace!("insert_file_content_hashes"); log::trace!("insert_file_content_hashes");
// Extract all regular files (key and path) from current_files, in arbitrary order, and compute // Extract all regular files (key and path) from current_files, in arbitrary order, and compute
// content hashes for each, then update the corresponding entry in current_files. // content hashes for each, then update the corresponding entry in current_files.
let allpaths: Vec<RSResult<(i32, String)>> = tx.prepare( let allpaths: Vec<RSResult<(i32, String)>> = tx
&format!( .prepare(&format!(
"SELECT id, filepath FROM current_files WHERE filetype == {}", "SELECT id, filepath FROM current_files WHERE filetype == {}",
FileType::Regular as u8) FileType::Regular as u8
)?.query_map((), |row| { ))?
.query_map((), |row| {
let id = row.get::<usize, i32>(0).unwrap(); let id = row.get::<usize, i32>(0).unwrap();
let filepath = row.get::<usize, String>(1).unwrap(); let filepath = row.get::<usize, String>(1).unwrap();
//println!("{} {}", id, filepath); //println!("{} {}", id, filepath);
Ok((id, filepath)) Ok((id, filepath))
}).unwrap().collect::<Vec< })
RSResult<(i32, String)>>>(); .unwrap()
.collect::<Vec<RSResult<(i32, String)>>>();
let start_systemtime = SystemTime::now(); let start_systemtime = SystemTime::now();
let start_instant = Instant::now(); let start_instant = Instant::now();
let hashes: Vec<IOResult<(i32, Hash256, usize, Instant)>> = allpaths.par_iter().map(|fpres| { let hashes: Vec<IOResult<(i32, Hash256, usize, Instant)>> = allpaths
if let Ok((id, filepath)) = fpres { .par_iter()
let input = File::open(filepath)?; .map(|fpres| {
let reader = BufReader::new(input); if let Ok((id, filepath)) = fpres {
let (chash, size_bytes) = buffered_hash256(reader)?; let input = File::open(filepath)?;
Ok((*id, chash, size_bytes, Instant::now())) let reader = BufReader::new(input);
} else { // should never happen let (chash, size_bytes) = buffered_hash256(reader)?;
Ok((0_i32, Hash256([0; 32]), 0, Instant::now())) Ok((*id, chash, size_bytes, Instant::now()))
} } else {
}).collect(); // should never happen
Ok((0_i32, Hash256([0; 32]), 0, Instant::now()))
}
})
.collect();
let mut update_stmt = tx.prepare( let mut update_stmt = tx.prepare(
"UPDATE current_files "UPDATE current_files
SET SET
@ -242,7 +253,8 @@ fn insert_file_content_hashes(tx: &Transaction) -> Result<(), RecordError> {
size_bytes = ?3, size_bytes = ?3,
recorded_time = ?4 recorded_time = ?4
WHERE WHERE
id = ?1")?; id = ?1",
)?;
for (id, hash, size_bytes, recorded_instant) in hashes.into_iter().flatten() { for (id, hash, size_bytes, recorded_instant) in hashes.into_iter().flatten() {
update_stmt.execute(( update_stmt.execute((
id, id,
@ -294,8 +306,13 @@ fn compute_current_directory_hashes(_tx: &Transaction) -> Result<(), RecordError
} }
/// Transfer rows in current_files and deleted_files into new rows in filedir_version /// Transfer rows in current_files and deleted_files into new rows in filedir_version
fn persist_temp_tables(tx: &Transaction, task_uuid: Uuid, dataset_uuid: Uuid) -> Result<(), RecordError> { fn persist_temp_tables(
tx.execute(" tx: &Transaction,
task_uuid: Uuid,
dataset_uuid: Uuid,
) -> Result<(), RecordError> {
tx.execute(
"
INSERT OR IGNORE INTO INSERT OR IGNORE INTO
filedir filedir
SELECT SELECT
@ -303,8 +320,9 @@ fn persist_temp_tables(tx: &Transaction, task_uuid: Uuid, dataset_uuid: Uuid) ->
FROM current_files FROM current_files
", ",
(dataset_uuid.as_bytes(),), (dataset_uuid.as_bytes(),),
)?; )?;
tx.execute(" tx.execute(
"
INSERT INTO INSERT INTO
filedir_version filedir_version
SELECT SELECT
@ -312,7 +330,7 @@ fn persist_temp_tables(tx: &Transaction, task_uuid: Uuid, dataset_uuid: Uuid) ->
FROM current_files FROM current_files
", ",
(task_uuid.as_bytes(),), (task_uuid.as_bytes(),),
)?; )?;
Ok(()) Ok(())
} }
@ -333,10 +351,11 @@ pub fn record(
// get local dataset UUID // get local dataset UUID
let u = Uuid::parse_str( let u = Uuid::parse_str(
tx.prepare("SELECT value FROM local_metadata WHERE key = 'dataset_uuid'")? tx.prepare("SELECT value FROM local_metadata WHERE key = 'dataset_uuid'")?
.query_row([], |row| { .query_row([], |row| {
let uuidstr: Result<String, RSError> = row.get(0); let uuidstr: Result<String, RSError> = row.get(0);
uuidstr uuidstr
})?.as_str() })?
.as_str(),
)?; )?;
log::debug!("Found dataset UUID: {}", u); log::debug!("Found dataset UUID: {}", u);
@ -351,7 +370,8 @@ pub fn record(
// This schema is like filedir joined with filedir_version // This schema is like filedir joined with filedir_version
// TODO: revert this to a TEMP TABLE after it's debugged // TODO: revert this to a TEMP TABLE after it's debugged
tx.execute("CREATE TEMP TABLE current_files ( tx.execute(
"CREATE TEMP TABLE current_files (
id INTEGER PRIMARY KEY NOT NULL, -- only used in this table id INTEGER PRIMARY KEY NOT NULL, -- only used in this table
sha256 BLOB NOT NULL, -- will become the primary key on filedir sha256 BLOB NOT NULL, -- will become the primary key on filedir
name TEXT NOT NULL, -- filename without path name TEXT NOT NULL, -- filename without path
@ -365,7 +385,9 @@ pub fn record(
size_bytes INTEGER, -- null for anything but files/dirs. For dirs, the sum of children size_bytes INTEGER, -- null for anything but files/dirs. For dirs, the sum of children
content_sha256 BLOB, content_sha256 BLOB,
UNIQUE(sha256) -- prevent inserting same file twice UNIQUE(sha256) -- prevent inserting same file twice
)", [])?; )",
[],
)?;
for p in paths { for p in paths {
let p = p.canonicalize()?; let p = p.canonicalize()?;
@ -377,8 +399,11 @@ pub fn record(
// UUID to indicate the root directory, then taking the SHA256 of the parent plus filename, // UUID to indicate the root directory, then taking the SHA256 of the parent plus filename,
// as we descend into subdirectories until we reach p. // as we descend into subdirectories until we reach p.
let mut ancestors = LinkedList::new(); let mut ancestors = LinkedList::new();
for a in p.ancestors() { // fill a stack of ancestor dirs for a in p.ancestors() {
if a.canonicalize()? == ds_root.canonicalize()? { break }; // fill a stack of ancestor dirs
if a.canonicalize()? == ds_root.canonicalize()? {
break;
};
ancestors.push_front(a); ancestors.push_front(a);
} }
@ -386,7 +411,8 @@ pub fn record(
let mut prev_key = root_key; let mut prev_key = root_key;
for a in ancestors { for a in ancestors {
// hash of parent hash + filename // hash of parent hash + filename
let filename = a.file_name() let filename = a
.file_name()
.ok_or_else(|| RecordError::CantGetFilename(a.to_owned()))? .ok_or_else(|| RecordError::CantGetFilename(a.to_owned()))?
.to_str() .to_str()
.ok_or_else(|| RecordError::FilenameNotUTF8(a.to_owned()))?; .ok_or_else(|| RecordError::FilenameNotUTF8(a.to_owned()))?;

View File

@ -24,7 +24,7 @@ enum Command {
#[arg(short, long)] #[arg(short, long)]
name: String, name: String,
/// The top level directory of the dataset (must be a directory) /// The top level directory of the dataset (must be a directory)
#[arg(default_value=".")] #[arg(default_value = ".")]
dataset_path: PathBuf, dataset_path: PathBuf,
}, },
/// Record changes to files/directories within a dataset (or create a new dataset) /// Record changes to files/directories within a dataset (or create a new dataset)
@ -41,27 +41,22 @@ enum Command {
} }
fn init_schema(conn: &mut Connection, name: &str) -> Result<Uuid> { fn init_schema(conn: &mut Connection, name: &str) -> Result<Uuid> {
let dataset_uuid = nancy::db::init(conn, name) let dataset_uuid =
.map_err(|e| anyhow!("failed to initialize schema: {}", e))?; nancy::db::init(conn, name).map_err(|e| anyhow!("failed to initialize schema: {}", e))?;
log::trace!("Init OK"); log::trace!("Init OK");
log::info!("Dataset UUID is {dataset_uuid}"); log::info!("Dataset UUID is {dataset_uuid}");
// Run an empty program so that the dataset log reflects when it was // Run an empty program so that the dataset log reflects when it was
// initialized // initialized
nancy::program::with_program( nancy::program::with_program(conn, "INIT", "Initialize dataset", |prog| {
conn, let _ = prog.perform_task(&[], |task| {
"INIT", log::debug!("INIT task UUID is {}", task.key);
"Initialize dataset", Ok::<(), ()>(())
|prog| { });
let _ = prog.perform_task(&[], |task| { let okres: Result<()> = Ok(());
log::debug!("INIT task UUID is {}", task.key); okres
Ok::<(), ()>(()) })
}); .context("Could not run empty program during init_schema")??;
let okres: Result<()> = Ok(());
okres
},
)
.context("Could not run empty program during init_schema")??;
Ok(dataset_uuid) Ok(dataset_uuid)
} }
@ -69,12 +64,18 @@ fn init_schema(conn: &mut Connection, name: &str) -> Result<Uuid> {
/// Run init subcommand and return the return code /// Run init subcommand and return the return code
fn init_cmd(name: &str, dataset_path: &Path) -> Result<Uuid> { fn init_cmd(name: &str, dataset_path: &Path) -> Result<Uuid> {
if !dataset_path.is_dir() { if !dataset_path.is_dir() {
bail!("Path {:?} does not point to an existing directory", dataset_path); bail!(
"Path {:?} does not point to an existing directory",
dataset_path
);
} }
let dbpath = &dataset_path.join("nancy.db"); let dbpath = &dataset_path.join("nancy.db");
if dbpath.exists() { if dbpath.exists() {
bail!("Database {:?} exists, indicating this dataset is already \ bail!(
initialized. Refusing to overwrite.", dbpath); "Database {:?} exists, indicating this dataset is already \
initialized. Refusing to overwrite.",
dbpath
);
} }
log::info!("Initializing new database at {:?}", dbpath); log::info!("Initializing new database at {:?}", dbpath);
let mut conn = Connection::open(dbpath) let mut conn = Connection::open(dbpath)
@ -99,7 +100,8 @@ fn record_cmd(message: &str, record_paths: &Vec<PathBuf>) -> Result<()> {
let mut conn = Connection::open_with_flags( let mut conn = Connection::open_with_flags(
dbpath, dbpath,
OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_NO_MUTEX, OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_NO_MUTEX,
).context("Could not open existing SQLite database at {dbpath:?}: {e:?}")?; )
.context("Could not open existing SQLite database at {dbpath:?}: {e:?}")?;
conn.pragma_update(None, "foreign_keys", &"ON")?; conn.pragma_update(None, "foreign_keys", &"ON")?;
@ -117,7 +119,8 @@ fn record_cmd(message: &str, record_paths: &Vec<PathBuf>) -> Result<()> {
task.key, task.key,
) )
}) })
})??.map_err(|e| anyhow!("Record program failed: {}", e)) })??
.map_err(|e| anyhow!("Record program failed: {}", e))
} }
fn main() -> Result<()> { fn main() -> Result<()> {
@ -125,10 +128,9 @@ fn main() -> Result<()> {
let args = Cli::parse(); let args = Cli::parse();
match &args.command { match &args.command {
Command::Init { Command::Init { name, dataset_path } => {
name, init_cmd(name, dataset_path)?;
dataset_path }
} => { init_cmd(name, dataset_path)?; },
Command::Record { Command::Record {
message, message,
record_paths, record_paths,

View File

@ -32,22 +32,20 @@ impl Task {
log::debug!("New Task with UUID {}", key); log::debug!("New Task with UUID {}", key);
// NOTE: we currently do not save the "function" column // NOTE: we currently do not save the "function" column
program.transaction.execute( program
"INSERT INTO task VALUES (?1, ?2, NULL)", .transaction
( .execute(
key.as_bytes(), "INSERT INTO task VALUES (?1, ?2, NULL)",
program.key.as_bytes(), (key.as_bytes(), program.key.as_bytes()),
), )
).map_err(TaskError::InsertTaskFailed)?; .map_err(TaskError::InsertTaskFailed)?;
Ok(Task { Ok(Task { key })
key,
})
} }
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum ProgramError{ pub enum ProgramError {
#[error("creating transaction failed: {0}")] #[error("creating transaction failed: {0}")]
CreateTransactionFailed(RSError), CreateTransactionFailed(RSError),
#[error("creating new program failed: {0}")] #[error("creating new program failed: {0}")]
@ -66,7 +64,7 @@ pub enum ProgramError{
PerformedTaskWhileNotRunning, PerformedTaskWhileNotRunning,
} }
#[derive(Clone,Debug,PartialEq)] #[derive(Clone, Debug, PartialEq)]
enum ProgramState { enum ProgramState {
Initialized, Initialized,
Running, Running,
@ -85,7 +83,11 @@ pub struct Program<'conn> {
state: ProgramState, state: ProgramState,
} }
impl<'conn> Program<'conn> { impl<'conn> Program<'conn> {
pub fn new(tx: &'conn Transaction, name: &str, message: &str) -> Result<Program<'conn>, ProgramError> { pub fn new(
tx: &'conn Transaction,
name: &str,
message: &str,
) -> Result<Program<'conn>, ProgramError> {
let log_target = &format!("nancy.program ({})", name); let log_target = &format!("nancy.program ({})", name);
// start transaction // start transaction
@ -116,19 +118,22 @@ impl<'conn> Program<'conn> {
state: ProgramState::Initialized, state: ProgramState::Initialized,
}; };
prog.environment.record(prog.transaction) prog.environment
.record(prog.transaction)
.map_err(ProgramError::RecordEnvFailed)?; .map_err(ProgramError::RecordEnvFailed)?;
log::debug!("Environment: {:#?}", prog.environment); log::debug!("Environment: {:#?}", prog.environment);
prog.transaction.execute( prog.transaction
"INSERT INTO program VALUES (?1, ?2, ?3, ?4, NULL, NULL)", .execute(
( "INSERT INTO program VALUES (?1, ?2, ?3, ?4, NULL, NULL)",
key.as_bytes(), (
prog.environment.key.as_bytes(), key.as_bytes(),
name, prog.environment.key.as_bytes(),
message, name,
), message,
).map_err(ProgramError::InsertProgramFailed)?; ),
)
.map_err(ProgramError::InsertProgramFailed)?;
Ok(prog) Ok(prog)
} }
@ -155,15 +160,14 @@ impl<'conn> Program<'conn> {
"Elapsed: {} seconds", "Elapsed: {} seconds",
(end - self.start_instant).as_secs_f64() (end - self.start_instant).as_secs_f64()
); );
let end_stamp = timing::persistent_stamp( let end_stamp = timing::persistent_stamp(end, self.start_instant, self.start_systemtime);
end,
self.start_instant,
self.start_systemtime,
);
self.transaction.execute("UPDATE program SET end_time = ?1 WHERE key = ?2", self.transaction
(end_stamp, self.key.as_bytes()), .execute(
).map_err(ProgramError::RecordTimestampsFailed)?; "UPDATE program SET end_time = ?1 WHERE key = ?2",
(end_stamp, self.key.as_bytes()),
)
.map_err(ProgramError::RecordTimestampsFailed)?;
Ok(()) Ok(())
} }
@ -172,7 +176,10 @@ impl<'conn> Drop for Program<'conn> {
/// Checks that the program has been cleaned up by setting state to Finished /// Checks that the program has been cleaned up by setting state to Finished
fn drop(&mut self) { fn drop(&mut self) {
if self.state != ProgramState::Finished { if self.state != ProgramState::Finished {
log::error!("Program reached destructor with unfinished state={:?}", self.state); log::error!(
"Program reached destructor with unfinished state={:?}",
self.state
);
} }
} }
} }
@ -185,7 +192,8 @@ pub fn with_program<F, R, E>(
f: F, f: F,
) -> Result<Result<R, E>, ProgramError> ) -> Result<Result<R, E>, ProgramError>
where where
F: FnOnce(&mut Program) -> Result<R, E> { F: FnOnce(&mut Program) -> Result<R, E>,
{
// NOTE: for Errors outside of f(prog), we should not rely on ?, so that we can keep the // NOTE: for Errors outside of f(prog), we should not rely on ?, so that we can keep the
// From instance for ProgramError as general as possible. Instead, we should manually check // From instance for ProgramError as general as possible. Instead, we should manually check
// errors in the surrounding code in this function and only use ? for f(prog). // errors in the surrounding code in this function and only use ? for f(prog).
@ -199,8 +207,9 @@ where
message message
); );
let tx = conn.transaction() let tx = conn
.map_err(|e| { ProgramError::CreateTransactionFailed(e) })?; .transaction()
.map_err(|e| ProgramError::CreateTransactionFailed(e))?;
let mut prog = Program::new(&tx, name, message)?; let mut prog = Program::new(&tx, name, message)?;
@ -215,14 +224,17 @@ where
prog.record_timestamps()?; prog.record_timestamps()?;
// commit transaction // commit transaction
log::debug!(target: log_target, "Committing transaction and finalizing program"); log::debug!(
target: log_target,
"Committing transaction and finalizing program"
);
prog.state = ProgramState::Finished; prog.state = ProgramState::Finished;
log::debug!(target: log_target, "Set prog.state to Finished"); log::debug!(target: log_target, "Set prog.state to Finished");
drop(prog); // stop borrowing tx, since tx.commit() will consume tx drop(prog); // stop borrowing tx, since tx.commit() will consume tx
log::debug!(target: log_target, "Dropped prog"); log::debug!(target: log_target, "Dropped prog");
tx.commit().map_err(|e| { ProgramError::CommitFailed(e) })?; tx.commit().map_err(|e| ProgramError::CommitFailed(e))?;
} }
Ok(ret) Ok(ret)

View File

@ -1,11 +1,11 @@
use assert_cmd::prelude::*; use assert_cmd::prelude::*;
use assert_fs::prelude::*; //use assert_fs::prelude::*;
use predicates::prelude::*; //use predicates::prelude::*;
use std::fs; use std::fs;
use std::io::Write; use std::io::Write;
use std::path::{Path}; use std::path::Path;
use std::process::{Command}; use std::process::Command;
fn empty_dir() -> Result<assert_fs::TempDir, assert_fs::fixture::FixtureError> { fn empty_dir() -> Result<assert_fs::TempDir, assert_fs::fixture::FixtureError> {
let dir = assert_fs::TempDir::new()?; let dir = assert_fs::TempDir::new()?;
@ -15,7 +15,8 @@ fn empty_dir() -> Result<assert_fs::TempDir, assert_fs::fixture::FixtureError> {
fn init_dir(dir: &Path) -> Result<(), assert_cmd::cargo::CargoError> { fn init_dir(dir: &Path) -> Result<(), assert_cmd::cargo::CargoError> {
let mut cmd = Command::cargo_bin("nancy")?; let mut cmd = Command::cargo_bin("nancy")?;
cmd.arg("init") cmd.arg("init")
.arg("--name").arg("Temporary data directory") .arg("--name")
.arg("Temporary data directory")
.arg(dir) .arg(dir)
.assert() .assert()
.success(); .success();
@ -28,11 +29,7 @@ fn add_some_files(dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
let bazpath = subdir.join("baz.txt"); let bazpath = subdir.join("baz.txt");
fs::create_dir_all(subdir)?; fs::create_dir_all(subdir)?;
let mut bazfile = fs::File::create(bazpath)?; let mut bazfile = fs::File::create(bazpath)?;
write!( write!(bazfile, "{}", "bat",)?;
bazfile,
"{}",
"bat",
)?;
Ok(()) Ok(())
} }
@ -41,7 +38,8 @@ fn add_some_files(dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
fn init_missing_dir() -> Result<(), Box<dyn std::error::Error>> { fn init_missing_dir() -> Result<(), Box<dyn std::error::Error>> {
let mut cmd = Command::cargo_bin("nancy")?; let mut cmd = Command::cargo_bin("nancy")?;
cmd.arg("init") cmd.arg("init")
.arg("--name").arg("Missing data directory") .arg("--name")
.arg("Missing data directory")
.arg("/path/to/missing/dir") .arg("/path/to/missing/dir")
.assert() .assert()
.failure(); .failure();
@ -49,7 +47,6 @@ fn init_missing_dir() -> Result<(), Box<dyn std::error::Error>> {
Ok(()) Ok(())
} }
#[test] #[test]
fn init_empty_dir() -> Result<(), Box<dyn std::error::Error>> { fn init_empty_dir() -> Result<(), Box<dyn std::error::Error>> {
let dir = empty_dir()?; let dir = empty_dir()?;
@ -61,8 +58,10 @@ fn init_empty_dir() -> Result<(), Box<dyn std::error::Error>> {
fn record_dir(dir: &Path) -> Result<assert_cmd::assert::Assert, assert_cmd::cargo::CargoError> { fn record_dir(dir: &Path) -> Result<assert_cmd::assert::Assert, assert_cmd::cargo::CargoError> {
let mut cmd = Command::cargo_bin("nancy")?; let mut cmd = Command::cargo_bin("nancy")?;
Ok(cmd.arg("record") Ok(cmd
.arg("--message").arg("Test recording") .arg("record")
.arg("--message")
.arg("Test recording")
.arg(dir) .arg(dir)
.assert() .assert()
.success()) .success())