use regex::Regex;
use std::{collections::HashSet, fs, io::Error, path::PathBuf};
pub struct Storage {
root: PathBuf,
pub max_filecount: Option<usize>,
pub max_filesize: Option<u64>,
pub regex: Option<Regex>,
}
impl Storage {
pub fn init(
root: PathBuf,
regex: Option<Regex>,
max_filecount: Option<usize>,
max_filesize: Option<u64>,
) -> Result<Self, String> {
if !root.is_dir() {
return Err("Storage root is not directory".into());
}
Ok(Self {
max_filecount,
max_filesize,
regex,
root: root.canonicalize().map_err(|e| e.to_string())?,
})
}
pub fn commit(
&self,
info_hash: &str,
torrent_bytes: Vec<u8>,
persist_files: Option<HashSet<PathBuf>>,
) -> Result<(), Error> {
let permanent_dir = self.permanent_dir(info_hash, true)?;
let tmp_dir = self.tmp_dir(info_hash, false)?;
if let Some(files) = persist_files {
let components_count = permanent_dir.components().count(); for file in files {
let tmp_file = {
let mut p = PathBuf::from(&tmp_dir);
p.push(file);
p.canonicalize()?
};
assert!(tmp_file.starts_with(&self.root) && !tmp_file.is_dir());
let mut permanent_file = PathBuf::from(&permanent_dir);
for component in tmp_file.components().skip(components_count) {
permanent_file.push(component)
}
assert!(tmp_file.components().count() == permanent_file.components().count());
fs::create_dir_all(permanent_file.parent().unwrap())?;
fs::rename(&tmp_file, &permanent_file)?;
log::debug!(
"persist tmp file `{}` to `{}`",
tmp_file.to_string_lossy(),
permanent_file.to_string_lossy()
);
}
}
if tmp_dir.exists() {
fs::remove_dir_all(&tmp_dir)?;
log::debug!("clean tmp data `{}`", tmp_dir.to_string_lossy())
}
let torrent_file = self.torrent(info_hash);
fs::write(&torrent_file, torrent_bytes)?;
log::debug!(
"persist torrent bytes for `{}`",
torrent_file.to_string_lossy()
);
Ok(())
}
pub fn tmp_dir(&self, info_hash: &str, is_create: bool) -> Result<PathBuf, Error> {
let mut p = PathBuf::from(&self.root);
p.push(tmp_component(info_hash));
assert!(!p.is_file());
if is_create && !p.exists() {
fs::create_dir(&p)?;
log::debug!("create tmp directory `{}`", p.to_string_lossy())
}
Ok(p)
}
fn permanent_dir(&self, info_hash: &str, is_clear: bool) -> Result<PathBuf, Error> {
let mut p = PathBuf::from(&self.root);
p.push(info_hash);
assert!(!p.is_file());
if is_clear && p.exists() {
fs::remove_dir_all(&p)?;
log::debug!("clean previous data `{}`", p.to_string_lossy())
}
Ok(p)
}
pub fn root(&self) -> &PathBuf {
&self.root
}
pub fn contains_torrent(&self, info_hash: &str) -> Result<bool, Error> {
Ok(fs::exists(self.torrent(info_hash))?)
}
fn torrent(&self, info_hash: &str) -> PathBuf {
let mut p = PathBuf::from(&self.root);
p.push(format!("{info_hash}.torrent"));
assert!(!p.is_dir());
p
}
}
fn tmp_component(info_hash: &str) -> String {
format!(".{info_hash}")
}