Skip to main content

xmtp_mls/groups/lifecycle/
restored.rs

1//! Each archive group element commits its inert stub and row together.
2
3use super::*;
4use xmtp_db::ConnectionExt;
5use xmtp_proto::xmtp::device_sync::group_backup::GroupSave;
6
7impl<Context: XmtpSharedContext> MlsGroup<Context> {
8    /// Apply one archive `group` element. Returns whether storage changed.
9    // implements: ARCH-014, ARCH-020, ARCH-021
10    pub fn restore_from_archive(context: &Context, save: &GroupSave) -> Result<bool, GroupError> {
11        let group_id = GroupId::try_from(save.id.as_slice())?;
12        state_write(context.mls_storage(), |tx| {
13            if tx.storage().db().find_group(&group_id)?.is_some() {
14                return Ok::<_, GroupError>(Continue(merge_activity(
15                    &tx.storage().db(),
16                    &group_id,
17                    save.last_message_ns,
18                )?));
19            }
20            let conversation_type: ConversationType = save.conversation_type().try_into()?;
21            let pair = if conversation_type == ConversationType::Dm {
22                Some(crate::groups::parse_canonical_dm_id(save.dm_id.as_deref())?)
23            } else {
24                None
25            };
26            let attributes = save
27                .mutable_metadata
28                .as_ref()
29                .map(|metadata| &metadata.attributes);
30            let attribute = |name| {
31                attributes
32                    .and_then(|attributes| attributes.get(name))
33                    .cloned()
34            };
35            let opts = GroupMetadataOptions {
36                name: attribute("group_name"),
37                image_url_square: attribute("group_image_url_square"),
38                description: attribute("description"),
39                app_data: attribute("app_data"),
40                message_disappearing_settings: match (
41                    save.message_disappear_from_ns,
42                    save.message_disappear_in_ns,
43                ) {
44                    (Some(from_ns), Some(in_ns)) => {
45                        Some(MessageDisappearingSettings::new(from_ns, in_ns))
46                    }
47                    _ => None,
48                },
49            };
50            let kind = match &pair {
51                Some(pair) => InitialGroupKind::RestoredDm {
52                    member_one_inbox_id: &pair.member_one_inbox_id,
53                    member_two_inbox_id: &pair.member_two_inbox_id,
54                },
55                None => InitialGroupKind::Group {
56                    conversation_type,
57                    oneshot_message: None,
58                },
59            };
60            let policy = if pair.is_some() {
61                PolicySet::new_dm()
62            } else {
63                PolicySet::default()
64            };
65            // The placeholder never receives archived admin lists or a signer.
66            let dictionary = initial_dictionary(
67                kind,
68                &policy
69                    .to_proto()
70                    .map_err(group_permissions::GroupMutablePermissionsError::from)
71                    .map_err(MetadataPermissionsError::from)?,
72                &opts,
73                context.inbox_id(),
74                None,
75                &context
76                    .server_configuration()
77                    .configuration()
78                    .application_components,
79            )
80            .map_err(app_data::migration::BootstrapSynthesisError::from)?;
81            let config = build_group_config(dictionary)?;
82            if let Some(pair) = &pair {
83                Self::insert_dm_row(
84                    context,
85                    tx,
86                    GroupMembershipState::Restored,
87                    pair,
88                    &opts,
89                    Some(&save.id),
90                    &config,
91                    false,
92                )?;
93            } else {
94                Self::insert_group_row(
95                    context,
96                    tx,
97                    Some(&save.id),
98                    GroupMembershipState::Restored,
99                    conversation_type,
100                    &opts,
101                    &config,
102                    false,
103                )?;
104            }
105            let storage = tx.storage();
106            let db = storage.db();
107            use xmtp_db::diesel::prelude::*;
108            use xmtp_db::schema::groups::dsl;
109            db.raw_query(|conn| {
110                xmtp_db::diesel::update(dsl::groups.find(&group_id))
111                    .set((
112                        dsl::created_at_ns.eq(save.created_at_ns),
113                        dsl::added_by_inbox_id.eq(&save.added_by_inbox_id),
114                        dsl::message_disappear_from_ns.eq(save.message_disappear_from_ns),
115                        dsl::message_disappear_in_ns.eq(save.message_disappear_in_ns),
116                        dsl::should_publish_commit_log.eq(false),
117                    ))
118                    .execute(conn)
119            })?;
120            merge_activity(&db, &group_id, save.last_message_ns)?;
121            Ok(Continue(true))
122        })
123        .map(TransactionOutcome::into_continued)
124    }
125}
126
127fn merge_activity(
128    db: &impl DbQuery,
129    group_id: &GroupId,
130    timestamp: Option<i64>,
131) -> Result<bool, StorageError> {
132    let Some(timestamp) = timestamp else {
133        return Ok(false);
134    };
135    use xmtp_db::diesel::prelude::*;
136    use xmtp_db::schema::groups::dsl;
137    Ok(db.raw_query(|conn| {
138        xmtp_db::diesel::update(dsl::groups.find(group_id))
139            .filter(
140                dsl::last_message_ns
141                    .is_null()
142                    .or(dsl::last_message_ns.lt(timestamp)),
143            )
144            .set(dsl::last_message_ns.eq(Some(timestamp)))
145            .execute(conn)
146    })? > 0)
147}