break content deserializing and sync code into smaller pieces (fixes #9)
233 lines
6.8 KiB
Rust
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())
|
|
}
|
|
}
|