use crate::model::*; use std::collections::*; use std::io; use std::path::{Path, PathBuf}; mod image_convert; mod metadata_file; pub struct DataStore { //data_path: PathBuf, base_dir: PathBuf, textures: HashSet, id_index: HashMap, name_index: HashMap, preview_cache: HashMap<(TextureFormat, Sha256), Vec>, } impl DataStore { fn texture_base_path(&self) -> PathBuf { let mut path = self.base_dir.clone(); path.push("textures"); path } fn texture_file_path(&self, sha: &Sha256) -> PathBuf { let mut path = self.texture_base_path(); path.push(sha.as_hex_string()); path } fn index_file_path(&self) -> PathBuf { let mut path = self.base_dir.clone(); path.push("collection.json"); path } pub fn new(base_dir: &Path) -> io::Result { let mut store = DataStore { base_dir: base_dir.into(), textures: Default::default(), id_index: Default::default(), name_index: Default::default(), preview_cache: Default::default(), }; let metadata_file = metadata_file::MetadataFile::load(&store.index_file_path())?; for texture in metadata_file.textures.iter() { match store.insert(texture.clone()) { true => (), false => { panic!("inserting {:#?} failed !!!", texture); // TODO: What should be done? } } } store.garbage_collect()?; store.flush_metadata()?; Ok(store) } pub fn texture_by_id(&self, id: &str) -> Option { self.id_index.get(id).cloned() } pub fn texture_by_name(&self, id: &str) -> Option { self.name_index.get(id).cloned() } pub fn insert(&mut self, texture: Texture) -> bool { if self.id_index.contains_key(&texture.id) { return false; } if self.name_index.contains_key(&texture.name) { return false; } if !self.is_texture_file_on_disk(&texture.texture_hash) { return false; } self.id_index.insert(texture.id.clone(), texture.clone()); self.name_index .insert(texture.name.clone(), texture.clone()); self.textures.insert(texture.clone()); true } /// returns true if successful pub fn delete(&mut self, tex: &Texture) -> bool { if self.textures.remove(tex) { // remove assert!(self.id_index.remove(&tex.id).is_some()); assert!(self.name_index.remove(&tex.name).is_some()); // don't delete cache, since it could be used // by other texture. true } else { false } } /// Check if the texture, given by hash, physically exists on the file system. pub fn is_texture_file_on_disk(&self, hash: &Sha256) -> bool { let file_path = self.texture_file_path(&hash); file_path.is_file() } pub fn read_texture_file_by_hash(&self, hash: &Sha256) -> io::Result> { use std::fs::*; use std::io::*; let file_path = self.texture_file_path(&hash); let mut file = File::open(file_path)?; let mut buffer = Vec::new(); file.read_to_end(&mut buffer)?; Ok(buffer) } pub fn store_texture_file(&mut self, data: &[u8]) -> io::Result<()> { use std::fs; use std::io::Write; let hash = crate::model::Sha256::from_data(data); fs::create_dir_all(&self.texture_base_path())?; let file_path = self.texture_file_path(&hash); let mut file = fs::File::create(&file_path)?; file.write_all(data) } pub fn flush_metadata(&self) -> io::Result<()> { let f = metadata_file::MetadataFile::from_iterator(self.textures.iter()); f.store(self.index_file_path().as_path()) } pub fn get_texture_preview( &mut self, hash: &Sha256, desired_format: TextureFormat, ) -> io::Result> { let key = (desired_format.clone(), hash.clone()); if let Some(preview) = self.preview_cache.get(&key) { return Ok(preview.clone()); } let original = self.read_texture_file_by_hash(&hash)?; let preview = image_convert::generate_preview(&original[..], desired_format).map_err(|_e| { io::Error::new(io::ErrorKind::InvalidData, "Invalid Texture Image on Disk.") })?; self.preview_cache.insert(key, preview.clone()); Ok(preview) } pub fn borrow_textures(&self) -> impl Iterator { self.textures.iter() } pub fn garbage_collect(&mut self) -> io::Result<()> { fn extract_hash(filename: &std::ffi::OsStr) -> Option { // directly return None for invalidly encoded file names let str_name = filename.to_str()?; let hash = Sha256::from_hex(str_name)?; // check back to ignore names with lowercase letters if hash.as_hex_string() == str_name { Some(hash) } else { None } } let texture_dir = std::fs::read_dir(self.texture_base_path())?; let mut hashs_on_disk = HashSet::new(); for result_direntry in texture_dir { let result_direntry = result_direntry?; // Skip if not a file. if !result_direntry.file_type()?.is_file() { continue; } let texture_path = result_direntry.path(); let filename = match texture_path.file_name() { Some(name) => name, None => continue, }; match extract_hash(filename) { Some(hash) => { hashs_on_disk.insert(hash); } None => (), // ignore other files }; } let mut unused_files = hashs_on_disk; for texture in &self.textures { unused_files.remove(&texture.texture_hash); } // remove what is still contained in the HashSet for entry in unused_files { let path = self.texture_file_path(&entry); std::fs::remove_file(path)?; } Ok(()) } }