iroh_docs/
protocol.rs

1//! [`ProtocolHandler`] implementation for the docs [`Engine`].
2
3use 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/// Docs protocol.
25#[derive(Debug, Clone)]
26pub struct Docs {
27    engine: Arc<Engine>,
28    api: DocsApi,
29}
30
31impl Docs {
32    /// Create a new [`Builder`] for the docs protocol, using in memory replica and author storage.
33    pub fn memory() -> Builder {
34        Builder::default()
35    }
36
37    /// Create a new [`Builder`] for the docs protocol, using a persistent replica and author storage
38    /// in the given directory.
39    #[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    /// Creates a new [`Docs`] from an [`Engine`].
48    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    /// Returns the API for this docs instance.
55    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/// Builder for the docs protocol.
85#[derive(Debug, Default)]
86pub struct Builder {
87    storage: Storage,
88    protect_cb: Option<ProtectCallbackHandler>,
89}
90
91impl Builder {
92    /// Set the garbage collection protection handler for blobs.
93    ///
94    /// See [`ProtectCallbackHandler::new`] for details.
95    pub fn protect_handler(mut self, protect_handler: ProtectCallbackHandler) -> Self {
96        self.protect_cb = Some(protect_handler);
97        self
98    }
99
100    /// Build a [`Docs`] protocol given a [`BlobsStore`] and [`Gossip`] protocol.
101    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}