support selecting a file from an archive

This commit is contained in:
Robin Appelman 2024-11-05 19:50:13 +01:00
commit 9634736d8c
5 changed files with 94 additions and 22 deletions

64
Cargo.lock generated
View file

@ -231,6 +231,19 @@ dependencies = [
"static_assertions", "static_assertions",
] ]
[[package]]
name = "console"
version = "0.15.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
dependencies = [
"encode_unicode",
"lazy_static",
"libc",
"unicode-width 0.1.14",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "constant_time_eq" name = "constant_time_eq"
version = "0.3.1" version = "0.3.1"
@ -386,6 +399,19 @@ dependencies = [
"syn 2.0.87", "syn 2.0.87",
] ]
[[package]]
name = "dialoguer"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de"
dependencies = [
"console",
"shell-words",
"tempfile",
"thiserror",
"zeroize",
]
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.10.7" version = "0.10.7"
@ -414,6 +440,12 @@ version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.1" version = "1.0.1"
@ -430,6 +462,12 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "fastrand"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
[[package]] [[package]]
name = "filetime" name = "filetime"
version = "0.2.25" version = "0.2.25"
@ -607,6 +645,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.161" version = "0.2.161"
@ -661,6 +705,7 @@ dependencies = [
"bzip2-rs", "bzip2-rs",
"clap", "clap",
"derive_more", "derive_more",
"dialoguer",
"flate2", "flate2",
"hdrhistogram", "hdrhistogram",
"itertools", "itertools",
@ -1091,6 +1136,12 @@ dependencies = [
"digest", "digest",
] ]
[[package]]
name = "shell-words"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
[[package]] [[package]]
name = "shlex" name = "shlex"
version = "1.3.0" version = "1.3.0"
@ -1212,6 +1263,19 @@ dependencies = [
"xattr", "xattr",
] ]
[[package]]
name = "tempfile"
version = "3.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b"
dependencies = [
"cfg-if",
"fastrand",
"once_cell",
"rustix",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "termcolor" name = "termcolor"
version = "1.4.1" version = "1.4.1"

View file

@ -29,6 +29,7 @@ flate2 = "1.0.31"
xz2 = "0.1.7" xz2 = "0.1.7"
bzip2-rs = "0.1.2" bzip2-rs = "0.1.2"
ruzstd = "0.7.2" ruzstd = "0.7.2"
dialoguer = "0.11.0"
[target.'cfg(not(target_os = "windows"))'.dependencies] [target.'cfg(not(target_os = "windows"))'.dependencies]
tikv-jemallocator = "0.6.0" tikv-jemallocator = "0.6.0"

View file

@ -25,8 +25,6 @@ pub enum ReadError {
Zip(#[from] ZipError), Zip(#[from] ZipError),
#[error(transparent)] #[error(transparent)]
Zstd(#[from] FrameDecoderError), Zstd(#[from] FrameDecoderError),
#[error("archive contains multiple files")]
MultipleFiles,
#[error("archive contains no files")] #[error("archive contains no files")]
NoFiles, NoFiles,
#[error("log file contained non-utf8 characters: {0:#}")] #[error("log file contained non-utf8 characters: {0:#}")]

View file

@ -3,10 +3,10 @@ mod archive;
use crate::error::ReadError; use crate::error::ReadError;
use crate::logfile::archive::{Archive, ArchiveEntry, TarArchive, ZipArchive}; use crate::logfile::archive::{Archive, ArchiveEntry, TarArchive, ZipArchive};
use bzip2_rs::DecoderReader; use bzip2_rs::DecoderReader;
use dialoguer::Select;
use flate2::read::GzDecoder; use flate2::read::GzDecoder;
use ruzstd::StreamingDecoder; use ruzstd::StreamingDecoder;
use std::fs::File; use std::io::{Cursor, Read, Seek};
use std::io::{BufReader, Read};
use xz2::read::XzDecoder; use xz2::read::XzDecoder;
pub struct LogFile { pub struct LogFile {
@ -14,14 +14,10 @@ pub struct LogFile {
} }
impl LogFile { impl LogFile {
pub fn open(path: &str) -> Result<LogFile, ReadError> { pub fn open<R: Read + Seek>(path: &str, file: R) -> Result<LogFile, ReadError> {
let file = File::open(path)?;
let file = BufReader::new(file);
if path.ends_with(".zip") { if path.ends_with(".zip") {
let mut zip = ZipArchive::new(file)?; let mut zip = ZipArchive::new(file)?;
let content = select_file(&mut zip)?; return select_file(&mut zip);
return Ok(LogFile { content });
} }
if let Some(path) = path.strip_suffix(".gz") { if let Some(path) = path.strip_suffix(".gz") {
@ -44,9 +40,7 @@ impl LogFile {
fn open_no_seek<R: Read>(path: &str, mut file: R) -> Result<LogFile, ReadError> { fn open_no_seek<R: Read>(path: &str, mut file: R) -> Result<LogFile, ReadError> {
if path.ends_with(".tar") { if path.ends_with(".tar") {
let mut zip = TarArchive::new(file)?; let mut zip = TarArchive::new(file)?;
let content = select_file(&mut zip)?; select_file(&mut zip)
Ok(LogFile { content })
} else { } else {
let mut content = String::new(); let mut content = String::new();
file.read_to_string(&mut content)?; file.read_to_string(&mut content)?;
@ -64,21 +58,32 @@ impl LogFile {
} }
} }
fn select_file<A: Archive>(archive: &mut A) -> Result<String, ReadError> { fn select_file<A: Archive>(archive: &mut A) -> Result<LogFile, ReadError> {
let entry = { let entry = {
let mut entries = archive let mut entries = archive
.entries() .entries()
.filter(|entry| !entry.name().starts_with("__MACOSX")) .filter(|entry| !entry.name().starts_with("__MACOSX") && !entry.name().ends_with('/'))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
// todo: present a picker instead if entries.is_empty() {
if entries.len() > 1 {
return Err(ReadError::MultipleFiles);
} else if entries.is_empty() {
return Err(ReadError::NoFiles); return Err(ReadError::NoFiles);
} }
entries.pop().unwrap()
let index = if entries.len() == 1 {
0usize
} else {
let names = entries.iter().map(A::Entry::name).collect::<Vec<_>>();
Select::new()
.with_prompt("Select file to load?")
.items(&names)
.interact()
.unwrap()
}; };
entries.remove(index)
};
let name = entry.name().to_string();
let raw = entry.extract()?; let raw = entry.extract()?;
Ok(String::from_utf8(raw)?)
LogFile::open(&name, Cursor::new(raw))
} }

View file

@ -11,6 +11,8 @@ use main_error::MainResult;
use rayon::prelude::*; use rayon::prelude::*;
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashMap; use std::collections::HashMap;
use std::fs::File;
use std::io::BufReader;
use std::iter::once; use std::iter::once;
mod app; mod app;
@ -39,7 +41,9 @@ struct Args {
fn main() -> MainResult { fn main() -> MainResult {
let args = Args::parse(); let args = Args::parse();
let log_file = LogFile::open(&args.file).map_err(|err| LogError::Read { let file = File::open(&args.file)?;
let file = BufReader::new(file);
let log_file = LogFile::open(&args.file, file).map_err(|err| LogError::Read {
err, err,
path: args.file, path: args.file,
})?; })?;