use std::io::Write;
use std::path::Path;
use chrono::offset::FixedOffset;
use chrono::DateTime;
use serde_derive::*;
use thiserror::Error;
use url::Url;
pub struct Client {
origin: String,
}
impl Client {
pub fn new<S: AsRef<str>>(origin: S) -> Self {
Self {
origin: origin.as_ref().to_string(),
}
}
pub async fn file_info(&self, path: ArtifactoryPath) -> Result<FileInfo, Error> {
let url = format!("{}/artifactory/api/storage/{}", self.origin, path.0);
let url = Url::parse(&url).unwrap();
let info: FileInfo = reqwest::get(url).await?.json().await?;
Ok(info)
}
pub async fn pull<F>(
&self,
path: ArtifactoryPath,
dest: &Path,
mut progress: F,
) -> Result<(), Error>
where
F: FnMut(DownloadProgress),
{
let url = format!("{}/artifactory/{}", self.origin, path.0);
let url = Url::parse(&url).unwrap();
let mut dest = std::fs::File::create(dest)?;
let mut res = reqwest::get(url).await?;
let expected_bytes_downloaded = res.content_length().unwrap_or(0);
let mut bytes_downloaded = 0;
while let Some(chunk) = res.chunk().await? {
bytes_downloaded += chunk.as_ref().len() as u64;
dest.write_all(chunk.as_ref())?;
let status = DownloadProgress {
expected_bytes_downloaded,
bytes_downloaded,
};
progress(status);
}
Ok(())
}
}
pub struct ArtifactoryPath(String);
impl<S: AsRef<str>> From<S> for ArtifactoryPath {
fn from(s: S) -> Self {
Self(s.as_ref().to_string())
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FileInfo {
uri: Url,
download_uri: Url,
repo: String,
path: String,
remote_url: Option<Url>,
created: DateTime<FixedOffset>,
created_by: String,
last_modified: DateTime<FixedOffset>,
modified_by: String,
last_updated: DateTime<FixedOffset>,
size: String,
mime_type: String,
pub checksums: Checksums,
original_checksums: OriginalChecksums,
}
#[derive(Debug, Deserialize)]
pub struct Checksums {
#[serde(with = "hex")]
pub md5: Vec<u8>,
#[serde(with = "hex")]
pub sha1: Vec<u8>,
#[serde(with = "hex")]
pub sha256: Vec<u8>,
}
#[derive(Debug, Deserialize)]
pub struct OriginalChecksums {
md5: Option<String>,
sha1: Option<String>,
sha256: Option<String>,
}
#[derive(Clone, Copy, Debug)]
pub struct DownloadProgress {
pub expected_bytes_downloaded: u64,
pub bytes_downloaded: u64,
}
#[derive(Error, Debug)]
pub enum Error {
#[error("An IO error occurred.")]
Io(#[from] std::io::Error),
#[error("A HTTP related error occurred.")]
Reqwest(#[from] reqwest::Error),
}