Skip to main content

rsynko_memory/
options.rs

1//! The layout of one request's fixed choices and of the formats discovered for it.
2
3use crate::{Format, InfoValue, MediaSyntax, predicate_accepts};
4use rsynko_manager::{MediaStreams, MediaStreamsExt, Performer};
5use rsynko_rsync::SyncProfile;
6use rsynko_ui::FormatDescriptionAlg;
7
8/// Denotes editable choices for one not-yet-started download.
9#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct DownloadOptions {
11    pub(crate) media_streams: Option<MediaStreams>,
12    pub(crate) format: FormatChoice,
13    pub(crate) profile: Option<SyncProfile>,
14    pub(crate) dry_run: Option<bool>,
15}
16
17impl DownloadOptions {
18    /// Selects the best progressive format containing audio and video.
19    #[must_use]
20    pub const fn progressive() -> Self {
21        Self {
22            media_streams: Some(MediaStreams::AudioVideo),
23            format: FormatChoice::Best,
24            profile: None,
25            dry_run: None,
26        }
27    }
28
29    /// Selects the best audio-only format.
30    #[must_use]
31    pub const fn audio() -> Self {
32        Self {
33            media_streams: Some(MediaStreams::Audio),
34            format: FormatChoice::Best,
35            profile: None,
36            dry_run: None,
37        }
38    }
39
40    /// Selects the best video-only format.
41    #[must_use]
42    pub const fn video() -> Self {
43        Self {
44            media_streams: Some(MediaStreams::Video),
45            format: FormatChoice::Best,
46            profile: None,
47            dry_run: None,
48        }
49    }
50
51    /// Selects one transferred path, rehearsed before it is transferred.
52    ///
53    /// A transfer states what it would do before it does it, so it begins in rehearsal mode and
54    /// only leaves it when someone turns that mode off. What is transferred may be one file or a
55    /// whole folder; the request is the same either way.
56    #[must_use]
57    pub const fn transfer() -> Self {
58        Self {
59            media_streams: None,
60            format: FormatChoice::Best,
61            profile: Some(SyncProfile::Copy),
62            dry_run: Some(true),
63        }
64    }
65
66    /// Observes the way a path is transferred, and that a media item is transferred no way.
67    #[must_use]
68    pub const fn profile(&self) -> Option<SyncProfile> {
69        self.profile
70    }
71
72    /// Observes what performs the request these options belong to.
73    ///
74    /// A way of transferring names a program to run; everything else is retrieved from the source
75    /// that claimed the line.
76    #[must_use]
77    pub const fn performer(&self) -> Performer {
78        if self.profile.is_some() {
79            Performer::Program
80        } else {
81            Performer::Retrieval
82        }
83    }
84
85    /// Selects the way a path is transferred, for a request that transfers one.
86    #[must_use]
87    pub const fn with_profile(mut self, profile: SyncProfile) -> Self {
88        if self.profile.is_some() {
89            self.profile = Some(profile);
90        }
91        self
92    }
93
94    /// Turns the rehearsal mode back on, for a request that has one.
95    ///
96    /// A request nobody has performed yet states what it would do before it does it, whether it
97    /// was submitted or duplicated from one somebody had already armed.
98    #[must_use]
99    pub const fn rehearsing(mut self) -> Self {
100        if self.dry_run.is_some() {
101            self.dry_run = Some(true);
102        }
103        self
104    }
105
106    /// Observes the rehearsal mode, and that the request has none when it has none.
107    #[must_use]
108    pub const fn dry_run(&self) -> Option<bool> {
109        self.dry_run
110    }
111
112    /// Selects the rehearsal mode of a request that has one.
113    pub const fn set_dry_run(&mut self, dry_run: bool) {
114        if self.dry_run.is_some() {
115            self.dry_run = Some(dry_run);
116        }
117    }
118
119    /// Observes the selected stream roles.
120    #[must_use]
121    pub const fn media_streams(&self) -> Option<MediaStreams> {
122        self.media_streams
123    }
124
125    /// Observes the selected format policy.
126    #[must_use]
127    pub const fn format(&self) -> &FormatChoice {
128        &self.format
129    }
130
131    /// Selects new stream roles and releases the concrete format identity.
132    #[must_use]
133    pub fn with_media_streams(mut self, media_streams: MediaStreams) -> Self {
134        self.media_streams = Some(media_streams);
135        self.format = FormatChoice::Best;
136        self
137    }
138
139    /// Selects one format policy without changing stream roles.
140    #[must_use]
141    pub fn with_format(mut self, format: FormatChoice) -> Self {
142        self.format = format;
143        self
144    }
145}
146
147/// Selects either the best matching format or one concrete format identity.
148#[derive(Clone, Debug, PartialEq, Eq)]
149pub enum FormatChoice {
150    /// Selects the most preferred format with the requested stream roles.
151    Best,
152    /// Selects one extractor-defined format identity.
153    Id(String),
154}
155
156/// Denotes discovery state for selectable formats.
157#[derive(Clone, Debug, PartialEq)]
158pub enum FormatCatalog {
159    /// Denotes a source whose formats have not been requested.
160    Unknown,
161    /// Denotes a request waiting for an interpreter to inspect the source.
162    Waiting,
163    /// Denotes source inspection currently in progress.
164    Inspecting,
165    /// Denotes the discovered formats in extractor preference order.
166    Available(Vec<Format>),
167    /// Denotes an inspection failure suitable for display.
168    Failed(String),
169}
170
171impl FormatCatalog {
172    /// Observes discovered formats when inspection succeeded.
173    #[must_use]
174    pub fn available(&self) -> Option<&[Format]> {
175        match self {
176            Self::Available(formats) => Some(formats),
177            Self::Unknown | Self::Waiting | Self::Inspecting | Self::Failed(_) => None,
178        }
179    }
180}
181
182/// Observes whether one described format carries exactly the streams a role requires.
183#[must_use]
184pub fn role_accepts(streams: MediaStreams, format: &Format) -> bool {
185    predicate_accepts(&MediaSyntax.stream_role_format(streams), format)
186}
187
188impl FormatDescriptionAlg for Format {
189    fn format_identity(&self) -> &str {
190        &self.id
191    }
192
193    fn format_extension(&self) -> Option<&str> {
194        self.extension()
195    }
196
197    fn format_streams(&self) -> Option<MediaStreams> {
198        match (self.has_audio(), self.has_video()) {
199            (true, true) => Some(MediaStreams::AudioVideo),
200            (true, false) => Some(MediaStreams::Audio),
201            (false, true) => Some(MediaStreams::Video),
202            (false, false) => None,
203        }
204    }
205
206    fn format_quality(&self) -> Option<&str> {
207        self.text("quality")
208    }
209
210    fn format_height(&self) -> Option<u64> {
211        format_count(self, "height")
212    }
213
214    fn format_width(&self) -> Option<u64> {
215        format_count(self, "width")
216    }
217
218    fn format_bitrate(&self) -> Option<u64> {
219        format_count(self, "bitrate")
220    }
221
222    fn format_size(&self) -> Option<u64> {
223        format_count(self, "content_length")
224    }
225
226    fn format_codecs(&self) -> Option<&str> {
227        self.text("codecs")
228    }
229}
230
231/// Observes one counted observation of a format, which only a whole number states.
232fn format_count(format: &Format, key: &str) -> Option<u64> {
233    match format.observe(key) {
234        Some(InfoValue::Integer(value)) => u64::try_from(*value).ok(),
235        Some(
236            InfoValue::Null
237            | InfoValue::Bool(_)
238            | InfoValue::Float(_)
239            | InfoValue::String(_)
240            | InfoValue::List(_)
241            | InfoValue::Record(_),
242        )
243        | None => None,
244    }
245}