/*
* Copyright (c) 2024 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 "lru_cache.h"
#include <algorithm>
#include <mutex>
#include "netstack_log.h"
#import <Foundation/Foundation.h>
static constexpr const char *LRU_INDEX = "LRUIndex";
static constexpr const int DECIMAL_BASE = 10;
static constexpr const int MAX_SIZE = 1024 * 1024;
static constexpr const size_t INVALID_SIZE = SIZE_MAX;
namespace OHOS::NetStack::Http {
static size_t GetMapValueSize(const std::unordered_map<std::string, std::string> &m)
{
size_t size = 0;
for (const auto &p : m) {
if (p.second.size() > MAX_SIZE) {
return INVALID_SIZE;
}
if (size + p.second.size() > MAX_SIZE) {
return INVALID_SIZE;
}
size += p.second.size();
}
if (size > MAX_SIZE || size == 0) {
return INVALID_SIZE;
}
return size;
}
LRUCache::Node::Node(std::string key, std::unordered_map<std::string, std::string> value)
: key(std::move(key)), value(std::move(value))
{
}
LRUCache::LRUCache() : capacity_(MAX_SIZE), size_(0) {}
LRUCache::LRUCache(size_t capacity) : capacity_(std::min<size_t>(MAX_SIZE, capacity)), size_(0) {}
void LRUCache::AddNode(const Node &node)
{
nodeList_.emplace_front(node);
cache_[node.key] = nodeList_.begin();
size_ += GetMapValueSize(node.value);
}
void LRUCache::MoveNodeToHead(const std::list<Node>::iterator &it)
{
std::string key = it->key;
std::unordered_map<std::string, std::string> value = it->value;
nodeList_.erase(it);
nodeList_.emplace_front(key, value);
cache_[key] = nodeList_.begin();
}
void LRUCache::EraseTailNode()
{
if (nodeList_.empty()) {
return;
}
Node node = nodeList_.back();
nodeList_.pop_back();
cache_.erase(node.key);
size_ -= GetMapValueSize(node.value);
}
std::unordered_map<std::string, std::string> LRUCache::Get(const std::string &key)
{
std::lock_guard<std::mutex> guard(mutex_);
if (cache_.find(key) == cache_.end()) {
return {};
}
auto it = cache_[key];
auto value = it->value;
MoveNodeToHead(it);
return value;
}
void LRUCache::Put(const std::string &key, const std::unordered_map<std::string, std::string> &value)
{
std::lock_guard<std::mutex> guard(mutex_);
if (GetMapValueSize(value) == INVALID_SIZE) {
NETSTACK_LOGE("value is invalid(0 or too long) can not insert to cache");
return;
}
if (cache_.find(key) == cache_.end()) {
AddNode(Node(key, value));
while (size_ > capacity_) {
EraseTailNode();
}
return;
}
auto it = cache_[key];
size_ -= GetMapValueSize(it->value);
it->value = value;
size_ += GetMapValueSize(it->value);
MoveNodeToHead(it);
while (size_ > capacity_) {
EraseTailNode();
}
}
void LRUCache::MergeOtherCache(const LRUCache &other)
{
std::list<Node> reverseList;
{
// set mutex in min scope
std::lock_guard<std::mutex> guard(mutex_);
if (other.nodeList_.empty()) {
return;
}
reverseList = other.nodeList_;
}
reverseList.reverse();
for (const auto &node : reverseList) {
Put(node.key, node.value);
}
}
std::string LRUCache::WriteCacheToJsonValue()
{
NSMutableDictionary *root = [[NSMutableDictionary alloc] init];
int index = 0;
{
// set mutex in min scope
std::lock_guard<std::mutex> guard(mutex_);
for (const auto &node : nodeList_) {
NSMutableDictionary *nodeKey = [[NSMutableDictionary alloc] init];
for (const auto &p : node.value) {
NSData *rootData = [[NSData alloc] initWithBytes:p.second.c_str() length:p.second.length()];
NSError *error;
id rootObject = [NSJSONSerialization JSONObjectWithData:rootData options:0 error:&error];
if (rootObject == nil) {
NETSTACK_LOGE("Error serializing JSON data.");
return "";
}
[nodeKey setObject:rootObject forKey:[NSString stringWithCString:p.first.c_str() encoding:NSUTF8StringEncoding]];
}
[nodeKey setObject:[NSString stringWithFormat:@"%d", index] forKey:[NSString stringWithUTF8String:LRU_INDEX]];
index++;
[root setObject:nodeKey forKey:[NSString stringWithUTF8String:node.key.c_str()]];
}
}
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:root options:0 error:&error];
if (jsonData == nil) {
return "";
}
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
std::string json([jsonString UTF8String]);
return json;
}
void LRUCache::ReadCacheFromJsonValue(const std::string root)
{
NSData *rootData = [[NSData alloc] initWithBytes:root.c_str() length:root.length()];
NSError *error;
id rootObject = [NSJSONSerialization JSONObjectWithData:rootData options:0 error:&error];
if (rootObject == nil) {
NETSTACK_LOGE("parse json not success, maybe file is broken.");
return;
}
std::vector<Node> nodeVec;
NSUInteger rootCount = [rootObject count];
for (uint32_t i = 0; i < rootCount; i++) {
id keyItem = [rootObject objectAtIndex:i];
if (keyItem == nil || ![keyItem isKindOfClass:[NSDictionary class]]) {
continue;
}
NSArray *allKeys = [keyItem allKeys];
NSString *firstKey = allKeys[0];
std::unordered_map<std::string, std::string> m;
NSUInteger subCount = [keyItem count];
for (uint32_t j = 0; j < subCount; j++) {
id subItem = [keyItem objectAtIndex:j];
if (subItem == nil) {
continue;
}
NSArray *subAllKeys = [subItem allKeys];
NSString *subFirstKey = subAllKeys[0];
m[[subFirstKey cStringUsingEncoding:NSUTF8StringEncoding]] = [subItem[subAllKeys[0]] cStringUsingEncoding:NSUTF8StringEncoding];
}
if (m.find(LRU_INDEX) != m.end()) {
nodeVec.emplace_back([firstKey cStringUsingEncoding:NSUTF8StringEncoding], m);
}
}
std::sort(nodeVec.begin(), nodeVec.end(), [](Node &a, Node &b) {
return std::strtol(a.value[LRU_INDEX].c_str(), nullptr, DECIMAL_BASE) >
std::strtol(b.value[LRU_INDEX].c_str(), nullptr, DECIMAL_BASE);
});
for (auto &node : nodeVec) {
node.value.erase(LRU_INDEX);
if (!node.value.empty()) {
Put(node.key, node.value);
}
}
}
void LRUCache::Clear()
{
std::lock_guard<std::mutex> guard(mutex_);
cache_.clear();
nodeList_.clear();
}
} // namespace OHOS::NetStack::Http