d4tools 0.3.8

The CLI utils for D4 file format
Documentation
use d4::ssio::http::HttpReader;
use d4_framefile::*;
use d4tools::AppResult;
use std::fs::File;
use std::io::{Read, Seek, Write};

#[allow(clippy::print_literal)]
pub fn dump_dir<R: Read + Seek>(dir: &Directory<R>) {
    println!(
        "{:20}\t{:8}\t{}\t{}\n",
        "Name", "Type", "    Offset", "Primary-Size"
    );
    for entry in dir.entries() {
        println!(
            "{:20}\t{:8}\t{:10}\t{:10}",
            entry.name,
            match entry.kind {
                EntryKind::SubDir => "[SUBDIR]",
                EntryKind::Blob => "[BLOB]",
                EntryKind::Stream => "[STREAM]",
            },
            entry.primary_offset,
            entry.primary_size
        );
    }
}
fn show_impl<R: Read + Seek>(stream: R, path: Vec<&str>) -> AppResult<()> {
    let mut dir = Directory::open_root(stream, 8).unwrap();
    let lc = path.len();
    if path == [""] {
        dump_dir(&dir);
    }
    for (idx, name) in path.iter().enumerate() {
        let last = lc - idx == 1;
        let kind = dir.entry_kind(name);
        match kind {
            Some(EntryKind::SubDir) if !last => {
                dir = dir.open_directory(name)?;
            }
            None | Some(_) if !last => {
                panic!("Path not found");
            }
            Some(EntryKind::Stream) => {
                let mut stream = dir.open_stream(name)?;
                let mut buf = vec![0; 4096];
                while let Ok(len) = stream.read(&mut buf) {
                    std::io::stdout().write_all(&buf[..len])?;
                    if len < 4096 {
                        break;
                    }
                }
            }
            Some(EntryKind::Blob) => {
                let mut blob = dir.open_blob(name)?;
                let mut offset = 0;
                let mut buffer = [0; 40960];
                let mut stdout = std::io::stdout();
                while offset < blob.size() {
                    let actual_read = blob.read_block(offset as u64, &mut buffer)?;
                    offset += actual_read;
                    stdout.write_all(&buffer[..actual_read])?;
                }
            }
            Some(EntryKind::SubDir) => {
                let dir = dir.open_directory(name)?;
                dump_dir(&dir);
            }
            _ => {}
        }
    }
    Ok(())
}
pub fn entry_point(args: Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
    let frame_path: Vec<_> = args
        .get(2)
        .map_or("", |x| x.as_ref())
        .split(|x| x == '/')
        .collect();
    match args.get(1) {
        Some(url) if url.starts_with("http://") || url.starts_with("https://") => {
            let http_reader = HttpReader::new(url)?;
            show_impl(http_reader, frame_path)
        }
        Some(fs_path) => {
            let file_reader = File::open(fs_path)?;
            show_impl(file_reader, frame_path)
        }
        _ => panic!("Usage: d4tools framedump <path-or-url> <frame-path>"),
    }
}