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-027
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, a group
66            // root, or a signer: only a Welcome brings the root.
67            let dictionary = initial_dictionary(
68                kind,
69                &policy
70                    .to_proto()
71                    .map_err(group_permissions::GroupMutablePermissionsError::from)
72                    .map_err(MetadataPermissionsError::from)?,
73                &opts,
74                context.inbox_id(),
75                None,
76                &context
77                    .server_configuration()
78                    .configuration()
79                    .application_components,
80            )
81            .map_err(app_data::migration::BootstrapSynthesisError::from)?;
82            let config = build_group_config(dictionary)?;
83            if let Some(pair) = &pair {
84                Self::insert_dm_row(
85                    context,
86                    tx,
87                    GroupMembershipState::Restored,
88                    pair,
89                    &opts,
90                    NewGroupId::Placeholder(group_id),
91                    &config,
92                    false,
93                )?;
94            } else {
95                Self::insert_group_row(
96                    context,
97                    tx,
98                    NewGroupId::Placeholder(group_id),
99                    GroupMembershipState::Restored,
100                    conversation_type,
101                    &opts,
102                    &config,
103                    false,
104                )?;
105            }
106            let storage = tx.storage();
107            let db = storage.db();
108            use xmtp_db::diesel::prelude::*;
109            use xmtp_db::schema::groups::dsl;
110            db.raw_query(|conn| {
111                xmtp_db::diesel::update(dsl::groups.find(&group_id))
112                    .set((
113                        dsl::created_at_ns.eq(save.created_at_ns),
114                        dsl::added_by_inbox_id.eq(&save.added_by_inbox_id),
115                        dsl::message_disappear_from_ns.eq(save.message_disappear_from_ns),
116                        dsl::message_disappear_in_ns.eq(save.message_disappear_in_ns),
117                        dsl::should_publish_commit_log.eq(false),
118                    ))
119                    .execute(conn)
120            })?;
121            merge_activity(&db, &group_id, save.last_message_ns)?;
122            Ok(Continue(true))
123        })
124        .map(TransactionOutcome::into_continued)
125    }
126}
127
128fn merge_activity(
129    db: &impl DbQuery,
130    group_id: &GroupId,
131    timestamp: Option<i64>,
132) -> Result<bool, StorageError> {
133    let Some(timestamp) = timestamp else {
134        return Ok(false);
135    };
136    use xmtp_db::diesel::prelude::*;
137    use xmtp_db::schema::groups::dsl;
138    Ok(db.raw_query(|conn| {
139        xmtp_db::diesel::update(dsl::groups.find(group_id))
140            .filter(
141                dsl::last_message_ns
142                    .is_null()
143                    .or(dsl::last_message_ns.lt(timestamp)),
144            )
145            .set(dsl::last_message_ns.eq(Some(timestamp)))
146            .execute(conn)
147    })? > 0)
148}