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