| // Copyright 2016 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "peridot/bin/story_runner/module_controller_impl.h" |
| |
| #include <lib/async/cpp/task.h> |
| #include <lib/async/default.h> |
| |
| #include "lib/fidl/cpp/interface_handle.h" |
| #include "lib/fidl/cpp/interface_ptr.h" |
| #include "lib/fidl/cpp/interface_request.h" |
| #include "lib/fsl/tasks/message_loop.h" |
| #include "lib/fxl/logging.h" |
| #include "lib/fxl/tasks/task_runner.h" |
| #include "lib/fxl/time/time_delta.h" |
| #include "peridot/bin/story_runner/story_controller_impl.h" |
| #include "peridot/lib/common/teardown.h" |
| #include "peridot/lib/fidl/clone.h" |
| |
| namespace modular { |
| |
| constexpr char kAppStoragePath[] = "/data/APP_DATA"; |
| |
| namespace { |
| |
| // A stopgap solution to map a module's url to a directory name where the |
| // module's /data is mapped. We need three properties here - (1) two module urls |
| // that are the same get mapped to the same hash, (2) two modules urls that are |
| // different don't get the same name (with very high probability) and (3) the |
| // name is visually inspectable. |
| std::string HashModuleUrl(const std::string& module_url) { |
| std::size_t found = module_url.find_last_of('/'); |
| auto last_part = |
| found == module_url.length() - 1 ? "" : module_url.substr(found + 1); |
| return std::to_string(std::hash<std::string>{}(module_url)) + last_part; |
| } |
| |
| }; // namespace |
| |
| ModuleControllerImpl::ModuleControllerImpl( |
| StoryControllerImpl* const story_controller_impl, |
| component::ApplicationLauncher* const application_launcher, |
| AppConfig module_config, |
| const ModuleData* const module_data, |
| component::ServiceListPtr service_list, |
| fidl::InterfaceHandle<ModuleContext> module_context, |
| fidl::InterfaceRequest<views_v1::ViewProvider> view_provider_request, |
| fidl::InterfaceRequest<component::ServiceProvider> incoming_services) |
| : story_controller_impl_(story_controller_impl), |
| app_client_( |
| application_launcher, |
| CloneStruct(module_config), |
| std::string(kAppStoragePath) + HashModuleUrl(module_config.url), |
| std::move(service_list)), |
| module_data_(module_data) { |
| app_client_.SetAppErrorHandler([this] { SetState(ModuleState::ERROR); }); |
| |
| app_client_.services().ConnectToService(module_service_.NewRequest()); |
| module_service_.set_error_handler([this] { OnConnectionError(); }); |
| module_service_->Initialize(std::move(module_context), |
| std::move(incoming_services)); |
| |
| app_client_.services().ConnectToService(std::move(view_provider_request)); |
| |
| // Push the initial module state to story controller. TODO(mesch): This is |
| // only needed for the root module to transition the story state to STARTING |
| // and get IsRunning() to true. This could be handled inside |
| // StoryControllerImpl too. |
| story_controller_impl_->OnModuleStateChange(module_data_->module_path, |
| state_); |
| } |
| |
| ModuleControllerImpl::~ModuleControllerImpl() {} |
| |
| void ModuleControllerImpl::Connect( |
| fidl::InterfaceRequest<ModuleController> request) { |
| module_controller_bindings_.AddBinding(this, std::move(request)); |
| } |
| |
| // If the Module instance closes its own connection, we signal this to |
| // all current and future watchers by an appropriate state transition. |
| void ModuleControllerImpl::OnConnectionError() { |
| if (state_ == ModuleState::STARTING) { |
| SetState(ModuleState::UNLINKED); |
| } else { |
| SetState(ModuleState::ERROR); |
| } |
| } |
| |
| void ModuleControllerImpl::SetState(const ModuleState new_state) { |
| if (state_ == new_state) { |
| return; |
| } |
| |
| state_ = new_state; |
| for (const auto& i : watchers_.ptrs()) { |
| (*i)->OnStateChange(state_); |
| } |
| |
| story_controller_impl_->OnModuleStateChange(module_data_->module_path, |
| state_); |
| } |
| |
| void ModuleControllerImpl::Teardown(std::function<void()> done) { |
| teardown_.push_back(done); |
| |
| if (teardown_.size() != 1) { |
| // Not the first request, Stop() in progress. |
| return; |
| } |
| |
| auto cont = [this] { |
| module_service_.Unbind(); |
| SetState(ModuleState::STOPPED); |
| |
| // ReleaseModule() must be called before the callbacks, because |
| // StoryControllerImpl::Stop() relies on being called back *after* the |
| // module controller was disposed. |
| story_controller_impl_->ReleaseModule(this); |
| |
| for (auto& done : teardown_) { |
| done(); |
| } |
| |
| // |this| must be deleted after the callbacks so that the |done()| calls |
| // above can be dispatched while the bindings still exist in case they are |
| // FIDL method callbacks. |
| // |
| // The destructor of |this| deletes |app_client_|, which will kill the |
| // related application if it's still running. |
| delete this; |
| }; |
| |
| // At this point, it's no longer an error if the module closes its |
| // connection, or the application exits. |
| app_client_.SetAppErrorHandler(nullptr); |
| module_service_.set_error_handler(nullptr); |
| |
| // If the module was UNLINKED, stop it without a delay. Otherwise |
| // call Module.Stop(), but also schedule a timeout in case it |
| // doesn't return from Stop(). |
| if (state_ == ModuleState::UNLINKED) { |
| async::PostTask(async_get_default(), cont); |
| } else { |
| app_client_.Teardown(kBasicTimeout, cont); |
| } |
| } |
| |
| void ModuleControllerImpl::Watch(fidl::InterfaceHandle<ModuleWatcher> watcher) { |
| auto ptr = watcher.Bind(); |
| ptr->OnStateChange(state_); |
| watchers_.AddInterfacePtr(std::move(ptr)); |
| } |
| |
| void ModuleControllerImpl::Focus() { |
| story_controller_impl_->FocusModule(module_data_->module_path); |
| } |
| |
| void ModuleControllerImpl::Defocus() { |
| story_controller_impl_->DefocusModule(module_data_->module_path); |
| } |
| |
| void ModuleControllerImpl::Stop(StopCallback done) { |
| story_controller_impl_->StopModule(module_data_->module_path, done); |
| } |
| |
| } // namespace modular |