• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 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 #include "core/image/image_file_cache.h"
16 
17 #include <dirent.h>
18 #include <fstream>
19 #include <sys/stat.h>
20 
21 #include "base/log/dump_log.h"
22 #include "base/thread/background_task_executor.h"
23 #include "core/image/image_loader.h"
24 #include "core/image/image_source_info.h"
25 
26 #ifdef USE_ROSEN_DRAWING
27 #include "core/components_ng/image_provider/adapter/rosen/drawing_image_data.h"
28 #endif
29 
30 namespace OHOS::Ace {
31 ImageFileCache::ImageFileCache() = default;
32 ImageFileCache::~ImageFileCache() = default;
33 
34 namespace {
IsAstcFile(const char fileName[])35 bool IsAstcFile(const char fileName[])
36 {
37     auto length = strlen(fileName);
38     if (length >= ASTC_SUFFIX.length()) {
39         auto suffixPos = length - ASTC_SUFFIX.length();
40         if (std::memcmp(fileName + suffixPos, ASTC_SUFFIX.c_str(), ASTC_SUFFIX.length()) == 0) {
41             return true;
42         }
43     }
44     return false;
45 }
46 
EndsWith(const std::string & str,const std::string & substr)47 bool EndsWith(const std::string& str, const std::string& substr)
48 {
49     return str.rfind(substr) == (str.length() - substr.length());
50 }
51 }
52 
SetImageCacheFilePath(const std::string & cacheFilePath)53 void ImageFileCache::SetImageCacheFilePath(const std::string& cacheFilePath)
54 {
55     std::unique_lock<std::shared_mutex> lock(cacheFilePathMutex_);
56     if (cacheFilePath_.empty()) {
57         cacheFilePath_ = cacheFilePath;
58     }
59 }
60 
GetImageCacheFilePath()61 std::string ImageFileCache::GetImageCacheFilePath()
62 {
63     std::shared_lock<std::shared_mutex> lock(cacheFilePathMutex_);
64     return cacheFilePath_;
65 }
66 
GetImageCacheFilePath(const std::string & url)67 std::string ImageFileCache::GetImageCacheFilePath(const std::string& url)
68 {
69     return ConstructCacheFilePath(std::to_string(std::hash<std::string> {}(url)));
70 }
71 
ConstructCacheFilePath(const std::string & fileName)72 std::string ImageFileCache::ConstructCacheFilePath(const std::string& fileName)
73 {
74     std::shared_lock<std::shared_mutex> lock(cacheFilePathMutex_);
75 #if !defined(PREVIEW)
76     return cacheFilePath_ + SLASH + fileName;
77 #elif defined(MAC_PLATFORM) || defined(LINUX_PLATFORM)
78 
79     return "/tmp/" + fileName;
80 #elif defined(WINDOWS_PLATFORM)
81     char* pathvar = getenv("TEMP");
82     if (!pathvar) {
83         return std::string("C:\\Windows\\Temp") + BACKSLASH + fileName;
84     }
85     return std::string(pathvar) + BACKSLASH + fileName;
86 #endif
87 }
88 
GetImageCacheKey(const std::string & fileName)89 std::string ImageFileCache::GetImageCacheKey(const std::string& fileName)
90 {
91     size_t suffixStartAt = fileName.find_last_of(".");
92     return suffixStartAt == std::string::npos ? fileName : fileName.substr(0, suffixStartAt);
93 }
94 
SetCacheFileLimit(size_t cacheFileLimit)95 void ImageFileCache::SetCacheFileLimit(size_t cacheFileLimit)
96 {
97     TAG_LOGI(AceLogTag::ACE_IMAGE, "Set file cache limit size : %{public}d", static_cast<int32_t>(cacheFileLimit));
98     fileLimit_ = cacheFileLimit;
99 }
100 
SetClearCacheFileRatio(float clearRatio)101 void ImageFileCache::SetClearCacheFileRatio(float clearRatio)
102 {
103     // clearRatio must in (0, 1].
104     if (clearRatio < 0) {
105         clearRatio = 0.1f;
106     } else if (clearRatio > 1) {
107         clearRatio = 1.0f;
108     }
109     clearCacheFileRatio_ = clearRatio;
110 }
111 
GetDataFromCacheFile(const std::string & url,const std::string & suffix)112 RefPtr<NG::ImageData> ImageFileCache::GetDataFromCacheFile(const std::string& url, const std::string& suffix)
113 {
114     std::lock_guard<std::mutex> lock(cacheFileInfoMutex_);
115     auto filePath = GetCacheFilePathInner(url, suffix);
116     if (filePath == "") {
117         return nullptr;
118     }
119     auto cacheFileLoader = AceType::MakeRefPtr<FileImageLoader>();
120     auto rsData = cacheFileLoader->LoadImageData(ImageSourceInfo(std::string("file:/").append(filePath)));
121 #ifndef USE_ROSEN_DRAWING
122     return NG::ImageData::MakeFromDataWrapper(&rsData);
123 #else
124     return AceType::MakeRefPtr<NG::DrawingImageData>(rsData);
125 #endif
126 }
127 
SaveCacheInner(const std::string & cacheKey,const std::string & suffix,size_t cacheSize,std::vector<std::string> & removeVector)128 void ImageFileCache::SaveCacheInner(const std::string& cacheKey, const std::string& suffix, size_t cacheSize,
129     std::vector<std::string>& removeVector)
130 {
131     auto cacheFileName = cacheKey + suffix;
132     auto iter = fileNameToFileInfoPos_.find(cacheKey);
133     auto cacheTime = time(nullptr);
134     if (iter != fileNameToFileInfoPos_.end()) {
135         auto infoIter = iter->second;
136         cacheFileInfo_.splice(cacheFileInfo_.begin(), cacheFileInfo_, infoIter);
137         cacheFileSize_ += cacheSize - infoIter->fileSize;
138         removeVector.push_back(ConstructCacheFilePath(infoIter->fileName));
139 
140         infoIter->fileName = cacheFileName;
141         infoIter->fileSize = cacheSize;
142         infoIter->accessTime = cacheTime;
143         infoIter->accessCount = suffix == ASTC_SUFFIX ? convertAstcThreshold_ : 1;
144     } else {
145         cacheFileInfo_.emplace_front(cacheFileName, cacheSize, cacheTime,
146             suffix == ASTC_SUFFIX ? convertAstcThreshold_ : 1);
147         fileNameToFileInfoPos_[cacheKey] = cacheFileInfo_.begin();
148         cacheFileSize_ += cacheSize;
149     }
150     // check if cache files too big.
151     if (cacheFileSize_ > static_cast<int32_t>(fileLimit_)) {
152         auto removeSizeTarget = static_cast<int32_t>(fileLimit_ * clearCacheFileRatio_);
153         int32_t removeSize = 0;
154         auto iter = cacheFileInfo_.rbegin();
155         while (removeSize < removeSizeTarget && iter != cacheFileInfo_.rend()) {
156             removeSize += static_cast<int32_t>(iter->fileSize);
157             removeVector.push_back(ConstructCacheFilePath(iter->fileName));
158             fileNameToFileInfoPos_.erase(GetImageCacheKey(iter->fileName));
159             iter++;
160         }
161         cacheFileInfo_.erase(iter.base(), cacheFileInfo_.end());
162         cacheFileSize_ -= static_cast<int32_t>(removeSize);
163     }
164 }
165 
WriteCacheFile(const std::string & url,const void * const data,size_t size,const std::string & suffix)166 void ImageFileCache::WriteCacheFile(
167     const std::string& url, const void* const data, size_t size, const std::string& suffix)
168 {
169     if (size > fileLimit_) {
170         TAG_LOGW(AceLogTag::ACE_IMAGE, "file size is %{public}d, greater than limit %{public}d, cannot cache",
171             static_cast<int32_t>(size), static_cast<int32_t>(fileLimit_));
172         return;
173     }
174     auto fileCacheKey = std::to_string(std::hash<std::string> {}(url));
175     {
176         std::scoped_lock<std::mutex> lock(cacheFileInfoMutex_);
177         // 1. first check if file has been cached.
178         auto iter = fileNameToFileInfoPos_.find(fileCacheKey);
179         if (iter != fileNameToFileInfoPos_.end()) {
180             auto infoIter = iter->second;
181             // either suffix not specified, or fileName ends with the suffix
182             if (suffix == "" || EndsWith(infoIter->fileName, suffix)) {
183                 TAG_LOGI(AceLogTag::ACE_IMAGE, "file has been wrote %{private}s", infoIter->fileName.c_str());
184                 return;
185             }
186         }
187     }
188 
189     // 2. if not in dist, write file into disk.
190     std::string writeFilePath = ConstructCacheFilePath(fileCacheKey + suffix);
191 #ifdef WINDOWS_PLATFORM
192     std::ofstream outFile(writeFilePath, std::ios::binary);
193 #else
194     std::ofstream outFile(writeFilePath, std::fstream::out);
195 #endif
196     if (!outFile.is_open()) {
197         TAG_LOGW(AceLogTag::ACE_IMAGE, "open cache file failed, cannot write.");
198         return;
199     }
200     outFile.write(reinterpret_cast<const char*>(data), size);
201     TAG_LOGI(
202         AceLogTag::ACE_IMAGE, "write image cache: %{public}s %{private}s", url.c_str(), writeFilePath.c_str());
203 
204     std::vector<std::string> removeVector;
205     {
206         std::scoped_lock<std::mutex> lock(cacheFileInfoMutex_);
207         SaveCacheInner(fileCacheKey, suffix, size, removeVector);
208     }
209     // 3. clear files removed from cache list.
210     ClearCacheFile(removeVector);
211 }
212 
ClearCacheFile(const std::vector<std::string> & removeFiles)213 void ImageFileCache::ClearCacheFile(const std::vector<std::string>& removeFiles)
214 {
215     for (auto&& iter : removeFiles) {
216         if (remove(iter.c_str()) != 0) {
217             TAG_LOGW(AceLogTag::ACE_IMAGE, "remove file %{private}s failed.", iter.c_str());
218             continue;
219         }
220     }
221 }
222 
GetCacheFilePath(const std::string & url)223 std::string ImageFileCache::GetCacheFilePath(const std::string& url)
224 {
225     std::scoped_lock<std::mutex> lock(cacheFileInfoMutex_);
226     return GetCacheFilePathInner(url, "");
227 }
228 
GetCacheFilePathInner(const std::string & url,const std::string & suffix)229 std::string ImageFileCache::GetCacheFilePathInner(const std::string& url, const std::string& suffix)
230 {
231     auto fileCacheKey = std::to_string(std::hash<std::string> {}(url));
232     auto iter = fileNameToFileInfoPos_.find(fileCacheKey);
233     if (iter != fileNameToFileInfoPos_.end()) {
234         auto infoIter = iter->second;
235         // either suffix not specified, or fileName ends with the suffix
236         if (suffix == "" || EndsWith(infoIter->fileName, suffix)) {
237             cacheFileInfo_.splice(cacheFileInfo_.begin(), cacheFileInfo_, infoIter);
238             infoIter->accessTime = time(nullptr);
239             infoIter->accessCount++;
240             return ConstructCacheFilePath(infoIter->fileName);
241         }
242     }
243     return "";
244 }
245 
SetCacheFileInfo()246 void ImageFileCache::SetCacheFileInfo()
247 {
248     std::lock_guard<std::mutex> lock(cacheFileInfoMutex_);
249     // Set cache file information only once.
250     if (hasSetCacheFileInfo_) {
251         return;
252     }
253     std::string cacheFilePath = GetImageCacheFilePath();
254     std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(cacheFilePath.c_str()), closedir);
255     if (dir == nullptr) {
256         TAG_LOGW(AceLogTag::ACE_IMAGE, "cache file path wrong! maybe it is not set.");
257         return;
258     }
259     int64_t cacheFileSize = 0;
260     dirent* filePtr = readdir(dir.get());
261     while (filePtr != nullptr) {
262         // skip . or ..
263         if (filePtr->d_name[0] != '.') {
264             std::string filePath = cacheFilePath + SLASH + std::string(filePtr->d_name);
265             struct stat fileStatus;
266             if (stat(filePath.c_str(), &fileStatus) == -1) {
267                 filePtr = readdir(dir.get());
268                 continue;
269             }
270             cacheFileInfo_.emplace_front(filePtr->d_name, fileStatus.st_size, fileStatus.st_atime,
271                 IsAstcFile(filePtr->d_name) ? convertAstcThreshold_ : 1);
272             std::string fileCacheKey = GetImageCacheKey(std::string(filePtr->d_name));
273             fileNameToFileInfoPos_[fileCacheKey] = cacheFileInfo_.begin();
274             cacheFileSize += static_cast<int64_t>(fileStatus.st_size);
275         }
276         filePtr = readdir(dir.get());
277     }
278     cacheFileInfo_.sort();
279     cacheFileSize_ = cacheFileSize;
280     hasSetCacheFileInfo_ = true;
281 }
282 
DumpCacheInfo()283 void ImageFileCache::DumpCacheInfo()
284 {
285     auto cacheFileInfoSize = cacheFileInfo_.size();
286     auto fileLimit = static_cast<int32_t>(fileLimit_);
287     auto cacheFileSize = static_cast<int32_t>(cacheFileSize_);
288     DumpLog::GetInstance().Print("------------ImageCacheInfo------------");
289     DumpLog::GetInstance().Print("User set ImageFileCacheSize : " + std::to_string(fileLimit) + "(B)");
290     DumpLog::GetInstance().Print("cacheFileSize: " + std::to_string(cacheFileSize) + "(B)");
291     if (cacheFileInfoSize == 0) {
292         return;
293     }
294     auto totalCount = 0;
295     for (const auto& item : cacheFileInfo_) {
296         auto filePath = ConstructCacheFilePath(item.fileName);
297         auto fileSize = item.fileSize;
298         totalCount += fileSize;
299         DumpLog::GetInstance().Print(
300             "fileCache Obj of filePath: " + filePath + ", fileSize" + std::to_string(fileSize) + "(B)");
301     }
302     DumpLog::GetInstance().Print("FileCache total size: " + std::to_string(totalCount) + "(B)");
303 }
304 } // namespace OHOS::Ace
305