Skip to main content

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::{collections::HashSet, sync::Arc};
18
19use matrix_sdk_common::deserialized_responses::TimelineEvent;
20use ruma::{
21    OwnedRoomId, OwnedUserId, RoomId,
22    events::{
23        direct::OwnedDirectUserIdentifier,
24        room::{
25            avatar::PossiblyRedactedRoomAvatarEventContent,
26            canonical_alias::PossiblyRedactedRoomCanonicalAliasEventContent,
27            create::RoomCreateEventContent, encryption::PossiblyRedactedRoomEncryptionEventContent,
28            guest_access::PossiblyRedactedRoomGuestAccessEventContent,
29            history_visibility::PossiblyRedactedRoomHistoryVisibilityEventContent,
30            join_rules::PossiblyRedactedRoomJoinRulesEventContent,
31            name::PossiblyRedactedRoomNameEventContent,
32            tombstone::PossiblyRedactedRoomTombstoneEventContent,
33            topic::PossiblyRedactedRoomTopicEventContent,
34        },
35    },
36};
37use serde::{Deserialize, Serialize};
38
39use crate::{
40    MinimalStateEvent, RoomInfo, RoomState,
41    deserialized_responses::SyncOrStrippedState,
42    latest_event::LatestEventValue,
43    room::{BaseRoomInfo, RoomSummary, SyncInfo},
44    sync::UnreadNotificationsCount,
45};
46
47/// [`RoomInfo`] version 1.
48///
49/// The `name` field in `RoomNameEventContent` was optional and has become
50/// required. It means that sometimes the field has been serialized with the
51/// value `null`.
52///
53/// For the migration:
54///
55/// 1. Deserialize the stored room info using this type,
56/// 2. Get the `m.room.create` event for the room, if it is available,
57/// 3. Convert this to [`RoomInfo`] with `.migrate(create_event)`,
58/// 4. Replace the room info in the store.
59#[derive(Clone, Debug, Serialize, Deserialize)]
60pub struct RoomInfoV1 {
61    room_id: OwnedRoomId,
62    room_type: RoomState,
63    notification_counts: UnreadNotificationsCount,
64    summary: RoomSummary,
65    members_synced: bool,
66    last_prev_batch: Option<String>,
67    #[serde(default = "sync_info_complete")] // see fn docs for why we use this default
68    sync_info: SyncInfo,
69    #[serde(default = "encryption_state_default")] // see fn docs for why we use this default
70    encryption_state_synced: bool,
71    latest_event: Option<TimelineEvent>,
72    base_info: BaseRoomInfoV1,
73}
74
75impl RoomInfoV1 {
76    /// Get the room ID of this room.
77    pub fn room_id(&self) -> &RoomId {
78        &self.room_id
79    }
80
81    /// Returns the state this room is in.
82    pub fn state(&self) -> RoomState {
83        self.room_type
84    }
85
86    /// Migrate this to a [`RoomInfo`], using the given `m.room.create` event
87    /// from the room state.
88    pub fn migrate(self, create: Option<&SyncOrStrippedState<RoomCreateEventContent>>) -> RoomInfo {
89        let RoomInfoV1 {
90            room_id,
91            room_type,
92            notification_counts,
93            summary,
94            members_synced,
95            last_prev_batch,
96            sync_info,
97            encryption_state_synced,
98            latest_event: _, // deprecated
99            base_info,
100        } = self;
101
102        RoomInfo {
103            data_format_version: 0,
104            room_id,
105            room_state: room_type,
106            notification_counts,
107            summary,
108            members_synced,
109            last_prev_batch,
110            sync_info,
111            encryption_state_synced,
112            latest_event_value: LatestEventValue::None,
113            read_receipts: Default::default(),
114            base_info: base_info.migrate(create),
115            warned_about_unknown_room_version_rules: Arc::new(false.into()),
116            cached_display_name: None,
117            cached_user_defined_notification_mode: None,
118            recency_stamp: None,
119        }
120    }
121}
122
123// The sync_info field introduced a new field in the database schema, for
124// backwards compatibility we assume that if the room is in the database, yet
125// the field isn't, we have synced it before this field was introduced - which
126// was a a full sync.
127fn sync_info_complete() -> SyncInfo {
128    SyncInfo::FullySynced
129}
130
131// The encryption_state_synced field introduced a new field in the database
132// schema, for backwards compatibility we assume that if the room is in the
133// database, yet the field isn't, we have synced it before this field was
134// introduced - which was a a full sync.
135fn encryption_state_default() -> bool {
136    true
137}
138
139/// [`BaseRoomInfo`] version 1.
140#[derive(Clone, Debug, Serialize, Deserialize)]
141struct BaseRoomInfoV1 {
142    avatar: Option<MinimalStateEvent<PossiblyRedactedRoomAvatarEventContent>>,
143    canonical_alias: Option<MinimalStateEvent<PossiblyRedactedRoomCanonicalAliasEventContent>>,
144    dm_targets: HashSet<OwnedUserId>,
145    encryption: Option<PossiblyRedactedRoomEncryptionEventContent>,
146    guest_access: Option<MinimalStateEvent<PossiblyRedactedRoomGuestAccessEventContent>>,
147    history_visibility:
148        Option<MinimalStateEvent<PossiblyRedactedRoomHistoryVisibilityEventContent>>,
149    join_rules: Option<MinimalStateEvent<PossiblyRedactedRoomJoinRulesEventContent>>,
150    max_power_level: i64,
151    name: Option<MinimalStateEvent<PossiblyRedactedRoomNameEventContent>>,
152    tombstone: Option<MinimalStateEvent<PossiblyRedactedRoomTombstoneEventContent>>,
153    topic: Option<MinimalStateEvent<PossiblyRedactedRoomTopicEventContent>>,
154}
155
156impl BaseRoomInfoV1 {
157    /// Migrate this to a [`BaseRoomInfo`].
158    fn migrate(
159        self,
160        create: Option<&SyncOrStrippedState<RoomCreateEventContent>>,
161    ) -> Box<BaseRoomInfo> {
162        let BaseRoomInfoV1 {
163            avatar,
164            canonical_alias,
165            dm_targets,
166            encryption,
167            guest_access,
168            history_visibility,
169            join_rules,
170            max_power_level,
171            name,
172            tombstone,
173            topic,
174        } = self;
175
176        let create = create.map(|ev| match ev {
177            SyncOrStrippedState::Sync(e) => e.into(),
178            SyncOrStrippedState::Stripped(e) => e.into(),
179        });
180
181        let mut converted_dm_targets = HashSet::new();
182        for dm_target in dm_targets {
183            converted_dm_targets.insert(OwnedDirectUserIdentifier::from(dm_target));
184        }
185
186        Box::new(BaseRoomInfo {
187            avatar,
188            canonical_alias,
189            create,
190            dm_targets: converted_dm_targets,
191            encryption,
192            guest_access,
193            history_visibility,
194            join_rules,
195            max_power_level,
196            name,
197            tombstone,
198            topic,
199            ..Default::default()
200        })
201    }
202}