gog-sync/src/http.rs
Sebastian Hugentobler 1f8329cd3a sync dlcs (fixes #12)
break content deserializing and sync code into smaller pieces (fixes #9)
2017-05-10 14:50:38 +02:00

233 lines
6.8 KiB
Rust

//! A thin wrapper around [curl-rust](https://crates.io/crates/curl) to make
//! get requests and download files.
use curl;
use curl::easy::{Easy, List, WriteError};
use std::fmt;
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;
/// Wraps `curl::Error`, `Utf8Error`, `io::Error` and `url::ParseError`.
#[derive(Debug)]
pub enum HttpError {
Error(&'static str),
CurlError(curl::Error),
Utf8Error(Utf8Error),
IOError(io::Error),
UrlParseError(url::ParseError),
}
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),
}
}
}
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 {
fn from(_: HttpError) -> Self {
WriteError::__Nonexhaustive
}
}
/// Wraps curl-rust.
pub struct Http {
curl: Easy,
}
impl Http {
/// Create a new instance of `Http`.
/// Ensures that curl follows redirects.
pub fn new() -> Http {
let mut curl = Easy::new();
curl.follow_location(true).unwrap();
Http { curl: curl }
}
/// Make a get request and return the response.
///
/// # Example
/// ```
/// use http::Http;
///
/// let mut http_client = Http::new();
/// http_client.get("https://discworld.com/");
/// ```
pub fn get(&mut self, uri: &str) -> Result<String, HttpError> {
let mut data = Vec::new();
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())
})?;
transfer.perform()?;
}
match str::from_utf8(data.as_slice()) {
Ok(value) => Ok(value.to_owned()),
Err(error) => Err(HttpError::Utf8Error(error)),
}
}
/// 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");
/// ```
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)),
}
}
/// Find the filename for a download uri.
///
/// Useful if the initial link gets redirected.
///
/// The filename is taken from the last URI segment and returned in the result.
/// # Example
/// ```
/// use http::Http;
/// use std::path::Path;
///
/// let mut http_client = Http::new();
///
/// http_client.get_filename("https://example.com/sed");
/// ```
pub fn get_filename(&mut self, download_uri: &str) -> Result<String, HttpError> {
self.curl.url(download_uri)?;
self.curl.nobody(true)?;
self.curl.perform()?;
self.curl.nobody(false)?;
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.")),
};
Ok(file_name.to_owned())
}
/// Download a file to the specified folder without creating the folder.
///
/// The filename is taken from the last URI segment and returned in the result.
/// # 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);
/// ```
pub fn download(&mut self,
download_uri: &str,
download_dir: &PathBuf)
-> Result<String, HttpError> {
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)),
}?;
Ok(file_name.to_owned())
}
}