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