Migrate PreferencesRepository from location/nearby to third_party/nearby

PiperOrigin-RevId: 524185819
This commit is contained in:
Chun Zhang
2023-04-13 21:19:12 -07:00
committed by Copybara-Service
parent b023a666b9
commit 00bba36219
13 changed files with 651 additions and 1 deletions
+2
View File
@@ -31,6 +31,7 @@ cc_library(
"log_message.h",
"mutex.h",
"output_file.h",
"preferences_repository.h",
"scheduled_executor.h",
"settable_future.h",
"submittable_executor.h",
@@ -53,6 +54,7 @@ cc_library(
"@com_google_absl//absl/functional:any_invocable",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/time",
"@nlohmann_json//:json",
],
)
@@ -29,6 +29,7 @@ objc_library(
"log_message.mm",
"multi_thread_executor.mm",
"platform.mm",
"preferences_repository.mm",
"scheduled_executor.mm",
"timer.mm",
"utils.mm",
@@ -40,6 +41,7 @@ objc_library(
"device_info.h",
"log_message.h",
"multi_thread_executor.h",
"preferences_repository.h",
"scheduled_executor.h",
"single_thread_executor.h",
"timer.h",
@@ -67,6 +69,7 @@ objc_library(
"@com_google_absl//absl/strings",
"@com_google_absl//absl/time",
"@com_google_absl//absl/types:optional",
"@nlohmann_json//:json",
] + select({
"//tools/cc_target_os:platform_ios": [
"//third_party/apple_frameworks:UIKit",
@@ -0,0 +1,40 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPLEMENTATION_APPLE_PREFERENCES_REPOSITORY_H_
#define PLATFORM_IMPLEMENTATION_APPLE_PREFERENCES_REPOSITORY_H_
#include "absl/base/thread_annotations.h"
#include "absl/synchronization/mutex.h"
#include "nlohmann/json_fwd.hpp"
#include "internal/platform/implementation/preferences_repository.h"
namespace nearby::apple {
class PreferencesRepository : public api::PreferencesRepository {
public:
explicit PreferencesRepository(absl::string_view path)
: api::PreferencesRepository(path) {}
nlohmann::json LoadPreferences() override ABSL_LOCKS_EXCLUDED(&mutex_);
bool SavePreferences(nlohmann::json preferences) override
ABSL_LOCKS_EXCLUDED(&mutex_);
private:
absl::Mutex mutex_;
};
} // namespace nearby::apple
#endif // PLATFORM_IMPLEMENTATION_APPLE_PREFERENCES_REPOSITORY_H_
@@ -0,0 +1,32 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/apple/preferences_repository.h"
#include "absl/synchronization/mutex.h"
#include "nlohmann/json.hpp"
namespace nearby::apple {
nlohmann::json PreferencesRepository::LoadPreferences() {
absl::MutexLock lock(&mutex_);
return nlohmann::json::object();
}
bool PreferencesRepository::SavePreferences(nlohmann::json preferences) {
absl::MutexLock lock(&mutex_);
return false;
}
} // namespace nearby::apple
+4 -1
View File
@@ -18,6 +18,7 @@ cc_library(
testonly = True,
srcs = [
"log_message.cc",
"preferences_repository.cc",
"scheduled_executor.cc",
"system_clock.cc",
],
@@ -30,11 +31,12 @@ cc_library(
"multi_thread_executor.h",
"mutex.h",
"pipe.h",
"preferences_repository.h",
"scheduled_executor.h",
"single_thread_executor.h",
"timer.h",
],
visibility = ["//visibility:private"],
visibility = ["//location/nearby/cpp:__subpackages__"],
deps = [
"//internal/platform:base",
"//internal/platform:logging",
@@ -52,6 +54,7 @@ cc_library(
"@com_google_absl//absl/time",
"@com_google_glog//:glog",
"@com_google_nisaba//nisaba/port:thread_pool",
"@nlohmann_json//:json",
],
alwayslink = 1,
)
@@ -0,0 +1,70 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/g3/preferences_repository.h"
#include <filesystem> // NOLINT(build/c++17)
#include <fstream>
#include "absl/synchronization/mutex.h"
#include "nlohmann/json.hpp"
#include "nlohmann/json_fwd.hpp"
namespace nearby {
namespace g3 {
namespace {
using json = nlohmann::json;
} // namespace
json PreferencesRepository::LoadPreferences() {
absl::MutexLock lock(&mutex_);
// Emulate Windows implementation
try {
// settings.json is used for testing, but we should look at having
// an implementation override for G3 in PreferencesManager to override
// the path for testing.
std::filesystem::path path =
std::filesystem::temp_directory_path() / "settings.json";
if (!std::filesystem::exists(path)) {
return value_;
}
std::ifstream preferences_file(path.c_str());
if (!preferences_file.good()) {
return value_;
}
json preferences = json::parse(preferences_file, nullptr, false);
preferences_file.close();
if (preferences.is_discarded()) {
return value_;
}
value_ = preferences;
} catch (...) {
return value_;
}
return value_;
}
bool PreferencesRepository::SavePreferences(json preferences) {
absl::MutexLock lock(&mutex_);
value_ = preferences;
return true;
}
} // namespace g3
} // namespace nearby
@@ -0,0 +1,47 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPLEMENTATION_G3_PREFERENCES_REPOSITORY_H_
#define PLATFORM_IMPLEMENTATION_G3_PREFERENCES_REPOSITORY_H_
#include "absl/base/thread_annotations.h"
#include "absl/strings/string_view.h"
#include "absl/synchronization/mutex.h"
#include "nlohmann/json.hpp"
#include "nlohmann/json_fwd.hpp"
#include "internal/platform/implementation/preferences_repository.h"
namespace nearby {
namespace g3 {
class PreferencesRepository : public api::PreferencesRepository {
public:
explicit PreferencesRepository(absl::string_view path)
: api::PreferencesRepository(path) {}
nlohmann::json LoadPreferences() override ABSL_LOCKS_EXCLUDED(&mutex_);
bool SavePreferences(nlohmann::json preferences) override
ABSL_LOCKS_EXCLUDED(&mutex_);
private:
// Avoid to write in google3, just create a memory value to simulate a
// preferences storage
nlohmann::json value_ = nlohmann::json::object();
absl::Mutex mutex_;
};
} // namespace g3
} // namespace nearby
#endif // PLATFORM_IMPLEMENTATION_G3_PREFERENCES_REPOSITORY_H_
@@ -0,0 +1,68 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/g3/preferences_repository.h"
#include <filesystem> // NOLINT(build/c++17)
#include <string>
#include "gtest/gtest.h"
#include "nlohmann/json.hpp"
#include "nlohmann/json_fwd.hpp"
namespace nearby {
namespace platform {
namespace g3 {
namespace {
using json = ::nlohmann::json;
} // namespace
TEST(Preferences, TestSaveAndGetPreferences) {
PreferencesRepository preferences_repository{
std::filesystem::temp_directory_path().string()};
std::string string_key = "string_value";
std::string string_value = "hello world";
std::string int_key = "int_value";
json prefs = {{int_key, 345}, {string_key, string_value}};
EXPECT_TRUE(preferences_repository.SavePreferences(prefs));
auto result = preferences_repository.LoadPreferences();
EXPECT_EQ(result[string_key].get<std::string>(), string_value);
EXPECT_EQ(result[int_key].get<int>(), 345);
}
TEST(Preferences, TestMultipleSaveAndGetPreferences) {
PreferencesRepository preferences_repository{
std::filesystem::temp_directory_path().string()};
std::string string_key = "string_value";
std::string string_value = "hello world";
std::string string_new_value = "again";
std::string int_key = "int_value";
json prefs = {{int_key, 345}, {string_key, string_value}};
EXPECT_TRUE(preferences_repository.SavePreferences(prefs));
json result = preferences_repository.LoadPreferences();
EXPECT_EQ(result[string_key].get<std::string>(), string_value);
prefs[string_key] = string_new_value;
prefs[int_key] = 456;
EXPECT_TRUE(preferences_repository.SavePreferences(prefs));
result = preferences_repository.LoadPreferences();
EXPECT_EQ(result[string_key].get<std::string>(), string_new_value);
EXPECT_EQ(result[int_key].get<int>(), 456);
}
} // namespace g3
} // namespace platform
} // namespace nearby
@@ -0,0 +1,47 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPLEMENTATION_PREFERENCE_REPOSITORY_H_
#define PLATFORM_IMPLEMENTATION_PREFERENCE_REPOSITORY_H_
#include <string>
#include "absl/strings/string_view.h"
#include "nlohmann/json_fwd.hpp"
namespace nearby {
namespace api {
// A repository for preferences. The implementations are different on different
// platforms.
//
// The data in preferences has multiple types, such as int, bool, string and
// dictionary. Using proto to describe it is a little complicated. In the
// repository, we use json as the parser for now.
class PreferencesRepository {
public:
explicit PreferencesRepository(absl::string_view path) : path_(path) {}
virtual ~PreferencesRepository() = default;
virtual nlohmann::json LoadPreferences() = 0;
virtual bool SavePreferences(nlohmann::json preferences) = 0;
protected:
std::string path_;
};
} // namespace api
} // namespace nearby
#endif // PLATFORM_IMPLEMENTATION_PREFERENCE_REPOSITORY_H_
@@ -33,6 +33,7 @@ cc_library(
"log_message.h",
"mutex.h",
"output_file.h",
"preferences_repository.h",
"scheduled_executor.h",
"settable_future.h",
"submittable_executor.h",
@@ -148,6 +149,7 @@ cc_library(
"file_path.cc",
"http_loader.cc",
"platform.cc",
"preferences_repository.cc",
"scheduled_executor.cc",
"submittable_executor.cc",
"system_clock.cc",
@@ -239,6 +241,7 @@ cc_test(
"executor_test.cc",
"file_path_test.cc",
"http_loader_test.cc",
"preferences_repository_test.cc",
"scheduled_executor_test.cc",
"submittable_executor_test.cc",
"thread_pool_test.cc",
@@ -0,0 +1,145 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/windows/preferences_repository.h"
#include <exception>
#include <filesystem> // NOLINT(build/c++17)
#include <fstream>
#include <optional>
#include "nlohmann/json.hpp"
#include "nlohmann/json_fwd.hpp"
#include "internal/platform/logging.h"
namespace nearby {
namespace windows {
namespace {
using json = ::nlohmann::json;
constexpr char kPreferencesFileName[] = "preferences.json";
constexpr char kPreferencesBackupFileName[] = "preferences_bak.json";
} // namespace
json PreferencesRepository::LoadPreferences() {
absl::MutexLock lock(&mutex_);
std::optional<json> preferences = AttemptLoad();
if (preferences.has_value()) {
return preferences.value();
}
NEARBY_LOGS(ERROR) << "Could not load preferences file, trying backup.";
// In the future we should switch to using a transaction log or another
// stable method which doesn't pose a risk of losing settings
preferences = RestoreFromBackup();
if (preferences.has_value()) {
NEARBY_LOGS(ERROR) << "Successfully recovered from backup.";
return preferences.value();
}
NEARBY_LOGS(ERROR) << "Failed to load preferences file from back up.";
return json::object();
}
bool PreferencesRepository::SavePreferences(json preferences) {
absl::MutexLock lock(&mutex_);
try {
std::filesystem::path path = path_;
if (!std::filesystem::exists(path) &&
!std::filesystem::create_directories(path)) {
NEARBY_LOGS(ERROR) << "Failed to create preferences path.";
return false;
}
std::filesystem::path full_name = path / kPreferencesFileName;
std::filesystem::path full_name_backup = path / kPreferencesBackupFileName;
// Create a backup without moving the bytes on disk
if (std::filesystem::exists(full_name)) {
NEARBY_LOGS(INFO) << "Making backup of preferences file.";
std::filesystem::rename(full_name, full_name_backup);
}
std::ofstream preferences_file(full_name.c_str());
preferences_file << preferences;
preferences_file.close();
// Make sure the file wasn't saved in a corrupted state
if (!AttemptLoad().has_value()) {
NEARBY_LOGS(ERROR) << "Preferences saved to disk in corrupted state. "
"Restoring from backup.";
if (!RestoreFromBackup().has_value()) {
NEARBY_LOGS(ERROR) << "Failed to restore preferences file.";
return false;
}
}
} catch (const std::exception& e) {
NEARBY_LOGS(ERROR) << "Failed to save preferences file: " << e.what();
return false;
}
return true;
}
std::optional<json> PreferencesRepository::AttemptLoad() {
std::filesystem::path path = path_;
std::filesystem::path full_name = path / kPreferencesFileName;
if (!std::filesystem::exists(path) || !std::filesystem::exists(full_name)) {
return std::nullopt;
}
try {
std::ifstream preferences_file(full_name.c_str());
if (!preferences_file.good()) {
return std::nullopt;
}
json preferences = json::parse(preferences_file, nullptr, false);
preferences_file.close();
if (preferences.is_discarded()) {
NEARBY_LOGS(ERROR) << "Preferences file corrupted.";
return std::nullopt;
}
return preferences;
} catch (const std::exception& e) {
NEARBY_LOGS(ERROR) << "Exception while loading preferences: " << e.what();
return std::nullopt;
}
}
std::optional<json> PreferencesRepository::RestoreFromBackup() {
std::filesystem::path path = path_;
std::filesystem::path full_name = path / kPreferencesFileName;
std::filesystem::path full_name_backup = path / kPreferencesBackupFileName;
if (!std::filesystem::exists(full_name_backup)) {
NEARBY_LOGS(WARNING)
<< "Backup requested but no backup preferences file found.";
return std::nullopt;
}
std::filesystem::rename(full_name_backup, full_name);
NEARBY_LOGS(INFO) << "Attempting load from backup preferences.";
return AttemptLoad();
}
} // namespace windows
} // namespace nearby
@@ -0,0 +1,48 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPLEMENTATION_WINDOWS_PREFERENCES_REPOSITORY_H_
#define PLATFORM_IMPLEMENTATION_WINDOWS_PREFERENCES_REPOSITORY_H_
#include <optional>
#include "absl/base/thread_annotations.h"
#include "absl/strings/string_view.h"
#include "absl/synchronization/mutex.h"
#include "nlohmann/json_fwd.hpp"
#include "internal/platform/implementation/preferences_repository.h"
namespace nearby {
namespace windows {
class PreferencesRepository : public api::PreferencesRepository {
public:
explicit PreferencesRepository(absl::string_view path)
: api::PreferencesRepository(path) {}
nlohmann::json LoadPreferences() override ABSL_LOCKS_EXCLUDED(&mutex_);
bool SavePreferences(nlohmann::json preferences) override
ABSL_LOCKS_EXCLUDED(&mutex_);
std::optional<nlohmann::json> AttemptLoad();
std::optional<nlohmann::json> RestoreFromBackup();
private:
absl::Mutex mutex_;
};
} // namespace windows
} // namespace nearby
#endif // PLATFORM_IMPLEMENTATION_WINDOWS_PREFERENCES_REPOSITORY_H_
@@ -0,0 +1,142 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/windows/preferences_repository.h"
#include <filesystem> // NOLINT(build/c++17)
#include <fstream>
#include <optional>
#include "gtest/gtest.h"
#include "nlohmann/json.hpp"
#include "nlohmann/json_fwd.hpp"
#include "internal/platform/implementation/device_info.h"
#include "internal/platform/implementation/platform.h"
namespace nearby {
namespace windows {
namespace {
using json = ::nlohmann::json;
constexpr char kPreferencesFileName[] = "preferences.json";
constexpr char kPreferencesBackupFileName[] = "preferences_bak.json";
constexpr char kPreferencesPath[] = "Google/Nearby/Sharing";
TEST(PreferencesRepository, LoadWithBadPath) {
PreferencesRepository preferences_repository{"c:\\users\\a\\b\\c\\d\\e\\f"};
json result = preferences_repository.LoadPreferences();
EXPECT_TRUE(result.empty());
}
TEST(PreferencesRepository, SaveAndLoadPreferences) {
std::optional<std::filesystem::path> app_data_path =
api::ImplementationPlatform::CreateDeviceInfo()->GetLocalAppDataPath();
ASSERT_TRUE(app_data_path.has_value());
std::filesystem::path full_path = *app_data_path / kPreferencesPath;
std::filesystem::path full_name = full_path / kPreferencesFileName;
if (std::filesystem::exists(full_name)) {
std::filesystem::remove(full_name);
}
PreferencesRepository preferences_repository{full_path.string()};
json data;
data["key1"] = "value1";
data["key2"] = "value2";
EXPECT_TRUE(preferences_repository.SavePreferences(data));
json result = preferences_repository.LoadPreferences();
EXPECT_EQ(result.size(), 2);
EXPECT_EQ(result["key1"], "value1");
EXPECT_EQ(result["key2"], "value2");
std::filesystem::remove(full_name);
}
TEST(PreferencesRepository, LoadFromBackup) {
std::optional<std::filesystem::path> app_data_path =
api::ImplementationPlatform::CreateDeviceInfo()->GetLocalAppDataPath();
ASSERT_TRUE(app_data_path.has_value());
std::filesystem::path full_path = *app_data_path / kPreferencesPath;
std::filesystem::path full_name = full_path / kPreferencesFileName;
std::filesystem::path full_name_backup =
full_path / kPreferencesBackupFileName;
if (std::filesystem::exists(full_name)) {
std::filesystem::remove(full_name);
}
if (std::filesystem::exists(full_name_backup)) {
std::filesystem::remove(full_name_backup);
}
PreferencesRepository preferences_repository{full_path.string()};
json data;
data["key1"] = "value1";
data["key2"] = "value2";
std::ofstream backup_file(full_name_backup.c_str());
backup_file << data;
backup_file.close();
std::optional<json> result;
result = preferences_repository.AttemptLoad();
EXPECT_FALSE(result.has_value());
result = preferences_repository.RestoreFromBackup();
EXPECT_TRUE(result.has_value());
EXPECT_EQ(result.value()["key1"], "value1");
EXPECT_EQ(result.value()["key2"], "value2");
std::filesystem::remove(full_name);
EXPECT_FALSE(std::filesystem::exists(full_name_backup));
}
TEST(PreferencesRepository, RecoverFromCorruption) {
std::optional<std::filesystem::path> app_data_path =
api::ImplementationPlatform::CreateDeviceInfo()->GetLocalAppDataPath();
ASSERT_TRUE(app_data_path.has_value());
std::filesystem::path full_path = *app_data_path / kPreferencesPath;
std::filesystem::path full_name = full_path / kPreferencesFileName;
std::filesystem::path full_name_backup =
full_path / kPreferencesBackupFileName;
if (std::filesystem::exists(full_name)) {
std::filesystem::remove(full_name);
}
if (std::filesystem::exists(full_name_backup)) {
std::filesystem::remove(full_name_backup);
}
PreferencesRepository preferences_repository{full_path.string()};
json data;
data["key1"] = "value1";
data["key2"] = "value2";
std::ofstream preferences_file(full_name_backup.c_str());
preferences_file << data;
preferences_file.close();
std::ofstream backup_file(full_name.c_str());
backup_file << "[BAD JSON FILE]";
backup_file.close();
std::optional<json> result = preferences_repository.LoadPreferences();
EXPECT_EQ(result.value()["key1"], "value1");
EXPECT_EQ(result.value()["key2"], "value2");
std::filesystem::remove(full_name);
EXPECT_FALSE(std::filesystem::exists(full_name_backup));
}
} // namespace
} // namespace windows
} // namespace nearby