Asynchronous File Fetcher
Rust crate that provides a high level abstraction around the asynchronous reqwest client. This abstraction is oriented towards making it easier to fetch and update files from remote locations. The primary purpose of this crate is to make it easy to fetch and decompress lists from apt repositories, for use in Pop!_OS as a more efficient apt
replacement with superior error handling capabilities.
The included AsyncFetcher
is used to construct future(s) for execution on an asynchronous runtime, such as tokio. The generated futures should be supplied to an asynchronous parallel runtime, to enable dispatching fetches across threads, in addition to processing multiple fetch tasks from the same thread. The general idea is to generate an iterator of fetch requests, and then combine these into a single future for parallel & asynchronous execution.
Features
- Configurable API with optional state abstractions.
- Supports execution on an asynchronous parallel tokio runtime.
- Only fetches files when the existing file does not exist or is older than the server's copy.
See the examples directory for an elaborate example.
Example
Example partially taken from the example.rs example.
pub fn main() {
init_logging().unwrap();
let files: Vec<(String, &'static str, &'static str, String)> = vec![
(
"http://apt.pop-os.org/proprietary/dists/cosmic/Contents-amd64.xz".into(),
"ed1c4d5b21086baa1dc98f912b5d52adaaeb248e35a9b27cd7e1f06a25781502",
"8e03685a2420b63ad937ab1e741f00b19d4297122cb421dcbb0700d5c2cc145b",
"Contents-amd64".into(),
),
(
"http://apt.pop-os.org/proprietary/dists/cosmic/Contents-all".into(),
"ad740b679291e333d35106735870cf7217d6c76801b45a01a8e7fde58c93aaf0",
"ad740b679291e333d35106735870cf7217d6c76801b45a01a8e7fde58c93aaf0",
"Contents-all".into(),
),
(
"http://apt.pop-os.org/proprietary/dists/cosmic/Contents-i386.gz".into(),
"379f7a6c108bd9feb63848110a556fffb15975cdb22e4eeb25844292cd4c9285",
"67eb11e9bed8ac046572268f6748bf9dcf7848d771dd3343fba1490a7bbefb8a",
"Contents-i386".into(),
),
];
let mut runtime = Runtime::new().unwrap();
let client = Arc::new(Client::new());
let future_iterator = files
.into_iter()
.map(move |(url, fetched_sha256, dest_sha256, dest)| {
let temporary = [&dest, ".partial"].concat();
let fetched_checksum: Arc<str> = Arc::from(fetched_sha256);
let dest_checksum: Arc<str> = Arc::from(dest_sha256);
let request = AsyncFetcher::new(&client, url.clone())
.request_with_checksum_to_path::<Sha256>(dest.into(), &dest_checksum)
.then_download(temporary.into())
.with_checksum::<Sha256>(fetched_checksum);
let future: Box<dyn Future<Item = (), Error = io::Error> + Send> =
if url.ends_with(".xz") {
Box::new(
request
.then_process(move |file| Box::new(XzDecoder::new(file)))
.with_destination_checksum::<Sha256>(dest_checksum),
)
} else if url.ends_with(".gz") {
Box::new(
request
.then_process(move |file| Box::new(GzDecoder::new(file)))
.with_destination_checksum::<Sha256>(dest_checksum),
)
} else {
Box::new(request.then_rename().into_future())
};
future
});
let joined = futures::future::join_all(future_iterator);
let result = runtime.block_on(joined);
eprintln!("result: {:?}", result);
}