This commit is contained in:
2026-05-11 20:44:14 -05:00
parent 1b9814f5c6
commit becab7abd6
2 changed files with 91 additions and 5 deletions
+22
View File
@@ -0,0 +1,22 @@
# Phase 8
Another quality of life feature, I want to add a fourth command:
`drawer info foo.drawer`
This command will take a drawer file (as well as the -i flag for the key that open / close take) and print, to stdout,
some information about it.
## Folder drawers
Print something like this:
```
foo.drawer (folder, 3 files)
```
Be sure to include files in subfolders in the archive.
## Single file drawers
Print something like this:
```
foo.drawer (file, 47872 bytes)
```
The file size printed should be the uncompressed file size.
+69 -5
View File
@@ -15,6 +15,7 @@ pub enum OperationType {
Open, Open,
Close, Close,
Key, Key,
Info,
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@@ -60,6 +61,12 @@ enum Command {
Key { Key {
filename: PathBuf, filename: PathBuf,
}, },
/// Show information about a drawer file
Info {
drawer_file: PathBuf,
#[arg(short = 'i')]
key: Option<PathBuf>,
},
} }
pub fn parse_args<I, T>(args: I) -> Result<DrawerOperation, DrawerError> pub fn parse_args<I, T>(args: I) -> Result<DrawerOperation, DrawerError>
@@ -100,6 +107,16 @@ where
force: false, force: false,
}) })
} }
Command::Info { drawer_file, key } => {
let key = resolve_key(key);
Ok(DrawerOperation {
operation_type: OperationType::Info,
drawer_file,
key,
target_path: None,
force: false,
})
}
} }
} }
@@ -110,6 +127,39 @@ fn resolve_key(key: Option<PathBuf>) -> KeyType {
EnvVar EnvVar
} }
enum ArchiveInfo {
SingleFile { size: u64 },
Folder { file_count: usize },
}
fn archive_info(data: &[u8]) -> Result<ArchiveInfo, DrawerError> {
let cursor = std::io::Cursor::new(data);
let gz = GzDecoder::new(cursor);
let mut archive = tar::Archive::new(gz);
let mut entries = archive.entries().map_err(|_| DrawerError::UntarFailed)?;
let first = entries.next()
.ok_or(DrawerError::UntarFailed)?
.map_err(|_| DrawerError::UntarFailed)?;
if first.header().entry_type().is_file() {
let size = first.header().size().map_err(|_| DrawerError::UntarFailed)?;
if entries.next().is_none() {
return Ok(ArchiveInfo::SingleFile { size });
}
}
let cursor = std::io::Cursor::new(data);
let gz = GzDecoder::new(cursor);
let mut archive = tar::Archive::new(gz);
let file_count = archive.entries()
.map_err(|_| DrawerError::UntarFailed)?
.filter_map(|e| e.ok())
.filter(|e| e.header().entry_type().is_file())
.count();
Ok(ArchiveInfo::Folder { file_count })
}
fn single_file_entry(data: &[u8]) -> Option<PathBuf> { fn single_file_entry(data: &[u8]) -> Option<PathBuf> {
let cursor = std::io::Cursor::new(data); let cursor = std::io::Cursor::new(data);
let gz = GzDecoder::new(cursor); let gz = GzDecoder::new(cursor);
@@ -142,6 +192,7 @@ impl DrawerOperation {
OperationType::Open => self.perform_open(), OperationType::Open => self.perform_open(),
OperationType::Close => self.perform_close(), OperationType::Close => self.perform_close(),
OperationType::Key => self.perform_key(), OperationType::Key => self.perform_key(),
OperationType::Info => self.perform_info(),
} }
} }
@@ -264,16 +315,14 @@ impl DrawerOperation {
self.validate_key()?; self.validate_key()?;
} }
match self.operation_type { match self.operation_type {
OperationType::Open => self.validate_open(), OperationType::Open | OperationType::Info => self.validate_drawer_file(),
OperationType::Close => self.validate_close(), OperationType::Close => self.validate_close(),
OperationType::Key => self.validate_key_gen(), OperationType::Key => self.validate_key_gen(),
} }
} }
fn validate_key_gen(&self) -> Result<(), DrawerError> { fn validate_key_gen(&self) -> Result<(), DrawerError> {
if self.drawer_file.exists() { self.validate_drawer_file()?;
return Err(DrawerError::KeyOutputFailed(self.drawer_file.clone()));
}
let parent = match self.drawer_file.parent() { let parent = match self.drawer_file.parent() {
None => Path::new("."), None => Path::new("."),
Some(p) => { Some(p) => {
@@ -312,13 +361,28 @@ impl DrawerOperation {
Ok(()) Ok(())
} }
fn validate_open(&self) -> Result<(), DrawerError> { // Ensure the drawer file actually exists
fn validate_drawer_file(&self) -> Result<(), DrawerError> {
if !self.drawer_file.exists() { if !self.drawer_file.exists() {
return Err(DrawerError::DrawerFileNotFound(self.drawer_file.clone())); return Err(DrawerError::DrawerFileNotFound(self.drawer_file.clone()));
} }
Ok(()) Ok(())
} }
fn perform_info(&self) -> Result<(), DrawerError> {
let encrypted = std::fs::read(&self.drawer_file)
.map_err(|_| DrawerError::DrawerFileNotFound(self.drawer_file.clone()))?;
let decrypted = self.decrypt(encrypted)?;
let info = archive_info(&decrypted)?;
match info {
ArchiveInfo::SingleFile { size } =>
println!("{} (file, {} bytes)", self.drawer_file.display(), size),
ArchiveInfo::Folder { file_count } =>
println!("{} (folder, {} files)", self.drawer_file.display(), file_count),
}
Ok(())
}
fn perform_key(&self) -> Result<(), DrawerError> { fn perform_key(&self) -> Result<(), DrawerError> {
let private_key = PrivateKey::random(&mut rand::rngs::OsRng, Algorithm::Ed25519) let private_key = PrivateKey::random(&mut rand::rngs::OsRng, Algorithm::Ed25519)
.map_err(|_| DrawerError::KeyGenerateFailed)?; .map_err(|_| DrawerError::KeyGenerateFailed)?;