casc_storage/storage/
loose_files.rs1use crate::error::{CascError, Result};
4use crate::types::EKey;
5use std::collections::HashMap;
6use std::io::{Read, Seek};
7use std::path::PathBuf;
8use tracing::{debug, info};
9
10pub struct LooseFileStorage {
12 files: HashMap<EKey, PathBuf>,
14 base_path: PathBuf,
16}
17
18impl LooseFileStorage {
19 fn parse_hex_filename(filename: &str) -> Option<EKey> {
21 if filename.len() != 32 {
22 return None;
23 }
24
25 let mut bytes = [0u8; 16];
26 for i in 0..16 {
27 let hex_pair = &filename[i * 2..i * 2 + 2];
28 bytes[i] = u8::from_str_radix(hex_pair, 16).ok()?;
29 }
30
31 EKey::from_slice(&bytes)
32 }
33
34 pub fn new(base_path: PathBuf) -> Result<Self> {
36 std::fs::create_dir_all(&base_path)?;
37 Ok(Self {
38 files: HashMap::new(),
39 base_path,
40 })
41 }
42
43 pub fn scan(&mut self) -> Result<()> {
45 info!("Scanning for loose files in {:?}", self.base_path);
46
47 self.files.clear();
48
49 for entry in std::fs::read_dir(&self.base_path)? {
51 let entry = entry?;
52 let path = entry.path();
53
54 if path.is_file() {
55 if let Some(filename) = path.file_stem().and_then(|s| s.to_str()) {
56 if filename.len() == 32 {
58 if let Some(ekey) = Self::parse_hex_filename(filename) {
59 debug!("Found loose file: {}", ekey);
60 self.files.insert(ekey, path);
61 }
62 }
63 }
64 }
65 }
66
67 info!("Found {} loose files", self.files.len());
68 Ok(())
69 }
70
71 pub fn read(&self, ekey: &EKey) -> Result<Vec<u8>> {
73 let path = self
74 .files
75 .get(ekey)
76 .ok_or_else(|| CascError::EntryNotFound(ekey.to_string()))?;
77
78 debug!("Reading loose file {} from {:?}", ekey, path);
79
80 let mut file = std::fs::File::open(path)?;
82
83 let mut magic = [0u8; 4];
85 file.read_exact(&mut magic)?;
86
87 if magic == blte::BLTE_MAGIC {
88 debug!(
89 "Loose file {} is BLTE compressed, using streaming decompression",
90 ekey
91 );
92 file.seek(std::io::SeekFrom::Start(0))?;
94
95 let mut stream = blte::create_streaming_reader(file, None)
97 .map_err(|e| CascError::DecompressionError(e.to_string()))?;
98
99 let mut result = Vec::new();
100 stream
101 .read_to_end(&mut result)
102 .map_err(|e| CascError::DecompressionError(e.to_string()))?;
103 Ok(result)
104 } else {
105 debug!("Loose file {} is uncompressed", ekey);
106 file.seek(std::io::SeekFrom::Start(0))?;
108 let mut data = Vec::new();
109 file.read_to_end(&mut data)?;
110 Ok(data)
111 }
112 }
113
114 pub fn write(&mut self, ekey: &EKey, data: &[u8], compress: bool) -> Result<()> {
116 let filename = format!("{ekey}");
117 let path = self.base_path.join(&filename);
118
119 debug!("Writing loose file {} to {:?}", ekey, path);
120
121 let output = if compress {
123 blte::compress_data_single(data.to_vec(), blte::CompressionMode::ZLib, None)?
124 } else {
125 data.to_vec()
126 };
127
128 std::fs::write(&path, output)?;
129 self.files.insert(*ekey, path);
130
131 Ok(())
132 }
133
134 pub fn remove(&mut self, ekey: &EKey) -> Result<()> {
136 if let Some(path) = self.files.remove(ekey) {
137 debug!("Removing loose file {} at {:?}", ekey, path);
138 std::fs::remove_file(path)?;
139 }
140 Ok(())
141 }
142
143 pub fn contains(&self, ekey: &EKey) -> bool {
145 self.files.contains_key(ekey)
146 }
147
148 pub fn len(&self) -> usize {
150 self.files.len()
151 }
152
153 pub fn is_empty(&self) -> bool {
155 self.files.is_empty()
156 }
157
158 pub fn iter(&self) -> impl Iterator<Item = (&EKey, &PathBuf)> {
160 self.files.iter()
161 }
162
163 pub fn total_size(&self) -> Result<u64> {
165 let mut total = 0u64;
166 for path in self.files.values() {
167 total += std::fs::metadata(path)?.len();
168 }
169 Ok(total)
170 }
171}