1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
pub mod client;
pub mod http_client;

use crate::{receiver::client as receiver, sender::util::replace_protocol};
use anyhow::{anyhow, Result};

use tokio_tungstenite::{
    connect_async,
    tungstenite::{client::IntoClientRequest, http::HeaderValue},
};
use tracing::{debug, error};

/// Start the receiver process.
///
/// This function initiates the receiver process by performing the following steps:
/// 1. Replaces the protocol of the given `relay` URL.
/// 2. Downloads the room information from the server.
/// 3. Connects to the local or relay server based on the platform.
/// 4. Downloads the file from the server.
///
/// # Arguments
///
/// * `filepath` - The path to the file to be received.
/// * `relay` - The URL of the relay server.
/// * `name` - The name of the receiver.
///
/// # Returns
///
/// Returns a `Result` indicating the success or failure of the receiver process.
pub async fn start_receiver(filepath: String, relay: &str, name: &str) -> Result<()> {
    let http_url = replace_protocol(relay);
    let res = http_client::download_info(http_url.as_str(), name)
        .await
        .unwrap();
    debug!("Got room_id from Server: {:?}", res);
    let res_ip = String::from("ws://") + res.ip.as_str() + ":9000";

    #[cfg(not(target_os = "android"))]
    if let Err(local_err) = start_ws_com(
        filepath.clone(),
        res_ip.as_str(),
        res.local_room_id.as_str(),
    )
    .await
    {
        debug!("Failed to connect local: {local_err}");
        if let Err(relay_err) = start_ws_com(filepath, relay, res.relay_room_id.as_str()).await {
            debug!("Failed to connect remote: {relay_err}");
        }
    }

    #[cfg(target_os = "android")]
    if let Err(relay_err) = start_ws_com(filepath, relay, res.relay_room_id.as_str()).await {
        debug!("Failed to connect remote: {relay_err}");
    }
    http_client::download_success(http_url.as_str(), name)
        .await
        .map_err(|e| anyhow!("Failed to download success: {}", e))?;

    debug!("Success");
    Ok(())
}

/// Asynchronously starts a WebSocket communication with a relay server.
///
/// # Arguments
///
/// * `filepath` - The path of the file to transfer.
/// * `relay` - The URL of the relay server.
/// * `name` - The name of the receiver.
///
/// # Returns
///
/// Returns a `Result` indicating the success or failure of the WebSocket communication.
pub async fn start_ws_com(filepath: String, relay: &str, name: &str) -> Result<()> {
    // Construct the WebSocket URL by appending "/ws" to the relay URL.
    let url = String::from(relay) + "/ws";

    // Create a WebSocket request using the constructed URL.
    let mut request = url
        .into_client_request()
        .map_err(|e| anyhow!("Failed to create request: {}", e))?;

    // Set the "Origin" header of the request to the relay URL.
    request
        .headers_mut()
        .insert("Origin", HeaderValue::from_str(relay).unwrap());

    // Print a message indicating the attempt to connect.
    println!("Attempting to connect...");

    // Attempt to establish a WebSocket connection with the relay server.
    // If the connection fails or times out, return an error.
    let _ = match tokio::time::timeout(std::time::Duration::from_secs(5), connect_async(request))
        .await
    {
        Ok(Ok((socket, _))) => {
            // Start the receiver process with the established WebSocket connection.
            receiver::start(filepath, socket, name).await;
            Ok(())
        }
        Ok(Err(e)) => {
            // Log the failure to connect.
            error!("Error: Failed to connect: {e:?}");
            Err(Box::new(e))
        }
        Err(e) => {
            // Log the timeout.
            error!("Error: Timeout reached for local connection attempt");
            Err(Box::new(e))
        }?,
    };
    Ok(())
}