148 lines
3.4 KiB
Rust
148 lines
3.4 KiB
Rust
use std::{
|
|
env,
|
|
fs,
|
|
io,
|
|
path::{Path, PathBuf},
|
|
process,
|
|
time::SystemTime,
|
|
};
|
|
|
|
use anyhow::{
|
|
Context,
|
|
bail,
|
|
};
|
|
|
|
#[tokio::main]
|
|
async fn main () -> anyhow::Result <()> {
|
|
use clap::{Arg, App, SubCommand};
|
|
|
|
let matches = App::new ("ReactorScram/Lookup")
|
|
.author ("ReactorScram (Trisha)")
|
|
.about ("Looks up information about a file, based on its hash")
|
|
.subcommand (SubCommand::with_name ("browse")
|
|
.about ("Opens a read-only Lookup file, from Trisha's repo, in a web browser, using `xdg-open`")
|
|
.arg (
|
|
Arg::with_name ("INPUT")
|
|
.help ("Sets the input file to use")
|
|
.required (true)
|
|
)
|
|
)
|
|
.subcommand (SubCommand::with_name ("edit")
|
|
.about ("Opens a local Lookup file with $EDITOR")
|
|
.arg (
|
|
Arg::with_name ("INPUT")
|
|
.help ("Sets the input file to use")
|
|
.required (true)
|
|
)
|
|
)
|
|
.arg (
|
|
Arg::with_name ("INPUT")
|
|
.help ("Sets the input file to use")
|
|
)
|
|
.get_matches ();
|
|
|
|
if let Some (matches) = matches.subcommand_matches ("browse") {
|
|
let public_repo = "https://six-five-six-four.com/git/reactor/lookup/src/branch/main/lookup_repo";
|
|
|
|
let input_path = Path::new (matches.value_of ("INPUT").unwrap ());
|
|
let b3_hash = hash (input_path)?;
|
|
|
|
let url = format! ("{}/{:?}", public_repo, repo_path (&b3_hash));
|
|
|
|
process::Command::new ("xdg-open")
|
|
.arg (url)
|
|
.status ().context ("while spawning browser")?;
|
|
return Ok (());
|
|
}
|
|
if let Some (matches) = matches.subcommand_matches ("edit") {
|
|
let input_path = Path::new (matches.value_of ("INPUT").unwrap ());
|
|
|
|
let b3_hash = hash (input_path)?;
|
|
|
|
let local_path = PathBuf::from ("lookup_repo").join (repo_path (&b3_hash));
|
|
let local_dir = local_path.parent ().unwrap ();
|
|
|
|
let editor = env::var ("EDITOR").unwrap_or_else (|_| String::from ("nano"));
|
|
|
|
fs::create_dir_all (local_dir)?;
|
|
process::Command::new ("kate")
|
|
.arg ("-b")
|
|
.arg (local_path)
|
|
.status ().context ("while spawning editor")?;
|
|
return Ok (());
|
|
}
|
|
|
|
let input_path = Path::new (matches.value_of ("INPUT").unwrap ());
|
|
|
|
let b3_hash = hash (input_path)?;
|
|
|
|
let local_path = PathBuf::from ("lookup_repo").join (repo_path (&b3_hash));
|
|
|
|
println! ("Local path: {:?}", local_path);
|
|
|
|
Ok (())
|
|
}
|
|
|
|
fn hash (input_path: &Path) -> anyhow::Result <blake3::Hash>
|
|
{
|
|
let mut hasher = blake3::Hasher::new ();
|
|
|
|
{
|
|
let mod_guard = FileModificationGuard::try_new (input_path)?;
|
|
let mut input_file = fs::File::open (input_path)?;
|
|
|
|
io::copy (&mut input_file, &mut hasher)?;
|
|
|
|
mod_guard.check ().context ("while hashing input file")?;
|
|
}
|
|
|
|
Ok (hasher.finalize ())
|
|
}
|
|
|
|
fn repo_path (b3_hash: &blake3::Hash) -> PathBuf {
|
|
let b3_hex = b3_hash.to_hex ();
|
|
|
|
PathBuf::from ("blake3")
|
|
.join (&b3_hex [0..2])
|
|
.join (&b3_hex [2..])
|
|
}
|
|
|
|
struct FileModificationGuard <'a> {
|
|
path: &'a Path,
|
|
metadata_start: CommonFileMetadata,
|
|
}
|
|
|
|
#[derive (PartialEq)]
|
|
struct CommonFileMetadata {
|
|
len: u64,
|
|
modified: SystemTime
|
|
}
|
|
|
|
impl <'a> FileModificationGuard <'a> {
|
|
fn try_new (path: &'a Path) -> anyhow::Result <Self> {
|
|
Ok (Self {
|
|
path,
|
|
metadata_start: CommonFileMetadata::try_new (path)?,
|
|
})
|
|
}
|
|
|
|
fn check (&self) -> anyhow::Result <()> {
|
|
let md = CommonFileMetadata::try_new (self.path)?;
|
|
if md != self.metadata_start {
|
|
bail! ("File metadata changed");
|
|
}
|
|
|
|
Ok (())
|
|
}
|
|
}
|
|
|
|
impl CommonFileMetadata {
|
|
fn try_new (path: &Path) -> anyhow::Result <Self> {
|
|
let md = fs::metadata (path)?;
|
|
Ok (Self {
|
|
len: md.len (),
|
|
modified: md.modified ()?,
|
|
})
|
|
}
|
|
}
|