Skip to content

Commit d45bfb4

Browse files
committed
Example of eyre use for extract command
1 parent 5192c95 commit d45bfb4

File tree

7 files changed

+191
-181
lines changed

7 files changed

+191
-181
lines changed

Cargo.lock

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ edition = "2021"
1616
bytes = "1.5.0"
1717
chrono = "0.4.34"
1818
clap = { version = "4.5.1", features = ["derive", "string"] }
19+
color-eyre = { version = "0.6.2", default-features = false }
1920
crossterm = "0.27.0"
2021
derive_more = "0.99"
2122
dialoguer = "0.11.0"

moss/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ vfs = { path = "../crates/vfs" }
1212

1313
bytes.workspace = true
1414
chrono.workspace = true
15+
color-eyre.workspace = true
1516
clap.workspace = true
1617
derive_more.workspace = true
1718
itertools.workspace = true

moss/src/cli/extract.rs

Lines changed: 119 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,17 @@ use std::{
66
fs::{create_dir_all, hard_link, remove_dir_all, remove_file, File},
77
io::{copy, Read, Seek, SeekFrom},
88
os::unix::fs::symlink,
9-
path::PathBuf,
9+
path::{Path, PathBuf},
1010
};
1111

1212
use clap::{arg, ArgMatches, Command};
13-
use moss::package::{self, MissingMetaFieldError};
13+
use color_eyre::{
14+
eyre::{eyre, Context},
15+
Result, Section,
16+
};
17+
use moss::package;
1418
use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
1519
use stone::{payload::layout, read::PayloadKind};
16-
use thiserror::{self, Error};
1720
use tui::{ProgressBar, ProgressStyle};
1821

1922
pub fn command() -> Command {
@@ -24,127 +27,139 @@ pub fn command() -> Command {
2427
}
2528

2629
/// Handle the `extract` command
27-
pub fn handle(args: &ArgMatches) -> Result<(), Error> {
30+
pub fn handle(args: &ArgMatches) -> Result<()> {
2831
let paths = args
2932
.get_many::<PathBuf>("PATH")
3033
.into_iter()
3134
.flatten()
3235
.cloned()
3336
.collect::<Vec<_>>();
3437

35-
// Begin unpack
36-
create_dir_all(".stoneStore")?;
38+
let content_store = Path::new(".stoneStore");
3739

38-
let content_store = PathBuf::from(".stoneStore");
40+
// Begin unpack
41+
create_dir_all(&content_store)
42+
.context("create temporary extract directory")
43+
.suggestion("is the current directory writable?")?;
3944

4045
for path in paths {
41-
println!("Extract: {:?}", path);
46+
extract(&path, &content_store).with_context(|| eyre!("extract {path:?}"))?;
47+
}
4248

43-
let rdr = File::open(path).map_err(Error::IO)?;
44-
let mut reader = stone::read(rdr).map_err(Error::Format)?;
49+
// Clean up.
50+
remove_dir_all(content_store).context("remove temporary extract directory")?;
4551

46-
let payloads = reader.payloads()?.collect::<Result<Vec<_>, _>>()?;
47-
let content = payloads.iter().find_map(PayloadKind::content);
48-
let layouts = payloads.iter().find_map(PayloadKind::layout);
49-
let meta = payloads.iter().find_map(PayloadKind::meta).ok_or(Error::MissingMeta)?;
52+
Ok(())
53+
}
5054

51-
let pkg = package::Meta::from_stone_payload(&meta.body).map_err(Error::MalformedMeta)?;
52-
let extraction_root = PathBuf::from(pkg.id().to_string());
55+
fn extract(path: &Path, content_store: &Path) -> Result<()> {
56+
println!("Extract: {:?}", path);
57+
58+
let rdr = File::open(path)
59+
.context("open file")
60+
.suggestion("does the file exist?")?;
61+
let mut reader = stone::read(rdr)
62+
.context("read stone file")
63+
.suggestion("is this a valid stone file?")?;
64+
65+
let payloads = reader
66+
.payloads()
67+
.context("seeking payloads")?
68+
.collect::<Result<Vec<_>, _>>()
69+
.context("decode payload")?;
70+
let content = payloads.iter().find_map(PayloadKind::content);
71+
let layouts = payloads.iter().find_map(PayloadKind::layout);
72+
let meta = payloads
73+
.iter()
74+
.find_map(PayloadKind::meta)
75+
.ok_or_else(|| eyre!("missing metadata payload"))?;
76+
77+
let pkg = package::Meta::from_stone_payload(&meta.body).context("metadata payload is malformed")?;
78+
let extraction_root = PathBuf::from(pkg.id().to_string());
79+
80+
// Cleanup old extraction root
81+
if extraction_root.exists() {
82+
remove_dir_all(&extraction_root).context("remove temporary stone extract directory")?;
83+
}
5384

54-
// Cleanup old extraction root
55-
if extraction_root.exists() {
56-
remove_dir_all(&extraction_root)?;
57-
}
85+
if let Some(content) = content {
86+
let content_file = File::options()
87+
.read(true)
88+
.write(true)
89+
.create(true)
90+
.open(".stoneContent")
91+
.context("open temporary content extract file")?;
92+
93+
let progress = ProgressBar::new(content.header.plain_size).with_style(
94+
ProgressStyle::with_template("|{bar:20.cyan/bue}| {percent}%")
95+
.unwrap()
96+
.progress_chars("■≡=- "),
97+
);
98+
reader
99+
.unpack_content(content, &mut progress.wrap_write(&content_file))
100+
.context("unpacking stone content payload")?;
101+
102+
// Extract all indices from the `.stoneContent` into hash-indexed unique files
103+
payloads
104+
.par_iter()
105+
.filter_map(PayloadKind::index)
106+
.flat_map(|p| &p.body)
107+
.map(|idx| {
108+
// Split file reader over index range
109+
let mut file = &content_file;
110+
file.seek(SeekFrom::Start(idx.start))
111+
.with_context(|| eyre!("seek to byte {}", idx.start))?;
112+
let mut split_file = (&mut file).take(idx.end - idx.start);
113+
114+
let mut output = File::create(format!(".stoneStore/{:02x}", idx.digest))
115+
.with_context(|| eyre!("create output file .stoneStore/{:02x}", idx.digest))?;
116+
117+
copy(&mut split_file, &mut output).with_context(|| eyre!("copy bytes {} to {}", idx.start, idx.end))?;
118+
119+
Ok(())
120+
})
121+
.collect::<Result<Vec<_>>>()
122+
.context("unpack file from content payload")?;
123+
124+
remove_file(".stoneContent").context("remove temporary content extract file")?;
125+
}
58126

59-
if let Some(content) = content {
60-
let content_file = File::options()
61-
.read(true)
62-
.write(true)
63-
.create(true)
64-
.open(".stoneContent")?;
65-
66-
let progress = ProgressBar::new(content.header.plain_size).with_style(
67-
ProgressStyle::with_template("|{bar:20.cyan/bue}| {percent}%")
68-
.unwrap()
69-
.progress_chars("■≡=- "),
70-
);
71-
reader.unpack_content(content, &mut progress.wrap_write(&content_file))?;
72-
73-
// Extract all indices from the `.stoneContent` into hash-indexed unique files
74-
payloads
75-
.par_iter()
76-
.filter_map(PayloadKind::index)
77-
.flat_map(|p| &p.body)
78-
.map(|idx| {
79-
// Split file reader over index range
80-
let mut file = &content_file;
81-
file.seek(SeekFrom::Start(idx.start))?;
82-
let mut split_file = (&mut file).take(idx.end - idx.start);
83-
84-
let mut output = File::create(format!(".stoneStore/{:02x}", idx.digest))?;
85-
86-
copy(&mut split_file, &mut output)?;
87-
88-
Ok(())
89-
})
90-
.collect::<Result<Vec<_>, Error>>()?;
91-
92-
remove_file(".stoneContent")?;
93-
}
127+
if let Some(layouts) = layouts {
128+
for layout in &layouts.body {
129+
match &layout.entry {
130+
layout::Entry::Regular(id, target) => {
131+
let store_path = content_store.join(format!("{:02x}", id));
132+
let target_disk = extraction_root.join("usr").join(target);
133+
134+
// drop it into a valid dir
135+
// TODO: Fix the permissions & mask
136+
let directory_target = target_disk.parent().unwrap();
137+
create_dir_all(directory_target).context("create extract directory")?;
138+
139+
// link from CA store
140+
hard_link(&store_path, &target_disk)
141+
.with_context(|| eyre!("hardlink from {store_path:?} to {target_disk:?}"))?;
142+
}
143+
layout::Entry::Symlink(source, target) => {
144+
let target_disk = extraction_root.join("usr").join(target);
145+
let directory_target = target_disk.parent().unwrap();
146+
147+
// ensure dumping ground exists
148+
create_dir_all(directory_target).context("create extract directory")?;
94149

95-
if let Some(layouts) = layouts {
96-
for layout in &layouts.body {
97-
match &layout.entry {
98-
layout::Entry::Regular(id, target) => {
99-
let store_path = content_store.join(format!("{:02x}", id));
100-
let target_disk = extraction_root.join("usr").join(target);
101-
102-
// drop it into a valid dir
103-
// TODO: Fix the permissions & mask
104-
let directory_target = target_disk.parent().unwrap();
105-
create_dir_all(directory_target)?;
106-
107-
// link from CA store
108-
hard_link(store_path, target_disk)?;
109-
}
110-
layout::Entry::Symlink(source, target) => {
111-
let target_disk = extraction_root.join("usr").join(target);
112-
let directory_target = target_disk.parent().unwrap();
113-
114-
// ensure dumping ground exists
115-
create_dir_all(directory_target)?;
116-
117-
// join the link path to the directory target for relative joinery
118-
symlink(source, target_disk)?;
119-
}
120-
layout::Entry::Directory(target) => {
121-
let target_disk = extraction_root.join("usr").join(target);
122-
// TODO: Fix perms!
123-
create_dir_all(target_disk)?;
124-
}
125-
_ => unreachable!(),
150+
// join the link path to the directory target for relative joinery
151+
symlink(&source, &target_disk)
152+
.with_context(|| eyre!("hardlink from {source:?} to {target_disk:?}"))?;
153+
}
154+
layout::Entry::Directory(target) => {
155+
let target_disk = extraction_root.join("usr").join(target);
156+
// TODO: Fix perms!
157+
create_dir_all(target_disk).context("create extract directory")?;
126158
}
159+
_ => unreachable!(),
127160
}
128161
}
129162
}
130163

131-
// Clean up.
132-
remove_dir_all(content_store)?;
133-
134164
Ok(())
135165
}
136-
137-
#[derive(Debug, Error)]
138-
pub enum Error {
139-
#[error("Missing metadata")]
140-
MissingMeta,
141-
142-
#[error("malformed meta")]
143-
MalformedMeta(#[from] MissingMetaFieldError),
144-
145-
#[error("io")]
146-
IO(#[from] std::io::Error),
147-
148-
#[error("stone format")]
149-
Format(#[from] stone::read::Error),
150-
}

0 commit comments

Comments
 (0)