Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/cli/src/state.rs
3309 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
extern crate dirs;
7
8
use std::{
9
fs::{self, create_dir_all, read_to_string, remove_dir_all},
10
io::Write,
11
path::{Path, PathBuf},
12
sync::{Arc, Mutex},
13
};
14
15
use serde::{de::DeserializeOwned, Serialize};
16
17
use crate::{
18
constants::{DEFAULT_DATA_PARENT_DIR, VSCODE_CLI_QUALITY},
19
download_cache::DownloadCache,
20
util::errors::{wrap, AnyError, NoHomeForLauncherError, WrappedError},
21
};
22
23
const HOME_DIR_ALTS: [&str; 2] = ["$HOME", "~"];
24
25
#[derive(Clone)]
26
pub struct LauncherPaths {
27
pub server_cache: DownloadCache,
28
pub cli_cache: DownloadCache,
29
root: PathBuf,
30
}
31
32
struct PersistedStateContainer<T>
33
where
34
T: Clone + Serialize + DeserializeOwned + Default,
35
{
36
path: PathBuf,
37
state: Option<T>,
38
#[allow(dead_code)]
39
mode: u32,
40
}
41
42
impl<T> PersistedStateContainer<T>
43
where
44
T: Clone + Serialize + DeserializeOwned + Default,
45
{
46
fn load_or_get(&mut self) -> T {
47
if let Some(state) = &self.state {
48
return state.clone();
49
}
50
51
let state = if let Ok(s) = read_to_string(&self.path) {
52
serde_json::from_str::<T>(&s).unwrap_or_default()
53
} else {
54
T::default()
55
};
56
57
self.state = Some(state.clone());
58
state
59
}
60
61
fn save(&mut self, state: T) -> Result<(), WrappedError> {
62
let s = serde_json::to_string(&state).unwrap();
63
self.state = Some(state);
64
self.write_state(s).map_err(|e| {
65
wrap(
66
e,
67
format!("error saving launcher state into {}", self.path.display()),
68
)
69
})
70
}
71
72
fn write_state(&mut self, s: String) -> std::io::Result<()> {
73
#[cfg(not(windows))]
74
use std::os::unix::fs::OpenOptionsExt;
75
76
let mut f = fs::OpenOptions::new();
77
f.create(true);
78
f.write(true);
79
f.truncate(true);
80
#[cfg(not(windows))]
81
f.mode(self.mode);
82
83
let mut f = f.open(&self.path)?;
84
f.write_all(s.as_bytes())
85
}
86
}
87
88
/// Container that holds some state value that is persisted to disk.
89
#[derive(Clone)]
90
pub struct PersistedState<T>
91
where
92
T: Clone + Serialize + DeserializeOwned + Default,
93
{
94
container: Arc<Mutex<PersistedStateContainer<T>>>,
95
}
96
97
impl<T> PersistedState<T>
98
where
99
T: Clone + Serialize + DeserializeOwned + Default,
100
{
101
/// Creates a new state container that persists to the given path.
102
pub fn new(path: PathBuf) -> PersistedState<T> {
103
Self::new_with_mode(path, 0o644)
104
}
105
106
/// Creates a new state container that persists to the given path.
107
pub fn new_with_mode(path: PathBuf, mode: u32) -> PersistedState<T> {
108
PersistedState {
109
container: Arc::new(Mutex::new(PersistedStateContainer {
110
path,
111
state: None,
112
mode,
113
})),
114
}
115
}
116
117
/// Loads persisted state.
118
pub fn load(&self) -> T {
119
self.container.lock().unwrap().load_or_get()
120
}
121
122
/// Saves persisted state.
123
pub fn save(&self, state: T) -> Result<(), WrappedError> {
124
self.container.lock().unwrap().save(state)
125
}
126
127
/// Mutates persisted state.
128
pub fn update<R>(&self, mutator: impl FnOnce(&mut T) -> R) -> Result<R, WrappedError> {
129
let mut container = self.container.lock().unwrap();
130
let mut state = container.load_or_get();
131
let r = mutator(&mut state);
132
container.save(state).map(|_| r)
133
}
134
}
135
136
impl LauncherPaths {
137
/// todo@conno4312: temporary migration from the old CLI data directory
138
pub fn migrate(root: Option<String>) -> Result<LauncherPaths, AnyError> {
139
if root.is_some() {
140
return Self::new(root);
141
}
142
143
let home_dir = match dirs::home_dir() {
144
None => return Self::new(root),
145
Some(d) => d,
146
};
147
148
let old_dir = home_dir.join(".vscode-cli");
149
let mut new_dir = home_dir;
150
new_dir.push(DEFAULT_DATA_PARENT_DIR);
151
new_dir.push("cli");
152
if !old_dir.exists() || new_dir.exists() {
153
return Self::new_for_path(new_dir);
154
}
155
156
if let Err(e) = std::fs::rename(&old_dir, &new_dir) {
157
// no logger exists at this point in the lifecycle, so just log to stderr
158
eprintln!("Failed to migrate old CLI data directory, will create a new one ({e})");
159
}
160
161
Self::new_for_path(new_dir)
162
}
163
164
pub fn new(root: Option<String>) -> Result<LauncherPaths, AnyError> {
165
let root = root.unwrap_or_else(|| format!("~/{DEFAULT_DATA_PARENT_DIR}/cli"));
166
let mut replaced = root.to_owned();
167
for token in HOME_DIR_ALTS {
168
if root.contains(token) {
169
if let Some(home) = dirs::home_dir() {
170
replaced = root.replace(token, &home.to_string_lossy())
171
} else {
172
return Err(AnyError::from(NoHomeForLauncherError()));
173
}
174
}
175
}
176
177
Self::new_for_path(PathBuf::from(replaced))
178
}
179
180
fn new_for_path(root: PathBuf) -> Result<LauncherPaths, AnyError> {
181
if !root.exists() {
182
create_dir_all(&root)
183
.map_err(|e| wrap(e, format!("error creating directory {}", root.display())))?;
184
}
185
186
Ok(LauncherPaths::new_without_replacements(root))
187
}
188
189
pub fn new_without_replacements(root: PathBuf) -> LauncherPaths {
190
// cleanup folders that existed before the new LRU strategy:
191
let _ = std::fs::remove_dir_all(root.join("server-insiders"));
192
let _ = std::fs::remove_dir_all(root.join("server-stable"));
193
194
LauncherPaths {
195
server_cache: DownloadCache::new(root.join("servers")),
196
cli_cache: DownloadCache::new(root.join("cli")),
197
root,
198
}
199
}
200
201
/// Root directory for the server launcher
202
pub fn root(&self) -> &Path {
203
&self.root
204
}
205
206
/// Lockfile for the running tunnel
207
pub fn tunnel_lockfile(&self) -> PathBuf {
208
self.root.join(format!(
209
"tunnel-{}.lock",
210
VSCODE_CLI_QUALITY.unwrap_or("oss")
211
))
212
}
213
214
/// Lockfile for port forwarding
215
pub fn forwarding_lockfile(&self) -> PathBuf {
216
self.root.join(format!(
217
"forwarding-{}.lock",
218
VSCODE_CLI_QUALITY.unwrap_or("oss")
219
))
220
}
221
222
/// Suggested path for tunnel service logs, when using file logs
223
pub fn service_log_file(&self) -> PathBuf {
224
self.root.join("tunnel-service.log")
225
}
226
227
/// Removes the launcher data directory.
228
pub fn remove(&self) -> Result<(), WrappedError> {
229
remove_dir_all(&self.root).map_err(|e| {
230
wrap(
231
e,
232
format!(
233
"error removing launcher data directory {}",
234
self.root.display()
235
),
236
)
237
})
238
}
239
240
/// Suggested path for web server storage
241
pub fn web_server_storage(&self) -> PathBuf {
242
self.root.join("serve-web")
243
}
244
}
245
246