matrix_sdk_indexeddb/event_cache_store/types.rs
1// Copyright 2025 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License
14
15use std::time::Duration;
16
17use matrix_sdk_base::{
18 cross_process_lock::CrossProcessLockGeneration,
19 deserialized_responses::TimelineEvent,
20 event_cache::store::extract_event_relation,
21 linked_chunk::{ChunkIdentifier, LinkedChunkId, OwnedLinkedChunkId},
22};
23use ruma::{EventId, OwnedEventId, RoomId};
24use serde::{Deserialize, Serialize};
25
26/// Representation of a time-based lock on the entire
27/// [`IndexeddbEventCacheStore`](crate::event_cache_store::IndexeddbEventCacheStore)
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct Lease {
30 pub key: String,
31 pub holder: String,
32 pub expiration: Duration,
33 pub generation: CrossProcessLockGeneration,
34}
35
36/// Representation of a [`Chunk`](matrix_sdk_base::linked_chunk::Chunk)
37/// which can be stored in IndexedDB.
38#[derive(Debug, Serialize, Deserialize)]
39pub struct Chunk {
40 /// The linked chunk id in which the chunk exists.
41 pub linked_chunk_id: OwnedLinkedChunkId,
42 /// The identifier of the chunk - i.e.,
43 /// [`ChunkIdentifier`](matrix_sdk_base::linked_chunk::ChunkIdentifier).
44 pub identifier: u64,
45 /// The previous chunk in the list.
46 pub previous: Option<u64>,
47 /// The next chunk in the list.
48 pub next: Option<u64>,
49 /// The type of the chunk.
50 pub chunk_type: ChunkType,
51}
52
53/// The type of a [`Chunk`](Chunk)
54#[derive(Debug, Serialize, Deserialize)]
55#[serde(rename_all = "snake_case")]
56pub enum ChunkType {
57 /// A chunk that holds events.
58 Event,
59 /// A chunk that represents a gap.
60 Gap,
61}
62
63/// An inclusive representation of an
64/// [`Event`](matrix_sdk_base::event_cache::Event) which can be stored in
65/// IndexedDB.
66///
67/// This is useful when (de)serializing an event which may either be in-band or
68/// out-of-band.
69#[derive(Debug, Serialize, Deserialize)]
70#[serde(untagged)]
71pub enum Event {
72 /// An in-band event, i.e., an event which is part of a chunk.
73 InBand(InBandEvent),
74 /// An out-of-band event, i.e., an event which is not part of a chunk.
75 OutOfBand(OutOfBandEvent),
76}
77
78impl From<Event> for TimelineEvent {
79 fn from(value: Event) -> Self {
80 match value {
81 Event::InBand(e) => e.content,
82 Event::OutOfBand(e) => e.content,
83 }
84 }
85}
86
87impl Event {
88 /// The [`LinkedChunkId`] in which the underlying event exists.
89 pub fn linked_chunk_id(&self) -> LinkedChunkId<'_> {
90 match self {
91 Event::InBand(e) => e.linked_chunk_id.as_ref(),
92 Event::OutOfBand(e) => e.linked_chunk_id.as_ref(),
93 }
94 }
95
96 /// The [`RoomId`] of the room in which the underlying event exists.
97 pub fn room_id(&self) -> &RoomId {
98 match self {
99 Event::InBand(e) => e.room_id(),
100 Event::OutOfBand(e) => e.room_id(),
101 }
102 }
103
104 /// The [`EventId`] of the underlying event.
105 pub fn event_id(&self) -> Option<&EventId> {
106 match self {
107 Event::InBand(e) => e.event_id(),
108 Event::OutOfBand(e) => e.event_id(),
109 }
110 }
111
112 /// The [`Position`] of the underlying event, if it is in a chunk.
113 pub fn position(&self) -> Option<Position> {
114 match self {
115 Event::InBand(e) => Some(e.position),
116 Event::OutOfBand(_) => None,
117 }
118 }
119
120 /// The [`OwnedEventId`] and
121 /// [`RelationType`](ruma::events::relation::RelationType) of the underlying
122 /// event as a [`String`].
123 pub fn relation(&self) -> Option<(OwnedEventId, String)> {
124 match self {
125 Event::InBand(e) => e.relation(),
126 Event::OutOfBand(e) => e.relation(),
127 }
128 }
129
130 /// Sets the content of the underlying [`GenericEvent`] and returns
131 /// the mutated [`Event`]
132 pub fn with_content(mut self, content: TimelineEvent) -> Self {
133 match self {
134 Event::InBand(ref mut i) => i.content = content,
135 Event::OutOfBand(ref mut o) => o.content = content,
136 }
137 self
138 }
139
140 /// Ensures that the underlying [`GenericEvent`] is an [`OutOfBandEvent`].
141 /// If it is not an [`OutOfBandEvent`], then it is converted into one.
142 pub fn into_out_of_band_event(self) -> Self {
143 match self {
144 Event::InBand(i) => Self::OutOfBand(i.into()),
145 o @ Event::OutOfBand(_) => o,
146 }
147 }
148}
149
150/// A generic representation of an
151/// [`Event`](matrix_sdk_base::event_cache::Event) which can be stored in
152/// IndexedDB.
153///
154/// This is useful when (de)serializing an event which is required to be either
155/// in-band or out-of-band.
156#[derive(Debug, Serialize, Deserialize)]
157pub struct GenericEvent<P> {
158 /// The linked chunk id in which the event exists.
159 pub linked_chunk_id: OwnedLinkedChunkId,
160 /// The full content of the event.
161 pub content: TimelineEvent,
162 /// The position of the event, if it is in a chunk.
163 pub position: P,
164}
165
166impl<P> GenericEvent<P> {
167 /// The [`RoomId`] of the room in which the event exists.
168 pub fn room_id(&self) -> &RoomId {
169 self.linked_chunk_id.room_id()
170 }
171
172 /// The [`EventId`] of the underlying event.
173 pub fn event_id(&self) -> Option<&EventId> {
174 self.content.event_id()
175 }
176
177 /// The event that the underlying event relates to, if any.
178 ///
179 /// Returns the related [`OwnedEventId`] and the
180 /// [`RelationType`](ruma::events::relation::RelationType) as a [`String`].
181 pub fn relation(&self) -> Option<(OwnedEventId, String)> {
182 extract_event_relation(self.content.raw())
183 }
184}
185
186/// A concrete instance of [`GenericEvent`] for in-band events, i.e.,
187/// events which are part of a chunk and therefore have a position.
188pub type InBandEvent = GenericEvent<Position>;
189
190/// A concrete instance of [`GenericEvent`] for out-of-band events, i.e.,
191/// events which are not part of a chunk and therefore have no position.
192pub type OutOfBandEvent = GenericEvent<()>;
193
194impl From<InBandEvent> for OutOfBandEvent {
195 fn from(value: InBandEvent) -> Self {
196 Self { linked_chunk_id: value.linked_chunk_id, content: value.content, position: () }
197 }
198}
199
200/// A representation of [`Position`](matrix_sdk_base::linked_chunk::Position)
201/// which can be stored in IndexedDB.
202#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)]
203pub struct Position {
204 /// The identifier of the chunk.
205 pub chunk_identifier: u64,
206 /// The index of the event within the chunk.
207 pub index: usize,
208}
209
210impl From<Position> for matrix_sdk_base::linked_chunk::Position {
211 fn from(value: Position) -> Self {
212 Self::new(ChunkIdentifier::new(value.chunk_identifier), value.index)
213 }
214}
215
216impl From<matrix_sdk_base::linked_chunk::Position> for Position {
217 fn from(value: matrix_sdk_base::linked_chunk::Position) -> Self {
218 Self { chunk_identifier: value.chunk_identifier().index(), index: value.index() }
219 }
220}
221
222/// A representation of [`Gap`](matrix_sdk_base::linked_chunk::Gap)
223/// which can be stored in IndexedDB.
224#[derive(Debug, Serialize, Deserialize)]
225pub struct Gap {
226 /// The linked chunk id in which the gap exists.
227 pub linked_chunk_id: OwnedLinkedChunkId,
228 /// The identifier of the chunk containing this gap.
229 pub chunk_identifier: u64,
230 /// The token to use in the query, extracted from a previous "from" /
231 /// "end" field of a `/messages` response.
232 #[serde(alias = "prev_token")]
233 pub token: String,
234}