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 #define MLOG_TAG "FileNotify"
16 #include "medialibrary_notify.h"
17 #include "medialibrary_async_worker.h"
18 #include "data_ability_helper_impl.h"
19 #include "media_file_utils.h"
20 #include "media_log.h"
21 #include "medialibrary_command.h"
22 #include "medialibrary_data_manager_utils.h"
23 #include "medialibrary_db_const.h"
24 #include "medialibrary_errno.h"
25 #include "medialibrary_object_utils.h"
26 #include "medialibrary_tracer.h"
27 #include "medialibrary_unistore_manager.h"
28 #include "photo_album_column.h"
29 #include "photo_map_column.h"
30 #include "result_set_utils.h"
31 #include "uri.h"
32
33 using namespace std;
34
35 namespace OHOS::Media {
36 using ChangeType = AAFwk::ChangeInfo::ChangeType;
37 using NotifyDataMap = unordered_map<NotifyType, list<Uri>>;
38 static const int32_t WAIT_TIME = 2;
39 shared_ptr<MediaLibraryNotify> MediaLibraryNotify::instance_;
40 mutex MediaLibraryNotify::mutex_;
41 unordered_map<string, NotifyDataMap> MediaLibraryNotify::nfListMap_ = {};
42 uint32_t MediaLibraryNotify::timerId_ = 0;
43
GetInstance()44 shared_ptr<MediaLibraryNotify> MediaLibraryNotify::GetInstance()
45 {
46 if (instance_ != nullptr) {
47 return instance_;
48 }
49 lock_guard<mutex> lock(mutex_);
50 if (instance_ == nullptr) {
51 instance_ = shared_ptr<MediaLibraryNotify>(new MediaLibraryNotify());
52 if (instance_ == nullptr) {
53 MEDIA_ERR_LOG("GetInstance nullptr");
54 return instance_;
55 }
56 instance_->Init();
57 }
58 return instance_;
59 }
MediaLibraryNotify()60 MediaLibraryNotify::MediaLibraryNotify() : timer_("on_notify") {};
61
~MediaLibraryNotify()62 MediaLibraryNotify::~MediaLibraryNotify()
63 {
64 timer_.Shutdown();
65 timer_.Unregister(timerId_);
66 };
67
SolveUris(const list<Uri> & uris,Parcel & parcel)68 static bool SolveUris(const list<Uri> &uris, Parcel &parcel)
69 {
70 if (uris.size() > numeric_limits<uint32_t>::max() ||
71 !parcel.WriteUint32(static_cast<uint32_t>(uris.size()))) {
72 MEDIA_ERR_LOG("Failed to write uri list length, list size = %{private}zu", uris.size());
73 return false;
74 }
75 for (auto const &uri : uris) {
76 if (!parcel.WriteString(uri.ToString())) {
77 MEDIA_ERR_LOG("Failed to write strUri uri = %{private}s", uri.ToString().c_str());
78 return false;
79 }
80 }
81 return true;
82 }
83
SendAlbumSub(const Uri & notifyUri,NotifyType type,list<Uri> & uris)84 static int SendAlbumSub(const Uri ¬ifyUri, NotifyType type, list<Uri> &uris)
85 {
86 Parcel parcel;
87 CHECK_AND_RETURN_RET_LOG(SolveUris(uris, parcel), E_SOLVE_URIS_FAILED, "SolveUris failed");
88 auto obsMgrClient = AAFwk::DataObsMgrClient::GetInstance();
89 uintptr_t buf = parcel.GetData();
90 if (parcel.GetDataSize() == 0) {
91 MEDIA_ERR_LOG("NotifyChangeExt parcel.GetDataSize failed");
92 return E_PARCEL_GET_SIZE_FAILED;
93 }
94 auto *uBuf = new (std::nothrow) uint8_t[parcel.GetDataSize()];
95 if (uBuf == nullptr) {
96 MEDIA_ERR_LOG("parcel.GetDataSize is null");
97 return E_PARCEL_GET_SIZE_FAILED;
98 }
99 int ret = memcpy_s(uBuf, parcel.GetDataSize(), reinterpret_cast<uint8_t *>(buf), parcel.GetDataSize());
100 if (ret != 0) {
101 MEDIA_ERR_LOG("Parcel data copy failed, err = %{public}d", ret);
102 }
103 ChangeType changeType;
104 if (type == NotifyType::NOTIFY_ALBUM_ADD_ASSET) {
105 changeType = ChangeType::INSERT;
106 } else {
107 changeType = ChangeType::DELETE;
108 }
109 MEDIA_DEBUG_LOG("obsMgrClient->NotifyChangeExt URI is %{public}s, NotifyType is %{public}d",
110 notifyUri.ToString().c_str(), type);
111 return obsMgrClient->NotifyChangeExt({changeType, {notifyUri}, uBuf, parcel.GetDataSize()});
112 }
113
SolveAlbumUri(const Uri & notifyUri,NotifyType type,list<Uri> & uris)114 static int SolveAlbumUri(const Uri ¬ifyUri, NotifyType type, list<Uri> &uris)
115 {
116 auto obsMgrClient = AAFwk::DataObsMgrClient::GetInstance();
117 MEDIA_DEBUG_LOG("obsMgrClient->NotifyChangeExt URI is %{public}s, NotifyType is %{public}d",
118 notifyUri.ToString().c_str(), type);
119 if ((type == NotifyType::NOTIFY_ALBUM_ADD_ASSET) || (type == NotifyType::NOTIFY_ALBUM_REMOVE_ASSET)) {
120 return SendAlbumSub(notifyUri, type, uris);
121 } else {
122 return obsMgrClient->NotifyChangeExt({static_cast<ChangeType>(type), uris});
123 }
124 }
125
PushNotifyDataMap(const string & uri,NotifyDataMap notifyDataMap)126 static void PushNotifyDataMap(const string &uri, NotifyDataMap notifyDataMap)
127 {
128 int ret;
129 for (auto &[type, uris] : notifyDataMap) {
130 if (uri.find(PhotoAlbumColumns::ALBUM_URI_PREFIX) != string::npos) {
131 Uri notifyUri = Uri(uri);
132 ret = SolveAlbumUri(notifyUri, type, uris);
133 } else {
134 auto obsMgrClient = AAFwk::DataObsMgrClient::GetInstance();
135 MEDIA_DEBUG_LOG("obsMgrClient->NotifyChangeExt URI is %{public}s, type is %{public}d",
136 uri.c_str(), static_cast<int>(type));
137 ret = obsMgrClient->NotifyChangeExt({static_cast<ChangeType>(type), uris});
138 }
139 if (ret != E_OK) {
140 MEDIA_ERR_LOG("PushNotification failed, errorCode = %{public}d", ret);
141 }
142 }
143 return;
144 }
145
PushNotification()146 static void PushNotification()
147 {
148 unordered_map<string, NotifyDataMap> tmpNfListMap;
149 {
150 lock_guard<mutex> lock(MediaLibraryNotify::mutex_);
151 if (MediaLibraryNotify::nfListMap_.empty()) {
152 return;
153 }
154 MediaLibraryNotify::nfListMap_.swap(tmpNfListMap);
155 MediaLibraryNotify::nfListMap_.clear();
156 }
157 for (auto &[uri, notifyDataMap] : tmpNfListMap) {
158 if (notifyDataMap.empty()) {
159 continue;
160 }
161 PushNotifyDataMap(uri, notifyDataMap);
162 }
163 }
164
AddNotify(const string & srcUri,const string & keyUri,NotifyTaskData * taskData)165 static void AddNotify(const string &srcUri, const string &keyUri, NotifyTaskData* taskData)
166 {
167 NotifyDataMap notifyDataMap;
168 list<Uri> sendUris;
169 Uri uri(srcUri);
170 MEDIA_DEBUG_LOG("AddNotify ,keyUri = %{private}s, uri = %{private}s, "
171 "notifyType = %{private}d", keyUri.c_str(), uri.ToString().c_str(), taskData->notifyType_);
172 lock_guard<mutex> lock(MediaLibraryNotify::mutex_);
173 if (MediaLibraryNotify::nfListMap_.count(keyUri) == 0) {
174 sendUris.emplace_back(uri);
175 notifyDataMap.insert(make_pair(taskData->notifyType_, sendUris));
176 MediaLibraryNotify::nfListMap_.insert(make_pair(keyUri, notifyDataMap));
177 } else {
178 auto iter = MediaLibraryNotify::nfListMap_.find(keyUri);
179 if (iter->second.count(taskData->notifyType_) == 0) {
180 sendUris.emplace_back(uri);
181 iter->second.insert(make_pair(taskData->notifyType_, sendUris));
182 } else {
183 auto haveIter = find_if(
184 iter->second.at(taskData->notifyType_).begin(),
185 iter->second.at(taskData->notifyType_).end(),
186 [uri](const Uri &listUri) { return uri.Equals(listUri); });
187 if (haveIter == iter->second.at(taskData->notifyType_).end()) {
188 iter->second.find(taskData->notifyType_)->second.emplace_back(uri);
189 }
190 }
191 }
192 }
193
GetAlbumsById(const string & fileId,list<string> & albumIdList)194 static int32_t GetAlbumsById(const string &fileId, list<string> &albumIdList)
195 {
196 auto uniStore = MediaLibraryUnistoreManager::GetInstance().GetRdbStore();
197 MediaLibraryCommand queryAlbumMapCmd(OperationObject::PAH_PHOTO, OperationType::QUERY);
198 queryAlbumMapCmd.GetAbsRdbPredicates()->EqualTo(PhotoColumn::MEDIA_ID, fileId);
199 auto resultSet = uniStore->Query(queryAlbumMapCmd, {PhotoColumn::PHOTO_OWNER_ALBUM_ID});
200 if (resultSet == nullptr) {
201 MEDIA_ERR_LOG("GetAlbumsById failed");
202 return E_INVALID_FILEID;
203 }
204 int32_t count = -1;
205 int32_t ret = resultSet->GetRowCount(count);
206 CHECK_AND_RETURN_RET_LOG(ret == E_OK, ret, "Failed to get count");
207 if (count <= 0) {
208 return E_OK;
209 }
210 ret = resultSet->GoToFirstRow();
211 CHECK_AND_RETURN_RET_LOG(ret == E_OK, ret, "Failed to GoToFirstRow");
212 do {
213 int32_t albumId = get<int32_t>(ResultSetUtils::GetValFromColumn(PhotoColumn::PHOTO_OWNER_ALBUM_ID,
214 resultSet, TYPE_INT32));
215 albumIdList.emplace_back(to_string(albumId));
216 } while (!resultSet->GoToNextRow());
217 return E_OK;
218 }
219
HandleAlbumNotify(NotifyTaskData * taskData)220 static void HandleAlbumNotify(NotifyTaskData *taskData)
221 {
222 list<string> albumIdList;
223 string id = MediaFileUtils::GetIdFromUri(taskData->uri_);
224 int err = GetAlbumsById(id, albumIdList);
225 CHECK_AND_RETURN_LOG(err == E_OK, "Fail to get albumId");
226 for (const string &id : albumIdList) {
227 AddNotify(taskData->uri_, PhotoAlbumColumns::ALBUM_URI_PREFIX + id, taskData);
228 }
229
230 if (!taskData->hiddenOnly_) {
231 return;
232 }
233 NotifyType hiddenAlbumsNotifyType = taskData->notifyType_;
234 if (taskData->notifyType_ == NotifyType::NOTIFY_ALBUM_ADD_ASSET) {
235 hiddenAlbumsNotifyType = NotifyType::NOTIFY_ALBUM_REMOVE_ASSET;
236 } else if (taskData->notifyType_ == NotifyType::NOTIFY_ALBUM_REMOVE_ASSET) {
237 hiddenAlbumsNotifyType = NotifyType::NOTIFY_ALBUM_ADD_ASSET;
238 }
239 taskData->notifyType_ = hiddenAlbumsNotifyType;
240 for (const string &id : albumIdList) {
241 AddNotify(taskData->uri_, PhotoAlbumColumns::HIDDEN_ALBUM_URI_PREFIX + id, taskData);
242 }
243 }
244
AddNfListMap(AsyncTaskData * data)245 static void AddNfListMap(AsyncTaskData *data)
246 {
247 if (data == nullptr) {
248 return;
249 }
250 auto* taskData = static_cast<NotifyTaskData*>(data);
251 if ((taskData->notifyType_ == NotifyType::NOTIFY_ALBUM_ADD_ASSET) ||
252 (taskData->notifyType_ == NotifyType::NOTIFY_ALBUM_REMOVE_ASSET)) {
253 if (taskData->albumId_ > 0) {
254 AddNotify(taskData->uri_,
255 PhotoAlbumColumns::ALBUM_URI_PREFIX + to_string(taskData->albumId_), taskData);
256 } else {
257 HandleAlbumNotify(taskData);
258 }
259 } else {
260 string typeUri = MediaLibraryDataManagerUtils::GetTypeUriByUri(taskData->uri_);
261 AddNotify(taskData->uri_, typeUri, taskData);
262 }
263 }
264
Init()265 int32_t MediaLibraryNotify::Init()
266 {
267 MediaLibraryNotify::timer_.Setup();
268 MediaLibraryNotify::timerId_ = MediaLibraryNotify::timer_.Register(PushNotification, MNOTIFY_TIME_INTERVAL);
269 return E_OK;
270 }
271
Notify(const string & uri,const NotifyType notifyType,const int albumId,const bool hiddenOnly)272 int32_t MediaLibraryNotify::Notify(const string &uri, const NotifyType notifyType, const int albumId,
273 const bool hiddenOnly)
274 {
275 if (MediaLibraryNotify::nfListMap_.size() > MAX_NOTIFY_LIST_SIZE) {
276 MediaLibraryNotify::timer_.Shutdown();
277 PushNotification();
278 MediaLibraryNotify::timer_.Register(PushNotification, MNOTIFY_TIME_INTERVAL);
279 MediaLibraryNotify::timer_.Setup();
280 }
281 unique_ptr<NotifyTaskWorker> &asyncWorker = NotifyTaskWorker::GetInstance();
282 CHECK_AND_RETURN_RET_LOG(asyncWorker != nullptr, E_ASYNC_WORKER_IS_NULL, "AsyncWorker is null");
283 auto *taskData = new (nothrow) NotifyTaskData(uri, notifyType, albumId, hiddenOnly);
284 CHECK_AND_RETURN_RET_LOG(taskData != nullptr, E_NOTIFY_TASK_DATA_IS_NULL, "taskData is null");
285 MEDIA_DEBUG_LOG("Notify ,uri = %{private}s, notifyType = %{private}d, albumId = %{private}d",
286 uri.c_str(), notifyType, albumId);
287 shared_ptr<MediaLibraryAsyncTask> notifyAsyncTask = make_shared<MediaLibraryAsyncTask>(AddNfListMap, taskData);
288 if (notifyAsyncTask != nullptr) {
289 asyncWorker->AddTask(notifyAsyncTask);
290 }
291 return E_OK;
292 }
293
Notify(const shared_ptr<FileAsset> & closeAsset)294 int32_t MediaLibraryNotify::Notify(const shared_ptr<FileAsset> &closeAsset)
295 {
296 bool isCreateFile = false;
297 if (closeAsset->GetDateModified() == 0) {
298 isCreateFile = true;
299 }
300 if (closeAsset->GetMediaType() == MediaType::MEDIA_TYPE_IMAGE ||
301 closeAsset->GetMediaType() == MediaType::MEDIA_TYPE_VIDEO) {
302 if (isCreateFile) {
303 return Notify(PhotoColumn::PHOTO_URI_PREFIX + to_string(closeAsset->GetId()), NotifyType::NOTIFY_ADD);
304 }
305 return Notify(PhotoColumn::PHOTO_URI_PREFIX + to_string(closeAsset->GetId()), NotifyType::NOTIFY_UPDATE);
306 } else if (closeAsset->GetMediaType() == MediaType::MEDIA_TYPE_AUDIO) {
307 if (isCreateFile) {
308 return Notify(AudioColumn::AUDIO_URI_PREFIX + to_string(closeAsset->GetId()), NotifyType::NOTIFY_ADD);
309 }
310 return Notify(AudioColumn::AUDIO_URI_PREFIX + to_string(closeAsset->GetId()), NotifyType::NOTIFY_UPDATE);
311 } else {
312 return E_CHECK_MEDIATYPE_FAIL;
313 }
314 }
315
GetDefaultAlbums(std::unordered_map<PhotoAlbumSubType,int> & outAlbums)316 int32_t MediaLibraryNotify::GetDefaultAlbums(std::unordered_map<PhotoAlbumSubType, int> &outAlbums)
317 {
318 auto uniStore = MediaLibraryUnistoreManager::GetInstance().GetRdbStore();
319 MediaLibraryCommand queryAlbumMapCmd(OperationObject::PHOTO_ALBUM, OperationType::QUERY);
320 queryAlbumMapCmd.GetAbsRdbPredicates()->EqualTo(PhotoAlbumColumns::ALBUM_TYPE, to_string(PhotoAlbumType::SYSTEM));
321 CHECK_AND_RETURN_RET_LOG(uniStore != nullptr, E_HAS_DB_ERROR, "UniStore is nullptr!");
322 auto resultSet = uniStore->Query(queryAlbumMapCmd,
323 {PhotoAlbumColumns::ALBUM_ID, PhotoAlbumColumns::ALBUM_SUBTYPE});
324 if (resultSet == nullptr) {
325 return E_HAS_DB_ERROR;
326 }
327 int32_t count = -1;
328 int32_t ret = resultSet->GetRowCount(count);
329 CHECK_AND_RETURN_RET_LOG(ret == E_OK, ret, "Failed to get count");
330 ret = resultSet->GoToFirstRow();
331 CHECK_AND_RETURN_RET_LOG(ret == E_OK, ret, "Failed to GoToFirstRow");
332 do {
333 int32_t albumId = get<int32_t>(ResultSetUtils::GetValFromColumn(PhotoAlbumColumns::ALBUM_ID, resultSet,
334 TYPE_INT32));
335 int32_t albumSubType = get<int32_t>(ResultSetUtils::GetValFromColumn(PhotoAlbumColumns::ALBUM_SUBTYPE,
336 resultSet, TYPE_INT32));
337 MEDIA_INFO_LOG("GetDefaultAlbums albumId: %{public}d, albumSubType: %{public}d", albumId, albumSubType);
338 outAlbums.insert(make_pair(static_cast<PhotoAlbumSubType>(albumSubType), albumId));
339 } while (!resultSet->GoToNextRow());
340 return E_OK;
341 }
342
GetAlbumIdBySubType(const PhotoAlbumSubType subType)343 int32_t MediaLibraryNotify::GetAlbumIdBySubType(const PhotoAlbumSubType subType)
344 {
345 int errCode = E_OK;
346 if (defaultAlbums_.size() == 0) {
347 errCode = GetDefaultAlbums(defaultAlbums_);
348 }
349 CHECK_AND_RETURN_RET_LOG(errCode == E_OK, errCode, "Failed to GetDefaultAlbums");
350 if (defaultAlbums_.count(subType) == 0) {
351 return E_ERR;
352 }
353 return defaultAlbums_.find(subType)->second;
354 }
355
GetNotifyUri(shared_ptr<NativeRdb::ResultSet> & resultSet,vector<string> & notifyUris)356 static void GetNotifyUri(shared_ptr<NativeRdb::ResultSet> &resultSet, vector<string> ¬ifyUris)
357 {
358 int32_t fileId = MediaLibraryRdbStore::GetInt(resultSet, PhotoColumn::MEDIA_ID);
359 string path = MediaLibraryRdbStore::GetString(resultSet, PhotoColumn::MEDIA_FILE_PATH);
360 string displayName = MediaLibraryRdbStore::GetString(resultSet, PhotoColumn::MEDIA_NAME);
361 string notifyUri = MediaFileUtils::GetUriByExtrConditions(PhotoColumn::PHOTO_URI_PREFIX, to_string(fileId),
362 MediaFileUtils::GetExtraUri(displayName, path));
363 notifyUris.push_back(notifyUri);
364 }
365
GetNotifyUris(const NativeRdb::AbsRdbPredicates & predicates,vector<string> & notifyUris)366 void MediaLibraryNotify::GetNotifyUris(const NativeRdb::AbsRdbPredicates &predicates, vector<string> ¬ifyUris)
367 {
368 MediaLibraryTracer tracer;
369 tracer.Start("GetNotifyUris");
370 auto rdbStore = MediaLibraryUnistoreManager::GetInstance().GetRdbStore();
371 if (rdbStore == nullptr) {
372 return;
373 }
374 auto resultSet = rdbStore->QueryWithFilter(predicates, {
375 PhotoColumn::MEDIA_ID,
376 PhotoColumn::MEDIA_FILE_PATH,
377 PhotoColumn::MEDIA_NAME
378 });
379 if (resultSet == nullptr) {
380 return;
381 }
382
383 int32_t count = 0;
384 int32_t err = resultSet->GetRowCount(count);
385 if (err != E_OK || count <= 0) {
386 MEDIA_WARN_LOG("Failed to get row count: %{public}d", err);
387 return;
388 }
389 err = resultSet->GoToFirstRow();
390 if (err != E_OK) {
391 MEDIA_WARN_LOG("Failed to go to first row: %{public}d", err);
392 return;
393 }
394 do {
395 GetNotifyUri(resultSet, notifyUris);
396 count--;
397 if (count > 0) {
398 err = resultSet->GoToNextRow();
399 if (err < 0) {
400 MEDIA_WARN_LOG("Failed to go to next row err: %{public}d", err);
401 return;
402 }
403 }
404 } while (count > 0);
405 }
406
NotifyTaskWorker()407 NotifyTaskWorker::NotifyTaskWorker() : isThreadRunning_(false)
408 {}
409
~NotifyTaskWorker()410 NotifyTaskWorker::~NotifyTaskWorker()
411 {
412 isThreadRunning_ = false;
413 if (thread_.joinable()) {
414 thread_.join();
415 }
416 }
417
StartThread()418 void NotifyTaskWorker::StartThread()
419 {
420 isThreadRunning_ = true;
421 thread_ = std::thread([this]() { this->StartWorker(); });
422 thread_.detach();
423 }
424
AddTask(const shared_ptr<MediaLibraryAsyncTask> & task)425 int32_t NotifyTaskWorker::AddTask(const shared_ptr<MediaLibraryAsyncTask> &task)
426 {
427 lock_guard<mutex> lockGuard(taskLock_);
428 taskQueue_.push(task);
429 if (isThreadRunning_) {
430 taskCv_.notify_all();
431 } else {
432 StartThread();
433 }
434 return 0;
435 }
436
GetTask()437 shared_ptr<MediaLibraryAsyncTask> NotifyTaskWorker::GetTask()
438 {
439 lock_guard<mutex> lockGuard(taskLock_);
440 if (taskQueue_.empty()) {
441 return nullptr;
442 }
443 shared_ptr<MediaLibraryAsyncTask> task = taskQueue_.front();
444 taskQueue_.pop();
445 return task;
446 }
447
IsQueueEmpty()448 bool NotifyTaskWorker::IsQueueEmpty()
449 {
450 lock_guard<mutex> lock_Guard(taskLock_);
451 return taskQueue_.empty();
452 }
453
WaitForTask()454 bool NotifyTaskWorker::WaitForTask()
455 {
456 std::unique_lock<std::mutex> lock(cvLock_);
457 return taskCv_.wait_for(lock, std::chrono::minutes(WAIT_TIME),
458 [this]() { return !IsQueueEmpty(); });
459 }
460
StartWorker()461 void NotifyTaskWorker::StartWorker()
462 {
463 string name("NotifyTaskWorker");
464 pthread_setname_np(pthread_self(), name.c_str());
465 while (true) {
466 if (WaitForTask()) {
467 shared_ptr<MediaLibraryAsyncTask> task = GetTask();
468 if (task != nullptr) {
469 task->executor_(task->data_);
470 task = nullptr;
471 }
472 } else {
473 isThreadRunning_ = false;
474 return;
475 }
476 }
477 }
478 } // namespace OHOS::Media
479