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 <sys/stat.h>
19
20 #include "base/image/image_packer.h"
21 #include "base/image/image_source.h"
22 #include "base/log/dump_log.h"
23 #include "base/thread/background_task_executor.h"
24 #include "base/utils/utils.h"
25 #include "core/image/image_loader.h"
26
27 #ifdef USE_ROSEN_DRAWING
28 #include "core/components_ng/image_provider/adapter/rosen/drawing_image_data.h"
29 #endif
30
31 namespace OHOS::Ace {
32 ImageFileCache::ImageFileCache() = default;
33 ImageFileCache::~ImageFileCache() = default;
34
35 namespace {
36 const std::string ASTC_SUFFIX = ".astc";
37 const std::string CONVERT_ASTC_FORMAT = "image/astc/4*4";
38 const std::string SLASH = "/";
39 const std::string BACKSLASH = "\\";
40 const mode_t CHOWN_RW_UG = 0660;
41 const std::string SVG_FORMAT = "image/svg+xml";
42
EndsWith(const std::string & str,const std::string & substr)43 bool EndsWith(const std::string& str, const std::string& substr)
44 {
45 return str.rfind(substr) == (str.length() - substr.length());
46 }
47
IsAstcFile(const char fileName[])48 bool IsAstcFile(const char fileName[])
49 {
50 auto fileNameStr = std::string(fileName);
51 return (fileNameStr.length() >= ASTC_SUFFIX.length()) && EndsWith(fileNameStr, ASTC_SUFFIX);
52 }
53
IsFileExists(const char * path)54 bool IsFileExists(const char* path)
55 {
56 FILE *file = fopen(path, "r");
57 if (file) {
58 fclose(file);
59 return true;
60 }
61 return false;
62 }
63
64 } // namespace
65
SetImageCacheFilePath(const std::string & cacheFilePath)66 void ImageFileCache::SetImageCacheFilePath(const std::string& cacheFilePath)
67 {
68 std::unique_lock<std::shared_mutex> lock(cacheFilePathMutex_);
69 if (cacheFilePath_.empty()) {
70 cacheFilePath_ = cacheFilePath;
71 }
72 }
73
GetImageCacheFilePath()74 std::string ImageFileCache::GetImageCacheFilePath()
75 {
76 std::shared_lock<std::shared_mutex> lock(cacheFilePathMutex_);
77 return cacheFilePath_;
78 }
79
GetImageCacheFilePath(const std::string & url)80 std::string ImageFileCache::GetImageCacheFilePath(const std::string& url)
81 {
82 return ConstructCacheFilePath(std::to_string(std::hash<std::string> {}(url)));
83 }
84
ConstructCacheFilePath(const std::string & fileName)85 std::string ImageFileCache::ConstructCacheFilePath(const std::string& fileName)
86 {
87 std::shared_lock<std::shared_mutex> lock(cacheFilePathMutex_);
88 #if !defined(PREVIEW)
89 return cacheFilePath_ + SLASH + fileName;
90 #elif defined(MAC_PLATFORM) || defined(LINUX_PLATFORM)
91
92 return "/tmp/" + fileName;
93 #elif defined(WINDOWS_PLATFORM)
94 char* pathvar = getenv("TEMP");
95 if (!pathvar) {
96 return std::string("C:\\Windows\\Temp") + BACKSLASH + fileName;
97 }
98 return std::string(pathvar) + BACKSLASH + fileName;
99 #endif
100 }
101
WriteFile(const std::string & url,const void * const data,size_t size,const std::string & fileCacheKey,const std::string & suffix)102 bool ImageFileCache::WriteFile(const std::string& url, const void* const data, size_t size,
103 const std::string& fileCacheKey, const std::string& suffix)
104 {
105 std::string writeFilePath = ConstructCacheFilePath(fileCacheKey + suffix);
106 #ifdef WINDOWS_PLATFORM
107 std::ofstream outFile(writeFilePath, std::ios::binary);
108 #else
109 std::ofstream outFile(writeFilePath, std::fstream::out);
110 #endif
111 if (!outFile.is_open()) {
112 TAG_LOGW(AceLogTag::ACE_IMAGE, "open cache file failed, cannot write.");
113 return false;
114 }
115 outFile.write(reinterpret_cast<const char*>(data), size);
116 TAG_LOGI(
117 AceLogTag::ACE_IMAGE, "write image cache: %{private}s %{private}s", url.c_str(), writeFilePath.c_str());
118 #ifndef WINDOWS_PLATFORM
119 if (chmod(writeFilePath.c_str(), CHOWN_RW_UG) != 0) {
120 TAG_LOGW(AceLogTag::ACE_IMAGE, "write image cache chmod failed: %{private}s %{private}s",
121 url.c_str(), writeFilePath.c_str());
122 }
123 #endif
124 return true;
125 }
126
GetImageCacheKey(const std::string & fileName)127 std::string ImageFileCache::GetImageCacheKey(const std::string& fileName)
128 {
129 size_t suffixStartAt = fileName.find_last_of(".");
130 return suffixStartAt == std::string::npos ? fileName : fileName.substr(0, suffixStartAt);
131 }
132
SetCacheFileLimit(size_t cacheFileLimit)133 void ImageFileCache::SetCacheFileLimit(size_t cacheFileLimit)
134 {
135 TAG_LOGI(AceLogTag::ACE_IMAGE, "User Set file cache limit size : %{public}d", static_cast<int32_t>(cacheFileLimit));
136 fileLimit_ = cacheFileLimit;
137 }
138
SetClearCacheFileRatio(float clearRatio)139 void ImageFileCache::SetClearCacheFileRatio(float clearRatio)
140 {
141 // clearRatio must in (0, 1].
142 if (clearRatio < 0) {
143 clearRatio = 0.1f;
144 } else if (clearRatio > 1) {
145 clearRatio = 1.0f;
146 }
147 clearCacheFileRatio_ = clearRatio;
148 }
149
GetDataFromCacheFile(const std::string & url,const std::string & suffix)150 RefPtr<NG::ImageData> ImageFileCache::GetDataFromCacheFile(const std::string& url, const std::string& suffix)
151 {
152 std::lock_guard<std::mutex> lock(cacheFileInfoMutex_);
153 auto filePath = GetCacheFilePathInner(url, suffix);
154 if (filePath == "") {
155 return nullptr;
156 }
157 auto cacheFileLoader = AceType::MakeRefPtr<FileImageLoader>();
158 auto rsData = cacheFileLoader->LoadImageData(ImageSourceInfo(std::string("file:/").append(filePath)));
159 #ifndef USE_ROSEN_DRAWING
160 return NG::ImageData::MakeFromDataWrapper(&rsData);
161 #else
162 return AceType::MakeRefPtr<NG::DrawingImageData>(rsData);
163 #endif
164 }
165
SaveCacheInner(const std::string & cacheKey,const std::string & suffix,size_t cacheSize,std::vector<std::string> & removeVector)166 void ImageFileCache::SaveCacheInner(const std::string& cacheKey, const std::string& suffix, size_t cacheSize,
167 std::vector<std::string>& removeVector)
168 {
169 auto cacheFileName = cacheKey + suffix;
170 auto iter = fileNameToFileInfoPos_.find(cacheKey);
171 auto cacheTime = time(nullptr);
172 auto convertAstcThreshold = SystemProperties::GetImageFileCacheConvertAstcThreshold();
173 if (iter != fileNameToFileInfoPos_.end()) {
174 // update cache file info
175 auto infoIter = iter->second;
176 cacheFileInfo_.splice(cacheFileInfo_.begin(), cacheFileInfo_, infoIter);
177 cacheFileSize_ = cacheFileSize_ + cacheSize - infoIter->fileSize;
178
179 infoIter->fileName = cacheFileName;
180 infoIter->fileSize = cacheSize;
181 infoIter->accessTime = cacheTime;
182 infoIter->accessCount = static_cast<uint32_t>(suffix == ASTC_SUFFIX ? convertAstcThreshold : 1);
183 } else {
184 cacheFileInfo_.emplace_front(cacheFileName, cacheSize, cacheTime,
185 suffix == ASTC_SUFFIX ? convertAstcThreshold : 1);
186 fileNameToFileInfoPos_[cacheKey] = cacheFileInfo_.begin();
187 cacheFileSize_ += cacheSize;
188 }
189 // check if cache files too big.
190 if (cacheFileSize_ > fileLimit_) {
191 auto removeSizeTarget = fileLimit_ * clearCacheFileRatio_;
192 size_t removeSize = 0;
193 auto iter = cacheFileInfo_.rbegin();
194 while (removeSize < removeSizeTarget && iter != cacheFileInfo_.rend()) {
195 removeSize += iter->fileSize;
196 removeVector.push_back(ConstructCacheFilePath(iter->fileName));
197 fileNameToFileInfoPos_.erase(GetImageCacheKey(iter->fileName));
198 iter++;
199 }
200 cacheFileInfo_.erase(iter.base(), cacheFileInfo_.end());
201 cacheFileSize_ -= removeSize;
202 }
203 }
204
EraseCacheFile(const std::string & url)205 void ImageFileCache::EraseCacheFile(const std::string &url)
206 {
207 auto fileCacheKey = std::to_string(std::hash<std::string> {}(url));
208 {
209 std::scoped_lock<std::mutex> lock(cacheFileInfoMutex_);
210 // 1. first check if file has been cached.
211 auto iter = fileNameToFileInfoPos_.find(fileCacheKey);
212 if (iter != fileNameToFileInfoPos_.end()) {
213 auto infoIter = iter->second;
214 auto removeFile = ConstructCacheFilePath(infoIter->fileName);
215 if (remove(removeFile.c_str()) != 0) {
216 TAG_LOGW(AceLogTag::ACE_IMAGE, "remove file %{private}s failed.", removeFile.c_str());
217 return;
218 }
219 cacheFileSize_ -= infoIter->fileSize;
220 cacheFileInfo_.erase(infoIter);
221 fileNameToFileInfoPos_.erase(fileCacheKey);
222 }
223 }
224 }
225
WriteCacheFile(const std::string & url,const void * data,size_t size,const std::string & suffix)226 void ImageFileCache::WriteCacheFile(const std::string& url, const void* data, size_t size, const std::string& suffix)
227 {
228 if (size > fileLimit_) {
229 TAG_LOGW(AceLogTag::ACE_IMAGE, "file size is %{public}d, greater than limit %{public}d, cannot cache",
230 static_cast<int32_t>(size), static_cast<int32_t>(fileLimit_));
231 return;
232 }
233 auto fileCacheKey = std::to_string(std::hash<std::string> {}(url));
234 auto writeFilePath = ConstructCacheFilePath(fileCacheKey + suffix);
235 {
236 std::scoped_lock<std::mutex> lock(cacheFileInfoMutex_);
237 // 1. first check if file has been cached.
238 auto iter = fileNameToFileInfoPos_.find(fileCacheKey);
239 if (iter != fileNameToFileInfoPos_.end()) {
240 auto infoIter = iter->second;
241 // either suffix not specified, or fileName ends with the suffix
242 if ((suffix.empty() || EndsWith(infoIter->fileName, suffix)) && IsFileExists(writeFilePath.c_str())) {
243 TAG_LOGI(AceLogTag::ACE_IMAGE, "file has been wrote %{private}s", infoIter->fileName.c_str());
244 return;
245 }
246 }
247 }
248
249 #ifndef ACE_UNITTEST
250 // 2. if not in dist, write file into disk.
251 if (!WriteFile(url, data, size, fileCacheKey, suffix)) {
252 return;
253 }
254 #endif
255
256 std::vector<std::string> removeVector;
257 {
258 std::scoped_lock<std::mutex> lock(cacheFileInfoMutex_);
259 SaveCacheInner(fileCacheKey, suffix, size, removeVector);
260 }
261 // 3. clear files removed from cache list.
262 ClearCacheFile(removeVector);
263 }
264
ConvertToAstcAndWriteToFile(const std::string & fileCacheKey,const std::string & filePath,const std::string & url)265 void ImageFileCache::ConvertToAstcAndWriteToFile(const std::string& fileCacheKey, const std::string& filePath,
266 const std::string& url)
267 {
268 ACE_FUNCTION_TRACE();
269 RefPtr<ImageSource> imageSource = ImageSource::Create(filePath);
270 if (!imageSource || imageSource->GetFrameCount() != 1) {
271 TAG_LOGI(AceLogTag::ACE_IMAGE, "Image frame count is not 1, will not convert to astc. %{public}s",
272 fileCacheKey.c_str());
273 return;
274 }
275 if (imageSource->GetEncodedFormat() == SVG_FORMAT) {
276 TAG_LOGI(AceLogTag::ACE_IMAGE, "Image is svg, will not convert to astc. %{public}s",
277 fileCacheKey.c_str());
278 return;
279 }
280
281 RefPtr<ImagePacker> imagePacker = ImagePacker::Create();
282 PackOption option;
283 option.format = CONVERT_ASTC_FORMAT;
284 auto pixelMap = imageSource->CreatePixelMap({-1, -1});
285 if (pixelMap == nullptr) {
286 TAG_LOGW(AceLogTag::ACE_IMAGE, "Get pixel map failed, will not convert to astc. %{public}s",
287 fileCacheKey.c_str());
288 return;
289 }
290
291 auto astcFileName = fileCacheKey + ASTC_SUFFIX;
292 auto astcFilePath = ConstructCacheFilePath(astcFileName);
293 imagePacker->StartPacking(astcFilePath, option);
294 imagePacker->AddImage(*pixelMap);
295 int64_t packedSize = 0;
296 if (imagePacker->FinalizePacking(packedSize)) {
297 TAG_LOGW(AceLogTag::ACE_IMAGE, "convert to astc failed. %{public}s", fileCacheKey.c_str());
298 return;
299 }
300 #if !defined(WINDOWS_PLATFORM) && !defined(ACE_UNITTEST)
301 if (chmod(astcFilePath.c_str(), CHOWN_RW_UG) != 0) {
302 TAG_LOGW(AceLogTag::ACE_IMAGE, "convert to astc chmod failed: %{public}s %{private}s",
303 url.c_str(), astcFilePath.c_str());
304 }
305 #endif
306
307 std::vector<std::string> removeVector;
308 {
309 std::scoped_lock<std::mutex> lock(cacheFileInfoMutex_);
310 removeVector.push_back(filePath);
311
312 auto infoIter = fileNameToFileInfoPos_[fileCacheKey];
313 cacheFileSize_ = cacheFileSize_ + static_cast<size_t>(packedSize) - infoIter->fileSize;
314 infoIter->fileName = astcFileName;
315 infoIter->fileSize = static_cast<uint64_t>(packedSize);
316 }
317 // remove the old file before convert
318 ClearCacheFile(removeVector);
319 TAG_LOGI(AceLogTag::ACE_IMAGE, "write image astc cache: %{public}s %{private}s", url.c_str(), astcFilePath.c_str());
320 }
321
ClearCacheFile(const std::vector<std::string> & removeFiles)322 void ImageFileCache::ClearCacheFile(const std::vector<std::string>& removeFiles)
323 {
324 #ifndef ACE_UNITTEST
325 for (auto&& iter : removeFiles) {
326 if (remove(iter.c_str()) != 0) {
327 TAG_LOGW(AceLogTag::ACE_IMAGE, "remove file %{private}s failed.", iter.c_str());
328 continue;
329 }
330 }
331 #endif
332 }
333
GetCacheFilePath(const std::string & url)334 std::string ImageFileCache::GetCacheFilePath(const std::string& url)
335 {
336 std::scoped_lock<std::mutex> lock(cacheFileInfoMutex_);
337 return GetCacheFilePathInner(url, "");
338 }
339
GetCacheFilePathInner(const std::string & url,const std::string & suffix)340 std::string ImageFileCache::GetCacheFilePathInner(const std::string& url, const std::string& suffix)
341 {
342 auto fileCacheKey = std::to_string(std::hash<std::string> {}(url));
343 auto iter = fileNameToFileInfoPos_.find(fileCacheKey);
344 // either suffix not specified, or fileName ends with the suffix
345 if (iter != fileNameToFileInfoPos_.end() && (suffix == "" || EndsWith(iter->second->fileName, suffix))) {
346 auto infoIter = iter->second;
347 cacheFileInfo_.splice(cacheFileInfo_.begin(), cacheFileInfo_, infoIter);
348 infoIter->accessTime = time(nullptr);
349 infoIter->accessCount++;
350 auto filePath = ConstructCacheFilePath(infoIter->fileName);
351 if (SystemProperties::IsImageFileCacheConvertAstcEnabled() &&
352 infoIter->accessCount == static_cast<uint32_t>(SystemProperties::GetImageFileCacheConvertAstcThreshold())) {
353 BackgroundTaskExecutor::GetInstance().PostTask(
354 [this, fileCacheKey, filePath, url] () {
355 ConvertToAstcAndWriteToFile(fileCacheKey, filePath, url);
356 },
357 BgTaskPriority::LOW);
358 }
359 return filePath;
360 }
361 return "";
362 }
363
SetCacheFileInfo()364 void ImageFileCache::SetCacheFileInfo()
365 {
366 std::lock_guard<std::mutex> lock(cacheFileInfoMutex_);
367 // Set cache file information only once.
368 if (hasSetCacheFileInfo_) {
369 return;
370 }
371 std::string cacheFilePath = GetImageCacheFilePath();
372 std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(cacheFilePath.c_str()), closedir);
373 if (dir == nullptr) {
374 TAG_LOGW(AceLogTag::ACE_IMAGE, "cache file path wrong! maybe it is not set.");
375 return;
376 }
377 size_t cacheFileSize = 0;
378 dirent* filePtr = readdir(dir.get());
379 while (filePtr != nullptr) {
380 // skip . or ..
381 if (filePtr->d_name[0] != '.') {
382 std::string filePath = cacheFilePath + SLASH + std::string(filePtr->d_name);
383 struct stat fileStatus;
384 if (stat(filePath.c_str(), &fileStatus) == -1) {
385 filePtr = readdir(dir.get());
386 continue;
387 }
388 cacheFileInfo_.emplace_front(filePtr->d_name, fileStatus.st_size, fileStatus.st_atime,
389 IsAstcFile(filePtr->d_name) ? SystemProperties::GetImageFileCacheConvertAstcThreshold() : 1);
390 std::string fileCacheKey = GetImageCacheKey(std::string(filePtr->d_name));
391 fileNameToFileInfoPos_[fileCacheKey] = cacheFileInfo_.begin();
392 cacheFileSize += static_cast<size_t>(fileStatus.st_size);
393 }
394 filePtr = readdir(dir.get());
395 }
396 cacheFileInfo_.sort();
397 cacheFileSize_ = cacheFileSize;
398 hasSetCacheFileInfo_ = true;
399 }
400
DumpCacheInfo()401 void ImageFileCache::DumpCacheInfo()
402 {
403 auto cacheFileInfoSize = cacheFileInfo_.size();
404 auto fileLimit = static_cast<int32_t>(fileLimit_);
405 auto cacheFileSize = static_cast<int32_t>(cacheFileSize_);
406 DumpLog::GetInstance().Print("------------ImageCacheInfo------------");
407 DumpLog::GetInstance().Print("User set ImageFileCacheSize : " + std::to_string(fileLimit) + "(B)");
408 DumpLog::GetInstance().Print("cacheFileSize: " + std::to_string(cacheFileSize) + "(B)");
409 if (cacheFileInfoSize == 0) {
410 return;
411 }
412 size_t totalCount = 0;
413 for (const auto& item : cacheFileInfo_) {
414 auto filePath = ConstructCacheFilePath(item.fileName);
415 auto fileSize = item.fileSize;
416 totalCount += fileSize;
417 DumpLog::GetInstance().Print(
418 "fileCache Obj of filePath: " + filePath + ", fileSize: " + std::to_string(fileSize) + "(B)" +
419 ", accessCount: " + std::to_string(item.accessCount));
420 }
421 DumpLog::GetInstance().Print("FileCache total size: " + std::to_string(totalCount) + "(B)");
422 }
423 } // namespace OHOS::Ace
424