Opening local dapp (#4041)

* Opening local dapp

* Using Path/PathBuf instead of Strings

* Fixing typo and adding some docs to apps::fs functions
This commit is contained in:
Tomasz Drwięga 2017-01-06 16:05:58 +01:00 committed by Gav Wood
parent 332a45846d
commit e983339edd
9 changed files with 188 additions and 66 deletions

View File

@ -17,7 +17,7 @@
use std::io;
use std::io::Read;
use std::fs;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use page::{LocalPageEndpoint, PageCache};
use endpoint::{Endpoints, EndpointInfo};
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest};
@ -28,10 +28,79 @@ struct LocalDapp {
info: EndpointInfo,
}
fn local_dapps(dapps_path: String) -> Vec<LocalDapp> {
let files = fs::read_dir(dapps_path.as_str());
/// Tries to find and read manifest file in given `path` to extract `EndpointInfo`
/// If manifest is not found sensible default `EndpointInfo` is returned based on given `name`.
fn read_manifest(name: &str, mut path: PathBuf) -> EndpointInfo {
path.push(MANIFEST_FILENAME);
fs::File::open(path.clone())
.map_err(|e| format!("{:?}", e))
.and_then(|mut f| {
// Reat file
let mut s = String::new();
f.read_to_string(&mut s).map_err(|e| format!("{:?}", e))?;
// Try to deserialize manifest
deserialize_manifest(s)
})
.map(Into::into)
.unwrap_or_else(|e| {
warn!(target: "dapps", "Cannot read manifest file at: {:?}. Error: {:?}", path, e);
EndpointInfo {
name: name.into(),
description: name.into(),
version: "0.0.0".into(),
author: "?".into(),
icon_url: "icon.png".into(),
}
})
}
/// Returns Dapp Id and Local Dapp Endpoint for given filesystem path.
/// Parses the path to extract last component (for name).
/// `None` is returned when path is invalid or non-existent.
pub fn local_endpoint<P: AsRef<Path>>(path: P, signer_address: Option<(String, u16)>) -> Option<(String, Box<LocalPageEndpoint>)> {
let path = path.as_ref().to_owned();
path.canonicalize().ok().and_then(|path| {
let name = path.file_name().and_then(|name| name.to_str());
name.map(|name| {
let dapp = local_dapp(name.into(), path.clone());
(dapp.id, Box::new(LocalPageEndpoint::new(
dapp.path, dapp.info, PageCache::Disabled, signer_address.clone())
))
})
})
}
fn local_dapp(name: String, path: PathBuf) -> LocalDapp {
// try to get manifest file
let info = read_manifest(&name, path.clone());
LocalDapp {
id: name,
path: path,
info: info,
}
}
/// Returns endpoints for Local Dapps found for given filesystem path.
/// Scans the directory and collects `LocalPageEndpoints`.
pub fn local_endpoints<P: AsRef<Path>>(dapps_path: P, signer_address: Option<(String, u16)>) -> Endpoints {
let mut pages = Endpoints::new();
for dapp in local_dapps(dapps_path.as_ref()) {
pages.insert(
dapp.id,
Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, PageCache::Disabled, signer_address.clone()))
);
}
pages
}
fn local_dapps(dapps_path: &Path) -> Vec<LocalDapp> {
let files = fs::read_dir(dapps_path);
if let Err(e) = files {
warn!(target: "dapps", "Unable to load local dapps from: {}. Reason: {:?}", dapps_path, e);
warn!(target: "dapps", "Unable to load local dapps from: {}. Reason: {:?}", dapps_path.display(), e);
return vec![];
}
@ -59,51 +128,6 @@ fn local_dapps(dapps_path: String) -> Vec<LocalDapp> {
}
m.ok()
})
.map(|(name, path)| {
// try to get manifest file
let info = read_manifest(&name, path.clone());
LocalDapp {
id: name,
path: path,
info: info,
}
})
.map(|(name, path)| local_dapp(name, path))
.collect()
}
fn read_manifest(name: &str, mut path: PathBuf) -> EndpointInfo {
path.push(MANIFEST_FILENAME);
fs::File::open(path.clone())
.map_err(|e| format!("{:?}", e))
.and_then(|mut f| {
// Reat file
let mut s = String::new();
f.read_to_string(&mut s).map_err(|e| format!("{:?}", e))?;
// Try to deserialize manifest
deserialize_manifest(s)
})
.map(Into::into)
.unwrap_or_else(|e| {
warn!(target: "dapps", "Cannot read manifest file at: {:?}. Error: {:?}", path, e);
EndpointInfo {
name: name.into(),
description: name.into(),
version: "0.0.0".into(),
author: "?".into(),
icon_url: "icon.png".into(),
}
})
}
pub fn local_endpoints(dapps_path: String, signer_address: Option<(String, u16)>) -> Endpoints {
let mut pages = Endpoints::new();
for dapp in local_dapps(dapps_path) {
pages.insert(
dapp.id,
Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, PageCache::Disabled, signer_address.clone()))
);
}
pages
}

View File

@ -14,6 +14,7 @@
// 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::path::PathBuf;
use std::sync::Arc;
use endpoint::{Endpoints, Endpoint};
use page::PageEndpoint;
@ -43,7 +44,8 @@ pub fn utils() -> Box<Endpoint> {
}
pub fn all_endpoints<F: Fetch>(
dapps_path: String,
dapps_path: PathBuf,
extra_dapps: Vec<PathBuf>,
signer_address: Option<(String, u16)>,
web_proxy_tokens: Arc<WebProxyTokens>,
remote: Remote,
@ -51,6 +53,13 @@ pub fn all_endpoints<F: Fetch>(
) -> Endpoints {
// fetch fs dapps at first to avoid overwriting builtins
let mut pages = fs::local_endpoints(dapps_path, signer_address.clone());
for path in extra_dapps {
if let Some((id, endpoint)) = fs::local_endpoint(path.clone(), signer_address.clone()) {
pages.insert(id, endpoint);
} else {
warn!(target: "dapps", "Ignoring invalid dapp at {}", path.display());
}
}
// NOTE [ToDr] Dapps will be currently embeded on 8180
insert::<parity_ui::App>(&mut pages, "ui", Embeddable::Yes(signer_address.clone()));

View File

@ -88,6 +88,7 @@ mod web;
#[cfg(test)]
mod tests;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::net::SocketAddr;
use std::collections::HashMap;
@ -123,7 +124,8 @@ impl<F> WebProxyTokens for F where F: Fn(String) -> bool + Send + Sync {
/// Webapps HTTP+RPC server build.
pub struct ServerBuilder<T: Fetch = FetchClient> {
dapps_path: String,
dapps_path: PathBuf,
extra_dapps: Vec<PathBuf>,
handler: Arc<IoHandler>,
registrar: Arc<ContractClient>,
sync_status: Arc<SyncStatus>,
@ -141,9 +143,10 @@ impl<T: Fetch> Extendable for ServerBuilder<T> {
impl ServerBuilder {
/// Construct new dapps server
pub fn new(dapps_path: String, registrar: Arc<ContractClient>, remote: Remote) -> Self {
pub fn new<P: AsRef<Path>>(dapps_path: P, registrar: Arc<ContractClient>, remote: Remote) -> Self {
ServerBuilder {
dapps_path: dapps_path,
dapps_path: dapps_path.as_ref().to_owned(),
extra_dapps: vec![],
handler: Arc::new(IoHandler::new()),
registrar: registrar,
sync_status: Arc::new(|| false),
@ -160,6 +163,7 @@ impl<T: Fetch> ServerBuilder<T> {
pub fn fetch<X: Fetch>(self, fetch: X) -> ServerBuilder<X> {
ServerBuilder {
dapps_path: self.dapps_path,
extra_dapps: vec![],
handler: self.handler,
registrar: self.registrar,
sync_status: self.sync_status,
@ -188,6 +192,12 @@ impl<T: Fetch> ServerBuilder<T> {
self
}
/// Change extra dapps paths (apart from `dapps_path`)
pub fn extra_dapps<P: AsRef<Path>>(mut self, extra_dapps: &[P]) -> Self {
self.extra_dapps = extra_dapps.iter().map(|p| p.as_ref().to_owned()).collect();
self
}
/// Asynchronously start server with no authentication,
/// returns result with `Server` handle on success or an error.
pub fn start_unsecured_http(self, addr: &SocketAddr, hosts: Option<Vec<String>>) -> Result<Server, ServerError> {
@ -197,6 +207,7 @@ impl<T: Fetch> ServerBuilder<T> {
NoAuth,
self.handler.clone(),
self.dapps_path.clone(),
self.extra_dapps.clone(),
self.signer_address.clone(),
self.registrar.clone(),
self.sync_status.clone(),
@ -215,6 +226,7 @@ impl<T: Fetch> ServerBuilder<T> {
HttpBasicAuth::single_user(username, password),
self.handler.clone(),
self.dapps_path.clone(),
self.extra_dapps.clone(),
self.signer_address.clone(),
self.registrar.clone(),
self.sync_status.clone(),
@ -270,7 +282,8 @@ impl Server {
hosts: Option<Vec<String>>,
authorization: A,
handler: Arc<IoHandler>,
dapps_path: String,
dapps_path: PathBuf,
extra_dapps: Vec<PathBuf>,
signer_address: Option<(String, u16)>,
registrar: Arc<ContractClient>,
sync_status: Arc<SyncStatus>,
@ -287,7 +300,14 @@ impl Server {
remote.clone(),
fetch.clone(),
));
let endpoints = Arc::new(apps::all_endpoints(dapps_path, signer_address.clone(), web_proxy_tokens, remote.clone(), fetch.clone()));
let endpoints = Arc::new(apps::all_endpoints(
dapps_path,
extra_dapps,
signer_address.clone(),
web_proxy_tokens,
remote.clone(),
fetch.clone(),
));
let cors_domains = Self::cors_domains(signer_address.clone());
let special = Arc::new({

View File

@ -51,7 +51,7 @@ pub fn init_server<F, B>(hosts: Option<Vec<String>>, process: F, remote: Remote)
let mut dapps_path = env::temp_dir();
dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading");
let server = process(ServerBuilder::new(
dapps_path.to_str().unwrap().into(), registrar.clone(), remote,
&dapps_path, registrar.clone(), remote,
))
.signer_address(Some(("127.0.0.1".into(), SIGNER_PORT)))
.start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), hosts).unwrap();
@ -66,7 +66,7 @@ pub fn serve_with_auth(user: &str, pass: &str) -> Server {
let registrar = Arc::new(FakeRegistrar::new());
let mut dapps_path = env::temp_dir();
dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading");
ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar.clone(), Remote::new_sync())
ServerBuilder::new(&dapps_path, registrar.clone(), Remote::new_sync())
.signer_address(Some(("127.0.0.1".into(), SIGNER_PORT)))
.start_basic_auth_http(&"127.0.0.1:0".parse().unwrap(), None, user, pass).unwrap()
}

View File

@ -37,6 +37,7 @@ usage! {
cmd_snapshot: bool,
cmd_restore: bool,
cmd_ui: bool,
cmd_dapp: bool,
cmd_tools: bool,
cmd_hash: bool,
cmd_kill: bool,
@ -525,6 +526,7 @@ mod tests {
cmd_snapshot: false,
cmd_restore: false,
cmd_ui: false,
cmd_dapp: false,
cmd_tools: false,
cmd_hash: false,
cmd_db: false,

View File

@ -5,6 +5,7 @@ Parity. Ethereum Client.
Usage:
parity [options]
parity ui [options]
parity dapp <path> [options]
parity daemon <pid-file> [options]
parity account (new | list ) [options]
parity account import <path>... [options]

View File

@ -17,7 +17,7 @@
use std::time::Duration;
use std::io::Read;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::cmp::max;
use cli::{Args, ArgsError};
use util::{Hashable, U256, Uint, Bytes, version_data, Secret, Address};
@ -335,6 +335,7 @@ impl Configuration {
net_settings: self.network_settings(),
dapps_conf: dapps_conf,
signer_conf: signer_conf,
dapp: self.dapp_to_open()?,
ui: self.args.cmd_ui,
name: self.args.flag_identity,
custom_bootnodes: self.args.flag_bootnodes.is_some(),
@ -507,10 +508,28 @@ impl Configuration {
hosts: self.dapps_hosts(),
user: self.args.flag_dapps_user.clone(),
pass: self.args.flag_dapps_pass.clone(),
dapps_path: self.directories().dapps,
dapps_path: PathBuf::from(self.directories().dapps),
extra_dapps: if self.args.cmd_dapp {
self.args.arg_path.iter().map(|path| PathBuf::from(path)).collect()
} else {
vec![]
},
}
}
fn dapp_to_open(&self) -> Result<Option<String>, String> {
if !self.args.cmd_dapp {
return Ok(None);
}
let path = self.args.arg_path.get(0).map(String::as_str).unwrap_or(".");
let path = Path::new(path).canonicalize()
.map_err(|e| format!("Invalid path: {}. Error: {:?}", path, e))?;
let name = path.file_name()
.and_then(|name| name.to_str())
.ok_or_else(|| "Root path is not supported.".to_owned())?;
Ok(Some(name.into()))
}
fn gas_pricer_config(&self) -> Result<GasPricerConfig, String> {
if let Some(d) = self.args.flag_gasprice.as_ref() {
return Ok(GasPricerConfig::Fixed(to_u256(d)?));
@ -1030,6 +1049,7 @@ mod tests {
dapps_conf: Default::default(),
signer_conf: Default::default(),
ui: false,
dapp: None,
name: "".into(),
custom_bootnodes: false,
fat_db: Default::default(),
@ -1224,6 +1244,22 @@ mod tests {
});
}
#[test]
fn should_parse_dapp_opening() {
// given
let temp = RandomTempPath::new();
let name = temp.file_name().unwrap().to_str().unwrap();
create_dir(temp.as_str().to_owned()).unwrap();
// when
let conf0 = parse(&["parity", "dapp", temp.to_str().unwrap()]);
// then
assert_eq!(conf0.dapp_to_open(), Ok(Some(name.into())));
let extra_dapps = conf0.dapps_config().extra_dapps;
assert_eq!(extra_dapps, vec![temp.to_owned()]);
}
#[test]
fn should_not_bail_on_empty_line_in_reserved_peers() {
let temp = RandomTempPath::new();

View File

@ -14,6 +14,7 @@
// 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::path::PathBuf;
use std::sync::Arc;
use io::PanicHandler;
use rpc_apis;
@ -33,7 +34,8 @@ pub struct Configuration {
pub hosts: Option<Vec<String>>,
pub user: Option<String>,
pub pass: Option<String>,
pub dapps_path: String,
pub dapps_path: PathBuf,
pub extra_dapps: Vec<PathBuf>,
}
impl Default for Configuration {
@ -46,7 +48,8 @@ impl Default for Configuration {
hosts: Some(Vec::new()),
user: None,
pass: None,
dapps_path: replace_home(&data_dir, "$BASE/dapps"),
dapps_path: replace_home(&data_dir, "$BASE/dapps").into(),
extra_dapps: vec![],
}
}
}
@ -80,7 +83,14 @@ pub fn new(configuration: Configuration, deps: Dependencies) -> Result<Option<We
(username.to_owned(), password)
});
Ok(Some(setup_dapps_server(deps, configuration.dapps_path, &addr, configuration.hosts, auth)?))
Ok(Some(setup_dapps_server(
deps,
configuration.dapps_path,
configuration.extra_dapps,
&addr,
configuration.hosts,
auth
)?))
}
pub use self::server::WebappServer;
@ -94,7 +104,8 @@ mod server {
pub struct WebappServer;
pub fn setup_dapps_server(
_deps: Dependencies,
_dapps_path: String,
_dapps_path: PathBuf,
_extra_dapps: Vec<PathBuf>,
_url: &SocketAddr,
_allowed_hosts: Option<Vec<String>>,
_auth: Option<(String, String)>,
@ -106,6 +117,7 @@ mod server {
#[cfg(feature = "dapps")]
mod server {
use super::Dependencies;
use std::path::PathBuf;
use std::sync::Arc;
use std::net::SocketAddr;
use std::io;
@ -122,7 +134,8 @@ mod server {
pub fn setup_dapps_server(
deps: Dependencies,
dapps_path: String,
dapps_path: PathBuf,
extra_dapps: Vec<PathBuf>,
url: &SocketAddr,
allowed_hosts: Option<Vec<String>>,
auth: Option<(String, String)>,
@ -130,7 +143,7 @@ mod server {
use ethcore_dapps as dapps;
let server = dapps::ServerBuilder::new(
dapps_path,
&dapps_path,
Arc::new(Registrar { client: deps.client.clone() }),
deps.remote.clone(),
);
@ -141,6 +154,7 @@ mod server {
.fetch(deps.fetch.clone())
.sync_status(Arc::new(move || is_major_importing(Some(sync.status().state), client.queue_info())))
.web_proxy_tokens(Arc::new(move |token| signer.is_valid_web_proxy_access_token(&token)))
.extra_dapps(&extra_dapps)
.signer_address(deps.signer.address());
let server = rpc_apis::setup_rpc(server, deps.apis.clone(), rpc_apis::ApiSet::UnsafeContext);

View File

@ -92,6 +92,7 @@ pub struct RunCmd {
pub net_settings: NetworkSettings,
pub dapps_conf: dapps::Configuration,
pub signer_conf: signer::Configuration,
pub dapp: Option<String>,
pub ui: bool,
pub name: String,
pub custom_bootnodes: bool,
@ -118,6 +119,17 @@ pub fn open_ui(dapps_conf: &dapps::Configuration, signer_conf: &signer::Configur
Ok(())
}
pub fn open_dapp(dapps_conf: &dapps::Configuration, dapp: &str) -> Result<(), String> {
if !dapps_conf.enabled {
return Err("Cannot use DAPP command with Dapps turned off.".into())
}
let url = format!("http://{}:{}/{}/", dapps_conf.interface, dapps_conf.port, dapp);
url::open(&url);
Ok(())
}
pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> Result<bool, String> {
if cmd.ui && cmd.dapps_conf.enabled {
// Check if Parity is already running
@ -441,6 +453,10 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc<RotatingLogger>) -> R
open_ui(&cmd.dapps_conf, &cmd.signer_conf)?;
}
if let Some(dapp) = cmd.dapp {
open_dapp(&cmd.dapps_conf, &dapp)?;
}
// Handle exit
let restart = wait_for_exit(panic_handler, Some(updater), can_restart);