Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Threema/libthreema/lib/examples/bin/   (Social Medium ©)  Datei vom 26.9.2026 mit Größe 60 kB image not shown  

Quelle  data_backup_create.rs   Sprache: Rust

 

//! Example for creating a data backup.
#![expect(unused_crate_dependencies, reason = "Example triggered false positive")]
mod utils;

use std::{
    fs,
    io::{self, Seek as _, Write as _},
    path,
    sync::{Arc, Mutex},
};

use clap::{Args, Parser};
use libthreema::{
    data_backup::task::{
        DataBackupProviders,
        create::{
            DataBackupCreateInit, DataBackupCreateLocalWriteInstruction, DataBackupCreateLoop,
            DataBackupCreateTask, DataBackupCreateWriteInstruction,
        },
    },
    utils::logging::init_stderr_logging,
};
use tracing::{Level, debug, info, trace};

use crate::provider::CreateDataProvider;

mod random_number_generator {
    use core::ops::Range;

    use libthreema_dependencies::rand::{
        self, Rng as _, RngCore as _, SeedableRng as _, distributions::uniform::SampleRange, rngs::StdRng,
        seq::SliceRandom as _,
    };

    pub(super) struct RandomNumberGenerator(StdRng);
    impl RandomNumberGenerator {
        pub(super) fn new() -> Self {
            Self(StdRng::from_seed([42; 32]))
        }

        pub(super) fn generate<T>(&mut self) -> T
        where
            rand::distributions::Standard: rand::distributions::Distribution<T>,
        {
            self.0.r#gen()
        }

        pub(super) fn random_string(&mut self, length: Range<usize>) -> String {
            self.0
                .clone()
                .sample_iter(rand::distributions::Alphanumeric)
                .take(self.0.gen_range(length))
                .map(char::from)
                .collect::<String>()
        }

        pub(super) fn random_optional_u64(&mut self) -> Option<u64> {
            if self.generate() {
                Some(self.generate())
            } else {
                None
            }
        }

        pub(super) fn random_bytes(&mut self, length: Range<usize>) -> Vec<u8> {
            let mut bytes = vec![0; self.0.gen_range(length)];
            self.0.fill_bytes(&mut bytes);
            bytes
        }

        pub(super) fn random_fixed_length_bytes<const LENGTH: usize>(&mut self) -> [u8; LENGTH] {
            let mut bytes = [0; LENGTH];
            self.0.fill_bytes(&mut bytes);
            bytes
        }

        pub(super) fn random_choice<'set, T>(&mut self, set: &'set [T]) -> Option<&'set T> {
            set.choose(&mut self.0)
        }

        pub(super) fn random_choice_multiple<T>(&mut self, set: &[T], count: usize) -> Vec<T>
        where
            T: Copy,
        {
            set.choose_multiple(&mut self.0, count).copied().collect()
        }

        pub(super) fn random_in_range<R>(&mut self, range: R) -> usize
        where
            R: SampleRange<usize>,
        {
            self.0.gen_range(range)
        }
    }
}

mod provider {
    #![expect(clippy::unimplemented, reason = "Unused methods for backup creation")]

    use core::{cmp::min, iter, ops::Range};
    use std::{
        collections::{BTreeMap, HashMap, HashSet},
        sync::{Arc, Mutex},
    };

    use libthreema::{
        common::{
            ChatServerGroup, ConversationId, ConversationType, DistributionListId, FeatureMask,
            GroupIdentity, HashedNonce, MessageId, MessageOverrides, MessageOverridesDeliveryReceiptPolicy,
            ThreemaId,
            keys::{ClientKey, PublicKey},
        },
        model::{
            contact::{Contact, ContactProvider, ContactWithProfilePictures},
            conversation::ConversationProvider,
            distribution_list::{DistributionList, DistributionListProvider},
            group::{Group, GroupMembershipFilter, GroupProvider, GroupUpdate, GroupWithProfilePicture},
            message::{
                EmojiReaction, IncomingMessageDirection, LocationMessageState, MessageDirection,
                MessageReaction, MessageStarMarker, MessageState, MessageStateBody, MessageStateMetadata,
                MessageStateUpdate, MessageText, MessageTextRevision, OutgoingMessageDirection, PollClose,
                PollOpen, TextMessageState,
            },
            poll::{
                Poll, PollChoice, PollChoiceId, PollId, PollProvider, PollVote, PollVotes, PollVotesSummary,
            },
            profile::{ProfilePicture, ProfilePictureMediaType},
            settings::{Settings, SettingsProvider, SettingsUpdate},
            user_profile::{IdentityData, UserProfile, UserProfileProvider},
        },
        protobuf,
        provider::{NonceProvider, ProviderError},
    };
    use libthreema_macros::concat_fixed_bytes;
    use tracing::warn;

    use crate::{
        BackupCreateParameters, random_number_generator::RandomNumberGenerator,
        utils::sync::MutexPoisonPanic as _,
    };

    const PROFILE_PICTURE_BYTE_LENGTH: Range<usize> = 1024..(2 * 1024);

    pub(super) struct CreateSettingsProvider;
    impl SettingsProvider for CreateSettingsProvider {
        fn set(&mut self, _settings: Settings) -> Result<(), ProviderError> {
            unimplemented!("Not needed for backup create");
        }

        fn update(&mut self, _update: SettingsUpdate) -> Result<(), ProviderError> {
            unimplemented!("Not needed for backup create");
        }

        fn get(&self) -> Result<Settings, ProviderError> {
            Ok(Settings::default())
        }
    }

    pub(super) struct CreateUserProfileProvider;
    impl UserProfileProvider for CreateUserProfileProvider {
        fn get(&self) -> Result<UserProfile, ProviderError> {
            Ok(UserProfile {
                nickname: "Moritz Mocker".to_owned(),
                profile_picture: Some(ProfilePicture {
                    media_type: ProfilePictureMediaType::Jpeg,
                    data: vec![0; PROFILE_PICTURE_BYTE_LENGTH.start],
                }),
                ..UserProfile::default()
            })
        }

        fn set(&mut self, _user_profile: UserProfile) -> Result<(), ProviderError> {
            unimplemented!("Not needed for backup create");
        }
    }

    pub(super) struct CreateContactProvider {
        pub(super) user_identity: ThreemaId,
        pub(super) random_number_generator: Arc<Mutex<RandomNumberGenerator>>,
        pub(super) contacts: Arc<Mutex<BTreeMap<ThreemaId, protobuf::d2d_sync::contact::AcquaintanceLevel>>>,
    }
    impl CreateContactProvider {
        fn random_contact(
            random_number_generator: &Arc<Mutex<RandomNumberGenerator>>,
            contacts: &Arc<Mutex<BTreeMap<ThreemaId, protobuf::d2d_sync::contact::AcquaintanceLevel>>>,
        ) -> Option<ThreemaId> {
            let contacts: Vec<ThreemaId> = contacts
                .lock_unwrap()
                .iter()
                .filter_map(|(contact, acquaintance_level)| {
                    if acquaintance_level == &protobuf::d2d_sync::contact::AcquaintanceLevel::Direct {
                        Some(contact)
                    } else {
                        None
                    }
                })
                .copied()
                .collect();

            random_number_generator
                .lock_unwrap()
                .random_choice(&contacts)
                .copied()
        }
    }
    impl ContactProvider for CreateContactProvider {
        fn add(&mut self, _contacts: Vec<Contact>) -> Result<(), ProviderError> {
            unimplemented!("Not needed for backup create");
        }

        fn add_with_profile_pictures(
            &mut self,
            _contacts: Vec<ContactWithProfilePictures>,
        ) -> Result<(), ProviderError> {
            unimplemented!("Not needed for backup create");
        }

        fn set_contact_defined_profile_picture(
            &self,
            _identity: ThreemaId,
            _profile_picture: Option<ProfilePicture>,
        ) -> Result<(), ProviderError> {
            unimplemented!("Not needed for backup create");
        }

        fn set_user_defined_profile_picture(
            &self,
            _identity: ThreemaId,
            _profile_picture: Option<ProfilePicture>,
        ) -> Result<(), ProviderError> {
            unimplemented!("Not needed for backup create");
        }

        fn update(
            &mut self,
            _updates: Vec<libthreema::model::contact::ContactUpdate>,
        ) -> Result<(), ProviderError> {
            unimplemented!("Not needed for backup create");
        }

        fn count_existing(&self, _identities: &[ThreemaId]) -> Result<usize, ProviderError> {
            unimplemented!("Not needed for backup create");
        }

        fn get(&self, _identity: ThreemaId) -> Result<Option<Contact>, ProviderError> {
            unimplemented!("Not needed for backup create");
        }

        fn get_identities(&self) -> Result<HashSet<ThreemaId>, ProviderError> {
            Ok(self.contacts.lock_unwrap().keys().copied().collect())
        }

        fn get_with_profile_pictures(
            &self,
            identity: ThreemaId,
        ) -> Result<Option<ContactWithProfilePictures>, ProviderError> {
            // Bail if identity is the user's identity.
            if identity == self.user_identity {
                return Err(ProviderError::ContractViolation(
                    "Unexpected encounter of user's identity in contact provider".to_owned(),
                ));
            }

            // Get a snapshot of the contact (if it exists).
            let contacts = self.contacts.lock_unwrap();
            let Some(acquaintance_level) = contacts.get(&identity) else {
                return Ok(None);
            };

            let mut random_number_generator = self.random_number_generator.lock_unwrap();
            Ok(Some(ContactWithProfilePictures {
                contact: Contact {
                    identity,
                    public_key: PublicKey::from(random_number_generator.random_fixed_length_bytes()),
                    created_at: random_number_generator.generate(),
                    last_update_at: random_number_generator.random_optional_u64(),
                    first_name: random_number_generator.random_string(0..20),
                    last_name: random_number_generator.random_string(0..20),
                    nickname: random_number_generator.random_string(0..20),
                    verification_level: protobuf::d2d_sync::contact::VerificationLevel::Unverified,
                    work_verification_level: protobuf::d2d_sync::contact::WorkVerificationLevel::None,
                    identity_type: protobuf::d2d_sync::contact::IdentityType::Work,
                    acquaintance_level: *acquaintance_level,
                    activity_state: protobuf::d2d_sync::contact::ActivityState::Active,
                    feature_mask: FeatureMask(0),
                    sync_state: protobuf::d2d_sync::contact::SyncState::Initial,
                    work_last_full_sync_at: random_number_generator.random_optional_u64(),
                    work_availability_status: None,
                    read_receipt_policy_override: None,
                    typing_indicator_policy_override: None,
                    notification_trigger_policy_override: None,
                    conversation_category: protobuf::d2d_sync::ConversationCategory::Default,
                    conversation_visibility: protobuf::d2d_sync::ConversationVisibility::Normal,
                },
                contact_defined_profile_picture: if random_number_generator.generate() {
                    Some(ProfilePicture {
                        media_type: ProfilePictureMediaType::Jpeg,
                        data: random_number_generator.random_bytes(PROFILE_PICTURE_BYTE_LENGTH),
                    })
                } else {
                    None
                },
                user_defined_profile_picture: if random_number_generator.generate() {
                    Some(ProfilePicture {
                        media_type: ProfilePictureMediaType::Jpeg,
                        data: random_number_generator.random_bytes(PROFILE_PICTURE_BYTE_LENGTH),
                    })
                } else {
                    None
                },
            }))
        }

        fn is_explicitly_blocked(&self, _identity: ThreemaId) -> Result<bool, ProviderError> {
            unimplemented!("Not needed for backup create");
        }

        fn has(&self, _identity: ThreemaId) -> Result<bool, ProviderError> {
            unimplemented!("Not needed for backup create");
        }
    }

    pub(super) struct CreateDistributionListProvider {
        pub(super) random_number_generator: Arc<Mutex<RandomNumberGenerator>>,
        pub(super) distribution_lists: Arc<Mutex<BTreeMap<DistributionListId, HashSet<ThreemaId>>>>,
    }
    impl DistributionListProvider for CreateDistributionListProvider {
        fn add(&mut self, _distribution_lists: Vec<DistributionList>) -> Result<(), ProviderError> {
            unimplemented!("Not needed for backup create");
        }

        fn get(&self, id: DistributionListId) -> Result<Option<DistributionList>, ProviderError> {
            let Some(members) = self.distribution_lists.lock_unwrap().get(&id).cloned() else {
                return Ok(None);
            };

            let mut random_number_generator = self.random_number_generator.lock_unwrap();
            Ok(Some(DistributionList {
                identity: id,
                name: random_number_generator.random_string(10..20),
                created_at: random_number_generator.generate(),
                last_update_at: random_number_generator.generate(),
                members,
                conversation_category: protobuf::d2d_sync::ConversationCategory::Default,
                conversation_visibility: protobuf::d2d_sync::ConversationVisibility::Normal,
            }))
        }

        fn get_ids(&self) -> Result<HashSet<DistributionListId>, ProviderError> {
            Ok(self.distribution_lists.lock_unwrap().keys().copied().collect())
        }
    }

    pub(super) struct CreateGroupProvider {
        pub(super) random_number_generator: Arc<Mutex<RandomNumberGenerator>>,
        pub(super) groups: Arc<Mutex<BTreeMap<GroupIdentity, HashSet<ThreemaId>>>>,
    }
    impl GroupProvider for CreateGroupProvider {
        fn add_with_profile_picture(
            &mut self,
            _groups: Vec<GroupWithProfilePicture>,
        ) -> Result<(), ProviderError> {
            unimplemented!("Not needed for backup create");
        }

        fn set_profile_picture(
            &mut self,
            _identity: GroupIdentity,
            _profile_picture: Option<ProfilePicture>,
        ) -> Result<(), ProviderError> {
            unimplemented!("Not needed for backup create");
        }

        fn update(&mut self, _updates: Vec<GroupUpdate>) -> Result<(), ProviderError> {
            unimplemented!("Not needed for backup create");
        }

        fn get(&self, _identity: GroupIdentity) -> Result<Option<Group>, ProviderError> {
            unimplemented!("Not needed for backup create");
        }

        fn get_identities(&self) -> Result<HashSet<GroupIdentity>, ProviderError> {
            Ok(self.groups.lock_unwrap().keys().copied().collect())
        }

        fn get_with_profile_picture(
            &self,
            identity: GroupIdentity,
        ) -> Result<Option<GroupWithProfilePicture>, ProviderError> {
            let Some(members) = self.groups.lock_unwrap().get(&identity).cloned() else {
                return Ok(None);
            };
            let mut random_number_generator = self.random_number_generator.lock_unwrap();
            Ok(Some(GroupWithProfilePicture {
                group: Group {
                    identity,
                    name: random_number_generator.random_string(10..20),
                    created_at: random_number_generator.generate(),
                    last_update_at: random_number_generator.random_optional_u64(),
                    user_state: protobuf::d2d_sync::group::UserState::Member,
                    members,
                    notification_trigger_policy_override: None,
                    conversation_category: protobuf::d2d_sync::ConversationCategory::Default,
                    conversation_visibility: protobuf::d2d_sync::ConversationVisibility::Normal,
                },
                profile_picture: if random_number_generator.generate() {
                    Some(ProfilePicture {
                        media_type: ProfilePictureMediaType::Jpeg,
                        data: random_number_generator.random_bytes(PROFILE_PICTURE_BYTE_LENGTH),
                    })
                } else {
                    None
                },
            }))
        }

        fn has(&self, _identity: GroupIdentity) -> Result<bool, ProviderError> {
            unimplemented!("Not needed for backup create");
        }

        fn is_part_of_any_group(
            &self,
            _identity: ThreemaId,
            _filter: GroupMembershipFilter,
        ) -> Result<bool, ProviderError> {
            unimplemented!("Not needed for backup create");
        }
    }

    pub(super) struct CreateConversationProvider {
        pub(super) random_number_generator: Arc<Mutex<RandomNumberGenerator>>,
        pub(super) user_identity: ThreemaId,
        pub(super) contacts: Arc<Mutex<BTreeMap<ThreemaId, protobuf::d2d_sync::contact::AcquaintanceLevel>>>,
        pub(super) distribution_lists: Arc<Mutex<BTreeMap<DistributionListId, HashSet<ThreemaId>>>>,
        pub(super) groups: Arc<Mutex<BTreeMap<GroupIdentity, HashSet<ThreemaId>>>>,

        /// Pool of [`PollOpen`] messages, grouped by conversation.
        ///
        /// Originated from polls generated by [`BackupPollProvider`] and consumed by
        /// [`Self::take_poll_open`].
        pub(super) poll_open_pool: Arc<Mutex<HashMap<ConversationId, Vec<PollOpen>>>>,

        /// Pool of [`PollClose`] messages for polls that have been opened and are to be closed.
        ///
        /// Populated when [`PollOpen`] entries are consumed by [`Self::take_poll_open`],
        /// and consumed by [`Self::take_poll_close`].
        pub(super) poll_close_pool: Arc<Mutex<HashMap<ConversationId, Vec<PollClose>>>>,

        pub(super) total_number_of_contact_messages: usize,
        pub(super) total_number_of_distribution_list_messages: usize,
        pub(super) total_number_of_group_messages: usize,
    }
    impl CreateConversationProvider {
        const MESSAGES_PER_BATCH: usize = 128;

        fn random_text_message_state(&self) -> TextMessageState {
            let mut random_number_generator = self.random_number_generator.lock_unwrap();
            TextMessageState {
                original: MessageText {
                    text: random_number_generator.random_string(10..500),
                },
                revisions: vec![MessageTextRevision {
                    created_at: random_number_generator.generate(),
                    message: MessageText {
                        text: random_number_generator.random_string(10..500),
                    },
                }],
            }
        }

        fn random_location_message(&self) -> LocationMessageState {
            let mut random_number_generator = self.random_number_generator.lock_unwrap();
            LocationMessageState {
                latitude: random_number_generator.generate(),
                longitude: random_number_generator.generate(),
                accuracy_m: if random_number_generator.generate() {
                    Some(5.0)
                } else {
                    None
                },
                name: random_number_generator.random_string(0..20),
                address: random_number_generator.random_string(0..20),
            }
        }

        fn random_reactions(&self, creators: &[ThreemaId]) -> Vec<MessageReaction> {
            let mut random_number_generator = self.random_number_generator.lock_unwrap();
            let mut reactions = vec![];
            for creator_identity in creators {
                if random_number_generator.generate() {
                    reactions.push(MessageReaction {
                        created_at: random_number_generator.generate(),
                        creator_identity: *creator_identity,
                        emoji: EmojiReaction {
                            codepoint: "\u{1f68}".to_owned(),
                        },
                    });
                }
            }
            reactions
        }

        /// Consume a [`PollOpen`] from the pool for `conversation`.
        ///
        /// The consumed [`PollOpen`] is used to generate a [`PollClose`] which is added
        /// to the close pool so that a matching poll-close message can be generated later.
        fn take_poll_open(&self, conversation: ConversationId) -> Option<PollOpen> {
            let poll_open = self
                .poll_open_pool
                .lock_unwrap()
                .get_mut(&conversation)
                .and_then(Vec::pop)?;
            self.poll_close_pool
                .lock_unwrap()
                .entry(conversation)
                .or_default()
                .push(PollClose { id: poll_open.id });
            Some(poll_open)
        }

        /// Consume a [`PollClose`] from the close pool for `conversation`.
        ///
        /// Only polls that have already been opened (via [`Self::take_poll_open`])
        /// are available here, ensuring every poll-close has a matching poll-open.
        fn take_poll_close(&self, conversation: ConversationId) -> Option<PollClose> {
            self.poll_close_pool
                .lock_unwrap()
                .get_mut(&conversation)
                .and_then(Vec::pop)
        }

        fn random_message_body_state(&self, conversation_id: ConversationId) -> MessageStateBody {
            let message_type = self.random_number_generator.lock_unwrap().random_in_range(0..4);
            match message_type {
                0 => MessageStateBody::Text(self.random_text_message_state()),
                1 => MessageStateBody::Location(self.random_location_message()),
                2 => self.take_poll_open(conversation_id).map_or_else(
                    || MessageStateBody::Text(self.random_text_message_state()),
                    MessageStateBody::PollOpen,
                ),
                3 => self.take_poll_close(conversation_id).map_or_else(
                    || MessageStateBody::Text(self.random_text_message_state()),
                    MessageStateBody::PollClose,
                ),
                _ => unreachable!("All message types should have been covered"),
            }
        }

        fn random_contact(&self) -> Option<ThreemaId> {
            let contacts = self
                .contacts
                .lock_unwrap()
                .iter()
                .filter_map(|(contact, acquaintance_level)| {
                    if acquaintance_level == &protobuf::d2d_sync::contact::AcquaintanceLevel::Direct {
                        Some(contact)
                    } else {
                        None
                    }
                })
                .copied()
                .collect::<Vec<_>>();

            self.random_number_generator
                .lock_unwrap()
                .random_choice(&contacts)
                .copied()
        }

        fn random_group_identity(&self) -> Option<GroupIdentity> {
            self.random_number_generator
                .lock_unwrap()
                .random_choice(&self.groups.lock_unwrap().keys().collect::<Vec<_>>())
                .copied()
                .copied()
        }

        fn random_distribution_list_identity(&self) -> Option<DistributionListId> {
            self.random_number_generator
                .lock_unwrap()
                .random_choice(&self.distribution_lists.lock_unwrap().keys().collect::<Vec<_>>())
                .copied()
                .copied()
        }

        fn random_reaction_pool(
            &self,
            guaranteed: impl IntoIterator<Item = ThreemaId>,
            members: &HashSet<ThreemaId>,
        ) -> Vec<ThreemaId> {
            let mut pool: HashSet<ThreemaId> = guaranteed.into_iter().collect();
            pool.extend(self.random_members(members));
            pool.into_iter().collect()
        }

        fn random_incoming_message_state(&self, conversation_type: ConversationType) -> MessageState {
            let (conversation_id, creator_identity, reactions, body) = match conversation_type {
                ConversationType::Contact => {
                    let sender = self
                        .random_contact()
                        .expect("There must be at least one direct contact for 1:1 messages");
                    let body = self.random_message_body_state(ConversationId::Contact(sender));
                    let reactions = self.random_reactions(&[self.user_identity, sender]);
                    (ConversationId::Contact(sender), sender, reactions, body)
                },

                ConversationType::DistributionList => {
                    unreachable!("Cannot generate incoming distribution list message for outgoing message");
                },

                ConversationType::Group => {
                    let group_identity = self
                        .random_group_identity()
                        .expect("There must be at least one group for group messages");

                    let body = self.random_message_body_state(ConversationId::Group(group_identity));

                    // Get group members for sender selection and reaction pool.
                    let groups = self.groups.lock_unwrap();
                    let members = groups.get(&group_identity).expect("Group should have members");

                    // Choose sender from list of group members or direct contacts if group has no members.
                    // Note: A sender might have been a part of the group in the past but isn't anymore.
                    let sender = {
                        let mut random_number_generator = self.random_number_generator.lock_unwrap();
                        random_number_generator
                            .random_choice(&members.iter().copied().collect::<Vec<_>>())
                            .copied()
                    }
                    .or_else(|| self.random_contact())
                    .expect("Incoming message should have a sender");

                    // Build reaction pool with guaranteed participants.
                    let reactions = self.random_reactions(&self.random_reaction_pool(
                        [self.user_identity, group_identity.creator_identity, sender],
                        members,
                    ));
                    (ConversationId::Group(group_identity), sender, reactions, body)
                },
            };

            let mut random_number_generator = self.random_number_generator.lock_unwrap();
            MessageState {
                conversation_id,
                creator_identity,
                message_id: MessageId(random_number_generator.generate()),
                created_at: random_number_generator.generate(),
                direction: MessageDirection::Incoming(IncomingMessageDirection {
                    received_at: random_number_generator.generate(),
                    overrides: MessageOverrides {
                        delivery_receipt_policy: MessageOverridesDeliveryReceiptPolicy::Default,
                    },
                }),
                read_at: random_number_generator.generate(),
                star_marker: MessageStarMarker::None,
                reactions,
                body,
            }
        }

        fn random_outgoing_message_state(&self, conversation_type: ConversationType) -> MessageState {
            let (conversation_id, reactions, body) = match conversation_type {
                ConversationType::Contact => {
                    let receiver_identity = self
                        .random_contact()
                        .expect("There must be at least one contact for 1:1 messages");
                    let reactions = self.random_reactions(&[self.user_identity, receiver_identity]);
                    let body = self.random_message_body_state(ConversationId::Contact(receiver_identity));

                    (ConversationId::Contact(receiver_identity), reactions, body)
                },
                ConversationType::DistributionList => {
                    let distribution_list_id = self
                        .random_distribution_list_identity()
                        .expect("There must be at least one distribution list");

                    (
                        ConversationId::DistributionList(distribution_list_id),
                        vec![],
                        self.random_message_body_state(ConversationId::DistributionList(
                            distribution_list_id,
                        )),
                    )
                },
                ConversationType::Group => {
                    let group_identity = self
                        .random_group_identity()
                        .expect("There must be at least one group for group messages");

                    let body = self.random_message_body_state(ConversationId::Group(group_identity));

                    // Build reaction pool from group members.
                    let reactions = self.random_reactions(
                        &self.random_reaction_pool(
                            [self.user_identity],
                            self.groups
                                .lock_unwrap()
                                .get(&group_identity)
                                .expect("Group should have members"),
                        ),
                    );

                    (ConversationId::Group(group_identity), reactions, body)
                },
            };

            let mut random_number_generator = self.random_number_generator.lock_unwrap();
            MessageState {
                conversation_id,
                creator_identity: self.user_identity,
                message_id: MessageId(random_number_generator.generate()),
                created_at: random_number_generator.generate(),
                direction: MessageDirection::Outgoing(OutgoingMessageDirection {
                    sent_at: Some(random_number_generator.generate()),
                    delivered_at: Some(random_number_generator.generate()),
                }),
                read_at: random_number_generator.generate(),
                star_marker: MessageStarMarker::None,
                reactions,
                body,
            }
        }

        fn random_members(&self, members: &HashSet<ThreemaId>) -> Vec<ThreemaId> {
            if members.is_empty() {
                return Vec::new();
            }
            let mut random_number_generator = self.random_number_generator.lock_unwrap();
            let count = random_number_generator.random_in_range(1..=members.len());
            let members: Vec<_> = members.iter().copied().collect();
            random_number_generator.random_choice_multiple(&members, count)
        }

        fn random_message_state(&self, conversation_type: ConversationType) -> MessageState {
            // Generate random message states (Incoming/Outgoing) for conversation types other
            // than DistributionLists (always Outgoing) which only supports Outgoing.
            match conversation_type {
                ConversationType::Contact | ConversationType::Group => {
                    if self.random_number_generator.lock_unwrap().generate() {
                        self.random_outgoing_message_state(conversation_type)
                    } else {
                        self.random_incoming_message_state(conversation_type)
                    }
                },
                ConversationType::DistributionList => self.random_outgoing_message_state(conversation_type),
            }
        }
    }
    impl ConversationProvider for CreateConversationProvider {
        fn add_messages(&mut self, _message_states: Vec<MessageState>) -> Result<(), ProviderError> {
            unimplemented!("Not needed for backup create");
        }

        fn set_typing_indicator(
            &mut self,
            _sender_identity: ThreemaId,
            _is_typing: bool,
        ) -> Result<(), ProviderError> {
            unimplemented!("Not needed for backup create");
        }

        fn update_messages(&mut self, _updates: Vec<MessageStateUpdate>) -> Result<(), ProviderError> {
            unimplemented!("Not needed for backup create");
        }

        fn get_many_messages(
            &self,
            conversation_type: ConversationType,
            batch_index: usize,
        ) -> Result<Vec<MessageState>, ProviderError> {
            let limit = match conversation_type {
                ConversationType::Contact => self.total_number_of_contact_messages,
                ConversationType::DistributionList => self.total_number_of_distribution_list_messages,
                ConversationType::Group => self.total_number_of_group_messages,
            };
            let mut messages = Vec::with_capacity(Self::MESSAGES_PER_BATCH);

            for _ in batch_index.saturating_mul(Self::MESSAGES_PER_BATCH)
                ..min(
                    batch_index
                        .saturating_add(1)
                        .saturating_mul(Self::MESSAGES_PER_BATCH),
                    limit,
                )
            {
                messages.push(self.random_message_state(conversation_type));
            }

            Ok(messages)
        }

        fn get_messages_metadata(
            &self,
            _conversation_id: ConversationId,
            _message_ids: &[MessageId],
        ) -> Result<Vec<MessageStateMetadata>, ProviderError> {
            unimplemented!("Not needed for backup create");
        }

        fn is_message_marked_used(
            &self,
            _sender_identity: ThreemaId,
            _message_id: MessageId,
        ) -> Result<bool, ProviderError> {
            unimplemented!("Not needed for backup create");
        }
    }

    pub(super) struct CreateCspNonceProvider {
        pub(super) total_number_of_nonces: usize,
    }
    impl CreateCspNonceProvider {
        const NONCES_PER_BATCH: usize = 1024;
    }
    impl NonceProvider for CreateCspNonceProvider {
        fn add_many(&mut self, _hashed_nonces: Vec<HashedNonce>) -> Result<(), ProviderError> {
            unimplemented!("Not needed for backup create");
        }

        fn get_many(&self, batch_index: usize) -> Result<Vec<HashedNonce>, ProviderError> {
            let mut nonces = Vec::with_capacity(Self::NONCES_PER_BATCH);

            for nonce in batch_index.saturating_mul(Self::NONCES_PER_BATCH)
                ..min(
                    batch_index
                        .saturating_add(1)
                        .saturating_mul(Self::NONCES_PER_BATCH),
                    self.total_number_of_nonces,
                )
            {
                nonces.push(HashedNonce(concat_fixed_bytes!(nonce.to_le_bytes(), [0_u8; 24])));
            }

            Ok(nonces)
        }

        fn has(&self, _hashed_nonce: &HashedNonce) -> Result<bool, ProviderError> {
            unimplemented!("Not needed for backup create");
        }
    }

    pub(super) struct CreatePollProvider {
        pub(super) random_number_generator: Arc<Mutex<RandomNumberGenerator>>,
        pub(super) user_identity: ThreemaId,
        pub(super) contacts: Arc<Mutex<BTreeMap<ThreemaId, protobuf::d2d_sync::contact::AcquaintanceLevel>>>,
        pub(super) groups: Arc<Mutex<BTreeMap<GroupIdentity, HashSet<ThreemaId>>>>,
        /// Registry of generated [`PollOpen`] messages, grouped by their conversation.
        ///
        /// Shared with [`BackupConversationProvider`] so that poll-open/poll-close messages can
        /// reference an actual poll in the same conversation.
        pub(super) polls: Arc<Mutex<HashMap<ConversationId, Vec<PollOpen>>>>,
        pub(super) number_of_polls: usize,
    }
    impl CreatePollProvider {
        const POLLS_PER_BATCH: usize = 64;

        /// Choose a random, non-empty set of voters for a poll in a group conversation.
        ///
        /// Selects a random subset of group members, then extends with a coin-flip
        /// - by a random number of non-group contacts to simulate former group members
        /// - by the user itself.
        fn random_group_voters(&self, group_identity: &GroupIdentity) -> Vec<ThreemaId> {
            let group_members: Vec<ThreemaId> = self
                .groups
                .lock_unwrap()
                .get(group_identity)
                .expect("Group must exist")
                .iter()
                .copied()
                .collect();

            let mut random_number_generator = self.random_number_generator.lock_unwrap();

            // Select a random subset of group members as voters.
            let voter_count = random_number_generator.random_in_range(0..=group_members.len());
            let mut voters = random_number_generator.random_choice_multiple(&group_members, voter_count);

            // Coin-flip: extend with random contacts that are not group members.
            if random_number_generator.generate() {
                let non_group_contacts: Vec<ThreemaId> = self
                    .contacts
                    .lock_unwrap()
                    .keys()
                    .copied()
                    .filter(|contact| !group_members.contains(contact))
                    .collect();

                if !non_group_contacts.is_empty() {
                    let extra_count = random_number_generator.random_in_range(1..=non_group_contacts.len());
                    voters.extend(
                        random_number_generator.random_choice_multiple(&non_group_contacts, extra_count),
                    );
                }
            }

            // Coin-flip: user will be part of the voters.
            if random_number_generator.generate() && !voters.contains(&self.user_identity) {
                voters.push(self.user_identity);
            }
            voters
        }

        /// Generate a random group or 1:1 `ConversationId`.
        ///
        /// Note: A group `ConversationId` is only generated if a group with at least one member exists.
        fn random_contact_or_group(&self) -> ConversationId {
            if self.random_number_generator.lock_unwrap().generate()
                && let Some(group_identity) = self
                    .random_number_generator
                    .lock_unwrap()
                    .random_choice(&self.groups.lock_unwrap().keys().collect::<Vec<_>>())
                    .copied()
                    .copied()
            {
                ConversationId::Group(group_identity)
            } else {
                let peer =
                    CreateContactProvider::random_contact(&self.random_number_generator, &self.contacts)
                        .expect("There must be at least one contact for polls");
                ConversationId::Contact(peer)
            }
        }

        /// Generate a random poll located either in a group or in a 1:1 conversation.
        ///
        /// A group poll is generated only if a group with at least one member exists and a 'coin flip'
        /// selects it, otherwise a 1:1 poll is generated.
        ///
        /// The voters depend on the conversation:
        /// - Group poll: a non-empty set of valid contacts.
        /// - 1:1 poll: the user and the contact the conversation is with.
        fn random_poll(&self) -> Poll {
            // Determine the conversation the poll is located in and its associated voters.
            let conversation = self.random_contact_or_group();
            let (creator_identity, voters) = match conversation {
                ConversationId::Group(group_identity) => (
                    if self.random_number_generator.lock_unwrap().generate() {
                        self.random_number_generator
                            .lock_unwrap()
                            .random_choice(&self.contacts.lock_unwrap().keys().copied().collect::<Vec<_>>())
                            .copied()
                            .expect("Contacts must not be empty")
                    } else {
                        self.user_identity
                    },
                    self.random_group_voters(&group_identity),
                ),
                ConversationId::Contact(contact) => (
                    self.random_number_generator
                        .lock_unwrap()
                        .random_choice(&[self.user_identity, contact])
                        .copied()
                        .expect("A valid user or contact"),
                    vec![self.user_identity, contact],
                ),
                ConversationId::DistributionList(_) => {
                    unreachable!("Unsupported conversation type for polls")
                },
            };

            let mut random_number_generator = self.random_number_generator.lock_unwrap();

            let answer_type = if random_number_generator.generate() {
                protobuf::data_backup::poll::PollAnswerType::SingleChoice
            } else {
                protobuf::data_backup::poll::PollAnswerType::MultipleChoice
            };
            let display_mode = if random_number_generator.generate() {
                protobuf::data_backup::poll::PollDisplayMode::List
            } else {
                protobuf::data_backup::poll::PollDisplayMode::Summary
            };

            let choices: Vec<PollChoice> = {
                let number_of_choices = random_number_generator.random_in_range(1..11);
                iter::repeat_with(|| PollChoice {
                    id: PollChoiceId(random_number_generator.generate()),
                    description: random_number_generator.random_string(1..20),
                })
                .take(number_of_choices)
                .collect()
            };

            let votes = {
                // Cast votes.
                let votes = voters
                    .iter()
                    .copied()
                    .map(|voter| {
                        (
                            voter,
                            PollVote {
                                choices: match answer_type {
                                    protobuf::data_backup::poll::PollAnswerType::SingleChoice => {
                                        HashSet::from([*random_number_generator
                                            .random_choice(
                                                &choices
                                                    .iter()
                                                    .map(|poll_choice| poll_choice.id)
                                                    .collect::<Vec<_>>(),
                                            )
                                            .expect("There should be at least one choice")])
                                    },

                                    protobuf::data_backup::poll::PollAnswerType::MultipleChoice => choices
                                        .iter()
                                        .filter_map(|poll_choice| {
                                            if random_number_generator.generate() {
                                                Some(poll_choice.id)
                                            } else {
                                                None
                                            }
                                        })
                                        .collect(),
                                }
                                .into_iter()
                                .collect(),
                                created_at: random_number_generator.generate(),
                                modified_at: random_number_generator.random_optional_u64(),
                            },
                        )
                    })
                    .collect();

                // Map votes to display mode.
                match display_mode {
                    protobuf::data_backup::poll::PollDisplayMode::List => PollVotes::Votes(votes),
                    protobuf::data_backup::poll::PollDisplayMode::Summary => {
                        let user_choices = if let Some(user_votes) = votes.get(&self.user_identity) {
                            user_votes.choices.iter().copied().collect()
                        } else {
                            HashSet::new()
                        };
                        PollVotes::Summary(PollVotesSummary {
                            summary: choices
                                .iter()
                                .map(|poll_choice| {
                                    (
                                        poll_choice.id,
                                        votes
                                            .iter()
                                            .filter(|(_, vote)| vote.choices.contains(&poll_choice.id))
                                            .count()
                                            .try_into()
                                            .expect("The number of votes should fit into u16"),
                                    )
                                })
                                .collect(),
                            user_choices,
                        })
                    },
                }
            };

            Poll {
                id: PollId(random_number_generator.generate()),
                conversation,
                creator_identity,
                created_at: random_number_generator.generate(),
                state: if random_number_generator.generate() {
                    protobuf::data_backup::poll::PollState::Open
                } else {
                    protobuf::data_backup::poll::PollState::Closed
                },
                description: random_number_generator.random_string(0..40),
                answer_type,
                announce_type: if random_number_generator.generate() {
                    protobuf::data_backup::poll::PollAnnounceType::CreatorOnly
                } else {
                    protobuf::data_backup::poll::PollAnnounceType::Everyone
                },
                display_mode,
                choices,
                votes,
            }
        }
    }
    impl PollProvider for CreatePollProvider {
        fn add(&mut self, _polls: Vec<Poll>) -> Result<(), ProviderError> {
            unimplemented!("Not needed for backup create");
        }

        fn get_many(&self, batch_index: usize) -> Result<Vec<Poll>, ProviderError> {
            let mut polls = Vec::with_capacity(Self::POLLS_PER_BATCH);

            for _ in batch_index.saturating_mul(Self::POLLS_PER_BATCH)
                ..min(
                    batch_index
                        .saturating_add(1)
                        .saturating_mul(Self::POLLS_PER_BATCH),
                    self.number_of_polls,
                )
            {
                let poll = self.random_poll();

                // Register the poll so poll-open/poll-close messages can reference it later.
                self.polls
                    .lock_unwrap()
                    .entry(poll.conversation)
                    .or_default()
                    .push(PollOpen { id: poll.id });

                polls.push(poll);
            }

            Ok(polls)
        }
    }

    pub(super) struct CreateDataProvider {
        random_number_generator: Arc<Mutex<RandomNumberGenerator>>,

        user_identity: ThreemaId,

        contacts: Arc<Mutex<BTreeMap<ThreemaId, protobuf::d2d_sync::contact::AcquaintanceLevel>>>,

        distribution_lists: Arc<Mutex<BTreeMap<DistributionListId, HashSet<ThreemaId>>>>,

        groups: Arc<Mutex<BTreeMap<GroupIdentity, HashSet<ThreemaId>>>>,

        /// Shared registry of generated [`PollOpen`] messages, keyed by conversation.
        /// Filled by the poll provider and read by the conversation provider so poll
        /// messages reference real polls.
        poll_opens: Arc<Mutex<HashMap<ConversationId, Vec<PollOpen>>>>,

        parameters: BackupCreateParameters,
    }
    impl CreateDataProvider {
        pub(super) fn new(parameters: BackupCreateParameters) -> anyhow::Result<(Self, IdentityData)> {
            let user_identity = ThreemaId::try_from("BACKMEUP").expect("Should be a valid Threema ID");
            let mut random_number_generator = RandomNumberGenerator::new();
            let contacts = Self::generate_contacts(&mut random_number_generator, parameters.contacts)?;
            let distribution_lists = Self::generate_distribution_lists(
                &mut random_number_generator,
                &contacts.keys().copied().collect::<Vec<_>>(),
                parameters.distribution_lists,
            );
            let groups = Self::generate_groups(
                &mut random_number_generator,
                user_identity,
                &contacts.keys().copied().collect::<Vec<_>>(),
                parameters.groups,
            );
            let example_data_provider = Self {
                random_number_generator: Arc::new(Mutex::new(random_number_generator)),
                user_identity,
                contacts: Arc::new(Mutex::new(contacts)),
                groups: Arc::new(Mutex::new(groups)),
                distribution_lists: Arc::new(Mutex::new(distribution_lists)),
                poll_opens: Arc::new(Mutex::new(HashMap::new())),
                parameters,
            };
            let identity_data = IdentityData {
                user_identity: example_data_provider.user_identity,
                client_key: ClientKey::from([1; ClientKey::LENGTH]),
                csp_server_group: ChatServerGroup::try_from("a").expect("Chat Server Group should be valid"),
            };

            Ok((example_data_provider, identity_data))
        }

        fn generate_contacts(
            random_number_generator: &mut RandomNumberGenerator,
            number: usize,
        ) -> anyhow::Result<BTreeMap<ThreemaId, protobuf::d2d_sync::contact::AcquaintanceLevel>> {
            (0..number)
                .map(|index| {
                    Ok((
                        ThreemaId::try_from(format!("{index:08X}").as_str())?,
                        *random_number_generator
                            .random_choice(&[
                                protobuf::d2d_sync::contact::AcquaintanceLevel::Direct,
                                protobuf::d2d_sync::contact::AcquaintanceLevel::GroupOrDeleted,
                            ])
                            .expect("Acquaintance level choice set non-empty"),
                    ))
                })
                .collect()
        }

        fn generate_distribution_lists(
            random_number_generator: &mut RandomNumberGenerator,
            contacts: &[ThreemaId],
            number: usize,
        ) -> BTreeMap<DistributionListId, HashSet<ThreemaId>> {
            if contacts.is_empty() {
                warn!("Cannot generate a distribution list without any contacts");
                return BTreeMap::new();
            }
            (0..number)
                .map(|index| {
                    let n_members = random_number_generator.random_in_range(1..=256);
                    let members = random_number_generator
                        .random_choice_multiple(contacts, n_members)
                        .into_iter()
                        .collect();
                    (DistributionListId(index as u64), members)
                })
                .collect()
        }

        fn generate_groups(
            random_number_generator: &mut RandomNumberGenerator,
            user_identity: ThreemaId,
            contacts: &[ThreemaId],
            number: usize,
        ) -> BTreeMap<GroupIdentity, HashSet<ThreemaId>> {
            (0..number)
                .map(|index| {
                    let creator = if index.is_multiple_of(2) {
                        user_identity
                    } else {
                        *random_number_generator
                            .random_choice(contacts)
                            .unwrap_or(&user_identity)
                    };
                    let n_members = random_number_generator.random_in_range(0..=256);
                    let members = random_number_generator
                        .random_choice_multiple(contacts, n_members)
                        .into_iter()
                        .filter(|contact| *contact != creator)
                        .collect();
                    (
                        GroupIdentity {
                            group_id: index as u64,
                            creator_identity: creator,
                        },
                        members,
                    )
                })
                .collect()
        }

        #[expect(clippy::unused_self, reason = "Follow pattern")]
        pub(super) fn user_profile_provider(&self) -> CreateUserProfileProvider {
            CreateUserProfileProvider
        }

        #[expect(clippy::unused_self, reason = "Follow pattern")]
        pub(super) fn settings_provider(&self) -> CreateSettingsProvider {
            CreateSettingsProvider
        }

        pub(super) fn contact_provider(&self) -> CreateContactProvider {
            CreateContactProvider {
                user_identity: self.user_identity,
                random_number_generator: Arc::clone(&self.random_number_generator),
                contacts: Arc::clone(&self.contacts),
            }
        }

        pub(super) fn group_provider(&self) -> CreateGroupProvider {
            CreateGroupProvider {
                random_number_generator: Arc::clone(&self.random_number_generator),
                groups: Arc::clone(&self.groups),
            }
        }

        pub(super) fn distribution_list_provider(&self) -> CreateDistributionListProvider {
            CreateDistributionListProvider {
                random_number_generator: Arc::clone(&self.random_number_generator),
                distribution_lists: Arc::clone(&self.distribution_lists),
            }
        }

        pub(super) fn csp_nonce_provider(&self) -> CreateCspNonceProvider {
            CreateCspNonceProvider {
                total_number_of_nonces: self.parameters.csp_nonces,
            }
        }

        pub(super) fn conversation_provider(&self) -> CreateConversationProvider {
            CreateConversationProvider {
                random_number_generator: Arc::clone(&self.random_number_generator),
                user_identity: self.user_identity,
                contacts: Arc::clone(&self.contacts),
                distribution_lists: Arc::clone(&self.distribution_lists),
                groups: Arc::clone(&self.groups),
                poll_open_pool: Arc::clone(&self.poll_opens),
                poll_close_pool: Arc::new(Mutex::new(HashMap::new())),
                total_number_of_contact_messages: self.parameters.contact_messages,
                total_number_of_distribution_list_messages: self.parameters.distribution_list_messages,
                total_number_of_group_messages: self.parameters.group_messages,
            }
        }

        pub(super) fn poll_provider(&self) -> CreatePollProvider {
            CreatePollProvider {
                random_number_generator: Arc::clone(&self.random_number_generator),
                user_identity: self.user_identity,
                contacts: Arc::clone(&self.contacts),
                groups: Arc::clone(&self.groups),
                polls: Arc::clone(&self.poll_opens),
                number_of_polls: self.parameters.polls,
            }
        }
    }
}

/// The parameters defining the amount of instances of each type.
#[derive(Debug, Args)]
pub struct BackupCreateParameters {
    /// Number of contacts.
    #[arg(long, default_value_t = 0)]
    contacts: usize,

    /// Number of distribution lists.
    #[arg(long, default_value_t = 0)]
    distribution_lists: usize,

    /// Number of groups.
    #[arg(long, default_value_t = 0)]
    groups: usize,

    /// Number of polls.
    #[arg(long, default_value_t = 0)]
    polls: usize,

    /// Number of 1:1 messages.
    #[arg(long, default_value_t = 0)]
    contact_messages: usize,

    /// Number of distribution list messages.
    #[arg(long, default_value_t = 0)]
    distribution_list_messages: usize,

    /// Number of group messages.
    #[arg(long, default_value_t = 0)]
    group_messages: usize,

    /// Number of CSP nonces.
    #[arg(long, default_value_t = 0)]
    csp_nonces: usize,

    /// Number of D2M nonces.
    #[arg(long, default_value_t = 0)]
    d2d_nonces: usize,
}

#[derive(Parser)]
#[command()]
struct DataBackupCommand {
    #[command(flatten)]
    config: BackupCreateParameters,

    /// The path where the backup should be written to.
    #[arg(short, long)]
    path: path::PathBuf,

    /// Password used to secure the backup.
    #[arg(long)]
    password: String,

    /// Turn on debugging information.
    /// Use -v -vv to control log level.
    #[arg(short, action = clap::ArgAction::Count)]
    verbose: u8,
}

struct FileWriter {
    path: path::PathBuf,
    file: fs::File,
}
impl FileWriter {
    fn new(path: path::PathBuf) -> anyhow::Result<Self> {
        info!(?path, "Create file");
        Ok(Self {
            path: path.clone(),

            // Note: Don't use the 'append' option as it would overrule the seek position.
            file: fs::OpenOptions::new().create_new(true).write(true).open(path)?,
        })
    }

    #[tracing::instrument(skip_all, fields(length = chunk.len()))]
    fn append(&mut self, chunk: &[u8]) -> anyhow::Result<()> {
        trace!("Append to file");
        let _ = self.file.seek(io::SeekFrom::End(0))?;
        self.file.write_all(chunk)?;
        Ok(())
    }

    #[tracing::instrument(skip_all, fields(offset = offset, length = chunk.len()))]
    fn write_at(&mut self, offset: u64, chunk: &[u8]) -> anyhow::Result<()> {
        trace!("Write at specific position");
        let _ = self.file.seek(io::SeekFrom::Start(offset))?;
        self.file.write_all(chunk)?;
        let _ = self.file.seek(io::SeekFrom::End(0))?;

        Ok(())
    }

    fn handle(&mut self, instruction: DataBackupCreateLocalWriteInstruction) -> anyhow::Result<()> {
        match instruction {
            DataBackupCreateLocalWriteInstruction::Append(chunk) => self.append(&chunk)?,
            DataBackupCreateLocalWriteInstruction::WriteAt { offset, data } => {
                self.write_at(offset.0, &data)?;
            },
        }

        Ok(())
    }
}

struct ExampleBackupCreator {
    task: DataBackupCreateTask,
    file_writer: FileWriter,
}

impl ExampleBackupCreator {
    fn new(parameters: BackupCreateParameters, path: &path::Path, password: String) -> anyhow::Result<Self> {
        let (database, identity_data) = CreateDataProvider::new(parameters)?;
        let (task, filename) = {
            let context = DataBackupCreateInit {
                password,
                identity_data,
                providers: DataBackupProviders {
                    user_profile: Arc::new(Mutex::new(database.user_profile_provider())),
                    contacts: Arc::new(Mutex::new(database.contact_provider())),
                    groups: Arc::new(Mutex::new(database.group_provider())),
                    distribution_lists: Arc::new(Mutex::new(database.distribution_list_provider())),
                    conversations: Arc::new(Mutex::new(database.conversation_provider())),
                    csp_nonces: Arc::new(Mutex::new(database.csp_nonce_provider())),
                    settings: Arc::new(Mutex::new(database.settings_provider())),
                    polls: Arc::new(Mutex::new(database.poll_provider())),
                },
            };
            DataBackupCreateTask::new(context)
        };
        let file_writer = FileWriter::new(path.join(filename))?;
        Ok(Self { task, file_writer })
    }

    fn run(mut self) -> anyhow::Result<()> {
        info!("Creating backup");
        loop {
            match self.task.poll()? {
                DataBackupCreateLoop::Instruction(instruction) => {
                    debug!(state_update = ?instruction.state_update);
                    match instruction.write_instruction {
                        Some(DataBackupCreateWriteInstruction::Local(instruction)) => {
                            self.file_writer.handle(instruction)?;
                        },
                        None => {},
                    }
                },

                DataBackupCreateLoop::Done(()) => {
                    info!(path = ?self.file_writer.path, "Backup created successfully");
                    return Ok(());
                },
            }
        }
    }
}

fn main() -> anyhow::Result<()> {
    let arguments = DataBackupCommand::parse();
    // Configure logging.
    let log_level = match arguments.verbose {
        1 => Level::DEBUG,
        2 => Level::TRACE,
        _ => Level::INFO,
    };
    init_stderr_logging(log_level);

    ExampleBackupCreator::new(arguments.config, &arguments.path, arguments.password)?.run()
}

#[test]
fn verify_cli() {
    use clap::CommandFactory;
    DataBackupCommand::command().debug_assert();
}

Messung
CHZG
8310010094

¤ Dauer der Verarbeitung: 0.14 Sekunden  (vorverarbeitet am  2026-11-04) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.