1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::path::Path;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub enum ResourceType {
10 Model,
12 Source,
14 Data,
16 Config,
18 Documentation,
20 License,
22 Binary,
24 Text,
26 Metadata,
28}
29
30impl ResourceType {
31 pub fn extension(&self) -> &'static str {
33 match self {
34 ResourceType::Model => "model",
35 ResourceType::Source => "rs",
36 ResourceType::Data => "data",
37 ResourceType::Config => "toml",
38 ResourceType::Documentation => "md",
39 ResourceType::License => "txt",
40 ResourceType::Binary => "bin",
41 ResourceType::Text => "txt",
42 ResourceType::Metadata => "json",
43 }
44 }
45
46 pub fn mime_type(&self) -> &'static str {
48 match self {
49 ResourceType::Model => "application/octet-stream",
50 ResourceType::Source => "text/x-rust",
51 ResourceType::Data => "application/octet-stream",
52 ResourceType::Config => "text/x-toml",
53 ResourceType::Documentation => "text/markdown",
54 ResourceType::License => "text/plain",
55 ResourceType::Binary => "application/octet-stream",
56 ResourceType::Text => "text/plain",
57 ResourceType::Metadata => "application/json",
58 }
59 }
60
61 pub fn from_extension(ext: &str) -> Self {
63 match ext.to_lowercase().as_str() {
64 "model" | "pth" | "pt" | "torsh" => ResourceType::Model,
65 "rs" => ResourceType::Source,
66 "json" => ResourceType::Metadata,
67 "toml" | "yaml" | "yml" => ResourceType::Config,
68 "md" | "rst" => ResourceType::Documentation,
69 "txt" | "license" => ResourceType::License,
70 "bin" | "dat" => ResourceType::Binary,
71 _ => ResourceType::Data,
72 }
73 }
74
75 pub fn as_tag(&self) -> &'static str {
87 match self {
88 ResourceType::Model => "model",
89 ResourceType::Source => "source",
90 ResourceType::Data => "data",
91 ResourceType::Config => "config",
92 ResourceType::Documentation => "documentation",
93 ResourceType::License => "license",
94 ResourceType::Binary => "binary",
95 ResourceType::Text => "text",
96 ResourceType::Metadata => "metadata",
97 }
98 }
99
100 pub fn from_tag(tag: &str) -> Option<Self> {
107 match tag {
108 "model" => Some(ResourceType::Model),
109 "source" => Some(ResourceType::Source),
110 "data" => Some(ResourceType::Data),
111 "config" => Some(ResourceType::Config),
112 "documentation" => Some(ResourceType::Documentation),
113 "license" => Some(ResourceType::License),
114 "binary" => Some(ResourceType::Binary),
115 "text" => Some(ResourceType::Text),
116 "metadata" => Some(ResourceType::Metadata),
117 _ => None,
118 }
119 }
120}
121
122pub(crate) const RESOURCE_TYPE_METADATA_KEY: &str = "__torsh_resource_type";
131
132#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
134pub struct Resource {
135 pub name: String,
137
138 pub resource_type: ResourceType,
140
141 pub data: Vec<u8>,
143
144 pub metadata: HashMap<String, String>,
146}
147
148impl Resource {
149 pub fn new(name: String, resource_type: ResourceType, data: Vec<u8>) -> Self {
151 Self {
152 name,
153 resource_type,
154 data,
155 metadata: HashMap::new(),
156 }
157 }
158
159 pub fn from_file<P: AsRef<Path>>(
161 path: P,
162 resource_type: Option<ResourceType>,
163 ) -> std::io::Result<Self> {
164 let path = path.as_ref();
165 let name = path
166 .file_name()
167 .and_then(|n| n.to_str())
168 .unwrap_or("unnamed")
169 .to_string();
170
171 let resource_type = resource_type.unwrap_or_else(|| {
172 path.extension()
173 .and_then(|e| e.to_str())
174 .map(ResourceType::from_extension)
175 .unwrap_or(ResourceType::Data)
176 });
177
178 let data = std::fs::read(path)?;
179
180 Ok(Self::new(name, resource_type, data))
181 }
182
183 pub fn size(&self) -> usize {
185 self.data.len()
186 }
187
188 pub fn sha256(&self) -> String {
190 use sha2::{Digest, Sha256};
191 let mut hasher = Sha256::new();
192 hasher.update(&self.data);
193 hex::encode(hasher.finalize())
194 }
195
196 pub fn add_metadata(&mut self, key: String, value: String) {
198 self.metadata.insert(key, value);
199 }
200
201 pub fn get_metadata(&self, key: &str) -> Option<&str> {
203 self.metadata.get(key).map(|s| s.as_str())
204 }
205
206 pub fn is_compressed(&self) -> bool {
208 self.metadata.contains_key("compression")
209 }
210
211 pub fn compression_method(&self) -> Option<&str> {
213 self.get_metadata("compression")
214 }
215}
216
217pub struct ResourceCollection {
219 resources: HashMap<String, Resource>,
220}
221
222impl ResourceCollection {
223 pub fn new() -> Self {
225 Self {
226 resources: HashMap::new(),
227 }
228 }
229
230 pub fn add(&mut self, resource: Resource) -> Result<(), String> {
232 if self.resources.contains_key(&resource.name) {
233 return Err(format!("Resource '{}' already exists", resource.name));
234 }
235
236 self.resources.insert(resource.name.clone(), resource);
237 Ok(())
238 }
239
240 pub fn get(&self, name: &str) -> Option<&Resource> {
242 self.resources.get(name)
243 }
244
245 pub fn get_mut(&mut self, name: &str) -> Option<&mut Resource> {
247 self.resources.get_mut(name)
248 }
249
250 pub fn remove(&mut self, name: &str) -> Option<Resource> {
252 self.resources.remove(name)
253 }
254
255 pub fn list(&self) -> Vec<&str> {
257 self.resources.keys().map(|s| s.as_str()).collect()
258 }
259
260 pub fn by_type(&self, resource_type: ResourceType) -> Vec<&Resource> {
262 self.resources
263 .values()
264 .filter(|r| r.resource_type == resource_type)
265 .collect()
266 }
267
268 pub fn total_size(&self) -> usize {
270 self.resources.values().map(|r| r.size()).sum()
271 }
272
273 pub fn clear(&mut self) {
275 self.resources.clear();
276 }
277
278 pub fn len(&self) -> usize {
280 self.resources.len()
281 }
282
283 pub fn is_empty(&self) -> bool {
285 self.resources.is_empty()
286 }
287}
288
289impl Default for ResourceCollection {
290 fn default() -> Self {
291 Self::new()
292 }
293}
294
295pub struct ResourceFilter {
297 types: Option<Vec<ResourceType>>,
298 name_pattern: Option<String>,
299 min_size: Option<usize>,
300 max_size: Option<usize>,
301}
302
303impl ResourceFilter {
304 pub fn new() -> Self {
306 Self {
307 types: None,
308 name_pattern: None,
309 min_size: None,
310 max_size: None,
311 }
312 }
313
314 pub fn with_types(mut self, types: Vec<ResourceType>) -> Self {
316 self.types = Some(types);
317 self
318 }
319
320 pub fn with_name_pattern(mut self, pattern: String) -> Self {
322 self.name_pattern = Some(pattern);
323 self
324 }
325
326 pub fn with_size_range(mut self, min: Option<usize>, max: Option<usize>) -> Self {
328 self.min_size = min;
329 self.max_size = max;
330 self
331 }
332
333 pub fn matches(&self, resource: &Resource) -> bool {
335 if let Some(types) = &self.types {
337 if !types.contains(&resource.resource_type) {
338 return false;
339 }
340 }
341
342 if let Some(pattern) = &self.name_pattern {
344 if !resource.name.contains(pattern) {
345 return false;
346 }
347 }
348
349 let size = resource.size();
351 if let Some(min) = self.min_size {
352 if size < min {
353 return false;
354 }
355 }
356 if let Some(max) = self.max_size {
357 if size > max {
358 return false;
359 }
360 }
361
362 true
363 }
364}
365
366impl Default for ResourceFilter {
367 fn default() -> Self {
368 Self::new()
369 }
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375
376 #[test]
377 fn test_resource_type() {
378 assert_eq!(ResourceType::from_extension("rs"), ResourceType::Source);
379 assert_eq!(ResourceType::from_extension("model"), ResourceType::Model);
380 assert_eq!(ResourceType::from_extension("json"), ResourceType::Metadata);
381 assert_eq!(ResourceType::from_extension("unknown"), ResourceType::Data);
382 }
383
384 #[test]
385 fn test_resource_creation() {
386 let resource = Resource::new(
387 "test.model".to_string(),
388 ResourceType::Model,
389 vec![1, 2, 3, 4],
390 );
391
392 assert_eq!(resource.name, "test.model");
393 assert_eq!(resource.resource_type, ResourceType::Model);
394 assert_eq!(resource.size(), 4);
395 }
396
397 #[test]
398 fn test_resource_collection() {
399 let mut collection = ResourceCollection::new();
400
401 let resource1 = Resource::new("res1".to_string(), ResourceType::Model, vec![1, 2, 3]);
402 let resource2 = Resource::new("res2".to_string(), ResourceType::Data, vec![4, 5, 6, 7]);
403
404 collection.add(resource1).unwrap();
405 collection.add(resource2).unwrap();
406
407 assert_eq!(collection.len(), 2);
408 assert_eq!(collection.total_size(), 7);
409
410 let models = collection.by_type(ResourceType::Model);
411 assert_eq!(models.len(), 1);
412 }
413
414 #[test]
415 fn test_resource_filter() {
416 let resource = Resource::new("test.model".to_string(), ResourceType::Model, vec![0; 100]);
417
418 let filter = ResourceFilter::new()
419 .with_types(vec![ResourceType::Model, ResourceType::Data])
420 .with_size_range(Some(50), Some(200));
421
422 assert!(filter.matches(&resource));
423
424 let filter2 = ResourceFilter::new()
425 .with_types(vec![ResourceType::Source])
426 .with_size_range(Some(50), Some(200));
427
428 assert!(!filter2.matches(&resource));
429 }
430}