1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
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),
}