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