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
use futures_util::StreamExt;
use indicatif::{ProgressBar, ProgressStyle};
use regex::Regex;
use reqwest::Client;
use std::cmp::min;
use std::fs::File;
use std::io::Write;
use std::process::Command;

use crate::error::UpdaterError;

mod error;

pub type Result<T> = std::result::Result<T, UpdaterError>;

const NIGHTLY_URL: &str = "https://github.com/neovim/neovim/releases/tag/nightly";
const DOWNLOAD_URL: &str =
    "https://github.com/neovim/neovim/releases/download/nightly/nvim.appimage";

pub async fn download(client: &Client, path: &str) -> Result<()> {
    let response = match client.get(DOWNLOAD_URL).send().await {
        Ok(response) => response,
        Err(e) => return Err(UpdaterError::Http(e)),
    };
    let total_size = match response.content_length() {
        Some(length) => length,
        None => return Err(UpdaterError::Base),
    };

    // Setup progress bar
    let pb = ProgressBar::new(total_size);
    let style = "{msg}\n[{elapsed_precise}] [{bar:.green}] {bytes}/{total_bytes} [ETA: {eta}] [speed: {bytes_per_sec}]";
    pb.set_style(ProgressStyle::default_bar().template(style).unwrap());
    pb.set_message(format!("Downloading {}", DOWNLOAD_URL));

    // Setup output file
    let mut file = match File::create(path) {
        Ok(f) => f,
        Err(_) => return Err(UpdaterError::Base),
    };
    let mut downloaded: u64 = 0;
    let mut stream = response.bytes_stream();

    // Write to fs
    while let Some(item) = stream.next().await {
        let chunk = match item {
            Ok(c) => c,
            Err(_) => break,
        };
        match file.write_all(&chunk) {
            Ok(()) => (),
            Err(_) => break,
        };
        let new = min(downloaded + (chunk.len() as u64), total_size);
        downloaded = new;
        pb.set_position(new);
    }

    if downloaded != total_size {
        return Err(UpdaterError::Base);
    }

    pb.finish();
    Ok(())
}

pub async fn fetch_latest_version(client: &Client) -> Result<String> {
    let response = match client.get(NIGHTLY_URL).send().await {
        Ok(response) => response,
        Err(e) => return Err(UpdaterError::Http(e)),
    };

    let text = match response.text().await {
        Ok(text) => text,
        Err(e) => return Err(UpdaterError::Http(e)),
    };

    Ok(text)
}

pub async fn fetch_current_version() -> Result<String> {
    let output = match Command::new("nvim").arg("--version").output() {
        Ok(out) => out,
        Err(_) => return Err(UpdaterError::Base),
    };

    match String::from_utf8(output.stdout) {
        Ok(s) => Ok(s),
        Err(e) => Err(UpdaterError::StringErr(e)),
    }
}

pub fn get_version(content: String) -> Option<String> {
    let re = match Regex::new(r"NVIM v(?P<Version>[.\d]+)-dev-\d+.g(?P<Commit>\w{9})") {
        Ok(regex) => regex,
        Err(_) => return None,
    };
    let captures = match re.captures(&content) {
        Some(capts) => capts,
        None => return None,
    };
    let version = match captures.name("Version") {
        Some(v) => v,
        None => return None,
    };
    let commit = match captures.name("Commit") {
        Some(v) => v,
        None => return None,
    };

    let full_version = format!("{} - {}", version.as_str(), commit.as_str());
    return Some(full_version);
}

#[cfg(test)]
mod test {
    use std::{env, fs};

    use super::*;

    #[test]
    fn test_get_local_version() {
        let root = env::var("CARGO_MANIFEST_DIR").unwrap();
        let content = fs::read_to_string(root + "/samples/local.txt").unwrap();
        let version = get_version(content);
        assert_ne!(version, None);
        assert_eq!(version.unwrap(), "0.8.0 - 8952def50");
    }

    #[test]
    fn test_get_remote_version() {
        let root = env::var("CARGO_MANIFEST_DIR").unwrap();
        let content = fs::read_to_string(root + "/samples/remote.html").unwrap();
        let version = get_version(content);
        assert_ne!(version, None);
        assert_eq!(version.unwrap(), "0.8.0 - 8952def50");
    }
}