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 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 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 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}