use std::future::Future;
use crate::error::{ArchToolkitError, Result};
use crate::types::index::OfficialIndex;
#[derive(Debug)]
pub struct IndexRefreshHandle {
task: tokio::task::JoinHandle<Result<OfficialIndex>>,
}
pub fn spawn_index_refresh<F>(refresh: F) -> IndexRefreshHandle
where
F: Future<Output = Result<OfficialIndex>> + Send + 'static,
{
IndexRefreshHandle {
task: tokio::spawn(refresh),
}
}
impl IndexRefreshHandle {
pub fn cancel(&self) {
self.task.abort();
}
#[must_use]
pub fn is_finished(&self) -> bool {
self.task.is_finished()
}
pub async fn wait(self) -> Result<OfficialIndex> {
self.task
.await
.map_err(|error| map_refresh_join_error(&error))?
}
}
fn map_refresh_join_error(error: &tokio::task::JoinError) -> ArchToolkitError {
if error.is_cancelled() {
return ArchToolkitError::Parse("background index refresh was cancelled".to_string());
}
ArchToolkitError::Parse(format!("background index refresh task failed: {error}"))
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use crate::error::Result;
use crate::types::index::OfficialIndex;
use super::spawn_index_refresh;
#[tokio::test]
async fn refresh_delivers_successful_result() {
let handle =
spawn_index_refresh(async { Result::<OfficialIndex>::Ok(OfficialIndex::default()) });
let index = handle.wait().await.expect("refresh result");
assert!(index.pkgs.is_empty());
}
#[tokio::test]
async fn refresh_cancellation_is_explicit() {
let handle = spawn_index_refresh(async {
tokio::time::sleep(Duration::from_mins(1)).await;
Result::<OfficialIndex>::Ok(OfficialIndex::default())
});
handle.cancel();
let error = handle.wait().await.expect_err("cancelled refresh error");
assert!(error.to_string().contains("cancelled"));
}
}