Skip to main content

matrix_sdk_search/index/
builder.rs

1use std::{
2    fs,
3    path::{Path, PathBuf},
4    sync::Arc,
5};
6
7use ruma::OwnedRoomId;
8use tantivy::{
9    Index,
10    directory::{MmapDirectory, error::OpenDirectoryError},
11};
12use tracing::warn;
13use zeroize::Zeroizing;
14
15use crate::{
16    encrypted::encrypted_dir::EncryptedMmapDirectory,
17    error::IndexError,
18    index::RoomIndex,
19    schema::{MatrixSearchIndexSchema, RoomMessageSchema},
20};
21
22/// Builder for [`RoomIndex`].
23pub struct RoomIndexBuilder {}
24
25impl RoomIndexBuilder {
26    /// Make an index on disk
27    pub fn new_on_disk<R: Into<OwnedRoomId>>(
28        path: PathBuf,
29        room_id: R,
30    ) -> PhysicalRoomIndexBuilder {
31        PhysicalRoomIndexBuilder::new(path, room_id.into())
32    }
33
34    /// Make an index in memory
35    pub fn new_in_memory<R: Into<OwnedRoomId>>(room_id: R) -> MemoryRoomIndexBuilder {
36        MemoryRoomIndexBuilder::new(room_id.into())
37    }
38}
39
40/// Incomplete builder for [`RoomIndex`] on disk.
41pub struct PhysicalRoomIndexBuilder {
42    path: PathBuf,
43    room_id: OwnedRoomId,
44}
45
46impl PhysicalRoomIndexBuilder {
47    /// Make an new [`PhysicalRoomIndexBuilder`]
48    pub(crate) fn new(path: PathBuf, room_id: OwnedRoomId) -> PhysicalRoomIndexBuilder {
49        PhysicalRoomIndexBuilder { path, room_id }
50    }
51
52    /// Make an unencrypted index
53    pub fn unencrypted(&self) -> UnencryptedPhysicalRoomIndexBuilder {
54        UnencryptedPhysicalRoomIndexBuilder {
55            path: self.path.clone(),
56            room_id: self.room_id.clone(),
57        }
58    }
59
60    /// Make an index encrypted with the given high-entropy key
61    pub fn encrypted<K: Into<String>>(&self, key: K) -> EncryptedPhysicalRoomIndexBuilder {
62        EncryptedPhysicalRoomIndexBuilder {
63            path: self.path.clone(),
64            room_id: self.room_id.clone(),
65            key: Zeroizing::new(key.into()),
66        }
67    }
68}
69
70/// Complete builder for [`RoomIndex`] on disk.
71pub struct UnencryptedPhysicalRoomIndexBuilder {
72    path: PathBuf,
73    room_id: OwnedRoomId,
74}
75
76impl UnencryptedPhysicalRoomIndexBuilder {
77    /// Build the [`RoomIndex`]
78    pub fn build(&self) -> Result<RoomIndex, IndexError> {
79        let path = self.path.join(self.room_id.as_str());
80        let mmap_dir = match MmapDirectory::open(path) {
81            Ok(dir) => Ok(dir),
82            Err(err) => match err {
83                OpenDirectoryError::DoesNotExist(path) => {
84                    fs::create_dir_all(path.clone()).map_err(|err| {
85                        OpenDirectoryError::IoError {
86                            io_error: Arc::new(err),
87                            directory_path: path.to_path_buf(),
88                        }
89                    })?;
90                    MmapDirectory::open(path)
91                }
92                _ => Err(err),
93            },
94        }?;
95        let schema = RoomMessageSchema::new();
96        let index = Index::open_or_create(mmap_dir, schema.as_tantivy_schema())?;
97        Ok(RoomIndex::new_with(index, schema, &self.room_id))
98    }
99}
100
101/// Complete builder for [`RoomIndex`] on disk.
102pub struct EncryptedPhysicalRoomIndexBuilder {
103    path: PathBuf,
104    room_id: OwnedRoomId,
105    key: Zeroizing<String>,
106}
107
108impl EncryptedPhysicalRoomIndexBuilder {
109    /// Build the [`RoomIndex`].
110    ///
111    /// An existing index that can't be opened, e.g. because it was encrypted
112    /// with another key or in an older format, is deleted and recreated.
113    pub fn build(&self) -> Result<RoomIndex, IndexError> {
114        let path = self.path.join(self.room_id.as_str());
115
116        // Any failure to open recreates the index, as it can't tell a wrong
117        // key from other errors.
118        self.open(&path).or_else(|err| {
119            warn!(room_id = %self.room_id, "Recreating the search index: {err}");
120            fs::remove_dir_all(&path)?;
121            self.open(&path)
122        })
123    }
124
125    fn open(&self, path: &Path) -> Result<RoomIndex, IndexError> {
126        let mmap_dir = EncryptedMmapDirectory::open_or_create(path, &self.key)?;
127        let schema = RoomMessageSchema::new();
128        let index = Index::open_or_create(mmap_dir, schema.as_tantivy_schema())?;
129        Ok(RoomIndex::new_with(index, schema, &self.room_id))
130    }
131}
132
133/// Builder for [`RoomIndex`] in memory
134pub struct MemoryRoomIndexBuilder {
135    room_id: OwnedRoomId,
136}
137
138impl MemoryRoomIndexBuilder {
139    /// Make an new [`MemoryIndexBuilder`]
140    pub(crate) fn new(room_id: OwnedRoomId) -> MemoryRoomIndexBuilder {
141        MemoryRoomIndexBuilder { room_id }
142    }
143
144    /// Build the [`RoomIndex`]
145    pub fn build(&self) -> RoomIndex {
146        let schema = RoomMessageSchema::new();
147        let index = Index::create_in_ram(schema.as_tantivy_schema());
148        RoomIndex::new_with(index, schema, &self.room_id)
149    }
150}