Add stream implementation for retrieving updates
Signed-off-by: trivernis <trivernis@protonmail.com>main
parent
642a6c3b55
commit
2811aeb40d
@ -0,0 +1,113 @@
|
|||||||
|
use crate::Result;
|
||||||
|
use crate::{Client, UpdateResponse};
|
||||||
|
use futures_core::Stream;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
pub async fn stream_updates(&self, since: u64) -> Result<UpdateStream> {
|
||||||
|
let entries = self.get_metadata(since).await?.0.entries;
|
||||||
|
let hashes = entries
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|e| e.update_hashes)
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
let client = self.clone();
|
||||||
|
|
||||||
|
Ok(UpdateStream::new(client, hashes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A stream of update files
|
||||||
|
/// Used like follows:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # use hydrus_ptr_client::{Client};
|
||||||
|
/// use futures_util::StreamExt;
|
||||||
|
///
|
||||||
|
/// # async fn a() {///
|
||||||
|
/// # let client = Client::new("", "");
|
||||||
|
/// let mut stream = client.stream_updates(0).await.unwrap();
|
||||||
|
///
|
||||||
|
/// while let Some(Ok(update)) = stream.next().await {
|
||||||
|
/// // do something
|
||||||
|
/// }
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub struct UpdateStream {
|
||||||
|
failed_hashes: Vec<String>,
|
||||||
|
pending_hash: Option<String>,
|
||||||
|
hashes: Vec<String>,
|
||||||
|
client: Option<Client>,
|
||||||
|
fut: Option<Pin<Box<dyn Future<Output = (Result<UpdateResponse>, Client)>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpdateStream {
|
||||||
|
pub(crate) fn new(client: Client, mut hashes: Vec<String>) -> Self {
|
||||||
|
hashes.reverse();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
client: Some(client),
|
||||||
|
hashes,
|
||||||
|
fut: None,
|
||||||
|
failed_hashes: Vec::new(),
|
||||||
|
pending_hash: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Re-enqueues the latest failed hash for retry
|
||||||
|
pub fn retry_latest(&mut self) {
|
||||||
|
if let Some(hash) = self.failed_hashes.pop() {
|
||||||
|
self.hashes.push(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Puts all failed hashes back into the queue for retry
|
||||||
|
pub fn retry_all(&mut self) {
|
||||||
|
self.hashes.append(&mut self.failed_hashes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a list of all failed hashes
|
||||||
|
pub fn failed_hashes(&self) -> &Vec<String> {
|
||||||
|
&self.failed_hashes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Unpin for UpdateStream {}
|
||||||
|
|
||||||
|
impl Stream for UpdateStream {
|
||||||
|
type Item = Result<UpdateResponse>;
|
||||||
|
|
||||||
|
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
|
if self.fut.is_none() {
|
||||||
|
if self.hashes.is_empty() {
|
||||||
|
return Poll::Ready(None);
|
||||||
|
}
|
||||||
|
let hash = self.hashes.pop().unwrap();
|
||||||
|
self.pending_hash = Some(hash.clone());
|
||||||
|
let client = self.client.take().unwrap();
|
||||||
|
|
||||||
|
self.fut = Some(Box::pin(async move {
|
||||||
|
let update = client.get_update(hash).await;
|
||||||
|
|
||||||
|
(update, client)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.fut.as_mut().unwrap().as_mut().poll(cx) {
|
||||||
|
Poll::Ready((result, client)) => {
|
||||||
|
self.client = Some(client);
|
||||||
|
self.fut = None;
|
||||||
|
|
||||||
|
if result.is_err() {
|
||||||
|
let pending_hash = self.pending_hash.take().unwrap();
|
||||||
|
self.failed_hashes.push(pending_hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
Poll::Ready(Some(result))
|
||||||
|
}
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue