• 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 <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