1use vissue_core::config::Layout;
4use vissue_core::error::Error;
5use vissue_core::views::{
6 AgendaRow, ClaimRow, Excerpt, IssueDetail, ListQuery, RelatedHit, SearchHit, TreeNode,
7};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum BackendKind {
12 Core,
13 Control,
14}
15
16#[derive(Debug, Clone, Default)]
18pub struct ListPage {
19 pub issues: Vec<vissue_core::views::IssueRow>,
20 pub total: u64,
21 pub matched: u64,
22 pub revision: u64,
23 pub generation: u64,
24 pub unchanged: bool,
25}
26
27#[derive(Debug, Clone)]
29pub struct MutResult {
30 pub ok: bool,
31 pub report: String,
32 pub issue: Option<IssueDetail>,
33 pub revision: u64,
34 pub generation: u64,
35}
36
37#[derive(Debug, Clone, Default)]
39pub struct UpdateReq {
40 pub id: String,
41 pub state: Option<String>,
42 pub priority: Option<char>,
43 pub block: Option<String>,
44 pub unblock: Option<String>,
45}
46
47#[derive(Debug)]
49pub struct SinceGate {
50 skip_once: std::sync::atomic::AtomicBool,
51}
52
53impl SinceGate {
54 pub fn after_attach() -> Self {
56 Self {
57 skip_once: std::sync::atomic::AtomicBool::new(true),
58 }
59 }
60
61 pub fn next(&self, revision: u64) -> Option<u64> {
62 if self
63 .skip_once
64 .swap(false, std::sync::atomic::Ordering::SeqCst)
65 {
66 None
67 } else if revision > 0 {
68 Some(revision)
69 } else {
70 None
71 }
72 }
73
74 pub fn invalidate(&self) {
76 self.skip_once
77 .store(true, std::sync::atomic::Ordering::SeqCst);
78 }
79}
80
81pub trait BoardBackend: Send + Sync {
84 fn layout(&self) -> &Layout;
85 fn generation(&self) -> u64;
86 fn revision(&self) -> u64;
87 fn live(&self) -> BackendKind;
88 fn identity(&self) -> &str;
89
90 fn list(&self, q: ListQuery) -> Result<ListPage, Error>;
91 fn ready(&self, project: Option<&str>) -> Result<ListPage, Error>;
92 fn get(&self, id: &str) -> Result<IssueDetail, Error>;
93 fn excerpt(&self, id: &str) -> Result<Excerpt, Error>;
94 fn search(&self, query: &str, limit: usize) -> Result<Vec<SearchHit>, Error>;
95 fn claims(&self, holder: Option<&str>, project: Option<&str>) -> Result<Vec<ClaimRow>, Error>;
96 fn agenda(&self, days: i64, project: Option<&str>) -> Result<Vec<AgendaRow>, Error>;
97 fn tree(&self, id: &str) -> Result<TreeNode, Error>;
98 fn related(&self, id: &str, depth: usize, limit: usize) -> Result<Vec<RelatedHit>, Error>;
99 fn projects(&self) -> Result<Vec<String>, Error>;
100 fn claim(&self, id: &str, force: bool) -> Result<MutResult, Error>;
101 fn note(&self, id: &str, text: &str) -> Result<MutResult, Error>;
102 fn update(&self, req: UpdateReq) -> Result<MutResult, Error>;
103 fn open(&self, id: &str) -> Result<IssueDetail, Error>;
104
105 fn wait(&self, last: u64, timeout_ms: u64) -> Result<u64, Error>;
107
108 fn last_since_revision(&self) -> Option<Option<u64>> {
111 None
112 }
113
114 fn invalidate_since(&self) {}
117
118 fn refresh(&self) -> Result<(), Error> {
121 Ok(())
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::SinceGate;
128
129 #[test]
130 fn after_attach_first_list_omits_since_revision() {
131 let gate = SinceGate::after_attach();
132 assert_eq!(gate.next(7), None);
133 assert_eq!(gate.next(7), Some(7));
134 assert_eq!(gate.next(8), Some(8));
135 }
136
137 #[test]
138 fn a_zero_revision_never_sends_since() {
139 let gate = SinceGate::after_attach();
140 assert_eq!(gate.next(0), None);
141 assert_eq!(gate.next(0), None);
142 }
143
144 #[test]
145 fn invalidate_omits_the_next_since() {
146 let gate = SinceGate::after_attach();
147 assert_eq!(gate.next(7), None);
148 assert_eq!(gate.next(7), Some(7));
149 gate.invalidate();
150 assert_eq!(gate.next(7), None);
151 assert_eq!(gate.next(7), Some(7));
152 }
153}