main.rs 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. use anyhow::{bail, Result};
  2. use clap::{Parser, Subcommand};
  3. use std::env;
  4. use std::fs::File;
  5. use std::io::{BufRead, BufReader, Write};
  6. use std::process::Command;
  7. /*
  8. Right now, this use tokio / reqwests async.
  9. I don't need async, change this to normal blocking.
  10. */
  11. pub mod built_info {
  12. include!(concat!(env!("OUT_DIR"), "/built.rs"));
  13. }
  14. fn reqwests_version() -> Option<String> {
  15. for (name, version) in built_info::DEPENDENCIES {
  16. if name == "reqwest" {
  17. return Some(version.to_string());
  18. }
  19. }
  20. None
  21. }
  22. #[derive(Parser)]
  23. #[command(about = "Go updater")]
  24. struct Cli {
  25. #[command(subcommand)]
  26. command: Option<Commands>,
  27. }
  28. #[derive(Subcommand)]
  29. enum Commands {
  30. /// Update go
  31. Update {},
  32. Info {},
  33. }
  34. /// Query `go version`
  35. fn find_go_version() -> Result<String> {
  36. let output = Command::new("go").arg("version").output()?;
  37. if output.status.success() {
  38. // Ok! We have something!
  39. return Ok(String::from_utf8_lossy(output.stdout.as_slice()).to_string());
  40. }
  41. bail!("Failed to query go version.");
  42. }
  43. // or possibly pathsearch or search_path
  44. fn find_go() -> Result<String> {
  45. let output = Command::new("which").arg("go").output()?;
  46. if output.status.success() {
  47. return Ok(String::from_utf8_lossy(output.stdout.as_slice()).to_string());
  48. }
  49. bail!("Failed to locate go binary.");
  50. }
  51. const GO_URL: &str = "https://go.dev/dl/";
  52. const GO_FILE: &str = "go-dl.html";
  53. // 2 MB download of html...
  54. static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
  55. /*
  56. CSS Selector elements of interest: (a href="" part).
  57. href are relative...
  58. div[class="downloadWrapper"] > a[class="download downloadBox"]
  59. <a class="download downloadBox" href="/dl/go1.24.0.windows-amd64.msi">
  60. <a class="download downloadBox" href="/dl/go1.24.0.linux-amd64.tar.gz">
  61. table[class="downloadtable"] > tr[class=" "] > td[class="filename"] > a[class="download"]
  62. Or possibly,
  63. table[class="downloadtable"] > tr[class=" "]
  64. or tr[class="highlight "] ?
  65. and grab the section of td's. class=filename has a href, last has SHA256.
  66. <a class="download" href="/dl/go1.24.0.src.tar.gz">go1.24.0.src.tar.gz</a>
  67. <a class="download" href="/dl/go1.24.0.linux-amd64.tar.gz">go1.24.0.linux-amd64.tar.gz</a>
  68. <a class="download" href="/dl/go1.24.0.linux-arm64.tar.gz">go1.24.0.linux-arm64.tar.gz</a>
  69. <a class="download" href="/dl/go1.24.0.windows-amd64.zip">go1.24.0.windows-amd64.zip</a>
  70. <a class="download" href="/dl/go1.24.0.windows-amd64.msi">go1.24.0.windows-amd64.msi</a>
  71. */
  72. fn get_go_downloads() -> Result<()> {
  73. // Result<(), Box<dyn std::error::Error>> {
  74. // save the file?
  75. let client = reqwest::blocking::Client::builder()
  76. .user_agent(APP_USER_AGENT)
  77. .build()?;
  78. print!("Downloading: {GO_URL} ");
  79. let mut resp = client.get(GO_URL).send()?;
  80. if resp.status().is_success() {
  81. // Yuck!
  82. // println!("{}", resp.text().await?);
  83. // https://webscraping.ai/faq/reqwest/what-is-the-best-way-to-handle-large-file-downloads-with-reqwest
  84. let mut file = File::create(GO_FILE)?;
  85. resp.copy_to(&mut file)?;
  86. /*
  87. while let Some(chunk) = resp.chunk()? {
  88. file.write_all(&chunk)?;
  89. }
  90. */
  91. } else {
  92. panic!("Failed: {:?}", resp.status());
  93. }
  94. println!("OK");
  95. Ok(())
  96. }
  97. // use std::fmt;
  98. use std::error::Error;
  99. /*
  100. #[derive(Debug)]
  101. struct ArchNotFoundError {}
  102. impl fmt::Display for ArchNotFoundError {
  103. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  104. write!(f, "Unable to locate architecture download link")
  105. }
  106. }
  107. */
  108. /// find_link for given arch (architecture)
  109. fn find_link(arch: &str) -> Result<String, Box<dyn Error>> {
  110. let fp = File::open(GO_FILE)?;
  111. let reader = BufReader::new(fp);
  112. for line in reader.lines() {
  113. if let Ok(line) = line {
  114. if line.contains("a class=\"download\"") {
  115. if line.contains(arch) {
  116. // Ok, return just the link part of this.
  117. // NOTE: Relative path...
  118. return Ok(line);
  119. }
  120. }
  121. }
  122. }
  123. Err(Box::from("Unable to locate architecture download link"))
  124. }
  125. fn main() -> Result<()> {
  126. let cli = Cli::parse();
  127. // Get go environment
  128. let go_path = env::var("GOPATH").unwrap_or(String::new());
  129. let go_root = env::var("GOROOT").unwrap_or(String::new());
  130. let go_version: String;
  131. if let Ok(version) = find_go_version() {
  132. go_version = version.as_str().trim().to_string();
  133. } else {
  134. panic!("I wasn't able to locate go. I need `go version` to know what arch to dl.");
  135. }
  136. // let go_version = go_version().await;
  137. let go_where: String;
  138. if let Ok(location) = find_go() {
  139. go_where = location.as_str().trim().to_string();
  140. } else {
  141. panic!("I wasn't able to locate the go binary.");
  142. }
  143. // Get arch
  144. let parts = go_version.split(" ");
  145. let mut arch = parts.last().unwrap().to_string();
  146. arch = arch.replace("/", "-");
  147. // println!("{:?}", built_info::DEPENDENCIES);
  148. println!("Reqwest version: {:?}", reqwests_version());
  149. println!("path {} / root {}", go_path, go_root);
  150. println!("version: {}", go_version);
  151. println!("where: {}", go_where);
  152. println!("arch: {}", arch);
  153. // println!("Result: {:?}", get_go_downloads().await );
  154. // Get go version and path
  155. match &cli.command {
  156. Some(Commands::Update {}) => {
  157. get_go_downloads()?;
  158. let link = find_link(&arch);
  159. println!("{:?}", link);
  160. }
  161. Some(Commands::Info {}) => {}
  162. None => {
  163. // Display help.
  164. let _show_help: Cli = Cli::parse_from(["--help"]);
  165. }
  166. }
  167. Ok(())
  168. }