From 4a5328a14eaefad696e55c0731105dc69a6f8c50 Mon Sep 17 00:00:00 2001 From: deadjakk Date: Wed, 29 Mar 2023 00:58:01 -0600 Subject: [PATCH] added ability to save the current url to a filepath --- src/gopher.rs | 45 +++++++++++++++++++++++++++++++++++++++++++++ src/ui.rs | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/src/gopher.rs b/src/gopher.rs index 0c48060..f5be137 100644 --- a/src/gopher.rs +++ b/src/gopher.rs @@ -112,6 +112,51 @@ fn clean_response(res: &mut String) { }) } +/// Downloads a binary to disk to a provided file name. +/// Allows canceling with Ctrl-c, but it's +/// kind of hacky - needs the UI receiver passed in. +/// Returns a tuple of: +/// (path it was saved to, the size in bytes) +pub fn download_url_with_filename( + url: &str, + tls: bool, + tor: bool, + chan: ui::KeyReceiver, + filename: &str, +) -> Result<(String, usize)> { + let u = parse_url(url); + let mut path = std::path::PathBuf::from("."); + path.push(filename); + + let mut stream = request(u.host, u.port, u.sel, tls, tor)?; + let mut file = fs::OpenOptions::new() + .write(true) + .create_new(true) + .append(true) + .open(&path) + .map_err(|e| error!("`open` error: {}", e))?; + + let mut buf = [0; 1024]; + let mut bytes = 0; + while let Ok(count) = stream.read(&mut buf) { + if count == 0 { + break; + } + bytes += count; + file.write_all(&buf[..count])?; + if let Ok(chan) = chan.lock() { + if let Ok(Key::Ctrl('c')) = chan.try_recv() { + if path.exists() { + fs::remove_file(path)?; + } + return Err(error!("Download cancelled")); + } + } + } + + Ok((filename.to_string(), bytes)) +} + /// Downloads a binary to disk. Allows canceling with Ctrl-c, but it's /// kind of hacky - needs the UI receiver passed in. /// Returns a tuple of: diff --git a/src/ui.rs b/src/ui.rs index b5bb1f2..b6fcc71 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -236,6 +236,31 @@ impl UI { }) } + /// Used to download content of the current view with a provided filename + fn download_file_with_filename(&mut self, url: &str, filename: String) -> Result<()> { + let url = url.to_string(); + let (tls, tor) = ( + self.config.read().unwrap().tls, + self.config.read().unwrap().tor, + ); + let chan = self.keys.clone(); + self.spinner(&format!("Downloading {}", url), move || { + gopher::download_url_with_filename(&url, tls, tor, chan, &filename) + }) + .and_then(|res| res) + .map(|(path, bytes)| { + self.set_status( + format!( + "Download complete! {} saved to {}", + utils::human_bytes(bytes), + path + ) + .as_ref(), + ); + }) + } + + /// Download a binary file. Used by `open()` internally. fn download(&mut self, url: &str) -> Result<()> { let url = url.to_string(); @@ -659,6 +684,19 @@ impl UI { Action::Keypress(Key::Char(key)) | Action::Keypress(Key::Ctrl(key)) => match key { 'a' => self.open("History", "gopher://phetch/1/history")?, 'b' => self.open("Bookmarks", "gopher://phetch/1/bookmarks")?, + 'c' => { + let url = match self.views.get(self.focused) { + Some(view)=> String::from(view.url()), + None => {return Err(error!("Could not get url from view"));}, + }; + + if let Some(filename) = self.prompt("Provide a filepath: ", ""){ + match self.download_file_with_filename(url.as_str(), String::from(filename)){ + Ok(()) => (), + Err(e) => return Err(error!("Save failed: {}", e)), + } + } + } 'g' => { if let Some(url) = self.prompt("Go to URL: ", "") { self.open(&url, &url)?;