Skip to content

Commit 82d4d34

Browse files
committed
remaster 'update' config & add update channel config;
'async-ify' some api to prevent having an infinite loop to monitor thread-pool; remaster progress handler to make it thread-safe, so GUI and CLI mode can both use the same api in async;
1 parent 7cd6117 commit 82d4d34

39 files changed

+1006
-1222
lines changed

locales/en-US.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@
8686
"installer": "installer",
8787
"installer_help": "installation of this tool will start a separated installer process that might needs your input.",
8888
"installing": "Installing",
89-
"installing_hint": "Installing selected components - this may take a few moments.",
9089
"installing_msvc_info": "running VS BuildTools installer...",
9190
"installing_tool_info": "installing '%{name}'",
9291
"integers": "integers",

locales/zh-CN.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@
8686
"installer": "安装程序",
8787
"installer_help": "此工具的安装将启动一个单独的安装程序进程,可能需要您的输入。",
8888
"installing": "正在安装",
89-
"installing_hint": "正在安装所选组件,请稍候...",
9089
"installing_msvc_info": "正在安装 VS 生成工具...",
9190
"installing_tool_info": "正在安装工具 '%{name}'",
9291
"integers": "整数",

rim_common/src/types/configuration.rs

Lines changed: 97 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,13 @@
44
use crate::setter;
55
use crate::{dirs::rim_config_dir, types::TomlParser};
66
use anyhow::Result;
7-
use chrono::{NaiveDateTime, Utc};
87
use serde::{Deserialize, Serialize};
98
use 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)]
1911
pub struct Configuration {
2012
pub language: Option<Language>,
21-
pub update: UpdateCheckerOpt,
13+
pub update: UpdateConfig,
2214
}
2315

2416
impl 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)]
246162
mod tests {
247163
use super::*;
248164

249165
#[test]
250-
fn skip_update() {
166+
fn backward_compatible() {
251167
let input = r#"
252168
[update]
253169
manager = { skip = "0.1.0", last-run = "1970-01-01T00:00:00" }
254170
toolkit = { 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

Comments
 (0)