1 /*
2  * Copyright (C) 2024 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 "PhotoAlbumLPathOperation"
16 
17 #include "photo_album_lpath_operation.h"
18 
19 #include <algorithm>
20 #include <numeric>
21 
22 #include "media_log.h"
23 #include "medialibrary_errno.h"
24 #include "userfile_manager_types.h"
25 #include "media_file_utils.h"
26 #include "result_set_utils.h"
27 #include "photo_album_merge_operation.h"
28 
29 namespace OHOS::Media {
30 std::shared_ptr<PhotoAlbumLPathOperation> PhotoAlbumLPathOperation::instance_ = nullptr;
31 std::mutex PhotoAlbumLPathOperation::objMutex_;
32 
GetInstance()33 PhotoAlbumLPathOperation &PhotoAlbumLPathOperation::GetInstance()
34 {
35     std::lock_guard<std::mutex> lock(PhotoAlbumLPathOperation::objMutex_);
36     if (PhotoAlbumLPathOperation::instance_ == nullptr) {
37         PhotoAlbumLPathOperation::instance_ = std::make_shared<PhotoAlbumLPathOperation>();
38     }
39     return *PhotoAlbumLPathOperation::instance_;
40 }
41 
Start()42 PhotoAlbumLPathOperation &PhotoAlbumLPathOperation::Start()
43 {
44     this->isContinue_.store(true);
45     return *this;
46 }
47 
Stop()48 void PhotoAlbumLPathOperation::Stop()
49 {
50     this->isContinue_.store(false);
51 }
52 
SetRdbStore(const std::shared_ptr<MediaLibraryRdbStore> & rdbStorePtr)53 PhotoAlbumLPathOperation &PhotoAlbumLPathOperation::SetRdbStore(
54     const std::shared_ptr<MediaLibraryRdbStore> &rdbStorePtr)
55 {
56     this->rdbStorePtr_ = rdbStorePtr;
57     return *this;
58 }
59 
GetAlbumAffectedCount() const60 int32_t PhotoAlbumLPathOperation::GetAlbumAffectedCount() const
61 {
62     return this->albumAffectedCount_;
63 }
64 
CleanInvalidPhotoAlbums()65 PhotoAlbumLPathOperation &PhotoAlbumLPathOperation::CleanInvalidPhotoAlbums()
66 {
67     if (!this->isContinue_.load()) {
68         MEDIA_INFO_LOG("Media_Operation: clean invalid album operation is not allowed.");
69         return *this;
70     }
71     std::vector<PhotoAlbumInfoPo> invalidAlbumList = this->GetInvalidPhotoAlbums();
72     if (invalidAlbumList.empty()) {
73         MEDIA_INFO_LOG("Media_Operation: no invalid album found.");
74         return *this;
75     }
76     // Log the invalid albums
77     int32_t index = 0;
78     int32_t total = static_cast<int32_t>(invalidAlbumList.size());
79     for (const auto &albumInfo : invalidAlbumList) {
80         MEDIA_INFO_LOG("Media_Operation: clean invalid album! index: %{public}d / %{public}d, Object: %{public}s",
81             ++index,
82             total,
83             albumInfo.ToString().c_str());
84     }
85     this->albumAffectedCount_ += total;
86     // Delete the invalid albums
87     std::string sql = this->SQL_PHOTO_ALBUM_EMPTY_DELETE;
88     int32_t ret = this->rdbStorePtr_->ExecuteSql(sql);
89     if (ret != NativeRdb::E_OK) {
90         MEDIA_ERR_LOG("Media_Operation: Failed to exec: %{public}s", sql.c_str());
91     }
92     MEDIA_INFO_LOG("Media_Operation: clean invalid album completed! "
93                    "total: %{public}d, ret: %{public}d",
94         total,
95         ret);
96     return *this;
97 }
98 
GetInvalidPhotoAlbums()99 std::vector<PhotoAlbumInfoPo> PhotoAlbumLPathOperation::GetInvalidPhotoAlbums()
100 {
101     if (this->rdbStorePtr_ == nullptr) {
102         return {};
103     }
104     std::string querySql = this->SQL_PHOTO_ALBUM_EMPTY_QUERY;
105     auto resultSet = this->rdbStorePtr_->QuerySql(querySql);
106     if (resultSet == nullptr) {
107         return {};
108     }
109     std::vector<PhotoAlbumInfoPo> result;
110     while (resultSet->GoToNextRow() == NativeRdb::E_OK) {
111         result.emplace_back(PhotoAlbumInfoPo().Parse(resultSet));
112     }
113     return result;
114 }
115 
CleanDuplicatePhotoAlbums()116 PhotoAlbumLPathOperation &PhotoAlbumLPathOperation::CleanDuplicatePhotoAlbums()
117 {
118     if (!this->isContinue_.load()) {
119         MEDIA_INFO_LOG("Media_Operation: clean invalid album operation is not allowed.");
120         return *this;
121     }
122     std::vector<PhotoAlbumInfoPo> mainAlbumInfoList = this->GetDuplicatelPathAlbumInfoMain();
123     if (mainAlbumInfoList.empty()) {
124         MEDIA_INFO_LOG("Media_Operation: no duplicate album found.");
125         return *this;
126     }
127     // Execute & Log the duplicate albums
128     int index = 0;
129     int32_t total = static_cast<int32_t>(mainAlbumInfoList.size());
130     for (const auto &albumInfo : mainAlbumInfoList) {
131         int32_t ret = this->CleanDuplicatePhotoAlbum(albumInfo);
132         MEDIA_INFO_LOG("Media_Operation: clean duplicate album (MAIN) completed! "
133                        "isContinue: %{public}d, index: %{public}d / %{public}d, ret: %{public}d, Object: %{public}s",
134             this->isContinue_.load(),
135             ++index,
136             total,
137             ret,
138             albumInfo.ToString().c_str());
139         if (!this->isContinue_.load()) {
140             break;
141         }
142     }
143     this->albumAffectedCount_ += index;
144     return *this;
145 }
146 
CleanDuplicatePhotoAlbum(const PhotoAlbumInfoPo & mainAlbumInfo)147 int32_t PhotoAlbumLPathOperation::CleanDuplicatePhotoAlbum(const PhotoAlbumInfoPo &mainAlbumInfo)
148 {
149     bool isInvalid = mainAlbumInfo.albumId <= 0 || mainAlbumInfo.lPath.empty() || mainAlbumInfo.albumName.empty();
150     if (isInvalid) {
151         return NativeRdb::E_OK;
152     }
153     std::vector<PhotoAlbumInfoPo> subAlbumInfoList = this->GetDuplicatelPathAlbumInfoSub(mainAlbumInfo);
154     if (subAlbumInfoList.empty()) {
155         return NativeRdb::E_OK;
156     }
157     int32_t index = 0;
158     int32_t total = static_cast<int32_t>(subAlbumInfoList.size());
159     for (const auto &subAlbumInfo : subAlbumInfoList) {
160         index++;
161         bool isInvalidSub = subAlbumInfo.albumId <= 0 || subAlbumInfo.lPath.empty() || subAlbumInfo.albumName.empty();
162         isInvalidSub = isInvalidSub || this->MergePhotoAlbum(mainAlbumInfo, subAlbumInfo) != NativeRdb::E_OK;
163         MEDIA_INFO_LOG("Media_Operation: clean duplicate album (sub) completed! "
164                        "index: %{public}d / %{public}d, "
165                        "isInvalid: %{public}d, mainAlbum: %{public}s, subAlbum: %{public}s",
166             index,
167             total,
168             isInvalidSub,
169             mainAlbumInfo.ToString().c_str(),
170             subAlbumInfo.ToString().c_str());
171     }
172     return this->UpdateAlbumInfoFromAlbumPluginByAlbumId(mainAlbumInfo);
173 }
174 
CleanEmptylPathPhotoAlbums()175 PhotoAlbumLPathOperation &PhotoAlbumLPathOperation::CleanEmptylPathPhotoAlbums()
176 {
177     if (!this->isContinue_.load()) {
178         MEDIA_INFO_LOG("Media_Operation: clean invalid album operation is not allowed.");
179         return *this;
180     }
181     std::vector<PhotoAlbumInfoPo> subAlbumInfoList = this->GetEmptylPathAlbumInfo();
182     if (subAlbumInfoList.empty()) {
183         MEDIA_INFO_LOG("Media_Operation: no empty lPath album found.");
184         return *this;
185     }
186     // Execute & Log the empty albums
187     int32_t index = 0;
188     int32_t total = static_cast<int32_t>(subAlbumInfoList.size());
189     for (const auto &subAlbumInfo : subAlbumInfoList) {
190         index++;
191         int32_t ret = this->CleanEmptylPathPhotoAlbum(subAlbumInfo);
192         MEDIA_INFO_LOG("Media_Operation: clean empty lPath album completed! "
193                        "isContinue: %{public}d, index: %{public}d / %{public}d, ret: %{public}d, subAlbum: %{public}s",
194             this->isContinue_.load(),
195             index,
196             total,
197             ret,
198             subAlbumInfo.ToString().c_str());
199         if (!this->isContinue_.load()) {
200             break;
201         }
202     }
203     this->albumAffectedCount_ += index;
204     return *this;
205 }
206 
ToString(const std::vector<NativeRdb::ValueObject> & values)207 std::string PhotoAlbumLPathOperation::ToString(const std::vector<NativeRdb::ValueObject> &values)
208 {
209     std::vector<std::string> result;
210     std::string str;
211     for (auto &value : values) {
212         value.GetString(str);
213         result.emplace_back(str + ", ");
214     }
215     return std::accumulate(result.begin(), result.end(), std::string());
216 }
217 
UpdateAlbumInfoFromAlbumPluginByAlbumId(const PhotoAlbumInfoPo & albumInfo)218 int32_t PhotoAlbumLPathOperation::UpdateAlbumInfoFromAlbumPluginByAlbumId(const PhotoAlbumInfoPo &albumInfo)
219 {
220     if (this->rdbStorePtr_ == nullptr) {
221         return NativeRdb::E_ERROR;
222     }
223     bool isInvalid = albumInfo.albumId <= 0 || albumInfo.lPath.empty();
224     if (isInvalid) {
225         return NativeRdb::E_ERROR;
226     }
227     std::string sql = this->SQL_PHOTO_ALBUM_SYNC_BUNDLE_NAME_UPDATE;
228     const std::vector<NativeRdb::ValueObject> bindArgs = {
229         albumInfo.lPath, albumInfo.lPath, albumInfo.lPath, albumInfo.albumId, albumInfo.lPath};
230     int32_t ret = this->rdbStorePtr_->ExecuteSql(sql, bindArgs);
231     if (ret != NativeRdb::E_OK) {
232         MEDIA_ERR_LOG("Media_Operation: Failed to exec: %{public}s, bindArgs: %{public}s",
233             sql.c_str(),
234             this->ToString(bindArgs).c_str());
235         return ret;
236     }
237     return NativeRdb::E_OK;
238 }
239 
UpdateAlbumLPathByAlbumId(const PhotoAlbumInfoPo & albumInfo)240 int32_t PhotoAlbumLPathOperation::UpdateAlbumLPathByAlbumId(const PhotoAlbumInfoPo &albumInfo)
241 {
242     if (this->rdbStorePtr_ == nullptr) {
243         return NativeRdb::E_ERROR;
244     }
245     bool isInvalid = albumInfo.lPath.empty() || albumInfo.albumId <= 0;
246     if (isInvalid) {
247         return NativeRdb::E_ERROR;
248     }
249     std::string sql = this->SQL_PHOTO_ALBUM_UPDATE_LPATH_BY_ALBUM_ID;
250     const std::vector<NativeRdb::ValueObject> bindArgs = {albumInfo.lPath, albumInfo.albumId, albumInfo.lPath};
251     int32_t ret = this->rdbStorePtr_->ExecuteSql(sql, bindArgs);
252     if (ret != NativeRdb::E_OK) {
253         MEDIA_ERR_LOG("Media_Operation: Failed to exec: %{public}s, bindArgs: %{public}s",
254             sql.c_str(),
255             this->ToString(bindArgs).c_str());
256         return ret;
257     }
258     return NativeRdb::E_OK;
259 }
260 
GetLatestAlbumInfoBylPath(const std::string & lPath)261 PhotoAlbumInfoPo PhotoAlbumLPathOperation::GetLatestAlbumInfoBylPath(const std::string &lPath)
262 {
263     if (this->rdbStorePtr_ == nullptr || lPath.empty()) {
264         return PhotoAlbumInfoPo();
265     }
266     std::string sql = this->SQL_PHOTO_ALBUM_QUERY_BY_LPATH;
267     const std::vector<NativeRdb::ValueObject> bindArgs = {lPath};
268     auto resultSet = this->rdbStorePtr_->QuerySql(sql, bindArgs);
269     if (resultSet == nullptr || resultSet->GoToFirstRow() != NativeRdb::E_OK) {
270         return PhotoAlbumInfoPo();
271     }
272     return PhotoAlbumInfoPo().Parse(resultSet);
273 }
274 
CleanEmptylPathPhotoAlbum(const PhotoAlbumInfoPo & subAlbumInfo)275 int32_t PhotoAlbumLPathOperation::CleanEmptylPathPhotoAlbum(const PhotoAlbumInfoPo &subAlbumInfo)
276 {
277     bool isInvalid = subAlbumInfo.albumId <= 0 || subAlbumInfo.lPath.empty() || subAlbumInfo.albumName.empty();
278     if (isInvalid) {
279         return NativeRdb::E_ERROR;
280     }
281     PhotoAlbumInfoPo mainAlbumInfo = this->GetLatestAlbumInfoBylPath(subAlbumInfo.lPath);
282     bool isMainAlbumInvalid =
283         mainAlbumInfo.albumId <= 0 || mainAlbumInfo.lPath.empty() || mainAlbumInfo.albumName.empty();
284     bool isSuccessed = false;
285     if (!isMainAlbumInvalid) {
286         isSuccessed = this->MergePhotoAlbum(mainAlbumInfo, subAlbumInfo) == NativeRdb::E_OK;
287     } else {
288         isSuccessed = this->UpdateAlbumLPathByAlbumId(subAlbumInfo) == NativeRdb::E_OK;
289         mainAlbumInfo = subAlbumInfo;
290     }
291     isSuccessed = isSuccessed && this->UpdateAlbumInfoFromAlbumPluginByAlbumId(mainAlbumInfo) == NativeRdb::E_OK;
292     MEDIA_INFO_LOG("Media_Operation: clean empty lPath album (sub) completed! "
293                    "isSuccessed: %{public}d, mainAlbum: %{public}s, subAlbum: %{public}s",
294         isSuccessed,
295         mainAlbumInfo.ToString().c_str(),
296         subAlbumInfo.ToString().c_str());
297     return isSuccessed ? NativeRdb::E_OK : NativeRdb::E_ERROR;
298 }
299 
MergePhotoAlbum(const PhotoAlbumInfoPo & mainAlbumInfo,const PhotoAlbumInfoPo & subAlbumInfo)300 int32_t PhotoAlbumLPathOperation::MergePhotoAlbum(
301     const PhotoAlbumInfoPo &mainAlbumInfo, const PhotoAlbumInfoPo &subAlbumInfo)
302 {
303     bool isInvalid = this->rdbStorePtr_ == nullptr || mainAlbumInfo.albumId <= 0 || subAlbumInfo.albumId <= 0;
304     if (isInvalid) {
305         return NativeRdb::E_ERROR;
306     }
307     return PhotoAlbumMergeOperation()
308         .SetRdbStore(this->rdbStorePtr_)
309         .MergeAlbum(subAlbumInfo.albumId, mainAlbumInfo.albumId);
310 }
311 
GetDuplicatelPathAlbumInfoMain()312 std::vector<PhotoAlbumInfoPo> PhotoAlbumLPathOperation::GetDuplicatelPathAlbumInfoMain()
313 {
314     if (this->rdbStorePtr_ == nullptr) {
315         return {};
316     }
317     std::string querySql = this->SQL_PHOTO_ALBUM_DUPLICATE_LPATH_MAIN_QUERY;
318     auto resultSet = this->rdbStorePtr_->QuerySql(querySql);
319     if (resultSet == nullptr) {
320         return {};
321     }
322     std::vector<PhotoAlbumInfoPo> result;
323     while (resultSet->GoToNextRow() == NativeRdb::E_OK) {
324         result.emplace_back(PhotoAlbumInfoPo().Parse(resultSet));
325     }
326     return result;
327 }
328 
GetDuplicatelPathAlbumInfoSub(const PhotoAlbumInfoPo & albumInfo)329 std::vector<PhotoAlbumInfoPo> PhotoAlbumLPathOperation::GetDuplicatelPathAlbumInfoSub(const PhotoAlbumInfoPo &albumInfo)
330 {
331     bool isInvalid = this->rdbStorePtr_ == nullptr || albumInfo.albumId <= 0 || albumInfo.lPath.empty();
332     if (isInvalid) {
333         return {};
334     }
335     std::string sql = this->SQL_PHOTO_ALBUM_DUPLICATE_LPATH_SUB_QUERY;
336     const std::vector<NativeRdb::ValueObject> bindArgs = {albumInfo.albumId, albumInfo.lPath};
337     auto resultSet = this->rdbStorePtr_->QuerySql(sql, bindArgs);
338     if (resultSet == nullptr) {
339         return {};
340     }
341     std::vector<PhotoAlbumInfoPo> result;
342     while (resultSet->GoToNextRow() == NativeRdb::E_OK) {
343         result.emplace_back(PhotoAlbumInfoPo().Parse(resultSet));
344     }
345     return result;
346 }
347 
GetEmptylPathAlbumInfo()348 std::vector<PhotoAlbumInfoPo> PhotoAlbumLPathOperation::GetEmptylPathAlbumInfo()
349 {
350     if (this->rdbStorePtr_ == nullptr) {
351         return {};
352     }
353     std::string querySql = this->SQL_PHOTO_ALBUM_FIX_LPATH_QUERY;
354     auto resultSet = this->rdbStorePtr_->QuerySql(querySql);
355     if (resultSet == nullptr) {
356         return {};
357     }
358     std::vector<PhotoAlbumInfoPo> result;
359     while (resultSet->GoToNextRow() == NativeRdb::E_OK) {
360         result.emplace_back(PhotoAlbumInfoPo().Parse(resultSet));
361     }
362     return result;
363 }
364 }  // namespace OHOS::Media