Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
use std::{
fs::File,
io::{BufWriter, Cursor, Read, Seek, SeekFrom, Write},
path::Path,
};
use filetime::FileTime;
use crate::{
codec::ArCodec,
datamodel::{AllZeroExt, ArEntry, BlockPool, CompressionType, EntryType},
read_write_extension::ReadExtTypes,
};
pub struct UnArchiver<T: Read + Seek> {
codec: ArCodec,
blocks_in: BlockPool<T>,
metadata_compare: bool,
}
impl<T: Read + Seek> UnArchiver<T> {
pub fn new(mut codec: ArCodec, blocks_in: BlockPool<T>) -> Self {
// IndexTable is not always loaded for unarchiving, so use the BlockPool UID if it is not
// set
if codec.index_table.uid.is_all_zero() {
codec.index_table.uid = blocks_in.uid;
}
Self {
codec,
blocks_in,
metadata_compare: false,
}
}
pub fn set_metadata_compare(&mut self, metadata_compare: bool) {
self.metadata_compare = metadata_compare;
}
/// Check that all loaded files have the same UID
///
/// Returns true if the UIDs are the same, false otherwise
pub fn check_uids(&self) -> bool {
self.codec.file_table.uid == self.codec.index_table.uid
&& self.codec.file_table.uid == self.blocks_in.uid
}
pub fn codec(&self) -> &ArCodec {
&self.codec
}
pub fn entries_iter(&self) -> impl Iterator<Item = &ArEntry> {
self.codec.file_table.entries.iter()
}
pub fn unarchive_to(&mut self, out_dir: impl AsRef<Path>) -> Result<(), std::io::Error> {
// Swap the entries out of `self` for the loop to prevent borrowing issues
let mut file_table_entries = Vec::new();
std::mem::swap(&mut file_table_entries, &mut self.codec.file_table.entries);
for entry in file_table_entries.iter() {
// Removing the ':' fixes absolute paths in the archive on windows
let entry_path = &entry.path.replace(':', "");
let out_path = out_dir.as_ref().join(entry_path);
if let Err(e) = self.unarchive_entry_to_file_path(entry, out_path) {
println!("Error unarchiving entry: {} ({e})", entry.path);
}
}
std::mem::swap(&mut file_table_entries, &mut self.codec.file_table.entries);
Ok(())
}
/// Note: This will automatically create the parent directories and overwrite the file if it
/// exists already
pub fn unarchive_entry_to_file_path(
&mut self,
entry: &ArEntry,
out_path: impl AsRef<Path>,
) -> Result<(), std::io::Error> {
// If metadata compare is enabled, skip files that already exist and have the same mtime
// and size
if self.metadata_compare && out_path.as_ref().exists() {
let orig_md = out_path.as_ref().metadata()?;
let orig_mtime = filetime::FileTime::from_last_modification_time(&orig_md);
let entry_mtime = filetime::FileTime::from_unix_time(
entry.metadata.modified_unix_seconds,
entry.metadata.modified_nanos,
);
if orig_md.len() == entry.metadata.file_size && orig_mtime == entry_mtime {
return Ok(());
}
}
if let Some(parent) = out_path.as_ref().parent() {
std::fs::create_dir_all(parent)?;
}
if let Some(symlink_target) = entry.symlink_target.as_ref() {
#[cfg(target_os = "windows")]
{
if out_path.as_ref().exists() {
std::fs::remove_file(out_path.as_ref())?;
}
let res = match entry.metadata.entry_and_os_type.entry_type() {
EntryType::SymbolicLinkFile => {
std::os::windows::fs::symlink_file(symlink_target, out_path.as_ref())
}
EntryType::SymbolicLinkDir => {
std::os::windows::fs::symlink_dir(symlink_target, out_path.as_ref())
}
_ => panic!("Trying to create symlink for non symlink entry"),
};
if let Err(e) = res {
// Windows error code for missing symlink privileges
match e.raw_os_error().unwrap_or(0) {
1314 => {
println!(
"Skipping symlink creation due to missing privileges: {}",
out_path.as_ref().display()
);
return Ok(());
}
_ => return Err(e),
}
}
}
#[cfg(target_os = "linux")]
{
std::os::unix::fs::symlink(symlink_target, out_path.as_ref())?;
}
return Ok(());
}
if entry.metadata.entry_and_os_type.entry_type() == EntryType::Directory {
if !out_path.as_ref().exists() {
std::fs::create_dir(&out_path)?;
}
return Ok(());
}
let out_file = File::create(out_path.as_ref())?;
let out_file = BufWriter::new(out_file);
self.unarchive_entry_to_stream(entry, out_file)?;
let last_modified = FileTime::from_unix_time(
entry.metadata.modified_unix_seconds,
entry.metadata.modified_nanos,
);
filetime::set_file_mtime(out_path.as_ref(), last_modified)?;
Ok(())
}
pub fn unarchive_entry_to_stream(
&mut self,
entry: &ArEntry,
mut stream_out: impl Write,
) -> Result<(), std::io::Error> {
for start in entry.blocks.iter().copied() {
self.blocks_in.seek(SeekFrom::Start(start))?;
let block_len = self.blocks_in.read_exact_u32()?;
self.codec.buffer.resize(block_len as usize, 0);
let compression_type = self.blocks_in.read_exact_u8()?;
let compression_type: CompressionType = compression_type.try_into()?;
self.blocks_in.read_exact(&mut self.codec.buffer)?;
match compression_type {
CompressionType::Zstd => {
let mut decoder = zstd::Decoder::new(Cursor::new(&self.codec.buffer))?;
std::io::copy(&mut decoder, &mut stream_out)?;
}
CompressionType::Uncompressed => {
let mut cursor = Cursor::new(&self.codec.buffer);
std::io::copy(&mut cursor, &mut stream_out)?;
}
}
}
Ok(())
}
}