| // Copyright 2018 The Chromium 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 "chrome/browser/web_applications/web_app_database.h" |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "chrome/browser/web_applications/proto/web_app.pb.h" |
| #include "chrome/browser/web_applications/web_app.h" |
| #include "chrome/browser/web_applications/web_app_database_factory.h" |
| #include "components/sync/base/model_type.h" |
| #include "components/sync/model/model_error.h" |
| |
| namespace web_app { |
| |
| WebAppDatabase::WebAppDatabase(AbstractWebAppDatabaseFactory* database_factory) |
| : database_factory_(database_factory) { |
| DCHECK(database_factory_); |
| } |
| |
| WebAppDatabase::~WebAppDatabase() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| } |
| |
| void WebAppDatabase::OpenDatabase(OnceRegistryOpenedCallback callback) { |
| DCHECK(!store_); |
| |
| syncer::OnceModelTypeStoreFactory store_factory = |
| database_factory_->GetStoreFactory(); |
| |
| // CreateStore then ReadRegistry. |
| CreateStore( |
| std::move(store_factory), |
| base::BindOnce(&WebAppDatabase::ReadRegistry, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void WebAppDatabase::WriteWebApp(const WebApp& web_app) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(opened_); |
| |
| BeginTransaction(); |
| |
| auto proto = CreateWebAppProto(web_app); |
| write_batch_->WriteData(proto->app_id(), proto->SerializeAsString()); |
| |
| CommitTransaction(); |
| } |
| |
| void WebAppDatabase::DeleteWebApps(std::vector<AppId> app_ids) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(opened_); |
| |
| BeginTransaction(); |
| for (auto& app_id : app_ids) |
| write_batch_->DeleteData(app_id); |
| CommitTransaction(); |
| } |
| |
| // static |
| std::unique_ptr<WebAppProto> WebAppDatabase::CreateWebAppProto( |
| const WebApp& web_app) { |
| auto proto = std::make_unique<WebAppProto>(); |
| |
| proto->set_app_id(web_app.app_id()); |
| proto->set_name(web_app.name()); |
| proto->set_description(web_app.description()); |
| proto->set_launch_url(web_app.launch_url().spec()); |
| if (!web_app.scope().is_empty()) |
| proto->set_scope(web_app.scope().spec()); |
| if (web_app.theme_color()) |
| proto->set_theme_color(web_app.theme_color().value()); |
| |
| for (const WebApp::IconInfo& icon : web_app.icons()) { |
| WebAppIconInfoProto* icon_proto = proto->add_icons(); |
| icon_proto->set_url(icon.url.spec()); |
| icon_proto->set_size_in_px(icon.size_in_px); |
| } |
| |
| return proto; |
| } |
| |
| // static |
| std::unique_ptr<WebApp> WebAppDatabase::CreateWebApp(const WebAppProto& proto) { |
| auto web_app = std::make_unique<WebApp>(proto.app_id()); |
| |
| GURL launch_url(proto.launch_url()); |
| if (launch_url.is_empty() || !launch_url.is_valid()) { |
| LOG(ERROR) << "WebApp proto launch_url parse error: " |
| << launch_url.possibly_invalid_spec(); |
| return nullptr; |
| } |
| |
| web_app->SetLaunchUrl(launch_url); |
| web_app->SetName(proto.name()); |
| web_app->SetDescription(proto.description()); |
| |
| if (proto.has_scope()) { |
| GURL scope(proto.scope()); |
| if (scope.is_empty() || !scope.is_valid()) { |
| LOG(ERROR) << "WebApp proto scope parse error: " |
| << scope.possibly_invalid_spec(); |
| return nullptr; |
| } |
| web_app->SetScope(scope); |
| } |
| |
| if (proto.has_theme_color()) |
| web_app->SetThemeColor(proto.theme_color()); |
| |
| if (proto.icons_size() == 0) { |
| LOG(ERROR) << "WebApp proto parse icons error: no icons"; |
| return nullptr; |
| } |
| |
| WebApp::Icons icons; |
| for (int i = 0; i < proto.icons_size(); ++i) { |
| const WebAppIconInfoProto& icon_proto = proto.icons(i); |
| |
| GURL icon_url(icon_proto.url()); |
| if (icon_url.is_empty() || !icon_url.is_valid()) { |
| LOG(ERROR) << "WebApp IconInfo proto url parse error: " |
| << icon_url.possibly_invalid_spec(); |
| return nullptr; |
| } |
| |
| icons.push_back({icon_url, icon_proto.size_in_px()}); |
| } |
| web_app->SetIcons(std::move(icons)); |
| |
| return web_app; |
| } |
| |
| void WebAppDatabase::ReadRegistry(OnceRegistryOpenedCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(store_); |
| store_->ReadAllData(base::BindOnce(&WebAppDatabase::OnAllDataRead, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(callback))); |
| } |
| |
| void WebAppDatabase::CreateStore( |
| syncer::OnceModelTypeStoreFactory store_factory, |
| base::OnceClosure closure) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // For now we use syncer:APPS prefix within local isolated LevelDB, no sync. |
| // TODO(loyso): Create separate ModelType::WEB_APPS before implementing sync. |
| // Otherwise it may interfere with existing APPS data. |
| std::move(store_factory) |
| .Run(syncer::APPS, |
| base::BindOnce(&WebAppDatabase::OnStoreCreated, |
| weak_ptr_factory_.GetWeakPtr(), std::move(closure))); |
| } |
| |
| void WebAppDatabase::OnStoreCreated( |
| base::OnceClosure closure, |
| const base::Optional<syncer::ModelError>& error, |
| std::unique_ptr<syncer::ModelTypeStore> store) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (error) { |
| LOG(ERROR) << "WebApps LevelDB opening error: " << error->ToString(); |
| return; |
| } |
| |
| store_ = std::move(store); |
| std::move(closure).Run(); |
| } |
| |
| void WebAppDatabase::OnAllDataRead( |
| OnceRegistryOpenedCallback callback, |
| const base::Optional<syncer::ModelError>& error, |
| std::unique_ptr<syncer::ModelTypeStore::RecordList> data_records) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (error) { |
| LOG(ERROR) << "WebApps LevelDB read error: " << error->ToString(); |
| return; |
| } |
| |
| Registry registry; |
| for (const syncer::ModelTypeStore::Record& record : *data_records) { |
| const AppId app_id = record.id; |
| std::unique_ptr<WebApp> web_app = ParseWebApp(app_id, record.value); |
| if (web_app) |
| registry.emplace(app_id, std::move(web_app)); |
| } |
| |
| std::move(callback).Run(std::move(registry)); |
| opened_ = true; |
| } |
| |
| void WebAppDatabase::OnDataWritten( |
| const base::Optional<syncer::ModelError>& error) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (error) |
| LOG(ERROR) << "WebApps LevelDB write error: " << error->ToString(); |
| } |
| |
| // static |
| std::unique_ptr<WebApp> WebAppDatabase::ParseWebApp(const AppId& app_id, |
| const std::string& value) { |
| WebAppProto proto; |
| const bool parsed = proto.ParseFromString(value); |
| if (!parsed || proto.app_id() != app_id) { |
| LOG(ERROR) << "WebApps LevelDB parse error (unknown)."; |
| return nullptr; |
| } |
| |
| return CreateWebApp(proto); |
| } |
| |
| void WebAppDatabase::BeginTransaction() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(!write_batch_); |
| write_batch_ = store_->CreateWriteBatch(); |
| } |
| |
| void WebAppDatabase::CommitTransaction() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(write_batch_); |
| |
| store_->CommitWriteBatch(std::move(write_batch_), |
| base::BindOnce(&WebAppDatabase::OnDataWritten, |
| weak_ptr_factory_.GetWeakPtr())); |
| write_batch_.reset(); |
| } |
| |
| } // namespace web_app |