From 00bba362199fbe4d462775e10d2e825a9ee772ce Mon Sep 17 00:00:00 2001 From: Chun Zhang Date: Thu, 13 Apr 2023 21:17:58 -0700 Subject: [PATCH] Migrate PreferencesRepository from location/nearby to third_party/nearby PiperOrigin-RevId: 524185819 --- internal/platform/implementation/BUILD | 2 + internal/platform/implementation/apple/BUILD | 3 + .../apple/preferences_repository.h | 40 +++++ .../apple/preferences_repository.mm | 32 ++++ internal/platform/implementation/g3/BUILD | 5 +- .../g3/preferences_repository.cc | 70 +++++++++ .../g3/preferences_repository.h | 47 ++++++ .../g3/preferences_repository_test.cc | 68 ++++++++ .../implementation/preferences_repository.h | 47 ++++++ .../platform/implementation/windows/BUILD | 3 + .../windows/preferences_repository.cc | 145 ++++++++++++++++++ .../windows/preferences_repository.h | 48 ++++++ .../windows/preferences_repository_test.cc | 142 +++++++++++++++++ 13 files changed, 651 insertions(+), 1 deletion(-) create mode 100644 internal/platform/implementation/apple/preferences_repository.h create mode 100644 internal/platform/implementation/apple/preferences_repository.mm create mode 100644 internal/platform/implementation/g3/preferences_repository.cc create mode 100644 internal/platform/implementation/g3/preferences_repository.h create mode 100644 internal/platform/implementation/g3/preferences_repository_test.cc create mode 100644 internal/platform/implementation/preferences_repository.h create mode 100644 internal/platform/implementation/windows/preferences_repository.cc create mode 100644 internal/platform/implementation/windows/preferences_repository.h create mode 100644 internal/platform/implementation/windows/preferences_repository_test.cc diff --git a/internal/platform/implementation/BUILD b/internal/platform/implementation/BUILD index 98f7b327..31b2a52f 100644 --- a/internal/platform/implementation/BUILD +++ b/internal/platform/implementation/BUILD @@ -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", ], ) diff --git a/internal/platform/implementation/apple/BUILD b/internal/platform/implementation/apple/BUILD index a1347496..a5d76eba 100644 --- a/internal/platform/implementation/apple/BUILD +++ b/internal/platform/implementation/apple/BUILD @@ -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", diff --git a/internal/platform/implementation/apple/preferences_repository.h b/internal/platform/implementation/apple/preferences_repository.h new file mode 100644 index 00000000..af1430df --- /dev/null +++ b/internal/platform/implementation/apple/preferences_repository.h @@ -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_ diff --git a/internal/platform/implementation/apple/preferences_repository.mm b/internal/platform/implementation/apple/preferences_repository.mm new file mode 100644 index 00000000..176afd24 --- /dev/null +++ b/internal/platform/implementation/apple/preferences_repository.mm @@ -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 \ No newline at end of file diff --git a/internal/platform/implementation/g3/BUILD b/internal/platform/implementation/g3/BUILD index 1735101a..81df8931 100644 --- a/internal/platform/implementation/g3/BUILD +++ b/internal/platform/implementation/g3/BUILD @@ -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, ) diff --git a/internal/platform/implementation/g3/preferences_repository.cc b/internal/platform/implementation/g3/preferences_repository.cc new file mode 100644 index 00000000..0700743c --- /dev/null +++ b/internal/platform/implementation/g3/preferences_repository.cc @@ -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 // NOLINT(build/c++17) +#include + +#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 diff --git a/internal/platform/implementation/g3/preferences_repository.h b/internal/platform/implementation/g3/preferences_repository.h new file mode 100644 index 00000000..960c89ea --- /dev/null +++ b/internal/platform/implementation/g3/preferences_repository.h @@ -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_ diff --git a/internal/platform/implementation/g3/preferences_repository_test.cc b/internal/platform/implementation/g3/preferences_repository_test.cc new file mode 100644 index 00000000..b407f64f --- /dev/null +++ b/internal/platform/implementation/g3/preferences_repository_test.cc @@ -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 // NOLINT(build/c++17) +#include + +#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(), string_value); + EXPECT_EQ(result[int_key].get(), 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(), 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(), string_new_value); + EXPECT_EQ(result[int_key].get(), 456); +} + +} // namespace g3 +} // namespace platform +} // namespace nearby diff --git a/internal/platform/implementation/preferences_repository.h b/internal/platform/implementation/preferences_repository.h new file mode 100644 index 00000000..d3840ded --- /dev/null +++ b/internal/platform/implementation/preferences_repository.h @@ -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 + +#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_ diff --git a/internal/platform/implementation/windows/BUILD b/internal/platform/implementation/windows/BUILD index e76c9872..38bdfc5b 100644 --- a/internal/platform/implementation/windows/BUILD +++ b/internal/platform/implementation/windows/BUILD @@ -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", diff --git a/internal/platform/implementation/windows/preferences_repository.cc b/internal/platform/implementation/windows/preferences_repository.cc new file mode 100644 index 00000000..5c323c20 --- /dev/null +++ b/internal/platform/implementation/windows/preferences_repository.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 +#include // NOLINT(build/c++17) +#include +#include + +#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 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 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 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 diff --git a/internal/platform/implementation/windows/preferences_repository.h b/internal/platform/implementation/windows/preferences_repository.h new file mode 100644 index 00000000..1ed532a9 --- /dev/null +++ b/internal/platform/implementation/windows/preferences_repository.h @@ -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 + +#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 AttemptLoad(); + std::optional RestoreFromBackup(); + + private: + absl::Mutex mutex_; +}; + +} // namespace windows +} // namespace nearby + +#endif // PLATFORM_IMPLEMENTATION_WINDOWS_PREFERENCES_REPOSITORY_H_ diff --git a/internal/platform/implementation/windows/preferences_repository_test.cc b/internal/platform/implementation/windows/preferences_repository_test.cc new file mode 100644 index 00000000..78794a00 --- /dev/null +++ b/internal/platform/implementation/windows/preferences_repository_test.cc @@ -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 // NOLINT(build/c++17) +#include +#include + +#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 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 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 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 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 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