/* * Copyright (C) 2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "thumbnail_manager.h" #include #include #include #include #include #include "ashmem.h" #include "image_source.h" #include "image_type.h" #include "js_native_api.h" #include "media_file_uri.h" #include "medialibrary_errno.h" #include "medialibrary_napi_log.h" #include "medialibrary_napi_utils.h" #include "medialibrary_tracer.h" #include "pixel_map.h" #include "pixel_map_napi.h" #include "post_proc.h" #include "securec.h" #include "string_ex.h" #include "thumbnail_const.h" #include "unique_fd.h" #include "userfile_manager_types.h" #include "uv.h" #include "userfile_client.h" #ifdef IMAGE_PURGEABLE_PIXELMAP #include "purgeable_pixelmap_builder.h" #endif using namespace std; const int UUID_STR_LENGTH = 37; namespace OHOS { namespace Media { shared_ptr ThumbnailManager::instance_ = nullptr; mutex ThumbnailManager::mutex_; bool ThumbnailManager::init_ = false; static constexpr int32_t DEFAULT_FD = -1; ThumbnailRequest::ThumbnailRequest(const RequestPhotoParams ¶ms, napi_env env, napi_ref callback) : callback_(env, callback), requestPhotoType(params.type), uri_(params.uri), path_(params.path), requestSize_(params.size) { } ThumbnailRequest::~ThumbnailRequest() { } void ThumbnailRequest::ReleaseCallbackRef() { std::lock_guard lock(mutex_); if (callback_.callBackRef_) { napi_delete_reference(callback_.env_, callback_.callBackRef_); callback_.callBackRef_ = nullptr; } } bool ThumbnailRequest::UpdateStatus(ThumbnailStatus status) { std::lock_guard lock(mutex_); if (status <= status_) { return false; } status_ = status; return true; } ThumbnailStatus ThumbnailRequest::GetStatus() { std::lock_guard lock(mutex_); return status_; } bool ThumbnailRequest::NeedContinue() { return GetStatus() < ThumbnailStatus::THUMB_REMOVE; } static bool IsPhotoSizeThumb(const Size &size) { return ((size.width >= DEFAULT_THUMB_SIZE || size.height >= DEFAULT_THUMB_SIZE) || (size.width == DEFAULT_MTH_SIZE || size.height == DEFAULT_MTH_SIZE)); } static bool NeedFastThumb(const Size &size, RequestPhotoType type) { return IsPhotoSizeThumb(size) && (type != RequestPhotoType::REQUEST_QUALITY_THUMBNAIL); } static bool NeedQualityThumb(const Size &size, RequestPhotoType type) { return IsPhotoSizeThumb(size) && (type != RequestPhotoType::REQUEST_FAST_THUMBNAIL); } MMapFdPtr::MMapFdPtr(int32_t fd, bool isNeedRelease) { if (fd < 0) { NAPI_ERR_LOG("Fd is invalid: %{public}d", fd); return; } struct stat st; if (fstat(fd, &st) == -1) { NAPI_ERR_LOG("fstat error, errno:%{public}d", errno); return; } size_ = st.st_size; // mmap ptr from fd fdPtr_ = mmap(nullptr, size_, PROT_READ, MAP_SHARED, fd, 0); if (fdPtr_ == MAP_FAILED || fdPtr_ == nullptr) { NAPI_ERR_LOG("mmap uniqueFd failed, errno = %{public}d", errno); return; } isValid_ = true; isNeedRelease_ = isNeedRelease; } MMapFdPtr::~MMapFdPtr() { // munmap ptr from fd if (isNeedRelease_) { munmap(fdPtr_, size_); } } void* MMapFdPtr::GetFdPtr() { return fdPtr_; } off_t MMapFdPtr::GetFdSize() { return size_; } bool MMapFdPtr::IsValid() { return isValid_; } static string GenerateRequestId() { uuid_t uuid; uuid_generate(uuid); char str[UUID_STR_LENGTH] = {}; uuid_unparse(uuid, str); return str; } shared_ptr ThumbnailManager::GetInstance() { if (instance_ == nullptr) { lock_guard lock(mutex_); if (instance_ == nullptr) { instance_ = shared_ptr(new ThumbnailManager()); } } return instance_; } void ThumbnailManager::Init() { std::lock_guard lock(mutex_); if (init_) { return; } init_ = true; isThreadRunning_ = true; for (auto i = 0; i < THREAD_NUM; i++) { threads_.emplace_back( std::thread([this, num = i]() { this->ImageWorker(num); }) ); threads_[i].detach(); } return; } string ThumbnailManager::AddPhotoRequest(const RequestPhotoParams ¶ms, napi_env env, napi_ref callback) { shared_ptr request = make_shared(params, env, callback); string requestId = GenerateRequestId(); request->SetUUID(requestId); if (!thumbRequest_.Insert(requestId, request)) { return ""; } // judge from request option if (NeedFastThumb(params.size, params.type)) { AddFastPhotoRequest(request); } else { AddQualityPhotoRequest(request); } return requestId; } void ThumbnailManager::RemovePhotoRequest(const string &requestId) { RequestSharedPtr ptr; if (thumbRequest_.Find(requestId, ptr)) { if (ptr == nullptr) { return; } // do not need delete from queue, just update remove status. ptr->UpdateStatus(ThumbnailStatus::THUMB_REMOVE); ptr->ReleaseCallbackRef(); } thumbRequest_.Erase(requestId); } ThumbnailManager::~ThumbnailManager() { isThreadRunning_ = false; queueCv_.notify_all(); for (auto &thread : threads_) { if (thread.joinable()) { thread.join(); } } } void SetThreadName(const string &threadName, int num) { string name = threadName; name.append(to_string(num)); pthread_setname_np(pthread_self(), name.c_str()); } void ThumbnailManager::AddFastPhotoRequest(const RequestSharedPtr &request) { request->UpdateStatus(ThumbnailStatus::THUMB_FAST); fastQueue_.Push(request); queueCv_.notify_one(); } void ThumbnailManager::AddQualityPhotoRequest(const RequestSharedPtr &request) { request->UpdateStatus(ThumbnailStatus::THUMB_QUALITY); qualityQueue_.Push(request); queueCv_.notify_one(); } static bool GetFastThumbNewSize(const Size &size, Size &newSize) { // if thumbnail size is YEAR SIZE, do not need to request fast thumb // if thumbnail size is MTH SIZE, return YEAR SIZE // if thumbnail size is THUMB SIZE, return MTH SIZE // else return THUMB SIZE if (size.width == DEFAULT_YEAR_SIZE && size.height == DEFAULT_YEAR_SIZE) { newSize.height = DEFAULT_YEAR_SIZE; newSize.width = DEFAULT_YEAR_SIZE; return false; } else if (size.width == DEFAULT_MTH_SIZE && size.height == DEFAULT_MTH_SIZE) { newSize.height = DEFAULT_YEAR_SIZE; newSize.width = DEFAULT_YEAR_SIZE; return true; } else if (size.width <= DEFAULT_THUMB_SIZE && size.height <= DEFAULT_THUMB_SIZE) { newSize.height = DEFAULT_MTH_SIZE; newSize.width = DEFAULT_MTH_SIZE; return true; } else { newSize.height = DEFAULT_THUMB_SIZE; newSize.width = DEFAULT_THUMB_SIZE; return true; } } static int OpenThumbnail(const string &path, ThumbnailType type) { if (!path.empty()) { string sandboxPath = GetSandboxPath(path, type); int fd = -1; if (!sandboxPath.empty()) { fd = open(sandboxPath.c_str(), O_RDONLY); } if (fd > 0) { return fd; } } return E_ERR; } static bool IfSizeEqualsRatio(const Size &imageSize, const Size &targetSize) { if (imageSize.height <= 0 || targetSize.height <= 0) { return false; } float imageSizeScale = static_cast(imageSize.width) / static_cast(imageSize.height); float targetSizeScale = static_cast(targetSize.width) / static_cast(targetSize.height); if (imageSizeScale - targetSizeScale > FLOAT_EPSILON || targetSizeScale - imageSizeScale > FLOAT_EPSILON) { return false; } else { return true; } } static PixelMapPtr CreateThumbnailByAshmem(UniqueFd &uniqueFd, const Size &size) { MediaLibraryTracer tracer; tracer.Start("CreateThumbnailByAshmem"); Media::InitializationOptions option = { .size = size, }; PixelMapPtr pixel = Media::PixelMap::Create(option); if (pixel == nullptr) { NAPI_ERR_LOG("Can not create pixel"); return nullptr; } UniqueFd dupFd = UniqueFd(dup(uniqueFd.Get())); MMapFdPtr mmapFd(dupFd.Get(), false); if (!mmapFd.IsValid()) { NAPI_ERR_LOG("Can not mmap by fd"); return nullptr; } auto memSize = static_cast(mmapFd.GetFdSize()); void* fdPtr = new int32_t(); *static_cast(fdPtr) = dupFd.Release(); pixel->SetPixelsAddr(mmapFd.GetFdPtr(), fdPtr, memSize, Media::AllocatorType::SHARE_MEM_ALLOC, nullptr); return pixel; } static PixelMapPtr DecodeThumbnail(const UniqueFd &uniqueFd, const Size &size) { MediaLibraryTracer tracer; tracer.Start("ImageSource::CreateImageSource"); SourceOptions opts; uint32_t err = 0; unique_ptr imageSource = ImageSource::CreateImageSource(uniqueFd.Get(), opts, err); if (imageSource == nullptr) { NAPI_ERR_LOG("CreateImageSource err %{public}d", err); return nullptr; } ImageInfo imageInfo; err = imageSource->GetImageInfo(0, imageInfo); if (err != E_OK) { NAPI_ERR_LOG("GetImageInfo err %{public}d", err); return nullptr; } bool isEqualsRatio = IfSizeEqualsRatio(imageInfo.size, size); DecodeOptions decodeOpts; decodeOpts.desiredSize = isEqualsRatio ? size : imageInfo.size; unique_ptr pixelMap = imageSource->CreatePixelMap(decodeOpts, err); if (pixelMap == nullptr) { NAPI_ERR_LOG("CreatePixelMap err %{public}d", err); return nullptr; } PostProc postProc; if (size.width != DEFAULT_ORIGINAL && !isEqualsRatio && !postProc.CenterScale(size, *pixelMap)) { NAPI_ERR_LOG("CenterScale failed, size: %{public}d * %{public}d, imageInfo size: %{public}d * %{public}d", size.width, size.height, imageInfo.size.width, imageInfo.size.height); return nullptr; } // Make the ashmem of pixelmap to be purgeable after the operation on ashmem. // And then make the pixelmap subject to PurgeableManager's control. #ifdef IMAGE_PURGEABLE_PIXELMAP PurgeableBuilder::MakePixelMapToBePurgeable(pixelMap, imageSource, decodeOpts, size); #endif return pixelMap; } static int32_t GetPixelMapFromServer(const string &uriStr, const Size &size, const string &path) { string openUriStr = uriStr + "?" + MEDIA_OPERN_KEYWORD + "=" + MEDIA_DATA_DB_THUMBNAIL + "&" + MEDIA_DATA_DB_WIDTH + "=" + to_string(size.width) + "&" + MEDIA_DATA_DB_HEIGHT + "=" + to_string(size.height); if (IsAsciiString(path)) { openUriStr += "&" + THUMBNAIL_PATH + "=" + path; } Uri openUri(openUriStr); return UserFileClient::OpenFile(openUri, "R"); } unique_ptr ThumbnailManager::QueryThumbnail(const string &uriStr, const Size &size, const string &path) { MediaLibraryTracer tracer; tracer.Start("QueryThumbnail uri:" + uriStr); tracer.Start("DataShare::OpenFile"); ThumbnailType thumbType = GetThumbType(size.width, size.height); if (MediaFileUri::GetMediaTypeFromUri(uriStr) == MediaType::MEDIA_TYPE_AUDIO && (thumbType == ThumbnailType::MTH || thumbType == ThumbnailType::YEAR)) { thumbType = ThumbnailType::THUMB; } UniqueFd uniqueFd(OpenThumbnail(path, thumbType)); if (uniqueFd.Get() == E_ERR) { uniqueFd = UniqueFd(GetPixelMapFromServer(uriStr, size, path)); if (uniqueFd.Get() < 0) { NAPI_ERR_LOG("queryThumb is null, errCode is %{public}d", uniqueFd.Get()); return nullptr; } return DecodeThumbnail(uniqueFd, size); } tracer.Finish(); if (thumbType == ThumbnailType::MTH || thumbType == ThumbnailType::YEAR) { return CreateThumbnailByAshmem(uniqueFd, size); } else { return DecodeThumbnail(uniqueFd, size); } } void ThumbnailManager::DeleteRequestIdFromMap(const string &requestId) { thumbRequest_.Erase(requestId); } bool ThumbnailManager::RequestFastImage(const RequestSharedPtr &request) { MediaLibraryTracer tracer; tracer.Start("ThumbnailManager::RequestFastImage"); request->SetFd(DEFAULT_FD); Size fastSize; if (!GetFastThumbNewSize(request->GetRequestSize(), fastSize)) { return false; } UniqueFd uniqueFd(OpenThumbnail(request->GetPath(), GetThumbType(fastSize.width, fastSize.height))); if (uniqueFd.Get() < 0) { // Can not get fast image in sandbox int32_t outFd = GetPixelMapFromServer(request->GetUri(), request->GetRequestSize(), request->GetPath()); if (outFd <= 0) { NAPI_ERR_LOG("Can not get thumbnail from server, uri=%{private}s", request->GetUri().c_str()); request->error = E_FAIL; return false; } request->SetFd(outFd); } ThumbnailType thumbType = GetThumbType(fastSize.width, fastSize.height); PixelMapPtr pixelMap = nullptr; if (request->GetFd().Get() == DEFAULT_FD && (thumbType == ThumbnailType::MTH || thumbType == ThumbnailType::YEAR)) { pixelMap = CreateThumbnailByAshmem(uniqueFd, fastSize); } else { pixelMap = DecodeThumbnail(request->GetFd(), fastSize); } if (pixelMap == nullptr) { request->error = E_FAIL; return false; } request->SetFastPixelMap(move(pixelMap)); return true; } void ThumbnailManager::DealWithFastRequest(const RequestSharedPtr &request) { MediaLibraryTracer tracer; tracer.Start("ThumbnailManager::DealWithFastRequest"); if (request == nullptr) { return; } if (!RequestFastImage(request) && request->error != E_FAIL) { // when local pixelmap not exit, must add QualityThread AddQualityPhotoRequest(request); return; } // callback NotifyImage(request); } void ThumbnailManager::DealWithQualityRequest(const RequestSharedPtr &request) { MediaLibraryTracer tracer; tracer.Start("ThumbnailManager::DealWithQualityRequest"); unique_ptr pixelMapPtr = nullptr; if (request->GetFd().Get() > 0) { pixelMapPtr = DecodeThumbnail(request->GetFd(), request->GetRequestSize()); } else { pixelMapPtr = QueryThumbnail(request->GetUri(), request->GetRequestSize(), request->GetPath()); } if (pixelMapPtr == nullptr) { NAPI_ERR_LOG("Can not get pixelMap"); request->error = E_FAIL; } request->SetPixelMap(move(pixelMapPtr)); // callback NotifyImage(request); } void ThumbnailManager::ImageWorker(int num) { SetThreadName("ImageWorker", num); while (true) { if (!isThreadRunning_) { return; } if (!fastQueue_.Empty()) { RequestSharedPtr request; if (fastQueue_.Pop(request) && request->NeedContinue()) { DealWithFastRequest(request); } } else if (!qualityQueue_.Empty()) { RequestSharedPtr request; if (qualityQueue_.Pop(request) && request->NeedContinue()) { DealWithQualityRequest(request); } } else { std::unique_lock lock(queueLock_); queueCv_.wait(lock, [this]() { return !isThreadRunning_ || !(qualityQueue_.Empty() && fastQueue_.Empty()); }); } } } static void HandlePixelCallback(const RequestSharedPtr &request) { napi_env env = request->callback_.env_; napi_value jsCallback = nullptr; napi_status status = napi_get_reference_value(env, request->callback_.callBackRef_, &jsCallback); if (status != napi_ok) { NAPI_ERR_LOG("Create reference fail, status: %{public}d", status); return; } napi_value retVal = nullptr; napi_value result[ARGS_TWO]; if (request->GetStatus() == ThumbnailStatus::THUMB_REMOVE) { return; } if (request->error == E_FAIL) { int32_t errorNum = MediaLibraryNapiUtils::TransErrorCode("requestPhoto", request->error); MediaLibraryNapiUtils::CreateNapiErrorObject(env, result[PARAM0], errorNum, "Failed to request Photo"); } else { result[PARAM0] = nullptr; } if (request->GetStatus() == ThumbnailStatus::THUMB_FAST) { result[PARAM1] = Media::PixelMapNapi::CreatePixelMap(env, shared_ptr(request->GetFastPixelMap())); } else { result[PARAM1] = Media::PixelMapNapi::CreatePixelMap(env, shared_ptr(request->GetPixelMap())); } status = napi_call_function(env, nullptr, jsCallback, ARGS_TWO, result, &retVal); if (status != napi_ok) { NAPI_ERR_LOG("CallJs napi_call_function fail, status: %{public}d", status); return; } } static void UvJsExecute(uv_work_t *work) { // js thread if (work == nullptr) { return; } ThumnailUv *uvMsg = reinterpret_cast(work->data); if (uvMsg == nullptr) { delete work; return; } if (uvMsg->request_ == nullptr) { delete uvMsg; delete work; return; } do { napi_env env = uvMsg->request_->callback_.env_; if (!uvMsg->request_->NeedContinue()) { break; } NapiScopeHandler scopeHandler(env); if (!scopeHandler.IsValid()) { break; } HandlePixelCallback(uvMsg->request_); } while (0); if (uvMsg->manager_ == nullptr) { delete uvMsg; delete work; return; } if (uvMsg->request_->GetStatus() == ThumbnailStatus::THUMB_FAST && NeedQualityThumb(uvMsg->request_->GetRequestSize(), uvMsg->request_->requestPhotoType)) { uvMsg->manager_->AddQualityPhotoRequest(uvMsg->request_); } else { uvMsg->manager_->DeleteRequestIdFromMap(uvMsg->request_->GetUUID()); uvMsg->request_->ReleaseCallbackRef(); } delete uvMsg; delete work; } void ThumbnailManager::NotifyImage(const RequestSharedPtr &request) { MediaLibraryTracer tracer; tracer.Start("ThumbnailManager::NotifyImage"); if (!request->NeedContinue()) { DeleteRequestIdFromMap(request->GetUUID()); return; } uv_loop_s *loop = nullptr; napi_get_uv_event_loop(request->callback_.env_, &loop); if (loop == nullptr) { DeleteRequestIdFromMap(request->GetUUID()); return; } uv_work_t *work = new (nothrow) uv_work_t; if (work == nullptr) { DeleteRequestIdFromMap(request->GetUUID()); return; } ThumnailUv *msg = new (nothrow) ThumnailUv(request, this); if (msg == nullptr) { delete work; DeleteRequestIdFromMap(request->GetUUID()); return; } work->data = reinterpret_cast(msg); int ret = uv_queue_work(loop, work, [](uv_work_t *w) {}, [](uv_work_t *w, int s) { UvJsExecute(w); }); if (ret != 0) { NAPI_ERR_LOG("Failed to execute libuv work queue, ret: %{public}d", ret); delete msg; delete work; return; } return; } } }