// Copyright 2015-2019 Parity Technologies (UK) Ltd. // This file is part of Parity Ethereum. // Parity Ethereum 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 Ethereum 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 Ethereum. If not, see . //! Address Book Store use std::{fs, fmt, hash, ops}; use std::collections::HashMap; use std::path::{Path, PathBuf}; use ethkey::Address; use log::{trace, warn}; use crate::AccountMeta; /// Disk-backed map from Address to String. Uses JSON. pub struct AddressBook { cache: DiskMap, } impl AddressBook { /// Creates new address book at given directory. pub fn new(path: &Path) -> Self { let mut r = AddressBook { cache: DiskMap::new(path, "address_book.json") }; r.cache.revert(AccountMeta::read); r } /// Creates transient address book (no changes are saved to disk). pub fn transient() -> Self { AddressBook { cache: DiskMap::transient() } } /// Get the address book. pub fn get(&self) -> HashMap { self.cache.clone() } fn save(&self) { self.cache.save(AccountMeta::write) } /// Sets new name for given address. pub fn set_name(&mut self, a: Address, name: String) { { let x = self.cache.entry(a) .or_insert_with(|| AccountMeta {name: Default::default(), meta: "{}".to_owned(), uuid: None}); x.name = name; } self.save(); } /// Sets new meta for given address. pub fn set_meta(&mut self, a: Address, meta: String) { { let x = self.cache.entry(a) .or_insert_with(|| AccountMeta {name: "Anonymous".to_owned(), meta: Default::default(), uuid: None}); x.meta = meta; } self.save(); } /// Removes an entry pub fn remove(&mut self, a: Address) { self.cache.remove(&a); self.save(); } } /// Disk-serializable HashMap #[derive(Debug)] struct DiskMap { path: PathBuf, cache: HashMap, transient: bool, } impl ops::Deref for DiskMap { type Target = HashMap; fn deref(&self) -> &Self::Target { &self.cache } } impl ops::DerefMut for DiskMap { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.cache } } impl DiskMap { pub fn new(path: &Path, file_name: &str) -> Self { let mut path = path.to_owned(); path.push(file_name); trace!(target: "diskmap", "path={:?}", path); DiskMap { path: path, cache: HashMap::new(), transient: false, } } pub fn transient() -> Self { let mut map = DiskMap::new(&PathBuf::new(), "diskmap.json".into()); map.transient = true; map } fn revert(&mut self, read: F) where F: Fn(fs::File) -> Result, E>, E: fmt::Display, { if self.transient { return; } trace!(target: "diskmap", "revert {:?}", self.path); let _ = fs::File::open(self.path.clone()) .map_err(|e| trace!(target: "diskmap", "Couldn't open disk map: {}", e)) .and_then(|f| read(f).map_err(|e| warn!(target: "diskmap", "Couldn't read disk map at: {:?} {}", self.path, e))) .and_then(|m| { self.cache = m; Ok(()) }); } fn save(&self, write: F) where F: Fn(&HashMap, &mut fs::File) -> Result<(), E>, E: fmt::Display, { if self.transient { return; } trace!(target: "diskmap", "save {:?}", self.path); let _ = fs::File::create(self.path.clone()) .map_err(|e| warn!(target: "diskmap", "Couldn't open disk map for writing at: {:?} {}", self.path, e)) .and_then(|mut f| { write(&self.cache, &mut f).map_err(|e| warn!(target: "diskmap", "Couldn't write to disk map at: {:?} {}", self.path, e)) }); } } #[cfg(test)] mod tests { use super::{AddressBook, Address}; use std::collections::HashMap; use tempdir::TempDir; use crate::account_data::AccountMeta; #[test] fn should_save_and_reload_address_book() { let tempdir = TempDir::new("").unwrap(); let mut b = AddressBook::new(tempdir.path()); b.set_name(Address::from_low_u64_be(1), "One".to_owned()); b.set_meta(Address::from_low_u64_be(1), "{1:1}".to_owned()); let b = AddressBook::new(tempdir.path()); assert_eq!(b.get(), vec![ (Address::from_low_u64_be(1), AccountMeta {name: "One".to_owned(), meta: "{1:1}".to_owned(), uuid: None}) ].into_iter().collect::>()); } #[test] fn should_remove_address() { let tempdir = TempDir::new("").unwrap(); let mut b = AddressBook::new(tempdir.path()); b.set_name(Address::from_low_u64_be(1), "One".to_owned()); b.set_name(Address::from_low_u64_be(2), "Two".to_owned()); b.set_name(Address::from_low_u64_be(3), "Three".to_owned()); b.remove(Address::from_low_u64_be(2).into()); let b = AddressBook::new(tempdir.path()); assert_eq!(b.get(), vec![ (Address::from_low_u64_be(1), AccountMeta{name: "One".to_owned(), meta: "{}".to_owned(), uuid: None}), (Address::from_low_u64_be(3), AccountMeta{name: "Three".to_owned(), meta: "{}".to_owned(), uuid: None}), ].into_iter().collect::>()); } }