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#[derive(Parser, Debug)]
18struct Cli {
19 #[clap(value_parser)]
21 homeserver: Url,
22
23 #[clap(value_parser)]
25 user_name: String,
26
27 #[clap(value_parser)]
29 password: String,
30
31 #[clap(short, long)]
33 proxy: Option<Url>,
34
35 #[clap(short, long, action)]
37 verbose: bool,
38
39 #[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}