44use crate :: setter;
55use crate :: { dirs:: rim_config_dir, types:: TomlParser } ;
66use anyhow:: Result ;
7- use chrono:: { NaiveDateTime , Utc } ;
87use serde:: { Deserialize , Serialize } ;
98use std:: str:: FromStr ;
10- use std:: { collections:: HashMap , fmt:: Display , time:: Duration } ;
119
12- /// Default update check timeout is 1440 minutes (1 day)
13- const DEFAULT_UPDATE_CHECK_TIMEOUT_IN_MINUTES : u64 = 1440 ;
14- /// Default update check timeout in duration
15- pub const DEFAULT_UPDATE_CHECK_DURATION : Duration =
16- Duration :: from_secs ( 60 * DEFAULT_UPDATE_CHECK_TIMEOUT_IN_MINUTES ) ;
17-
18- #[ derive( Debug , Deserialize , Serialize , Default ) ]
10+ #[ derive( Debug , Deserialize , Serialize , Default , PartialEq , Eq ) ]
1911pub struct Configuration {
2012 pub language : Option < Language > ,
21- pub update : UpdateCheckerOpt ,
13+ pub update : UpdateConfig ,
2214}
2315
2416impl TomlParser for Configuration {
@@ -30,15 +22,6 @@ impl Configuration {
3022 Self :: default ( )
3123 }
3224
33- /// Mark a version as skipped.
34- ///
35- /// This function can be chained.
36- pub fn skip_update < T : Into < String > > ( mut self , target : UpdateTarget , version : T ) -> Self {
37- let conf = self . update . conf_mut ( target) ;
38- conf. skip = Some ( version. into ( ) ) ;
39- self
40- }
41-
4225 /// Try loading from [`rim_config_dir`], return `None` if it doesn't exists yet.
4326 pub fn try_load_from_config_dir ( ) -> Option < Self > {
4427 Self :: load_from_dir ( rim_config_dir ( ) ) . ok ( )
@@ -57,11 +40,19 @@ impl Configuration {
5740 self . write_to_dir ( rim_config_dir ( ) )
5841 }
5942
60- pub fn update_skipped < T : AsRef < str > > ( & self , target : UpdateTarget , version : T ) -> bool {
61- self . update . is_skipped ( target, version)
62- }
63-
64- setter ! ( set_language( self . language, Option <Language >) ) ;
43+ setter ! ( set_language( self . language, val: Language ) { Some ( val) } ) ;
44+ setter ! ( set_manager_update_channel(
45+ self . update. manager_update_channel,
46+ UpdateChannel
47+ ) ) ;
48+ setter ! ( set_auto_check_manager_updates(
49+ self . update. auto_check_manager_updates,
50+ bool
51+ ) ) ;
52+ setter ! ( set_auto_check_toolkit_updates(
53+ self . update. auto_check_toolkit_updates,
54+ bool
55+ ) ) ;
6556}
6657
6758#[ derive( Debug , Deserialize , Serialize , Default , PartialEq , Eq , Clone , Copy ) ]
@@ -107,192 +98,131 @@ impl FromStr for Language {
10798 }
10899}
109100
110- // If we ever need to support more things for update checker,
111- // just add one in this enum, without breaking compatibility.
112- #[ derive( Clone , Copy , Debug , Deserialize , Serialize , Hash , PartialEq , Eq ) ]
113- #[ serde( rename_all = "kebab-case" ) ]
114- pub enum UpdateTarget {
115- Manager ,
116- Toolkit ,
101+ /// App update channel.
102+ #[ derive( Debug , Default , Deserialize , Serialize , PartialEq , Eq ) ]
103+ #[ serde( rename_all = "lowercase" ) ]
104+ #[ non_exhaustive]
105+ pub enum UpdateChannel {
106+ #[ default]
107+ Stable ,
108+ Beta ,
117109}
118110
119- // The display implementation must return the same result as
120- // serde's serialization, which means it should be in 'kebab-case' as well.
121- impl Display for UpdateTarget {
122- fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
123- f. write_str ( match self {
124- Self :: Manager => "manager" ,
125- Self :: Toolkit => "toolkit" ,
126- } )
127- }
111+ /// Representing the configuration for update checker.
112+ #[ derive( Debug , Deserialize , Serialize , PartialEq , Eq ) ]
113+ #[ serde( rename_all = "kebab-case" ) ]
114+ pub struct UpdateConfig {
115+ /// Channel of manager (app) updates to check, i.e. "stable", "beta"
116+ #[ serde( default ) ]
117+ pub manager_update_channel : UpdateChannel ,
118+ /// Automatically checking for manager (app) updates.
119+ #[ serde( default = "bool_true" ) ]
120+ pub auto_check_manager_updates : bool ,
121+ /// Automatically checking for toolkit updates.
122+ #[ serde( default = "bool_true" ) ]
123+ pub auto_check_toolkit_updates : bool ,
128124}
129125
130- #[ derive( Debug , Deserialize , Serialize ) ]
131- #[ serde( rename_all = "kebab-case" ) ]
132- pub struct UpdateConf {
133- /// The datetime when the last update check happened,
134- /// defaulting to [`UNIX_EPOCH`](NaiveDateTime::UNIX_EPOCH)
135- last_run : NaiveDateTime ,
136- /// Timeout (in minutes) until next update check
137- timeout : Option < u64 > ,
138- /// The specific version to disable auto check.
139- ///
140- /// If there's a newer version available, it will still
141- skip : Option < String > ,
126+ // Return `true` for the serde default arg.
127+ // really? there's no better way?
128+ fn bool_true ( ) -> bool {
129+ true
142130}
143131
144- impl Default for UpdateConf {
132+ impl Default for UpdateConfig {
145133 fn default ( ) -> Self {
146134 Self {
147- last_run : NaiveDateTime :: default ( ) ,
148- timeout : Some ( DEFAULT_UPDATE_CHECK_TIMEOUT_IN_MINUTES ) ,
149- skip : None ,
135+ manager_update_channel : UpdateChannel :: default ( ) ,
136+ auto_check_manager_updates : true ,
137+ auto_check_toolkit_updates : true ,
150138 }
151139 }
152140}
153141
154- impl UpdateConf {
155- /// Get the timeout in duration until next update check.
156- ///
157- /// Default is 1 day.
158- fn timeout ( & self ) -> Duration {
159- self . timeout
160- . map ( |timeout_in_minutes| Duration :: from_secs ( timeout_in_minutes * 60 ) )
161- . unwrap_or ( DEFAULT_UPDATE_CHECK_DURATION )
162- }
163- }
164-
165- /// Representing the configuration for update checker.
166- ///
167- /// Containing information about what version to skip (by the user),
168- /// how often should we check for next update,
169- /// and when was the last check happened.
170- ///
171- /// # Configuration example
172- ///
173- /// ```toml
174- /// [update.manager]
175- /// last-run = "2024-01-01 10:30:05" # when was the last update check
176- /// timeout = 1440 # how long (in minutes) until we need to check for update since `last-run`,
177- /// skip = "0.5.0" # the version the user choose to skip
178- /// ```
179- #[ derive( Debug , Default , Deserialize , Serialize ) ]
180- pub struct UpdateCheckerOpt ( HashMap < UpdateTarget , UpdateConf > ) ;
181-
182- impl UpdateCheckerOpt {
142+ impl UpdateConfig {
183143 pub fn new ( ) -> Self {
184144 Self :: default ( )
185145 }
186146
187- fn conf_mut ( & mut self , target : UpdateTarget ) -> & mut UpdateConf {
188- self . 0 . entry ( target) . or_default ( )
189- }
190-
191- /// Return `true` if the given `version` is marked as skipped before.
192- fn is_skipped < T : AsRef < str > > ( & self , target : UpdateTarget , version : T ) -> bool {
193- let Some ( skipped) = self . 0 . get ( & target) . and_then ( |conf| conf. skip . as_deref ( ) ) else {
194- return false ;
195- } ;
196- version. as_ref ( ) == skipped
197- }
198-
199- /// Change a target's update checkout timeout to a specific number in minutes.
200- ///
201- /// This function can be chained.
202- pub fn remind_later ( mut self , target : UpdateTarget , minutes : u64 ) -> Self {
203- let conf = self . conf_mut ( target) ;
204- if let Some ( t) = conf. timeout . as_mut ( ) {
205- * t += minutes
206- } else {
207- conf. timeout = Some ( minutes) ;
208- }
209- self
210- }
211-
212- /// Update the `last-run` value for given target.
213- pub fn mark_checked ( & mut self , target : UpdateTarget ) -> & mut Self {
214- let conf = self . conf_mut ( target) ;
215- conf. last_run = Utc :: now ( ) . naive_utc ( ) ;
216- self
217- }
218-
219- /// Return how much time (in duration) until the next update check.
220- ///
221- /// - If the update hasn't be checked yet, we should check now,
222- /// thus returning [`Duration::ZERO`].
223- /// - If the update has been checked, but right now is not the time for the
224- /// next check, the remaining time will be returned.
225- /// - If the update has been checked, and it's already past the time for the next
226- /// update check, then [`Duration::ZERO`] will be returned.
227- pub fn duration_until_next_run ( & self , target : UpdateTarget ) -> Duration {
228- let Some ( conf) = self . 0 . get ( & target) else {
229- // return the full default duration
230- return Duration :: ZERO ;
231- } ;
232- let timeout = conf. timeout ( ) ;
233- let next_check_date = conf. last_run + timeout;
234- let now = Utc :: now ( ) . naive_utc ( ) ;
235- if next_check_date > now {
236- let time_delta_in_secs = ( next_check_date - now) . num_seconds ( ) ;
237- // safe to unwrap, we are converting a known positive i64 to u64
238- Duration :: from_secs ( time_delta_in_secs. try_into ( ) . unwrap ( ) )
239- } else {
240- Duration :: ZERO
241- }
242- }
147+ setter ! ( manager_update_channel(
148+ self . manager_update_channel,
149+ UpdateChannel
150+ ) ) ;
151+ setter ! ( auto_check_manager_updates(
152+ self . auto_check_manager_updates,
153+ bool
154+ ) ) ;
155+ setter ! ( auto_check_toolkit_updates(
156+ self . auto_check_toolkit_updates,
157+ bool
158+ ) ) ;
243159}
244160
245161#[ cfg( test) ]
246162mod tests {
247163 use super :: * ;
248164
249165 #[ test]
250- fn skip_update ( ) {
166+ fn backward_compatible ( ) {
251167 let input = r#"
252168[update]
253169manager = { skip = "0.1.0", last-run = "1970-01-01T00:00:00" }
254170toolkit = { skip = "1.0.0", last-run = "1970-01-01T00:00:00" }"# ;
255171 let expected = Configuration :: from_str ( input) . unwrap ( ) ;
256- assert ! ( expected. update_skipped( UpdateTarget :: Manager , "0.1.0" ) ) ;
257- assert ! ( expected. update_skipped( UpdateTarget :: Toolkit , "1.0.0" ) ) ;
172+ assert_eq ! ( expected, Configuration :: default ( ) ) ;
258173 }
259174
260175 #[ test]
261- fn skip_update_programmatically ( ) {
262- let vs = Configuration :: new ( )
263- . skip_update ( UpdateTarget :: Manager , "0.1.0" )
264- . skip_update ( UpdateTarget :: Toolkit , "1.0.0" ) ;
265- assert ! ( vs. update_skipped( UpdateTarget :: Manager , "0.1.0" ) ) ;
266- assert ! ( vs. update_skipped( UpdateTarget :: Toolkit , "1.0.0" ) ) ;
176+ fn default_serialization ( ) {
177+ let conf = Configuration :: new ( ) ;
178+
179+ assert_eq ! (
180+ conf. to_toml( ) . unwrap( ) ,
181+ r#"[update]
182+ manager-update-channel = "stable"
183+ auto-check-manager-updates = true
184+ auto-check-toolkit-updates = true
185+ "#
186+ ) ;
267187 }
268188
269189 #[ test]
270- fn remind_update_later ( ) {
271- let input = r#"
272- [update]
273- manager = { last-run = "1970-01-01T00:00:00" }"# ;
190+ fn configured_serialization ( ) {
191+ let conf = Configuration :: new ( )
192+ . set_language ( Language :: CN )
193+ . set_manager_update_channel ( UpdateChannel :: Beta )
194+ . set_auto_check_manager_updates ( false )
195+ . set_auto_check_toolkit_updates ( false ) ;
196+
197+ let expected = conf. to_toml ( ) . unwrap ( ) ;
198+ assert_eq ! (
199+ expected,
200+ r#"language = "CN"
274201
275- let mut expected = Configuration :: from_str ( input) . unwrap ( ) . update ;
276- let manager = UpdateTarget :: Manager ;
277- assert_eq ! ( expected. conf_mut( manager) . timeout, None ) ;
278- expected = expected. remind_later ( manager, 60 ) ;
279- assert_eq ! ( expected. conf_mut( manager) . timeout, Some ( 60 ) ) ;
280- expected = expected. remind_later ( manager, 60 ) ;
281- assert_eq ! ( expected. conf_mut( manager) . timeout, Some ( 120 ) ) ;
202+ [update]
203+ manager-update-channel = "beta"
204+ auto-check-manager-updates = false
205+ auto-check-toolkit-updates = false
206+ "#
207+ ) ;
282208 }
283209
284210 #[ test]
285211 fn lang_config ( ) {
286- let input = r#"language = "CN"
287-
288- [update]
289- "# ;
212+ let input = "language = \" CN\" \n [update]" ;
290213
291214 let expected = Configuration :: from_str ( input) . unwrap ( ) ;
292215 assert_eq ! ( expected. language, Some ( Language :: CN ) ) ;
293216
294- // check if the language consistance since we have a `FromStr` impl for it.
217+ // check if the language consistence since we have a `FromStr` impl for it.
295218 let back_to_str = toml:: to_string ( & expected) . unwrap ( ) ;
296- assert_eq ! ( back_to_str, input) ;
219+ assert_eq ! (
220+ back_to_str,
221+ "language = \" CN\" \n \n \
222+ [update]\n \
223+ manager-update-channel = \" stable\" \n \
224+ auto-check-manager-updates = true\n \
225+ auto-check-toolkit-updates = true\n "
226+ ) ;
297227 }
298228}
0 commit comments