1pub mod docs;
9pub mod legacy;
10pub mod network;
11
12use crate::Lb;
13use crate::model::account::Account;
14use crate::model::file_metadata::Owner;
15use crate::model::signed_meta::SignedMeta;
16use crate::service::activity::DocEvent;
17use crate::service::lb_id::LbID;
18#[cfg(all(unix, not(target_os = "ios")))]
19use crate::subscribers::ipc::Ipc;
20use db_rs::View;
21use db_rs::config::Config;
22use db_rs::errors::Result as DbResult;
23use db_rs::guard::WriteTx;
24use db_rs::views::{
25 composite_view::{Composite, Schema},
26 hashmap::DbHashMap,
27 option::DbOption,
28 vec::DbVec,
29};
30use db_rs_old::hasher::UuidIdentityHasherBuilder;
31use db_rs_old::{Config as OldConfig, Db};
32use legacy::CoreV4;
33use std::error::Error;
34use std::fs;
35use std::path::Path;
36use std::sync::Arc;
37use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
38use uuid::Uuid;
39use web_time::{Duration, Instant};
40
41pub(crate) type LbDb = Arc<RwLock<CoreDb>>;
42pub type CoreDb = Composite<CoreV5>;
44pub type MigrationResult<T> = Result<T, Box<dyn Error + Send + Sync>>;
45
46#[derive(Default)]
47pub struct CoreV5 {
48 pub account: DbOption<Account>,
49 pub last_synced: DbOption<i64>,
50 pub root: DbOption<Uuid>,
51 pub local_metadata: DbHashMap<Uuid, SignedMeta, UuidIdentityHasherBuilder>,
52 pub base_metadata: DbHashMap<Uuid, SignedMeta, UuidIdentityHasherBuilder>,
53
54 pub pub_key_lookup: DbHashMap<Owner, String>,
56
57 pub doc_events: DbVec<DocEvent>,
58 pub id: DbOption<LbID>,
59 pub pinned_files: DbVec<Uuid>,
60
61 pub last_extracted_panic: DbOption<i64>,
65}
66
67impl Schema for CoreV5 {
68 fn views_mut(&mut self) -> impl AsMut<[&mut dyn View]> {
69 let views: [&mut dyn View; 10] = [
70 &mut self.account,
71 &mut self.last_synced,
72 &mut self.root,
73 &mut self.local_metadata,
74 &mut self.base_metadata,
75 &mut self.pub_key_lookup,
76 &mut self.doc_events,
77 &mut self.id,
78 &mut self.pinned_files,
79 &mut self.last_extracted_panic,
80 ];
81 views
82 }
83}
84
85pub fn init_with_migration(path: &Path) -> MigrationResult<CoreDb> {
86 let directory = path.join("CoreV5");
87 fs::create_dir_all(&directory)?;
88 let mut db = CoreDb::init(&Config::default().log_location(directory))?;
89 let old_path = path.join("CoreV4.db");
90
91 if db.schema.account.is_none() {
92 if !old_path.try_exists()? && !path.join("CoreV4").try_exists()? {
93 return Ok(db);
94 }
95 let source = CoreV4::init(OldConfig {
96 create_db: false,
97 create_path: false,
98 ..OldConfig::in_folder(path)
99 })?;
100 let tx = db.write_tx()?;
101 let dest = &mut db.schema;
102 if let Some(value) = source.account.get() {
103 dest.account.replace(value.clone())?;
104 }
105 if let Some(value) = source.last_synced.get() {
106 dest.last_synced.replace(*value)?;
107 }
108 if let Some(value) = source.root.get() {
109 dest.root.replace(*value)?;
110 }
111 for (key, value) in source.local_metadata.get() {
112 dest.local_metadata.insert(*key, value.clone())?;
113 }
114 for (key, value) in source.base_metadata.get() {
115 dest.base_metadata.insert(*key, value.clone())?;
116 }
117 for (key, value) in source.pub_key_lookup.get() {
118 dest.pub_key_lookup.insert(*key, value.clone())?;
119 }
120 for value in source.doc_events.get() {
121 dest.doc_events.push(*value)?;
122 }
123 if let Some(value) = source.id.get() {
124 dest.id.replace(*value)?;
125 }
126 for value in source.pinned_files.get() {
127 dest.pinned_files.push(*value)?;
128 }
129 if let Some(value) = source.last_extracted_panic.get() {
130 dest.last_extracted_panic.replace(*value)?;
131 }
132 tx.end_tx(&mut db)?;
133 drop(source);
134 fs::remove_file(old_path)?;
135 }
136 Ok(db)
137}
138
139pub struct LbRO<'a> {
140 guard: RwLockReadGuard<'a, CoreDb>,
141}
142
143impl LbRO<'_> {
144 pub fn db(&self) -> &CoreV5 {
145 &self.guard.schema
146 }
147}
148
149pub struct LbTx<'a> {
150 guard: RwLockWriteGuard<'a, CoreDb>,
151 tx: Option<WriteTx>,
152 #[cfg(all(unix, not(target_os = "ios")))]
153 ipc: &'a Ipc,
154}
155
156impl LbTx<'_> {
157 pub fn db(&mut self) -> &mut CoreV5 {
158 &mut self.guard.schema
159 }
160
161 pub fn end(mut self) {
162 self.flush().unwrap();
163 }
164
165 fn flush(&mut self) -> DbResult<()> {
166 let tx = self.tx.take().unwrap();
167 let seq_no = tx.seq_no;
168 let committed_seq = tx.end_tx(&mut *self.guard)?;
169 if committed_seq == seq_no {
171 #[cfg(all(unix, not(target_os = "ios")))]
172 self.ipc.notify_peers();
173 }
174 Ok(())
175 }
176}
177
178impl Drop for LbTx<'_> {
179 fn drop(&mut self) {
180 if self.tx.is_some() {
181 if let Err(error) = self.flush() {
182 error!(?error, "failed to flush database transaction on drop");
183 }
184 }
185 }
186}
187
188impl Lb {
189 pub async fn ro_tx(&self) -> LbRO<'_> {
190 let start = Instant::now();
191
192 let guard = self.db.read().await;
193
194 if start.elapsed() > Duration::from_millis(100) {
195 warn!("readonly transaction lock acquisition took {:?}", start.elapsed());
196 }
197
198 LbRO { guard }
199 }
200
201 pub async fn begin_tx(&self) -> LbTx<'_> {
215 let start = Instant::now();
216
217 let mut guard = self.db.write().await;
218
219 if start.elapsed() > Duration::from_millis(100) {
220 warn!("readwrite transaction lock acquisition took {:?}", start.elapsed());
221 }
222
223 let previous_seq = guard.last_modified();
224 let tx = guard.write_tx().unwrap();
225 self.notify_catch_up(&guard.schema, previous_seq).await;
226
227 LbTx {
228 guard,
229 tx: Some(tx),
230 #[cfg(all(unix, not(target_os = "ios")))]
231 ipc: &self.ipc,
232 }
233 }
234}