diff --git a/internal/platform/implementation/BUILD b/internal/platform/implementation/BUILD index 636a92e6..2ddde5dd 100644 --- a/internal/platform/implementation/BUILD +++ b/internal/platform/implementation/BUILD @@ -31,7 +31,7 @@ cc_library( "log_message.h", "mutex.h", "output_file.h", - "preferences_repository.h", + "preferences_manager.h", "scheduled_executor.h", "settable_future.h", "submittable_executor.h", @@ -58,6 +58,7 @@ cc_library( "@com_google_absl//absl/functional:any_invocable", "@com_google_absl//absl/strings", "@com_google_absl//absl/time", + "@com_google_absl//absl/types:span", "@nlohmann_json//:json", ], ) diff --git a/internal/platform/implementation/apple/BUILD b/internal/platform/implementation/apple/BUILD index 2c6616d7..7ae54b0b 100644 --- a/internal/platform/implementation/apple/BUILD +++ b/internal/platform/implementation/apple/BUILD @@ -29,7 +29,7 @@ objc_library( "log_message.mm", "multi_thread_executor.mm", "platform.mm", - "preferences_repository.mm", + "preferences_manager.mm", "scheduled_executor.mm", "timer.mm", "utils.mm", @@ -41,7 +41,7 @@ objc_library( "device_info.h", "log_message.h", "multi_thread_executor.h", - "preferences_repository.h", + "preferences_manager.h", "scheduled_executor.h", "single_thread_executor.h", "timer.h", @@ -71,6 +71,7 @@ objc_library( "@com_google_absl//absl/synchronization", "@com_google_absl//absl/time", "@com_google_absl//absl/types:optional", + "@com_google_absl//absl/types:span", "@nlohmann_json//:json", ] + select({ "@platforms//os:platform_ios": [ diff --git a/internal/platform/implementation/apple/platform.mm b/internal/platform/implementation/apple/platform.mm index f8de03e6..607c42f9 100644 --- a/internal/platform/implementation/apple/platform.mm +++ b/internal/platform/implementation/apple/platform.mm @@ -19,6 +19,7 @@ #include #include +#include "absl/strings/string_view.h" #include "internal/platform/implementation/apple/atomic_boolean.h" #include "internal/platform/implementation/apple/atomic_uint32.h" #include "internal/platform/implementation/apple/ble.h" @@ -28,7 +29,7 @@ #import "internal/platform/implementation/apple/log_message.h" #import "internal/platform/implementation/apple/multi_thread_executor.h" #include "internal/platform/implementation/apple/mutex.h" -#include "internal/platform/implementation/apple/preferences_repository.h" +#include "internal/platform/implementation/apple/preferences_manager.h" #import "internal/platform/implementation/apple/scheduled_executor.h" #import "internal/platform/implementation/apple/single_thread_executor.h" #include "internal/platform/implementation/apple/timer.h" @@ -248,9 +249,9 @@ std::unique_ptr ImplementationPlatform::CreateDeviceInf } // TODO(b/261503919): Add implementation. -std::unique_ptr -ImplementationPlatform::CreatePreferencesRepository(absl::string_view path) { - return std::make_unique(path); +std::unique_ptr ImplementationPlatform::CreatePreferencesManager( + absl::string_view path) { + return std::make_unique(path); } } // namespace api diff --git a/internal/platform/implementation/apple/preferences_manager.h b/internal/platform/implementation/apple/preferences_manager.h new file mode 100644 index 00000000..70a69b04 --- /dev/null +++ b/internal/platform/implementation/apple/preferences_manager.h @@ -0,0 +1,86 @@ +// 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_MANAGER_H_ +#define PLATFORM_IMPLEMENTATION_APPLE_PREFERENCES_MANAGER_H_ + +#include +#include + +#include "absl/base/thread_annotations.h" +#include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" +#include "absl/types/span.h" +#include "nlohmann/json_fwd.hpp" +#include "internal/platform/implementation/preferences_manager.h" + +namespace nearby::apple { + +class PreferencesManager : public api::PreferencesManager { + public: + explicit PreferencesManager(absl::string_view path); + + // Sets values + + bool Set(const std::string& key, const nlohmann::json& value) override; + + bool SetBoolean(absl::string_view key, bool value) override; + bool SetInteger(absl::string_view key, int value) override; + bool SetInt64(absl::string_view key, int64_t value) override; + bool SetString(absl::string_view key, absl::string_view value) override; + + bool SetBooleanArray(absl::string_view key, + absl::Span value) override; + bool SetIntegerArray(absl::string_view key, + absl::Span value) override; + bool SetInt64Array(absl::string_view key, + absl::Span value) override; + bool SetStringArray(absl::string_view key, + absl::Span value) override; + + bool SetTime(absl::string_view key, absl::Time value) override; + + // Gets values + nlohmann::json Get(absl::string_view key, + const nlohmann::json& default_value) const override; + + bool GetBoolean(absl::string_view key, bool default_value) const override; + int GetInteger(absl::string_view key, int default_value) const override; + int64_t GetInt64(absl::string_view key, int64_t default_value) const override; + std::string GetString(absl::string_view key, + const std::string& default_value) const override; + + std::vector GetBooleanArray( + absl::string_view key, + absl::Span default_value) const override; + std::vector GetIntegerArray( + absl::string_view key, + absl::Span default_value) const override; + std::vector GetInt64Array( + absl::string_view key, + absl::Span default_value) const override; + std::vector GetStringArray( + absl::string_view key, + absl::Span default_value) const override; + + absl::Time GetTime(absl::string_view key, + absl::Time default_value) const override; + + // Removes preferences + void Remove(absl::string_view key) override; +}; + +} // namespace nearby::apple + +#endif // PLATFORM_IMPLEMENTATION_APPLE_PREFERENCES_MANAGER_H_ diff --git a/internal/platform/implementation/apple/preferences_manager.mm b/internal/platform/implementation/apple/preferences_manager.mm new file mode 100644 index 00000000..1060334d --- /dev/null +++ b/internal/platform/implementation/apple/preferences_manager.mm @@ -0,0 +1,256 @@ +// 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_manager.h" + +#import + +#include +#include + +#include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" +#include "absl/types/span.h" +#include "nlohmann/json.hpp" + +namespace nearby::apple { + +PreferencesManager::PreferencesManager(absl::string_view file_path) + : api::PreferencesManager(file_path) {} + +bool PreferencesManager::Set(const std::string& key, const nlohmann::json& value) { + [[NSUserDefaults standardUserDefaults] setObject:@(value.dump().c_str()) forKey:@(key.c_str())]; + return true; +} + +bool PreferencesManager::SetBoolean(absl::string_view key, bool value) { + [[NSUserDefaults standardUserDefaults] setBool:value forKey:@(std::string(key).c_str())]; + return true; +} + +bool PreferencesManager::SetInteger(absl::string_view key, int value) { + [[NSUserDefaults standardUserDefaults] setInteger:value forKey:@(std::string(key).c_str())]; + return true; +} + +bool PreferencesManager::SetInt64(absl::string_view key, int64_t value) { + [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithLongLong:value] + forKey:@(std::string(key).c_str())]; + return true; +} + +bool PreferencesManager::SetString(absl::string_view key, absl::string_view value) { + [[NSUserDefaults standardUserDefaults] setObject:@(std::string(value).c_str()) + forKey:@(std::string(key).c_str())]; + return true; +} + +bool PreferencesManager::SetBooleanArray(absl::string_view key, absl::Span value) { + NSMutableArray* array = [NSMutableArray array]; + for (const bool& item : value) { + [array addObject:@(item)]; + } + [[NSUserDefaults standardUserDefaults] setObject:array forKey:@(std::string(key).c_str())]; + return true; +} + +bool PreferencesManager::SetIntegerArray(absl::string_view key, absl::Span value) { + NSMutableArray* array = [NSMutableArray array]; + for (const int& item : value) { + [array addObject:@(item)]; + } + [[NSUserDefaults standardUserDefaults] setObject:array forKey:@(std::string(key).c_str())]; + return true; +} + +bool PreferencesManager::SetInt64Array(absl::string_view key, absl::Span value) { + NSMutableArray* array = [NSMutableArray array]; + for (const int64_t& item : value) { + [array addObject:@(item)]; + } + [[NSUserDefaults standardUserDefaults] setObject:array forKey:@(std::string(key).c_str())]; + return true; +} + +bool PreferencesManager::SetStringArray(absl::string_view key, + absl::Span value) { + NSMutableArray* array = [NSMutableArray array]; + for (const std::string& item : value) { + [array addObject:@(item.c_str())]; + } + [[NSUserDefaults standardUserDefaults] setObject:array forKey:@(std::string(key).c_str())]; + return true; +} + +bool PreferencesManager::SetTime(absl::string_view key, absl::Time value) { + int64_t nanos = absl::ToUnixNanos(value); + NSDate* date = [[NSDate alloc] initWithTimeIntervalSince1970:nanos / NSEC_PER_SEC]; + [[NSUserDefaults standardUserDefaults] setObject:date forKey:@(std::string(key).c_str())]; + return true; +} + +// Get JSON value. +nlohmann::json PreferencesManager::Get(absl::string_view key, + const nlohmann::json& default_value) const { + NSString* keyString = @(std::string(key).c_str()); + id value = [[NSUserDefaults standardUserDefaults] objectForKey:keyString]; + if (value == nil) { + return default_value; + } + // We store JSON as a serialized string, so retrieve it as a string and deserialize. + NSCAssert([value isKindOfClass:[NSString class]], @"value for key \"%@\" must be JSON", + keyString); + return nlohmann::json::parse([(NSString*)value UTF8String]); +} + +bool PreferencesManager::GetBoolean(absl::string_view key, bool default_value) const { + NSString* keyString = @(std::string(key).c_str()); + id value = [[NSUserDefaults standardUserDefaults] objectForKey:keyString]; + if (value == nil) { + return default_value; + } + NSCAssert([value isKindOfClass:[NSNumber class]], @"value for key \"%@\" must be bool", + keyString); + return [(NSNumber*)value boolValue]; +} + +int PreferencesManager::GetInteger(absl::string_view key, int default_value) const { + NSString* keyString = @(std::string(key).c_str()); + id value = [[NSUserDefaults standardUserDefaults] objectForKey:keyString]; + if (value == nil) { + return default_value; + } + NSCAssert([value isKindOfClass:[NSNumber class]], @"value for key \"%@\" must be int", keyString); + return [value integerValue]; +} + +int64_t PreferencesManager::GetInt64(absl::string_view key, int64_t default_value) const { + NSString* keyString = @(std::string(key).c_str()); + id value = [[NSUserDefaults standardUserDefaults] objectForKey:keyString]; + if (value == nil) { + return default_value; + } + NSCAssert([value isKindOfClass:[NSNumber class]], @"value for key \"%@\" must be int64_t", + keyString); + return [value longLongValue]; +} + +std::string PreferencesManager::GetString(absl::string_view key, + const std::string& default_value) const { + NSString* keyString = @(std::string(key).c_str()); + id value = [[NSUserDefaults standardUserDefaults] objectForKey:keyString]; + if (value == nil) { + return default_value; + } + NSCAssert([value isKindOfClass:[NSString class]], @"value for key \"%@\" must be string", + keyString); + return [(NSString*)value UTF8String]; +} + +std::vector PreferencesManager::GetBooleanArray(absl::string_view key, + absl::Span default_value) const { + NSString* keyString = @(std::string(key).c_str()); + id array = [[NSUserDefaults standardUserDefaults] objectForKey:keyString]; + if (array == nil) { + return std::vector(default_value.begin(), default_value.end()); + } + NSCAssert([array isKindOfClass:[NSArray class]], @"value for key \"%@\" must be array", + keyString); + std::vector vector; + vector.reserve([array count]); + for (id value in array) { + NSCAssert([value isKindOfClass:[NSNumber class]], + @"all values of array for key \"%@\" must be bool", keyString); + vector.push_back([(NSNumber*)value boolValue]); + } + + return vector; +} + +std::vector PreferencesManager::GetIntegerArray(absl::string_view key, + absl::Span default_value) const { + NSString* keyString = @(std::string(key).c_str()); + id array = [[NSUserDefaults standardUserDefaults] objectForKey:keyString]; + if (array == nil) { + return std::vector(default_value.begin(), default_value.end()); + } + NSCAssert([array isKindOfClass:[NSArray class]], @"value for key \"%@\" must be array", + keyString); + std::vector vector; + vector.reserve([array count]); + for (id value in array) { + NSCAssert([value isKindOfClass:[NSNumber class]], + @"all values of array for key \"%@\" must be bool", keyString); + vector.push_back([(NSNumber*)value integerValue]); + } + + return vector; +} + +std::vector PreferencesManager::GetInt64Array( + absl::string_view key, absl::Span default_value) const { + NSString* keyString = @(std::string(key).c_str()); + id array = [[NSUserDefaults standardUserDefaults] objectForKey:keyString]; + if (array == nil) { + return std::vector(default_value.begin(), default_value.end()); + } + NSCAssert([array isKindOfClass:[NSArray class]], @"value for key \"%@\" must be array", + keyString); + std::vector vector; + vector.reserve([array count]); + for (id value in array) { + NSCAssert([value isKindOfClass:[NSNumber class]], + @"all values of array for key \"%@\" must be bool", keyString); + vector.push_back([(NSNumber*)value integerValue]); + } + + return vector; +} + +std::vector PreferencesManager::GetStringArray( + absl::string_view key, absl::Span default_value) const { + NSString* keyString = @(std::string(key).c_str()); + id array = [[NSUserDefaults standardUserDefaults] objectForKey:keyString]; + if (array == nil) { + return std::vector(default_value.begin(), default_value.end()); + } + NSCAssert([array isKindOfClass:[NSArray class]], @"value for key \"%@\" must be array", + keyString); + std::vector vector; + vector.reserve([array count]); + for (id value in array) { + NSCAssert([value isKindOfClass:[NSString class]], + @"all values of array for key \"%@\" must be bool", keyString); + vector.push_back([(NSString*)value UTF8String]); + } + + return vector; +} + +absl::Time PreferencesManager::GetTime(absl::string_view key, absl::Time default_value) const { + NSString* keyString = @(std::string(key).c_str()); + id value = [[NSUserDefaults standardUserDefaults] objectForKey:keyString]; + if (value == nil) { + return default_value; + } + NSCAssert([value isKindOfClass:[NSDate class]], @"value for key \"%@\" must be time", keyString); + return absl::FromUnixNanos([(NSDate*)value timeIntervalSince1970] * NSEC_PER_SEC); +} + +// Removes preferences +void PreferencesManager::Remove(absl::string_view key) { + [[NSUserDefaults standardUserDefaults] removeObjectForKey:@(std::string(key).c_str())]; +} + +} // namespace nearby::apple \ No newline at end of file diff --git a/internal/platform/implementation/apple/preferences_repository.h b/internal/platform/implementation/apple/preferences_repository.h deleted file mode 100644 index af1430df..00000000 --- a/internal/platform/implementation/apple/preferences_repository.h +++ /dev/null @@ -1,40 +0,0 @@ -// 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 deleted file mode 100644 index 176afd24..00000000 --- a/internal/platform/implementation/apple/preferences_repository.mm +++ /dev/null @@ -1,32 +0,0 @@ -// 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 bd6e10af..01cc507d 100644 --- a/internal/platform/implementation/g3/BUILD +++ b/internal/platform/implementation/g3/BUILD @@ -18,7 +18,7 @@ cc_library( testonly = True, srcs = [ "log_message.cc", - "preferences_repository.cc", + "preferences_manager.cc", "scheduled_executor.cc", "system_clock.cc", ], @@ -31,13 +31,14 @@ cc_library( "multi_thread_executor.h", "mutex.h", "pipe.h", - "preferences_repository.h", + "preferences_manager.h", "scheduled_executor.h", "single_thread_executor.h", "timer.h", ], visibility = ["//location/nearby/cpp:__subpackages__"], deps = [ + ":preferences_repository", "//internal/platform:base", "//internal/platform:logging", "//internal/platform:test_util", @@ -52,6 +53,7 @@ cc_library( "@com_google_absl//absl/strings", "@com_google_absl//absl/synchronization", "@com_google_absl//absl/time", + "@com_google_absl//absl/types:span", "@com_google_glog//:glog", "@com_google_nisaba//nisaba/port:thread_pool", "@nlohmann_json//:json", @@ -156,11 +158,21 @@ cc_library( "//internal/platform/implementation:types", "//internal/platform/implementation/shared:count_down_latch", "//internal/platform/implementation/shared:file", - "@com_google_absl//absl/base:core_headers", - "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/memory", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", "@com_google_absl//absl/time", ], ) + +cc_library( + name = "preferences_repository", + srcs = ["preferences_repository.cc"], + hdrs = ["preferences_repository.h"], + deps = [ + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/synchronization", + "@nlohmann_json//:json", + ], +) diff --git a/internal/platform/implementation/g3/platform.cc b/internal/platform/implementation/g3/platform.cc index f8fc4fbb..6c753394 100644 --- a/internal/platform/implementation/g3/platform.cc +++ b/internal/platform/implementation/g3/platform.cc @@ -32,7 +32,7 @@ #include "internal/platform/implementation/condition_variable.h" #include "internal/platform/implementation/log_message.h" #include "internal/platform/implementation/mutex.h" -#include "internal/platform/implementation/preferences_repository.h" +#include "internal/platform/implementation/preferences_manager.h" #include "internal/platform/implementation/scheduled_executor.h" #include "internal/platform/implementation/server_sync.h" #include "internal/platform/implementation/shared/count_down_latch.h" @@ -53,7 +53,7 @@ #include "internal/platform/implementation/g3/log_message.h" #include "internal/platform/implementation/g3/multi_thread_executor.h" #include "internal/platform/implementation/g3/mutex.h" -#include "internal/platform/implementation/g3/preferences_repository.h" +#include "internal/platform/implementation/g3/preferences_manager.h" #include "internal/platform/implementation/g3/scheduled_executor.h" #include "internal/platform/implementation/g3/single_thread_executor.h" #include "internal/platform/implementation/g3/timer.h" @@ -237,9 +237,9 @@ ImplementationPlatform::CreateDeviceInfo() { return std::make_unique(); } -std::unique_ptr -ImplementationPlatform::CreatePreferencesRepository(absl::string_view path) { - return std::make_unique(path); +std::unique_ptr +ImplementationPlatform::CreatePreferencesManager(absl::string_view path) { + return std::make_unique(path); } } // namespace api diff --git a/internal/platform/implementation/g3/preferences_manager.cc b/internal/platform/implementation/g3/preferences_manager.cc new file mode 100644 index 00000000..5b53b853 --- /dev/null +++ b/internal/platform/implementation/g3/preferences_manager.cc @@ -0,0 +1,254 @@ +// Copyright 2021-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_manager.h" + +#include // NOLINT(build/c++17) +#include +#include +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "nlohmann/json.hpp" +#include "nlohmann/json_fwd.hpp" +#include "internal/platform/implementation/g3/preferences_repository.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace g3 { +namespace { +using json = ::nlohmann::json; +} // namespace + +PreferencesManager::PreferencesManager(absl::string_view file_path) + : api::PreferencesManager(file_path) { + std::optional path = + nearby::api::ImplementationPlatform::CreateDeviceInfo() + ->GetLocalAppDataPath(); + if (!path.has_value()) { + path = std::filesystem::temp_directory_path(); + } + + std::filesystem::path full_path = *path / std::string(file_path); + preferences_repository_ = + std::make_unique(full_path.string()); + value_ = preferences_repository_->LoadPreferences(); +} + +bool PreferencesManager::Set(const std::string& key, const json& value) { + absl::MutexLock lock(&mutex_); + return SetValue(key, value); +} + +bool PreferencesManager::SetBoolean(absl::string_view key, bool value) { + absl::MutexLock lock(&mutex_); + return SetValue(key, value); +} + +bool PreferencesManager::SetInteger(absl::string_view key, int value) { + absl::MutexLock lock(&mutex_); + return SetValue(key, value); +} + +bool PreferencesManager::SetInt64(absl::string_view key, int64_t value) { + absl::MutexLock lock(&mutex_); + return SetValue(key, value); +} + +bool PreferencesManager::SetString(absl::string_view key, + absl::string_view value) { + absl::MutexLock lock(&mutex_); + return SetValue(key, absl::StrCat(value)); +} + +bool PreferencesManager::SetBooleanArray(absl::string_view key, + absl::Span value) { + absl::MutexLock lock(&mutex_); + return SetArrayValue(key, value); +} + +bool PreferencesManager::SetIntegerArray(absl::string_view key, + absl::Span value) { + absl::MutexLock lock(&mutex_); + return SetArrayValue(key, value); +} + +bool PreferencesManager::SetInt64Array(absl::string_view key, + absl::Span value) { + absl::MutexLock lock(&mutex_); + return SetArrayValue(key, value); +} + +bool PreferencesManager::SetStringArray(absl::string_view key, + absl::Span value) { + absl::MutexLock lock(&mutex_); + return SetArrayValue(key, value); +} + +bool PreferencesManager::SetTime(absl::string_view key, absl::Time value) { + // Save time as nanos + absl::MutexLock lock(&mutex_); + int64_t tt = absl::ToUnixNanos(value); + if (value_[absl::StrCat(key)] == tt) { + return false; + } + + value_[absl::StrCat(key)] = tt; + return Commit(); +} + +// Get JSON value. +json PreferencesManager::Get(absl::string_view key, + const json& default_value) const { + absl::MutexLock lock(&mutex_); + return GetValue(key, default_value); +} + +bool PreferencesManager::GetBoolean(absl::string_view key, + bool default_value) const { + absl::MutexLock lock(&mutex_); + return GetValue(key, default_value); +} + +int PreferencesManager::GetInteger(absl::string_view key, + int default_value) const { + absl::MutexLock lock(&mutex_); + return GetValue(key, default_value); +} + +int64_t PreferencesManager::GetInt64(absl::string_view key, + int64_t default_value) const { + absl::MutexLock lock(&mutex_); + return GetValue(key, default_value); +} + +std::string PreferencesManager::GetString( + absl::string_view key, const std::string& default_value) const { + absl::MutexLock lock(&mutex_); + return GetValue(key, default_value); +} + +std::vector PreferencesManager::GetBooleanArray( + absl::string_view key, absl::Span default_value) const { + absl::MutexLock lock(&mutex_); + return GetArrayValue(key, default_value); +} + +std::vector PreferencesManager::GetIntegerArray( + absl::string_view key, absl::Span default_value) const { + absl::MutexLock lock(&mutex_); + return GetArrayValue(key, default_value); +} + +std::vector PreferencesManager::GetInt64Array( + absl::string_view key, absl::Span default_value) const { + absl::MutexLock lock(&mutex_); + return GetArrayValue(key, default_value); +} + +std::vector PreferencesManager::GetStringArray( + absl::string_view key, absl::Span default_value) const { + absl::MutexLock lock(&mutex_); + return GetArrayValue(key, default_value); +} + +absl::Time PreferencesManager::GetTime(absl::string_view key, + absl::Time default_value) const { + absl::MutexLock lock(&mutex_); + auto result = value_.find(absl::StrCat(key)); + if (result == value_.end()) { + return default_value; + } + + return absl::FromUnixNanos(result->get()); +} + +// Removes preferences +void PreferencesManager::Remove(absl::string_view key) { + absl::MutexLock lock(&mutex_); + value_.erase(absl::StrCat(key)); +} + +// Private methods + +// Writes data to storage. +bool PreferencesManager::Commit() { + if (!preferences_repository_->SavePreferences(value_)) { + NEARBY_LOGS(ERROR) << "Failed to save preference." << std::endl; + return false; + } + return true; +} + +bool PreferencesManager::SetValue(absl::string_view key, const json& value) { + if (value_[absl::StrCat(key)] == value) { + return false; + } + + value_[absl::StrCat(key)] = value; + return Commit(); +} + +template +T PreferencesManager::GetValue(absl::string_view key, + const T& default_value) const { + auto it = value_.find(absl::StrCat(key)); + if (it == value_.end()) { + return default_value; + } + return it->get(); +} + +template +bool PreferencesManager::SetArrayValue(absl::string_view key, + absl::Span value) { + json array_value = json::array(); + for (const T& item_value : value) { + array_value.push_back(item_value); + } + + if (value_[absl::StrCat(key)] == array_value) { + return false; + } + + value_[absl::StrCat(key)] = array_value; + return Commit(); +} + +template +std::vector PreferencesManager::GetArrayValue( + absl::string_view key, absl::Span default_value) const { + std::vector result; + + auto array_value = value_.find(absl::StrCat(key)); + if (array_value == value_.end() || !array_value->is_array()) { + for (const T& value : default_value) { + result.push_back(value); + } + return result; + } + + auto it = array_value->begin(); + while (it != array_value->end()) { + result.push_back(it->get()); + ++it; + } + + return result; +} + +} // namespace g3 +} // namespace nearby diff --git a/internal/platform/implementation/g3/preferences_manager.h b/internal/platform/implementation/g3/preferences_manager.h new file mode 100644 index 00000000..575fa305 --- /dev/null +++ b/internal/platform/implementation/g3/preferences_manager.h @@ -0,0 +1,141 @@ +// Copyright 2021-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_G3_PREFERENCES_MANAGER_H_ +#define PLATFORM_IMPLEMENTATION_G3_PREFERENCES_MANAGER_H_ + +#include + +#include +#include +#include + +#include "absl/base/thread_annotations.h" +#include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/time.h" +#include "absl/types/span.h" +#include "nlohmann/json.hpp" +#include "nlohmann/json_fwd.hpp" +#include "internal/platform/implementation/g3/preferences_repository.h" +#include "internal/platform/implementation/preferences_manager.h" + +namespace nearby { +namespace g3 { + +// Sets and gets preference settings from the application. +// Preferences are persistent storage for application settings, it is key/value +// based settings. Application components can observe the interested preference +// change by the observer. +class PreferencesManager : public api::PreferencesManager { + public: + explicit PreferencesManager(absl::string_view path); + + // Sets values + + bool Set(const std::string& key, const nlohmann::json& value) override + ABSL_LOCKS_EXCLUDED(mutex_); + + bool SetBoolean(absl::string_view key, bool value) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool SetInteger(absl::string_view key, int value) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool SetInt64(absl::string_view key, int64_t value) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool SetString(absl::string_view key, absl::string_view value) override + ABSL_LOCKS_EXCLUDED(mutex_); + + bool SetBooleanArray(absl::string_view key, + absl::Span value) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool SetIntegerArray(absl::string_view key, + absl::Span value) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool SetInt64Array(absl::string_view key, + absl::Span value) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool SetStringArray(absl::string_view key, + absl::Span value) override + ABSL_LOCKS_EXCLUDED(mutex_); + + bool SetTime(absl::string_view key, absl::Time value) override + ABSL_LOCKS_EXCLUDED(mutex_); + + // Gets values + nlohmann::json Get(absl::string_view key, + const nlohmann::json& default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + + bool GetBoolean(absl::string_view key, bool default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + int GetInteger(absl::string_view key, int default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + int64_t GetInt64(absl::string_view key, int64_t default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + std::string GetString(absl::string_view key, + const std::string& default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + + std::vector GetBooleanArray(absl::string_view key, + absl::Span default_value) + const override ABSL_LOCKS_EXCLUDED(mutex_); + std::vector GetIntegerArray( + absl::string_view key, absl::Span default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + std::vector GetInt64Array(absl::string_view key, + absl::Span default_value) + const override ABSL_LOCKS_EXCLUDED(mutex_); + std::vector GetStringArray( + absl::string_view key, + absl::Span default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + + absl::Time GetTime(absl::string_view key, + absl::Time default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + + // Removes preferences + void Remove(absl::string_view key) override ABSL_LOCKS_EXCLUDED(mutex_); + + private: + // Writes data to storage. + bool Commit() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + bool SetValue(absl::string_view key, const nlohmann::json& value) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + template + T GetValue(absl::string_view key, const T& default_value) const + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + template + bool SetArrayValue(absl::string_view key, absl::Span value) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + template + std::vector GetArrayValue(absl::string_view key, + absl::Span default_value) const + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + nlohmann::json value_ ABSL_GUARDED_BY(mutex_); + std::unique_ptr preferences_repository_ + ABSL_GUARDED_BY(mutex_); + + mutable absl::Mutex mutex_; +}; + +} // namespace g3 +} // namespace nearby + +#endif // PLATFORM_IMPLEMENTATION_G3_PREFERENCES_MANAGER_H_ diff --git a/internal/platform/implementation/g3/preferences_repository.h b/internal/platform/implementation/g3/preferences_repository.h index 960c89ea..bff1d61b 100644 --- a/internal/platform/implementation/g3/preferences_repository.h +++ b/internal/platform/implementation/g3/preferences_repository.h @@ -15,30 +15,30 @@ #ifndef PLATFORM_IMPLEMENTATION_G3_PREFERENCES_REPOSITORY_H_ #define PLATFORM_IMPLEMENTATION_G3_PREFERENCES_REPOSITORY_H_ +#include + #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 { +class PreferencesRepository { public: - explicit PreferencesRepository(absl::string_view path) - : api::PreferencesRepository(path) {} + explicit PreferencesRepository(absl::string_view path) : path_(path) {} - nlohmann::json LoadPreferences() override ABSL_LOCKS_EXCLUDED(&mutex_); - bool SavePreferences(nlohmann::json preferences) override - ABSL_LOCKS_EXCLUDED(&mutex_); + nlohmann::json LoadPreferences() ABSL_LOCKS_EXCLUDED(&mutex_); + bool SavePreferences(nlohmann::json preferences) 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_; + const std::string path_; }; } // namespace g3 diff --git a/internal/platform/implementation/platform.h b/internal/platform/implementation/platform.h index 4c494d38..cfc36832 100644 --- a/internal/platform/implementation/platform.h +++ b/internal/platform/implementation/platform.h @@ -47,7 +47,7 @@ #include "internal/platform/implementation/webrtc.h" #endif #ifndef NEARBY_CHROMIUM -#include "internal/platform/implementation/preferences_repository.h" +#include "internal/platform/implementation/preferences_manager.h" #endif #include "internal/platform/implementation/wifi.h" #include "internal/platform/implementation/wifi_direct.h" @@ -93,7 +93,7 @@ class ImplementationPlatform { static std::unique_ptr CreateAtomicBoolean(bool initial_value); // Supports enums and integers up to 32-bit. - // Does not use locking, if platform supports 32-bit atimics natively. + // Does not use locking, if platform supports 32-bit atomics natively. // Does not use dynamic memory allocations in operations. static std::unique_ptr CreateAtomicUint32(std::uint32_t value); @@ -154,8 +154,8 @@ class ImplementationPlatform { static absl::StatusOr SendRequest(const WebRequest& request); #ifndef NEARBY_CHROMIUM - static std::unique_ptr - CreatePreferencesRepository(absl::string_view path); + static std::unique_ptr + CreatePreferencesManager(absl::string_view path); #endif }; diff --git a/internal/platform/implementation/preferences_manager.h b/internal/platform/implementation/preferences_manager.h new file mode 100644 index 00000000..aa70f402 --- /dev/null +++ b/internal/platform/implementation/preferences_manager.h @@ -0,0 +1,93 @@ +// 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_MANAGER_H_ +#define PLATFORM_IMPLEMENTATION_PREFERENCE_MANAGER_H_ + +#include + +#include +#include + +#include "absl/strings/string_view.h" +#include "absl/time/time.h" +#include "absl/types/span.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 PreferencesManager { + public: + explicit PreferencesManager(absl::string_view path) {} + virtual ~PreferencesManager() = default; + + // Sets values + + virtual bool Set(const std::string& key, const nlohmann::json& value) = 0; + + virtual bool SetBoolean(absl::string_view key, bool value) = 0; + virtual bool SetInteger(absl::string_view key, int value) = 0; + virtual bool SetInt64(absl::string_view key, int64_t value) = 0; + virtual bool SetString(absl::string_view key, absl::string_view value) = 0; + + virtual bool SetBooleanArray(absl::string_view key, + absl::Span value) = 0; + virtual bool SetIntegerArray(absl::string_view key, + absl::Span value) = 0; + virtual bool SetInt64Array(absl::string_view key, + absl::Span value) = 0; + virtual bool SetStringArray(absl::string_view key, + absl::Span value) = 0; + + virtual bool SetTime(absl::string_view key, absl::Time value) = 0; + + // Gets values + virtual nlohmann::json Get(absl::string_view key, + const nlohmann::json& default_value) const = 0; + + virtual bool GetBoolean(absl::string_view key, bool default_value) const = 0; + virtual int GetInteger(absl::string_view key, int default_value) const = 0; + virtual int64_t GetInt64(absl::string_view key, + int64_t default_value) const = 0; + virtual std::string GetString(absl::string_view key, + const std::string& default_value) const = 0; + + virtual std::vector GetBooleanArray( + absl::string_view key, absl::Span default_value) const = 0; + virtual std::vector GetIntegerArray( + absl::string_view key, absl::Span default_value) const = 0; + virtual std::vector GetInt64Array( + absl::string_view key, absl::Span default_value) const = 0; + virtual std::vector GetStringArray( + absl::string_view key, + absl::Span default_value) const = 0; + + virtual absl::Time GetTime(absl::string_view key, + absl::Time default_value) const = 0; + + // Removes preferences + virtual void Remove(absl::string_view key) = 0; +}; + +} // namespace api +} // namespace nearby + +#endif // PLATFORM_IMPLEMENTATION_PREFERENCE_MANAGER_H_ diff --git a/internal/platform/implementation/preferences_repository.h b/internal/platform/implementation/preferences_repository.h deleted file mode 100644 index d3840ded..00000000 --- a/internal/platform/implementation/preferences_repository.h +++ /dev/null @@ -1,47 +0,0 @@ -// 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 38bdfc5b..77c644eb 100644 --- a/internal/platform/implementation/windows/BUILD +++ b/internal/platform/implementation/windows/BUILD @@ -33,7 +33,7 @@ cc_library( "log_message.h", "mutex.h", "output_file.h", - "preferences_repository.h", + "preferences_manager.h", "scheduled_executor.h", "settable_future.h", "submittable_executor.h", @@ -44,16 +44,25 @@ cc_library( defines = ["_SILENCE_CLANG_COROUTINE_MESSAGE"], visibility = ["//location/nearby/cpp/sharing/implementation/internal/impl/windows:__pkg__"], deps = [ + ":comm", "//base", "//base:stringprintf", "//internal/base:bluetooth_address", "//internal/platform:base", "//internal/platform:logging", + "//internal/platform:types", + "//internal/platform:uuid", "//internal/platform/implementation:types", - "//internal/platform/implementation/windows:comm", "//internal/platform/implementation/windows/generated:types", "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/functional:any_invocable", + "@com_google_absl//absl/memory", + "@com_google_absl//absl/strings", "@com_google_absl//absl/synchronization", + "@com_google_absl//absl/time", + "@com_google_absl//absl/types:span", + "@nlohmann_json//:json", ], ) @@ -149,7 +158,9 @@ cc_library( "file_path.cc", "http_loader.cc", "platform.cc", + "preferences_manager.cc", "preferences_repository.cc", + "preferences_repository.h", "scheduled_executor.cc", "submittable_executor.cc", "system_clock.cc", @@ -194,7 +205,10 @@ cc_library( "//internal/platform/implementation/shared:count_down_latch", "//internal/platform/implementation/shared:file", "//internal/platform/implementation/windows/generated:types", + "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/functional:any_invocable", + "@com_google_absl//absl/log:check", "@com_google_absl//absl/memory", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", @@ -202,6 +216,8 @@ cc_library( "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/synchronization", "@com_google_absl//absl/time", + "@com_google_absl//absl/types:optional", + "@com_google_absl//absl/types:span", "@nlohmann_json//:json", ], ) @@ -241,6 +257,7 @@ cc_test( "executor_test.cc", "file_path_test.cc", "http_loader_test.cc", + "preferences_manager_test.cc", "preferences_repository_test.cc", "scheduled_executor_test.cc", "submittable_executor_test.cc", @@ -255,14 +272,21 @@ cc_test( ":crypto", ":test_utils", ":types", + ":windows", "//internal/platform:base", "//internal/platform:logging", + "//internal/platform/implementation:comm", "//internal/platform/implementation:platform", - "//internal/platform/implementation/windows", + "//internal/platform/implementation:types", + "//internal/platform/implementation/shared:count_down_latch", "//internal/platform/implementation/windows/generated:types", "@com_github_protobuf_matchers//protobuf-matchers", "@com_google_absl//absl/status", "@com_google_absl//absl/strings", + "@com_google_absl//absl/synchronization", + "@com_google_absl//absl/time", + "@com_google_absl//absl/types:span", "@com_google_googletest//:gtest_main", + "@nlohmann_json//:json", ], ) diff --git a/internal/platform/implementation/windows/platform.cc b/internal/platform/implementation/windows/platform.cc index c214237e..4ab4bcb0 100644 --- a/internal/platform/implementation/windows/platform.cc +++ b/internal/platform/implementation/windows/platform.cc @@ -53,7 +53,7 @@ #include "internal/platform/implementation/windows/listenable_future.h" #include "internal/platform/implementation/windows/log_message.h" #include "internal/platform/implementation/windows/mutex.h" -#include "internal/platform/implementation/windows/preferences_repository.h" +#include "internal/platform/implementation/windows/preferences_manager.h" #include "internal/platform/implementation/windows/scheduled_executor.h" #include "internal/platform/implementation/windows/server_sync.h" #include "internal/platform/implementation/windows/settable_future.h" @@ -320,9 +320,9 @@ std::unique_ptr ImplementationPlatform::CreateDeviceInfo() { return std::make_unique(); } -std::unique_ptr -ImplementationPlatform::CreatePreferencesRepository(absl::string_view path) { - return std::make_unique(path); +std::unique_ptr +ImplementationPlatform::CreatePreferencesManager(absl::string_view path) { + return std::make_unique(path); } } // namespace api diff --git a/internal/platform/implementation/windows/preferences_manager.cc b/internal/platform/implementation/windows/preferences_manager.cc new file mode 100644 index 00000000..2cb8021e --- /dev/null +++ b/internal/platform/implementation/windows/preferences_manager.cc @@ -0,0 +1,254 @@ +// Copyright 2021-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_manager.h" + +#include // NOLINT(build/c++17) +#include +#include +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "nlohmann/json.hpp" +#include "nlohmann/json_fwd.hpp" +#include "internal/platform/implementation/windows/preferences_repository.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace windows { +namespace { +using json = ::nlohmann::json; +} // namespace + +PreferencesManager::PreferencesManager(absl::string_view file_path) + : api::PreferencesManager(file_path) { + std::optional path = + nearby::api::ImplementationPlatform::CreateDeviceInfo() + ->GetLocalAppDataPath(); + if (!path.has_value()) { + path = std::filesystem::temp_directory_path(); + } + + std::filesystem::path full_path = *path / std::string(file_path); + preferences_repository_ = + std::make_unique(full_path.string()); + value_ = preferences_repository_->LoadPreferences(); +} + +bool PreferencesManager::Set(const std::string& key, const json& value) { + absl::MutexLock lock(&mutex_); + return SetValue(key, value); +} + +bool PreferencesManager::SetBoolean(absl::string_view key, bool value) { + absl::MutexLock lock(&mutex_); + return SetValue(key, value); +} + +bool PreferencesManager::SetInteger(absl::string_view key, int value) { + absl::MutexLock lock(&mutex_); + return SetValue(key, value); +} + +bool PreferencesManager::SetInt64(absl::string_view key, int64_t value) { + absl::MutexLock lock(&mutex_); + return SetValue(key, value); +} + +bool PreferencesManager::SetString(absl::string_view key, + absl::string_view value) { + absl::MutexLock lock(&mutex_); + return SetValue(key, absl::StrCat(value)); +} + +bool PreferencesManager::SetBooleanArray(absl::string_view key, + absl::Span value) { + absl::MutexLock lock(&mutex_); + return SetArrayValue(key, value); +} + +bool PreferencesManager::SetIntegerArray(absl::string_view key, + absl::Span value) { + absl::MutexLock lock(&mutex_); + return SetArrayValue(key, value); +} + +bool PreferencesManager::SetInt64Array(absl::string_view key, + absl::Span value) { + absl::MutexLock lock(&mutex_); + return SetArrayValue(key, value); +} + +bool PreferencesManager::SetStringArray(absl::string_view key, + absl::Span value) { + absl::MutexLock lock(&mutex_); + return SetArrayValue(key, value); +} + +bool PreferencesManager::SetTime(absl::string_view key, absl::Time value) { + // Save time as nanos + absl::MutexLock lock(&mutex_); + int64_t tt = absl::ToUnixNanos(value); + if (value_[absl::StrCat(key)] == tt) { + return false; + } + + value_[absl::StrCat(key)] = tt; + return Commit(); +} + +// Get JSON value. +json PreferencesManager::Get(absl::string_view key, + const json& default_value) const { + absl::MutexLock lock(&mutex_); + return GetValue(key, default_value); +} + +bool PreferencesManager::GetBoolean(absl::string_view key, + bool default_value) const { + absl::MutexLock lock(&mutex_); + return GetValue(key, default_value); +} + +int PreferencesManager::GetInteger(absl::string_view key, + int default_value) const { + absl::MutexLock lock(&mutex_); + return GetValue(key, default_value); +} + +int64_t PreferencesManager::GetInt64(absl::string_view key, + int64_t default_value) const { + absl::MutexLock lock(&mutex_); + return GetValue(key, default_value); +} + +std::string PreferencesManager::GetString( + absl::string_view key, const std::string& default_value) const { + absl::MutexLock lock(&mutex_); + return GetValue(key, default_value); +} + +std::vector PreferencesManager::GetBooleanArray( + absl::string_view key, absl::Span default_value) const { + absl::MutexLock lock(&mutex_); + return GetArrayValue(key, default_value); +} + +std::vector PreferencesManager::GetIntegerArray( + absl::string_view key, absl::Span default_value) const { + absl::MutexLock lock(&mutex_); + return GetArrayValue(key, default_value); +} + +std::vector PreferencesManager::GetInt64Array( + absl::string_view key, absl::Span default_value) const { + absl::MutexLock lock(&mutex_); + return GetArrayValue(key, default_value); +} + +std::vector PreferencesManager::GetStringArray( + absl::string_view key, absl::Span default_value) const { + absl::MutexLock lock(&mutex_); + return GetArrayValue(key, default_value); +} + +absl::Time PreferencesManager::GetTime(absl::string_view key, + absl::Time default_value) const { + absl::MutexLock lock(&mutex_); + auto result = value_.find(absl::StrCat(key)); + if (result == value_.end()) { + return default_value; + } + + return absl::FromUnixNanos(result->get()); +} + +// Removes preferences +void PreferencesManager::Remove(absl::string_view key) { + absl::MutexLock lock(&mutex_); + value_.erase(absl::StrCat(key)); +} + +// Private methods + +// Writes data to storage. +bool PreferencesManager::Commit() { + if (!preferences_repository_->SavePreferences(value_)) { + NEARBY_LOGS(ERROR) << "Failed to save preference." << std::endl; + return false; + } + return true; +} + +bool PreferencesManager::SetValue(absl::string_view key, const json& value) { + if (value_[absl::StrCat(key)] == value) { + return false; + } + + value_[absl::StrCat(key)] = value; + return Commit(); +} + +template +T PreferencesManager::GetValue(absl::string_view key, + const T& default_value) const { + auto it = value_.find(absl::StrCat(key)); + if (it == value_.end()) { + return default_value; + } + return it->get(); +} + +template +bool PreferencesManager::SetArrayValue(absl::string_view key, + absl::Span value) { + json array_value = json::array(); + for (const T& item_value : value) { + array_value.push_back(item_value); + } + + if (value_[absl::StrCat(key)] == array_value) { + return false; + } + + value_[absl::StrCat(key)] = array_value; + return Commit(); +} + +template +std::vector PreferencesManager::GetArrayValue( + absl::string_view key, absl::Span default_value) const { + std::vector result; + + auto array_value = value_.find(absl::StrCat(key)); + if (array_value == value_.end() || !array_value->is_array()) { + for (const T& value : default_value) { + result.push_back(value); + } + return result; + } + + auto it = array_value->begin(); + while (it != array_value->end()) { + result.push_back(it->get()); + ++it; + } + + return result; +} + +} // namespace windows +} // namespace nearby diff --git a/internal/platform/implementation/windows/preferences_manager.h b/internal/platform/implementation/windows/preferences_manager.h new file mode 100644 index 00000000..607f46df --- /dev/null +++ b/internal/platform/implementation/windows/preferences_manager.h @@ -0,0 +1,141 @@ +// Copyright 2021-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_WINDOWS_PREFERENCES_MANAGER_H_ +#define PLATFORM_IMPLEMENTATION_WINDOWS_PREFERENCES_MANAGER_H_ + +#include + +#include +#include +#include + +#include "absl/base/thread_annotations.h" +#include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/time.h" +#include "absl/types/span.h" +#include "nlohmann/json.hpp" +#include "nlohmann/json_fwd.hpp" +#include "internal/platform/implementation/preferences_manager.h" +#include "internal/platform/implementation/windows/preferences_repository.h" + +namespace nearby { +namespace windows { + +// Sets and gets preference settings from the application. +// Preferences are persistent storage for application settings, it is key/value +// based settings. Application components can observe the interested preference +// change by the observer. +class PreferencesManager : public api::PreferencesManager { + public: + explicit PreferencesManager(absl::string_view path); + + // Sets values + + bool Set(const std::string& key, const nlohmann::json& value) override + ABSL_LOCKS_EXCLUDED(mutex_); + + bool SetBoolean(absl::string_view key, bool value) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool SetInteger(absl::string_view key, int value) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool SetInt64(absl::string_view key, int64_t value) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool SetString(absl::string_view key, absl::string_view value) override + ABSL_LOCKS_EXCLUDED(mutex_); + + bool SetBooleanArray(absl::string_view key, + absl::Span value) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool SetIntegerArray(absl::string_view key, + absl::Span value) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool SetInt64Array(absl::string_view key, + absl::Span value) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool SetStringArray(absl::string_view key, + absl::Span value) override + ABSL_LOCKS_EXCLUDED(mutex_); + + bool SetTime(absl::string_view key, absl::Time value) override + ABSL_LOCKS_EXCLUDED(mutex_); + + // Gets values + nlohmann::json Get(absl::string_view key, + const nlohmann::json& default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + + bool GetBoolean(absl::string_view key, bool default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + int GetInteger(absl::string_view key, int default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + int64_t GetInt64(absl::string_view key, int64_t default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + std::string GetString(absl::string_view key, + const std::string& default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + + std::vector GetBooleanArray(absl::string_view key, + absl::Span default_value) + const override ABSL_LOCKS_EXCLUDED(mutex_); + std::vector GetIntegerArray( + absl::string_view key, absl::Span default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + std::vector GetInt64Array(absl::string_view key, + absl::Span default_value) + const override ABSL_LOCKS_EXCLUDED(mutex_); + std::vector GetStringArray( + absl::string_view key, + absl::Span default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + + absl::Time GetTime(absl::string_view key, + absl::Time default_value) const override + ABSL_LOCKS_EXCLUDED(mutex_); + + // Removes preferences + void Remove(absl::string_view key) override ABSL_LOCKS_EXCLUDED(mutex_); + + private: + // Writes data to storage. + bool Commit() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + bool SetValue(absl::string_view key, const nlohmann::json& value) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + template + T GetValue(absl::string_view key, const T& default_value) const + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + template + bool SetArrayValue(absl::string_view key, absl::Span value) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + template + std::vector GetArrayValue(absl::string_view key, + absl::Span default_value) const + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + nlohmann::json value_ ABSL_GUARDED_BY(mutex_); + std::unique_ptr preferences_repository_ + ABSL_GUARDED_BY(mutex_); + + mutable absl::Mutex mutex_; +}; + +} // namespace windows +} // namespace nearby + +#endif // PLATFORM_IMPLEMENTATION_WINDOWS_PREFERENCES_MANAGER_H_ diff --git a/internal/platform/implementation/windows/preferences_manager_test.cc b/internal/platform/implementation/windows/preferences_manager_test.cc new file mode 100644 index 00000000..04a7a799 --- /dev/null +++ b/internal/platform/implementation/windows/preferences_manager_test.cc @@ -0,0 +1,186 @@ +// Copyright 2021-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_manager.h" + +#include + +#include // NOLINT(build/c++17) +#include +#include +#include +#include + +#include "gtest/gtest.h" +#include "absl/strings/string_view.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "absl/types/span.h" +#include "nlohmann/json.hpp" +#include "nlohmann/json_fwd.hpp" + +namespace nearby { +namespace windows { +namespace { +using json = ::nlohmann::json; +constexpr absl::Duration kTimeOut = absl::Milliseconds(200); +constexpr char kPreferencesFilePath[] = "Google/Nearby/Sharing"; +} // namespace + +TEST(PreferencesManager, CorruptedConfigFile) { + std::filesystem::path settingsPath = + std::filesystem::temp_directory_path() / "settings.json"; + std::ofstream output_stream{settingsPath}; + output_stream << "{\"data\":8, \"names\": [\"In valid\"}" << std::endl; + output_stream.close(); + + // Should use an empty setting for a corrupted configuration file. + EXPECT_EQ(PreferencesManager(kPreferencesFilePath).GetInteger("data", 100), + 100); +} + +TEST(PreferencesManager, ValidConfigFile) { + std::filesystem::path settingsPath = + std::filesystem::temp_directory_path() / "settings.json"; + std::ofstream output_stream{settingsPath}; + output_stream << "{\"data\":8, \"name\": \"Valid\"}" << std::endl; + output_stream.close(); + + // Should use an empty setting for a corrupted configuration file. + EXPECT_EQ(PreferencesManager(kPreferencesFilePath).GetInteger("data", 100), + 8); +} + +TEST(PreferencesManager, SetAndGetBoolean) { + std::string bool_key = "bool_key"; + PreferencesManager pm(kPreferencesFilePath); + EXPECT_TRUE(pm.GetBoolean(bool_key, true)); + pm.SetBoolean(bool_key, true); + EXPECT_TRUE(pm.GetBoolean(bool_key, false)); +} + +TEST(PreferencesManager, SetAndGetInt) { + std::string int_key = "int_key"; + PreferencesManager pm(kPreferencesFilePath); + EXPECT_EQ(pm.GetInteger(int_key, 1234), 1234); + pm.SetInteger(int_key, 6789); + EXPECT_EQ(pm.GetInteger(int_key, 0), 6789); +} + +TEST(PreferencesManager, SetAndGetInt64) { + std::string int64_key = "int64_key"; + PreferencesManager pm(kPreferencesFilePath); + EXPECT_EQ(pm.GetInt64(int64_key, 1234), 1234); + pm.SetInt64(int64_key, 56789); + EXPECT_EQ(pm.GetInt64(int64_key, 0), 56789); +} + +TEST(PreferencesManager, SetAndGetString) { + std::string string_key = "string_key"; + PreferencesManager pm(kPreferencesFilePath); + EXPECT_EQ(pm.GetString(string_key, "abcd"), "abcd"); + pm.SetString(string_key, "this is a test string"); + EXPECT_EQ(pm.GetString(string_key, ""), "this is a test string"); +} + +TEST(PreferencesManager, SetAndGetTime) { + std::string time_key = "time_key"; + PreferencesManager pm(kPreferencesFilePath); + absl::Time time = absl::Now(); + EXPECT_EQ(pm.GetTime(time_key, time), time); + pm.SetTime(time_key, time); + absl::Time ret = pm.GetTime(time_key, absl::Now()); + EXPECT_EQ(absl::ToUnixNanos(ret), absl::ToUnixNanos(time)); +} + +TEST(PreferencesManager, MultipleSetAndGetString) { + std::string string1_key = "string1_key"; + PreferencesManager pm(kPreferencesFilePath); + pm.SetString(string1_key, "this is first string"); + pm.SetString(string1_key, "this is second string"); + EXPECT_EQ(pm.GetString(string1_key, ""), "this is second string"); +} + +TEST(PreferencesManager, SetAndGetValue) { + std::string value_key = "value_key"; + PreferencesManager pm(kPreferencesFilePath); + json value = {{"key1", "value1"}, {"key2", "value2"}}; + EXPECT_TRUE(pm.Get(value_key, json()).empty()); + pm.Set(value_key, value); + auto result = pm.Get(value_key, json()); + ASSERT_FALSE(result.empty()); + auto val = result["key2"]; + EXPECT_EQ(val.get(), "value2"); +} + +TEST(PreferencesManager, SetAndGetBooleanArray) { + std::string bool_array_key = "bool_array_key"; + auto pm = PreferencesManager(kPreferencesFilePath); + auto default_result = + pm.GetBooleanArray(bool_array_key, absl::Span({true})); + EXPECT_EQ(default_result[0], true); + pm.SetBooleanArray(bool_array_key, + absl::Span({true, false, false, true, true})); + auto result = + pm.GetBooleanArray(bool_array_key, absl::Span({true})); + EXPECT_EQ(result[2], false); + EXPECT_EQ(result[3], true); +} + +TEST(PreferencesManager, SetAndGetIntArray) { + std::string int_array_key = "int_array_key"; + auto pm = PreferencesManager(kPreferencesFilePath); + auto result = pm.GetIntegerArray(int_array_key, std::vector{5, 6}); + EXPECT_EQ(result[1], 6); + pm.SetIntegerArray(int_array_key, std::vector{1, 7, 4, 10, 12}); + result = pm.GetIntegerArray(int_array_key, std::vector{11, 17, 14, 110}); + EXPECT_EQ(result[3], 10); +} + +TEST(PreferencesManager, SetAndGetInt64Array) { + std::string int64_array_key = "int64_array_key"; + auto pm = PreferencesManager(kPreferencesFilePath); + auto result = pm.GetInt64Array(int64_array_key, std::vector{99}); + EXPECT_EQ(result[0], 99); + pm.SetInt64Array(int64_array_key, std::vector{16, 7, 64, 100, 12}); + result = pm.GetInt64Array(int64_array_key, std::vector{1, 5, 6, 12}); + EXPECT_EQ(result[3], 100); + EXPECT_EQ(result[4], 12); +} + +TEST(PreferencesManager, SetAndGetStringArray) { + std::string string_array_key = "string_array_key"; + auto pm = PreferencesManager(kPreferencesFilePath); + auto result = pm.GetStringArray(string_array_key, + std::vector{"value", "morning"}); + EXPECT_EQ(result[1], "morning"); + pm.SetStringArray( + string_array_key, + std::vector{"one", "two", "three", "four", "five"}); + result = pm.GetStringArray(string_array_key, + std::vector{"good", "morning"}); + EXPECT_EQ(result[3], "four"); +} + +TEST(PreferencesManager, RemoveKey) { + std::string string_key = "string_key"; + auto pm = PreferencesManager(kPreferencesFilePath); + pm.SetString(string_key, "remove key"); + pm.Remove(string_key); + auto result = pm.GetString(string_key, "default key"); + EXPECT_EQ(result, "default key"); +} + +} // namespace windows +} // namespace nearby diff --git a/internal/platform/implementation/windows/preferences_repository.h b/internal/platform/implementation/windows/preferences_repository.h index 1ed532a9..dfa915cb 100644 --- a/internal/platform/implementation/windows/preferences_repository.h +++ b/internal/platform/implementation/windows/preferences_repository.h @@ -16,30 +16,30 @@ #define PLATFORM_IMPLEMENTATION_WINDOWS_PREFERENCES_REPOSITORY_H_ #include +#include #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 windows { -class PreferencesRepository : public api::PreferencesRepository { +class PreferencesRepository { public: - explicit PreferencesRepository(absl::string_view path) - : api::PreferencesRepository(path) {} + explicit PreferencesRepository(absl::string_view path) : path_(path) {} - nlohmann::json LoadPreferences() override ABSL_LOCKS_EXCLUDED(&mutex_); - bool SavePreferences(nlohmann::json preferences) override - ABSL_LOCKS_EXCLUDED(&mutex_); + nlohmann::json LoadPreferences() ABSL_LOCKS_EXCLUDED(&mutex_); + bool SavePreferences(nlohmann::json preferences) ABSL_LOCKS_EXCLUDED(&mutex_); std::optional AttemptLoad(); std::optional RestoreFromBackup(); private: absl::Mutex mutex_; + const std::string path_; }; } // namespace windows