Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Create toolchain directory atomically #121

Merged
merged 2 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
227 changes: 6 additions & 221 deletions src/download/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,6 @@
extern crate error_chain;
extern crate url;

#[cfg(feature = "reqwest-backend")]
extern crate reqwest;
#[cfg(feature = "reqwest-backend")]
#[macro_use]
extern crate lazy_static;

use std::path::Path;
use url::Url;

Expand All @@ -19,12 +13,10 @@ pub use errors::*;
#[derive(Debug, Copy, Clone)]
pub enum Backend {
Curl,
Reqwest,
}

#[derive(Debug, Copy, Clone)]
pub enum Event<'a> {
ResumingPartialDownload,
/// Received the Content-Length of the to-be downloaded data.
DownloadContentLengthReceived(u64),
/// Received some data.
Expand All @@ -34,77 +26,33 @@ pub enum Event<'a> {
fn download_with_backend(
backend: Backend,
url: &Url,
resume_from: u64,
callback: &dyn Fn(Event) -> Result<()>,
) -> Result<()> {
match backend {
Backend::Curl => curl::download(url, resume_from, callback),
Backend::Reqwest => reqwest_be::download(url, resume_from, callback),
Backend::Curl => curl::download(url, callback),
}
}

pub fn download_to_path_with_backend(
backend: Backend,
url: &Url,
path: &Path,
resume_from_partial: bool,
callback: Option<&dyn Fn(Event) -> Result<()>>,
) -> Result<()> {
use std::cell::RefCell;
use std::fs::OpenOptions;
use std::io::{Read, Seek, SeekFrom, Write};
use std::io::Write;

|| -> Result<()> {
let (file, resume_from) = if resume_from_partial {
let possible_partial = OpenOptions::new().read(true).open(&path);

let downloaded_so_far = if let Ok(mut partial) = possible_partial {
if let Some(cb) = callback {
cb(Event::ResumingPartialDownload)?;

let mut buf = vec![0; 32768];
let mut downloaded_so_far = 0;
loop {
let n = partial.read(&mut buf)?;
downloaded_so_far += n as u64;
if n == 0 {
break;
}
cb(Event::DownloadDataReceived(&buf[..n]))?;
}

downloaded_so_far
} else {
let file_info = partial.metadata()?;
file_info.len()
}
} else {
0
};

let mut possible_partial = OpenOptions::new()
.write(true)
.create(true)
.open(&path)
.chain_err(|| "error opening file for download")?;

possible_partial.seek(SeekFrom::End(0))?;

(possible_partial, downloaded_so_far)
} else {
(
OpenOptions::new()
let file = OpenOptions::new()
.write(true)
.create(true)
.open(&path)
.chain_err(|| "error creating file for download")?,
0,
)
};
.chain_err(|| "error creating file for download")?;

let file = RefCell::new(file);

download_with_backend(backend, url, resume_from, &|event| {
download_with_backend(backend, url, &|event| {
if let Event::DownloadDataReceived(data) = event {
file.borrow_mut()
.write_all(data)
Expand Down Expand Up @@ -147,7 +95,6 @@ pub mod curl {

pub fn download(
url: &Url,
resume_from: u64,
callback: &dyn Fn(Event) -> Result<()>,
) -> Result<()> {
// Fetch either a cached libcurl handle (which will preserve open
Expand All @@ -165,16 +112,6 @@ pub mod curl {
.follow_location(true)
.chain_err(|| "failed to set follow redirects")?;

if resume_from > 0 {
handle
.resume_from(resume_from)
.chain_err(|| "setting the range header for download resumption")?;
} else {
// an error here indicates that the range header isn't supported by underlying curl,
// so there's nothing to "clear" - safe to ignore this error.
let _ = handle.resume_from(0);
}

// Take at most 30s to connect
handle
.connect_timeout(Duration::new(30, 0))
Expand Down Expand Up @@ -205,7 +142,7 @@ pub mod curl {
let prefix = "Content-Length: ";
if data.starts_with(prefix) {
if let Ok(s) = data[prefix.len()..].trim().parse::<u64>() {
let msg = Event::DownloadContentLengthReceived(s + resume_from);
let msg = Event::DownloadContentLengthReceived(s);
match callback(msg) {
Ok(()) => (),
Err(e) => {
Expand Down Expand Up @@ -254,155 +191,3 @@ pub mod curl {
})
}
}

#[cfg(feature = "reqwest-backend")]
pub mod reqwest_be {
extern crate env_proxy;

use super::Event;
use errors::*;
use reqwest::{header, Client, Proxy, Response};
use std::io;
use std::time::Duration;
use url::Url;

pub fn download(url: &Url, resume_from: u64, callback: &Fn(Event) -> Result<()>) -> Result<()> {
// Short-circuit reqwest for the "file:" URL scheme
if download_from_file_url(url, resume_from, callback)? {
return Ok(());
}

let mut res = request(url, resume_from).chain_err(|| "failed to make network request")?;

if !res.status().is_success() {
let code: u16 = res.status().into();
return Err(ErrorKind::HttpStatus(code as u32).into());
}

let buffer_size = 0x10000;
let mut buffer = vec![0u8; buffer_size];

if let Some(len) = res.headers().get::<header::ContentLength>() {
callback(Event::DownloadContentLengthReceived(len.0 + resume_from))?;
}

loop {
let bytes_read =
io::Read::read(&mut res, &mut buffer).chain_err(|| "error reading from socket")?;

if bytes_read != 0 {
callback(Event::DownloadDataReceived(&buffer[0..bytes_read]))?;
} else {
return Ok(());
}
}
}

lazy_static! {
static ref CLIENT: Client = {
let catcher = || {
Client::builder()
.gzip(false)
.proxy(Proxy::custom(env_proxy))
.timeout(Duration::from_secs(30))
.build()
};

// woah, an unwrap?!
// It's OK. This is the same as what is happening in curl.
//
// The curl::Easy::new() internally assert!s that the initialized
// Easy is not null. Inside reqwest, the errors here would be from
// the TLS library returning a null pointer as well.
catcher().unwrap()
};
}

fn env_proxy(url: &Url) -> Option<Url> {
env_proxy::for_url(url).to_url()
}

fn request(url: &Url, resume_from: u64) -> ::reqwest::Result<Response> {
let mut req = CLIENT.get(url.clone());

if resume_from != 0 {
req.header(header::Range::Bytes(vec![header::ByteRangeSpec::AllFrom(
resume_from,
)]));
}

req.send()
}

fn download_from_file_url(
url: &Url,
resume_from: u64,
callback: &Fn(Event) -> Result<()>,
) -> Result<bool> {
use std::fs;
use std::io;

// The file scheme is mostly for use by tests to mock the dist server
if url.scheme() == "file" {
let src = url
.to_file_path()
.map_err(|_| Error::from(format!("bogus file url: '{}'", url)))?;
if !src.is_file() {
// Because some of elan's logic depends on checking
// the error when a downloaded file doesn't exist, make
// the file case return the same error value as the
// network case.
return Err(ErrorKind::FileNotFound.into());
}

let ref mut f = fs::File::open(src).chain_err(|| "unable to open downloaded file")?;
io::Seek::seek(f, io::SeekFrom::Start(resume_from))?;

let ref mut buffer = vec![0u8; 0x10000];
loop {
let bytes_read =
io::Read::read(f, buffer).chain_err(|| "unable to read downloaded file")?;
if bytes_read == 0 {
break;
}
callback(Event::DownloadDataReceived(&buffer[0..bytes_read]))?;
}

Ok(true)
} else {
Ok(false)
}
}
}

#[cfg(not(feature = "curl-backend"))]
pub mod curl {

use super::Event;
use errors::*;
use url::Url;

pub fn download(
_url: &Url,
_resume_from: u64,
_callback: &dyn Fn(Event) -> Result<()>,
) -> Result<()> {
Err(ErrorKind::BackendUnavailable("curl").into())
}
}

#[cfg(not(feature = "reqwest-backend"))]
pub mod reqwest_be {

use super::Event;
use errors::*;
use url::Url;

pub fn download(
_url: &Url,
_resume_from: u64,
_callback: &dyn Fn(Event) -> Result<()>,
) -> Result<()> {
Err(ErrorKind::BackendUnavailable("reqwest").into())
}
}
2 changes: 1 addition & 1 deletion src/elan-cli/self_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1283,7 +1283,7 @@ pub fn prepare_update() -> Result<Option<PathBuf>> {

// Download new version
info!("downloading self-update");
utils::download_file(&download_url, &archive_path, None, &|_| ())?;
utils::download_file(&download_url, &archive_path, &|_| ())?;

let file = fs::File::open(archive_path)?;
if cfg!(target_os = "windows") {
Expand Down
Loading
Loading