TextureSync/server/texture-sync-server/src/persistency/mod.rs

233 lines
6.3 KiB
Rust

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<Texture>,
id_index: HashMap<String, Texture>,
name_index: HashMap<String, Texture>,
preview_cache: HashMap<(TextureFormat, Sha256), Vec<u8>>,
}
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<DataStore> {
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<Texture> {
self.id_index.get(id).cloned()
}
pub fn texture_by_name(&self, id: &str) -> Option<Texture> {
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<Vec<u8>> {
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<Vec<u8>> {
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<Item = &Texture> {
self.textures.iter()
}
pub fn garbage_collect(&mut self) -> io::Result<()> {
fn extract_hash(filename: &std::ffi::OsStr) -> Option<Sha256> {
// 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(())
}
}