mirror of
https://github.com/YGGverse/titanit.git
synced 2026-04-01 01:25:31 +00:00
130 lines
4 KiB
Rust
130 lines
4 KiB
Rust
use anyhow::{anyhow, bail, Error, Result};
|
|
use std::{
|
|
fs::{create_dir_all, remove_file, rename, File},
|
|
path::{PathBuf, MAIN_SEPARATOR},
|
|
str::FromStr,
|
|
thread,
|
|
time::{Duration, SystemTime, UNIX_EPOCH},
|
|
};
|
|
|
|
const TMP_SUFFIX: &str = ".tmp";
|
|
|
|
pub struct Item {
|
|
pub file: File,
|
|
pub path: PathBuf,
|
|
// pub id: u64,
|
|
}
|
|
|
|
impl Item {
|
|
// Constructors
|
|
|
|
/// Build new `Self` from URL (request)
|
|
/// * e.g. 123 -> /directory/1/2/3
|
|
pub fn from_url(url: &str, directory: &str) -> Result<Self> {
|
|
let s = match url.rfind('/') {
|
|
Some(pos) => match url.get(pos + 1..) {
|
|
Some(u) => u.parse::<u64>()?,
|
|
None => bail!("Invalid request"),
|
|
},
|
|
None => bail!("ID not found"),
|
|
}
|
|
.to_string();
|
|
let mut p = String::with_capacity(s.len());
|
|
for (i, d) in s.chars().enumerate() {
|
|
if i > 0 {
|
|
p.push(MAIN_SEPARATOR)
|
|
}
|
|
p.push(d)
|
|
}
|
|
let path = PathBuf::from_str(&format!(
|
|
"{}{MAIN_SEPARATOR}{p}",
|
|
directory.trim_end_matches(MAIN_SEPARATOR),
|
|
))?;
|
|
Ok(Self {
|
|
file: File::open(&path)?,
|
|
path,
|
|
})
|
|
}
|
|
|
|
/// Create new `Self` with unique pathname in the root `directory`
|
|
pub fn create(directory: &str) -> Result<Self> {
|
|
loop {
|
|
// generate file id from current unix time
|
|
let id = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
|
|
|
|
// build optimized fs path:
|
|
// add directory separator after every digit to keep up to 10 files per directory
|
|
let path = PathBuf::from(format!(
|
|
"{}{MAIN_SEPARATOR}{}{TMP_SUFFIX}",
|
|
directory.trim_end_matches(MAIN_SEPARATOR),
|
|
id.to_string().chars().fold(String::new(), |mut acc, c| {
|
|
if !acc.is_empty() {
|
|
acc.push(MAIN_SEPARATOR);
|
|
}
|
|
acc.push(c);
|
|
acc
|
|
})
|
|
));
|
|
|
|
// recursively create directories
|
|
// * parent directory is expected
|
|
create_dir_all(path.parent().unwrap())?;
|
|
|
|
// build `Self`
|
|
match File::create_new(&path) {
|
|
// make sure slot is not taken (by another thread)
|
|
Ok(file) => {
|
|
return Ok(Self {
|
|
file,
|
|
path,
|
|
// id
|
|
});
|
|
}
|
|
Err(_) => {
|
|
println!("[warning] Could not init location: {:?}", path.to_str());
|
|
// find free slot after some delay..
|
|
thread::sleep(Duration::from_secs(1));
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Actions
|
|
|
|
/// Commit changes, return permanent Self
|
|
pub fn commit(mut self) -> Result<Self, (Self, Error)> {
|
|
match self.path.to_str() {
|
|
Some(old) => match old.strip_suffix(TMP_SUFFIX) {
|
|
Some(new) => match rename(old, new) {
|
|
Ok(()) => {
|
|
self.path = match PathBuf::from_str(new) {
|
|
Ok(path) => path,
|
|
Err(e) => return Err((self, anyhow!(e))),
|
|
};
|
|
Ok(self)
|
|
}
|
|
Err(e) => Err((self, anyhow!(e))),
|
|
},
|
|
None => Err((self, anyhow!("Unexpected suffix"))),
|
|
},
|
|
None => Err((self, anyhow!("Unexpected file path"))),
|
|
}
|
|
}
|
|
|
|
/// Delete `Self`, cleanup FS
|
|
pub fn delete(self) -> Result<()> {
|
|
Ok(remove_file(self.path)?)
|
|
}
|
|
|
|
// Getters
|
|
|
|
/// Shared helper to build public URI
|
|
pub fn to_uri(&self, directory: &str) -> String {
|
|
self.path
|
|
.to_str()
|
|
.unwrap()
|
|
.replace(directory, "")
|
|
.replace("/", "")
|
|
}
|
|
}
|