chiark / gitweb /
changelog: document further make-release changes
[otter.git] / support / progress.rs
1 // Copyright 2020-2021 Ian Jackson and contributors to Otter
2 // SPDX-License-Identifier: AGPL-3.0-or-later
3 // There is NO WARRANTY.
4
5 use crate::prelude::*;
6
7 #[derive(Debug,Clone,Serialize,Deserialize,IntoOwned)]
8 pub struct ProgressInfo<'pi> {
9   pub phase: Count<'pi>,
10   pub item:  Count<'pi>,
11 }
12
13 #[derive(Debug,Clone,Serialize,Deserialize,IntoOwned)]
14 pub struct Count<'pi> {
15   pub value: Value,
16   pub desc: Cow<'pi, str>,
17 }
18
19 #[derive(Debug,Clone,Serialize,Deserialize,IntoOwned)]
20 #[derive(Educe)]
21 #[educe(Default)]
22 pub enum Value {
23   #[educe(Default)] Exact {
24     i: usize,
25     n: usize,
26   },
27   Fraction {
28     f: f32, // [0..1]
29   }
30 }
31
32 impl Value {
33   pub fn fraction(&self) -> f32 {
34     match self {
35       &Value::Exact{ i:_, n } if n == 0 => 0.,
36       &Value::Exact{ i, n } => (i as f32) / (n as f32),
37       &Value::Fraction{ f } => f,
38     }
39   }
40 }
41
42 pub trait Originator {
43   fn report(&mut self, info: ProgressInfo<'_>);
44   fn phase_begin_(&mut self, phase: Count<'_>, len: usize);
45   fn item_(&mut self, item: usize, desc: Cow<'_, str>);
46 }
47
48 pub struct ResponseOriginator<'c,'w,W,F> where W: Write {
49   chan: &'c mut ResponseWriter<'w,W>,
50   phase: Count<'static>,
51   len: usize,
52   formatter: F,
53 }
54 impl<'c,'w,W,F> ResponseOriginator<'c,'w,W,F> where W: Write {
55   pub fn new(chan: &'c mut ResponseWriter<'w,W>,
56              formatter: F) -> Self {
57     Self {
58       chan,
59       phase: Count { value: default(), desc: Cow::Borrowed("") },
60       len: 0,
61       formatter,
62     }
63   }
64 }
65
66 impl<W,F,M> Originator for ResponseOriginator<'_,'_,W,F>
67 where W: Write,
68       F: Fn(ProgressInfo<'_>) -> M,
69       M: Serialize,
70 {
71   fn report(&mut self, pi: ProgressInfo<'_>) {
72     let resp = (self.formatter)(pi);
73     self.chan.progress_with(resp).unwrap_or(());
74   }
75   fn phase_begin_(&mut self, phase: Count<'_>, len: usize) {
76     self.phase = phase.into_owned();
77     self.len = len;
78   }
79   fn item_(&mut self, item: usize, desc: Cow<'_, str>) {
80     let value = Value::Exact { i: item, n: self.len };
81     self.report(ProgressInfo {
82       phase: self.phase.clone(),
83       item: Count { value, desc }
84     })
85   }
86 }
87
88 #[allow(unused_variables)]
89 impl Originator for () {
90   fn report(&mut self, pi: ProgressInfo<'_>) { }
91   fn phase_begin_(&mut self, phase: Count<'_>, len: usize) { }
92   fn item_(&mut self, item: usize, desc: Cow<'_, str>) { }
93 }
94
95 pub trait Enum: EnumCount + ToPrimitive + EnumMessage { }
96 impl<T> From<T> for Count<'static> where T: Enum {
97   fn from(t: T) -> Count<'static> {
98     let value = Value::Exact { 
99       i: t.to_usize().unwrap(),
100       n: T::COUNT - 1,
101     };
102     Count {
103       value,
104       // Show be Borrowed  https://github.com/Peternator7/strum/issues/159
105       desc: Cow::Owned(t.get_message().unwrap_or("...").to_owned()),
106     }
107   }
108 }
109 impl<'t> From<&'t str> for Count<'t> {
110   fn from(s: &'t str) -> Count<'t> {
111     Count { value: default(), desc: Cow::Borrowed(s) }
112   }
113 }
114 impl From<String> for Count<'static> {
115   fn from(s: String) -> Count<'static> {
116     Count { value: default(), desc: Cow::Owned(s) }
117   }
118 }
119 impl<'t> From<()> for Count<'t> { fn from(_:()) -> Count<'t> {
120     Count { value: default(), desc: Cow::Borrowed("") }
121 } }
122
123 #[ext(pub, name=OriginatorExt)]
124 impl &'_ mut (dyn Originator + '_) {
125   fn phase_item<'p,'e,P,E>(&mut self, phase: P, item: E)
126   where P: Into<Count<'p>>,
127         E: Into<Count<'e>>,
128   {
129     let phase = phase.into();
130     let item  = item .into();
131     self.report(ProgressInfo { phase, item });
132   }
133
134   fn phase<'p,P>(&mut self, phase: P, len: usize)
135   where P: Into<Count<'p>>,
136   {
137     self.phase_begin_(phase.into(), len)
138   }
139
140   fn item<'s,S>(&mut self, item: usize, desc: S)
141   where S: Into<Cow<'s, str>>,
142   {
143     self.item_(item, desc.into())
144   }
145 }
146
147 pub struct ReadOriginator<'o,R:Read> {
148   r: R,
149   total_len: usize,
150   orig: &'o mut dyn Originator,
151   // state:
152   counter: usize,
153   last_report: usize,
154 }
155
156 impl<'oo,'o,R:Read> ReadOriginator<'o,R> {
157   pub fn new<'p,P>(mut orig: &'o mut dyn Originator, phase: P,
158                    total_len: usize, r: R) -> Self
159   where P: Into<Count<'p>>
160   {
161     orig.phase(phase, total_len);
162     let mut ro = ReadOriginator {
163       r, orig, total_len,
164       counter: 0,
165       last_report: 0,
166     };
167     ro.report();
168     ro
169   }
170
171   fn report(&mut self) {
172     let t = self.total_len.to_string();
173     let c = format!("{:>width$}", self.counter, width=t.len());
174     let m = |s: String| {
175       izip!( iter::once("").chain(["",""," "].iter().cloned().cycle()),
176              s.chars().rev() )
177         .map(|(s,c)| [s.chars().next(), Some(c)])
178         .flatten()
179         .flatten()
180         .collect::<String>()
181         .chars().rev()
182         .collect::<String>()
183     };
184     let desc = format!(" {} of {}", m(c), m(t));
185     self.orig.item(self.counter, desc);
186     self.last_report = self.counter;
187   }
188 }
189
190 impl<R:Read> Read for ReadOriginator<'_,R> {
191   #[throws(io::Error)]
192   fn read(&mut self, buf: &mut [u8]) -> usize {
193     let got = self.r.read(buf)?;
194     self.counter += got;
195     if self.counter - self.last_report > 10000 {
196       self.report();
197     }
198     got
199   }
200 }