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