• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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::cacheFileLimit_ = 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 int32_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 NG_BUILD
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     std::list<FileInfo>::iterator 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 
CacheImageNG(const std::string & key,const std::shared_ptr<NG::CachedImage> & image)120 void ImageCache::CacheImageNG(const std::string& key, const std::shared_ptr<NG::CachedImage>& image)
121 {
122     if (key.empty() || capacity_ == 0) {
123         return;
124     }
125     std::scoped_lock lock(imageCacheMutex_);
126     CacheWithCountLimitLRU<std::shared_ptr<NG::CachedImage>>(key, image, cacheListNG_, imageCacheNG_, capacity_);
127 }
128 
GetCacheImage(const std::string & key)129 std::shared_ptr<CachedImage> ImageCache::GetCacheImage(const std::string& key)
130 {
131     std::scoped_lock lock(imageCacheMutex_);
132     return GetCacheObjWithCountLimitLRU<std::shared_ptr<CachedImage>>(key, cacheList_, imageCache_);
133 }
134 
GetCacheImageNG(const std::string & key)135 std::shared_ptr<NG::CachedImage> ImageCache::GetCacheImageNG(const std::string& key)
136 {
137     std::scoped_lock lock(imageCacheMutex_);
138     return GetCacheObjWithCountLimitLRU<std::shared_ptr<NG::CachedImage>>(key, cacheListNG_, imageCacheNG_);
139 }
140 
CacheImgObjNG(const std::string & key,const RefPtr<NG::ImageObject> & imgObj)141 void ImageCache::CacheImgObjNG(const std::string& key, const RefPtr<NG::ImageObject>& imgObj)
142 {
143     if (key.empty() || imgObjCapacity_ == 0) {
144         return;
145     }
146     std::scoped_lock lock(cacheImgObjListMutex_, imgObjCacheMutex_);
147     CacheWithCountLimitLRU<RefPtr<NG::ImageObject>>(key, imgObj, cacheImgObjListNG_, imgObjCacheNG_, imgObjCapacity_);
148 }
149 
GetCacheImgObjNG(const std::string & key)150 RefPtr<NG::ImageObject> ImageCache::GetCacheImgObjNG(const std::string& key)
151 {
152     std::scoped_lock lock(cacheImgObjListMutex_, imgObjCacheMutex_);
153     return GetCacheObjWithCountLimitLRU<RefPtr<NG::ImageObject>>(key, cacheImgObjListNG_, imgObjCacheNG_);
154 }
155 
CacheImgObj(const std::string & key,const RefPtr<ImageObject> & imgObj)156 void ImageCache::CacheImgObj(const std::string& key, const RefPtr<ImageObject>& imgObj)
157 {
158     if (key.empty() || imgObjCapacity_ == 0) {
159         return;
160     }
161     std::scoped_lock lock(cacheImgObjListMutex_, imgObjCacheMutex_);
162     CacheWithCountLimitLRU<RefPtr<ImageObject>>(key, imgObj, cacheImgObjList_, imgObjCache_, imgObjCapacity_);
163 }
164 
GetCacheImgObj(const std::string & key)165 RefPtr<ImageObject> ImageCache::GetCacheImgObj(const std::string& key)
166 {
167     std::scoped_lock lock(cacheImgObjListMutex_, imgObjCacheMutex_);
168     return GetCacheObjWithCountLimitLRU<RefPtr<ImageObject>>(key, cacheImgObjList_, imgObjCache_);
169 }
170 
CacheImageData(const std::string & key,const RefPtr<CachedImageData> & imageData)171 void ImageCache::CacheImageData(const std::string& key, const RefPtr<CachedImageData>& imageData)
172 {
173     if (key.empty() || !imageData || dataSizeLimit_ == 0) {
174         return;
175     }
176     std::scoped_lock lock(dataCacheListMutex_, imageDataCacheMutex_);
177     auto dataSize = imageData->GetSize();
178     if (dataSize > (dataSizeLimit_ >> 1)) { // if data is longer than half limit, do not cache it.
179         LOGW("data is %{public}d, bigger than half limit %{public}d, do not cache it", static_cast<int32_t>(dataSize),
180             static_cast<int32_t>(dataSizeLimit_ >> 1));
181         return;
182     }
183     auto iter = imageDataCache_.find(key);
184     if (iter == imageDataCache_.end()) {
185         if (!ProcessImageDataCacheInner(dataSize)) {
186             return;
187         }
188         dataCacheList_.emplace_front(key, imageData);
189         imageDataCache_.emplace(key, dataCacheList_.begin());
190     } else {
191         auto oldSize = iter->second->imageDataPtr->GetSize();
192         if (oldSize != dataSize) {
193             curDataSize_ -= oldSize;
194             if (!ProcessImageDataCacheInner(dataSize)) {
195                 return;
196             }
197         }
198         iter->second->imageDataPtr = imageData;
199         dataCacheList_.splice(dataCacheList_.begin(), dataCacheList_, iter->second);
200         iter->second = dataCacheList_.begin();
201     }
202 }
203 
ProcessImageDataCacheInner(size_t dataSize)204 bool ImageCache::ProcessImageDataCacheInner(size_t dataSize)
205 {
206     while (dataSize + curDataSize_ > dataSizeLimit_ && !dataCacheList_.empty()) {
207         curDataSize_ -= dataCacheList_.back().imageDataPtr->GetSize();
208         imageDataCache_.erase(dataCacheList_.back().imageDataKey);
209         dataCacheList_.pop_back();
210     }
211     if (dataSize + curDataSize_ > dataSizeLimit_) {
212         return false;
213     }
214     curDataSize_ += dataSize;
215     return true;
216 }
217 
GetCacheImageData(const std::string & key)218 RefPtr<CachedImageData> ImageCache::GetCacheImageData(const std::string& key)
219 {
220     std::scoped_lock lock(dataCacheListMutex_, imageDataCacheMutex_);
221     auto iter = imageDataCache_.find(key);
222     if (iter != imageDataCache_.end()) {
223         dataCacheList_.splice(dataCacheList_.begin(), dataCacheList_, iter->second);
224         iter->second = dataCacheList_.begin();
225         return iter->second->imageDataPtr;
226     }
227     return nullptr;
228 }
229 
WriteCacheFile(const std::string & url,const void * const data,size_t size,const std::string & suffix)230 void ImageCache::WriteCacheFile(const std::string& url, const void* const data, size_t size, const std::string& suffix)
231 {
232     std::vector<std::string> removeVector;
233     std::string cacheNetworkFilePath = GetImageCacheFilePath(url) + suffix;
234 
235     std::lock_guard<std::mutex> lock(cacheFileInfoMutex_);
236     // 1. first check if file has been cached.
237     if (ImageCache::GetFromCacheFileInner(cacheNetworkFilePath)) {
238         LOGI("file has been wrote %{private}s", cacheNetworkFilePath.c_str());
239         return;
240     }
241 
242     // 2. if not in dist, write file into disk.
243 #ifdef WINDOWS_PLATFORM
244     std::ofstream outFile(cacheNetworkFilePath, std::ios::binary);
245 #else
246     std::ofstream outFile(cacheNetworkFilePath, std::fstream::out);
247 #endif
248     if (!outFile.is_open()) {
249         LOGW("open cache file failed, cannot write.");
250         return;
251     }
252     outFile.write(reinterpret_cast<const char*>(data), size);
253     LOGI("write image cache: %{public}s %{private}s", url.c_str(), cacheNetworkFilePath.c_str());
254 
255     cacheFileSize_ += size;
256     cacheFileInfo_.emplace_back(cacheNetworkFilePath, size, time(nullptr));
257     // check if cache files too big.
258     if (cacheFileSize_ > static_cast<int32_t>(cacheFileLimit_)) {
259         int32_t removeCount = static_cast<int32_t>(cacheFileInfo_.size() * clearCacheFileRatio_);
260         int32_t removeSize = 0;
261         auto iter = cacheFileInfo_.begin();
262         int32_t count = 0;
263         while (count < removeCount) {
264             removeSize += static_cast<int32_t>(iter->fileSize);
265             removeVector.push_back(iter->filePath);
266             iter++;
267             count++;
268         }
269         cacheFileInfo_.erase(cacheFileInfo_.begin(), iter);
270         cacheFileSize_ -= static_cast<int32_t>(removeSize);
271     }
272     // 3. clear files removed from cache list.
273     ClearCacheFile(removeVector);
274 }
275 
ClearCacheFile(const std::vector<std::string> & removeFiles)276 void ImageCache::ClearCacheFile(const std::vector<std::string>& removeFiles)
277 {
278     LOGD("begin to clear %{public}zu files: ", removeFiles.size());
279     for (auto iter : removeFiles) {
280         if (remove(iter.c_str()) != 0) {
281             LOGW("remove file %{private}s failed.", iter.c_str());
282             continue;
283         }
284     }
285 }
286 
SetCacheFileInfo()287 void ImageCache::SetCacheFileInfo()
288 {
289     std::lock_guard<std::mutex> lock(cacheFileInfoMutex_);
290     // Set cache file information only once.
291     if (hasSetCacheFileInfo_) {
292         return;
293     }
294     std::string cacheFilePath = GetImageCacheFilePath();
295     std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(cacheFilePath.c_str()), closedir);
296     if (dir == nullptr) {
297         LOGW("cache file path wrong! maybe it is not set.");
298         return;
299     }
300     int32_t cacheFileSize = 0;
301     dirent* filePtr = readdir(dir.get());
302     while (filePtr != nullptr) {
303         // skip . or ..
304         if (filePtr->d_name[0] != '.') {
305             std::string filePath = cacheFilePath + "/" + std::string(filePtr->d_name);
306             struct stat fileStatus;
307             if (stat(filePath.c_str(), &fileStatus) == -1) {
308                 filePtr = readdir(dir.get());
309                 continue;
310             }
311             cacheFileInfo_.emplace_back(filePath, fileStatus.st_size, fileStatus.st_atime);
312             cacheFileSize += static_cast<int32_t>(fileStatus.st_size);
313         }
314         filePtr = readdir(dir.get());
315     }
316     cacheFileInfo_.sort();
317     cacheFileSize_ = cacheFileSize;
318     hasSetCacheFileInfo_ = true;
319 }
320 
321 } // namespace OHOS::Ace
322