post_archiver/manager/
mod.rs1use std::path::{Path, PathBuf};
2use std::sync::{Arc, Mutex};
3use std::time::Duration;
4
5use cached::TimedSizedCache;
6use rusqlite::{Connection, Transaction};
7
8use crate::error::{Error, Result};
9use crate::utils::{DATABASE_NAME, VERSION};
10
11pub mod binded;
12pub use binded::*;
13
14pub mod author;
15pub use author::{AuthorThumb, AuthorUpdated, UpdateAuthor};
16
17pub mod collection;
18pub use collection::{CollectionThumb, UpdateCollection};
19
20pub mod file_meta;
21pub use file_meta::{UpdateFileMeta, WritableFileMeta};
22
23pub mod platform;
24pub use platform::UpdatePlatform;
25
26pub mod post;
27pub use post::{PostUpdated, UpdatePost};
28
29pub mod tag;
30pub use tag::UpdateTag;
31
32#[derive(Debug)]
44pub struct PostArchiverManager<C = Connection> {
45 pub caches: Arc<Mutex<ManagerCaches>>,
46 pub path: PathBuf,
47 conn: C,
48}
49
50impl PostArchiverManager {
51 pub fn create<P>(path: P) -> Result<Self>
63 where
64 P: AsRef<Path>,
65 {
66 let path = path.as_ref().to_path_buf();
67 let db_path = path.join(DATABASE_NAME);
68
69 if db_path.exists() {
70 return Err(Error::DatabaseAlreadyExists);
71 }
72
73 let conn = Connection::open(&db_path)?;
74
75 conn.execute_batch(include_str!("../utils/template.sql"))?;
77
78 conn.execute(
80 "INSERT INTO post_archiver_meta (version) VALUES (?)",
81 [VERSION],
82 )?;
83
84 Ok(Self {
85 conn,
86 path,
87 caches: Default::default(),
88 })
89 }
90
91 pub fn open<P>(path: P) -> Result<Option<Self>>
107 where
108 P: AsRef<Path>,
109 {
110 let manager = Self::open_uncheck(path)?;
111
112 if let Some(manager) = &manager {
114 let version: String = manager
115 .conn()
116 .query_row("SELECT version FROM post_archiver_meta", [], |row| {
117 row.get(0)
118 })
119 .unwrap_or("unknown".to_string());
120
121 let get_compatible_version =
122 |version: &str| version.splitn(3, ".").collect::<Vec<_>>()[0..2].join(".");
123
124 let match_version = match version.as_str() {
125 "unknown" => "unknown".to_string(),
126 version => get_compatible_version(version),
127 };
128 let expect_version = get_compatible_version(VERSION);
129
130 if match_version != expect_version {
131 return Err(Error::VersionMismatch {
132 current: version,
133 expected: VERSION.to_string(),
134 });
135 }
136 }
137
138 Ok(manager)
139 }
140
141 pub fn open_uncheck<P>(path: P) -> Result<Option<Self>>
148 where
149 P: AsRef<Path>,
150 {
151 let path = path.as_ref().to_path_buf();
152 let db_path = path.join(DATABASE_NAME);
153
154 if !db_path.exists() {
155 return Ok(None);
156 }
157
158 let conn = Connection::open(db_path)?;
159
160 Ok(Some(Self {
161 conn,
162 path,
163 caches: Default::default(),
164 }))
165 }
166
167 pub fn open_or_create<P>(path: P) -> Result<Self>
176 where
177 P: AsRef<Path>,
178 {
179 Self::open(&path)
180 .transpose()
181 .unwrap_or_else(|| Self::create(&path))
182 }
183
184 pub fn open_in_memory() -> Result<Self> {
193 let path = std::env::temp_dir();
194
195 let conn = Connection::open_in_memory()?;
196
197 conn.execute_batch(include_str!("../utils/template.sql"))?;
199
200 conn.execute(
202 "INSERT INTO post_archiver_meta (version) VALUES (?)",
203 [VERSION],
204 )?;
205
206 Ok(Self {
207 conn,
208 path,
209 caches: Default::default(),
210 })
211 }
212
213 pub fn transaction(&mut self) -> Result<PostArchiverManager<Transaction<'_>>> {
225 Ok(PostArchiverManager {
226 path: self.path.clone(),
227 caches: self.caches.clone(),
228 conn: self.conn.transaction()?,
229 })
230 }
231}
232
233impl PostArchiverManager<Transaction<'_>> {
234 pub fn commit(self) -> Result<()> {
236 Ok(self.conn.commit()?)
237 }
238}
239
240impl<C> PostArchiverManager<C>
241where
242 C: PostArchiverConnection,
243{
244 pub fn conn(&self) -> &Connection {
246 self.conn.connection()
247 }
248
249 pub fn bind<Id: BindableId>(&self, id: Id) -> Binded<'_, Id, C> {
263 Binded::new(self, id)
264 }
265}
266
267pub trait PostArchiverConnection {
269 fn connection(&self) -> &Connection;
270}
271
272impl PostArchiverConnection for Connection {
273 fn connection(&self) -> &Connection {
274 self
275 }
276}
277
278impl PostArchiverConnection for Transaction<'_> {
279 fn connection(&self) -> &Connection {
280 self
281 }
282}
283
284#[derive(Debug)]
285pub struct ManagerCaches {
286 pub counts: TimedSizedCache<(String, String), u64>,
287}
288
289impl Default for ManagerCaches {
290 fn default() -> Self {
291 Self {
292 counts: TimedSizedCache::with_size_and_lifespan(128, Duration::from_mins(30)),
293 }
294 }
295}