uhrwerk 0.1.0

Simple scheduler for recurring tasks
Documentation
use crate::{
	Interval,
	job::Job,
	job_schedule::{JobSchedule, WithSchedule},
	timeprovider::{DefaultTimeProvider, TimeProvider}
};
use std::{
	fmt::{self, Debug, Formatter},
	future::Future,
	pin::Pin
};
use time::{OffsetDateTime, UtcOffset};

pub(crate) type JobFuture = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;

/// An asynchronous job to run on the scheduler.
/// Create these by calling [`AsyncScheduler::every()`](crate::AsyncScheduler::every).
///
/// Methods for scheduling the job live in the [Job] trait.
pub struct AsyncJob<TP = DefaultTimeProvider> {
	schedule: JobSchedule<TP>,
	job: Option<Box<dyn GiveMeAPinnedFuture + Send>>
}

trait GiveMeAPinnedFuture {
	fn get_pinned(&mut self) -> JobFuture;
}

struct JobWrapper<F, T>
where
	F: FnMut() -> T,
	T: Future
{
	f: F
}

impl<F, T> JobWrapper<F, T>
where
	F: FnMut() -> T,
	T: Future
{
	fn new(f: F) -> Self {
		JobWrapper { f }
	}
}

impl<F, T> GiveMeAPinnedFuture for JobWrapper<F, T>
where
	F: FnMut() -> T,
	T: Future<Output = ()> + Send + 'static
{
	fn get_pinned(&mut self) -> JobFuture {
		Box::pin((self.f)())
	}
}

impl<TP: TimeProvider> WithSchedule<TP> for AsyncJob<TP> {
	fn schedule_mut(&mut self) -> &mut JobSchedule<TP> {
		&mut self.schedule
	}

	fn schedule(&self) -> &JobSchedule<TP> {
		&self.schedule
	}
}

impl<TP> Debug for AsyncJob<TP> {
	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
		self.schedule.fmt(f)
	}
}

impl<TP: TimeProvider> Job<TP> for AsyncJob<TP> {}

impl<TP: TimeProvider> AsyncJob<TP> {
	pub(crate) fn new(ival: Interval, utc_offset: UtcOffset) -> Self {
		AsyncJob {
			schedule: JobSchedule::new(ival, utc_offset),
			job: None
		}
	}

	/// Specify a task to run, and schedule its next run
	///
	/// The function passed into this method should return a value implementing `Future<Output = ()>`.
	pub fn run<F, T>(&mut self, f: F) -> &mut Self
	where
		F: 'static + FnMut() -> T + Send,
		T: 'static + Future<Output = ()> + Send
	{
		self.job = Some(Box::new(JobWrapper::new(f)));
		self.schedule.start_schedule();
		self
	}

	/// Run a task and re-schedule it. This is usually only called by
	/// [AsyncScheduler::run_pending()](crate::AsyncScheduler::run_pending).
	pub fn execute(&mut self, now: OffsetDateTime) -> Option<JobFuture> {
		// Don't do anything if we're run out of runs
		if !self.schedule.can_run_again() {
			return None;
		}
		let rv = self.job.as_mut().map(|f| f.get_pinned());
		self.schedule.schedule_next(now);
		rv
	}
}