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
130
131
//! 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::{Cmds, 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) -> Cmds {
Cmds::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,
});
}
/// Opens `route` in place of the page on screen, fetched again, to wait
/// there for GitHub to show `what`.
pub fn replace(state: &mut State, route: Route, what: impl Expectation) -> Cmds {
expect(state, route.clone(), what);
state.replace(route, true, None)
}
/// 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.
pub fn follow(state: &mut State) -> Cmds {
let Some(f) = following(state) else { return Cmds::new() };
let polls = f.polls;
match f.what.shown(state, &f.route) {
Shown::No => Cmds::new(),
Shown::Yes => {
let Some(f) = state.following.take() else { return Cmds::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)
}
}
}