use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use cached::TimedSizedCache;
use rusqlite::{Connection, Transaction};
use crate::error::{Error, Result};
use crate::utils::{DATABASE_NAME, VERSION};
pub mod binded;
pub use binded::*;
pub mod author;
pub use author::{AuthorThumb, AuthorUpdated, UpdateAuthor};
pub mod collection;
pub use collection::{CollectionThumb, UpdateCollection};
pub mod file_meta;
pub use file_meta::{UpdateFileMeta, WritableFileMeta};
pub mod platform;
pub use platform::UpdatePlatform;
pub mod post;
pub use post::{PostUpdated, UpdatePost};
pub mod tag;
pub use tag::UpdateTag;
#[derive(Debug)]
pub struct PostArchiverManager<C = Connection> {
pub caches: Arc<Mutex<ManagerCaches>>,
pub path: PathBuf,
conn: C,
}
impl PostArchiverManager {
pub fn create<P>(path: P) -> Result<Self>
where
P: AsRef<Path>,
{
let path = path.as_ref().to_path_buf();
let db_path = path.join(DATABASE_NAME);
if db_path.exists() {
return Err(Error::DatabaseAlreadyExists);
}
let conn = Connection::open(&db_path)?;
conn.execute_batch(include_str!("../utils/template.sql"))?;
conn.execute(
"INSERT INTO post_archiver_meta (version) VALUES (?)",
[VERSION],
)?;
Ok(Self {
conn,
path,
caches: Default::default(),
})
}
pub fn open<P>(path: P) -> Result<Option<Self>>
where
P: AsRef<Path>,
{
let manager = Self::open_uncheck(path)?;
if let Some(manager) = &manager {
let version: String = manager
.conn()
.query_row("SELECT version FROM post_archiver_meta", [], |row| {
row.get(0)
})
.unwrap_or("unknown".to_string());
let get_compatible_version =
|version: &str| version.splitn(3, ".").collect::<Vec<_>>()[0..2].join(".");
let match_version = match version.as_str() {
"unknown" => "unknown".to_string(),
version => get_compatible_version(version),
};
let expect_version = get_compatible_version(VERSION);
if match_version != expect_version {
return Err(Error::VersionMismatch {
current: version,
expected: VERSION.to_string(),
});
}
}
Ok(manager)
}
pub fn open_uncheck<P>(path: P) -> Result<Option<Self>>
where
P: AsRef<Path>,
{
let path = path.as_ref().to_path_buf();
let db_path = path.join(DATABASE_NAME);
if !db_path.exists() {
return Ok(None);
}
let conn = Connection::open(db_path)?;
Ok(Some(Self {
conn,
path,
caches: Default::default(),
}))
}
pub fn open_or_create<P>(path: P) -> Result<Self>
where
P: AsRef<Path>,
{
Self::open(&path)
.transpose()
.unwrap_or_else(|| Self::create(&path))
}
pub fn open_in_memory() -> Result<Self> {
let path = std::env::temp_dir();
let conn = Connection::open_in_memory()?;
conn.execute_batch(include_str!("../utils/template.sql"))?;
conn.execute(
"INSERT INTO post_archiver_meta (version) VALUES (?)",
[VERSION],
)?;
Ok(Self {
conn,
path,
caches: Default::default(),
})
}
pub fn transaction(&mut self) -> Result<PostArchiverManager<Transaction<'_>>> {
Ok(PostArchiverManager {
path: self.path.clone(),
caches: self.caches.clone(),
conn: self.conn.transaction()?,
})
}
}
impl PostArchiverManager<Transaction<'_>> {
pub fn commit(self) -> Result<()> {
Ok(self.conn.commit()?)
}
}
impl<C> PostArchiverManager<C>
where
C: PostArchiverConnection,
{
pub fn conn(&self) -> &Connection {
self.conn.connection()
}
pub fn bind<Id: BindableId>(&self, id: Id) -> Binded<'_, Id, C> {
Binded::new(self, id)
}
}
pub trait PostArchiverConnection {
fn connection(&self) -> &Connection;
}
impl PostArchiverConnection for Connection {
fn connection(&self) -> &Connection {
self
}
}
impl PostArchiverConnection for Transaction<'_> {
fn connection(&self) -> &Connection {
self
}
}
#[derive(Debug)]
pub struct ManagerCaches {
pub counts: TimedSizedCache<(String, String), u64>,
}
impl Default for ManagerCaches {
fn default() -> Self {
Self {
counts: TimedSizedCache::with_size_and_lifespan(128, Duration::from_mins(30)),
}
}
}