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