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

View file

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

View file

@ -3,10 +3,10 @@ mod archive;
use crate::error::ReadError;
use crate::logfile::archive::{Archive, ArchiveEntry, TarArchive, ZipArchive};
use bzip2_rs::DecoderReader;
use dialoguer::Select;
use flate2::read::GzDecoder;
use ruzstd::StreamingDecoder;
use std::fs::File;
use std::io::{BufReader, Read};
use std::io::{Cursor, Read, Seek};
use xz2::read::XzDecoder;
pub struct LogFile {
@ -14,14 +14,10 @@ pub struct LogFile {
}
impl LogFile {
pub fn open(path: &str) -> Result<LogFile, ReadError> {
let file = File::open(path)?;
let file = BufReader::new(file);
pub fn open<R: Read + Seek>(path: &str, file: R) -> Result<LogFile, ReadError> {
if path.ends_with(".zip") {
let mut zip = ZipArchive::new(file)?;
let content = select_file(&mut zip)?;
return Ok(LogFile { content });
return select_file(&mut zip);
}
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> {
if path.ends_with(".tar") {
let mut zip = TarArchive::new(file)?;
let content = select_file(&mut zip)?;
Ok(LogFile { content })
select_file(&mut zip)
} else {
let mut content = String::new();
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 mut entries = archive
.entries()
.filter(|entry| !entry.name().starts_with("__MACOSX"))
.filter(|entry| !entry.name().starts_with("__MACOSX") && !entry.name().ends_with('/'))
.collect::<Vec<_>>();
// todo: present a picker instead
if entries.len() > 1 {
return Err(ReadError::MultipleFiles);
} else if entries.is_empty() {
if entries.is_empty() {
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()?;
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 std::borrow::Cow;
use std::collections::HashMap;
use std::fs::File;
use std::io::BufReader;
use std::iter::once;
mod app;
@ -39,7 +41,9 @@ struct Args {
fn main() -> MainResult {
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,
path: args.file,
})?;