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