Initial commit
This commit is contained in:
commit
a344c251b0
31 changed files with 3295 additions and 0 deletions
229
src/db/mod.rs
Normal file
229
src/db/mod.rs
Normal file
|
@ -0,0 +1,229 @@
|
|||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use eyre::Context;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub use upload_id::*;
|
||||
|
||||
use crate::schemas::novel::{FullNovel, Novel};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||
pub struct State {
|
||||
pub posted_until: Option<jiff::Zoned>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Db {
|
||||
novels: PathBuf,
|
||||
|
||||
state_path: PathBuf,
|
||||
state: State,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Entry {
|
||||
pub info: PathBuf,
|
||||
pub thumbnail: PathBuf,
|
||||
|
||||
pub screenshots: PathBuf,
|
||||
pub files: PathBuf,
|
||||
pub upload_queue: PathBuf,
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
pub fn finish_upload(&self, id: UploadId, file_name: &str) -> eyre::Result<()> {
|
||||
let src = self.upload_queue.join(id.to_file_name());
|
||||
let dest = match id.kind {
|
||||
UploadKind::Thumbnail => self.thumbnail.clone(),
|
||||
UploadKind::Screenshot => self.screenshots.join(file_name),
|
||||
UploadKind::File => self.files.join(file_name),
|
||||
};
|
||||
|
||||
fs::rename(src, dest).wrap_err("failed to mark upload as finished")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn start_upload(&self, slug: impl Into<String>, kind: UploadKind) -> (UploadId, PathBuf) {
|
||||
let id = UploadId {
|
||||
id: Uuid::new_v4(),
|
||||
kind,
|
||||
novel: slug.into(),
|
||||
};
|
||||
|
||||
let path = self.upload_queue.join(id.to_file_name());
|
||||
(id, path)
|
||||
}
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
pub fn read_info(&self) -> eyre::Result<Novel> {
|
||||
let raw_info = fs::read_to_string(&self.info).wrap_err("failed to read info")?;
|
||||
let info: Novel = toml::from_str(&raw_info).wrap_err("failed to deserialize TOML")?;
|
||||
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
pub fn get_full_info(&self) -> eyre::Result<FullNovel> {
|
||||
let info = self.read_info()?;
|
||||
|
||||
Ok(FullNovel {
|
||||
data: info,
|
||||
upload_queue: self.upload_queue()?,
|
||||
files: self.list_files()?,
|
||||
screenshots: self.list_screenshots()?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn write_info(&self, info: &Novel) -> eyre::Result<()> {
|
||||
let s = toml::to_string_pretty(info).wrap_err("failed to serialize novel")?;
|
||||
fs::write(&self.info, &s)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn screenshot(&self, name: &str) -> eyre::Result<PathBuf> {
|
||||
let path = self.files.join(name);
|
||||
eyre::ensure!(path.exists(), "the screenshot does not exist");
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
pub fn file(&self, name: &str) -> eyre::Result<PathBuf> {
|
||||
let path = self.files.join(name);
|
||||
eyre::ensure!(path.exists(), "the file does not exist");
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
fn listdir(dir: &Path) -> eyre::Result<Vec<String>> {
|
||||
let mut out = Vec::new();
|
||||
for entry in fs::read_dir(dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
let last = path.components().last().unwrap();
|
||||
let last = last.as_os_str().to_string_lossy().to_string();
|
||||
out.push(last);
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
pub fn list_screenshots(&self) -> eyre::Result<Vec<String>> {
|
||||
Self::listdir(&self.screenshots)
|
||||
}
|
||||
|
||||
pub fn list_files(&self) -> eyre::Result<Vec<String>> {
|
||||
Self::listdir(&self.files)
|
||||
}
|
||||
|
||||
pub fn upload_queue(&self) -> eyre::Result<Vec<String>> {
|
||||
Self::listdir(&self.upload_queue)
|
||||
}
|
||||
|
||||
pub fn init(&self, info: &Novel) -> eyre::Result<()> {
|
||||
if self.info.exists() || self.files.exists() {
|
||||
eyre::bail!("this entry is already exists");
|
||||
}
|
||||
|
||||
fs::create_dir_all(&self.screenshots).wrap_err("failed to create screenshots dir")?;
|
||||
fs::create_dir_all(&self.files).wrap_err("failed to create files dir")?;
|
||||
fs::create_dir_all(&self.upload_queue).wrap_err("failed to create upload queue")?;
|
||||
self.write_info(info)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn exists(&self) -> bool {
|
||||
self.info.exists()
|
||||
}
|
||||
}
|
||||
|
||||
fn create_dir(p: impl AsRef<Path>) -> eyre::Result<()> {
|
||||
let p = p.as_ref();
|
||||
if p.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
tracing::info!("creating directory {}", p.display());
|
||||
fs::create_dir_all(p)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Db {
|
||||
pub fn state(&self) -> &State {
|
||||
&self.state
|
||||
}
|
||||
pub fn update_state<O>(&mut self, f: impl FnOnce(&mut State) -> O) -> O {
|
||||
let out = f(&mut self.state);
|
||||
let serialized = toml::to_string_pretty(&self.state).unwrap();
|
||||
|
||||
fs::write(&self.state_path, serialized).unwrap();
|
||||
out
|
||||
}
|
||||
|
||||
pub fn from_root(root: impl AsRef<Path>) -> eyre::Result<Self> {
|
||||
let root = root.as_ref();
|
||||
fs::create_dir_all(root)?;
|
||||
let novels = root.join("novels");
|
||||
let state_path = root.join("state.toml");
|
||||
|
||||
if !state_path.exists() {
|
||||
fs::write(
|
||||
&state_path,
|
||||
toml::to_string_pretty(&State::default()).unwrap(),
|
||||
)
|
||||
.wrap_err("failed to create state")?;
|
||||
}
|
||||
|
||||
create_dir(root)?;
|
||||
create_dir(&novels)?;
|
||||
|
||||
let state = toml::from_str(&fs::read_to_string(&state_path).unwrap())
|
||||
.wrap_err("failed to read state")?;
|
||||
|
||||
Ok(Self {
|
||||
novels,
|
||||
state,
|
||||
state_path,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Db {
|
||||
pub fn enumerate(&self) -> Vec<(String, Entry)> {
|
||||
let read = fs::read_dir(&self.novels).unwrap();
|
||||
let mut entries = vec![];
|
||||
|
||||
for entry in read.map(|e| e.unwrap().path()) {
|
||||
let slug = entry.file_stem().unwrap().to_str().unwrap();
|
||||
entries.push((slug.to_owned(), self.entry(slug)));
|
||||
}
|
||||
|
||||
entries
|
||||
}
|
||||
|
||||
pub fn entry(&self, slug: &str) -> Entry {
|
||||
let root = self.novels.join(slug);
|
||||
let thumbnail = root.join("thumbnail");
|
||||
let screenshots = root.join("screenshots");
|
||||
let upload_queue = root.join("upload_queue");
|
||||
|
||||
let info = root.join("info.toml");
|
||||
let files = root.join("files");
|
||||
|
||||
Entry {
|
||||
info,
|
||||
files,
|
||||
upload_queue,
|
||||
thumbnail,
|
||||
screenshots,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod upload_id;
|
Loading…
Add table
Add a link
Reference in a new issue