gog-sync/src/http.rs

191 lines
5.3 KiB
Rust
Raw Normal View History

2017-03-20 18:00:27 +00:00
//! A thin wrapper around [curl-rust](https://crates.io/crates/curl) to make
//! get requests and download files.
2017-03-20 12:14:49 +00:00
use curl;
use curl::easy::{Easy, List, WriteError};
2017-03-21 21:28:03 +00:00
use std::fmt;
2017-03-20 12:14:49 +00:00
use std::fs;
use std::fs::File;
use std::io;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::str;
use std::str::Utf8Error;
use url;
use url::Url;
2017-03-20 18:00:27 +00:00
/// Wraps `curl::Error`, `Utf8Error`, `io::Error` and `url::ParseError`.
2017-03-20 12:14:49 +00:00
#[derive(Debug)]
pub enum HttpError {
Error(&'static str),
CurlError(curl::Error),
Utf8Error(Utf8Error),
IOError(io::Error),
UrlParseError(url::ParseError),
}
2017-03-21 21:28:03 +00:00
impl fmt::Display for HttpError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
HttpError::Error(ref err) => fmt::Display::fmt(err, f),
HttpError::CurlError(ref err) => fmt::Display::fmt(err, f),
HttpError::Utf8Error(ref err) => fmt::Display::fmt(err, f),
HttpError::IOError(ref err) => fmt::Display::fmt(err, f),
HttpError::UrlParseError(ref err) => fmt::Display::fmt(err, f),
}
}
}
2017-03-20 12:14:49 +00:00
impl From<curl::Error> for HttpError {
fn from(e: curl::Error) -> Self {
HttpError::CurlError(e)
}
}
impl From<Utf8Error> for HttpError {
fn from(e: Utf8Error) -> Self {
HttpError::Utf8Error(e)
}
}
impl From<io::Error> for HttpError {
fn from(e: io::Error) -> Self {
HttpError::IOError(e)
}
}
impl From<url::ParseError> for HttpError {
fn from(e: url::ParseError) -> Self {
HttpError::UrlParseError(e)
}
}
impl From<HttpError> for WriteError {
2017-03-20 15:20:37 +00:00
fn from(_: HttpError) -> Self {
2017-03-20 12:14:49 +00:00
WriteError::__Nonexhaustive
}
}
2017-03-20 18:00:27 +00:00
/// Wraps curl-rust.
2017-03-20 12:14:49 +00:00
pub struct Http {
curl: Easy,
}
impl Http {
2017-03-20 18:00:27 +00:00
/// Create a new instance of `Http`.
/// Ensures that curl follows redirects.
2017-03-20 12:14:49 +00:00
pub fn new() -> Http {
let mut curl = Easy::new();
curl.follow_location(true).unwrap();
Http { curl: curl }
}
2017-03-20 18:00:27 +00:00
/// Make a get request and return the response.
///
/// # Example
/// ```
/// use http::Http;
///
/// let mut http_client = Http::new();
/// http_client.get("https://discworld.com/");
/// ```
2017-03-20 12:14:49 +00:00
pub fn get(&mut self, uri: &str) -> Result<String, HttpError> {
let mut data = Vec::new();
2017-03-20 12:14:49 +00:00
self.curl.url(uri)?;
{
let mut transfer = self.curl.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
2017-03-20 12:14:49 +00:00
})?;
transfer.perform()?;
}
match str::from_utf8(data.as_slice()) {
Ok(value) => Ok(value.to_owned()),
Err(error) => Err(HttpError::Utf8Error(error)),
}
2017-03-20 12:14:49 +00:00
}
2017-03-20 18:00:27 +00:00
/// Add a header to all future requests.
///
/// # Example
/// ```
/// use http::Http;
///
/// let mut http_client = Http::new();
/// http_client.add_header("X-Clacks-Overhead: GNU Terry Pratchett");
/// ```
2017-03-20 12:14:49 +00:00
pub fn add_header(&mut self, header: &str) -> Result<(), HttpError> {
let mut list = List::new();
list.append(header)?;
match self.curl.http_headers(list) {
Ok(_) => Ok(()),
Err(error) => Err(HttpError::CurlError(error)),
}
}
2017-03-20 18:00:27 +00:00
/// Download a file to the specified folder without creating the folder.
///
2017-05-04 14:19:06 +00:00
/// The filename is taken from the last URI segment and returned in the result.
2017-03-20 18:00:27 +00:00
/// # Example
/// ```
/// use http::Http;
/// use std::path::Path;
///
/// let mut http_client = Http::new();
///
/// let download_path = Path::new("/opt/bin");
/// http_client.download("https://example.com/sed", &download_path);
/// ```
2017-03-20 12:14:49 +00:00
pub fn download(&mut self,
download_uri: &str,
download_dir: &PathBuf)
2017-05-04 14:19:06 +00:00
-> Result<String, HttpError> {
2017-03-20 12:14:49 +00:00
let download_path_tmp = Path::new(download_dir.as_os_str()).join(".progress");
let mut file_download = File::create(&download_path_tmp)?;
self.curl.url(download_uri)?;
{
let mut transfer = self.curl.transfer();
transfer.write_function(|data| {
match file_download.write(data) {
Ok(_) => Ok(()),
Err(error) => Err(HttpError::IOError(error)),
}?;
Ok(data.len())
})?;
transfer.perform()?;
}
let download_url_string = match self.curl.effective_url()? {
Some(value) => value,
None => return Err(HttpError::Error("Can't get effective download url.")),
};
let download_url = Url::parse(download_url_string)?;
let download_url_segments = match download_url.path_segments() {
Some(value) => value,
None => return Err(HttpError::Error("Can't parse download segments.")),
};
let file_name = match download_url_segments.last() {
Some(value) => value,
None => return Err(HttpError::Error("No segments in download url.")),
};
let download_path = Path::new(download_dir.as_os_str()).join(file_name);
match fs::rename(download_path_tmp, download_path) {
Ok(_) => Ok(()),
Err(error) => Err(HttpError::IOError(error)),
}?;
2017-05-04 14:19:06 +00:00
Ok(file_name.to_owned())
2017-03-20 12:14:49 +00:00
}
}