pub mod docs;
pub mod legacy;
pub mod network;
use crate::Lb;
use crate::model::account::Account;
use crate::model::file_metadata::Owner;
use crate::model::signed_meta::SignedMeta;
use crate::service::activity::DocEvent;
use crate::service::lb_id::LbID;
use db_rs::View;
use db_rs::config::Config;
use db_rs::guard::WriteTx;
use db_rs::views::{
composite_view::{Composite, Schema},
hashmap::DbHashMap,
option::DbOption,
vec::DbVec,
};
use db_rs_old::hasher::UuidIdentityHasherBuilder;
use db_rs_old::{Config as OldConfig, Db};
use legacy::CoreV4;
use std::error::Error;
use std::fs;
use std::path::Path;
use std::sync::Arc;
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use uuid::Uuid;
use web_time::{Duration, Instant};
pub(crate) type LbDb = Arc<RwLock<CoreDb>>;
pub type CoreDb = Composite<CoreV5>;
pub type MigrationResult<T> = Result<T, Box<dyn Error + Send + Sync>>;
#[derive(Default)]
pub struct CoreV5 {
pub account: DbOption<Account>,
pub last_synced: DbOption<i64>,
pub root: DbOption<Uuid>,
pub local_metadata: DbHashMap<Uuid, SignedMeta, UuidIdentityHasherBuilder>,
pub base_metadata: DbHashMap<Uuid, SignedMeta, UuidIdentityHasherBuilder>,
pub pub_key_lookup: DbHashMap<Owner, String>,
pub doc_events: DbVec<DocEvent>,
pub id: DbOption<LbID>,
pub pinned_files: DbVec<Uuid>,
pub last_extracted_panic: DbOption<i64>,
}
impl Schema for CoreV5 {
fn views_mut(&mut self) -> impl AsMut<[&mut dyn View]> {
let views: [&mut dyn View; 10] = [
&mut self.account,
&mut self.last_synced,
&mut self.root,
&mut self.local_metadata,
&mut self.base_metadata,
&mut self.pub_key_lookup,
&mut self.doc_events,
&mut self.id,
&mut self.pinned_files,
&mut self.last_extracted_panic,
];
views
}
}
pub fn init_with_migration(path: &Path) -> MigrationResult<CoreDb> {
let directory = path.join("CoreV5");
fs::create_dir_all(&directory)?;
let mut db = CoreDb::init(&Config::default().log_location(directory))?;
let old_path = path.join("CoreV4.db");
if db.schema.account.is_none() {
if !old_path.try_exists()? && !path.join("CoreV4").try_exists()? {
return Ok(db);
}
let source = CoreV4::init(OldConfig {
create_db: false,
create_path: false,
..OldConfig::in_folder(path)
})?;
let tx = db.write_tx()?;
let dest = &mut db.schema;
if let Some(value) = source.account.get() {
dest.account.replace(value.clone())?;
}
if let Some(value) = source.last_synced.get() {
dest.last_synced.replace(*value)?;
}
if let Some(value) = source.root.get() {
dest.root.replace(*value)?;
}
for (key, value) in source.local_metadata.get() {
dest.local_metadata.insert(*key, value.clone())?;
}
for (key, value) in source.base_metadata.get() {
dest.base_metadata.insert(*key, value.clone())?;
}
for (key, value) in source.pub_key_lookup.get() {
dest.pub_key_lookup.insert(*key, value.clone())?;
}
for value in source.doc_events.get() {
dest.doc_events.push(*value)?;
}
if let Some(value) = source.id.get() {
dest.id.replace(*value)?;
}
for value in source.pinned_files.get() {
dest.pinned_files.push(*value)?;
}
if let Some(value) = source.last_extracted_panic.get() {
dest.last_extracted_panic.replace(*value)?;
}
tx.end_tx(&mut db)?;
drop(source);
fs::remove_file(old_path)?;
}
Ok(db)
}
pub struct LbRO<'a> {
guard: RwLockReadGuard<'a, CoreDb>,
}
impl LbRO<'_> {
pub fn db(&self) -> &CoreV5 {
&self.guard.schema
}
}
pub struct LbTx<'a> {
guard: RwLockWriteGuard<'a, CoreDb>,
tx: Option<WriteTx>,
}
impl LbTx<'_> {
pub fn db(&mut self) -> &mut CoreV5 {
&mut self.guard.schema
}
pub fn end(mut self) {
self.tx.take().unwrap().end_tx(&mut *self.guard).unwrap();
}
}
impl Drop for LbTx<'_> {
fn drop(&mut self) {
if let Some(tx) = self.tx.take() {
if let Err(error) = tx.end_tx(&mut *self.guard) {
error!(?error, "failed to flush database transaction on drop");
}
}
}
}
impl Lb {
pub async fn ro_tx(&self) -> LbRO<'_> {
let start = Instant::now();
let guard = self.db.read().await;
if start.elapsed() > Duration::from_millis(100) {
warn!("readonly transaction lock acquisition took {:?}", start.elapsed());
}
LbRO { guard }
}
pub async fn begin_tx(&self) -> LbTx<'_> {
let start = Instant::now();
let mut guard = self.db.write().await;
if start.elapsed() > Duration::from_millis(100) {
warn!("readwrite transaction lock acquisition took {:?}", start.elapsed());
}
let tx = guard.write_tx().unwrap();
LbTx { guard, tx: Some(tx) }
}
}