mirror of
https://github.com/teloxide/teloxide.git
synced 2024-12-22 22:46:39 +01:00
Merge pull request #662 from teloxide/polling_builder
Add `PollingBuilder` & friends
Former-commit-id: 3d4d506203
This commit is contained in:
commit
e590736585
3 changed files with 335 additions and 160 deletions
|
@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Security checks based on `secret_token` param of `set_webhook` to built-in webhooks
|
- Security checks based on `secret_token` param of `set_webhook` to built-in webhooks
|
||||||
|
- `dispatching::update_listeners::{PollingBuilder, Polling, PollingStream}`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
@ -17,6 +18,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Add the `Key: Clone` requirement for `impl Dispatcher` [**BC**].
|
- Add the `Key: Clone` requirement for `impl Dispatcher` [**BC**].
|
||||||
|
- `dispatching::update_listeners::{polling_default, polling}` now return a named, `Polling<_>` type
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
|
||||||
|
- `dispatching::update_listeners::polling`
|
||||||
|
|
||||||
## 0.9.2 - 2022-06-07
|
## 0.9.2 - 2022-06-07
|
||||||
|
|
||||||
|
|
|
@ -42,8 +42,9 @@ use crate::{
|
||||||
mod polling;
|
mod polling;
|
||||||
mod stateful_listener;
|
mod stateful_listener;
|
||||||
|
|
||||||
|
#[allow(deprecated)]
|
||||||
pub use self::{
|
pub use self::{
|
||||||
polling::{polling, polling_default},
|
polling::{polling, polling_default, Polling, PollingBuilder, PollingStream},
|
||||||
stateful_listener::StatefulListener,
|
stateful_listener::StatefulListener,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -125,3 +126,11 @@ pub trait AsUpdateStream<'a, E> {
|
||||||
/// [`Stream`]: AsUpdateStream::Stream
|
/// [`Stream`]: AsUpdateStream::Stream
|
||||||
fn as_stream(&'a mut self) -> Self::Stream;
|
fn as_stream(&'a mut self) -> Self::Stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn assert_update_listener<L, E>(listener: L) -> L
|
||||||
|
where
|
||||||
|
L: UpdateListener<E>,
|
||||||
|
{
|
||||||
|
listener
|
||||||
|
}
|
||||||
|
|
|
@ -1,89 +1,200 @@
|
||||||
use std::{convert::TryInto, time::Duration};
|
use std::{
|
||||||
|
convert::TryInto,
|
||||||
use futures::{
|
future::Future,
|
||||||
future::{ready, Either},
|
pin::Pin,
|
||||||
stream::{self, Stream, StreamExt},
|
task::{
|
||||||
|
self,
|
||||||
|
Poll::{self, Ready},
|
||||||
|
},
|
||||||
|
time::Duration,
|
||||||
|
vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use futures::{ready, stream::Stream};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
dispatching::{
|
dispatching::{
|
||||||
stop_token::{AsyncStopFlag, AsyncStopToken},
|
stop_token::{AsyncStopFlag, AsyncStopToken},
|
||||||
update_listeners::{stateful_listener::StatefulListener, UpdateListener},
|
update_listeners::{assert_update_listener, AsUpdateStream, UpdateListener},
|
||||||
},
|
},
|
||||||
payloads::{GetUpdates, GetUpdatesSetters as _},
|
|
||||||
requests::{HasPayload, Request, Requester},
|
requests::{HasPayload, Request, Requester},
|
||||||
types::{AllowedUpdate, Update},
|
types::{AllowedUpdate, Update},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Returns a long polling update listener with `timeout` of 10 seconds.
|
/// Builder for polling update listener.
|
||||||
///
|
///
|
||||||
/// See also: [`polling`](polling).
|
/// Can be created by [`Polling::builder`].
|
||||||
///
|
#[non_exhaustive]
|
||||||
/// ## Notes
|
#[must_use = "`PollingBuilder` is a builder and does nothing unless used"]
|
||||||
///
|
pub struct PollingBuilder<R> {
|
||||||
/// This function will automatically delete a webhook if it was set up.
|
pub bot: R,
|
||||||
pub async fn polling_default<R>(requester: R) -> impl UpdateListener<R::Err>
|
pub timeout: Option<Duration>,
|
||||||
|
pub limit: Option<u8>,
|
||||||
|
pub allowed_updates: Option<Vec<AllowedUpdate>>,
|
||||||
|
pub drop_pending_updates: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R> PollingBuilder<R>
|
||||||
where
|
where
|
||||||
R: Requester + Send + 'static,
|
R: Requester + Send + 'static,
|
||||||
<R as Requester>::GetUpdates: Send,
|
<R as Requester>::GetUpdates: Send,
|
||||||
{
|
{
|
||||||
delete_webhook_if_setup(&requester).await;
|
/// A timeout in seconds for polling.
|
||||||
polling(requester, Some(Duration::from_secs(10)), None, None)
|
///
|
||||||
|
/// ## Note
|
||||||
|
///
|
||||||
|
/// `timeout` should not be bigger than http client timeout, see
|
||||||
|
/// [`default_reqwest_settings`] for default http client settings.
|
||||||
|
///
|
||||||
|
/// [`default_reqwest_settings`]: crate::net::default_reqwest_settings
|
||||||
|
pub fn timeout(self, timeout: Duration) -> Self {
|
||||||
|
Self { timeout: Some(timeout), ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Limit the number of updates to be retrieved at once. Values between
|
||||||
|
/// 1—100 are accepted.
|
||||||
|
///
|
||||||
|
/// ## Panics
|
||||||
|
///
|
||||||
|
/// If `limit` is 0 or greater than 100.
|
||||||
|
#[track_caller]
|
||||||
|
pub fn limit(self, limit: u8) -> Self {
|
||||||
|
assert_ne!(limit, 0, "limit can't be 0");
|
||||||
|
assert!(limit <= 100, "maximum limit is 100, can't set limit to `{limit}`");
|
||||||
|
|
||||||
|
Self { limit: Some(limit), ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A list of the types of updates you want to receive.
|
||||||
|
///
|
||||||
|
/// ## Note
|
||||||
|
///
|
||||||
|
/// Teloxide normally (when using [`Dispatcher`] or [`repl`]s) sets this
|
||||||
|
/// automatically via [`hint_allowed_updates`], so you rarely need to use
|
||||||
|
/// `allowed_updates` explicitly.
|
||||||
|
///
|
||||||
|
/// [`Dispatcher`]: crate::dispatching::Dispatcher
|
||||||
|
/// [`repl`]: fn@crate::repl
|
||||||
|
/// [`hint_allowed_updates`]: crate::dispatching::update_listeners::UpdateListener::hint_allowed_updates
|
||||||
|
pub fn allowed_updates(self, allowed_updates: Vec<AllowedUpdate>) -> Self {
|
||||||
|
Self { allowed_updates: Some(allowed_updates), ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Drops pending updates.
|
||||||
|
pub fn drop_pending_updates(self) -> Self {
|
||||||
|
Self { drop_pending_updates: true, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes webhook if it was set up.
|
||||||
|
pub async fn delete_webhook(self) -> Self {
|
||||||
|
delete_webhook_if_setup(&self.bot).await;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a long polling update listener with configuration from the
|
||||||
|
/// builder.
|
||||||
|
///
|
||||||
|
/// See also: [`polling_default`], [`Polling`].
|
||||||
|
pub fn build(self) -> Polling<R> {
|
||||||
|
let Self { bot, timeout, limit, allowed_updates, drop_pending_updates } = self;
|
||||||
|
let (token, flag) = AsyncStopToken::new_pair();
|
||||||
|
let polling =
|
||||||
|
Polling { bot, timeout, limit, allowed_updates, drop_pending_updates, flag, token };
|
||||||
|
|
||||||
|
assert_update_listener(polling)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(doc, aquamarine::aquamarine)]
|
/// Returns a long polling update listener with `timeout` of 10 seconds.
|
||||||
/// Returns a long polling update listener with some additional options.
|
|
||||||
///
|
///
|
||||||
/// - `bot`: Using this bot, the returned update listener will receive updates.
|
/// See also: [`Polling::builder`].
|
||||||
/// - `timeout`: A timeout in seconds for polling.
|
|
||||||
/// - `limit`: Limits the number of updates to be retrieved at once. Values
|
|
||||||
/// between 1—100 are accepted.
|
|
||||||
/// - `allowed_updates`: A list the types of updates you want to receive.
|
|
||||||
///
|
|
||||||
/// See [`GetUpdates`] for defaults.
|
|
||||||
///
|
|
||||||
/// See also: [`polling_default`](polling_default).
|
|
||||||
///
|
///
|
||||||
/// ## Notes
|
/// ## Notes
|
||||||
///
|
///
|
||||||
/// - `timeout` should not be bigger than http client timeout, see
|
/// This function will automatically delete a webhook if it was set up.
|
||||||
/// [`default_reqwest_settings`] for default http client settings.
|
pub async fn polling_default<R>(bot: R) -> Polling<R>
|
||||||
/// - [`repl`]s and [`Dispatcher`] use [`hint_allowed_updates`] to set
|
where
|
||||||
/// `allowed_updates`, so you rarely need to pass `allowed_updates`
|
R: Requester + Send + 'static,
|
||||||
/// explicitly.
|
<R as Requester>::GetUpdates: Send,
|
||||||
///
|
{
|
||||||
/// [`default_reqwest_settings`]: teloxide::net::default_reqwest_settings
|
let polling =
|
||||||
/// [`repl`]: fn@crate::repl
|
Polling::builder(bot).timeout(Duration::from_secs(10)).delete_webhook().await.build();
|
||||||
/// [`Dispatcher`]: crate::dispatching::Dispatcher
|
|
||||||
/// [`hint_allowed_updates`]:
|
assert_update_listener(polling)
|
||||||
/// crate::dispatching::update_listeners::UpdateListener::hint_allowed_updates
|
}
|
||||||
|
|
||||||
|
/// Returns a long polling update listener with some additional options.
|
||||||
|
#[deprecated(since = "0.10.0", note = "use `Polling::builder()` instead")]
|
||||||
|
pub fn polling<R>(
|
||||||
|
bot: R,
|
||||||
|
timeout: Option<Duration>,
|
||||||
|
limit: Option<u8>,
|
||||||
|
allowed_updates: Option<Vec<AllowedUpdate>>,
|
||||||
|
) -> Polling<R>
|
||||||
|
where
|
||||||
|
R: Requester + Send + 'static,
|
||||||
|
<R as Requester>::GetUpdates: Send,
|
||||||
|
{
|
||||||
|
let mut builder = Polling::builder(bot);
|
||||||
|
builder.timeout = timeout;
|
||||||
|
builder.limit = limit;
|
||||||
|
builder.allowed_updates = allowed_updates;
|
||||||
|
assert_update_listener(builder.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_webhook_if_setup<R>(requester: &R)
|
||||||
|
where
|
||||||
|
R: Requester,
|
||||||
|
{
|
||||||
|
let webhook_info = match requester.get_webhook_info().send().await {
|
||||||
|
Ok(ok) => ok,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to get webhook info: {:?}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_webhook_setup = webhook_info.url.is_some();
|
||||||
|
|
||||||
|
if is_webhook_setup {
|
||||||
|
if let Err(e) = requester.delete_webhook().send().await {
|
||||||
|
log::error!("Failed to delete a webhook: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(doc, aquamarine::aquamarine)]
|
||||||
|
/// A polling update listener.
|
||||||
///
|
///
|
||||||
/// ## How it works
|
/// ## How it works
|
||||||
///
|
///
|
||||||
/// Long polling works by repeatedly calling [`Bot::get_updates`][get_updates].
|
/// Long polling works by repeatedly calling
|
||||||
/// If telegram has any updates, it returns them immediately, otherwise it waits
|
/// [`Bot::get_updates`][get_updates]. If telegram has any updates, it
|
||||||
/// until either it has any updates or `timeout` expires.
|
/// returns them immediately, otherwise it waits until either it has any
|
||||||
|
/// updates or `timeout` expires.
|
||||||
///
|
///
|
||||||
/// Each [`get_updates`][get_updates] call includes an `offset` parameter equal
|
/// Each [`get_updates`][get_updates] call includes an `offset` parameter
|
||||||
/// to the latest update id + one, that allows to only receive updates that has
|
/// equal to the latest update id + one, that allows to only receive
|
||||||
/// not been received before.
|
/// updates that has not been received before.
|
||||||
///
|
///
|
||||||
/// When telegram receives a [`get_updates`][get_updates] request with `offset =
|
/// When telegram receives a [`get_updates`][get_updates] request with
|
||||||
/// N` it forgets any updates with id < `N`. When `polling` listener is stopped,
|
/// `offset = N` it forgets any updates with id < `N`. When `polling`
|
||||||
/// it sends [`get_updates`][get_updates] with `timeout = 0, limit = 1` and
|
/// listener is stopped, it sends [`get_updates`][get_updates] with
|
||||||
/// appropriate `offset`, so future bot restarts won't see updates that were
|
/// `timeout = 0, limit = 1` and appropriate `offset`, so future bot
|
||||||
/// already seen.
|
/// restarts won't see updates that were already seen.
|
||||||
///
|
///
|
||||||
/// Consumers of a `polling` update listener then need to repeatedly call
|
/// Consumers of a [`Polling`] update listener then need to repeatedly call
|
||||||
/// [`futures::StreamExt::next`] to get the updates.
|
/// [`futures::StreamExt::next`] to get the updates.
|
||||||
///
|
///
|
||||||
/// Here is an example diagram that shows these interactions between consumers
|
/// Here is an example diagram that shows these interactions between
|
||||||
/// like [`Dispatcher`], `polling` update listener and telegram.
|
/// consumers like [`Dispatcher`], [`Polling`] update listener and
|
||||||
|
/// telegram.
|
||||||
///
|
///
|
||||||
/// ```mermaid
|
/// ```mermaid
|
||||||
/// sequenceDiagram
|
/// sequenceDiagram
|
||||||
/// participant C as Consumer
|
/// participant C as Consumer
|
||||||
/// participant P as polling
|
/// participant P as Polling
|
||||||
/// participant T as Telegram
|
/// participant T as Telegram
|
||||||
///
|
///
|
||||||
/// link C: Dispatcher @ ../struct.Dispatcher.html
|
/// link C: Dispatcher @ ../struct.Dispatcher.html
|
||||||
|
@ -123,131 +234,180 @@ where
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// [get_updates]: crate::requests::Requester::get_updates
|
/// [get_updates]: crate::requests::Requester::get_updates
|
||||||
pub fn polling<R>(
|
/// [`Dispatcher`]: crate::dispatching::Dispatcher
|
||||||
bot: R,
|
#[must_use = "`Polling` is an update listener and does nothing unless used"]
|
||||||
|
pub struct Polling<B: Requester> {
|
||||||
|
bot: B,
|
||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>,
|
||||||
limit: Option<u8>,
|
limit: Option<u8>,
|
||||||
allowed_updates: Option<Vec<AllowedUpdate>>,
|
allowed_updates: Option<Vec<AllowedUpdate>>,
|
||||||
) -> impl UpdateListener<R::Err>
|
drop_pending_updates: bool,
|
||||||
|
flag: AsyncStopFlag,
|
||||||
|
token: AsyncStopToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R> Polling<R>
|
||||||
where
|
where
|
||||||
R: Requester + Send + 'static,
|
R: Requester + Send + 'static,
|
||||||
<R as Requester>::GetUpdates: Send,
|
<R as Requester>::GetUpdates: Send,
|
||||||
{
|
{
|
||||||
struct State<B: Requester> {
|
/// Returns a builder for polling update listener.
|
||||||
bot: B,
|
pub fn builder(bot: R) -> PollingBuilder<R> {
|
||||||
timeout: Option<u32>,
|
PollingBuilder {
|
||||||
limit: Option<u8>,
|
bot,
|
||||||
allowed_updates: Option<Vec<AllowedUpdate>>,
|
timeout: None,
|
||||||
offset: i32,
|
limit: None,
|
||||||
flag: AsyncStopFlag,
|
allowed_updates: None,
|
||||||
token: AsyncStopToken,
|
drop_pending_updates: false,
|
||||||
force_stop: bool,
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stream<B>(st: &mut State<B>) -> impl Stream<Item = Result<Update, B::Err>> + Send + '_
|
|
||||||
where
|
|
||||||
B: Requester + Send,
|
|
||||||
<B as Requester>::GetUpdates: Send,
|
|
||||||
{
|
|
||||||
stream::unfold(st, move |state| async move {
|
|
||||||
let State { timeout, limit, allowed_updates, bot, offset, flag, force_stop, .. } =
|
|
||||||
&mut *state;
|
|
||||||
|
|
||||||
if *force_stop {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
if flag.is_stopped() {
|
|
||||||
let mut req = bot.get_updates().offset(*offset).timeout(0).limit(1);
|
|
||||||
req.payload_mut().allowed_updates = allowed_updates.take();
|
|
||||||
|
|
||||||
return match req.send().await {
|
|
||||||
Ok(_) => None,
|
|
||||||
Err(err) => {
|
|
||||||
// Prevents infinite retries, see https://github.com/teloxide/teloxide/issues/496
|
|
||||||
*force_stop = true;
|
|
||||||
|
|
||||||
Some((Either::Left(stream::once(ready(Err(err)))), state))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut req = bot.get_updates();
|
|
||||||
*req.payload_mut() = GetUpdates {
|
|
||||||
offset: Some(*offset),
|
|
||||||
timeout: *timeout,
|
|
||||||
limit: *limit,
|
|
||||||
allowed_updates: allowed_updates.take(),
|
|
||||||
};
|
|
||||||
|
|
||||||
match req.send().await {
|
|
||||||
Ok(updates) => {
|
|
||||||
// Set offset to the last update's id + 1
|
|
||||||
if let Some(upd) = updates.last() {
|
|
||||||
*offset = upd.id + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let updates = updates.into_iter().map(Ok);
|
|
||||||
Some((Either::Right(stream::iter(updates)), state))
|
|
||||||
}
|
|
||||||
Err(err) => Some((Either::Left(stream::once(ready(Err(err)))), state)),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.flatten()
|
|
||||||
}
|
|
||||||
|
|
||||||
let (token, flag) = AsyncStopToken::new_pair();
|
|
||||||
|
|
||||||
let state = State {
|
|
||||||
bot,
|
|
||||||
timeout: timeout.map(|t| t.as_secs().try_into().expect("timeout is too big")),
|
|
||||||
limit,
|
|
||||||
allowed_updates,
|
|
||||||
offset: 0,
|
|
||||||
flag,
|
|
||||||
token,
|
|
||||||
force_stop: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let stop_token = |st: &mut State<_>| st.token.clone();
|
|
||||||
|
|
||||||
let hint_allowed_updates =
|
|
||||||
Some(|state: &mut State<_>, allowed: &mut dyn Iterator<Item = AllowedUpdate>| {
|
|
||||||
// TODO: we should probably warn if there already were different allowed updates
|
|
||||||
// before
|
|
||||||
state.allowed_updates = Some(allowed.collect());
|
|
||||||
});
|
|
||||||
let timeout_hint = Some(move |_: &State<_>| timeout);
|
|
||||||
|
|
||||||
StatefulListener::new_with_hints(state, stream, stop_token, hint_allowed_updates, timeout_hint)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_webhook_if_setup<R>(requester: &R)
|
#[pin_project::pin_project]
|
||||||
where
|
pub struct PollingStream<'a, B: Requester> {
|
||||||
R: Requester,
|
/// Parent structure
|
||||||
{
|
polling: &'a mut Polling<B>,
|
||||||
let webhook_info = match requester.get_webhook_info().send().await {
|
|
||||||
Ok(ok) => ok,
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("Failed to get webhook info: {:?}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let is_webhook_setup = webhook_info.url.is_some();
|
/// Whatever to drop pending updates or not.
|
||||||
|
drop_pending_updates: bool,
|
||||||
|
|
||||||
if is_webhook_setup {
|
/// Timeout parameter for normal `get_updates()` calls.
|
||||||
if let Err(e) = requester.delete_webhook().send().await {
|
timeout: Option<u32>,
|
||||||
log::error!("Failed to delete a webhook: {:?}", e);
|
/// Allowed updates parameter for the first `get_updates()` call.
|
||||||
|
allowed_updates: Option<Vec<AllowedUpdate>>,
|
||||||
|
/// Offset parameter for normal `get_updates()` calls.
|
||||||
|
offset: i32,
|
||||||
|
|
||||||
|
/// If this is set, return `None` from `poll_next` immediately.
|
||||||
|
force_stop: bool,
|
||||||
|
/// If true we've sent last `get_updates()` call for graceful shutdown.
|
||||||
|
stopping: bool,
|
||||||
|
|
||||||
|
/// Buffer of updates to be yielded.
|
||||||
|
buffer: vec::IntoIter<Update>,
|
||||||
|
|
||||||
|
/// In-flight `get_updates()` call.
|
||||||
|
#[pin]
|
||||||
|
in_flight: Option<<B::GetUpdates as Request>::Send>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: Requester + Send + 'static> UpdateListener<B::Err> for Polling<B> {
|
||||||
|
type StopToken = AsyncStopToken;
|
||||||
|
|
||||||
|
fn stop_token(&mut self) -> Self::StopToken {
|
||||||
|
self.token.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hint_allowed_updates(&mut self, hint: &mut dyn Iterator<Item = AllowedUpdate>) {
|
||||||
|
// TODO: we should probably warn if there already were different allowed updates
|
||||||
|
// before
|
||||||
|
self.allowed_updates = Some(hint.collect());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timeout_hint(&self) -> Option<Duration> {
|
||||||
|
self.timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, B: Requester + Send + 'a> AsUpdateStream<'a, B::Err> for Polling<B> {
|
||||||
|
type Stream = PollingStream<'a, B>;
|
||||||
|
|
||||||
|
fn as_stream(&'a mut self) -> Self::Stream {
|
||||||
|
let timeout = self.timeout.map(|t| t.as_secs().try_into().expect("timeout is too big"));
|
||||||
|
let allowed_updates = self.allowed_updates.clone();
|
||||||
|
let drop_pending_updates = self.drop_pending_updates;
|
||||||
|
PollingStream {
|
||||||
|
polling: self,
|
||||||
|
drop_pending_updates,
|
||||||
|
timeout,
|
||||||
|
allowed_updates,
|
||||||
|
offset: 0,
|
||||||
|
force_stop: false,
|
||||||
|
stopping: false,
|
||||||
|
buffer: Vec::new().into_iter(),
|
||||||
|
in_flight: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<B: Requester> Stream for PollingStream<'_, B> {
|
||||||
|
type Item = Result<Update, B::Err>;
|
||||||
|
|
||||||
|
fn poll_next(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
|
let mut this = self.as_mut().project();
|
||||||
|
|
||||||
|
if *this.force_stop {
|
||||||
|
return Ready(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poll in-flight future until completion
|
||||||
|
if let Some(in_flight) = this.in_flight.as_mut().as_pin_mut() {
|
||||||
|
let res = ready!(in_flight.poll(cx));
|
||||||
|
this.in_flight.set(None);
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(_) if *this.stopping => return Ready(None),
|
||||||
|
Err(err) if *this.stopping => {
|
||||||
|
// Prevents infinite retries, see https://github.com/teloxide/teloxide/issues/496
|
||||||
|
*this.force_stop = true;
|
||||||
|
|
||||||
|
return Ready(Some(Err(err)));
|
||||||
|
}
|
||||||
|
Ok(updates) => {
|
||||||
|
if let Some(upd) = updates.last() {
|
||||||
|
*this.offset = upd.id + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
match *this.drop_pending_updates {
|
||||||
|
false => *this.buffer = updates.into_iter(),
|
||||||
|
true => *this.drop_pending_updates = false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => return Ready(Some(Err(err))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are any buffered updates, return one
|
||||||
|
if let Some(upd) = this.buffer.next() {
|
||||||
|
return Ready(Some(Ok(upd)));
|
||||||
|
}
|
||||||
|
|
||||||
|
*this.stopping = this.polling.flag.is_stopped();
|
||||||
|
let (offset, limit, timeout) = match (this.stopping, this.drop_pending_updates) {
|
||||||
|
// Normal `get_updates()` call
|
||||||
|
(false, false) => (*this.offset, this.polling.limit, *this.timeout),
|
||||||
|
// Graceful shutdown `get_updates()` call (shutdown takes priority over dropping pending
|
||||||
|
// updates)
|
||||||
|
//
|
||||||
|
// When stopping we set `timeout = 0` and `limit = 1` so that `get_updates()`
|
||||||
|
// set last seen update (offset) and return immediately
|
||||||
|
(true, _) => (*this.offset, Some(1), Some(0)),
|
||||||
|
// Drop pending updates
|
||||||
|
(_, true) => (-1, Some(1), Some(0)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let req = this
|
||||||
|
.polling
|
||||||
|
.bot
|
||||||
|
.get_updates()
|
||||||
|
.with_payload_mut(|pay| {
|
||||||
|
pay.offset = Some(offset);
|
||||||
|
pay.timeout = timeout;
|
||||||
|
pay.limit = limit;
|
||||||
|
pay.allowed_updates = this.allowed_updates.take();
|
||||||
|
})
|
||||||
|
.send();
|
||||||
|
this.in_flight.set(Some(req));
|
||||||
|
|
||||||
|
// Recurse to poll `self.in_flight`
|
||||||
|
self.poll_next(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn polling_is_send() {
|
fn polling_is_send() {
|
||||||
use crate::dispatching::update_listeners::AsUpdateStream;
|
|
||||||
|
|
||||||
let bot = crate::Bot::new("TOKEN");
|
let bot = crate::Bot::new("TOKEN");
|
||||||
|
#[allow(deprecated)]
|
||||||
let mut polling = polling(bot, None, None, None);
|
let mut polling = polling(bot, None, None, None);
|
||||||
|
|
||||||
assert_send(&polling);
|
assert_send(&polling);
|
||||||
|
|
Loading…
Reference in a new issue