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 &notifyUri, 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 &notifyUri, 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> &notifyUris)
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> &notifyUris)
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