1mod estimate;
28
29pub use estimate::{OverallEstimate, OverallEstimator, PhaseRates};
30
31#[non_exhaustive]
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37pub enum ProgressPhase {
38 ReceivingObjects,
40 IndexingObjects,
42 ResolvingDeltas,
44 CopyingObjects,
46 CheckingOutFiles,
48}
49
50impl ProgressPhase {
51 #[must_use]
53 pub fn default_unit(self) -> ProgressUnit {
54 match self {
55 Self::ReceivingObjects
56 | Self::IndexingObjects
57 | Self::ResolvingDeltas
58 | Self::CopyingObjects => ProgressUnit::Objects,
59 Self::CheckingOutFiles => ProgressUnit::Files,
60 }
61 }
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
66pub enum ProgressUnit {
67 Objects,
69 Bytes,
71 Files,
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
81pub struct ReceivedObjects {
82 pub received: u64,
84 pub total: u64,
86 pub deltas: u64,
88}
89
90pub trait ProgressSink {
97 fn start(&mut self, phase: ProgressPhase, total: Option<u64>) {
104 let _ = (phase, total);
105 }
106
107 fn start_with_unit(&mut self, phase: ProgressPhase, unit: ProgressUnit, total: Option<u64>) {
113 let _ = unit;
114 self.start(phase, total);
115 }
116
117 fn inc(&mut self, units: u64) {
119 let _ = units;
120 }
121
122 fn set(&mut self, current: u64) {
124 let _ = current;
125 }
126
127 fn finish(&mut self) {}
129
130 fn should_cancel(&self) -> bool {
136 false
137 }
138}
139
140impl<T: ProgressSink + ?Sized> ProgressSink for &mut T {
143 fn start(&mut self, phase: ProgressPhase, total: Option<u64>) {
144 (**self).start(phase, total);
145 }
146 fn start_with_unit(&mut self, phase: ProgressPhase, unit: ProgressUnit, total: Option<u64>) {
147 (**self).start_with_unit(phase, unit, total);
148 }
149 fn inc(&mut self, units: u64) {
150 (**self).inc(units);
151 }
152 fn set(&mut self, current: u64) {
153 (**self).set(current);
154 }
155 fn finish(&mut self) {
156 (**self).finish();
157 }
158 fn should_cancel(&self) -> bool {
159 (**self).should_cancel()
160 }
161}
162
163#[derive(Debug, Default, Clone, Copy)]
166pub struct NullProgress;
167
168impl ProgressSink for NullProgress {}
169
170pub trait Cancel {
173 fn is_cancelled(&self) -> bool;
175
176 fn shared_watch(&self) -> Option<std::sync::Arc<std::sync::atomic::AtomicBool>> {
179 None
180 }
181}
182
183#[derive(Debug, Default, Clone, Copy)]
186pub struct NeverCancel;
187
188impl Cancel for NeverCancel {
189 fn is_cancelled(&self) -> bool {
190 false
191 }
192}
193
194#[derive(Debug, Clone)]
196pub struct FlagCancel(pub std::sync::Arc<std::sync::atomic::AtomicBool>);
197
198impl FlagCancel {
199 #[must_use]
201 pub fn new() -> Self {
202 Self(std::sync::Arc::new(std::sync::atomic::AtomicBool::new(
203 false,
204 )))
205 }
206
207 pub fn cancel(&self) {
209 self.0.store(true, std::sync::atomic::Ordering::Relaxed);
210 }
211}
212
213impl Default for FlagCancel {
214 fn default() -> Self {
215 Self::new()
216 }
217}
218
219impl Cancel for FlagCancel {
220 fn is_cancelled(&self) -> bool {
221 self.0.load(std::sync::atomic::Ordering::Relaxed)
222 }
223
224 fn shared_watch(&self) -> Option<std::sync::Arc<std::sync::atomic::AtomicBool>> {
225 Some(std::sync::Arc::clone(&self.0))
226 }
227}
228
229#[must_use]
235pub fn cancel_watch_arc(cancel: &dyn Cancel) -> std::sync::Arc<std::sync::atomic::AtomicBool> {
236 cancel.shared_watch().unwrap_or_else(|| {
237 std::sync::Arc::new(std::sync::atomic::AtomicBool::new(cancel.is_cancelled()))
238 })
239}
240
241#[inline]
243pub fn poll_watch(watch: &std::sync::atomic::AtomicBool) -> crate::error::Result<()> {
244 if watch.load(std::sync::atomic::Ordering::Relaxed) {
245 Err(crate::error::Error::Cancelled)
246 } else {
247 Ok(())
248 }
249}
250
251#[inline]
253pub fn poll_cancel_watch(
254 progress: &mut dyn ProgressSink,
255 cancel: &dyn Cancel,
256 watch: &std::sync::atomic::AtomicBool,
257) -> crate::error::Result<()> {
258 if cancel.is_cancelled() || progress.should_cancel() {
259 watch.store(true, std::sync::atomic::Ordering::Relaxed);
260 return Err(crate::error::Error::Cancelled);
261 }
262 poll_watch(watch)
263}
264
265pub struct WatchProgress<'a> {
267 inner: &'a mut dyn ProgressSink,
268 watch: std::sync::Arc<std::sync::atomic::AtomicBool>,
269}
270
271impl<'a> WatchProgress<'a> {
272 #[must_use]
274 pub fn new(
275 inner: &'a mut dyn ProgressSink,
276 watch: std::sync::Arc<std::sync::atomic::AtomicBool>,
277 ) -> Self {
278 Self { inner, watch }
279 }
280
281 fn note_cancel(&self) {
282 if self.inner.should_cancel() {
283 self.watch.store(true, std::sync::atomic::Ordering::Relaxed);
284 }
285 }
286}
287
288impl ProgressSink for WatchProgress<'_> {
289 fn start(&mut self, phase: ProgressPhase, total: Option<u64>) {
290 self.note_cancel();
291 self.inner.start(phase, total);
292 }
293 fn start_with_unit(&mut self, phase: ProgressPhase, unit: ProgressUnit, total: Option<u64>) {
294 self.note_cancel();
295 self.inner.start_with_unit(phase, unit, total);
296 }
297 fn inc(&mut self, units: u64) {
298 self.note_cancel();
299 self.inner.inc(units);
300 }
301 fn set(&mut self, current: u64) {
302 self.note_cancel();
303 self.inner.set(current);
304 }
305 fn finish(&mut self) {
306 self.note_cancel();
307 self.inner.finish();
308 }
309 fn should_cancel(&self) -> bool {
310 self.inner.should_cancel()
311 }
312}
313
314#[inline]
316pub fn poll_cancel(
317 progress: &mut dyn ProgressSink,
318 cancel: &dyn Cancel,
319) -> crate::error::Result<()> {
320 if cancel.is_cancelled() || progress.should_cancel() {
321 Err(crate::error::Error::Cancelled)
322 } else {
323 Ok(())
324 }
325}
326
327pub struct CancelProgress<'a> {
332 inner: &'a mut dyn ProgressSink,
333 cancel: &'a dyn Cancel,
334 stopped: std::cell::Cell<bool>,
335}
336
337impl<'a> CancelProgress<'a> {
338 pub fn new(inner: &'a mut dyn ProgressSink, cancel: &'a dyn Cancel) -> Self {
340 Self {
341 inner,
342 cancel,
343 stopped: std::cell::Cell::new(false),
344 }
345 }
346
347 #[must_use]
349 pub fn stopped(&self) -> bool {
350 self.stopped.get()
351 }
352
353 pub fn finish_poll(&self) -> crate::error::Result<()> {
355 if self.stopped() {
356 Err(crate::error::Error::Cancelled)
357 } else {
358 Ok(())
359 }
360 }
361
362 fn note_stop(&self) {
363 if self.cancel.is_cancelled() || self.inner.should_cancel() {
364 self.stopped.set(true);
365 }
366 }
367}
368
369impl ProgressSink for CancelProgress<'_> {
370 fn start(&mut self, phase: ProgressPhase, total: Option<u64>) {
371 self.note_stop();
372 if !self.stopped.get() {
373 self.inner.start(phase, total);
374 }
375 }
376
377 fn start_with_unit(&mut self, phase: ProgressPhase, unit: ProgressUnit, total: Option<u64>) {
378 self.note_stop();
379 if !self.stopped.get() {
380 self.inner.start_with_unit(phase, unit, total);
381 }
382 }
383
384 fn inc(&mut self, units: u64) {
385 self.note_stop();
386 if !self.stopped.get() {
387 self.inner.inc(units);
388 }
389 }
390
391 fn set(&mut self, current: u64) {
392 self.note_stop();
393 if !self.stopped.get() {
394 self.inner.set(current);
395 }
396 }
397
398 fn finish(&mut self) {
399 self.note_stop();
400 if !self.stopped.get() {
401 self.inner.finish();
402 }
403 }
404
405 fn should_cancel(&self) -> bool {
406 self.stopped.get() || self.cancel.is_cancelled() || self.inner.should_cancel()
407 }
408}
409
410#[cfg(test)]
411mod tests {
412 use super::*;
413 use crate::fetch::{NoProgress, Progress};
414
415 #[derive(Default)]
418 struct PhaseRecorder {
419 started: Vec<(ProgressPhase, Option<u64>)>,
420 incs: u64,
421 finished: usize,
422 }
423
424 impl ProgressSink for PhaseRecorder {
425 fn start(&mut self, phase: ProgressPhase, total: Option<u64>) {
426 self.started.push((phase, total));
427 }
428 fn inc(&mut self, units: u64) {
429 self.incs += units;
430 }
431 fn finish(&mut self) {
432 self.finished += 1;
433 }
434 }
435
436 fn drive_typed(sink: &mut impl ProgressSink) {
437 sink.start(ProgressPhase::ReceivingObjects, Some(3));
438 sink.inc(1);
439 sink.inc(2);
440 sink.finish();
441 }
442
443 #[test]
444 fn null_progress_is_inert() {
445 drive_typed(&mut NullProgress);
446 }
447
448 #[test]
449 fn no_progress_is_inert() {
450 drive_typed(&mut NoProgress);
451 }
452
453 #[test]
454 fn typed_phase_recorder_roundtrip() {
455 let mut rec = PhaseRecorder::default();
456 drive_typed(&mut &mut rec);
457 assert_eq!(
458 rec.started,
459 vec![(ProgressPhase::ReceivingObjects, Some(3))]
460 );
461 assert_eq!(rec.incs, 3);
462 assert_eq!(rec.finished, 1);
463 }
464
465 #[test]
466 fn progress_supertrait_upcasts() {
467 struct SidebandCap(Vec<u8>);
468
469 impl ProgressSink for SidebandCap {}
470
471 impl Progress for SidebandCap {
472 fn message(&mut self, bytes: &[u8]) {
473 self.0.extend_from_slice(bytes);
474 }
475 }
476
477 fn takes_sink(sink: &mut dyn ProgressSink) {
478 sink.start(ProgressPhase::IndexingObjects, None);
479 sink.finish();
480 }
481
482 let mut cap = SidebandCap(Vec::new());
483 let progress: &mut dyn Progress = &mut cap;
484 takes_sink(progress);
485 }
486
487 #[test]
488 fn never_cancel_never_cancels() {
489 assert!(!NeverCancel.is_cancelled());
490 }
491
492 #[test]
493 fn poll_cancel_returns_typed_error() {
494 let flag = FlagCancel::new();
495 flag.cancel();
496 let mut sink = NullProgress;
497 let err = poll_cancel(&mut sink, &flag).expect_err("cancelled");
498 assert!(matches!(err, crate::error::Error::Cancelled));
499 }
500
501 #[test]
502 fn phase_default_unit() {
503 assert_eq!(
504 ProgressPhase::ReceivingObjects.default_unit(),
505 ProgressUnit::Objects
506 );
507 assert_eq!(
508 ProgressPhase::CheckingOutFiles.default_unit(),
509 ProgressUnit::Files
510 );
511 }
512}