| Index: shell/application_manager/network_fetcher.cc
 | 
| diff --git a/shell/application_manager/network_fetcher.cc b/shell/application_manager/network_fetcher.cc
 | 
| index 31caa194ce9193fe80fe0b453b50c7061b458b3f..a4a1e723013ecb1795dd569b1a6bebdcd7ad8295 100644
 | 
| --- a/shell/application_manager/network_fetcher.cc
 | 
| +++ b/shell/application_manager/network_fetcher.cc
 | 
| @@ -32,8 +32,96 @@ char kArchitecture[] = "android-arm";
 | 
|  #else
 | 
|  #error "Unsupported."
 | 
|  #endif
 | 
| +
 | 
| +// The delay to wait before trying to update an application after having served
 | 
| +// it from the cache.
 | 
| +const uint32_t kUpdateApplicationDelayInSeconds = 60;
 | 
| +
 | 
| +base::FilePath ToFilePath(const mojo::Array<uint8_t>& array) {
 | 
| +  return base::FilePath(
 | 
| +      std::string(reinterpret_cast<const char*>(&array.front()), array.size()));
 | 
| +}
 | 
| +
 | 
| +void IgnoreResult(bool result) {}
 | 
| +
 | 
| +mojo::URLRequestPtr GetRequest(const GURL& url, bool disable_cache) {
 | 
| +  mojo::URLRequestPtr request(mojo::URLRequest::New());
 | 
| +  request->url = mojo::String::From(url);
 | 
| +  request->auto_follow_redirects = false;
 | 
| +  if (disable_cache)
 | 
| +    request->cache_mode = mojo::URLRequest::CACHE_MODE_BYPASS_CACHE;
 | 
| +  auto architecture_header = mojo::HttpHeader::New();
 | 
| +  architecture_header->name = "X-Architecture";
 | 
| +  architecture_header->value = kArchitecture;
 | 
| +  mojo::Array<mojo::HttpHeaderPtr> headers;
 | 
| +  headers.push_back(architecture_header.Pass());
 | 
| +  request->headers = headers.Pass();
 | 
| +
 | 
| +  return request.Pass();
 | 
| +}
 | 
| +
 | 
| +// This class is self owned and will delete itself after having tried to update
 | 
| +// the application cache.
 | 
| +class ApplicationUpdater : public base::MessageLoop::DestructionObserver {
 | 
| + public:
 | 
| +  ApplicationUpdater(const GURL& url,
 | 
| +                     const base::TimeDelta& update_delay,
 | 
| +                     mojo::URLResponseDiskCache* url_response_disk_cache,
 | 
| +                     mojo::NetworkService* network_service);
 | 
| +  ~ApplicationUpdater() override;
 | 
| +
 | 
| + private:
 | 
| +  // DestructionObserver
 | 
| +  void WillDestroyCurrentMessageLoop() override;
 | 
| +
 | 
| +  void UpdateApplication();
 | 
| +  void OnLoadComplete(mojo::URLResponsePtr response);
 | 
| +
 | 
| +  GURL url_;
 | 
| +  base::TimeDelta update_delay_;
 | 
| +  mojo::URLResponseDiskCache* url_response_disk_cache_;
 | 
| +  mojo::NetworkService* network_service_;
 | 
| +  mojo::URLLoaderPtr url_loader_;
 | 
|  };
 | 
|  
 | 
| +ApplicationUpdater::ApplicationUpdater(
 | 
| +    const GURL& url,
 | 
| +    const base::TimeDelta& update_delay,
 | 
| +    mojo::URLResponseDiskCache* url_response_disk_cache,
 | 
| +    mojo::NetworkService* network_service)
 | 
| +    : url_(url),
 | 
| +      update_delay_(update_delay),
 | 
| +      url_response_disk_cache_(url_response_disk_cache),
 | 
| +      network_service_(network_service) {
 | 
| +  base::MessageLoop::current()->AddDestructionObserver(this);
 | 
| +  base::MessageLoop::current()->PostDelayedTask(
 | 
| +      FROM_HERE, base::Bind(&ApplicationUpdater::UpdateApplication,
 | 
| +                            base::Unretained(this)),
 | 
| +      update_delay_);
 | 
| +}
 | 
| +
 | 
| +ApplicationUpdater::~ApplicationUpdater() {
 | 
| +  base::MessageLoop::current()->RemoveDestructionObserver(this);
 | 
| +}
 | 
| +
 | 
| +void ApplicationUpdater::WillDestroyCurrentMessageLoop() {
 | 
| +  delete this;
 | 
| +}
 | 
| +
 | 
| +void ApplicationUpdater::UpdateApplication() {
 | 
| +  network_service_->CreateURLLoader(GetProxy(&url_loader_));
 | 
| +  url_loader_->Start(
 | 
| +      GetRequest(url_, false),
 | 
| +      base::Bind(&ApplicationUpdater::OnLoadComplete, base::Unretained(this)));
 | 
| +}
 | 
| +
 | 
| +void ApplicationUpdater::OnLoadComplete(mojo::URLResponsePtr response) {
 | 
| +  url_response_disk_cache_->Update(response.Pass());
 | 
| +  delete this;
 | 
| +}
 | 
| +
 | 
| +}  // namespace
 | 
| +
 | 
|  NetworkFetcher::NetworkFetcher(
 | 
|      bool disable_cache,
 | 
|      const GURL& url,
 | 
| @@ -46,7 +134,11 @@ NetworkFetcher::NetworkFetcher(
 | 
|        url_response_disk_cache_(url_response_disk_cache),
 | 
|        network_service_(network_service),
 | 
|        weak_ptr_factory_(this) {
 | 
| -  StartNetworkRequest(FROM_NETWORK);
 | 
| +  if (CanLoadDirectlyFromCache()) {
 | 
| +    LoadFromCache(true);
 | 
| +  } else {
 | 
| +    StartNetworkRequest();
 | 
| +  }
 | 
|  }
 | 
|  
 | 
|  NetworkFetcher::~NetworkFetcher() {
 | 
| @@ -69,61 +161,25 @@ GURL NetworkFetcher::GetRedirectURL() const {
 | 
|  mojo::URLResponsePtr NetworkFetcher::AsURLResponse(
 | 
|      base::TaskRunner* task_runner,
 | 
|      uint32_t skip) {
 | 
| -  if (skip != 0) {
 | 
| -    MojoResult result = ReadDataRaw(
 | 
| -        response_->body.get(), nullptr, &skip,
 | 
| -        MOJO_READ_DATA_FLAG_ALL_OR_NONE | MOJO_READ_DATA_FLAG_DISCARD);
 | 
| -    DCHECK_EQ(result, MOJO_RESULT_OK);
 | 
| -  }
 | 
| +  DCHECK(response_);
 | 
| +  DCHECK(!path_.empty());
 | 
| +  mojo::DataPipe data_pipe;
 | 
| +  response_->body = data_pipe.consumer_handle.Pass();
 | 
| +  mojo::common::CopyFromFile(path_, data_pipe.producer_handle.Pass(), skip,
 | 
| +                             task_runner, base::Bind(&IgnoreResult));
 | 
|    return response_.Pass();
 | 
|  }
 | 
|  
 | 
| -void NetworkFetcher::RecordCacheToURLMapping(const base::FilePath& path,
 | 
| -                                             const GURL& url) {
 | 
| -  // This is used to extract symbols on android.
 | 
| -  // TODO(eseidel): All users of this log should move to using the map file.
 | 
| -  LOG(INFO) << "Caching mojo app " << url << " at " << path.value();
 | 
| -
 | 
| -  base::FilePath temp_dir;
 | 
| -  base::GetTempDir(&temp_dir);
 | 
| -  base::ProcessId pid = base::Process::Current().Pid();
 | 
| -  std::string map_name = base::StringPrintf("mojo_shell.%d.maps", pid);
 | 
| -  base::FilePath map_path = temp_dir.Append(map_name);
 | 
| -
 | 
| -  // TODO(eseidel): Paths or URLs with spaces will need quoting.
 | 
| -  std::string map_entry =
 | 
| -      base::StringPrintf("%s %s\n", path.value().c_str(), url.spec().c_str());
 | 
| -  // TODO(eseidel): AppendToFile is missing O_CREAT, crbug.com/450696
 | 
| -  if (!PathExists(map_path))
 | 
| -    base::WriteFile(map_path, map_entry.data(), map_entry.length());
 | 
| -  else
 | 
| -    base::AppendToFile(map_path, map_entry.data(), map_entry.length());
 | 
| -}
 | 
| -
 | 
| -void NetworkFetcher::OnFileRetrievedFromCache(
 | 
| -    base::Callback<void(const base::FilePath&, bool)> callback,
 | 
| -    mojo::Array<uint8_t> path_as_array,
 | 
| -    mojo::Array<uint8_t> cache_dir) {
 | 
| -  bool success = !path_as_array.is_null();
 | 
| -  if (success) {
 | 
| -    path_ = base::FilePath(std::string(
 | 
| -        reinterpret_cast<char*>(&path_as_array.front()), path_as_array.size()));
 | 
| -    RecordCacheToURLMapping(path_, url_);
 | 
| -  }
 | 
| -
 | 
| -  base::MessageLoop::current()->PostTask(FROM_HERE,
 | 
| -                                         base::Bind(callback, path_, success));
 | 
| -}
 | 
| -
 | 
|  void NetworkFetcher::AsPath(
 | 
|      base::TaskRunner* task_runner,
 | 
|      base::Callback<void(const base::FilePath&, bool)> callback) {
 | 
|    // This should only called once, when we have a response.
 | 
|    DCHECK(response_.get());
 | 
|  
 | 
| -  url_response_disk_cache_->GetFile(
 | 
| -      response_.Pass(), base::Bind(&NetworkFetcher::OnFileRetrievedFromCache,
 | 
| -                                   weak_ptr_factory_.GetWeakPtr(), callback));
 | 
| +  base::MessageLoop::current()->PostTask(
 | 
| +      FROM_HERE, base::Bind(callback, path_, base::PathExists(path_)));
 | 
| +  response_.reset();
 | 
| +  return;
 | 
|  }
 | 
|  
 | 
|  std::string NetworkFetcher::MimeType() {
 | 
| @@ -131,51 +187,67 @@ std::string NetworkFetcher::MimeType() {
 | 
|  }
 | 
|  
 | 
|  bool NetworkFetcher::HasMojoMagic() {
 | 
| -  std::string magic;
 | 
| -  return BlockingPeekNBytes(response_->body.get(), &magic, strlen(kMojoMagic),
 | 
| -                            kPeekTimeout) &&
 | 
| -         magic == kMojoMagic;
 | 
| +  return Fetcher::HasMojoMagic(path_);
 | 
|  }
 | 
|  
 | 
|  bool NetworkFetcher::PeekFirstLine(std::string* line) {
 | 
| -  return BlockingPeekLine(response_->body.get(), line, kMaxShebangLength,
 | 
| -                          kPeekTimeout);
 | 
| +  return Fetcher::PeekFirstLine(path_, line);
 | 
|  }
 | 
|  
 | 
| -void NetworkFetcher::StartNetworkRequest(RequestType request_type) {
 | 
| -  TRACE_EVENT_ASYNC_BEGIN1("mojo_shell", "NetworkFetcher::NetworkRequest", this,
 | 
| -                           "url", url_.spec());
 | 
| -  mojo::URLRequestPtr request(mojo::URLRequest::New());
 | 
| -  request->url = mojo::String::From(url_);
 | 
| -  request->auto_follow_redirects = false;
 | 
| +bool NetworkFetcher::CanLoadDirectlyFromCache() {
 | 
|    if (disable_cache_)
 | 
| -    request->cache_mode = mojo::URLRequest::CACHE_MODE_BYPASS_CACHE;
 | 
| -  auto header = mojo::HttpHeader::New();
 | 
| -  header->name = "X-Architecture";
 | 
| -  header->value = kArchitecture;
 | 
| -  mojo::Array<mojo::HttpHeaderPtr> headers;
 | 
| -  headers.push_back(header.Pass());
 | 
| -  request->headers = headers.Pass();
 | 
| +    return false;
 | 
| +
 | 
| +  const std::string& host = url_.host();
 | 
| +  return !(host == "localhost" || host == "127.0.0.1" || host == "[::1]");
 | 
| +}
 | 
| +
 | 
| +void NetworkFetcher::LoadFromCache(bool sheduleUpdate) {
 | 
| +  url_response_disk_cache_->Get(
 | 
| +      mojo::String::From(url_),
 | 
| +      base::Bind(&NetworkFetcher::OnCachedResponseReceived,
 | 
| +                 base::Unretained(this), sheduleUpdate));
 | 
| +}
 | 
|  
 | 
| +void NetworkFetcher::OnCachedResponseReceived(
 | 
| +    bool sheduleUpdate,
 | 
| +    mojo::URLResponsePtr response,
 | 
| +    mojo::Array<uint8_t> path_as_array,
 | 
| +    mojo::Array<uint8_t> cache_dir) {
 | 
| +  if (!response) {
 | 
| +    // Not in cache, loading from net.
 | 
| +    StartNetworkRequest();
 | 
| +    return;
 | 
| +  }
 | 
| +  if (sheduleUpdate) {
 | 
| +    // The response has been found in the cache. Plan updating the application.
 | 
| +    new ApplicationUpdater(
 | 
| +        url_, base::TimeDelta::FromSeconds(kUpdateApplicationDelayInSeconds),
 | 
| +        url_response_disk_cache_, network_service_);
 | 
| +  }
 | 
| +  response_ = response.Pass();
 | 
| +  path_ = ToFilePath(path_as_array);
 | 
| +  RecordCacheToURLMapping(path_, url_);
 | 
| +  loader_callback_.Run(make_scoped_ptr(this));
 | 
| +}
 | 
| +
 | 
| +void NetworkFetcher::StartNetworkRequest() {
 | 
| +  TRACE_EVENT_ASYNC_BEGIN1("mojo_shell", "NetworkFetcher::NetworkRequest", this,
 | 
| +                           "url", url_.spec());
 | 
|    network_service_->CreateURLLoader(GetProxy(&url_loader_));
 | 
| -  url_loader_->Start(request.Pass(),
 | 
| +  url_loader_->Start(GetRequest(url_, disable_cache_),
 | 
|                       base::Bind(&NetworkFetcher::OnLoadComplete,
 | 
| -                                weak_ptr_factory_.GetWeakPtr(), request_type));
 | 
| +                                weak_ptr_factory_.GetWeakPtr()));
 | 
|  }
 | 
|  
 | 
| -void NetworkFetcher::OnLoadComplete(RequestType request_type,
 | 
| -                                    mojo::URLResponsePtr response) {
 | 
| +void NetworkFetcher::OnLoadComplete(mojo::URLResponsePtr response) {
 | 
|    TRACE_EVENT_ASYNC_END0("mojo_shell", "NetworkFetcher::NetworkRequest", this);
 | 
| -  scoped_ptr<Fetcher> owner(this);
 | 
|    if (response->error) {
 | 
|      LOG(ERROR) << "Error (" << response->error->code << ": "
 | 
|                 << response->error->description << ") while fetching "
 | 
|                 << response->url;
 | 
| -    if (request_type == FROM_NETWORK) {
 | 
| -      StartNetworkRequest(FROM_CACHE);
 | 
| -    } else {
 | 
| -      loader_callback_.Run(nullptr);
 | 
| -    }
 | 
| +    loader_callback_.Run(nullptr);
 | 
| +    delete this;
 | 
|      return;
 | 
|    }
 | 
|  
 | 
| @@ -184,11 +256,54 @@ void NetworkFetcher::OnLoadComplete(RequestType request_type,
 | 
|                 << response->status_line << "): "
 | 
|                 << "while fetching " << response->url;
 | 
|      loader_callback_.Run(nullptr);
 | 
| +    delete this;
 | 
|      return;
 | 
|    }
 | 
|  
 | 
| -  response_ = response.Pass();
 | 
| -  loader_callback_.Run(owner.Pass());
 | 
| +  if (!response->redirect_url.is_null()) {
 | 
| +    response_ = response.Pass();
 | 
| +    loader_callback_.Run(make_scoped_ptr(this));
 | 
| +    return;
 | 
| +  }
 | 
| +
 | 
| +  url_response_disk_cache_->UpdateAndGet(
 | 
| +      response.Pass(), base::Bind(&NetworkFetcher::OnFileSavedToCache,
 | 
| +                                  weak_ptr_factory_.GetWeakPtr()));
 | 
| +}
 | 
| +
 | 
| +void NetworkFetcher::OnFileSavedToCache(mojo::Array<uint8_t> path_as_array,
 | 
| +                                        mojo::Array<uint8_t> cache_dir) {
 | 
| +  if (!path_as_array) {
 | 
| +    LOG(WARNING) << "Error when retrieving content from cache for: "
 | 
| +                 << url_.spec();
 | 
| +    loader_callback_.Run(nullptr);
 | 
| +    delete this;
 | 
| +    return;
 | 
| +  }
 | 
| +  LoadFromCache(false);
 | 
| +}
 | 
| +
 | 
| +void NetworkFetcher::RecordCacheToURLMapping(const base::FilePath& path,
 | 
| +                                             const GURL& url) {
 | 
| +  // This is used to extract symbols on android.
 | 
| +  // TODO(eseidel): All users of this log should move to using the map file.
 | 
| +  LOG(INFO) << "Caching mojo app " << url << " at " << path.value();
 | 
| +
 | 
| +  base::FilePath temp_dir;
 | 
| +  base::GetTempDir(&temp_dir);
 | 
| +  base::ProcessId pid = base::Process::Current().Pid();
 | 
| +  std::string map_name = base::StringPrintf("mojo_shell.%d.maps", pid);
 | 
| +  base::FilePath map_path = temp_dir.Append(map_name);
 | 
| +
 | 
| +  // TODO(eseidel): Paths or URLs with spaces will need quoting.
 | 
| +  std::string map_entry =
 | 
| +      base::StringPrintf("%s %s\n", path.value().c_str(), url.spec().c_str());
 | 
| +  // TODO(eseidel): AppendToFile is missing O_CREAT, crbug.com/450696
 | 
| +  if (!PathExists(map_path)) {
 | 
| +    base::WriteFile(map_path, map_entry.data(), map_entry.length());
 | 
| +  } else {
 | 
| +    base::AppendToFile(map_path, map_entry.data(), map_entry.length());
 | 
| +  }
 | 
|  }
 | 
|  
 | 
|  }  // namespace shell
 | 
| 
 |