1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "mojo/shell/dynamic_application_loader.h"
6
7#include "base/bind.h"
8#include "base/command_line.h"
9#include "base/files/file_path.h"
10#include "base/files/file_util.h"
11#include "base/memory/scoped_ptr.h"
12#include "base/memory/weak_ptr.h"
13#include "base/message_loop/message_loop.h"
14#include "mojo/common/common_type_converters.h"
15#include "mojo/common/data_pipe_utils.h"
16#include "mojo/services/public/interfaces/network/url_loader.mojom.h"
17#include "mojo/shell/context.h"
18#include "mojo/shell/switches.h"
19#include "net/base/filename_util.h"
20
21namespace mojo {
22namespace shell {
23
24// Encapsulates loading and running one individual application.
25//
26// Loaders are owned by DynamicApplicationLoader. DynamicApplicationLoader must
27// ensure that all the parameters passed to Loader subclasses stay valid through
28// Loader's lifetime.
29//
30// Async operations are done with WeakPtr to protect against
31// DynamicApplicationLoader going away (and taking all the Loaders with it)
32// while the async operation is outstanding.
33class DynamicApplicationLoader::Loader {
34 public:
35  Loader(Context* context,
36         DynamicServiceRunnerFactory* runner_factory,
37         scoped_refptr<ApplicationLoader::LoadCallbacks> load_callbacks,
38         const LoaderCompleteCallback& loader_complete_callback)
39      : load_callbacks_(load_callbacks),
40        loader_complete_callback_(loader_complete_callback),
41        context_(context),
42        runner_factory_(runner_factory),
43        weak_ptr_factory_(this) {}
44
45  virtual ~Loader() {}
46
47 protected:
48  void RunLibrary(const base::FilePath& path, bool path_exists) {
49    ScopedMessagePipeHandle shell_handle =
50        load_callbacks_->RegisterApplication();
51    if (!shell_handle.is_valid()) {
52      LoaderComplete();
53      return;
54    }
55
56    if (!path_exists) {
57      DVLOG(1) << "Library not started because library path '" << path.value()
58               << "' does not exist.";
59      LoaderComplete();
60      return;
61    }
62
63    runner_ = runner_factory_->Create(context_);
64    runner_->Start(
65        path,
66        shell_handle.Pass(),
67        base::Bind(&Loader::LoaderComplete, weak_ptr_factory_.GetWeakPtr()));
68  }
69
70  void LoaderComplete() { loader_complete_callback_.Run(this); }
71
72  scoped_refptr<ApplicationLoader::LoadCallbacks> load_callbacks_;
73  LoaderCompleteCallback loader_complete_callback_;
74  Context* context_;
75
76 private:
77  DynamicServiceRunnerFactory* runner_factory_;
78  scoped_ptr<DynamicServiceRunner> runner_;
79  base::WeakPtrFactory<Loader> weak_ptr_factory_;
80};
81
82// A loader for local files.
83class DynamicApplicationLoader::LocalLoader : public Loader {
84 public:
85  LocalLoader(const GURL& url,
86              Context* context,
87              DynamicServiceRunnerFactory* runner_factory,
88              scoped_refptr<ApplicationLoader::LoadCallbacks> load_callbacks,
89              const LoaderCompleteCallback& loader_complete_callback)
90      : Loader(context,
91               runner_factory,
92               load_callbacks,
93               loader_complete_callback),
94        weak_ptr_factory_(this) {
95    base::FilePath path;
96    net::FileURLToFilePath(url, &path);
97
98    // Async for consistency with network case.
99    base::MessageLoop::current()->PostTask(
100        FROM_HERE,
101        base::Bind(&LocalLoader::RunLibrary,
102                   weak_ptr_factory_.GetWeakPtr(),
103                   path,
104                   base::PathExists(path)));
105  }
106
107  virtual ~LocalLoader() {}
108
109 private:
110  base::WeakPtrFactory<LocalLoader> weak_ptr_factory_;
111};
112
113// A loader for network files.
114class DynamicApplicationLoader::NetworkLoader : public Loader {
115 public:
116  NetworkLoader(const GURL& url,
117                MimeTypeToURLMap* mime_type_to_url,
118                Context* context,
119                DynamicServiceRunnerFactory* runner_factory,
120                NetworkService* network_service,
121                scoped_refptr<ApplicationLoader::LoadCallbacks> load_callbacks,
122                const LoaderCompleteCallback& loader_complete_callback)
123      : Loader(context,
124               runner_factory,
125               load_callbacks,
126               loader_complete_callback),
127        mime_type_to_url_(mime_type_to_url),
128        weak_ptr_factory_(this) {
129    URLRequestPtr request(URLRequest::New());
130    request->url = String::From(url);
131    request->auto_follow_redirects = true;
132
133    if (base::CommandLine::ForCurrentProcess()->HasSwitch(
134            switches::kDisableCache)) {
135      request->bypass_cache = true;
136    }
137
138    network_service->CreateURLLoader(Get(&url_loader_));
139    url_loader_->Start(request.Pass(),
140                       base::Bind(&NetworkLoader::OnLoadComplete,
141                                  weak_ptr_factory_.GetWeakPtr()));
142  }
143
144  virtual ~NetworkLoader() {
145    if (!file_.empty())
146      base::DeleteFile(file_, false);
147  }
148
149 private:
150  void OnLoadComplete(URLResponsePtr response) {
151    if (response->error) {
152      LOG(ERROR) << "Error (" << response->error->code << ": "
153                 << response->error->description << ") while fetching "
154                 << response->url;
155      LoaderComplete();
156      return;
157    }
158
159    MimeTypeToURLMap::iterator iter =
160        mime_type_to_url_->find(response->mime_type);
161    if (iter != mime_type_to_url_->end()) {
162      load_callbacks_->LoadWithContentHandler(iter->second, response.Pass());
163      return;
164    }
165
166    base::CreateTemporaryFile(&file_);
167    common::CopyToFile(
168        response->body.Pass(),
169        file_,
170        context_->task_runners()->blocking_pool(),
171        base::Bind(
172            &NetworkLoader::RunLibrary, weak_ptr_factory_.GetWeakPtr(), file_));
173  }
174
175  MimeTypeToURLMap* mime_type_to_url_;
176  URLLoaderPtr url_loader_;
177  base::FilePath file_;
178  base::WeakPtrFactory<NetworkLoader> weak_ptr_factory_;
179};
180
181DynamicApplicationLoader::DynamicApplicationLoader(
182    Context* context,
183    scoped_ptr<DynamicServiceRunnerFactory> runner_factory)
184    : context_(context),
185      runner_factory_(runner_factory.Pass()),
186
187      // Unretained() is correct here because DynamicApplicationLoader owns the
188      // loaders that we pass this callback to.
189      loader_complete_callback_(
190          base::Bind(&DynamicApplicationLoader::LoaderComplete,
191                     base::Unretained(this))) {
192}
193
194DynamicApplicationLoader::~DynamicApplicationLoader() {
195}
196
197void DynamicApplicationLoader::RegisterContentHandler(
198    const std::string& mime_type,
199    const GURL& content_handler_url) {
200  mime_type_to_url_[mime_type] = content_handler_url;
201}
202
203void DynamicApplicationLoader::Load(
204    ApplicationManager* manager,
205    const GURL& url,
206    scoped_refptr<LoadCallbacks> load_callbacks) {
207  GURL resolved_url;
208  if (url.SchemeIs("mojo")) {
209    resolved_url = context_->mojo_url_resolver()->Resolve(url);
210  } else {
211    resolved_url = url;
212  }
213
214  if (resolved_url.SchemeIsFile()) {
215    loaders_.push_back(new LocalLoader(resolved_url,
216                                       context_,
217                                       runner_factory_.get(),
218                                       load_callbacks,
219                                       loader_complete_callback_));
220    return;
221  }
222
223  if (!network_service_) {
224    context_->application_manager()->ConnectToService(
225        GURL("mojo:mojo_network_service"), &network_service_);
226  }
227
228  loaders_.push_back(new NetworkLoader(resolved_url,
229                                       &mime_type_to_url_,
230                                       context_,
231                                       runner_factory_.get(),
232                                       network_service_.get(),
233                                       load_callbacks,
234                                       loader_complete_callback_));
235}
236
237void DynamicApplicationLoader::OnApplicationError(ApplicationManager* manager,
238                                                  const GURL& url) {
239  // TODO(darin): What should we do about service errors? This implies that
240  // the app closed its handle to the service manager. Maybe we don't care?
241}
242
243void DynamicApplicationLoader::LoaderComplete(Loader* loader) {
244  loaders_.erase(std::find(loaders_.begin(), loaders_.end(), loader));
245}
246
247}  // namespace shell
248}  // namespace mojo
249