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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
use std::collections::HashMap;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::PathBuf;
use std::sync::Arc;

use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use minimo::showln;
use reqwest::header;
use reqwest::{Client, Response};
use tokio::sync::Mutex;
use tokio::task;

const ONE_KB: u64 = 1024;

pub struct Download {
    pub url: String,
    pub filename: String,
    pub memory: u64,
    pub threads: u64,
    pub network: Network,
    pub progress: Progress,
}

impl Default for Download {
    fn default() -> Download {
        Download {
            url: "".to_string(),
            filename: "".to_string(),
            memory: 256,
            threads: 1,
            network: Network {
                ..Default::default()
            },
            progress: Progress {
                ..Default::default()
            },
        }
    }
}

impl Download {
    pub async fn get(self) {
        let content_length_resp = self.network.get_content_length(&self.url).await;

        match content_length_resp {
            Some(content_length) => {
                let children = self.spawn_threads(content_length).await;
                futures::future::join_all(children).await;
            }
            None => println!("Content length is not present for this URL. Support for this type of hosted file will be added in the future."),
        }
    }

    fn calculate_ranges(
        threads: u64,
        content_length: u64,
        max_buffer_size: u64,
        mut progress: Progress,
    ) -> (Progress, Vec<(String, u64, u64, u64, u64)>) {
        let mut range_start = 0;
        let mut ranges = vec![];
        let chunk_size = content_length / threads - 1;

        // println!("Processing ranges: ");
        for thread in 0..threads {
            let mut range_end = chunk_size + range_start;
            if thread == (threads - 1) {
                range_end = content_length;
            }

            let thread_number = thread + 1;
            let range: String = format!("bytes={}-{}", range_start, range_end);
            let range_to_process: u64 = range_end - range_start;
            let buffer_chunks: u64 = range_to_process / max_buffer_size;
            let chunk_remainder: u64 = range_to_process % max_buffer_size;

            // println!("   Thread: {}, range: {}, chunks: {}, chunk_remainder: {}", thread_number, range, buffer_chunks, chunk_remainder);
            // showln!(white, "♾️ ",yellow_bold,thread_number,red_bold,"→",yellow_bold,range,white,"chunks:",yellow_bold,buffer_chunks,white,"remainder:",yellow_bold,chunk_remainder);
            ranges.push((range, range_start, thread_number, buffer_chunks, chunk_remainder));
            progress.add(range_to_process, &thread_number);

            range_start = range_start + chunk_size + 1;
        }
        (progress, ranges)
    }

    async fn spawn_threads(self, content_length: u64) -> Vec<task::JoinHandle<()>> {
        let max_buffer_size = ONE_KB * self.memory;
        let (progress, ranges) = Self::calculate_ranges(self.threads, content_length, max_buffer_size, self.progress);
    
        let progress_arc = Arc::new(Mutex::new(progress));
        let network_arc = Arc::new(self.network);
        let filename_arc =  Arc::new(self.filename);
    
        // println!("Spawning Threads...");
        showln!(yellow_bold, "╭─ 📦 ",cyan_bold,&filename_arc);
        let mut children = vec![];
        for (range, range_start, thread_number, buffer_chunks, chunk_remainder) in ranges {
            let network_ref = network_arc.clone();
            let progress_ref = progress_arc.clone();
            let filename_ref = filename_arc.clone();
            let url_ref = self.url.clone();
    
            children.push(task::spawn(async move {
                let mut file_handle = File::create(PathBuf::from( filename_ref.as_str())).unwrap();
                file_handle.seek(SeekFrom::Start(range_start)).unwrap();
    
                let mut file_range_resp = network_ref.make_request(&url_ref, range).await;
    
                while let Some(chunk) = file_range_resp.chunk().await.unwrap() {
                    file_handle.write_all(&chunk).unwrap();
                    file_handle.flush().unwrap();
                    progress_ref.lock().await.inc(chunk.len() as u64, &thread_number);
                }
    
                if chunk_remainder != 0 {
                    progress_ref.lock().await.set_position(chunk_remainder, &thread_number);
                }
                progress_ref.lock().await.finish(&thread_number);
            }));
        }
        progress_arc.lock().await.join_and_clear();
        children
    }
    
}

pub struct Network {
    pub client: Client,
}

impl Default for Network {
    fn default() -> Network {
        Network {
            client: Client::new(),
        }
    }
}

impl Network {
    pub async fn make_request(&self, url: &str, range: String) -> Response {
        self.client
            .get(url)
            .header(header::RANGE, range)
            .send()
            .await
            .expect("Could not send request.")
    }

    pub async fn get_content_length(&self, url: &str) -> Option<u64> {
        self.make_request(url, "".to_string()).await.content_length()
    }
}

pub struct Progress {
    pub multi_progress: MultiProgress,
    pub progress_bars: HashMap<u64, ProgressBar>,
}

impl Default for Progress {
    fn default() -> Progress {
        Progress {
            multi_progress: MultiProgress::new(),
            progress_bars: HashMap::new(),
        }
    }
}

impl Progress {
    pub fn add(&mut self, range: u64, thread_number: &u64) {
        let pb = self.multi_progress.add(ProgressBar::new(range));

        let style = ProgressStyle::default_bar()
            // spinner | 2 digit percent in yellow | 20 wide bar with white fill | 4 digit byte | 2 digit eta
            .template("|{percent:3}% {spinner:.white}|{bar:35.white/white}| {bytes:2} | {eta:2}")
            .unwrap()
            //use pythons progressbar style
            .progress_chars("█▓▒  ")
            //downloading style
            .tick_chars("⣿⠿⠟⢿⡿⣻⣽⣾⣷⣯⣟⡿⢿⠿⠻⠟⠋⠙⠹⡁⢁⠄⠂⠂")
          ;
        pb.set_style(style);
        self.progress_bars.insert(*thread_number, pb);
    }

    pub fn inc(&self, amount: u64, thread_number: &u64) {
        if let Some(pb) = self.progress_bars.get(thread_number) {
            pb.inc(amount);
        }
    }

    pub fn set_position(&self, amount: u64, thread_number: &u64) {
        if let Some(pb) = self.progress_bars.get(thread_number) {
            pb.set_position(amount);
        }
    }

    pub fn finish(&self, thread_number: &u64) {
        if let Some(pb) = self.progress_bars.get(thread_number) {
            pb.finish_with_message("🚀 Done!");
        }
    }

    pub fn join_and_clear(&self) {
        self.multi_progress.clear().unwrap();
    }
}