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 #include "photo_album_dao.h"
16 
17 #include <vector>
18 #include <string>
19 
20 #include "rdb_store.h"
21 #include "media_log.h"
22 #include "result_set_utils.h"
23 #include "album_plugin_config.h"
24 #include "userfile_manager_types.h"
25 #include "medialibrary_errno.h"
26 #include "backup_database_utils.h"
27 #include "medialibrary_rdb_transaction.h"
28 
29 namespace OHOS::Media {
ToLower(const std::string & str)30 std::string StringUtils::ToLower(const std::string &str)
31 {
32     std::string lowerStr;
33     std::transform(
34         str.begin(), str.end(), std::back_inserter(lowerStr), [](unsigned char c) { return std::tolower(c); });
35     return lowerStr;
36 }
37 
38 /**
39  * @brief Check the AlbumName unique or not. true - unique, false - not unique.
40  */
CheckAlbumNameUnique(const std::string & albumName,const std::string & lPath)41 bool PhotoAlbumDao::CheckAlbumNameUnique(const std::string &albumName, const std::string &lPath)
42 {
43     std::vector<NativeRdb::ValueObject> bindArgs = {albumName, lPath};
44     std::string querySql = this->SQL_PHOTO_ALBUM_CHECK_ALBUM_NAME_UNIQUE;
45     if (this->mediaLibraryRdb_ == nullptr) {
46         MEDIA_ERR_LOG("Media_Restore: mediaLibraryRdb_ is null.");
47         return true;
48     }
49     auto resultSet = this->mediaLibraryRdb_->QuerySql(querySql, bindArgs);
50     if (resultSet == nullptr || resultSet->GoToNextRow() != NativeRdb::E_OK) {
51         MEDIA_ERR_LOG("Media_Restore: Query resultSql is null.");
52         return true;
53     }
54     int32_t count = GetInt32Val("count", resultSet);
55     return count == 0;
56 }
57 
58 /**
59  * @brief Find the Unique Album Name.
60  */
FindUniqueAlbumName(const PhotoAlbumDao::PhotoAlbumRowData & photoAlbum)61 std::string PhotoAlbumDao::FindUniqueAlbumName(const PhotoAlbumDao::PhotoAlbumRowData &photoAlbum)
62 {
63     if (photoAlbum.lPath.empty() || photoAlbum.albumName.empty()) {
64         MEDIA_ERR_LOG("Media_Restore: Invalid album data");
65         return "";
66     }
67     const std::string lPath = photoAlbum.lPath;
68     // The PhotoAlbum is cached.
69     PhotoAlbumDao::PhotoAlbumRowData albumDataInCache;
70     if (this->photoAlbumCache_.Find(StringUtils::ToLower(lPath), albumDataInCache)) {
71         return albumDataInCache.albumName;
72     }
73     // Check if the album name is unique.
74     std::string albumName = photoAlbum.albumName;
75     int32_t sequence = 1;
76     bool isUnique = this->CheckAlbumNameUnique(albumName, photoAlbum.lPath);
77     while (!isUnique && sequence < this->MAX_ALBUM_NAME_SEQUENCE) {
78         albumName = photoAlbum.albumName + " " + std::to_string(sequence);
79         sequence++;
80         isUnique = this->CheckAlbumNameUnique(albumName, photoAlbum.lPath);
81     }
82     MEDIA_INFO_LOG("Media_Restore: FindUniqueAlbumName, old name: %{public}s, new name: %{public}s, lPath: %{public}s",
83         photoAlbum.albumName.c_str(),
84         albumName.c_str(),
85         photoAlbum.lPath.c_str());
86     return albumName;
87 }
88 
89 /**
90  * @brief get the data of PhotoAlbum table, to find the differenct between the PhotoAlbum and the gallery_album
91  */
GetPhotoAlbums()92 std::vector<PhotoAlbumDao::PhotoAlbumRowData> PhotoAlbumDao::GetPhotoAlbums()
93 {
94     std::vector<PhotoAlbumDao::PhotoAlbumRowData> result;
95     std::string querySql = this->SQL_PHOTO_ALBUM_SELECT;
96     int rowCount = 0;
97     int offset = 0;
98     int pageSize = 200;
99     do {
100         std::vector<NativeRdb::ValueObject> bindArgs = {offset, pageSize};
101         if (this->mediaLibraryRdb_ == nullptr) {
102             MEDIA_ERR_LOG("Media_Restore: mediaLibraryRdb_ is null.");
103             break;
104         }
105         auto resultSet = this->mediaLibraryRdb_->QuerySql(querySql, bindArgs);
106         if (resultSet == nullptr) {
107             MEDIA_ERR_LOG("Media_Restore: Query resultSql is null.");
108             break;
109         }
110         while (resultSet->GoToNextRow() == NativeRdb::E_OK) {
111             PhotoAlbumDao::PhotoAlbumRowData albumRowData;
112             albumRowData.albumId = GetInt32Val(this->FIELD_NAME_ALBUM_ID, resultSet);
113             albumRowData.albumName = GetStringVal(this->FIELD_NAME_ALBUM_NAME, resultSet);
114             albumRowData.bundleName = GetStringVal(this->FIELD_NAME_BUNDLE_NAME, resultSet);
115             albumRowData.albumType = GetInt32Val(this->FIELD_NAME_ALBUM_TYPE, resultSet);
116             albumRowData.albumSubType = GetInt32Val(this->FIELD_NAME_ALBUM_SUBTYPE, resultSet);
117             albumRowData.lPath = GetStringVal(this->FIELD_NAME_LPATH, resultSet);
118             albumRowData.priority = GetInt32Val(this->FIELD_NAME_PRIORITY, resultSet);
119             result.emplace_back(albumRowData);
120         }
121         // Check if there are more rows to fetch.
122         resultSet->GetRowCount(rowCount);
123         offset += pageSize;
124     } while (rowCount > 0);
125     return result;
126 }
127 
128 /**
129  * @brief Get and cache PhotoAlbum info by lPath from PhotoAlbum table.
130  */
GetPhotoAlbum(const std::string & lPath)131 PhotoAlbumDao::PhotoAlbumRowData PhotoAlbumDao::GetPhotoAlbum(const std::string &lPath)
132 {
133     std::unique_lock<std::mutex> lock(this->cacheLock_);
134     // find the PhotoAlbum info by lPath in cache
135     PhotoAlbumDao::PhotoAlbumRowData albumRowData;
136     if (this->photoAlbumCache_.Find(StringUtils::ToLower(lPath), albumRowData)) {
137         return albumRowData;
138     }
139     MEDIA_INFO_LOG("Media_Restore: can not find the PhotoAlbum info by lPath in cache."
140                    " lPath=%{public}s, lPath in cache=%{public}s",
141         lPath.c_str(),
142         StringUtils::ToLower(lPath).c_str());
143     // query the PhotoAlbum info by lPath from PhotoAlbum table
144     std::vector<NativeRdb::ValueObject> bindArgs = {lPath};
145     std::string querySql = this->SQL_PHOTO_ALBUM_SELECT_BY_LPATH;
146     if (this->mediaLibraryRdb_ == nullptr) {
147         MEDIA_ERR_LOG("Media_Restore: mediaLibraryRdb_ is null.");
148         return albumRowData;
149     }
150     auto resultSet = this->mediaLibraryRdb_->QuerySql(querySql, bindArgs);
151     if (resultSet == nullptr || resultSet->GoToFirstRow() != NativeRdb::E_OK) {
152         MEDIA_WARN_LOG("Media_Restore: can not find the PhotoAlbum info by lPath [%{public}s] in PhotoAlbum table.",
153             lPath.c_str());
154         return albumRowData;
155     }
156     albumRowData.albumId = GetInt32Val(this->FIELD_NAME_ALBUM_ID, resultSet);
157     albumRowData.albumName = GetStringVal(this->FIELD_NAME_ALBUM_NAME, resultSet);
158     albumRowData.bundleName = GetStringVal(this->FIELD_NAME_BUNDLE_NAME, resultSet);
159     albumRowData.albumType = GetInt32Val(this->FIELD_NAME_ALBUM_TYPE, resultSet);
160     albumRowData.albumSubType = GetInt32Val(this->FIELD_NAME_ALBUM_SUBTYPE, resultSet);
161     albumRowData.lPath = GetStringVal(this->FIELD_NAME_LPATH, resultSet);
162     albumRowData.priority = GetInt32Val(this->FIELD_NAME_PRIORITY, resultSet);
163     // cache the PhotoAlbum info by lPath
164     this->photoAlbumCache_.Insert(StringUtils::ToLower(lPath), albumRowData);
165     MEDIA_INFO_LOG("Media_Restore: add the PhotoAlbum info by lPath into cache."
166                    " lPath=%{public}s, lPath in cache=%{public}s",
167         lPath.c_str(),
168         StringUtils::ToLower(albumRowData.lPath).c_str());
169     return albumRowData;
170 }
171 
172 /**
173  * @brief Get and cache PhotoAlbum info by lPath, if PhotoAlbum not exists, create it.
174  */
GetOrCreatePhotoAlbum(const PhotoAlbumRowData & album)175 PhotoAlbumDao::PhotoAlbumRowData PhotoAlbumDao::GetOrCreatePhotoAlbum(const PhotoAlbumRowData &album)
176 {
177     // validate inputs
178     if (album.lPath.empty()) {
179         MEDIA_ERR_LOG(
180             "Media_Restore: Invalid album data, lPath is empty. Object: %{public}s", this->ToString(album).c_str());
181         return album;
182     }
183     std::unique_lock<std::mutex> lock(this->photoAlbumCreateLock_);
184     // try to get from cache
185     PhotoAlbumDao::PhotoAlbumRowData albumRowData = this->GetPhotoAlbum(album.lPath);
186     if (!albumRowData.lPath.empty()) {
187         return albumRowData;
188     }
189     std::string uniqueAlbumName = this->FindUniqueAlbumName(album);
190     std::vector<NativeRdb::ValueObject> bindArgs = {
191         album.albumType, album.albumSubType, uniqueAlbumName, album.bundleName, album.lPath, album.priority};
192     if (this->mediaLibraryRdb_ == nullptr) {
193         MEDIA_ERR_LOG("Media_Restore: mediaLibraryRdb_ is null.");
194         return album;
195     }
196     auto err = BackupDatabaseUtils::ExecuteSQL(this->mediaLibraryRdb_, this->SQL_PHOTO_ALBUM_INSERT, bindArgs);
197     if (err != NativeRdb::E_OK) {
198         MEDIA_ERR_LOG("Media_Restore: INSERT INTO PhotoAlbum failed, err = %{public}d, executeSql = %{public}s, "
199                       "bindArgs = %{public}s",
200             err,
201             this->SQL_PHOTO_ALBUM_INSERT.c_str(),
202             this->ToString(bindArgs).c_str());
203         return album;
204     }
205     MEDIA_INFO_LOG("Media_Restore: INSERT INTO PhotoAlbum success, Object: %{public}s", this->ToString(album).c_str());
206     return this->GetPhotoAlbum(album.lPath);
207 }
208 
ToString(const std::vector<NativeRdb::ValueObject> & bindArgs)209 std::string PhotoAlbumDao::ToString(const std::vector<NativeRdb::ValueObject> &bindArgs)
210 {
211     std::string args;
212     for (auto &arg : bindArgs) {
213         std::string tempStr;
214         arg.GetString(tempStr);
215         args += tempStr + ", ";
216     }
217     return args;
218 }
219 
220 /**
221  * @brief restore the PhotoAlbum table
222  */
RestoreAlbums(std::vector<PhotoAlbumDao::PhotoAlbumRowData> & photoAlbums)223 int32_t PhotoAlbumDao::RestoreAlbums(std::vector<PhotoAlbumDao::PhotoAlbumRowData> &photoAlbums)
224 {
225     if (photoAlbums.empty()) {
226         MEDIA_INFO_LOG("Media_Restore: albumInfos are empty");
227         return NativeRdb::E_OK;
228     }
229     int32_t err = NativeRdb::E_OK;
230     if (this->mediaLibraryRdb_ == nullptr) {
231         MEDIA_ERR_LOG("Media_Restore: mediaLibraryRdb_ is null.");
232         return E_FAIL;
233     }
234     int32_t count = 0;
235     for (const PhotoAlbumDao::PhotoAlbumRowData &data : photoAlbums) {
236         if (data.lPath.empty() || data.albumName.empty()) {
237             MEDIA_ERR_LOG("Media_Restore: restore albums failed, lPath or albumName is empty. Object: %{public}s",
238                 this->ToString(data).c_str());
239             continue;
240         }
241         std::vector<NativeRdb::ValueObject> bindArgs = {
242             data.albumType, data.albumSubType, data.albumName, data.bundleName, data.lPath, data.priority};
243         err = BackupDatabaseUtils::ExecuteSQL(this->mediaLibraryRdb_, this->SQL_PHOTO_ALBUM_INSERT, bindArgs);
244         if (err != NativeRdb::E_OK) {
245             MEDIA_ERR_LOG("Media_Restore: restore albums failed, "
246                           "err = %{public}d, executeSql = %{public}s, bindArgs = %{public}s",
247                 err,
248                 this->SQL_PHOTO_ALBUM_INSERT.c_str(),
249                 this->ToString(bindArgs).c_str());
250             continue;
251         }
252         count++;
253     }
254     MEDIA_INFO_LOG("Media_Restore: restore albums success, total %{public}d, restored %{public}d",
255         static_cast<int32_t>(photoAlbums.size()),
256         count);
257     return NativeRdb::E_OK;
258 }
259 
260 /**
261  * @brief Build PhotoAlbumRowData for ScreenRecorder.
262  */
BuildAlbumInfoOfRecorders()263 PhotoAlbumDao::PhotoAlbumRowData PhotoAlbumDao::BuildAlbumInfoOfRecorders()
264 {
265     PhotoAlbumDao::PhotoAlbumRowData albumInfo;
266     // bind albumName and bundleName by lPath.
267     albumInfo.albumName = AlbumPlugin::ALBUM_NAME_SCREEN_RECORDS;
268     albumInfo.bundleName = AlbumPlugin::BUNDLE_NAME_SCREEN_RECORDS;
269     albumInfo.lPath = AlbumPlugin::LPATH_SCREEN_RECORDS;
270     albumInfo.albumType = static_cast<int32_t>(PhotoAlbumType::SOURCE);
271     albumInfo.albumSubType = static_cast<int32_t>(PhotoAlbumSubType::SOURCE_GENERIC);
272     albumInfo.priority = 1;
273     return albumInfo;
274 }
275 
LoadPhotoAlbums()276 void PhotoAlbumDao::LoadPhotoAlbums()
277 {
278     std::vector<PhotoAlbumDao::PhotoAlbumRowData> photoAlbums = this->GetPhotoAlbums();
279     for (const auto &album : photoAlbums) {
280         if (album.lPath.empty()) {
281             continue;
282         }
283         this->photoAlbumCache_.Insert(StringUtils::ToLower(album.lPath), album);
284     }
285     MEDIA_INFO_LOG(
286         "Media_Restore: LoadPhotoAlbums success, %{public}d albums", static_cast<int32_t>(photoAlbums.size()));
287 }
288 
289 /**
290  * @brief Parse the sourcePath to lPath.
291  * example, sourcePath=/storage/emulated/0/DCIM/Camera/IMG_20240829_072213.jpg, lPath=/DCIM/Camera
292  * if the sourcePath can not be parsed, return /Pictures/其它.
293  */
ParseSourcePathToLPath(const std::string & sourcePath)294 std::string PhotoAlbumDao::ParseSourcePathToLPath(const std::string &sourcePath)
295 {
296     size_t start_pos = sourcePath.find(GALLERT_ROOT_PATH);
297     size_t end_pos = sourcePath.find_last_of("/");
298 
299     std::string result = "/Pictures/其它";
300     if (start_pos != std::string::npos && end_pos != std::string::npos) {
301         start_pos += GALLERT_ROOT_PATH.length();
302         result = sourcePath.substr(start_pos, end_pos - start_pos);
303         start_pos = result.find_first_of("/");
304         if (start_pos != std::string::npos) {
305             result = result.substr(start_pos);
306         }
307     }
308     return result;
309 }
310 
311 /**
312  * @brief Build PhotoAlbumRowData from lPath.
313  */
BuildAlbumInfoByLPath(const std::string & lPath,const int32_t albumType,const int32_t albumSubType)314 PhotoAlbumDao::PhotoAlbumRowData PhotoAlbumDao::BuildAlbumInfoByLPath(
315     const std::string &lPath, const int32_t albumType, const int32_t albumSubType)
316 {
317     PhotoAlbumDao::PhotoAlbumRowData albumInfo;
318     // find albumName from lPath
319     std::string albumName = "其它";
320     std::string albumlPath = lPath;
321     int32_t albumTypeTmp = albumType;
322     int32_t albumSubTypeTmp = albumSubType;
323     size_t fileIndex = albumlPath.find_last_of(FILE_SEPARATOR);
324     if (fileIndex != string::npos) {
325         albumName = albumlPath.substr(fileIndex + 1);
326     } else {
327         albumlPath = "/Pictures/其它";
328         albumTypeTmp = static_cast<int32_t>(PhotoAlbumType::SOURCE);
329         albumSubTypeTmp = static_cast<int32_t>(PhotoAlbumSubType::SOURCE_GENERIC);
330     }
331     albumInfo.albumName = albumName;
332     albumInfo.lPath = albumlPath;
333     albumInfo.albumType = albumTypeTmp;
334     albumInfo.albumSubType = albumSubTypeTmp;
335     albumInfo.priority = 1;
336     return albumInfo;
337 }
338 
339 /**
340  * @brief Build PhotoAlbumRowData from lPath.
341  */
BuildAlbumInfoByLPath(const std::string & lPath)342 PhotoAlbumDao::PhotoAlbumRowData PhotoAlbumDao::BuildAlbumInfoByLPath(const std::string &lPath)
343 {
344     int32_t albumType = static_cast<int32_t>(PhotoAlbumType::SOURCE);
345     int32_t albumSubType = static_cast<int32_t>(PhotoAlbumSubType::SOURCE_GENERIC);
346 
347     std::string target = "/Pictures/Users/";
348     std::transform(target.begin(), target.end(), target.begin(), ::tolower);
349     std::string lPathLower = lPath;
350     std::transform(lPathLower.begin(), lPathLower.end(), lPathLower.begin(), ::tolower);
351     if (lPathLower.find(target) == 0) {
352         albumType = static_cast<int32_t>(PhotoAlbumType::USER);
353         albumSubType = static_cast<int32_t>(PhotoAlbumSubType::USER_GENERIC);
354     }
355     return this->BuildAlbumInfoByLPath(lPath, albumType, albumSubType);
356 }
357 }  // namespace OHOS::Media