1 /*
2  * Copyright (c) 2021-2023 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "core/image/image_cache.h"
17 
18 #include "base/log/dump_log.h"
19 #include "core/components_ng/image_provider/image_object.h"
20 #include "core/image/image_object.h"
21 
22 namespace OHOS::Ace {
23 namespace {
24 constexpr uint64_t MAX_WAITING_TIME = 1000; // 1000ms
25 }
Create()26 RefPtr<ImageCache> ImageCache::Create()
27 {
28     return MakeRefPtr<ImageCache>();
29 }
30 
31 // TODO: Create a real ImageCache later
32 #ifdef FLUTTER_2_5
33 class MockImageCache : public ImageCache {
Clear()34     void Clear() override {};
GetDataFromCacheFile(const std::string & filePath)35     RefPtr<NG::ImageData> GetDataFromCacheFile(const std::string& filePath) override
36     {
37         return nullptr;
38     }
39 };
40 
Create()41 RefPtr<ImageCache> ImageCache::Create()
42 {
43     return AceType::MakeRefPtr<MockImageCache>();
44 }
45 
Purge()46 void ImageCache::Purge() {}
47 #endif
48 
CacheImage(const std::string & key,const std::shared_ptr<CachedImage> & image)49 void ImageCache::CacheImage(const std::string& key, const std::shared_ptr<CachedImage>& image)
50 {
51     if (key.empty() || capacity_ == 0) {
52         return;
53     }
54     std::scoped_lock lock(imageCacheMutex_);
55     CountLimitLRU::CacheWithCountLimitLRU<std::shared_ptr<CachedImage>>(key, image, cacheList_, imageCache_, capacity_);
56 }
57 
GetCacheImage(const std::string & key)58 std::shared_ptr<CachedImage> ImageCache::GetCacheImage(const std::string& key)
59 {
60     std::scoped_lock lock(imageCacheMutex_);
61     return CountLimitLRU::GetCacheObjWithCountLimitLRU<std::shared_ptr<CachedImage>>(key, cacheList_, imageCache_);
62 }
63 
CacheImgObjNG(const std::string & key,const RefPtr<NG::ImageObject> & imgObj)64 void ImageCache::CacheImgObjNG(const std::string& key, const RefPtr<NG::ImageObject>& imgObj)
65 {
66     if (key.empty() || imgObjCapacity_ == 0) {
67         return;
68     }
69     std::scoped_lock lock(imgObjMutex_);
70     CountLimitLRU::CacheWithCountLimitLRU<RefPtr<NG::ImageObject>>(
71         key, imgObj, cacheImgObjListNG_, imgObjCacheNG_, imgObjCapacity_);
72 }
73 
GetCacheImgObjNG(const std::string & key)74 RefPtr<NG::ImageObject> ImageCache::GetCacheImgObjNG(const std::string& key)
75 {
76     std::scoped_lock lock(imgObjMutex_);
77     return CountLimitLRU::GetCacheObjWithCountLimitLRU<RefPtr<NG::ImageObject>>(
78         key, cacheImgObjListNG_, imgObjCacheNG_);
79 }
80 
CacheImgObj(const std::string & key,const RefPtr<ImageObject> & imgObj)81 void ImageCache::CacheImgObj(const std::string& key, const RefPtr<ImageObject>& imgObj)
82 {
83     if (key.empty() || imgObjCapacity_ == 0) {
84         return;
85     }
86     std::scoped_lock lock(imgObjMutex_);
87     CountLimitLRU::CacheWithCountLimitLRU<RefPtr<ImageObject>>(
88         key, imgObj, cacheImgObjList_, imgObjCache_, imgObjCapacity_);
89 }
90 
GetCacheImgObj(const std::string & key)91 RefPtr<ImageObject> ImageCache::GetCacheImgObj(const std::string& key)
92 {
93     std::scoped_lock lock(imgObjMutex_);
94     return CountLimitLRU::GetCacheObjWithCountLimitLRU<RefPtr<ImageObject>>(key, cacheImgObjList_, imgObjCache_);
95 }
96 
CacheImageData(const std::string & key,const RefPtr<NG::ImageData> & imageData)97 void ImageCache::CacheImageData(const std::string& key, const RefPtr<NG::ImageData>& imageData)
98 {
99     if (key.empty() || !imageData || dataSizeLimit_ == 0) {
100         return;
101     }
102     ACE_SCOPED_TRACE("CacheImageData key:%s", key.c_str());
103     auto dataSize = imageData->GetSize();
104     std::vector<CacheNode<RefPtr<NG::ImageData>>> needErase;
105 
106     // Try to acquire the dataCacheMutex_ lock within MAX_WAITING_TIME milliseconds to avoid long blocking.
107     if (!dataCacheMutex_.try_lock_for(std::chrono::milliseconds(MAX_WAITING_TIME))) {
108         TAG_LOGW(AceLogTag::ACE_IMAGE,
109             "Failed to acquire mutex within %{public}" PRIu64 "milliseconds, proceeding without cache access.",
110             MAX_WAITING_TIME);
111         return;
112     }
113     // Adopt the already acquired lock
114     std::scoped_lock lock(std::adopt_lock, dataCacheMutex_);
115     auto iter = imageDataCache_.find(key);
116     bool inCache = (iter != imageDataCache_.end());
117     bool largerHalfSize = dataSize > (dataSizeLimit_ >> 1);
118     size_t oldSize = !inCache ? 0 : iter->second->cacheObj->GetSize();
119     if (largerHalfSize && inCache) {
120         // if data is longer than half limit, do not cache it.
121         // and if the key is in Cache, erase it.
122         curDataSize_ -= oldSize;
123         needErase.push_back(*(iter->second));
124         dataCacheList_.erase(iter->second);
125         imageDataCache_.erase(key);
126         TAG_LOGW(AceLogTag::ACE_IMAGE, "data is %{public}d, bigger than half limit %{public}d, do not cache it",
127             static_cast<int32_t>(dataSize), static_cast<int32_t>(dataSizeLimit_ >> 1));
128     } else if (!largerHalfSize && !inCache && ProcessImageDataCacheInner(dataSize, needErase)) {
129         dataCacheList_.emplace_front(key, imageData);
130         imageDataCache_.emplace(key, dataCacheList_.begin());
131     } else if (!largerHalfSize && inCache && oldSize >= dataSize) {
132         // if the image is in the cache, and dataSize <= oldSize, we can replace the imageData in cache.
133         curDataSize_ = curDataSize_ + dataSize - oldSize;
134         iter->second->cacheObj = imageData;
135         dataCacheList_.splice(dataCacheList_.begin(), dataCacheList_, iter->second);
136         iter->second = dataCacheList_.begin();
137     } else if (!largerHalfSize && inCache && oldSize < dataSize) {
138         // if the image is in the cache, and dataSize > oldSize, we erase the old one, the try to cache the new image.
139         curDataSize_ -= oldSize;
140         needErase.push_back(*(iter->second));
141         dataCacheList_.erase(iter->second);
142         imageDataCache_.erase(key);
143         if (ProcessImageDataCacheInner(dataSize, needErase)) {
144             dataCacheList_.emplace_front(key, imageData);
145             imageDataCache_.emplace(key, dataCacheList_.begin());
146         }
147     }
148 }
149 
ProcessImageDataCacheInner(size_t dataSize,std::vector<CacheNode<RefPtr<NG::ImageData>>> & needErase)150 bool ImageCache::ProcessImageDataCacheInner(size_t dataSize, std::vector<CacheNode<RefPtr<NG::ImageData>>>& needErase)
151 {
152     while (dataSize + curDataSize_ > dataSizeLimit_ && !dataCacheList_.empty()) {
153         curDataSize_ -= dataCacheList_.back().cacheObj->GetSize();
154         needErase.push_back(dataCacheList_.back());
155         imageDataCache_.erase(dataCacheList_.back().cacheKey);
156         dataCacheList_.pop_back();
157     }
158     if (dataSize + curDataSize_ > dataSizeLimit_) {
159         return false;
160     }
161     curDataSize_ += dataSize;
162     return true;
163 }
164 
GetCacheImageData(const std::string & key)165 RefPtr<NG::ImageData> ImageCache::GetCacheImageData(const std::string& key)
166 {
167     ACE_SCOPED_TRACE("GetCacheImageData key:%s", key.c_str());
168     // Try to acquire the dataCacheMutex_ lock within MAX_WAITING_TIME milliseconds to avoid long blocking.
169     if (!dataCacheMutex_.try_lock_for(std::chrono::milliseconds(MAX_WAITING_TIME))) {
170         TAG_LOGW(AceLogTag::ACE_IMAGE,
171             "Failed to acquire mutex within %{public}" PRIu64 "milliseconds, proceeding without cache access.",
172             MAX_WAITING_TIME);
173         return nullptr;
174     }
175     // Adopt the already acquired lock
176     std::scoped_lock lock(std::adopt_lock, dataCacheMutex_);
177     auto iter = imageDataCache_.find(key);
178     if (iter != imageDataCache_.end()) {
179         dataCacheList_.splice(dataCacheList_.begin(), dataCacheList_, iter->second);
180         iter->second = dataCacheList_.begin();
181         return iter->second->cacheObj;
182     }
183     return nullptr;
184 }
185 
ClearCacheImage(const std::string & key)186 void ImageCache::ClearCacheImage(const std::string& key)
187 {
188     ACE_SCOPED_TRACE("ClearCacheImage key:%s", key.c_str());
189     {
190         std::scoped_lock lock(imageCacheMutex_);
191         auto iter = imageCache_.find(key);
192         if (iter != imageCache_.end()) {
193             cacheList_.erase(iter->second);
194             imageCache_.erase(iter);
195         }
196     }
197 
198     {
199         // Try to acquire the dataCacheMutex_ lock within MAX_WAITING_TIME milliseconds to avoid long blocking.
200         if (!dataCacheMutex_.try_lock_for(std::chrono::milliseconds(MAX_WAITING_TIME))) {
201             TAG_LOGW(AceLogTag::ACE_IMAGE,
202                 "Failed to acquire mutex within %{public}" PRIu64 "milliseconds, proceeding without cache access.",
203                 MAX_WAITING_TIME);
204             return;
205         }
206         // Adopt the already acquired lock
207         std::scoped_lock lock(std::adopt_lock, dataCacheMutex_);
208         auto iter = imageDataCache_.find(key);
209         if (iter != imageDataCache_.end()) {
210             dataCacheList_.erase(iter->second);
211             imageDataCache_.erase(iter);
212         }
213     }
214 }
215 
Clear()216 void ImageCache::Clear()
217 {
218     ACE_SCOPED_TRACE("ImageCache Clear");
219     {
220         std::scoped_lock lock(imageCacheMutex_);
221         cacheList_.clear();
222         imageCache_.clear();
223     }
224     {
225         // Try to acquire the dataCacheMutex_ lock within MAX_WAITING_TIME milliseconds to avoid long blocking.
226         if (!dataCacheMutex_.try_lock_for(std::chrono::milliseconds(MAX_WAITING_TIME))) {
227             TAG_LOGW(AceLogTag::ACE_IMAGE,
228                 "Failed to acquire mutex within %{public}" PRIu64 "milliseconds, proceeding without cache access.",
229                 MAX_WAITING_TIME);
230             return;
231         }
232         // Adopt the already acquired lock
233         std::scoped_lock lock(std::adopt_lock, dataCacheMutex_);
234         dataCacheList_.clear();
235         imageDataCache_.clear();
236     }
237     {
238         std::scoped_lock lock(imgObjMutex_);
239         cacheImgObjListNG_.clear();
240         imgObjCacheNG_.clear();
241         cacheImgObjList_.clear();
242         imgObjCache_.clear();
243     }
244 }
245 
DumpCacheInfo()246 void ImageCache::DumpCacheInfo()
247 {
248     auto cacheSize = dataCacheList_.size();
249     auto capacity = static_cast<int32_t>(capacity_);
250     auto dataSizeLimit = static_cast<int32_t>(dataSizeLimit_);
251     DumpLog::GetInstance().Print("------------ImageCacheInfo------------");
252     DumpLog::GetInstance().Print("User set ImageRawDataCacheSize : " + std::to_string(dataSizeLimit) + "(B)" +
253                                  ", ImageCacheCount :" + std::to_string(capacity) + "(number)");
254     DumpLog::GetInstance().Print("Cache count: " + std::to_string(cacheSize));
255     if (cacheSize == 0) {
256         return;
257     }
258     auto totalCount = 0;
259     for (const auto& item : dataCacheList_) {
260         auto imageObj = item.cacheObj;
261         auto key = item.cacheKey;
262         std::string srcStr = "NA";
263         for (const auto& cacheImageObj : cacheImgObjListNG_) {
264             if (cacheImageObj.cacheKey == key) {
265                 srcStr = cacheImageObj.cacheObj->GetSourceInfo().ToString();
266                 break;
267             }
268         }
269         totalCount += static_cast<int32_t>(imageObj->GetSize());
270         DumpLog::GetInstance().Print("Cache Obj of key: " + key + ", src:" + srcStr + "," + imageObj->ToString());
271     }
272     DumpLog::GetInstance().Print("Cache total size: " + std::to_string(totalCount));
273 }
274 } // namespace OHOS::Ace
275