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