* Copyright (c) 2021 Huawei Device Co., Ltd.
* 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
*
* http://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 "preferences_impl.h"
#include <cinttypes>
#include <climits>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <thread>
#include <chrono>
#include <sstream>
#include "executor_pool.h"
#include "log_print.h"
#include "preferences_xml_utils.h"
#include "preferences_file_operation.h"
#include "preferences_anonymous.h"
#include "preferences_dfx_adapter.h"
#include "preferences_task_processor.h"
#include "preferences_utils.h"
namespace OHOS {
namespace NativePreferences {
using namespace std::chrono;
constexpr int32_t WAIT_TIME = 2;
constexpr int32_t TASK_EXEC_TIME = 100;
constexpr int32_t LOAD_XML_LOG_TIME = 1000;
constexpr int32_t MAX_LOG_LENGTH = 3000;
PreferencesImpl::PreferencesImpl(const Options &options) : PreferencesBase(options)
{
loaded_.store(false);
isNeverUnlock_ = false;
loadResult_= false;
queue_ = std::make_shared<SafeBlockQueue<uint64_t>>(1);
dataObsMgrClient_ = DataObsMgrClient::GetInstance();
isActive_.store(true);
isCleared_.store(false);
}
PreferencesImpl::~PreferencesImpl()
{
}
int PreferencesImpl::Init()
{
if (!StartLoadFromDisk()) {
return E_ERROR;
}
return E_OK;
}
bool PreferencesImpl::StartLoadFromDisk()
{
std::lock_guard<std::mutex> lock(mutex_);
loaded_.store(false);
isNeverUnlock_ = false;
loadResult_ = false;
auto task = [pref = shared_from_this()] {
PreferencesImpl::LoadFromDisk(pref);
};
return PreferencesTaskProcessor::GetInstance()->Execute(task);
}
void PreferencesImpl::LoadFromDisk(std::shared_ptr<PreferencesImpl> pref)
{
if (pref->loaded_.load()) {
return;
}
std::lock_guard<std::mutex> lock(pref->mutex_);
if (!pref->loaded_.load()) {
std::string::size_type pos = pref->options_.filePath.find_last_of('/');
std::string filePath = pref->options_.filePath.substr(0, pos);
if (Access(filePath) != 0) {
pref->isNeverUnlock_ = true;
}
std::unordered_map<std::string, PreferencesValue> values;
bool loadResult = pref->ReadSettingXml(values);
if (!loadResult) {
LOG_WARN("The settingXml %{public}s load failed.", ExtractFileName(pref->options_.filePath).c_str());
} else {
std::unique_lock<decltype(pref->cacheMutex_)> lock(pref->cacheMutex_);
pref->valuesCache_ = std::move(values);
pref->loadResult_ = true;
pref->isNeverUnlock_ = false;
}
pref->loaded_.store(true);
pref->cond_.notify_all();
}
}
bool PreferencesImpl::ReloadFromDisk()
{
if (loadResult_) {
return false;
}
std::unique_lock<decltype(cacheMutex_)> lock(cacheMutex_);
std::unordered_map<std::string, PreferencesValue> values = valuesCache_;
bool loadResult = ReadSettingXml(values);
LOG_WARN("The settingXml %{public}s reload result is %{public}d",
ExtractFileName(options_.filePath).c_str(), loadResult);
if (loadResult) {
valuesCache_ = std::move(values);
isNeverUnlock_ = false;
loadResult_ = true;
return true;
}
return false;
}
bool PreferencesImpl::PreLoad()
{
if (loadResult_) {
return true;
}
std::lock_guard<std::mutex> lock(mutex_);
if (!loaded_.load()) {
return true;
}
if (isNeverUnlock_ || !loadResult_) {
if (Access(options_.filePath) == 0) {
return ReloadFromDisk();
}
}
return true;
}
void PreferencesImpl::AwaitLoadFile()
{
if (loaded_.load()) {
PreLoad();
return;
}
std::unique_lock<std::mutex> lock(mutex_);
if (!loaded_.load()) {
cond_.wait_for(lock, std::chrono::seconds(WAIT_TIME), [this] { return loaded_.load(); });
}
if (!loaded_.load()) {
LOG_ERROR("The settingXml %{public}s load timeout.", ExtractFileName(options_.filePath).c_str());
}
}
PreferencesValue PreferencesImpl::Get(const std::string &key, const PreferencesValue &defValue)
{
if (PreferencesUtils::CheckKey(key) != E_OK) {
return defValue;
}
AwaitLoadFile();
IsClose(std::string(__FUNCTION__));
std::shared_lock<decltype(cacheMutex_)> lock(cacheMutex_);
if (!isCleared_.load()) {
auto iter = valuesCache_.find(key);
if (iter != valuesCache_.end()) {
return iter->second;
}
}
return defValue;
}
std::map<std::string, PreferencesValue> PreferencesImpl::GetAll()
{
AwaitLoadFile();
IsClose(std::string(__FUNCTION__));
std::map<std::string, PreferencesValue> allDatas;
std::shared_lock<decltype(cacheMutex_)> lock(cacheMutex_);
if (!isCleared_.load()) {
for (auto &it : valuesCache_) {
allDatas.insert_or_assign(it.first, it.second);
}
}
return allDatas;
}
static int64_t GetFileSize(const std::string &path)
{
int64_t fileSize = -1;
struct stat buffer;
if (stat(path.c_str(), &buffer) == 0) {
fileSize = static_cast<int64_t>(buffer.st_size);
}
return fileSize;
}
bool PreferencesImpl::ReadSettingXml(std::unordered_map<std::string, PreferencesValue> &conMap)
{
auto begin = static_cast<uint64_t>(duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count());
if (!PreferencesXmlUtils::ReadSettingXml(options_.filePath, options_.bundleName, conMap)) {
return false;
}
auto end = static_cast<uint64_t>(duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count());
if (end - begin > LOAD_XML_LOG_TIME) {
LOG_ERROR("The settingXml %{public}s load time exceed 1s, file size:%{public}" PRId64 ".",
ExtractFileName(options_.filePath).c_str(), GetFileSize(options_.filePath));
}
return true;
}
int PreferencesImpl::Close()
{
isActive_.store(false);
return E_OK;
}
bool PreferencesImpl::IsClose(const std::string &name)
{
if (isActive_.load()) {
return false;
}
LOG_WARN("%{public}s closed:%{public}s.", ExtractFileName(options_.filePath).c_str(), name.c_str());
std::string operationMsg = " use after close.";
ReportFaultParam reportParam = { "inactive object", options_.bundleName, NORMAL_DB,
ExtractFileName(options_.filePath), E_OBJECT_NOT_ACTIVE, name + operationMsg };
PreferencesDfxManager::ReportAbnormalOperation(reportParam, ReportedFaultBitMap::OBJECT_IS_NOT_ACTIVE);
return true;
}
bool PreferencesImpl::HasKey(const std::string &key)
{
if (PreferencesUtils::CheckKey(key) != E_OK) {
return false;
}
AwaitLoadFile();
IsClose(std::string(__FUNCTION__));
std::shared_lock<decltype(cacheMutex_)> lock(cacheMutex_);
if (isCleared_.load()) {
return false;
}
return valuesCache_.find(key) != valuesCache_.end();
}
int PreferencesImpl::Put(const std::string &key, const PreferencesValue &value)
{
int errCode = PreferencesUtils::CheckKey(key);
if (errCode != E_OK) {
return errCode;
}
errCode = PreferencesUtils::CheckValue(value);
if (errCode != E_OK) {
return errCode;
}
AwaitLoadFile();
IsClose(std::string(__FUNCTION__));
ReportObjectUsage(shared_from_this(), value);
std::unique_lock<decltype(cacheMutex_)> lock(cacheMutex_);
if (isCleared_.load()) {
for (auto &it : valuesCache_) {
modifiedKeys_.emplace(it.first);
}
valuesCache_.clear();
valuesCache_.insert_or_assign(key, value);
modifiedKeys_.emplace(key);
isCleared_.store(false);
} else {
auto iter = valuesCache_.find(key);
if (iter != valuesCache_.end()) {
PreferencesValue &val = iter->second;
if (val == value) {
return E_OK;
}
}
valuesCache_.insert_or_assign(key, value);
modifiedKeys_.emplace(key);
}
return E_OK;
}
int PreferencesImpl::Delete(const std::string &key)
{
int errCode = PreferencesUtils::CheckKey(key);
if (errCode != E_OK) {
return errCode;
}
AwaitLoadFile();
IsClose(std::string(__FUNCTION__));
std::unique_lock<decltype(cacheMutex_)> lock(cacheMutex_);
if (isCleared_.load()) {
return E_OK;
}
if (valuesCache_.find(key) != valuesCache_.end()) {
valuesCache_.erase(key);
modifiedKeys_.emplace(key);
}
return E_OK;
}
int PreferencesImpl::Clear()
{
AwaitLoadFile();
IsClose(std::string(__FUNCTION__));
isCleared_.store(true);
return E_OK;
}
int PreferencesImpl::WriteToDiskFile(std::shared_ptr<PreferencesImpl> pref)
{
auto keysModified = std::make_shared<std::unordered_set<std::string>>();
auto writeToDiskMap = std::make_shared<std::unordered_map<std::string, PreferencesValue>>();
{
std::unique_lock<decltype(pref->cacheMutex_)> lock(pref->cacheMutex_);
if (pref->isCleared_.load()) {
for (auto &it : pref->valuesCache_) {
pref->modifiedKeys_.emplace(it.first);
}
pref->valuesCache_.clear();
pref->isCleared_.store(false);
}
if (!pref->modifiedKeys_.empty()) {
*keysModified = std::move(pref->modifiedKeys_);
*writeToDiskMap = pref->valuesCache_;
} else {
return E_OK;
}
}
if (!PreferencesXmlUtils::WriteSettingXml(pref->options_.filePath, pref->options_.bundleName, *writeToDiskMap)) {
return E_ERROR;
}
if (pref->isNeverUnlock_) {
pref->isNeverUnlock_ = false;
}
if (!pref->loadResult_) {
pref->loadResult_ = true;
}
NotifyPreferencesObserver(pref, keysModified, writeToDiskMap);
return E_OK;
}
void PreferencesImpl::Flush()
{
IsClose(std::string(__FUNCTION__));
auto success = queue_->PushNoWait(1);
if (!success) {
return;
}
std::weak_ptr<SafeBlockQueue<uint64_t>> queue = queue_;
ExecutorPool::Task task = [queue, self = weak_from_this()] {
auto realQueue = queue.lock();
auto realThis = self.lock();
if (realQueue == nullptr || realThis == nullptr) {
return;
}
uint64_t value = 0;
if (!realThis->PreLoad()) {
return;
}
std::lock_guard<std::mutex> lock(realThis->mutex_);
auto has = realQueue->PopNotWait(value);
if (has && value == 1) {
PreferencesImpl::WriteToDiskFile(realThis);
}
};
executorPool_.Schedule(std::chrono::milliseconds(TASK_EXEC_TIME), std::move(task));
}
int PreferencesImpl::FlushSync()
{
IsClose(std::string(__FUNCTION__));
auto success = queue_->PushNoWait(1);
if (success) {
if (queue_ == nullptr) {
return E_ERROR;
}
if (!PreLoad()) {
return E_OK;
}
uint64_t value = 0;
std::lock_guard<std::mutex> lock(mutex_);
auto has = queue_->PopNotWait(value);
if (has && value == 1) {
return PreferencesImpl::WriteToDiskFile(shared_from_this());
}
}
return E_OK;
}
std::pair<int, PreferencesValue> PreferencesImpl::GetValue(const std::string &key, const PreferencesValue &defValue)
{
int errCode = PreferencesUtils::CheckKey(key);
if (errCode != E_OK) {
return std::make_pair(errCode, defValue);
}
AwaitLoadFile();
IsClose(std::string(__FUNCTION__));
std::shared_lock<decltype(cacheMutex_)> lock(cacheMutex_);
if (!isCleared_.load()) {
auto iter = valuesCache_.find(key);
if (iter != valuesCache_.end()) {
return std::make_pair(E_OK, iter->second);
}
}
return std::make_pair(E_NO_DATA, defValue);
}
std::pair<int, std::map<std::string, PreferencesValue>> PreferencesImpl::GetAllData()
{
AwaitLoadFile();
IsClose(std::string(__FUNCTION__));
std::map<std::string, PreferencesValue> allDatas;
std::shared_lock<decltype(cacheMutex_)> lock(cacheMutex_);
if (!isCleared_.load()) {
for (auto &it : valuesCache_) {
allDatas.insert_or_assign(it.first, it.second);
}
}
return std::make_pair(E_OK, allDatas);
}
std::unordered_map<std::string, PreferencesValue> PreferencesImpl::GetAllDatas()
{
AwaitLoadFile();
IsClose(std::string(__FUNCTION__));
std::shared_lock<decltype(cacheMutex_)> lock(cacheMutex_);
if (!isCleared_.load()) {
return valuesCache_;
}
return {};
}
void PreferencesImpl::NotifyPreferencesObserver(std::shared_ptr<PreferencesImpl> pref,
std::shared_ptr<std::unordered_set<std::string>> keysModified,
std::shared_ptr<std::unordered_map<std::string, PreferencesValue>> writeToDisk)
{
if (keysModified->empty()) {
return;
}
std::shared_lock<std::shared_mutex> autoLock(pref->observerMutex_);
for (const auto &[weakPrt, keys] : pref->dataObserversMap_) {
std::map<std::string, PreferencesValue> records;
for (auto &key : *keysModified) {
auto itKey = keys.find(key);
if (itKey == keys.end()) {
continue;
}
PreferencesValue value;
auto dataIt = writeToDisk->find(key);
if (dataIt != writeToDisk->end()) {
value = dataIt->second;
}
records.insert({key, value});
}
if (records.empty()) {
continue;
}
if (std::shared_ptr<PreferencesObserver> sharedPtr = weakPrt.lock()) {
sharedPtr->OnChange(records);
}
}
for (auto &it : pref->localObservers_) {
for (auto &key : *keysModified) {
std::weak_ptr<PreferencesObserver> weakPreferencesObserver = it;
if (std::shared_ptr<PreferencesObserver> sharedPreferencesObserver = weakPreferencesObserver.lock()) {
sharedPreferencesObserver->OnChange(key);
}
}
}
ExecuteNotifyChange(pref, keysModified);
}
void PreferencesImpl::ExecuteNotifyChange(std::shared_ptr<PreferencesImpl> pref,
std::shared_ptr<std::unordered_set<std::string>> keysModified)
{
ExecutorPool::Task task = [pref, keysModified] {
if (pref == nullptr || pref->dataObsMgrClient_ == nullptr) {
return;
}
std::stringstream ss;
for (auto &key : *keysModified) {
ss << Anonymous::ToBeAnonymous(key) << ". ";
if (ss.tellp() > MAX_LOG_LENGTH) {
LOG_INFO("key length too long, notify %{public}s", ss.str().c_str());
ss.str("");
ss.clear();
}
pref->dataObsMgrClient_->NotifyChange(pref->MakeUri(key));
}
if (!ss.str().empty()) {
LOG_INFO("notify %{public}s", ss.str().c_str());
}
};
executorPool_.Execute(std::move(task));
}
}
}