1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
//! Following GitHub until the page shows a change it accepted. GitHub
//! can take a while (a cancel, a minute or more): while the page the
//! change went to is on screen, what shows it is fetched again at the
//! live refresh's pace, until the page shows it or [`POLLS`] go by.
use std::any::Any;
use crate::data::DataKey;
use crate::keymap::Action;
use crate::route::Route;
use crate::state::{Cmd, State};
/// What a page shows once GitHub has a change.
pub trait Expectation: Any + std::fmt::Debug + Send + Sync {
/// What's fetched again to see it: the page's needs.
fn needs(&self, state: &State, route: &Route) -> Vec<DataKey> {
state.needs(route)
}
/// What the status bar says meanwhile.
fn waiting(&self) -> &'static str;
/// Whether `route`'s page, as loaded, shows it.
fn shown(&self, state: &State, route: &Route) -> Shown;
/// Once it's shown: what follows.
fn then(&self, _state: &mut State, _route: &Route) -> Vec<Cmd> {
Vec::new()
}
}
/// Whether the page shows a change.
pub enum Shown {
No,
Yes,
/// It's on another page, followed there: a run's latest attempt, a
/// job's re-run.
There(Route, Box<dyn Expectation>),
}
impl From<bool> for Shown {
fn from(shown: bool) -> Self {
if shown { Shown::Yes } else { Shown::No }
}
}
/// A change GitHub accepted that the page it went to doesn't show yet.
#[derive(Debug)]
pub struct Following {
route: Route,
what: Box<dyn Expectation>,
polls: u32,
}
/// How many times a page is fetched again for a change before giving up:
/// a few minutes, at the live refresh's pace.
const POLLS: u32 = 40;
/// Waits, on `route`, for GitHub to show `what`.
pub fn expect(state: &mut State, route: Route, what: impl Expectation) {
state.following = Some(Following {
route,
what: Box::new(what),
polls: 0,
});
}
/// What the page on screen waits for GitHub to show, if anything.
fn following(state: &State) -> Option<&Following> {
(state.following.as_ref()).filter(|f| state.route() == Some(&f.route))
}
/// What the page on screen waits for GitHub to show, if it's a `T`.
pub fn here<T: Expectation>(state: &State) -> Option<&T> {
(following(state)?.what.as_ref() as &dyn Any).downcast_ref()
}
/// What the status bar says while GitHub hasn't shown a change.
pub fn waiting(state: &State) -> Option<&'static str> {
following(state).map(|f| f.what.waiting())
}
/// What the live refresh fetches again for the change the page waits to
/// see. Past [`POLLS`], it stops waiting.
pub fn poll(state: &mut State) -> Vec<DataKey> {
let Some(needs) = following(state).map(|f| f.what.needs(state, &f.route)) else {
return Vec::new();
};
if let Some(f) = &mut state.following {
f.polls += 1;
if f.polls > POLLS {
state.following = None;
let key = state.first_key(Action::Refresh);
state.info(format!(
"GitHub doesn't show the change yet: {key} fetches the page again"
));
return Vec::new();
}
}
needs
}
/// After GitHub's data arrives: stops waiting for a change the page now
/// shows, or follows it to the page it's on.
#[must_use]
pub fn follow(state: &mut State) -> Vec<Cmd> {
let Some(f) = following(state) else {
return Vec::new();
};
let polls = f.polls;
match f.what.shown(state, &f.route) {
Shown::No => Vec::new(),
Shown::Yes => {
let Some(f) = state.following.take() else {
return Vec::new();
};
f.what.then(state, &f.route)
}
Shown::There(route, what) => {
let followed = Following {
route: route.clone(),
what,
polls,
};
state.following = Some(followed);
state.replace(route, false, None)
}
}
}