@@ -6,14 +6,17 @@ use std::{
6
6
fs:: { create_dir_all, hard_link, remove_dir_all, remove_file, File } ,
7
7
io:: { copy, Read , Seek , SeekFrom } ,
8
8
os:: unix:: fs:: symlink,
9
- path:: PathBuf ,
9
+ path:: { Path , PathBuf } ,
10
10
} ;
11
11
12
12
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;
14
18
use rayon:: prelude:: { IntoParallelRefIterator , ParallelIterator } ;
15
19
use stone:: { payload:: layout, read:: PayloadKind } ;
16
- use thiserror:: { self , Error } ;
17
20
use tui:: { ProgressBar , ProgressStyle } ;
18
21
19
22
pub fn command ( ) -> Command {
@@ -24,127 +27,139 @@ pub fn command() -> Command {
24
27
}
25
28
26
29
/// Handle the `extract` command
27
- pub fn handle ( args : & ArgMatches ) -> Result < ( ) , Error > {
30
+ pub fn handle ( args : & ArgMatches ) -> Result < ( ) > {
28
31
let paths = args
29
32
. get_many :: < PathBuf > ( "PATH" )
30
33
. into_iter ( )
31
34
. flatten ( )
32
35
. cloned ( )
33
36
. collect :: < Vec < _ > > ( ) ;
34
37
35
- // Begin unpack
36
- create_dir_all ( ".stoneStore" ) ?;
38
+ let content_store = Path :: new ( ".stoneStore" ) ;
37
39
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?" ) ?;
39
44
40
45
for path in paths {
41
- println ! ( "Extract: {:?}" , path) ;
46
+ extract ( & path, & content_store) . with_context ( || eyre ! ( "extract {path:?}" ) ) ?;
47
+ }
42
48
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" ) ?;
45
51
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
+ }
50
54
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
+ }
53
84
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
+ }
58
126
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" ) ?;
94
149
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" ) ?;
126
158
}
159
+ _ => unreachable ! ( ) ,
127
160
}
128
161
}
129
162
}
130
163
131
- // Clean up.
132
- remove_dir_all ( content_store) ?;
133
-
134
164
Ok ( ( ) )
135
165
}
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