nvim_updater_rs/
lib.rs

1use futures_util::StreamExt;
2use indicatif::{ProgressBar, ProgressStyle};
3use regex::Regex;
4use reqwest::Client;
5use std::cmp::min;
6use std::fs::File;
7use std::io::Write;
8use std::process::Command;
9
10use crate::error::UpdaterError;
11
12mod error;
13
14pub type Result<T> = std::result::Result<T, UpdaterError>;
15
16const NIGHTLY_URL: &str = "https://github.com/neovim/neovim/releases/tag/nightly";
17const DOWNLOAD_URL: &str =
18    "https://github.com/neovim/neovim/releases/download/nightly/nvim.appimage";
19
20pub async fn download(client: &Client, path: &str) -> Result<()> {
21    let response = match client.get(DOWNLOAD_URL).send().await {
22        Ok(response) => response,
23        Err(e) => return Err(UpdaterError::Http(e)),
24    };
25    let total_size = match response.content_length() {
26        Some(length) => length,
27        None => return Err(UpdaterError::Base),
28    };
29
30    // Setup progress bar
31    let pb = ProgressBar::new(total_size);
32    let style = "{msg}\n[{elapsed_precise}] [{bar:.green}] {bytes}/{total_bytes} [ETA: {eta}] [speed: {bytes_per_sec}]";
33    pb.set_style(ProgressStyle::default_bar().template(style).unwrap());
34    pb.set_message(format!("Downloading {}", DOWNLOAD_URL));
35
36    // Setup output file
37    let mut file = match File::create(path) {
38        Ok(f) => f,
39        Err(_) => return Err(UpdaterError::Base),
40    };
41    let mut downloaded: u64 = 0;
42    let mut stream = response.bytes_stream();
43
44    // Write to fs
45    while let Some(item) = stream.next().await {
46        let chunk = match item {
47            Ok(c) => c,
48            Err(_) => break,
49        };
50        match file.write_all(&chunk) {
51            Ok(()) => (),
52            Err(_) => break,
53        };
54        let new = min(downloaded + (chunk.len() as u64), total_size);
55        downloaded = new;
56        pb.set_position(new);
57    }
58
59    if downloaded != total_size {
60        return Err(UpdaterError::Base);
61    }
62
63    pb.finish();
64    Ok(())
65}
66
67pub async fn fetch_latest_version(client: &Client) -> Result<String> {
68    let response = match client.get(NIGHTLY_URL).send().await {
69        Ok(response) => response,
70        Err(e) => return Err(UpdaterError::Http(e)),
71    };
72
73    let text = match response.text().await {
74        Ok(text) => text,
75        Err(e) => return Err(UpdaterError::Http(e)),
76    };
77
78    Ok(text)
79}
80
81pub async fn fetch_current_version() -> Result<String> {
82    let output = match Command::new("nvim").arg("--version").output() {
83        Ok(out) => out,
84        Err(_) => return Err(UpdaterError::Base),
85    };
86
87    match String::from_utf8(output.stdout) {
88        Ok(s) => Ok(s),
89        Err(e) => Err(UpdaterError::StringErr(e)),
90    }
91}
92
93pub fn get_version(content: String) -> Option<String> {
94    let re = match Regex::new(r"NVIM v(?P<Version>[.\d]+)-dev-\d+.g(?P<Commit>\w{9})") {
95        Ok(regex) => regex,
96        Err(_) => return None,
97    };
98    let captures = match re.captures(&content) {
99        Some(capts) => capts,
100        None => return None,
101    };
102    let version = match captures.name("Version") {
103        Some(v) => v,
104        None => return None,
105    };
106    let commit = match captures.name("Commit") {
107        Some(v) => v,
108        None => return None,
109    };
110
111    let full_version = format!("{} - {}", version.as_str(), commit.as_str());
112    return Some(full_version);
113}
114
115#[cfg(test)]
116mod test {
117    use std::{env, fs};
118
119    use super::*;
120
121    #[test]
122    fn test_get_local_version() {
123        let root = env::var("CARGO_MANIFEST_DIR").unwrap();
124        let content = fs::read_to_string(root + "/samples/local.txt").unwrap();
125        let version = get_version(content);
126        assert_ne!(version, None);
127        assert_eq!(version.unwrap(), "0.8.0 - 8952def50");
128    }
129
130    #[test]
131    fn test_get_remote_version() {
132        let root = env::var("CARGO_MANIFEST_DIR").unwrap();
133        let content = fs::read_to_string(root + "/samples/remote.html").unwrap();
134        let version = get_version(content);
135        assert_ne!(version, None);
136        assert_eq!(version.unwrap(), "0.8.0 - 8952def50");
137    }
138}