1use std::sync::Arc;
4
5use anyhow::Result;
6use iroh::{endpoint::Connection, protocol::ProtocolHandler, Endpoint};
7use iroh_blobs::api::Store as BlobsStore;
8use iroh_gossip::net::Gossip;
9
10use crate::{
11 api::DocsApi,
12 engine::{DefaultAuthorStorage, Engine, ProtectCallbackHandler},
13 store::Store,
14};
15
16#[derive(Default, Debug)]
17enum Storage {
18 #[default]
19 Memory,
20 #[cfg(feature = "fs-store")]
21 Persistent(std::path::PathBuf),
22}
23
24#[derive(Debug, Clone)]
26pub struct Docs {
27 engine: Arc<Engine>,
28 api: DocsApi,
29}
30
31impl Docs {
32 pub fn memory() -> Builder {
34 Builder::default()
35 }
36
37 #[cfg(feature = "fs-store")]
40 pub fn persistent(path: std::path::PathBuf) -> Builder {
41 Builder {
42 storage: Storage::Persistent(path),
43 protect_cb: None,
44 }
45 }
46
47 pub fn new(engine: Engine) -> Self {
49 let engine = Arc::new(engine);
50 let api = DocsApi::spawn(engine.clone());
51 Self { engine, api }
52 }
53
54 pub fn api(&self) -> &DocsApi {
56 &self.api
57 }
58}
59
60impl std::ops::Deref for Docs {
61 type Target = DocsApi;
62
63 fn deref(&self) -> &Self::Target {
64 &self.api
65 }
66}
67
68impl ProtocolHandler for Docs {
69 async fn accept(&self, connection: Connection) -> Result<(), iroh::protocol::AcceptError> {
70 self.engine
71 .handle_connection(connection)
72 .await
73 .map_err(|err| err.into_boxed_dyn_error())?;
74 Ok(())
75 }
76
77 async fn shutdown(&self) {
78 if let Err(err) = self.engine.shutdown().await {
79 tracing::warn!("shutdown error: {:?}", err);
80 }
81 }
82}
83
84#[derive(Debug, Default)]
86pub struct Builder {
87 storage: Storage,
88 protect_cb: Option<ProtectCallbackHandler>,
89}
90
91impl Builder {
92 pub fn protect_handler(mut self, protect_handler: ProtectCallbackHandler) -> Self {
96 self.protect_cb = Some(protect_handler);
97 self
98 }
99
100 pub async fn spawn(
102 self,
103 endpoint: Endpoint,
104 blobs: BlobsStore,
105 gossip: Gossip,
106 ) -> anyhow::Result<Docs> {
107 let replica_store = match &self.storage {
108 Storage::Memory => Store::memory(),
109 #[cfg(feature = "fs-store")]
110 Storage::Persistent(path) => Store::persistent(path.join("docs.redb"))?,
111 };
112 let author_store = match &self.storage {
113 Storage::Memory => DefaultAuthorStorage::Mem,
114 #[cfg(feature = "fs-store")]
115 Storage::Persistent(path) => {
116 DefaultAuthorStorage::Persistent(path.join("default-author"))
117 }
118 };
119 let downloader = blobs.downloader(&endpoint);
120 let engine = Engine::spawn(
121 endpoint,
122 gossip,
123 replica_store,
124 blobs,
125 downloader,
126 author_store,
127 self.protect_cb,
128 )
129 .await?;
130 Ok(Docs::new(engine))
131 }
132}