user defaults (#2014)

* user defaults

* finished user defaults

* user defaults are network-dependent

* added tests for newly added functions, logger is initialized first

* dir cleanup in progress

* user_file is placed next to snapshots
This commit is contained in:
Marek Kotewicz
2016-09-26 19:21:25 +02:00
committed by GitHub
parent 598e9cea85
commit 56eb97abbf
16 changed files with 371 additions and 253 deletions

View File

@@ -26,15 +26,16 @@ use io::{PanicHandler, ForwardPanic};
use util::{ToPretty, Uint};
use rlp::PayloadInfo;
use ethcore::service::ClientService;
use ethcore::client::{Mode, DatabaseCompactionProfile, Switch, VMType, BlockImportError, BlockChainClient, BlockID};
use ethcore::client::{Mode, DatabaseCompactionProfile, VMType, BlockImportError, BlockChainClient, BlockID};
use ethcore::error::ImportError;
use ethcore::miner::Miner;
use cache::CacheConfig;
use params::{SpecType, Pruning, Switch, tracing_switch_to_bool};
use informant::{Informant, MillisecondDuration};
use io_handler::ImportIoHandler;
use params::{SpecType, Pruning};
use helpers::{to_client_config, execute_upgrades};
use dir::Directories;
use user_defaults::UserDefaults;
use fdlimit;
#[derive(Debug, PartialEq)]
@@ -113,29 +114,44 @@ fn execute_import(cmd: ImportBlockchain) -> Result<String, String> {
// Setup panic handler
let panic_handler = PanicHandler::new_in_arc();
// Setup logging
let _logger = setup_log(&cmd.logger_config);
// create dirs used by parity
try!(cmd.dirs.create_dirs());
// load spec file
let spec = try!(cmd.spec.spec());
// load genesis hash
let genesis_hash = spec.genesis_header().hash();
// Setup logging
let _logger = setup_log(&cmd.logger_config);
// database paths
let db_dirs = cmd.dirs.database(genesis_hash, spec.fork_name.clone());
// user defaults path
let user_defaults_path = db_dirs.user_defaults_path();
// load user defaults
let mut user_defaults = try!(UserDefaults::load(&user_defaults_path));
// check if tracing is on
let tracing = try!(tracing_switch_to_bool(cmd.tracing, &user_defaults));
fdlimit::raise_fd_limit();
// select pruning algorithm
let algorithm = cmd.pruning.to_algorithm(&cmd.dirs, genesis_hash, spec.fork_name.as_ref());
let algorithm = cmd.pruning.to_algorithm(&user_defaults);
// prepare client and snapshot paths.
let client_path = cmd.dirs.client_path(genesis_hash, spec.fork_name.as_ref(), algorithm);
let snapshot_path = cmd.dirs.snapshot_path(genesis_hash, spec.fork_name.as_ref());
let client_path = db_dirs.client_path(algorithm);
let snapshot_path = db_dirs.snapshot_path();
// execute upgrades
try!(execute_upgrades(&cmd.dirs, genesis_hash, spec.fork_name.as_ref(), algorithm, cmd.compaction.compaction_profile()));
try!(execute_upgrades(&db_dirs, algorithm, cmd.compaction.compaction_profile()));
// prepare client config
let client_config = to_client_config(&cmd.cache_config, &cmd.dirs, genesis_hash, cmd.mode, cmd.tracing, cmd.pruning, cmd.compaction, cmd.wal, cmd.vm_type, "".into(), spec.fork_name.as_ref());
let client_config = to_client_config(&cmd.cache_config, cmd.mode, tracing, cmd.compaction, cmd.wal, cmd.vm_type, "".into(), algorithm);
// build client
let service = try!(ClientService::start(
@@ -220,6 +236,12 @@ fn execute_import(cmd: ImportBlockchain) -> Result<String, String> {
}
}
client.flush_queue();
// save user defaults
user_defaults.pruning = algorithm;
user_defaults.tracing = tracing;
try!(user_defaults.save(&user_defaults_path));
let report = client.report();
let ms = timer.elapsed().as_milliseconds();
@@ -238,6 +260,12 @@ fn execute_export(cmd: ExportBlockchain) -> Result<String, String> {
// Setup panic handler
let panic_handler = PanicHandler::new_in_arc();
// Setup logging
let _logger = setup_log(&cmd.logger_config);
// create dirs used by parity
try!(cmd.dirs.create_dirs());
let format = cmd.format.unwrap_or_default();
// load spec file
@@ -246,23 +274,32 @@ fn execute_export(cmd: ExportBlockchain) -> Result<String, String> {
// load genesis hash
let genesis_hash = spec.genesis_header().hash();
// Setup logging
let _logger = setup_log(&cmd.logger_config);
// database paths
let db_dirs = cmd.dirs.database(genesis_hash, spec.fork_name.clone());
// user defaults path
let user_defaults_path = db_dirs.user_defaults_path();
// load user defaults
let user_defaults = try!(UserDefaults::load(&user_defaults_path));
// check if tracing is on
let tracing = try!(tracing_switch_to_bool(cmd.tracing, &user_defaults));
fdlimit::raise_fd_limit();
// select pruning algorithm
let algorithm = cmd.pruning.to_algorithm(&cmd.dirs, genesis_hash, spec.fork_name.as_ref());
let algorithm = cmd.pruning.to_algorithm(&user_defaults);
// prepare client and snapshot paths.
let client_path = cmd.dirs.client_path(genesis_hash, spec.fork_name.as_ref(), algorithm);
let snapshot_path = cmd.dirs.snapshot_path(genesis_hash, spec.fork_name.as_ref());
let client_path = db_dirs.client_path(algorithm);
let snapshot_path = db_dirs.snapshot_path();
// execute upgrades
try!(execute_upgrades(&cmd.dirs, genesis_hash, spec.fork_name.as_ref(), algorithm, cmd.compaction.compaction_profile()));
try!(execute_upgrades(&db_dirs, algorithm, cmd.compaction.compaction_profile()));
// prepare client config
let client_config = to_client_config(&cmd.cache_config, &cmd.dirs, genesis_hash, cmd.mode, cmd.tracing, cmd.pruning, cmd.compaction, cmd.wal, VMType::default(), "".into(), spec.fork_name.as_ref());
let client_config = to_client_config(&cmd.cache_config, cmd.mode, tracing, cmd.compaction, cmd.wal, VMType::default(), "".into(), algorithm);
let service = try!(ClientService::start(
client_config,

View File

@@ -52,32 +52,13 @@ impl Directories {
Ok(())
}
/// Get the chain's root path.
pub fn chain_path(&self, genesis_hash: H256, fork_name: Option<&String>) -> PathBuf {
let mut dir = Path::new(&self.db).to_path_buf();
dir.push(format!("{:?}{}", H64::from(genesis_hash), fork_name.map(|f| format!("-{}", f)).unwrap_or_default()));
dir
}
/// Get the root path for database
pub fn db_version_path(&self, genesis_hash: H256, fork_name: Option<&String>, pruning: Algorithm) -> PathBuf {
let mut dir = self.chain_path(genesis_hash, fork_name);
dir.push(format!("v{}-sec-{}", LEGACY_CLIENT_DB_VER_STR, pruning.as_internal_name_str()));
dir
}
/// Get the path for the databases given the genesis_hash and information on the databases.
pub fn client_path(&self, genesis_hash: H256, fork_name: Option<&String>, pruning: Algorithm) -> PathBuf {
let mut dir = self.db_version_path(genesis_hash, fork_name, pruning);
dir.push("db");
dir
}
/// Get the path for the snapshot directory given the genesis hash and fork name.
pub fn snapshot_path(&self, genesis_hash: H256, fork_name: Option<&String>) -> PathBuf {
let mut dir = self.chain_path(genesis_hash, fork_name);
dir.push("snapshot");
dir
/// Database paths.
pub fn database(&self, genesis_hash: H256, fork_name: Option<String>) -> DatabaseDirectories {
DatabaseDirectories {
path: self.db.clone(),
genesis_hash: genesis_hash,
fork_name: fork_name,
}
}
/// Get the ipc sockets path
@@ -88,6 +69,49 @@ impl Directories {
}
}
#[derive(Debug, PartialEq)]
pub struct DatabaseDirectories {
pub path: String,
pub genesis_hash: H256,
pub fork_name: Option<String>,
}
impl DatabaseDirectories {
fn fork_path(&self) -> PathBuf {
let mut dir = Path::new(&self.path).to_path_buf();
dir.push(format!("{:?}{}", H64::from(self.genesis_hash), self.fork_name.as_ref().map(|f| format!("-{}", f)).unwrap_or_default()));
dir
}
/// Get the root path for database
pub fn version_path(&self, pruning: Algorithm) -> PathBuf {
let mut dir = self.fork_path();
dir.push(format!("v{}-sec-{}", LEGACY_CLIENT_DB_VER_STR, pruning.as_internal_name_str()));
dir
}
/// Get the path for the databases given the genesis_hash and information on the databases.
pub fn client_path(&self, pruning: Algorithm) -> PathBuf {
let mut dir = self.version_path(pruning);
dir.push("db");
dir
}
/// Get user defaults path
pub fn user_defaults_path(&self) -> PathBuf {
let mut dir = self.fork_path();
dir.push("user_defaults");
dir
}
/// Get the path for the snapshot directory given the genesis hash and fork name.
pub fn snapshot_path(&self) -> PathBuf {
let mut dir = self.fork_path();
dir.push("snapshot");
dir
}
}
#[cfg(test)]
mod tests {
use super::Directories;

View File

@@ -19,13 +19,12 @@ use std::io::{Write, Read, BufReader, BufRead};
use std::time::Duration;
use std::path::Path;
use std::fs::File;
use util::{clean_0x, U256, Uint, Address, path, H256, CompactionProfile};
use util::{clean_0x, U256, Uint, Address, path, CompactionProfile};
use util::journaldb::Algorithm;
use ethcore::client::{Mode, BlockID, Switch, VMType, DatabaseCompactionProfile, ClientConfig};
use ethcore::client::{Mode, BlockID, VMType, DatabaseCompactionProfile, ClientConfig};
use ethcore::miner::PendingSet;
use cache::CacheConfig;
use dir::Directories;
use params::Pruning;
use dir::DatabaseDirectories;
use upgrade::upgrade;
use migration::migrate;
use ethsync::is_valid_node_url;
@@ -190,16 +189,13 @@ pub fn default_network_config() -> ::ethsync::NetworkConfiguration {
#[cfg_attr(feature = "dev", allow(too_many_arguments))]
pub fn to_client_config(
cache_config: &CacheConfig,
dirs: &Directories,
genesis_hash: H256,
mode: Mode,
tracing: Switch,
pruning: Pruning,
tracing: bool,
compaction: DatabaseCompactionProfile,
wal: bool,
vm_type: VMType,
name: String,
fork_name: Option<&String>,
pruning: Algorithm,
) -> ClientConfig {
let mut client_config = ClientConfig::default();
@@ -221,7 +217,7 @@ pub fn to_client_config(
client_config.mode = mode;
client_config.tracing.enabled = tracing;
client_config.pruning = pruning.to_algorithm(dirs, genesis_hash, fork_name);
client_config.pruning = pruning;
client_config.db_compaction = compaction;
client_config.db_wal = wal;
client_config.vm_type = vm_type;
@@ -230,14 +226,12 @@ pub fn to_client_config(
}
pub fn execute_upgrades(
dirs: &Directories,
genesis_hash: H256,
fork_name: Option<&String>,
dirs: &DatabaseDirectories,
pruning: Algorithm,
compaction_profile: CompactionProfile
) -> Result<(), String> {
match upgrade(Some(&dirs.db)) {
match upgrade(Some(&dirs.path)) {
Ok(upgrades_applied) if upgrades_applied > 0 => {
debug!("Executed {} upgrade scripts - ok", upgrades_applied);
},
@@ -247,7 +241,7 @@ pub fn execute_upgrades(
_ => {},
}
let client_path = dirs.db_version_path(genesis_hash, fork_name, pruning);
let client_path = dirs.version_path(pruning);
migrate(&client_path, pruning, compaction_profile).map_err(|e| format!("{}", e))
}

View File

@@ -39,6 +39,8 @@ extern crate semver;
extern crate ethcore_io as io;
extern crate ethcore_ipc as ipc;
extern crate ethcore_ipc_nano as nanoipc;
extern crate serde;
extern crate serde_json;
extern crate rlp;
extern crate json_ipc_server as jsonipc;
@@ -106,6 +108,7 @@ mod run;
mod sync;
#[cfg(feature="ipc")]
mod boot;
mod user_defaults;
#[cfg(feature="stratum")]
mod stratum;

View File

@@ -14,15 +14,14 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::str::FromStr;
use std::fs;
use std::{str, fs};
use std::time::Duration;
use util::{H256, Address, U256, version_data};
use util::{Address, U256, version_data};
use util::journaldb::Algorithm;
use ethcore::spec::Spec;
use ethcore::ethereum;
use ethcore::miner::{GasPricer, GasPriceCalibratorOptions};
use dir::Directories;
use user_defaults::UserDefaults;
#[derive(Debug, PartialEq)]
pub enum SpecType {
@@ -39,7 +38,7 @@ impl Default for SpecType {
}
}
impl FromStr for SpecType {
impl str::FromStr for SpecType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
@@ -81,7 +80,7 @@ impl Default for Pruning {
}
}
impl FromStr for Pruning {
impl str::FromStr for Pruning {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
@@ -93,24 +92,12 @@ impl FromStr for Pruning {
}
impl Pruning {
pub fn to_algorithm(&self, dirs: &Directories, genesis_hash: H256, fork_name: Option<&String>) -> Algorithm {
pub fn to_algorithm(&self, user_defaults: &UserDefaults) -> Algorithm {
match *self {
Pruning::Specific(algo) => algo,
Pruning::Auto => Self::find_best_db(dirs, genesis_hash, fork_name),
Pruning::Auto => user_defaults.pruning,
}
}
fn find_best_db(dirs: &Directories, genesis_hash: H256, fork_name: Option<&String>) -> Algorithm {
let mut algo_types = Algorithm::all_types();
// if all dbs have the same modification time, the last element is the default one
algo_types.push(Algorithm::default());
algo_types.into_iter().max_by_key(|i| {
let mut client_path = dirs.client_path(genesis_hash, fork_name, *i);
client_path.push("CURRENT");
fs::metadata(&client_path).and_then(|m| m.modified()).ok()
}).unwrap()
}
}
#[derive(Debug, PartialEq)]
@@ -128,7 +115,7 @@ impl Default for ResealPolicy {
}
}
impl FromStr for ResealPolicy {
impl str::FromStr for ResealPolicy {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
@@ -223,10 +210,50 @@ impl Default for MinerExtras {
}
}
/// 3-value enum.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Switch {
/// True.
On,
/// False.
Off,
/// Auto.
Auto,
}
impl Default for Switch {
fn default() -> Self {
Switch::Auto
}
}
impl str::FromStr for Switch {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"on" => Ok(Switch::On),
"off" => Ok(Switch::Off),
"auto" => Ok(Switch::Auto),
other => Err(format!("Invalid switch value: {}", other))
}
}
}
pub fn tracing_switch_to_bool(switch: Switch, user_defaults: &UserDefaults) -> Result<bool, String> {
match (user_defaults.is_first_launch, switch, user_defaults.tracing) {
(false, Switch::On, false) => Err("TraceDB resync required".into()),
(_, Switch::On, _) => Ok(true),
(_, Switch::Off, _) => Ok(false),
(_, Switch::Auto, def) => Ok(def),
}
}
#[cfg(test)]
mod tests {
use util::journaldb::Algorithm;
use super::{SpecType, Pruning, ResealPolicy};
use user_defaults::UserDefaults;
use super::{SpecType, Pruning, ResealPolicy, Switch, tracing_switch_to_bool};
#[test]
fn test_spec_type_parsing() {
@@ -274,4 +301,36 @@ mod tests {
let all = ResealPolicy { own: true, external: true };
assert_eq!(all, ResealPolicy::default());
}
#[test]
fn test_switch_parsing() {
assert_eq!(Switch::On, "on".parse().unwrap());
assert_eq!(Switch::Off, "off".parse().unwrap());
assert_eq!(Switch::Auto, "auto".parse().unwrap());
}
#[test]
fn test_switch_default() {
assert_eq!(Switch::default(), Switch::Auto);
}
fn user_defaults_with_tracing(first_launch: bool, tracing: bool) -> UserDefaults {
let mut ud = UserDefaults::default();
ud.is_first_launch = first_launch;
ud.tracing = tracing;
ud
}
#[test]
fn test_switch_to_bool() {
assert!(!tracing_switch_to_bool(Switch::Off, &user_defaults_with_tracing(true, true)).unwrap());
assert!(!tracing_switch_to_bool(Switch::Off, &user_defaults_with_tracing(true, false)).unwrap());
assert!(!tracing_switch_to_bool(Switch::Off, &user_defaults_with_tracing(false, true)).unwrap());
assert!(!tracing_switch_to_bool(Switch::Off, &user_defaults_with_tracing(false, false)).unwrap());
assert!(tracing_switch_to_bool(Switch::On, &user_defaults_with_tracing(true, true)).unwrap());
assert!(tracing_switch_to_bool(Switch::On, &user_defaults_with_tracing(true, false)).unwrap());
assert!(tracing_switch_to_bool(Switch::On, &user_defaults_with_tracing(false, true)).unwrap());
assert!(tracing_switch_to_bool(Switch::On, &user_defaults_with_tracing(false, false)).is_err());
}
}

View File

@@ -23,7 +23,7 @@ use ethcore_rpc::NetworkSettings;
use ethsync::NetworkConfiguration;
use util::{Colour, version, U256};
use io::{MayPanic, ForwardPanic, PanicHandler};
use ethcore::client::{Mode, Switch, DatabaseCompactionProfile, VMType, ChainNotify};
use ethcore::client::{Mode, DatabaseCompactionProfile, VMType, ChainNotify};
use ethcore::service::ClientService;
use ethcore::account_provider::AccountProvider;
use ethcore::miner::{Miner, MinerService, ExternalMiner, MinerOptions};
@@ -35,10 +35,11 @@ use rpc::{HttpServer, IpcServer, HttpConfiguration, IpcConfiguration};
use signer::SignerServer;
use dapps::WebappServer;
use io_handler::ClientIoHandler;
use params::{SpecType, Pruning, AccountsConfig, GasPricerConfig, MinerExtras};
use params::{SpecType, Pruning, AccountsConfig, GasPricerConfig, MinerExtras, Switch, tracing_switch_to_bool};
use helpers::{to_client_config, execute_upgrades, passwords_from_files};
use dir::Directories;
use cache::CacheConfig;
use user_defaults::UserDefaults;
use dapps;
use signer;
use modules;
@@ -87,34 +88,45 @@ pub struct RunCmd {
}
pub fn execute(cmd: RunCmd) -> Result<(), String> {
// increase max number of open files
raise_fd_limit();
// set up panic handler
let panic_handler = PanicHandler::new_in_arc();
// set up logger
let logger = try!(setup_log(&cmd.logger_config));
// set up panic handler
let panic_handler = PanicHandler::new_in_arc();
// increase max number of open files
raise_fd_limit();
// create dirs used by parity
try!(cmd.dirs.create_dirs());
// load spec
let spec = try!(cmd.spec.spec());
let fork_name = spec.fork_name.clone();
// load genesis hash
let genesis_hash = spec.genesis_header().hash();
// database paths
let db_dirs = cmd.dirs.database(genesis_hash, spec.fork_name.clone());
// user defaults path
let user_defaults_path = db_dirs.user_defaults_path();
// load user defaults
let mut user_defaults = try!(UserDefaults::load(&user_defaults_path));
// check if tracing is on
let tracing = try!(tracing_switch_to_bool(cmd.tracing, &user_defaults));
// select pruning algorithm
let algorithm = cmd.pruning.to_algorithm(&cmd.dirs, genesis_hash, fork_name.as_ref());
let algorithm = cmd.pruning.to_algorithm(&user_defaults);
// prepare client and snapshot paths.
let client_path = cmd.dirs.client_path(genesis_hash, fork_name.as_ref(), algorithm);
let snapshot_path = cmd.dirs.snapshot_path(genesis_hash, fork_name.as_ref());
let client_path = db_dirs.client_path(algorithm);
let snapshot_path = db_dirs.snapshot_path();
// execute upgrades
try!(execute_upgrades(&cmd.dirs, genesis_hash, fork_name.as_ref(), algorithm, cmd.compaction.compaction_profile()));
try!(execute_upgrades(&db_dirs, algorithm, cmd.compaction.compaction_profile()));
// run in daemon mode
if let Some(pid_file) = cmd.daemon {
@@ -152,16 +164,13 @@ pub fn execute(cmd: RunCmd) -> Result<(), String> {
// create client config
let client_config = to_client_config(
&cmd.cache_config,
&cmd.dirs,
genesis_hash,
cmd.mode,
cmd.tracing,
cmd.pruning,
tracing,
cmd.compaction,
cmd.wal,
cmd.vm_type,
cmd.name,
fork_name.as_ref(),
algorithm,
);
// set up bootnodes
@@ -288,6 +297,11 @@ pub fn execute(cmd: RunCmd) -> Result<(), String> {
url::open(&format!("http://{}:{}/", cmd.dapps_conf.interface, cmd.dapps_conf.port));
}
// save user defaults
user_defaults.pruning = algorithm;
user_defaults.tracing = tracing;
try!(user_defaults.save(&user_defaults_path));
// Handle exit
wait_for_exit(panic_handler, http_server, ipc_server, dapps_server, signer_server);

View File

@@ -25,14 +25,15 @@ use ethcore::snapshot::{Progress, RestorationStatus, SnapshotService as SS};
use ethcore::snapshot::io::{SnapshotReader, PackedReader, PackedWriter};
use ethcore::snapshot::service::Service as SnapshotService;
use ethcore::service::ClientService;
use ethcore::client::{Mode, DatabaseCompactionProfile, Switch, VMType};
use ethcore::client::{Mode, DatabaseCompactionProfile, VMType};
use ethcore::miner::Miner;
use ethcore::ids::BlockID;
use cache::CacheConfig;
use params::{SpecType, Pruning};
use params::{SpecType, Pruning, Switch, tracing_switch_to_bool};
use helpers::{to_client_config, execute_upgrades};
use dir::Directories;
use user_defaults::UserDefaults;
use fdlimit;
use io::PanicHandler;
@@ -129,23 +130,35 @@ impl SnapshotCommand {
// load genesis hash
let genesis_hash = spec.genesis_header().hash();
// database paths
let db_dirs = self.dirs.database(genesis_hash, spec.fork_name.clone());
// user defaults path
let user_defaults_path = db_dirs.user_defaults_path();
// load user defaults
let user_defaults = try!(UserDefaults::load(&user_defaults_path));
// check if tracing is on
let tracing = try!(tracing_switch_to_bool(self.tracing, &user_defaults));
// Setup logging
let _logger = setup_log(&self.logger_config);
fdlimit::raise_fd_limit();
// select pruning algorithm
let algorithm = self.pruning.to_algorithm(&self.dirs, genesis_hash, spec.fork_name.as_ref());
let algorithm = self.pruning.to_algorithm(&user_defaults);
// prepare client and snapshot paths.
let client_path = self.dirs.client_path(genesis_hash, spec.fork_name.as_ref(), algorithm);
let snapshot_path = self.dirs.snapshot_path(genesis_hash, spec.fork_name.as_ref());
let client_path = db_dirs.client_path(algorithm);
let snapshot_path = db_dirs.snapshot_path();
// execute upgrades
try!(execute_upgrades(&self.dirs, genesis_hash, spec.fork_name.as_ref(), algorithm, self.compaction.compaction_profile()));
try!(execute_upgrades(&db_dirs, algorithm, self.compaction.compaction_profile()));
// prepare client config
let client_config = to_client_config(&self.cache_config, &self.dirs, genesis_hash, self.mode, self.tracing, self.pruning, self.compaction, self.wal, VMType::default(), "".into(), spec.fork_name.as_ref());
let client_config = to_client_config(&self.cache_config, self.mode, tracing, self.compaction, self.wal, VMType::default(), "".into(), algorithm);
let service = try!(ClientService::start(
client_config,

98
parity/user_defaults.rs Normal file
View File

@@ -0,0 +1,98 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity 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.
// Parity 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 Parity. If not, see <http://www.gnu.org/licenses/>.
use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::collections::BTreeMap;
use serde::{Serialize, Serializer, Error, Deserialize, Deserializer};
use serde::de::{Visitor, MapVisitor};
use serde::de::impls::BTreeMapVisitor;
use serde_json::Value;
use serde_json::de::from_reader;
use serde_json::ser::to_string;
use util::journaldb::Algorithm;
pub struct UserDefaults {
pub is_first_launch: bool,
pub pruning: Algorithm,
pub tracing: bool,
}
impl Serialize for UserDefaults {
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
where S: Serializer {
let mut map: BTreeMap<String, Value> = BTreeMap::new();
map.insert("pruning".into(), Value::String(self.pruning.as_str().into()));
map.insert("tracing".into(), Value::Bool(self.tracing));
map.serialize(serializer)
}
}
struct UserDefaultsVisitor;
impl Deserialize for UserDefaults {
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
where D: Deserializer {
deserializer.deserialize(UserDefaultsVisitor)
}
}
impl Visitor for UserDefaultsVisitor {
type Value = UserDefaults;
fn visit_map<V>(&mut self, visitor: V) -> Result<Self::Value, V::Error>
where V: MapVisitor {
let mut map: BTreeMap<String, Value> = try!(BTreeMapVisitor::new().visit_map(visitor));
let pruning: Value = try!(map.remove("pruning".into()).ok_or_else(|| Error::custom("missing pruning")));
let pruning = try!(pruning.as_str().ok_or_else(|| Error::custom("invalid pruning value")));
let pruning = try!(pruning.parse().map_err(|_| Error::custom("invalid pruning method")));
let tracing: Value = try!(map.remove("tracing".into()).ok_or_else(|| Error::custom("missing tracing")));
let tracing = try!(tracing.as_bool().ok_or_else(|| Error::custom("invalid tracing value")));
let user_defaults = UserDefaults {
is_first_launch: false,
pruning: pruning,
tracing: tracing,
};
Ok(user_defaults)
}
}
impl Default for UserDefaults {
fn default() -> Self {
UserDefaults {
is_first_launch: true,
pruning: Algorithm::default(),
tracing: false,
}
}
}
impl UserDefaults {
pub fn load<P>(path: P) -> Result<Self, String> where P: AsRef<Path> {
match File::open(path) {
Ok(file) => from_reader(file).map_err(|e| e.to_string()),
_ => Ok(UserDefaults::default()),
}
}
pub fn save<P>(self, path: P) -> Result<(), String> where P: AsRef<Path> {
let mut file: File = try!(File::create(path).map_err(|_| "Cannot create user defaults file".to_owned()));
file.write_all(to_string(&self).unwrap().as_bytes()).map_err(|_| "Failed to save user defaults".to_owned())
}
}