Browse Source

Updated to using `go env`.

Code cleanup, `use std::fs::File`, etc.
Add helper functions.
Steve Thielemann 1 week ago
parent
commit
b227b648e2
5 changed files with 376 additions and 95 deletions
  1. 127 0
      Cargo.lock
  2. 3 0
      Cargo.toml
  3. 20 0
      NOTES.md
  4. 147 44
      src/cache.rs
  5. 79 51
      src/main.rs

+ 127 - 0
Cargo.lock

@@ -174,6 +174,37 @@ version = "1.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
 
+[[package]]
+name = "camino"
+version = "1.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo-platform"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo_metadata"
+version = "0.14.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa"
+dependencies = [
+ "camino",
+ "cargo-platform",
+ "semver",
+ "serde",
+ "serde_json",
+]
+
 [[package]]
 name = "cc"
 version = "1.2.17"
@@ -428,6 +459,7 @@ dependencies = [
  "anyhow",
  "clap",
  "reqwest",
+ "testdir",
  "url",
 ]
 
@@ -824,6 +856,15 @@ dependencies = [
  "tempfile",
 ]
 
+[[package]]
+name = "ntapi"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
+dependencies = [
+ "winapi",
+]
+
 [[package]]
 name = "object"
 version = "0.36.7"
@@ -931,6 +972,15 @@ version = "5.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
 
+[[package]]
+name = "redox_syscall"
+version = "0.5.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
+dependencies = [
+ "bitflags",
+]
+
 [[package]]
 name = "reqwest"
 version = "0.12.15"
@@ -1094,6 +1144,15 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "semver"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
+dependencies = [
+ "serde",
+]
+
 [[package]]
 name = "serde"
 version = "1.0.219"
@@ -1218,6 +1277,20 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "sysinfo"
+version = "0.26.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c18a6156d1f27a9592ee18c1a846ca8dd5c258b7179fc193ae87c74ebb666f5"
+dependencies = [
+ "cfg-if",
+ "core-foundation-sys",
+ "libc",
+ "ntapi",
+ "once_cell",
+ "winapi",
+]
+
 [[package]]
 name = "system-configuration"
 version = "0.6.1"
@@ -1252,6 +1325,21 @@ dependencies = [
  "windows-sys 0.59.0",
 ]
 
+[[package]]
+name = "testdir"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9ffa013be124f7e8e648876190de818e3a87088ed97ccd414a398b403aec8c8"
+dependencies = [
+ "anyhow",
+ "backtrace",
+ "cargo-platform",
+ "cargo_metadata",
+ "once_cell",
+ "sysinfo",
+ "whoami",
+]
+
 [[package]]
 name = "tinystr"
 version = "0.7.6"
@@ -1433,6 +1521,12 @@ dependencies = [
  "wit-bindgen-rt",
 ]
 
+[[package]]
+name = "wasite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
+
 [[package]]
 name = "wasm-bindgen"
 version = "0.2.100"
@@ -1514,6 +1608,39 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "whoami"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7"
+dependencies = [
+ "redox_syscall",
+ "wasite",
+ "web-sys",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
 [[package]]
 name = "windows-link"
 version = "0.1.1"

+ 3 - 0
Cargo.toml

@@ -8,3 +8,6 @@ anyhow = "1.0.97"
 clap = { version = "4.5.34", features = ["derive"] }
 reqwest = { version = "0.12.15", features = ["blocking", "brotli", "deflate", "gzip"] }
 url = "2.5.4"
+
+[dev-dependencies]
+testdir = "0.9.3"

+ 20 - 0
NOTES.md

@@ -1,8 +1,18 @@
+# Go
+
+Ok, it looks like GOROOT is managed by go.  See `go env`.
+GOPATH seems like it needs to be managed still.
+
+I've switched over to using `go env` for my information
+(OS, ARCH, VERSION).  Which seems cleaner and clearer.
+
 # Memory
 
 Ok, memory usage is about 1.8MB peak heap usage. (Tokio & non-async), so
 chunks and write_to work the same.
 
+https://poby.medium.com/memory-profiling-using-valgrind-b30514f35117
+
 ```valgrind --log-file=memory1.log target/release/go-up update```
 
 No leaks. ;)
@@ -12,4 +22,14 @@ valgrind --tool=massif --stacks=yes --log-file=valgrind3.log target/release/go-u
 massif-visualizer massif.out.2496324 
 ```
 
+## Valgrind memory usage
 
+Logging total memory usage:
+```cargo build --release; valgrind --log-file=memory1.log target/release/go-up update```
+
+```
+==2470578== HEAP SUMMARY:
+==2470578==     in use at exit: 796,398 bytes in 5,858 blocks
+==2470578==   total heap usage: 217,384 allocs, 211,526 frees, 269,243,726 bytes allocated
+==2470578== 
+```

+ 147 - 44
src/cache.rs

@@ -1,10 +1,7 @@
 // use sha256;
 use anyhow::{Context, Result, bail};
-use std::fs;
-use std::fs::remove_file;
-use std::io::BufRead;
-use std::io::BufReader;
-use std::io::Write;
+use std::fs::{File, remove_file, read_dir, create_dir_all};
+use std::io::{BufRead, BufReader, Write};
 use std::path::PathBuf;
 use std::time::{Duration, SystemTime};
 use url::Url;
@@ -13,6 +10,8 @@ use url::Url;
 // #[warn(missing_docs)]
 
 /// Convert relate to absolute
+///
+/// This can fail if Url is unable to parse, or Url is unable to join.
 #[must_use]
 pub fn relative_to_absolute(base_url: &str, relative_href: &str) -> Result<String> {
     let base_url = Url::parse(base_url).context(format!("Url::parse({})", base_url))?;
@@ -25,8 +24,11 @@ pub fn relative_to_absolute(base_url: &str, relative_href: &str) -> Result<Strin
 /*
 Or maybe I should just use the replacements only, so I have some
 idea where the file came from?
+
+It is nice having a clean filename go1.24.1.tar.gz in the cache directory.
  */
 
+/*
 /// Extract filename from the end of a URL.
 ///
 /// If this doesn't have a usable path, convert url:
@@ -51,6 +53,7 @@ pub fn filename_from_url(url: &str) -> Result<String> {
     }
     Ok(filename.to_string())
 }
+*/
 
 // Display a number as K, M, G, T, etc.
 // pub fn display_bytes(size: u64) -> String { }
@@ -59,12 +62,16 @@ pub fn filename_from_url(url: &str) -> Result<String> {
 ///
 /// This also stores the url in the file, so I know what URL was called for
 /// this reqwest.
+/// 
+/// It has each item on a single line:
+/// header: value
+/// The first line will be url: (Which is not part of original header.)
 pub fn save_headermap(
     filename: &str,
     url: &str,
     header: &reqwest::header::HeaderMap,
 ) -> Result<()> {
-    let mut fp = std::fs::File::create(filename)?;
+    let mut fp = File::create(filename)?;
 
     fp.write_all(format!("url: {}\n", url).as_bytes())?;
     for (key, value) in header.iter() {
@@ -79,7 +86,7 @@ pub fn save_headermap(
 ///
 /// This will have the url of the original call in the "url" section.
 pub fn load_headermap(filename: &str) -> Result<reqwest::header::HeaderMap> {
-    let fp = std::fs::File::open(filename)?;
+    let fp = File::open(filename)?;
     let mut buffer = BufReader::new(fp);
     let mut line = String::new();
     let mut header = reqwest::header::HeaderMap::new();
@@ -111,7 +118,7 @@ pub fn load_headermap(filename: &str) -> Result<reqwest::header::HeaderMap> {
 pub struct Cache {
     /// Directory where cache is stored
     pub directory: PathBuf,
-    // This is where we select async or blocking.
+    // *This is where we would select async or blocking.*
     /// Reqwest Client
     pub client: reqwest::blocking::Client,
     /// Vector of content-types to accept (empty=all)
@@ -120,15 +127,18 @@ pub struct Cache {
     pub max_size: Option<u64>,
 }
 
+// Should I also have std::io::Errors in here as well?
+// I can have File IO errors. 
+
 /// Status of fetch
 #[allow(dead_code)]
 #[derive(Debug)]
 #[repr(u8)]
 pub enum Status {
     /// File was downloaded.
-    Fetched(std::fs::File) = 0,
+    Fetched(PathBuf),
     /// File was retrieved from cache.
-    Cached(std::fs::File),
+    Cached(PathBuf),
     /// Reqwest error (unable to connect)
     Error(reqwest::Error),
     /// Content-Type wasn't allowed, see Cache.accept.
@@ -154,6 +164,7 @@ application/javascript
 
 // If nothing is given for useragent, we default to the application name and version.
 static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
+static HEADER_EXT: &str = ".header";
 
 impl Cache {
     /// Construct Cache using given directory for caching, and useragent.
@@ -170,7 +181,7 @@ impl Cache {
                 );
             }
         } else {
-            fs::create_dir_all(path).context(format!("Cache dir {}", path.display()))?;
+            create_dir_all(path).context(format!("Create cache dir {}", path.display()))?;
         }
 
         let user_agent = if let Some(ua) = useragent {
@@ -236,11 +247,12 @@ impl Cache {
     ///
     /// Use DirEntry.modified, since it updates when a file is freshened/downloaded.
     /// DirEntry.created isn't updated when file is rewritten.
+    #[allow(dead_code)]
     pub fn expire(&self, age: Duration) -> Result<bool> {
         let now = SystemTime::now();
         let mut result: bool = false;
 
-        for file in std::fs::read_dir(self.directory.as_path())? {
+        for file in read_dir(self.directory.as_path())? {
             let file = file?;
             if let Ok(d) = file.metadata() {
                 if d.is_file() {
@@ -248,7 +260,8 @@ impl Cache {
 
                     let filename = String::from(file.file_name().to_str().unwrap());
 
-                    if filename.ends_with(".header") {
+                    if filename.ends_with(HEADER_EXT) {
+                        // This is a header cache file...
                         if let Ok(modify) = d.modified() {
                             if let Ok(delta) = now.duration_since(modify) {
                                 // println!("expire {} = modified {}", filename, delta.as_secs());
@@ -260,7 +273,9 @@ impl Cache {
                                         println!("RemoveFile {:?}: {}", filepath, e);
                                     }
                                     // Also delete .content !
-                                    filepath.set_extension("content");
+                                    // Which is trickier to find now...
+                                    Self::remove_from_filename(&mut filepath);
+                                    // filepath.set_extension("content");
                                     let r = remove_file(&filepath);
                                     if let Err(e) = r {
                                         println!("RemoveFile {:?}: {}", filepath, e);
@@ -270,7 +285,7 @@ impl Cache {
                                 }
                             }
                         }
-                    }
+                    } 
 
                     /*
                     if let Ok(access) = d.accessed() {
@@ -298,48 +313,101 @@ impl Cache {
         Ok(result)
     }
 
+    /// Given a url, return the filename
+    ///
+    /// The filename might not exist.  It is only the filename
+    /// that would be used for the given url.
+    pub fn filename_for_url(&self, url: &str) -> PathBuf {
+        self.directory
+            .as_path()
+            .join(Self::url_to_basename(url).unwrap())
+    }
+
     /// Given a url, return an open file
     ///
     /// Reading from cache.
     #[allow(dead_code)]
-    pub fn file(&self, url: &str) -> Option<std::fs::File> {
+    pub fn file(&self, url: &str) -> Option<File> {
+        let base = self.filename_for_url(url);
+        /*
         let base = self
             .directory
             .as_path()
             .join(Self::url_to_basename(url).unwrap());
+        */
         if base.exists() {
-            return Some(std::fs::File::open(base).unwrap());
+            return Some(File::open(base).unwrap());
         }
         None
     }
 
+    /// Return filename from pathbuf as String
+    #[allow(dead_code)]
+    #[must_use]
+    fn pathbuf_filename(path: &PathBuf) -> String {
+        path.file_name().unwrap().to_string_lossy().to_string()
+    }
+
+    /// Add to the PathBuf filename
+    ///
+    /// This is different then PathBuf::set_extension
+    /// which replaces everything.
+    fn append_to_filename(path: &mut PathBuf, append: &str) {
+        // Append to the filename.
+        let filename = path.file_name().unwrap().to_string_lossy().to_string() + append;
+        path.set_file_name(filename);
+    }
+
+    /// Remove an extension from the filename.
+    /// 
+    /// Given something.tar.gz.header return something.tar.gz
+    fn remove_from_filename(path: &mut PathBuf) {
+        let filename = Self::pathbuf_filename(path);
+        if let Some(parts) = filename.rsplit_once(".") {
+            path.set_file_name(parts.0);
+        } else {
+            panic!("Unable to locate the trailing extension . from: {}", path.display());
+        }
+    }
+
+    /// Fetch, without using the cache.
+    /// 
+    /// This deletes the .header cache file, which forces a fetch.
+    #[allow(dead_code)]
+    pub fn fetch_nocache(&self, url: &str) -> Status {
+        let mut base = self.filename_for_url(url);
+        Self::append_to_filename(&mut base, HEADER_EXT);
+        if base.exists() {
+            let r = remove_file(&base);
+            // unlink failed - panic.
+            if r.is_err() {
+                panic!("Unlink {base:?}: {r:?}");
+            }
+        }
+        return self.fetch(url);
+    }
+    
     // I'm not sure about using Result<Status> here...
     // It would allow for ? usage.
 
     /// Fetch the URL from the web
     ///
     /// This returns Status, which could be Fetched or Cached copy (among other things).
+    #[must_use]
     pub fn fetch(&self, url: &str) -> Status {
+        let base = self.filename_for_url(url);
+        /*
         let base = self
             .directory
             .as_path()
             .join(Self::url_to_basename(url).unwrap());
-
+        */
         let mut builder = self.client.get(url);
         // Don't send just yet!
         // Set some headers to see if page content has changed.
 
-        // This break things - it destroys the file extension (if given)
-        // base.set_extension("header");
         let mut header_file = base.clone();
-        // Append .header to the filename.
-        let filename = header_file
-            .file_name()
-            .unwrap()
-            .to_string_lossy()
-            .to_string()
-            + ".header";
-        header_file.set_file_name(filename);
+        Self::append_to_filename(&mut header_file, HEADER_EXT);
 
         if header_file.exists() {
             // Ok! We have existing information.  Retrieve it.
@@ -362,17 +430,25 @@ impl Cache {
 
         if result.status() == 304 {
             // Cache hit!
-
+            return Status::Cached(base);
+            /*
             // println!("Cache hit! 304! {}", url);
             // base.set_extension("content");
-            let fp = std::fs::File::open(base.to_str().unwrap()).unwrap();
+            let fp = File::open(base.to_str().unwrap()).unwrap();
             return Status::Cached(fp);
+            */
         }
 
         // Ok!  Success!
         if result.status() == 200 {
             // Success!
 
+            // When caching fails ―
+            //
+            // If content_length (from previous fetch) matches current?
+            // Could we assume it hasn't changed, and just use cache?
+            // Or would that be a big assumption?
+
             // Only check content_length size, if we have been
             // given a max_size.
             if let Some(max_size) = self.max_size {
@@ -404,33 +480,24 @@ impl Cache {
                 }
             }
 
-            // This seems like a lot of work, just to add something.
-            // Maybe I should just change the save_header to take the url?
-            /*
-            // Add URL to the header, so I know what I fetched...
-            let mut header2 = result.headers().clone();
-            let head = reqwest::header::HeaderName::from_bytes(b"URL").unwrap();
-            if let Ok(value) = reqwest::header::HeaderValue::from_str(url) {
-                header2.insert(head, value);
-            }
-            */
-
             let r = save_headermap(header_file.to_str().unwrap(), url, result.headers());
             if r.is_err() {
                 println!("save_headermap: {} {:?}", r.unwrap_err(), header_file);
             }
 
             // base.set_extension("content");
-            let mut fp = std::fs::File::create(base.to_str().unwrap()).unwrap();
+            let mut fp = File::create(base.to_str().unwrap()).unwrap();
             let _ = result.copy_to(&mut fp);
             /*
             while let Ok(Some(chunk)) = result.chunk().await {
                 let _ = fp.write(&chunk);
             }
             */
-
-            let fp = std::fs::File::open(base.to_str().unwrap()).unwrap();
+            return Status::Fetched(base);
+            /*
+            let fp = File::open(base.to_str().unwrap()).unwrap();
             return Status::Fetched(fp);
+            */
         } else {
             // Status error?!
             println!("Error {} {}", result.status(), url);
@@ -438,3 +505,39 @@ impl Cache {
         }
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use testdir::testdir;
+
+    #[test]
+    fn cache_fetch() {
+        let mut dir = testdir!();
+        dir.push("cache");
+
+        // Make a copy of the cache directory PathBuf for verifying paths.
+        let mut t = dir.clone();
+
+        let cache = Cache::new(dir, None).unwrap();
+        let r = cache.fetch("https://httpbin.org/anything");
+
+        t.push("anything");
+
+        if let Status::Fetched(f) = r {
+            assert!(f.exists(), "Cache file exists.");
+            assert_eq!(f, t, "Cache path is what we were expecting.");
+            let mut header_file = t.clone();
+            Cache::append_to_filename(&mut header_file, HEADER_EXT);
+
+            assert!(header_file.exists(), "Cache header file exists.");
+            t.pop();
+            t.push("anything.header");
+            assert_eq!(header_file, t, "Cache header path is what we expected.");
+        } else {
+            panic!("Cache Status is not Status::Fetched, is: {:?}", r);
+        }
+
+        // println!("Dir: {:?}, Status: {:?}", t, r); // r has been partially moved.
+    }
+}

+ 79 - 51
src/main.rs

@@ -1,6 +1,7 @@
-use anyhow::{Result, bail};
+use anyhow::{Context, Result, bail};
 use cache::relative_to_absolute;
 use clap::{Parser, Subcommand};
+use std::collections::HashMap;
 use std::env;
 use std::fs::File;
 use std::io::{BufRead, BufReader};
@@ -47,8 +48,29 @@ enum Commands {
     Info {},
 }
 
+/// Query `go env`
+#[must_use]
+fn get_go_env() -> Result<HashMap<String, String>> {
+    let output = Command::new("go").arg("env").output()?;
+    if output.status.success() {
+        let mut result = HashMap::<String, String>::new();
+        for mut line in String::from_utf8_lossy(output.stdout.as_slice()).split("\n") {
+            line = line.trim();
+            if let Some(parts) = line.split_once("=") {
+                let value = parts.1.trim_matches('\'');
+                result.insert(parts.0.to_string(), value.to_string());
+            }
+        }
+        Ok(result)
+    } else {
+        bail!("Failed to query `go env`");
+    }
+}
+
 /// Query `go version`
 #[must_use]
+#[allow(dead_code)]
+#[deprecated = "Use get_go_env"]
 fn find_go_version() -> Result<String> {
     let output = Command::new("go").arg("version").output()?;
     if output.status.success() {
@@ -179,7 +201,7 @@ fn find_arch_link(arch: &str, fp: &File) -> Result<String> {
     bail!("Unable to locate architecture download link");
 }
 
-/* 
+/*
 /// find_link for given arch (architecture)
 ///
 /// Look for <a class="download" href=""
@@ -203,69 +225,73 @@ fn find_link(arch: &str) -> Result<String> {
 }
 */
 
+/// Get value from given HashMap<String,String>
+/// 
+/// This gives us a String, not a &String, and not an Option.
+fn hashget(hash: &HashMap<String,String>, key: &str) -> String {
+    if let Some(value) = hash.get(key) {
+        return value.clone();
+    }
+    String::new()
+}
+
 fn main() -> Result<()> {
     let cli = Cli::parse();
 
+    /*
+    GOROOT, defaults to $HOME/go
+    This seems to be managed by go now.  So running ```go env``` would
+    have/show GOROOT being set by that instance of go.
+    GOPATH is another story.  It's where go stores things it downloads from
+    the web, and most certainly shouldn't be $HOME/go (!= GOROOT).
+
+    I want to be a bit looser on requiring GOROOT later on.
+    */
+
     // Get go environment
     let go_path = env::var("GOPATH").unwrap_or(String::new());
     let go_root = env::var("GOROOT").unwrap_or(String::new());
 
-    let go_version: String;
-    if let Ok(version) = find_go_version() {
-        go_version = version.as_str().trim().to_string();
-    } else {
-        panic!("I wasn't able to locate go.  I need `go version` to know what arch to dl.");
-    }
-
-    let version = version_from_go(&go_version).unwrap();
-
-    // Since I have GO_PATH, I really don't need to do `where go`...
-    // $GOROOT/bin/go
-
-    let go_where: String;
-    if let Ok(location) = find_go() {
-        go_where = location.as_str().trim().to_string();
-    } else {
-        panic!("I wasn't able to locate the go binary.");
+    let go_env = get_go_env()
+        .context("Calling `go env` failed.")?;
+    let mut go_version = hashget(&go_env, "GOVERSION");
+    if go_version.starts_with("go") {
+        go_version.remove(0);
+        go_version.remove(0);
     }
 
-    // Get arch (from `go version` output)
-    let parts = go_version.split(" ");
-    let mut arch = parts.last().unwrap().to_string();
-    arch = arch.replace("/", "-");
+    let go_arch = hashget(&go_env, "GOARCH");
+    let go_os = hashget(&go_env, "GOOS");
+    let go_path = hashget(&go_env, "GOPATH");
+    let go_root = hashget(&go_env, "GOROOT");
+    let go_os_arch = format!("{go_os}-{go_arch}");
 
-    /*
-    println!("GO_PATH  {}", go_path);
-    println!("GO_ROOT  {}", go_root);
-    println!("version: {}", go_version);
-    println!("where:   {}", go_where);
-    println!("arch:    {}", arch);
-    */
+    println!("{} - {}-{}", go_version, go_os, go_arch);
 
+    // Initialize the cache.
     let cache = cache::Cache::new(cli.cache, None)?;
-    // println!("Result: {:?}", get_go_downloads().await );
-
-    // Get go version and path
 
     match &cli.command {
         Some(Commands::Update {}) => {
             let status = cache.fetch(GO_URL);
 
-            // Check to see if file already exists AND
-            // Check version against go's version.
-
-            // Since the go.dev site doesn't allow caching or knowing when it changed...
+            // Check to see if file already exists AND check version against go's version.
+            // Since the go.dev site doesn't allow caching or knowing when it changes,
+            // we always end up in Status::Fetched arm.
 
             match status {
-                cache::Status::Fetched(fp) => {
-                    let link = find_arch_link(&arch, &fp);
+                cache::Status::Fetched(filename) => {
+                    let fp = File::open(filename)?;
+                    let link = find_arch_link(&go_os_arch, &fp);
                     if let Ok(relative) = link {
+                        // Download link for arch located.  Make useable.
                         let abs = relative_to_absolute(GO_URL, &relative).unwrap();
                         println!("URL: {}", abs);
-                        let latest_version = version_from_url(&abs, &arch);
+
+                        let latest_version = version_from_url(&abs, &go_os_arch);
                         if let Some(latest) = latest_version {
-                            println!("Version: {} [have {}]", latest, version);
-                            if version != latest {
+                            println!("Version: {} [have {}]", latest, go_version);
+                            if go_version != latest {
                                 println!("Downloading newer version...");
                                 let latest_status = cache.fetch(&abs);
                                 println!("Latest: {:?}", latest_status);
@@ -273,12 +299,14 @@ fn main() -> Result<()> {
                         } else {
                             println!("Finding version failed for string: [{}]", abs);
                         }
+                    } else {
+                        bail!("Unable to locate download link");
                     }
                 }
-                cache::Status::Cached(fp) => {
+                cache::Status::Cached(filename) => {
                     println!("(from cache)"); // I wish I could see this.
-
-                    let link = find_arch_link(&arch, &fp);
+                    let fp = File::open(filename)?;
+                    let link = find_arch_link(&go_os_arch, &fp);
                     if let Ok(relative) = link {
                         let abs = relative_to_absolute(GO_URL, &relative).unwrap();
                         println!("URL: {}", abs);
@@ -290,12 +318,12 @@ fn main() -> Result<()> {
             }
         }
         Some(Commands::Info {}) => {
-            println!("GO_PATH  {}", go_path);
-            println!("GO_ROOT  {}", go_root);
-            println!("go ver:  {}", go_version);
-            println!("version: {}", version);
-            println!("where:   {}", go_where);
-            println!("arch:    {}", arch);
+            println!("GOPATH      {}", go_path);
+            println!("GOROOT      {}", go_root);
+            println!("go ver      {}", go_version);
+            println!("go_arch     {}", go_arch);
+            println!("go os       {}", go_os);
+            println!("go os arch: {}", go_os_arch);
         }
         None => {
             // Display help.