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 "PhotoMapOperation"
16 
17 #include "photo_map_operations.h"
18 
19 #include "media_column.h"
20 #include "media_file_uri.h"
21 #include "media_file_utils.h"
22 #include "medialibrary_album_operations.h"
23 #include "medialibrary_analysis_album_operations.h"
24 #include "medialibrary_asset_operations.h"
25 #include "medialibrary_db_const.h"
26 #include "medialibrary_data_manager.h"
27 #include "medialibrary_errno.h"
28 #include "medialibrary_notify.h"
29 #include "medialibrary_photo_operations.h"
30 #include "medialibrary_rdb_transaction.h"
31 #include "medialibrary_rdb_utils.h"
32 #include "medialibrary_rdbstore.h"
33 #include "medialibrary_unistore_manager.h"
34 #include "photo_album_column.h"
35 #include "photo_map_column.h"
36 #include "value_object.h"
37 #include "vision_column.h"
38 #include "rdb_utils.h"
39 #include "result_set_utils.h"
40 #include "vision_album_column.h"
41 #include "vision_face_tag_column.h"
42 #include "vision_image_face_column.h"
43 #include "vision_photo_map_column.h"
44 #include "dfx_manager.h"
45 #include "dfx_const.h"
46 
47 namespace OHOS::Media {
48 using namespace std;
49 using namespace OHOS::NativeRdb;
50 using namespace OHOS::DataShare;
51 
52 constexpr int32_t ALBUM_IS_REMOVED = 1;
53 
InsertAnalysisAsset(const DataShareValuesBucket & value,std::shared_ptr<TransactionOperations> trans)54 static int32_t InsertAnalysisAsset(const DataShareValuesBucket &value,
55     std::shared_ptr<TransactionOperations> trans)
56 {
57     if (trans == nullptr) {
58         MEDIA_ERR_LOG("transactionOperations is null");
59         return -EINVAL;
60     }
61     /**
62      * Build insert sql:
63      * INSERT INTO AnalysisPhotoMap (map_album, map_asset) SELECT
64      * ?, ?
65      * WHERE
66      *     (NOT EXISTS (SELECT * FROM AnalysisPhotoMap WHERE map_album = ? AND map_asset = ?))
67      *     AND (EXISTS (SELECT file_id FROM Photos WHERE file_id = ?))
68      *     AND (EXISTS (SELECT album_id FROM AnalysisAlbum WHERE album_id = ?));
69      */
70     static const std::string INSERT_MAP_SQL = "INSERT OR IGNORE INTO " + ANALYSIS_PHOTO_MAP_TABLE +
71         " (" + PhotoMap::ALBUM_ID + ", " + PhotoMap::ASSET_ID + ") " +
72         "SELECT ?, ? WHERE " +
73         "(NOT EXISTS (SELECT 1 FROM " + ANALYSIS_PHOTO_MAP_TABLE + " WHERE " +
74             PhotoMap::ALBUM_ID + " = ? AND " + PhotoMap::ASSET_ID + " = ?)) " +
75         "AND (EXISTS (SELECT 1 FROM " + PhotoColumn::PHOTOS_TABLE + " WHERE " +
76             MediaColumn::MEDIA_ID + " = ?)) " +
77         "AND (EXISTS (SELECT 1 FROM " + ANALYSIS_ALBUM_TABLE +
78             " WHERE " + PhotoAlbumColumns::ALBUM_ID + " = ? ));";
79     bool isValid = false;
80     int32_t albumId = value.Get(PhotoMap::ALBUM_ID, isValid);
81     if (!isValid) {
82         return -EINVAL;
83     }
84     int32_t assetId = value.Get(PhotoMap::ASSET_ID, isValid);
85     if (!isValid) {
86         return -EINVAL;
87     }
88     vector<ValueObject> bindArgs = { albumId, assetId, albumId, assetId, assetId, albumId};
89     return trans->ExecuteForLastInsertedRowId(INSERT_MAP_SQL, bindArgs);
90 }
91 
AddPhotoAssets(const vector<DataShareValuesBucket> & values)92 int32_t PhotoMapOperations::AddPhotoAssets(const vector<DataShareValuesBucket> &values)
93 {
94     auto rdbStore = MediaLibraryUnistoreManager::GetInstance().GetRdbStore();
95     if (rdbStore == nullptr) {
96         return E_HAS_DB_ERROR;
97     }
98 
99     int32_t changedRows = 0;
100     vector<int32_t> updateIds;
101     if (!values.empty()) {
102         bool isValid = false;
103         int32_t albumId = values[0].Get(PhotoColumn::PHOTO_OWNER_ALBUM_ID, isValid);
104         if (!isValid || albumId <= 0) {
105             MEDIA_WARN_LOG("Ignore failure on get album id when add assets. isValid: %{public}d, albumId: %{public}d",
106                 isValid, albumId);
107             return changedRows;
108         }
109 
110         changedRows = MediaLibraryRdbUtils::UpdateOwnerAlbumId(rdbStore, values, updateIds);
111         MediaLibraryRdbUtils::UpdateUserAlbumInternal(rdbStore, { to_string(albumId) });
112         MediaLibraryRdbUtils::UpdateSystemAlbumInternal(rdbStore, {
113             to_string(PhotoAlbumSubType::IMAGE), to_string(PhotoAlbumSubType::VIDEO),
114             to_string(PhotoAlbumSubType::FAVORITE)
115         });
116         auto watch = MediaLibraryNotify::GetInstance();
117         for (const auto &id : updateIds) {
118             string notifyUri = PhotoColumn::PHOTO_URI_PREFIX + to_string(id);
119             watch->Notify(MediaFileUtils::Encode(notifyUri), NotifyType::NOTIFY_ALBUM_ADD_ASSET, albumId);
120             watch->Notify(MediaFileUtils::Encode(notifyUri), NotifyType::NOTIFY_ALBUM_ADD_ASSET,
121                 watch->GetAlbumIdBySubType(PhotoAlbumSubType::IMAGE));
122             watch->Notify(MediaFileUtils::Encode(notifyUri), NotifyType::NOTIFY_ALBUM_ADD_ASSET,
123                 watch->GetAlbumIdBySubType(PhotoAlbumSubType::VIDEO));
124             watch->Notify(MediaFileUtils::Encode(notifyUri), NotifyType::NOTIFY_ALBUM_ADD_ASSET,
125                 watch->GetAlbumIdBySubType(PhotoAlbumSubType::FAVORITE));
126             watch->Notify(MediaFileUtils::Encode(notifyUri), NotifyType::NOTIFY_ADD);
127         }
128     }
129     return changedRows;
130 }
131 
GetPortraitAlbumIds(const string & albumId,vector<string> & portraitAlbumIds)132 static int32_t GetPortraitAlbumIds(const string &albumId, vector<string> &portraitAlbumIds)
133 {
134     auto uniStore = MediaLibraryUnistoreManager::GetInstance().GetRdbStore();
135     if (uniStore == nullptr) {
136         MEDIA_ERR_LOG("uniStore is nullptr! failed query album order");
137         return E_HAS_DB_ERROR;
138     }
139     const std::string queryPortraitAlbumIds = "SELECT " + ALBUM_ID + " FROM " + ANALYSIS_ALBUM_TABLE + " WHERE " +
140         GROUP_TAG + " IN(SELECT " + GROUP_TAG + " FROM " + ANALYSIS_ALBUM_TABLE +
141         " WHERE " + ALBUM_ID + " = " + albumId + " AND " + ALBUM_SUBTYPE + " = " + to_string(PORTRAIT) +")";
142 
143     auto resultSet = uniStore->QuerySql(queryPortraitAlbumIds);
144     if (resultSet == nullptr) {
145         return E_DB_FAIL;
146     }
147     while (resultSet->GoToNextRow() == NativeRdb::E_OK) {
148         portraitAlbumIds.push_back(to_string(GetInt32Val(ALBUM_ID, resultSet)));
149     }
150     return E_OK;
151 }
152 
AddAnaLysisPhotoAssets(const vector<DataShareValuesBucket> & values)153 int32_t PhotoMapOperations::AddAnaLysisPhotoAssets(const vector<DataShareValuesBucket> &values)
154 {
155     auto rdbStore = MediaLibraryUnistoreManager::GetInstance().GetRdbStore();
156     if (rdbStore == nullptr) {
157         return E_HAS_DB_ERROR;
158     }
159     if (values.empty()) {
160         return 0;
161     }
162     std::shared_ptr<TransactionOperations> trans = make_shared<TransactionOperations>(__func__);
163     int32_t changedRows = 0;
164     int32_t err = NativeRdb::E_OK;
165     std::function<int(void)> func = [&]()->int {
166         for (const auto &value : values) {
167             int ret =  InsertAnalysisAsset(value, trans);
168             if (ret == E_HAS_DB_ERROR) {
169                 MEDIA_WARN_LOG("InsertAnalysisAsset for db error, changedRows now: %{public}d", changedRows);
170                 return ret;
171             }
172             if (ret > 0) {
173                 changedRows++;
174             }
175         }
176         return NativeRdb::E_OK;
177     };
178     err = trans->RetryTrans(func);
179     if (err != E_OK) {
180         MEDIA_ERR_LOG("AddAnaLysisPhotoAssets: trans retry fail!, ret:%{public}d", err);
181         return err;
182     }
183     bool isValid = false;
184     std::vector<string> albumIdList;
185     for (const auto &value : values) {
186         int32_t albumId = value.Get(PhotoMap::ALBUM_ID, isValid);
187         if (!isValid || albumId <= 0) {
188             MEDIA_WARN_LOG("Ignore failure on get album id when add assets. isValid: %{public}d, albumId: %{public}d",
189                 isValid, albumId);
190             continue;
191         }
192         albumIdList.push_back(to_string(albumId));
193     }
194     MediaLibraryRdbUtils::UpdateAnalysisAlbumInternal(rdbStore, albumIdList);
195     return changedRows;
196 }
197 
GetDismissAssetsPredicates(NativeRdb::RdbPredicates & rdbPredicate,vector<string> & updateAlbumIds,PhotoAlbumSubType subtype,const string & albumId,const vector<string> & assetsArray)198 static void GetDismissAssetsPredicates(NativeRdb::RdbPredicates &rdbPredicate, vector<string> &updateAlbumIds,
199     PhotoAlbumSubType subtype, const string &albumId, const vector<string> &assetsArray)
200 {
201     if (subtype == PhotoAlbumSubType::PORTRAIT) {
202         GetPortraitAlbumIds(albumId, updateAlbumIds);
203         rdbPredicate.In(MAP_ALBUM, updateAlbumIds);
204         rdbPredicate.And()->In(MAP_ASSET, assetsArray);
205     } else {
206         rdbPredicate.EqualTo(MAP_ALBUM, albumId);
207         rdbPredicate.And()->In(MAP_ASSET, assetsArray);
208         updateAlbumIds.push_back(albumId);
209     }
210 }
211 
DoDismissAssets(int32_t subtype,const string & albumId,const vector<string> & assetIds)212 int32_t DoDismissAssets(int32_t subtype, const string &albumId, const vector<string> &assetIds)
213 {
214     int32_t deleteRow = 0;
215     if (subtype == PhotoAlbumSubType::GROUP_PHOTO) {
216         NativeRdb::RdbPredicates rdbPredicate { VISION_IMAGE_FACE_TABLE };
217         rdbPredicate.In(MediaColumn::MEDIA_ID, assetIds);
218         deleteRow = MediaLibraryRdbStore::Delete(rdbPredicate);
219         if (deleteRow != 0 && MediaLibraryDataManagerUtils::IsNumber(albumId)) {
220             MediaLibraryAnalysisAlbumOperations::UpdateGroupPhotoAlbumById(atoi(albumId.c_str()));
221         }
222         return deleteRow;
223     }
224 
225     vector<string> updateAlbumIds;
226     NativeRdb::RdbPredicates rdbPredicate { ANALYSIS_PHOTO_MAP_TABLE };
227     GetDismissAssetsPredicates(rdbPredicate, updateAlbumIds,
228         static_cast<PhotoAlbumSubType>(subtype), albumId, assetIds);
229     deleteRow = MediaLibraryRdbStore::Delete(rdbPredicate);
230     if (deleteRow <= 0) {
231         return deleteRow;
232     }
233     MediaLibraryRdbUtils::UpdateAnalysisAlbumInternal(
234         MediaLibraryUnistoreManager::GetInstance().GetRdbStore(), updateAlbumIds, assetIds);
235     return deleteRow;
236 }
237 
DismissAssets(NativeRdb::RdbPredicates & predicates)238 int32_t PhotoMapOperations::DismissAssets(NativeRdb::RdbPredicates &predicates)
239 {
240     vector<string> whereArgsUri = predicates.GetWhereArgs();
241     MediaLibraryRdbStore::ReplacePredicatesUriToId(predicates);
242 
243     const vector<string> &whereArgsId = predicates.GetWhereArgs();
244     if (whereArgsId.size() == 0) {
245         MEDIA_ERR_LOG("No fileAsset to delete");
246         return E_INVALID_ARGUMENTS;
247     }
248     string strAlbumId = whereArgsId[0];
249     if (strAlbumId.empty()) {
250         MEDIA_ERR_LOG("Failed to get albumId");
251         return E_INVALID_ARGUMENTS;
252     }
253 
254     int32_t albumId = atoi(strAlbumId.c_str());
255     if (albumId <= 0) {
256         MEDIA_WARN_LOG("Ignore failure on get album id when remove assets, album updating would be lost");
257         return E_INVALID_ARGUMENTS;
258     }
259     string strSubtype = whereArgsId[whereArgsId.size() - 1];
260     int32_t subtype = atoi(strSubtype.c_str());
261     if (subtype != PhotoAlbumSubType::CLASSIFY && subtype != PhotoAlbumSubType::PORTRAIT &&
262         subtype != PhotoAlbumSubType::GROUP_PHOTO) {
263         MEDIA_ERR_LOG("Invalid album subtype: %{public}d", subtype);
264         return E_INVALID_ARGUMENTS;
265     }
266 
267     vector<string> assetsArray;
268     for (size_t i = 1; i < whereArgsId.size() - 1; i++) {
269         assetsArray.push_back(whereArgsId[i]);
270     }
271 
272     int32_t deleteRow = DoDismissAssets(subtype, strAlbumId, assetsArray);
273     if (deleteRow > 0) {
274         auto watch = MediaLibraryNotify::GetInstance();
275         for (size_t i = 1; i < whereArgsUri.size() - 1; i++) {
276             watch->Notify(MediaFileUtils::Encode(whereArgsUri[i]), NotifyType::NOTIFY_ALBUM_DISMISS_ASSET, albumId);
277         }
278     }
279     return deleteRow;
280 }
281 
RemovePhotoAssets(RdbPredicates & predicates)282 int32_t PhotoMapOperations::RemovePhotoAssets(RdbPredicates &predicates)
283 {
284     vector<string> uriWhereArgs = predicates.GetWhereArgs();
285     int32_t deleteRow = 0;
286     CHECK_AND_RETURN_RET_LOG(!uriWhereArgs.empty(), deleteRow, "Remove photo assets failed: args is empty");
287     MediaLibraryRdbStore::ReplacePredicatesUriToId(predicates);
288     vector<string> idWhereArgs = predicates.GetWhereArgs();
289     string strAlbumId = idWhereArgs[0];
290     int32_t albumId = atoi(strAlbumId.c_str());
291     CHECK_AND_WARN_LOG(albumId > 0, "Invalid album Id: %{public}s", strAlbumId.c_str());
292     idWhereArgs.erase(idWhereArgs.begin());
293     if (idWhereArgs.empty()) {
294         MEDIA_WARN_LOG("No photo assets to remove");
295         return deleteRow;
296     }
297 
298     MEDIA_INFO_LOG("Remove %{public}zu photo assets from album %{public}d", idWhereArgs.size(), albumId);
299 
300     shared_ptr<MediaLibraryRdbStore> rdbStore = MediaLibraryUnistoreManager::GetInstance().GetRdbStore();
301     CHECK_AND_RETURN_RET_LOG(rdbStore != nullptr, E_HAS_DB_ERROR, "rdbStore is null");
302 
303     MediaLibraryPhotoOperations::UpdateSourcePath(idWhereArgs);
304     // Assets that don't belong to any albums should be moved to trash
305     deleteRow = MediaLibraryRdbUtils::UpdateRemovedAssetToTrash(rdbStore, idWhereArgs);
306     CHECK_AND_RETURN_RET_LOG(deleteRow > 0, deleteRow,
307         "Update Removed Asset to Trash failed, ret: %{public}d", deleteRow);
308 
309     MediaLibraryRdbUtils::UpdateUserAlbumInternal(rdbStore, { strAlbumId });
310     MediaLibraryRdbUtils::UpdateSystemAlbumInternal(rdbStore, {
311         to_string(PhotoAlbumSubType::IMAGE), to_string(PhotoAlbumSubType::VIDEO),
312         to_string(PhotoAlbumSubType::FAVORITE), to_string(PhotoAlbumSubType::TRASH),
313         to_string(PhotoAlbumSubType::HIDDEN)
314     });
315 
316     uriWhereArgs.erase(uriWhereArgs.begin());
317 
318     auto watch = MediaLibraryNotify::GetInstance();
319     if (watch != nullptr) {
320         int trashAlbumId = watch->GetAlbumIdBySubType(PhotoAlbumSubType::TRASH);
321         if (trashAlbumId <= 0) {
322             MEDIA_ERR_LOG("Trash album id error: %{public}d, trash album notification unavailable", trashAlbumId);
323         }
324         for (size_t i = 0; i < uriWhereArgs.size(); i++) {
325             watch->Notify(MediaFileUtils::Encode(uriWhereArgs[i]), NotifyType::NOTIFY_REMOVE);
326             watch->Notify(MediaFileUtils::Encode(uriWhereArgs[i]), NotifyType::NOTIFY_ALBUM_REMOVE_ASSET, albumId);
327             watch->Notify(MediaFileUtils::Encode(uriWhereArgs[i]), NotifyType::NOTIFY_ALBUM_ADD_ASSET, trashAlbumId);
328         }
329     } else {
330         MEDIA_ERR_LOG("Failed to get notify instance, notification unavailable");
331     }
332 
333     DfxManager::GetInstance()->HandleDeleteBehavior(DfxType::ALBUM_REMOVE_PHOTOS, deleteRow, uriWhereArgs);
334     return deleteRow;
335 }
336 
IsQueryGroupPhotoAlbumAssets(const string & albumId,string & tagId,int32_t & isRemoved)337 bool IsQueryGroupPhotoAlbumAssets(const string &albumId, string &tagId, int32_t &isRemoved)
338 {
339     if (albumId.empty() || !MediaLibraryDataManagerUtils::IsNumber(albumId)) {
340         return false;
341     }
342     RdbPredicates predicates(ANALYSIS_ALBUM_TABLE);
343     predicates.EqualTo(PhotoAlbumColumns::ALBUM_ID, albumId);
344     vector<string> columns = {PhotoAlbumColumns::ALBUM_TYPE, PhotoAlbumColumns::ALBUM_SUBTYPE, TAG_ID, IS_REMOVED};
345     auto resultSet = MediaLibraryRdbStore::QueryWithFilter(predicates, columns);
346     if (resultSet == nullptr || resultSet->GoToFirstRow() != E_OK) {
347         return false;
348     }
349     int32_t albumType = GetInt32Val(PhotoAlbumColumns::ALBUM_TYPE, resultSet);
350     int32_t albumSubtype = GetInt32Val(PhotoAlbumColumns::ALBUM_SUBTYPE, resultSet);
351     tagId = GetStringVal(TAG_ID, resultSet);
352     isRemoved = GetInt32Val(IS_REMOVED, resultSet);
353     return albumType == PhotoAlbumType::SMART && albumSubtype == PhotoAlbumSubType::GROUP_PHOTO;
354 }
355 
QueryGroupPhotoAlbumAssets(const string & albumId,const string & tagId,const vector<string> & columns)356 shared_ptr<OHOS::NativeRdb::ResultSet> QueryGroupPhotoAlbumAssets(const string &albumId, const string &tagId,
357     const vector<string> &columns)
358 {
359     string strColumns;
360     for (size_t i = 0; i < columns.size(); i++) {
361         strColumns.append("P." + columns[i]);
362         if (i != columns.size() - 1) {
363             strColumns.append(", ");
364         }
365     }
366     string strTags = "'";
367     int32_t albumTagCount = 1;
368     for (char c : tagId) {
369         if (c == ',') {
370             strTags.append("', '");
371             albumTagCount++;
372         } else {
373             strTags.push_back(c);
374         }
375     }
376     strTags.append("'");
377     string sql = "SELECT " + strColumns + " FROM " + VISION_IMAGE_FACE_TABLE + " F INNER JOIN " +
378         ANALYSIS_ALBUM_TABLE + " AA ON F." + TAG_ID + " = AA." + TAG_ID + " AND AA." + GROUP_TAG + " IN (" + strTags +
379         ") INNER JOIN " + ANALYSIS_PHOTO_MAP_TABLE + " ON " + MAP_ALBUM + " = AA." + PhotoAlbumColumns::ALBUM_ID +
380         " AND " + MAP_ASSET + " = F." + MediaColumn::MEDIA_ID + " INNER JOIN " + PhotoColumn::PHOTOS_TABLE +
381         " P ON P." + MediaColumn::MEDIA_ID + " = F." + MediaColumn::MEDIA_ID + " AND " +
382         MediaColumn::MEDIA_DATE_TRASHED + " = 0 AND " + MediaColumn::MEDIA_HIDDEN + " = 0 AND " +
383         MediaColumn::MEDIA_TIME_PENDING + " = 0 GROUP BY P." + MediaColumn::MEDIA_ID +
384         " HAVING COUNT(" + GROUP_TAG + ") = " + TOTAL_FACES + " AND " +
385         " COUNT(DISTINCT " + GROUP_TAG +") = " + to_string(albumTagCount) + ";";
386     return MediaLibraryUnistoreManager::GetInstance().GetRdbStore()->QuerySql(sql);
387 }
388 
QueryPhotoAssets(const RdbPredicates & rdbPredicate,const vector<string> & columns)389 shared_ptr<OHOS::NativeRdb::ResultSet> PhotoMapOperations::QueryPhotoAssets(const RdbPredicates &rdbPredicate,
390     const vector<string> &columns)
391 {
392     string albumId = rdbPredicate.GetWhereArgs()[0];
393     string tagId;
394     int32_t isRemoved;
395     if (IsQueryGroupPhotoAlbumAssets(albumId, tagId, isRemoved)) {
396         if (isRemoved == ALBUM_IS_REMOVED) {
397             return nullptr;
398         }
399         return QueryGroupPhotoAlbumAssets(albumId, tagId, columns);
400     }
401     return MediaLibraryRdbStore::QueryWithFilter(rdbPredicate, columns);
402 }
403 } // namespace OHOS::Media