matrix_sdk_base/store/
migration_helpers.rs

1// Copyright 2023 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
15//! Data migration helpers for StateStore implementations.
16
17use std::{
18    collections::{BTreeMap, HashSet},
19    sync::Arc,
20};
21
22use matrix_sdk_common::deserialized_responses::TimelineEvent;
23use ruma::{
24    OwnedRoomId, OwnedUserId, RoomId,
25    events::{
26        EmptyStateKey, RedactContent, StateEventContent, StateEventType,
27        direct::OwnedDirectUserIdentifier,
28        room::{
29            avatar::RoomAvatarEventContent,
30            canonical_alias::RoomCanonicalAliasEventContent,
31            create::RoomCreateEventContent,
32            encryption::RoomEncryptionEventContent,
33            guest_access::RoomGuestAccessEventContent,
34            history_visibility::RoomHistoryVisibilityEventContent,
35            join_rules::RoomJoinRulesEventContent,
36            name::{RedactedRoomNameEventContent, RoomNameEventContent},
37            tombstone::RoomTombstoneEventContent,
38            topic::RoomTopicEventContent,
39        },
40    },
41    room_version_rules::RedactionRules,
42};
43use serde::{Deserialize, Serialize};
44
45use crate::{
46    MinimalStateEvent, OriginalMinimalStateEvent, RoomInfo, RoomState,
47    deserialized_responses::SyncOrStrippedState,
48    latest_event::LatestEventValue,
49    room::{BaseRoomInfo, RoomSummary, SyncInfo},
50    sync::UnreadNotificationsCount,
51};
52
53/// [`RoomInfo`] version 1.
54///
55/// The `name` field in `RoomNameEventContent` was optional and has become
56/// required. It means that sometimes the field has been serialized with the
57/// value `null`.
58///
59/// For the migration:
60///
61/// 1. Deserialize the stored room info using this type,
62/// 2. Get the `m.room.create` event for the room, if it is available,
63/// 3. Convert this to [`RoomInfo`] with `.migrate(create_event)`,
64/// 4. Replace the room info in the store.
65#[derive(Clone, Debug, Serialize, Deserialize)]
66pub struct RoomInfoV1 {
67    room_id: OwnedRoomId,
68    room_type: RoomState,
69    notification_counts: UnreadNotificationsCount,
70    summary: RoomSummary,
71    members_synced: bool,
72    last_prev_batch: Option<String>,
73    #[serde(default = "sync_info_complete")] // see fn docs for why we use this default
74    sync_info: SyncInfo,
75    #[serde(default = "encryption_state_default")] // see fn docs for why we use this default
76    encryption_state_synced: bool,
77    latest_event: Option<TimelineEvent>,
78    base_info: BaseRoomInfoV1,
79}
80
81impl RoomInfoV1 {
82    /// Get the room ID of this room.
83    pub fn room_id(&self) -> &RoomId {
84        &self.room_id
85    }
86
87    /// Returns the state this room is in.
88    pub fn state(&self) -> RoomState {
89        self.room_type
90    }
91
92    /// Migrate this to a [`RoomInfo`], using the given `m.room.create` event
93    /// from the room state.
94    pub fn migrate(self, create: Option<&SyncOrStrippedState<RoomCreateEventContent>>) -> RoomInfo {
95        let RoomInfoV1 {
96            room_id,
97            room_type,
98            notification_counts,
99            summary,
100            members_synced,
101            last_prev_batch,
102            sync_info,
103            encryption_state_synced,
104            latest_event: _, // deprecated
105            base_info,
106        } = self;
107
108        RoomInfo {
109            data_format_version: 0,
110            room_id,
111            room_state: room_type,
112            notification_counts,
113            summary,
114            members_synced,
115            last_prev_batch,
116            sync_info,
117            encryption_state_synced,
118            latest_event_value: LatestEventValue::None,
119            read_receipts: Default::default(),
120            base_info: base_info.migrate(create),
121            warned_about_unknown_room_version_rules: Arc::new(false.into()),
122            cached_display_name: None,
123            cached_user_defined_notification_mode: None,
124            recency_stamp: None,
125            invite_acceptance_details: None,
126        }
127    }
128}
129
130// The sync_info field introduced a new field in the database schema, for
131// backwards compatibility we assume that if the room is in the database, yet
132// the field isn't, we have synced it before this field was introduced - which
133// was a a full sync.
134fn sync_info_complete() -> SyncInfo {
135    SyncInfo::FullySynced
136}
137
138// The encryption_state_synced field introduced a new field in the database
139// schema, for backwards compatibility we assume that if the room is in the
140// database, yet the field isn't, we have synced it before this field was
141// introduced - which was a a full sync.
142fn encryption_state_default() -> bool {
143    true
144}
145
146/// [`BaseRoomInfo`] version 1.
147#[derive(Clone, Debug, Serialize, Deserialize)]
148struct BaseRoomInfoV1 {
149    avatar: Option<MinimalStateEvent<RoomAvatarEventContent>>,
150    canonical_alias: Option<MinimalStateEvent<RoomCanonicalAliasEventContent>>,
151    dm_targets: HashSet<OwnedUserId>,
152    encryption: Option<RoomEncryptionEventContent>,
153    guest_access: Option<MinimalStateEvent<RoomGuestAccessEventContent>>,
154    history_visibility: Option<MinimalStateEvent<RoomHistoryVisibilityEventContent>>,
155    join_rules: Option<MinimalStateEvent<RoomJoinRulesEventContent>>,
156    max_power_level: i64,
157    name: Option<MinimalStateEvent<RoomNameEventContentV1>>,
158    tombstone: Option<MinimalStateEvent<RoomTombstoneEventContent>>,
159    topic: Option<MinimalStateEvent<RoomTopicEventContent>>,
160}
161
162impl BaseRoomInfoV1 {
163    /// Migrate this to a [`BaseRoomInfo`].
164    fn migrate(
165        self,
166        create: Option<&SyncOrStrippedState<RoomCreateEventContent>>,
167    ) -> Box<BaseRoomInfo> {
168        let BaseRoomInfoV1 {
169            avatar,
170            canonical_alias,
171            dm_targets,
172            encryption,
173            guest_access,
174            history_visibility,
175            join_rules,
176            max_power_level,
177            name,
178            tombstone,
179            topic,
180        } = self;
181
182        let create = create.map(|ev| match ev {
183            SyncOrStrippedState::Sync(e) => e.into(),
184            SyncOrStrippedState::Stripped(e) => e.into(),
185        });
186        let name = name.map(|name| match name {
187            MinimalStateEvent::Original(ev) => {
188                MinimalStateEvent::Original(OriginalMinimalStateEvent {
189                    content: ev.content.into(),
190                    event_id: ev.event_id,
191                })
192            }
193            MinimalStateEvent::Redacted(ev) => MinimalStateEvent::Redacted(ev),
194        });
195
196        let mut converted_dm_targets = HashSet::new();
197        for dm_target in dm_targets {
198            converted_dm_targets.insert(OwnedDirectUserIdentifier::from(dm_target));
199        }
200
201        Box::new(BaseRoomInfo {
202            avatar,
203            beacons: BTreeMap::new(),
204            canonical_alias,
205            create,
206            dm_targets: converted_dm_targets,
207            encryption,
208            guest_access,
209            history_visibility,
210            join_rules,
211            max_power_level,
212            name,
213            tombstone,
214            topic,
215            ..Default::default()
216        })
217    }
218}
219
220/// [`RoomNameEventContent`] version 1, with an optional `name`.
221#[derive(Clone, Debug, Serialize, Deserialize)]
222struct RoomNameEventContentV1 {
223    name: Option<String>,
224}
225
226impl StateEventContent for RoomNameEventContentV1 {
227    type StateKey = EmptyStateKey;
228
229    fn event_type(&self) -> StateEventType {
230        StateEventType::RoomName
231    }
232}
233
234impl RedactContent for RoomNameEventContentV1 {
235    type Redacted = RedactedRoomNameEventContent;
236
237    fn redact(self, _rules: &RedactionRules) -> Self::Redacted {
238        RedactedRoomNameEventContent::new()
239    }
240}
241
242impl From<RoomNameEventContentV1> for RoomNameEventContent {
243    fn from(value: RoomNameEventContentV1) -> Self {
244        RoomNameEventContent::new(value.name.unwrap_or_default())
245    }
246}