• 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 
16 #define MLOG_TAG "MediaAssetsChangeRequestNapi"
17 
18 #include "media_assets_change_request_napi.h"
19 
20 #include <unordered_set>
21 
22 #include "file_asset_napi.h"
23 #include "media_column.h"
24 #include "medialibrary_client_errno.h"
25 #include "medialibrary_errno.h"
26 #include "medialibrary_napi_log.h"
27 #include "medialibrary_tracer.h"
28 #include "userfile_client.h"
29 #include "userfile_manager_types.h"
30 #include "user_define_ipc_client.h"
31 #include "medialibrary_business_code.h"
32 #include "modify_assets_vo.h"
33 
34 using namespace std;
35 
36 namespace OHOS::Media {
37 static const string MEDIA_ASSETS_CHANGE_REQUEST_CLASS = "MediaAssetsChangeRequest";
38 thread_local napi_ref MediaAssetsChangeRequestNapi::constructor_ = nullptr;
39 
40 constexpr int32_t YES = 1;
41 constexpr int32_t NO = 0;
42 constexpr int32_t USER_COMMENT_MAX_LEN = 420;
43 
Init(napi_env env,napi_value exports)44 napi_value MediaAssetsChangeRequestNapi::Init(napi_env env, napi_value exports)
45 {
46     NapiClassInfo info = { .name = MEDIA_ASSETS_CHANGE_REQUEST_CLASS,
47         .ref = &constructor_,
48         .constructor = Constructor,
49         .props = {
50             DECLARE_NAPI_FUNCTION("setFavorite", JSSetFavorite),
51             DECLARE_NAPI_FUNCTION("setHidden", JSSetHidden),
52             DECLARE_NAPI_FUNCTION("setUserComment", JSSetUserComment),
53             DECLARE_NAPI_FUNCTION("setIsRecentShow", JSSetIsRecentShow),
54         } };
55     MediaLibraryNapiUtils::NapiDefineClass(env, exports, info);
56     return exports;
57 }
58 
GetAssetArray(napi_env env,napi_value arg,vector<shared_ptr<FileAsset>> & fileAssets)59 static napi_value GetAssetArray(napi_env env, napi_value arg, vector<shared_ptr<FileAsset>>& fileAssets)
60 {
61     bool isArray = false;
62     uint32_t len = 0;
63     CHECK_ARGS(env, napi_is_array(env, arg, &isArray), JS_INNER_FAIL);
64     CHECK_COND_WITH_MESSAGE(env, isArray, "Failed to check array type");
65     CHECK_ARGS(env, napi_get_array_length(env, arg, &len), JS_INNER_FAIL);
66     CHECK_COND_WITH_MESSAGE(env, len > 0, "Failed to check array length");
67     for (uint32_t i = 0; i < len; i++) {
68         napi_value asset = nullptr;
69         CHECK_ARGS(env, napi_get_element(env, arg, i, &asset), JS_INNER_FAIL);
70         CHECK_COND_WITH_MESSAGE(env, asset != nullptr, "Failed to get asset element");
71 
72         FileAssetNapi* fileAssetNapi = nullptr;
73         CHECK_ARGS(env, napi_unwrap(env, asset, reinterpret_cast<void**>(&fileAssetNapi)), JS_INNER_FAIL);
74         CHECK_COND_WITH_MESSAGE(env, fileAssetNapi != nullptr, "Failed to get FileAssetNapi object");
75 
76         auto fileAssetPtr = fileAssetNapi->GetFileAssetInstance();
77         CHECK_COND_WITH_MESSAGE(env, fileAssetPtr != nullptr, "fileAsset is null");
78         CHECK_COND_WITH_MESSAGE(env,
79             fileAssetPtr->GetResultNapiType() == ResultNapiType::TYPE_PHOTOACCESS_HELPER &&
80                 (fileAssetPtr->GetMediaType() == MEDIA_TYPE_IMAGE || fileAssetPtr->GetMediaType() == MEDIA_TYPE_VIDEO),
81             "Unsupported type of fileAsset");
82         fileAssets.push_back(fileAssetPtr);
83     }
84     RETURN_NAPI_TRUE(env);
85 }
86 
Constructor(napi_env env,napi_callback_info info)87 napi_value MediaAssetsChangeRequestNapi::Constructor(napi_env env, napi_callback_info info)
88 {
89     if (!MediaLibraryNapiUtils::IsSystemApp()) {
90         NapiError::ThrowError(env, E_CHECK_SYSTEMAPP_FAIL, "The constructor can be called only by system apps");
91         return nullptr;
92     }
93 
94     napi_value newTarget = nullptr;
95     CHECK_ARGS(env, napi_get_new_target(env, info, &newTarget), JS_INNER_FAIL);
96     CHECK_COND_RET(newTarget != nullptr, nullptr, "Failed to check new.target");
97 
98     size_t argc = ARGS_ONE;
99     napi_value argv[ARGS_ONE] = { 0 };
100     napi_value thisVar = nullptr;
101     CHECK_ARGS(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr), JS_INNER_FAIL);
102     CHECK_COND_WITH_MESSAGE(env, argc == ARGS_ONE, "Number of args is invalid");
103 
104     vector<shared_ptr<FileAsset>> fileAssets;
105     CHECK_COND_WITH_MESSAGE(env, GetAssetArray(env, argv[PARAM0], fileAssets), "Failed to parse args");
106     unique_ptr<MediaAssetsChangeRequestNapi> obj = make_unique<MediaAssetsChangeRequestNapi>();
107     CHECK_COND(env, obj != nullptr, JS_INNER_FAIL);
108     obj->fileAssets_ = fileAssets;
109     CHECK_ARGS(env,
110         napi_wrap(env, thisVar, reinterpret_cast<void*>(obj.get()), MediaAssetsChangeRequestNapi::Destructor, nullptr,
111             nullptr),
112         JS_INNER_FAIL);
113     obj.release();
114     return thisVar;
115 }
116 
Destructor(napi_env env,void * nativeObject,void * finalizeHint)117 void MediaAssetsChangeRequestNapi::Destructor(napi_env env, void* nativeObject, void* finalizeHint)
118 {
119     auto* assetsChangeRequest = reinterpret_cast<MediaAssetsChangeRequestNapi*>(nativeObject);
120     if (assetsChangeRequest != nullptr) {
121         delete assetsChangeRequest;
122         assetsChangeRequest = nullptr;
123     }
124 }
125 
GetFileAssetUriArray() const126 vector<string> MediaAssetsChangeRequestNapi::GetFileAssetUriArray() const
127 {
128     vector<string> uriArray;
129     uriArray.reserve(fileAssets_.size());
130     for (const auto& fileAsset : fileAssets_) {
131         uriArray.push_back(fileAsset->GetUri());
132     }
133     return uriArray;
134 }
135 
GetFileAssetIds(std::vector<int32_t> & fileIds) const136 void MediaAssetsChangeRequestNapi::GetFileAssetIds(std::vector<int32_t> &fileIds) const
137 {
138     for (const auto& fileAsset : fileAssets_) {
139         fileIds.push_back(fileAsset->GetId());
140     }
141 }
142 
GetFavoriteStatus() const143 bool MediaAssetsChangeRequestNapi::GetFavoriteStatus() const
144 {
145     return isFavorite_;
146 }
147 
GetHiddenStatus() const148 bool MediaAssetsChangeRequestNapi::GetHiddenStatus() const
149 {
150     return isHidden_;
151 }
152 
GetUpdatedUserComment() const153 string MediaAssetsChangeRequestNapi::GetUpdatedUserComment() const
154 {
155     return userComment_;
156 }
157 
GetRecentShowStatus() const158 bool MediaAssetsChangeRequestNapi::GetRecentShowStatus() const
159 {
160     return isRecentShow_;
161 }
162 
CheckChangeOperations(napi_env env)163 bool MediaAssetsChangeRequestNapi::CheckChangeOperations(napi_env env)
164 {
165     if (assetsChangeOperations_.empty()) {
166         NapiError::ThrowError(env, OHOS_INVALID_PARAM_CODE, "None request to apply");
167         return false;
168     }
169 
170     if (fileAssets_.empty()) {
171         NapiError::ThrowError(env, OHOS_INVALID_PARAM_CODE, "fileAssets is empty");
172         return false;
173     }
174 
175     for (const auto& fileAsset : fileAssets_) {
176         if (fileAsset == nullptr || fileAsset->GetId() <= 0 || fileAsset->GetUri().empty()) {
177             NapiError::ThrowError(env, OHOS_INVALID_PARAM_CODE, "Invalid fileAsset to apply");
178             return false;
179         }
180     }
181 
182     return true;
183 }
184 
JSSetFavorite(napi_env env,napi_callback_info info)185 napi_value MediaAssetsChangeRequestNapi::JSSetFavorite(napi_env env, napi_callback_info info)
186 {
187     if (!MediaLibraryNapiUtils::IsSystemApp()) {
188         NapiError::ThrowError(env, E_CHECK_SYSTEMAPP_FAIL, "This interface can be called only by system apps");
189         return nullptr;
190     }
191 
192     auto asyncContext = make_unique<MediaAssetsChangeRequestAsyncContext>();
193     bool isFavorite;
194     CHECK_COND_WITH_MESSAGE(env,
195         MediaLibraryNapiUtils::ParseArgsBoolCallBack(env, info, asyncContext, isFavorite) == napi_ok,
196         "Failed to parse args");
197     CHECK_COND_WITH_MESSAGE(env, asyncContext->argc == ARGS_ONE, "Number of args is invalid");
198 
199     auto changeRequest = asyncContext->objectInfo;
200     changeRequest->isFavorite_ = isFavorite;
201     for (const auto& fileAsset : changeRequest->fileAssets_) {
202         fileAsset->SetFavorite(isFavorite);
203     }
204     changeRequest->assetsChangeOperations_.push_back(AssetsChangeOperation::BATCH_SET_FAVORITE);
205     RETURN_NAPI_UNDEFINED(env);
206 }
207 
JSSetHidden(napi_env env,napi_callback_info info)208 napi_value MediaAssetsChangeRequestNapi::JSSetHidden(napi_env env, napi_callback_info info)
209 {
210     if (!MediaLibraryNapiUtils::IsSystemApp()) {
211         NapiError::ThrowError(env, E_CHECK_SYSTEMAPP_FAIL, "This interface can be called only by system apps");
212         return nullptr;
213     }
214 
215     auto asyncContext = make_unique<MediaAssetsChangeRequestAsyncContext>();
216     bool isHidden;
217     CHECK_COND_WITH_MESSAGE(env,
218         MediaLibraryNapiUtils::ParseArgsBoolCallBack(env, info, asyncContext, isHidden) == napi_ok,
219         "Failed to parse args");
220     CHECK_COND_WITH_MESSAGE(env, asyncContext->argc == ARGS_ONE, "Number of args is invalid");
221 
222     auto changeRequest = asyncContext->objectInfo;
223     changeRequest->isHidden_ = isHidden;
224     for (const auto& fileAsset : changeRequest->fileAssets_) {
225         fileAsset->SetHidden(isHidden);
226     }
227     changeRequest->assetsChangeOperations_.push_back(AssetsChangeOperation::BATCH_SET_HIDDEN);
228     RETURN_NAPI_UNDEFINED(env);
229 }
230 
JSSetUserComment(napi_env env,napi_callback_info info)231 napi_value MediaAssetsChangeRequestNapi::JSSetUserComment(napi_env env, napi_callback_info info)
232 {
233     if (!MediaLibraryNapiUtils::IsSystemApp()) {
234         NapiError::ThrowError(env, E_CHECK_SYSTEMAPP_FAIL, "This interface can be called only by system apps");
235         return nullptr;
236     }
237 
238     auto asyncContext = make_unique<MediaAssetsChangeRequestAsyncContext>();
239     string userComment;
240     CHECK_COND_WITH_MESSAGE(env,
241         MediaLibraryNapiUtils::ParseArgsStringCallback(env, info, asyncContext, userComment) == napi_ok,
242         "Failed to parse args");
243     CHECK_COND_WITH_MESSAGE(env, asyncContext->argc == ARGS_ONE, "Number of args is invalid");
244     CHECK_COND_WITH_MESSAGE(env, userComment.length() <= USER_COMMENT_MAX_LEN, "user comment too long");
245 
246     auto changeRequest = asyncContext->objectInfo;
247     changeRequest->userComment_ = userComment;
248     for (const auto& fileAsset : changeRequest->fileAssets_) {
249         fileAsset->SetUserComment(userComment);
250     }
251     changeRequest->assetsChangeOperations_.push_back(AssetsChangeOperation::BATCH_SET_USER_COMMENT);
252     RETURN_NAPI_UNDEFINED(env);
253 }
254 
JSSetIsRecentShow(napi_env env,napi_callback_info info)255 napi_value MediaAssetsChangeRequestNapi::JSSetIsRecentShow(napi_env env, napi_callback_info info)
256 {
257     if (!MediaLibraryNapiUtils::IsSystemApp()) {
258         NapiError::ThrowError(env, E_CHECK_SYSTEMAPP_FAIL, "This interface can be called only by system apps");
259         return nullptr;
260     }
261 
262     auto asyncContext = make_unique<MediaAssetsChangeRequestAsyncContext>();
263     bool isRecentShow;
264     CHECK_COND_WITH_MESSAGE(env,
265         MediaLibraryNapiUtils::ParseArgsBoolCallBack(env, info, asyncContext, isRecentShow) == napi_ok,
266         "Failed to parse args");
267     CHECK_COND_WITH_MESSAGE(env, asyncContext->argc == ARGS_ONE, "Number of args is invalid");
268 
269     auto changeRequest = asyncContext->objectInfo;
270     changeRequest->isRecentShow_ = isRecentShow;
271     for (const auto& fileAsset : changeRequest->fileAssets_) {
272         fileAsset->SetRecentShow(isRecentShow);
273     }
274     changeRequest->assetsChangeOperations_.push_back(AssetsChangeOperation::BATCH_SET_RECENT_SHOW);
275     RETURN_NAPI_UNDEFINED(env);
276 }
277 
CallSetAssetProperty(MediaAssetsChangeRequestAsyncContext & context,const AssetsChangeOperation & changeOperation)278 static bool CallSetAssetProperty(MediaAssetsChangeRequestAsyncContext& context,
279     const AssetsChangeOperation& changeOperation)
280 {
281     uint32_t businessCode = 0;
282     ModifyAssetsReqBody reqBody;
283     auto changeRequest = context.objectInfo;
284     CHECK_COND_RET(changeRequest != nullptr, false, "changeRequest is nullptr");
285     switch (changeOperation) {
286         case AssetsChangeOperation::BATCH_SET_FAVORITE:
287             reqBody.favorite = changeRequest->GetFavoriteStatus() ? YES : NO;
288             businessCode = static_cast<uint32_t>(MediaLibraryBusinessCode::PAH_SYSTEM_BATCH_SET_FAVORITE);
289             break;
290         case AssetsChangeOperation::BATCH_SET_HIDDEN:
291             reqBody.hiddenStatus = changeRequest->GetHiddenStatus() ? YES : NO;
292             businessCode = static_cast<uint32_t>(MediaLibraryBusinessCode::PAH_SYSTEM_BATCH_SET_HIDDEN);
293             break;
294         case AssetsChangeOperation::BATCH_SET_USER_COMMENT:
295             reqBody.userComment = changeRequest->GetUpdatedUserComment();
296             businessCode = static_cast<uint32_t>(MediaLibraryBusinessCode::PAH_SYSTEM_BATCH_SET_USER_COMMENT);
297             break;
298         case AssetsChangeOperation::BATCH_SET_RECENT_SHOW:
299             reqBody.recentShowStatus = changeRequest->GetRecentShowStatus() ? YES : NO;
300             businessCode = static_cast<uint32_t>(MediaLibraryBusinessCode::PAH_SYSTEM_BATCH_SET_RECENT_SHOW);
301             break;
302         default:
303             context.SaveError(E_FAIL);
304             NAPI_ERR_LOG("Unsupported assets change operation: %{public}d", changeOperation);
305             return false;
306     }
307 
308     changeRequest->GetFileAssetIds(reqBody.fileIds);
309     int32_t result = IPC::UserDefineIPCClient().Call(businessCode, reqBody);
310     if (result < 0) {
311         NAPI_ERR_LOG("after IPC::UserDefineIPCClient().Call, errCode: %{public}d.", result);
312         context.SaveError(result);
313         return false;
314     }
315     return true;
316 }
317 
SetAssetsPropertyExecute(MediaAssetsChangeRequestAsyncContext & context,const AssetsChangeOperation & changeOperation,bool useCall)318 static bool SetAssetsPropertyExecute(MediaAssetsChangeRequestAsyncContext& context,
319     const AssetsChangeOperation& changeOperation, bool useCall)
320 {
321     MediaLibraryTracer tracer;
322     tracer.Start("SetAssetsPropertyExecute");
323 
324     if (useCall) {
325         return CallSetAssetProperty(context, changeOperation);
326     }
327 
328     string uri;
329     DataShare::DataSharePredicates predicates;
330     DataShare::DataShareValuesBucket valuesBucket;
331     auto changeRequest = context.objectInfo;
332     predicates.In(PhotoColumn::MEDIA_ID, changeRequest->GetFileAssetUriArray());
333     switch (changeOperation) {
334         case AssetsChangeOperation::BATCH_SET_FAVORITE:
335             uri = PAH_BATCH_UPDATE_FAVORITE;
336             valuesBucket.Put(PhotoColumn::MEDIA_IS_FAV, changeRequest->GetFavoriteStatus() ? YES : NO);
337             NAPI_INFO_LOG("Batch set favorite: %{public}d", changeRequest->GetFavoriteStatus() ? YES : NO);
338             break;
339         case AssetsChangeOperation::BATCH_SET_HIDDEN:
340             uri = PAH_HIDE_PHOTOS;
341             valuesBucket.Put(PhotoColumn::MEDIA_HIDDEN, changeRequest->GetHiddenStatus() ? YES : NO);
342             break;
343         case AssetsChangeOperation::BATCH_SET_USER_COMMENT:
344             uri = PAH_BATCH_UPDATE_USER_COMMENT;
345             valuesBucket.Put(PhotoColumn::PHOTO_USER_COMMENT, changeRequest->GetUpdatedUserComment());
346             break;
347         case AssetsChangeOperation::BATCH_SET_RECENT_SHOW:
348             uri = PAH_BATCH_UPDATE_RECENT_SHOW;
349             valuesBucket.Put(PhotoColumn::PHOTO_IS_RECENT_SHOW, changeRequest->GetRecentShowStatus() ? YES : NO);
350             break;
351         default:
352             context.SaveError(E_FAIL);
353             NAPI_ERR_LOG("Unsupported assets change operation: %{public}d", changeOperation);
354             return false;
355     }
356 
357     NAPI_INFO_LOG("changeOperation:%{public}d, size:%{public}zu",
358         changeOperation, changeRequest->GetFileAssetUriArray().size());
359     MediaLibraryNapiUtils::UriAppendKeyValue(uri, API_VERSION, to_string(MEDIA_API_VERSION_V10));
360     Uri updateAssetsUri(uri);
361     int32_t changedRows = UserFileClient::Update(updateAssetsUri, predicates, valuesBucket);
362     if (changedRows < 0) {
363         context.SaveError(changedRows);
364         NAPI_ERR_LOG("Failed to set property, operation: %{public}d, err: %{public}d", changeOperation, changedRows);
365         return false;
366     }
367     return true;
368 }
369 
ApplyAssetsChangeRequestExecute(napi_env env,void * data)370 static void ApplyAssetsChangeRequestExecute(napi_env env, void* data)
371 {
372     MediaLibraryTracer tracer;
373     tracer.Start("ApplyAssetsChangeRequestExecute");
374 
375     auto* context = static_cast<MediaAssetsChangeRequestAsyncContext*>(data);
376     unordered_set<AssetsChangeOperation> appliedOperations;
377     for (const auto& changeOperation : context->assetsChangeOperations) {
378         // Keep the final result(s) of each operation, and commit only once.
379         if (appliedOperations.find(changeOperation) != appliedOperations.end()) {
380             continue;
381         }
382 
383         bool valid = SetAssetsPropertyExecute(*context, changeOperation, true);
384         if (!valid) {
385             NAPI_ERR_LOG("Failed to apply assets change request, operation: %{public}d", changeOperation);
386             return;
387         }
388         appliedOperations.insert(changeOperation);
389     }
390 }
391 
ApplyAssetsChangeRequestCompleteCallback(napi_env env,napi_status status,void * data)392 static void ApplyAssetsChangeRequestCompleteCallback(napi_env env, napi_status status, void* data)
393 {
394     MediaLibraryTracer tracer;
395     tracer.Start("ApplyAssetsChangeRequestCompleteCallback");
396 
397     auto* context = static_cast<MediaAssetsChangeRequestAsyncContext*>(data);
398     CHECK_NULL_PTR_RETURN_VOID(context, "Async context is null");
399     auto jsContext = make_unique<JSAsyncContextOutput>();
400     jsContext->status = false;
401     napi_get_undefined(env, &jsContext->data);
402     napi_get_undefined(env, &jsContext->error);
403     if (context->error == ERR_DEFAULT) {
404         jsContext->status = true;
405     } else {
406         context->HandleError(env, jsContext->error);
407     }
408 
409     if (context->work != nullptr) {
410         MediaLibraryNapiUtils::InvokeJSAsyncMethod(
411             env, context->deferred, context->callbackRef, context->work, *jsContext);
412     }
413     delete context;
414 }
415 
ApplyChanges(napi_env env,napi_callback_info info)416 napi_value MediaAssetsChangeRequestNapi::ApplyChanges(napi_env env, napi_callback_info info)
417 {
418     constexpr size_t minArgs = ARGS_ONE;
419     constexpr size_t maxArgs = ARGS_TWO;
420     auto asyncContext = make_unique<MediaAssetsChangeRequestAsyncContext>();
421     CHECK_COND_WITH_MESSAGE(env,
422         MediaLibraryNapiUtils::AsyncContextGetArgs(env, info, asyncContext, minArgs, maxArgs) == napi_ok,
423         "Failed to get args");
424     asyncContext->objectInfo = this;
425 
426     CHECK_COND_WITH_MESSAGE(env, CheckChangeOperations(env), "Failed to check assets change request operations");
427     asyncContext->assetsChangeOperations = assetsChangeOperations_;
428     assetsChangeOperations_.clear();
429     return MediaLibraryNapiUtils::NapiCreateAsyncWork(env, asyncContext, "ApplyMediaAssetsChangeRequest",
430         ApplyAssetsChangeRequestExecute, ApplyAssetsChangeRequestCompleteCallback);
431 }
432 } // namespace OHOS::Media