Skip to main content

example_backups/
main.rs

1#![recursion_limit = "256"]
2
3use std::pin::pin;
4
5use anyhow::Result;
6use clap::Parser;
7use futures_util::StreamExt;
8use matrix_sdk::{
9    Client,
10    config::SyncSettings,
11    encryption::{backups::BackupState, secret_storage::SecretStore},
12};
13use url::Url;
14
15/// A command line example showcasing how to resume backups by importing the
16/// backup key from secret storage.
17#[derive(Parser, Debug)]
18struct Cli {
19    /// The homeserver to connect to.
20    #[clap(value_parser)]
21    homeserver: Url,
22
23    /// The user ID that should be used to restore the session.
24    #[clap(value_parser)]
25    user_name: String,
26
27    /// The password that should be used for the login.
28    #[clap(value_parser)]
29    password: String,
30
31    /// Set the proxy that should be used for the connection.
32    #[clap(short, long)]
33    proxy: Option<Url>,
34
35    /// Enable verbose logging output.
36    #[clap(short, long, action)]
37    verbose: bool,
38
39    /// The secret storage key, this key will be used to open the secret-store.
40    #[clap(long, action)]
41    secret_store_key: String,
42}
43
44async fn import_known_secrets(client: &Client, secret_store: SecretStore) -> Result<()> {
45    secret_store.import_secrets().await?;
46
47    let status = client
48        .encryption()
49        .cross_signing_status()
50        .await
51        .expect("We should be able to get our cross-signing status");
52
53    if status.is_complete() {
54        println!("Successfully imported all the cross-signing keys");
55    } else {
56        eprintln!("Couldn't import all the cross-signing keys: {status:?}");
57    }
58
59    Ok(())
60}
61
62async fn login(cli: &Cli) -> Result<Client> {
63    let builder = Client::builder().homeserver_url(&cli.homeserver);
64
65    let builder = if let Some(proxy) = &cli.proxy { builder.proxy(proxy) } else { builder };
66
67    let client = builder.build().await?;
68
69    client
70        .matrix_auth()
71        .login_username(&cli.user_name, &cli.password)
72        .initial_device_display_name("rust-sdk")
73        .await?;
74
75    Ok(client)
76}
77
78async fn listen_for_backup_state_changes(client: Client) {
79    let stream = client.encryption().backups().state_stream();
80    let mut stream = pin!(stream);
81
82    while let Some(state) = stream.next().await {
83        let Ok(state) = state else { panic!("Error while receiving backup state updates") };
84
85        match state {
86            BackupState::Unknown => (),
87            BackupState::Enabling => println!("Trying to enable backups"),
88            BackupState::Resuming => println!("Trying to resume backups"),
89            BackupState::Enabled => println!("Backups have been successfully enabled"),
90            BackupState::Downloading => println!("Downloading the room keys from the backup"),
91            BackupState::Disabling => println!("Disabling the backup"),
92            BackupState::Creating => println!("Trying to create a new backup"),
93        }
94    }
95}
96
97#[tokio::main]
98async fn main() -> Result<()> {
99    let cli = Cli::parse();
100
101    if cli.verbose {
102        tracing_subscriber::fmt::init();
103    }
104
105    let client = login(&cli).await?;
106
107    client.sync_once(Default::default()).await?;
108
109    let secret_store =
110        client.encryption().secret_storage().open_secret_store(&cli.secret_store_key).await?;
111
112    let _task = tokio::spawn({
113        let client = client.clone();
114        async move { listen_for_backup_state_changes(client).await }
115    });
116
117    import_known_secrets(&client, secret_store).await?;
118
119    loop {
120        if let Err(e) = client.sync(SyncSettings::new()).await {
121            eprintln!("Error syncing: {e:?}")
122        }
123    }
124}