example_backups/
main.rs

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