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 <dirent.h>
19 #include <fstream>
20 #include <sys/stat.h>
21
22 #include "core/components_ng/image_provider/image_object.h"
23 #include "core/image/image_object.h"
24
25 namespace OHOS::Ace {
26
27 std::shared_mutex ImageCache::cacheFilePathMutex_;
28 std::string ImageCache::cacheFilePath_;
29
30 std::atomic<size_t> ImageCache::fileLimit_ = 100 * 1024 * 1024; // the capacity is 100MB
31
32 std::atomic<float> ImageCache::clearCacheFileRatio_ = 0.5f; // default clear ratio is 0.5
33
34 bool ImageCache::hasSetCacheFileInfo_ = false;
35
36 std::mutex ImageCache::cacheFileSizeMutex_;
37 int64_t ImageCache::cacheFileSize_ = 0;
38
39 std::mutex ImageCache::cacheFileInfoMutex_;
40 std::list<FileInfo> ImageCache::cacheFileInfo_;
41
42 // TODO: Create a real ImageCache later
43 #ifdef FLUTTER_2_5
44 class MockImageCache : public ImageCache {
Clear()45 void Clear() override {};
GetDataFromCacheFile(const std::string & filePath)46 RefPtr<CachedImageData> GetDataFromCacheFile(const std::string& filePath) override
47 {
48 return nullptr;
49 }
50 };
51
Create()52 RefPtr<ImageCache> ImageCache::Create()
53 {
54 return AceType::MakeRefPtr<MockImageCache>();
55 }
56
Purge()57 void ImageCache::Purge() {}
58 #endif
59
60 template<typename T>
CacheWithCountLimitLRU(const std::string & key,const T & cacheObj,std::list<CacheNode<T>> & cacheList,std::unordered_map<std::string,typename std::list<CacheNode<T>>::iterator> & cache,const std::atomic<size_t> & capacity)61 void ImageCache::CacheWithCountLimitLRU(const std::string& key, const T& cacheObj, std::list<CacheNode<T>>& cacheList,
62 std::unordered_map<std::string, typename std::list<CacheNode<T>>::iterator>& cache,
63 const std::atomic<size_t>& capacity)
64 {
65 auto iter = cache.find(key);
66 if (iter == cache.end()) {
67 if (cache.size() == capacity) {
68 cache.erase(cacheList.back().cacheKey);
69 cacheList.pop_back();
70 }
71 cacheList.emplace_front(key, cacheObj);
72 cache.emplace(key, cacheList.begin());
73 } else {
74 iter->second->cacheObj = cacheObj;
75 cacheList.splice(cacheList.begin(), cacheList, iter->second);
76 iter->second = cacheList.begin();
77 }
78 }
79
80 template<typename T>
GetCacheObjWithCountLimitLRU(const std::string & key,std::list<CacheNode<T>> & cacheList,std::unordered_map<std::string,typename std::list<CacheNode<T>>::iterator> & cache)81 T ImageCache::GetCacheObjWithCountLimitLRU(const std::string& key, std::list<CacheNode<T>>& cacheList,
82 std::unordered_map<std::string, typename std::list<CacheNode<T>>::iterator>& cache)
83 {
84 auto iter = cache.find(key);
85 if (iter != cache.end()) {
86 cacheList.splice(cacheList.begin(), cacheList, iter->second);
87 iter->second = cacheList.begin();
88 return iter->second->cacheObj;
89 }
90 return nullptr;
91 }
92
GetFromCacheFile(const std::string & filePath)93 bool ImageCache::GetFromCacheFile(const std::string& filePath)
94 {
95 std::lock_guard<std::mutex> lock(cacheFileInfoMutex_);
96 return GetFromCacheFileInner(filePath);
97 }
98
GetFromCacheFileInner(const std::string & filePath)99 bool ImageCache::GetFromCacheFileInner(const std::string& filePath)
100 {
101 auto iter = std::find_if(cacheFileInfo_.begin(), cacheFileInfo_.end(),
102 [&filePath](const FileInfo& fileInfo) { return fileInfo.filePath == filePath; });
103 if (iter == cacheFileInfo_.end()) {
104 return false;
105 }
106 iter->accessTime = time(nullptr);
107 cacheFileInfo_.splice(cacheFileInfo_.end(), cacheFileInfo_, iter);
108 return true;
109 }
110
CacheImage(const std::string & key,const std::shared_ptr<CachedImage> & image)111 void ImageCache::CacheImage(const std::string& key, const std::shared_ptr<CachedImage>& image)
112 {
113 if (key.empty() || capacity_ == 0) {
114 return;
115 }
116 std::scoped_lock lock(imageCacheMutex_);
117 CacheWithCountLimitLRU<std::shared_ptr<CachedImage>>(key, image, cacheList_, imageCache_, capacity_);
118 }
119
GetCacheImage(const std::string & key)120 std::shared_ptr<CachedImage> ImageCache::GetCacheImage(const std::string& key)
121 {
122 std::scoped_lock lock(imageCacheMutex_);
123 return GetCacheObjWithCountLimitLRU<std::shared_ptr<CachedImage>>(key, cacheList_, imageCache_);
124 }
125
CacheImgObjNG(const std::string & key,const RefPtr<NG::ImageObject> & imgObj)126 void ImageCache::CacheImgObjNG(const std::string& key, const RefPtr<NG::ImageObject>& imgObj)
127 {
128 if (key.empty() || imgObjCapacity_ == 0) {
129 return;
130 }
131 std::scoped_lock lock(imgObjCacheMutex_);
132 CacheWithCountLimitLRU<RefPtr<NG::ImageObject>>(key, imgObj, cacheImgObjListNG_, imgObjCacheNG_, imgObjCapacity_);
133 }
134
GetCacheImgObjNG(const std::string & key)135 RefPtr<NG::ImageObject> ImageCache::GetCacheImgObjNG(const std::string& key)
136 {
137 std::scoped_lock lock(imgObjCacheMutex_);
138 return GetCacheObjWithCountLimitLRU<RefPtr<NG::ImageObject>>(key, cacheImgObjListNG_, imgObjCacheNG_);
139 }
140
CacheImgObj(const std::string & key,const RefPtr<ImageObject> & imgObj)141 void ImageCache::CacheImgObj(const std::string& key, const RefPtr<ImageObject>& imgObj)
142 {
143 if (key.empty() || imgObjCapacity_ == 0) {
144 return;
145 }
146 std::scoped_lock lock(imgObjCacheMutex_);
147 CacheWithCountLimitLRU<RefPtr<ImageObject>>(key, imgObj, cacheImgObjList_, imgObjCache_, imgObjCapacity_);
148 }
149
GetCacheImgObj(const std::string & key)150 RefPtr<ImageObject> ImageCache::GetCacheImgObj(const std::string& key)
151 {
152 std::scoped_lock lock(imgObjCacheMutex_);
153 return GetCacheObjWithCountLimitLRU<RefPtr<ImageObject>>(key, cacheImgObjList_, imgObjCache_);
154 }
155
CacheImageData(const std::string & key,const RefPtr<CachedImageData> & imageData)156 void ImageCache::CacheImageData(const std::string& key, const RefPtr<CachedImageData>& imageData)
157 {
158 if (key.empty() || !imageData || dataSizeLimit_ == 0) {
159 return;
160 }
161 std::scoped_lock lock(dataCacheMutex_);
162 auto dataSize = imageData->GetSize();
163 if (dataSize > (dataSizeLimit_ >> 1)) { // if data is longer than half limit, do not cache it.
164 LOGW("data is %{public}d, bigger than half limit %{public}d, do not cache it", static_cast<int32_t>(dataSize),
165 static_cast<int32_t>(dataSizeLimit_ >> 1));
166 return;
167 }
168 auto iter = imageDataCache_.find(key);
169 if (iter == imageDataCache_.end()) {
170 if (!ProcessImageDataCacheInner(dataSize)) {
171 return;
172 }
173 dataCacheList_.emplace_front(key, imageData);
174 imageDataCache_.emplace(key, dataCacheList_.begin());
175 } else {
176 auto oldSize = iter->second->imageDataPtr->GetSize();
177 if (oldSize != dataSize) {
178 curDataSize_ -= oldSize;
179 if (!ProcessImageDataCacheInner(dataSize)) {
180 return;
181 }
182 }
183 iter->second->imageDataPtr = imageData;
184 dataCacheList_.splice(dataCacheList_.begin(), dataCacheList_, iter->second);
185 iter->second = dataCacheList_.begin();
186 }
187 }
188
ProcessImageDataCacheInner(size_t dataSize)189 bool ImageCache::ProcessImageDataCacheInner(size_t dataSize)
190 {
191 while (dataSize + curDataSize_ > dataSizeLimit_ && !dataCacheList_.empty()) {
192 curDataSize_ -= dataCacheList_.back().imageDataPtr->GetSize();
193 imageDataCache_.erase(dataCacheList_.back().imageDataKey);
194 dataCacheList_.pop_back();
195 }
196 if (dataSize + curDataSize_ > dataSizeLimit_) {
197 return false;
198 }
199 curDataSize_ += dataSize;
200 return true;
201 }
202
GetCacheImageData(const std::string & key)203 RefPtr<CachedImageData> ImageCache::GetCacheImageData(const std::string& key)
204 {
205 std::scoped_lock lock(dataCacheMutex_);
206 auto iter = imageDataCache_.find(key);
207 if (iter != imageDataCache_.end()) {
208 dataCacheList_.splice(dataCacheList_.begin(), dataCacheList_, iter->second);
209 iter->second = dataCacheList_.begin();
210 return iter->second->imageDataPtr;
211 }
212 return nullptr;
213 }
214
WriteCacheFile(const std::string & url,const void * const data,size_t size,const std::string & suffix)215 void ImageCache::WriteCacheFile(const std::string& url, const void* const data, size_t size, const std::string& suffix)
216 {
217 if (size > fileLimit_) {
218 LOGW("file size is %{public}d, greater than limit %{public}d, cannot cache", static_cast<int32_t>(size),
219 static_cast<int32_t>(fileLimit_));
220 return;
221 }
222 std::vector<std::string> removeVector;
223 std::string cacheNetworkFilePath = GetImageCacheFilePath(url) + suffix;
224
225 std::lock_guard<std::mutex> lock(cacheFileInfoMutex_);
226 // 1. first check if file has been cached.
227 if (ImageCache::GetFromCacheFileInner(cacheNetworkFilePath)) {
228 LOGI("file has been wrote %{private}s", cacheNetworkFilePath.c_str());
229 return;
230 }
231
232 // 2. if not in dist, write file into disk.
233 #ifdef WINDOWS_PLATFORM
234 std::ofstream outFile(cacheNetworkFilePath, std::ios::binary);
235 #else
236 std::ofstream outFile(cacheNetworkFilePath, std::fstream::out);
237 #endif
238 if (!outFile.is_open()) {
239 LOGW("open cache file failed, cannot write.");
240 return;
241 }
242 outFile.write(reinterpret_cast<const char*>(data), size);
243 LOGI("write image cache: %{public}s %{private}s", url.c_str(), cacheNetworkFilePath.c_str());
244
245 cacheFileSize_ += size;
246 cacheFileInfo_.emplace_back(cacheNetworkFilePath, size, time(nullptr));
247 // check if cache files too big.
248 if (cacheFileSize_ > static_cast<int32_t>(fileLimit_)) {
249 auto removeCount = static_cast<int32_t>(cacheFileInfo_.size() * clearCacheFileRatio_);
250 int32_t removeSize = 0;
251 auto iter = cacheFileInfo_.begin();
252 int32_t count = 0;
253 while (count < removeCount) {
254 removeSize += static_cast<int32_t>(iter->fileSize);
255 removeVector.push_back(iter->filePath);
256 ++iter;
257 ++count;
258 }
259 cacheFileInfo_.erase(cacheFileInfo_.begin(), iter);
260 cacheFileSize_ -= static_cast<int32_t>(removeSize);
261 }
262 // 3. clear files removed from cache list.
263 ClearCacheFile(removeVector);
264 }
265
ClearCacheFile(const std::vector<std::string> & removeFiles)266 void ImageCache::ClearCacheFile(const std::vector<std::string>& removeFiles)
267 {
268 LOGD("begin to clear %{public}zu files: ", removeFiles.size());
269 for (auto&& iter : removeFiles) {
270 if (remove(iter.c_str()) != 0) {
271 LOGW("remove file %{private}s failed.", iter.c_str());
272 continue;
273 }
274 }
275 }
276
ClearCacheImage(const std::string & key)277 void ImageCache::ClearCacheImage(const std::string& key)
278 {
279 {
280 std::scoped_lock lock(imageCacheMutex_);
281 auto iter = imageCache_.find(key);
282 if (iter != imageCache_.end()) {
283 cacheList_.erase(iter->second);
284 imageCache_.erase(iter);
285 }
286 }
287
288 {
289 std::scoped_lock lock(dataCacheMutex_);
290 auto iter = imageDataCache_.find(key);
291 if (iter != imageDataCache_.end()) {
292 dataCacheList_.erase(iter->second);
293 imageDataCache_.erase(iter);
294 }
295 }
296 }
297
SetCacheFileInfo()298 void ImageCache::SetCacheFileInfo()
299 {
300 std::lock_guard<std::mutex> lock(cacheFileInfoMutex_);
301 // Set cache file information only once.
302 if (hasSetCacheFileInfo_) {
303 return;
304 }
305 std::string cacheFilePath = GetImageCacheFilePath();
306 std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(cacheFilePath.c_str()), closedir);
307 if (dir == nullptr) {
308 LOGW("cache file path wrong! maybe it is not set.");
309 return;
310 }
311 int64_t cacheFileSize = 0;
312 dirent* filePtr = readdir(dir.get());
313 while (filePtr != nullptr) {
314 // skip . or ..
315 if (filePtr->d_name[0] != '.') {
316 std::string filePath = cacheFilePath + "/" + std::string(filePtr->d_name);
317 struct stat fileStatus;
318 if (stat(filePath.c_str(), &fileStatus) == -1) {
319 filePtr = readdir(dir.get());
320 continue;
321 }
322 cacheFileInfo_.emplace_back(filePath, fileStatus.st_size, fileStatus.st_atime);
323 cacheFileSize += static_cast<int64_t>(fileStatus.st_size);
324 }
325 filePtr = readdir(dir.get());
326 }
327 cacheFileInfo_.sort();
328 cacheFileSize_ = cacheFileSize;
329 hasSetCacheFileInfo_ = true;
330 }
331
332 } // namespace OHOS::Ace
333