Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Commit

Permalink
Merge pull request #2050 from ethcore/dapps-content
Browse files Browse the repository at this point in the history
Fetching any content-addressed content
  • Loading branch information
rphmeier authored Sep 8, 2016
2 parents 860bc5f + 8460733 commit 8b97196
Show file tree
Hide file tree
Showing 9 changed files with 330 additions and 144 deletions.
2 changes: 1 addition & 1 deletion dapps/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ clippy = { version = "0.0.85", optional = true}
serde_codegen = { version = "0.8", optional = true }

[features]
default = ["serde_codegen", "extra-dapps", "https-fetch/ca-github-only"]
default = ["serde_codegen", "extra-dapps"]
extra-dapps = ["parity-dapps-wallet"]
nightly = ["serde_macros"]
dev = ["clippy", "ethcore-rpc/dev", "ethcore-util/dev"]
Expand Down
187 changes: 125 additions & 62 deletions dapps/src/apps/fetcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,65 +38,65 @@ use handlers::{ContentHandler, ContentFetcherHandler, ContentValidator};
use endpoint::{Endpoint, EndpointPath, Handler};
use apps::cache::{ContentCache, ContentStatus};
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest, serialize_manifest, Manifest};
use apps::urlhint::{URLHintContract, URLHint};
use apps::urlhint::{URLHintContract, URLHint, URLHintResult};

const MAX_CACHED_DAPPS: usize = 10;

pub struct AppFetcher<R: URLHint = URLHintContract> {
pub struct ContentFetcher<R: URLHint = URLHintContract> {
dapps_path: PathBuf,
resolver: R,
cache: Arc<Mutex<ContentCache>>,
sync: Arc<SyncStatus>,
dapps: Arc<Mutex<ContentCache>>,
}

impl<R: URLHint> Drop for AppFetcher<R> {
impl<R: URLHint> Drop for ContentFetcher<R> {
fn drop(&mut self) {
// Clear cache path
let _ = fs::remove_dir_all(&self.dapps_path);
}
}

impl<R: URLHint> AppFetcher<R> {
impl<R: URLHint> ContentFetcher<R> {

pub fn new(resolver: R, sync_status: Arc<SyncStatus>) -> Self {
let mut dapps_path = env::temp_dir();
dapps_path.push(random_filename());

AppFetcher {
ContentFetcher {
dapps_path: dapps_path,
resolver: resolver,
sync: sync_status,
dapps: Arc::new(Mutex::new(ContentCache::default())),
cache: Arc::new(Mutex::new(ContentCache::default())),
}
}

#[cfg(test)]
fn set_status(&self, app_id: &str, status: ContentStatus) {
self.dapps.lock().insert(app_id.to_owned(), status);
fn set_status(&self, content_id: &str, status: ContentStatus) {
self.cache.lock().insert(content_id.to_owned(), status);
}

pub fn contains(&self, app_id: &str) -> bool {
let mut dapps = self.dapps.lock();
pub fn contains(&self, content_id: &str) -> bool {
let mut cache = self.cache.lock();
// Check if we already have the app
if dapps.get(app_id).is_some() {
if cache.get(content_id).is_some() {
return true;
}
// fallback to resolver
if let Ok(app_id) = app_id.from_hex() {
if let Ok(content_id) = content_id.from_hex() {
// if app_id is valid, but we are syncing always return true.
if self.sync.is_major_syncing() {
return true;
}
// else try to resolve the app_id
self.resolver.resolve(app_id).is_some()
self.resolver.resolve(content_id).is_some()
} else {
false
}
}

pub fn to_async_handler(&self, path: EndpointPath, control: hyper::Control) -> Box<Handler> {
let mut dapps = self.dapps.lock();
let app_id = path.app_id.clone();
let mut cache = self.cache.lock();
let content_id = path.app_id.clone();

if self.sync.is_major_syncing() {
return Box::new(ContentHandler::error(
Expand All @@ -108,7 +108,7 @@ impl<R: URLHint> AppFetcher<R> {
}

let (new_status, handler) = {
let status = dapps.get(&app_id);
let status = cache.get(&content_id);
match status {
// Just server dapp
Some(&mut ContentStatus::Ready(ref endpoint)) => {
Expand All @@ -125,40 +125,57 @@ impl<R: URLHint> AppFetcher<R> {
},
// We need to start fetching app
None => {
let app_hex = app_id.from_hex().expect("to_handler is called only when `contains` returns true.");
let app = self.resolver.resolve(app_hex);

if let Some(app) = app {
let abort = Arc::new(AtomicBool::new(false));

(Some(ContentStatus::Fetching(abort.clone())), Box::new(ContentFetcherHandler::new(
app,
abort,
control,
path.using_dapps_domains,
DappInstaller {
dapp_id: app_id.clone(),
dapps_path: self.dapps_path.clone(),
dapps: self.dapps.clone(),
}
)) as Box<Handler>)
} else {
// This may happen when sync status changes in between
// `contains` and `to_handler`
(None, Box::new(ContentHandler::error(
StatusCode::NotFound,
"Resource Not Found",
"Requested resource was not found.",
None
)) as Box<Handler>)
let content_hex = content_id.from_hex().expect("to_handler is called only when `contains` returns true.");
let content = self.resolver.resolve(content_hex);
let abort = Arc::new(AtomicBool::new(false));

match content {
Some(URLHintResult::Dapp(dapp)) => (
Some(ContentStatus::Fetching(abort.clone())),
Box::new(ContentFetcherHandler::new(
dapp.url(),
abort,
control,
path.using_dapps_domains,
DappInstaller {
id: content_id.clone(),
dapps_path: self.dapps_path.clone(),
cache: self.cache.clone(),
})) as Box<Handler>
),
Some(URLHintResult::Content(content)) => (
Some(ContentStatus::Fetching(abort.clone())),
Box::new(ContentFetcherHandler::new(
content.url,
abort,
control,
path.using_dapps_domains,
ContentInstaller {
id: content_id.clone(),
mime: content.mime,
content_path: self.dapps_path.clone(),
cache: self.cache.clone(),
}
)) as Box<Handler>,
),
None => {
// This may happen when sync status changes in between
// `contains` and `to_handler`
(None, Box::new(ContentHandler::error(
StatusCode::NotFound,
"Resource Not Found",
"Requested resource was not found.",
None
)) as Box<Handler>)
},
}
},
}
};

if let Some(status) = new_status {
dapps.clear_garbage(MAX_CACHED_DAPPS);
dapps.insert(app_id, status);
cache.clear_garbage(MAX_CACHED_DAPPS);
cache.insert(content_id, status);
}

handler
Expand All @@ -169,7 +186,7 @@ impl<R: URLHint> AppFetcher<R> {
pub enum ValidationError {
Io(io::Error),
Zip(zip::result::ZipError),
InvalidDappId,
InvalidContentId,
ManifestNotFound,
ManifestSerialization(String),
HashMismatch { expected: H256, got: H256, },
Expand All @@ -180,7 +197,7 @@ impl fmt::Display for ValidationError {
match *self {
ValidationError::Io(ref io) => write!(f, "Unexpected IO error occured: {:?}", io),
ValidationError::Zip(ref zip) => write!(f, "Unable to read ZIP archive: {:?}", zip),
ValidationError::InvalidDappId => write!(f, "Dapp ID is invalid. It should be 32 bytes hash of content."),
ValidationError::InvalidContentId => write!(f, "ID is invalid. It should be 256 bits keccak hash of content."),
ValidationError::ManifestNotFound => write!(f, "Downloaded Dapp bundle did not contain valid manifest.json file."),
ValidationError::ManifestSerialization(ref err) => {
write!(f, "There was an error during Dapp Manifest serialization: {:?}", err)
Expand All @@ -204,10 +221,55 @@ impl From<zip::result::ZipError> for ValidationError {
}
}

struct ContentInstaller {
id: String,
mime: String,
content_path: PathBuf,
cache: Arc<Mutex<ContentCache>>,
}

impl ContentValidator for ContentInstaller {
type Error = ValidationError;
type Result = PathBuf;

fn validate_and_install(&self, path: PathBuf) -> Result<(String, PathBuf), ValidationError> {
// Create dir
try!(fs::create_dir_all(&self.content_path));

// And prepare path for a file
let filename = path.file_name().expect("We always fetch a file.");
let mut content_path = self.content_path.clone();
content_path.push(&filename);

if content_path.exists() {
try!(fs::remove_dir_all(&content_path))
}

try!(fs::copy(&path, &content_path));

Ok((self.id.clone(), content_path))
}

fn done(&self, result: Option<&PathBuf>) {
let mut cache = self.cache.lock();
match result {
Some(result) => {
let page = LocalPageEndpoint::single_file(result.clone(), self.mime.clone());
cache.insert(self.id.clone(), ContentStatus::Ready(page));
},
// In case of error
None => {
cache.remove(&self.id);
},
}
}
}


struct DappInstaller {
dapp_id: String,
id: String,
dapps_path: PathBuf,
dapps: Arc<Mutex<ContentCache>>,
cache: Arc<Mutex<ContentCache>>,
}

impl DappInstaller {
Expand Down Expand Up @@ -244,15 +306,16 @@ impl DappInstaller {

impl ContentValidator for DappInstaller {
type Error = ValidationError;
type Result = Manifest;

fn validate_and_install(&self, app_path: PathBuf) -> Result<Manifest, ValidationError> {
fn validate_and_install(&self, app_path: PathBuf) -> Result<(String, Manifest), ValidationError> {
trace!(target: "dapps", "Opening dapp bundle at {:?}", app_path);
let mut file_reader = io::BufReader::new(try!(fs::File::open(app_path)));
let hash = try!(sha3(&mut file_reader));
let dapp_id = try!(self.dapp_id.as_str().parse().map_err(|_| ValidationError::InvalidDappId));
if dapp_id != hash {
let id = try!(self.id.as_str().parse().map_err(|_| ValidationError::InvalidContentId));
if id != hash {
return Err(ValidationError::HashMismatch {
expected: dapp_id,
expected: id,
got: hash,
});
}
Expand All @@ -262,7 +325,7 @@ impl ContentValidator for DappInstaller {
// First find manifest file
let (mut manifest, manifest_dir) = try!(Self::find_manifest(&mut zip));
// Overwrite id to match hash
manifest.id = self.dapp_id.clone();
manifest.id = self.id.clone();

let target = self.dapp_target_path(&manifest);

Expand Down Expand Up @@ -300,20 +363,20 @@ impl ContentValidator for DappInstaller {
try!(manifest_file.write_all(manifest_str.as_bytes()));

// Return modified app manifest
Ok(manifest)
Ok((manifest.id.clone(), manifest))
}

fn done(&self, manifest: Option<&Manifest>) {
let mut dapps = self.dapps.lock();
let mut cache = self.cache.lock();
match manifest {
Some(manifest) => {
let path = self.dapp_target_path(manifest);
let app = LocalPageEndpoint::new(path, manifest.clone().into());
dapps.insert(self.dapp_id.clone(), ContentStatus::Ready(app));
cache.insert(self.id.clone(), ContentStatus::Ready(app));
},
// In case of error
None => {
dapps.remove(&self.dapp_id);
cache.remove(&self.id);
},
}
}
Expand All @@ -327,12 +390,12 @@ mod tests {
use endpoint::EndpointInfo;
use page::LocalPageEndpoint;
use apps::cache::ContentStatus;
use apps::urlhint::{GithubApp, URLHint};
use super::AppFetcher;
use apps::urlhint::{URLHint, URLHintResult};
use super::ContentFetcher;

struct FakeResolver;
impl URLHint for FakeResolver {
fn resolve(&self, _app_id: Bytes) -> Option<GithubApp> {
fn resolve(&self, _id: Bytes) -> Option<URLHintResult> {
None
}
}
Expand All @@ -341,7 +404,7 @@ mod tests {
fn should_true_if_contains_the_app() {
// given
let path = env::temp_dir();
let fetcher = AppFetcher::new(FakeResolver, Arc::new(|| false));
let fetcher = ContentFetcher::new(FakeResolver, Arc::new(|| false));
let handler = LocalPageEndpoint::new(path, EndpointInfo {
name: "fake".into(),
description: "".into(),
Expand Down
Loading

0 comments on commit 8b97196

Please sign in to comment.