veloren_voxygen/menu/main/
client_init.rs

1use client::{
2    Client, ClientInitStage, ServerInfo,
3    addr::ConnectionArgs,
4    error::{Error as ClientError, NetworkConnectError, NetworkError},
5};
6use common_net::msg::ClientType;
7use crossbeam_channel::{Receiver, Sender, TryRecvError, unbounded};
8use std::{
9    path::Path,
10    sync::{
11        Arc,
12        atomic::{AtomicBool, Ordering},
13    },
14    time::Duration,
15};
16use tokio::runtime;
17use tracing::{trace, warn};
18
19#[derive(Debug)]
20#[expect(clippy::enum_variant_names)] //TODO: evaluate ClientError ends with Enum name
21pub enum Error {
22    ClientError {
23        error: ClientError,
24        mismatched_server_info: Option<ServerInfo>,
25    },
26    ClientCrashed,
27    ServerNotFound,
28}
29
30pub enum Msg {
31    IsAuthTrusted(String),
32    Done(Result<Client, Error>),
33}
34
35pub struct AuthTrust(String, bool);
36
37// Used to asynchronously parse the server address, resolve host names,
38// and create the client (which involves establishing a connection to the
39// server).
40pub struct ClientInit {
41    rx: Receiver<Msg>,
42    stage_rx: Receiver<ClientInitStage>,
43    trust_tx: Sender<AuthTrust>,
44    cancel: Arc<AtomicBool>,
45}
46impl ClientInit {
47    pub fn new(
48        connection_args: ConnectionArgs,
49        username: String,
50        password: String,
51        runtime: Arc<runtime::Runtime>,
52        locale: Option<String>,
53        config_dir: &Path,
54        client_type: ClientType,
55    ) -> Self {
56        let (tx, rx) = unbounded();
57        let (trust_tx, trust_rx) = unbounded();
58        let (init_stage_tx, init_stage_rx) = unbounded();
59        let cancel = Arc::new(AtomicBool::new(false));
60        let cancel2 = Arc::clone(&cancel);
61
62        let runtime2 = Arc::clone(&runtime);
63        let config_dir = config_dir.to_path_buf();
64
65        runtime.spawn(async move {
66            let trust_fn = |auth_server: &str| {
67                let _ = tx.send(Msg::IsAuthTrusted(auth_server.to_string()));
68                trust_rx
69                    .recv()
70                    .map(|AuthTrust(server, trust)| trust && server == *auth_server)
71                    .unwrap_or(false)
72            };
73
74            let mut last_err = None;
75
76            const FOUR_MINUTES_RETRIES: u64 = 48;
77            'tries: for _ in 0..FOUR_MINUTES_RETRIES {
78                if cancel2.load(Ordering::Relaxed) {
79                    break;
80                }
81                let mut mismatched_server_info = None;
82                match Client::new(
83                    connection_args.clone(),
84                    Arc::clone(&runtime2),
85                    &mut mismatched_server_info,
86                    &username,
87                    &password,
88                    locale.clone(),
89                    trust_fn,
90                    &|stage| {
91                        let _ = init_stage_tx.send(stage);
92                    },
93                    crate::ecs::sys::add_local_systems,
94                    config_dir.clone(),
95                    client_type,
96                )
97                .await
98                {
99                    Ok(client) => {
100                        let _ = tx.send(Msg::Done(Ok(client)));
101                        tokio::task::block_in_place(move || drop(runtime2));
102                        return;
103                    },
104                    Err(ClientError::NetworkErr(NetworkError::ConnectFailed(
105                        NetworkConnectError::Io(e),
106                    ))) => {
107                        warn!(?e, "Failed to connect to the server. Retrying...");
108                    },
109                    Err(e) => {
110                        trace!(?e, "Aborting server connection attempt");
111                        last_err = Some(Error::ClientError {
112                            error: e,
113                            mismatched_server_info,
114                        });
115                        break 'tries;
116                    },
117                }
118                tokio::time::sleep(Duration::from_secs(5)).await;
119            }
120
121            // Parsing/host name resolution successful but no connection succeeded
122            // If last_err is None this typically means there was no server up at the input
123            // address and all the attempts timed out.
124            let _ = tx.send(Msg::Done(Err(last_err.unwrap_or(Error::ServerNotFound))));
125
126            // Safe drop runtime
127            tokio::task::block_in_place(move || drop(runtime2));
128        });
129
130        ClientInit {
131            rx,
132            stage_rx: init_stage_rx,
133            trust_tx,
134            cancel,
135        }
136    }
137
138    /// Poll if the thread is complete.
139    /// Returns None if the thread is still running, otherwise returns the
140    /// Result of client creation.
141    pub fn poll(&self) -> Option<Msg> {
142        match self.rx.try_recv() {
143            Ok(msg) => Some(msg),
144            Err(TryRecvError::Empty) => None,
145            Err(TryRecvError::Disconnected) => Some(Msg::Done(Err(Error::ClientCrashed))),
146        }
147    }
148
149    /// Poll for connection stage updates from the client
150    pub fn stage_update(&self) -> Option<ClientInitStage> { self.stage_rx.try_recv().ok() }
151
152    /// Report trust status of auth server
153    pub fn auth_trust(&self, auth_server: String, trusted: bool) {
154        let _ = self.trust_tx.send(AuthTrust(auth_server, trusted));
155    }
156
157    pub fn cancel(&mut self) { self.cancel.store(true, Ordering::Relaxed); }
158}
159
160impl Drop for ClientInit {
161    fn drop(&mut self) { self.cancel(); }
162}