Skip to main content

rsynko_manager/
transfer.rs

1use alux_ext::ext;
2use std::path::Path;
3use std::time::Duration;
4
5/// Specifies what one transfer states about its own progress.
6pub trait TransferViewAlg {
7    /// Observes retrieved bytes.
8    fn transferred(&self) -> u64;
9    /// Observes expected bytes when the source states them.
10    fn transfer_total(&self) -> Option<u64>;
11    /// Observes elapsed transfer time.
12    fn transfer_elapsed(&self) -> Duration;
13    /// Observes whether the transfer reached terminal success.
14    fn transfer_complete(&self) -> bool;
15    /// Observes the path publication will produce once an interpreter states one.
16    fn transfer_destination(&self) -> Option<&Path>;
17    /// Observes the concise reason a transfer failed.
18    fn transfer_summary(&self) -> Option<&str>;
19    /// Observes the diagnostic accompanying a failure.
20    fn transfer_detail(&self) -> Option<&str>;
21}
22
23/// Derives rate, remaining time, and completed share from observed bytes and elapsed time.
24#[ext(name = TransferProgressExt)]
25pub impl<This> This
26where
27    This: TransferViewAlg,
28{
29    /// Derives average retrieved bytes per second once progress has duration.
30    fn bytes_per_second(&self) -> Option<u64> {
31        let elapsed = self.transfer_elapsed().as_nanos();
32        let transferred = self.transferred();
33        if transferred == 0 || elapsed == 0 {
34            return None;
35        }
36        let rate = u128::from(transferred).saturating_mul(1_000_000_000) / elapsed;
37        Some(u64::try_from(rate.max(1)).unwrap_or(u64::MAX))
38    }
39
40    /// Derives remaining time from average progress once the expected byte count is known.
41    fn estimated_remaining(&self) -> Option<Duration> {
42        let total = self.transfer_total()?;
43        let elapsed = self.transfer_elapsed().as_nanos();
44        let transferred = self.transferred();
45        if transferred == 0 || elapsed == 0 {
46            return None;
47        }
48        let remaining = total.saturating_sub(transferred);
49        let nanos = u128::from(remaining).saturating_mul(elapsed) / u128::from(transferred);
50        Some(Duration::from_nanos(u64::try_from(nanos).unwrap_or(u64::MAX)))
51    }
52
53    /// Derives the completed share as whole percent once it is known.
54    fn percent(&self) -> Option<u16> {
55        if self.transfer_complete() {
56            return Some(100);
57        }
58        let total = self.transfer_total()?;
59        if total == 0 {
60            return Some(0);
61        }
62        let percent = u128::from(self.transferred()).saturating_mul(100) / u128::from(total);
63        Some(u16::try_from(percent.min(100)).unwrap_or(100))
64    }
65}
66
67/// Denotes the lifecycle phase of one queued transfer.
68#[derive(Clone, Copy, Debug, PartialEq, Eq)]
69pub enum TransferPhase {
70    /// Denotes an entry ready to be scheduled.
71    Ready,
72    /// Denotes an enabled entry waiting for an interpreter to schedule it.
73    Waiting,
74    /// Denotes an interpreter stating what the request would do instead of doing it.
75    Rehearsing,
76    /// Denotes extraction and format selection.
77    Extracting,
78    /// Denotes incremental resource retrieval.
79    Downloading,
80    /// Denotes a cooperatively suspended active transfer.
81    Paused,
82    /// Denotes final atomic publication.
83    Publishing,
84    /// Denotes successful terminal completion.
85    Complete,
86    /// Denotes terminal failure.
87    Failed,
88}
89
90impl TransferPhase {
91    /// States every phase one transfer passes through, in lifecycle order.
92    pub const LIFECYCLE: [Self; 9] = [
93        Self::Ready,
94        Self::Waiting,
95        Self::Rehearsing,
96        Self::Extracting,
97        Self::Downloading,
98        Self::Paused,
99        Self::Publishing,
100        Self::Complete,
101        Self::Failed,
102    ];
103
104    /// States whether a run is happening on the request's behalf while it is in this phase.
105    ///
106    /// A request that has not been asked for, and one that is over, are both unattended: nothing
107    /// is working on their behalf, and nothing has to be told they were removed.
108    pub const fn is_running(self) -> bool {
109        matches!(self, Self::Rehearsing | Self::Extracting | Self::Downloading | Self::Paused | Self::Publishing)
110    }
111}