1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
// Copyright 2015-2020 Tetsy Technologies (UK) Ltd.
// This file is part of Tetsy Vapory.

// Tetsy Vapory is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Tetsy Vapory is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Tetsy Vapory.  If not, see <http://www.gnu.org/licenses/>.

#![warn(missing_docs)]

//! Dir utilities for platform-specific operations
extern crate app_dirs;
extern crate vapory_types;
extern crate journaldb;
extern crate home;

pub mod helpers;
use std::fs;
use std::path::{PathBuf, Path};
use vapory_types::{H64, H256};
use journaldb::Algorithm;
use helpers::{replace_home, replace_home_and_local};
use app_dirs::{AppInfo, get_app_root, AppDataType};
// re-export platform-specific functions
use platform::*;

pub use home::home_dir;

/// Platform-specific chains path for standard client - Windows only
#[cfg(target_os = "windows")] pub const CHAINS_PATH: &str = "$LOCAL/chains";
/// Platform-specific chains path for light client - Windows only
#[cfg(target_os = "windows")] pub const CHAINS_PATH_LIGHT: &str = "$LOCAL/chains_light";
/// Platform-specific chains path for standard client
#[cfg(not(target_os = "windows"))] pub const CHAINS_PATH: &str = "$BASE/chains";
/// Platform-specific chains path for light client
#[cfg(not(target_os = "windows"))] pub const CHAINS_PATH_LIGHT: &str = "$BASE/chains_light";

/// Platform-specific cache path - Windows only
#[cfg(target_os = "windows")] pub const CACHE_PATH: &str = "$LOCAL/cache";
/// Platform-specific cache path
#[cfg(not(target_os = "windows"))] pub const CACHE_PATH: &str = "$BASE/cache";

// this const is irrelevent cause we do have migrations now,
// but we still use it for backwards compatibility
const LEGACY_CLIENT_DB_VER_STR: &str = "5.3";

#[derive(Debug, PartialEq)]
/// Tetsy local data directories
pub struct Directories {
	/// Base dir
	pub base: String,
	/// Database dir
	pub db: String,
	/// Cache dir
	pub cache: String,
	/// Dir to store keys
	pub keys: String,
	/// Signer dir
	pub signer: String,
	/// Secrets dir
	pub secretstore: String,
}

impl Default for Directories {
	fn default() -> Self {
		let data_dir = default_data_path();
		let local_dir = default_local_path();
		Directories {
			base: replace_home(&data_dir, "$BASE"),
			db: replace_home_and_local(&data_dir, &local_dir, CHAINS_PATH),
			cache: replace_home_and_local(&data_dir, &local_dir, CACHE_PATH),
			keys: replace_home(&data_dir, "$BASE/keys"),
			signer: replace_home(&data_dir, "$BASE/signer"),
			secretstore: replace_home(&data_dir, "$BASE/secretstore"),
		}
	}
}

impl Directories {
	/// Create local directories
	pub fn create_dirs(&self, signer_enabled: bool, secretstore_enabled: bool) -> Result<(), String> {
		fs::create_dir_all(&self.base).map_err(|e| e.to_string())?;
		fs::create_dir_all(&self.db).map_err(|e| e.to_string())?;
		fs::create_dir_all(&self.cache).map_err(|e| e.to_string())?;
		fs::create_dir_all(&self.keys).map_err(|e| e.to_string())?;
		if signer_enabled {
			fs::create_dir_all(&self.signer).map_err(|e| e.to_string())?;
		}
		if secretstore_enabled {
			fs::create_dir_all(&self.secretstore).map_err(|e| e.to_string())?;
		}
		Ok(())
	}

	/// Database paths.
	pub fn database(&self, genesis_hash: H256, fork_name: Option<String>, spec_name: String) -> DatabaseDirectories {
		DatabaseDirectories {
			path: self.db.clone(),
			legacy_path: self.base.clone(),
			genesis_hash,
			fork_name,
			spec_name,
		}
	}

	/// Get the ipc sockets path
	pub fn ipc_path(&self) -> PathBuf {
		let mut dir = Path::new(&self.base).to_path_buf();
		dir.push("ipc");
		dir
	}

	/// Legacy keys path
	// TODO: remove in 1.7
	pub fn legacy_keys_path(&self, testnet: bool) -> PathBuf {
		let mut dir = Path::new(&self.base).to_path_buf();
		if testnet {
			dir.push("testnet_keys");
		} else {
			dir.push("keys");
		}
		dir
	}

	/// Get the keys path
	pub fn keys_path(&self, data_dir: &str) -> PathBuf {
		let mut dir = PathBuf::from(&self.keys);
		dir.push(data_dir);
		dir
	}
}

#[derive(Debug, PartialEq)]
/// Database directories for the given fork.
pub struct DatabaseDirectories {
	/// Base path
	pub path: String,
	/// Legacy path
	pub legacy_path: String,
	/// Genesis hash
	pub genesis_hash: H256,
	/// Name of current fork
	pub fork_name: Option<String>,
	/// Name of current spec
	pub spec_name: String,
}

impl DatabaseDirectories {
	/// Base DB directory for the given fork.
	// TODO: remove in 1.7
	pub fn legacy_fork_path(&self) -> PathBuf {
		let gh = H64::from_slice(&self.genesis_hash.as_bytes()[20..28]);
		Path::new(&self.legacy_path).join(format!(
			"{:x}{}",
			gh,
			self.fork_name.as_ref().map(|f| format!("-{}", f)).unwrap_or_default()
		))
	}

	/// Spec root directory for the given fork.
	pub fn spec_root_path(&self) -> PathBuf {
		Path::new(&self.path).join(&self.spec_name)
	}

	/// Generic client path
	pub fn client_path(&self, pruning: Algorithm) -> PathBuf {
		self.db_root_path().join(pruning.as_internal_name_str()).join("db")
	}

	/// DB root path, named after genesis hash
	pub fn db_root_path(&self) -> PathBuf {
		let gh = H64::from_slice(&self.genesis_hash.as_bytes()[20..28]);
		self.spec_root_path().join("db").join(format!("{:x}", gh))
	}

	/// DB path
	pub fn db_path(&self, pruning: Algorithm) -> PathBuf {
		self.db_root_path().join(pruning.as_internal_name_str())
	}

	/// Get the root path for database
	// TODO: remove in 1.7
	pub fn legacy_version_path(&self, pruning: Algorithm) -> PathBuf {
		self.legacy_fork_path().join(format!("v{}-sec-{}", LEGACY_CLIENT_DB_VER_STR, pruning.as_internal_name_str()))
	}

	/// Get user defaults path, legacy way
	// TODO: remove in 1.7
	pub fn legacy_user_defaults_path(&self) -> PathBuf {
		self.legacy_fork_path().join("user_defaults")
	}

	/// Get snapshot path, legacy way
	// TODO: remove in 1.7
	pub fn legacy_snapshot_path(&self) -> PathBuf {
		self.legacy_fork_path().join("snapshot")
	}

	/// Get user defaults path, legacy way
	// TODO: remove in 1.7
	pub fn legacy_network_path(&self) -> PathBuf {
		self.legacy_fork_path().join("network")
	}

	/// Get user defauls path
	pub fn user_defaults_path(&self) -> PathBuf {
		self.spec_root_path().join("user_defaults")
	}

	/// Get the path for the snapshot directory given the genesis hash and fork name.
	pub fn snapshot_path(&self) -> PathBuf {
		self.db_root_path().join("snapshot")
	}

	/// Get the path for the network directory.
	pub fn network_path(&self) -> PathBuf {
		self.spec_root_path().join("network")
	}
}

/// Default data path
pub fn default_data_path() -> String {
	let app_info = AppInfo { name: PRODUCT, author: AUTHOR };
	get_app_root(AppDataType::UserData, &app_info).map(|p| p.to_string_lossy().into_owned()).unwrap_or_else(|_| "$HOME/.tetsy".to_owned())
}

/// Default local path
pub fn default_local_path() -> String {
	let app_info = AppInfo { name: PRODUCT, author: AUTHOR };
	get_app_root(AppDataType::UserCache, &app_info).map(|p| p.to_string_lossy().into_owned()).unwrap_or_else(|_| "$HOME/.tetsy".to_owned())
}

/// Default hypervisor path
pub fn default_hypervisor_path() -> PathBuf {
	let app_info = AppInfo { name: PRODUCT_HYPERVISOR, author: AUTHOR };
	get_app_root(AppDataType::UserData, &app_info).unwrap_or_else(|_| "$HOME/.tetsy-hypervisor".into())
}

/// Get home directory.
fn home() -> PathBuf {
	home_dir().expect("Failed to get home dir")
}

/// Gvap path
pub fn gvap(testnet: bool) -> PathBuf {
	let mut base = gvap_base();
	if testnet {
		base.push("testnet");
	}
	base.push("keystore");
	base
}

/// Tetsy path for specific chain
pub fn tetsy(chain: &str) -> PathBuf {
	let mut base = tetsy_base();
	base.push(chain);
	base
}

#[cfg(target_os = "macos")]
mod platform {
	use std::path::PathBuf;
	pub const AUTHOR: &str = "Tetsy";
	pub const PRODUCT: &str = "io.tetsy.vapory";
	pub const PRODUCT_HYPERVISOR: &str = "io.tetsy.vapory-updates";

	pub fn tetsy_base() -> PathBuf {
		let mut home = super::home();
		home.push("Library");
		home.push("Application Support");
		home.push("io.tetsy.vapory");
		home.push("keys");
		home
	}

	pub fn gvap_base() -> PathBuf {
		let mut home = super::home();
		home.push("Library");
		home.push("Vapory");
		home
	}
}

#[cfg(windows)]
mod platform {
	use std::path::PathBuf;
	pub const AUTHOR: &str = "Tetsy";
	pub const PRODUCT: &str = "Vapory";
	pub const PRODUCT_HYPERVISOR: &str = "VaporyUpdates";

	pub fn tetsy_base() -> PathBuf {
		let mut home = super::home();
		home.push("AppData");
		home.push("Roaming");
		home.push("Tetsy");
		home.push("Vapory");
		home.push("keys");
		home
	}

	pub fn gvap_base() -> PathBuf {
		let mut home = super::home();
		home.push("AppData");
		home.push("Roaming");
		home.push("Vapory");
		home
	}
}

#[cfg(not(any(target_os = "macos", windows)))]
mod platform {
	use std::path::PathBuf;
	pub const AUTHOR: &str = "tetsy";
	pub const PRODUCT: &str = "io.tetsy.vapory";
	pub const PRODUCT_HYPERVISOR: &str = "io.tetsy.vapory-updates";

	pub fn tetsy_base() -> PathBuf {
		let mut home = super::home();
		home.push(".local");
		home.push("share");
		home.push("io.tetsy.vapory");
		home.push("keys");
		home
	}

	pub fn gvap_base() -> PathBuf {
		let mut home = super::home();
		home.push(".vapory");
		home
	}
}

#[cfg(test)]
mod tests {
	use super::Directories;
	use helpers::{replace_home, replace_home_and_local};

	#[test]
	fn test_default_directories() {
		let data_dir = super::default_data_path();
		let local_dir = super::default_local_path();
		let expected = Directories {
			base: replace_home(&data_dir, "$BASE"),
			db: replace_home_and_local(&data_dir, &local_dir,
				if cfg!(target_os = "windows") { "$LOCAL/chains" }
				else { "$BASE/chains" }
			),
			cache: replace_home_and_local(&data_dir, &local_dir,
				if cfg!(target_os = "windows") { "$LOCAL/cache" }
				else { "$BASE/cache" }
			),
			keys: replace_home(&data_dir, "$BASE/keys"),
			signer: replace_home(&data_dir, "$BASE/signer"),
			secretstore: replace_home(&data_dir, "$BASE/secretstore"),
		};
		assert_eq!(expected, Directories::default());
	}
}