1 /*
2 * Copyright (C) 2022-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
16 #include "userfile_client.h"
17
18 #include "ability.h"
19 #include "iservice_registry.h"
20
21 #include "media_asset_rdbstore.h"
22 #include "medialibrary_errno.h"
23 #include "medialibrary_napi_log.h"
24 #include "medialibrary_helper_container.h"
25 #include "media_file_utils.h"
26 #include "safe_map.h"
27
28 using namespace std;
29 using namespace OHOS::DataShare;
30 using namespace OHOS::AppExecFwk;
31 namespace OHOS {
32 namespace Media {
33
34 constexpr int32_t BUNDLE_MGR_SERVICE_SYS_ABILITY_ID = 401;
35 int32_t UserFileClient::userId_ = -1;
36 string UserFileClient::bundleName_ = "";
37 std::string MULTI_USER_URI_FLAG = "user=";
38 std::string USER_STR = "user";
39
40 SafeMap<int32_t, std::shared_ptr<DataShare::DataShareHelper>> UserFileClient::dataShareHelperMap_ = {};
41 sptr<AppExecFwk::IBundleMgr> UserFileClient::bundleMgr_ = nullptr;
42 mutex UserFileClient::bundleMgrMutex_;
GetMediaLibraryDataUri(const int32_t userId)43 static std::string GetMediaLibraryDataUri(const int32_t userId)
44 {
45 std::string mediaLibraryDataUri = MEDIALIBRARY_DATA_URI;
46 if (userId != -1) {
47 mediaLibraryDataUri = mediaLibraryDataUri + "?" + MULTI_USER_URI_FLAG + to_string(userId);
48 }
49 return mediaLibraryDataUri;
50 }
51
UriAppendKeyValue(std::string & uri,const std::string & key,const std::string & value)52 void UriAppendKeyValue(std::string &uri, const std::string &key, const std::string &value)
53 {
54 std::string uriKey = key + '=';
55 if (uri.find(uriKey) != std::string::npos) {
56 return;
57 }
58
59 char queryMark = (uri.find('?') == std::string::npos) ? '?' : '&';
60 std::string append = queryMark + key + '=' + value;
61
62 size_t posJ = uri.find('#');
63 if (posJ == std::string::npos) {
64 uri += append;
65 } else {
66 uri.insert(posJ, append);
67 }
68 }
69
MultiUserUriRecognition(Uri & uri,const int32_t userId)70 static Uri MultiUserUriRecognition(Uri &uri, const int32_t userId)
71 {
72 if (userId == -1) {
73 return uri;
74 }
75 std::string uriString = uri.ToString();
76 UriAppendKeyValue(uriString, USER_STR, to_string(userId));
77 return Uri(uriString);
78 }
79
DataShareCreator(const sptr<IRemoteObject> & token,shared_ptr<DataShare::DataShareHelper> & dataShareHelper,const int32_t userId)80 static void DataShareCreator(const sptr<IRemoteObject> &token,
81 shared_ptr<DataShare::DataShareHelper> &dataShareHelper, const int32_t userId)
82 {
83 dataShareHelper = DataShare::DataShareHelper::Creator(token, GetMediaLibraryDataUri(userId));
84 if (dataShareHelper == nullptr) {
85 NAPI_ERR_LOG("dataShareHelper Creator failed");
86 dataShareHelper = DataShare::DataShareHelper::Creator(token, GetMediaLibraryDataUri(userId));
87 }
88 }
89
GetDataShareHelper(napi_env env,napi_callback_info info,const int32_t userId)90 shared_ptr<DataShare::DataShareHelper> UserFileClient::GetDataShareHelper(napi_env env,
91 napi_callback_info info, const int32_t userId)
92 {
93 size_t argc = ARGS_ONE;
94 napi_value argv[ARGS_ONE] = {0};
95 napi_value thisVar = nullptr;
96 NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
97
98 std::shared_ptr<DataShare::DataShareHelper> dataShareHelper = nullptr;
99 bool isStageMode = false;
100 napi_status status = OHOS::AbilityRuntime::IsStageContext(env, argv[0], isStageMode);
101 if (status != napi_ok || !isStageMode) {
102 NAPI_INFO_LOG("status: %{public}d, isStageMode: %{public}d", status, static_cast<int32_t>(isStageMode));
103 auto ability = OHOS::AbilityRuntime::GetCurrentAbility(env);
104 if (ability == nullptr) {
105 NAPI_ERR_LOG("Failed to get native ability instance");
106 return nullptr;
107 }
108 auto context = ability->GetContext();
109 if (context == nullptr) {
110 NAPI_ERR_LOG("Failed to get native context instance");
111 return nullptr;
112 }
113 DataShareCreator(context->GetToken(), dataShareHelper, userId);
114 } else {
115 auto context = OHOS::AbilityRuntime::GetStageModeContext(env, argv[0]);
116 if (context == nullptr) {
117 NAPI_ERR_LOG("Failed to get native stage context instance");
118 return nullptr;
119 }
120 DataShareCreator(context->GetToken(), dataShareHelper, userId);
121 }
122 MediaLibraryHelperContainer::GetInstance()->SetDataShareHelper(dataShareHelper);
123 return dataShareHelper;
124 }
125
CheckIsStage(napi_env env,napi_callback_info info,bool & result)126 napi_status UserFileClient::CheckIsStage(napi_env env, napi_callback_info info, bool &result)
127 {
128 size_t argc = ARGS_ONE;
129 napi_value argv[ARGS_ONE] = {0};
130 napi_value thisVar = nullptr;
131 napi_status status = napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr);
132 if (status != napi_ok) {
133 NAPI_ERR_LOG("Failed to get cb info, status=%{public}d", (int) status);
134 return status;
135 }
136
137 result = false;
138 status = OHOS::AbilityRuntime::IsStageContext(env, argv[0], result);
139 if (status != napi_ok) {
140 NAPI_ERR_LOG("Failed to get stage mode, status=%{public}d", (int) status);
141 return status;
142 }
143 return napi_ok;
144 }
145
ParseTokenInStageMode(napi_env env,napi_callback_info info)146 sptr<IRemoteObject> UserFileClient::ParseTokenInStageMode(napi_env env, napi_callback_info info)
147 {
148 size_t argc = ARGS_ONE;
149 napi_value argv[ARGS_ONE] = {0};
150 napi_value thisVar = nullptr;
151 if (napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr) != napi_ok) {
152 NAPI_ERR_LOG("Failed to get cb info");
153 return nullptr;
154 }
155
156 auto context = OHOS::AbilityRuntime::GetStageModeContext(env, argv[0]);
157 if (context == nullptr) {
158 NAPI_ERR_LOG("Failed to get native stage context instance");
159 return nullptr;
160 }
161 return context->GetToken();
162 }
163
ParseTokenInAbility(napi_env env,napi_callback_info info)164 sptr<IRemoteObject> UserFileClient::ParseTokenInAbility(napi_env env, napi_callback_info info)
165 {
166 size_t argc = ARGS_ONE;
167 napi_value argv[ARGS_ONE] = {0};
168 napi_value thisVar = nullptr;
169 if (napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr) != napi_ok) {
170 NAPI_ERR_LOG("Failed to get cb info");
171 return nullptr;
172 }
173
174 auto ability = OHOS::AbilityRuntime::GetCurrentAbility(env);
175 if (ability == nullptr) {
176 NAPI_ERR_LOG("Failed to get native ability instance");
177 return nullptr;
178 }
179 auto context = ability->GetContext();
180 if (context == nullptr) {
181 NAPI_ERR_LOG("Failed to get native context instance");
182 return nullptr;
183 }
184 return context->GetToken();
185 }
186
IsValid(const int32_t userId)187 bool UserFileClient::IsValid(const int32_t userId)
188 {
189 std::shared_ptr<DataShare::DataShareHelper> helper;
190 if (dataShareHelperMap_.Find(userId, helper)) {
191 return helper != nullptr;
192 }
193 return false;
194 }
195
GetDataShareHelperByUser(const int32_t userId)196 std::shared_ptr<DataShare::DataShareHelper> UserFileClient::GetDataShareHelperByUser(const int32_t userId)
197 {
198 return dataShareHelperMap_.ReadVal(userId);
199 }
200
Init(const sptr<IRemoteObject> & token,bool isSetHelper,const int32_t userId)201 void UserFileClient::Init(const sptr<IRemoteObject> &token, bool isSetHelper, const int32_t userId)
202 {
203 if (GetDataShareHelperByUser(userId) == nullptr) {
204 std::shared_ptr<DataShare::DataShareHelper> dataShareHelper =
205 DataShare::DataShareHelper::Creator(token, GetMediaLibraryDataUri(userId));
206 if (isSetHelper) {
207 MediaLibraryHelperContainer::GetInstance()->SetDataShareHelper(dataShareHelper);
208 }
209 if (dataShareHelper != nullptr) {
210 if (!IsValid(userId)) {
211 dataShareHelperMap_.EnsureInsert(userId, dataShareHelper);
212 } else {
213 NAPI_ERR_LOG("dataShareHelperMap has userId and value");
214 }
215 } else {
216 NAPI_ERR_LOG("Failed to getDataShareHelper, dataShareHelper is null");
217 }
218 }
219 }
220
Init(napi_env env,napi_callback_info info,const int32_t userId)221 void UserFileClient::Init(napi_env env, napi_callback_info info, const int32_t userId)
222 {
223 if (GetDataShareHelperByUser(userId) == nullptr) {
224 std::shared_ptr<DataShare::DataShareHelper> dataShareHelper = GetDataShareHelper(env, info, userId);
225 if (dataShareHelper != nullptr) {
226 if (!IsValid(userId)) {
227 dataShareHelperMap_.EnsureInsert(userId, dataShareHelper);
228 } else {
229 NAPI_ERR_LOG("dataShareHelperMap has userId and value");
230 }
231 } else {
232 NAPI_ERR_LOG("Failed to getDataShareHelper, dataShareHelper is null");
233 }
234 }
235 }
236
Query(Uri & uri,const DataSharePredicates & predicates,std::vector<std::string> & columns,int & errCode,const int32_t userId)237 shared_ptr<DataShareResultSet> UserFileClient::Query(Uri &uri, const DataSharePredicates &predicates,
238 std::vector<std::string> &columns, int &errCode, const int32_t userId)
239 {
240 if (!IsValid(userId)) {
241 NAPI_ERR_LOG("Query fail, helper null, userId is %{public}d", userId);
242 return nullptr;
243 }
244
245 shared_ptr<DataShareResultSet> resultSet = nullptr;
246 OperationObject object = OperationObject::UNKNOWN_OBJECT;
247 if (MediaAssetRdbStore::GetInstance()->IsQueryAccessibleViaSandBox(uri, object, predicates) && userId == -1) {
248 resultSet = MediaAssetRdbStore::GetInstance()->Query(predicates, columns, object, errCode);
249 } else {
250 uri = MultiUserUriRecognition(uri, userId);
251 DatashareBusinessError businessError;
252 resultSet = GetDataShareHelperByUser(userId)->Query(uri, predicates, columns, &businessError);
253 errCode = businessError.GetCode();
254 }
255 return resultSet;
256 }
257
QueryByStep(const std::string & sql)258 shared_ptr<NativeRdb::ResultSet> UserFileClient::QueryByStep(const std::string &sql)
259 {
260 std::shared_ptr<NativeRdb::ResultSet> resultSet = MediaAssetRdbStore::GetInstance()->QueryByStep(sql);
261 return resultSet;
262 }
263
QueryAccessibleViaSandBox(Uri & uri,const DataSharePredicates & predicates,std::vector<std::string> & columns,int & errCode,const int32_t userId)264 std::pair<bool, shared_ptr<DataShareResultSet>> UserFileClient::QueryAccessibleViaSandBox(Uri &uri,
265 const DataSharePredicates &predicates, std::vector<std::string> &columns, int &errCode, const int32_t userId)
266 {
267 OperationObject object = OperationObject::UNKNOWN_OBJECT;
268 if (MediaAssetRdbStore::GetInstance()->IsQueryAccessibleViaSandBox(uri, object, predicates) && userId == -1) {
269 return {true, MediaAssetRdbStore::GetInstance()->Query(predicates, columns, object, errCode)};
270 }
271 return {false, nullptr};
272 }
273
QueryRdb(Uri & uri,const DataShare::DataSharePredicates & predicates,std::vector<std::string> & columns)274 std::shared_ptr<NativeRdb::ResultSet> UserFileClient::QueryRdb(Uri &uri,
275 const DataShare::DataSharePredicates &predicates, std::vector<std::string> &columns)
276 {
277 shared_ptr<NativeRdb::ResultSet> resultSet = nullptr;
278 OperationObject object = OperationObject::UNKNOWN_OBJECT;
279 if (MediaAssetRdbStore::GetInstance()->IsSupportSharedAssetQuery(uri, object)) {
280 resultSet = MediaAssetRdbStore::GetInstance()->QueryRdb(predicates, columns, object);
281 }
282 return resultSet;
283 }
284
Insert(Uri & uri,const DataShareValuesBucket & value,const int32_t userId)285 int UserFileClient::Insert(Uri &uri, const DataShareValuesBucket &value, const int32_t userId)
286 {
287 if (!IsValid(userId)) {
288 NAPI_ERR_LOG("insert fail, helper null, userId is %{public}d", userId);
289 return E_FAIL;
290 }
291 int index = GetDataShareHelperByUser(userId)->Insert(uri, value);
292 return index;
293 }
294
InsertExt(Uri & uri,const DataShareValuesBucket & value,string & result,const int32_t userId)295 int UserFileClient::InsertExt(Uri &uri, const DataShareValuesBucket &value, string &result, const int32_t userId)
296 {
297 if (!IsValid(userId)) {
298 NAPI_ERR_LOG("insert fail, helper null, userId is %{public}d", userId);
299 return E_FAIL;
300 }
301 int index = GetDataShareHelperByUser(userId)->InsertExt(uri, value, result);
302 return index;
303 }
304
BatchInsert(Uri & uri,const std::vector<DataShare::DataShareValuesBucket> & values)305 int UserFileClient::BatchInsert(Uri &uri, const std::vector<DataShare::DataShareValuesBucket> &values)
306 {
307 if (!IsValid(GetUserId())) {
308 NAPI_ERR_LOG("Batch insert fail, helper null, userId is %{public}d", GetUserId());
309 return E_FAIL;
310 }
311 return GetDataShareHelperByUser(GetUserId())->BatchInsert(uri, values);
312 }
313
Delete(Uri & uri,const DataSharePredicates & predicates)314 int UserFileClient::Delete(Uri &uri, const DataSharePredicates &predicates)
315 {
316 if (!IsValid(GetUserId())) {
317 NAPI_ERR_LOG("delete fail, helper null, userId is %{public}d", GetUserId());
318 return E_FAIL;
319 }
320 return GetDataShareHelperByUser(GetUserId())->Delete(uri, predicates);
321 }
322
NotifyChange(const Uri & uri)323 void UserFileClient::NotifyChange(const Uri &uri)
324 {
325 if (!IsValid(GetUserId())) {
326 NAPI_ERR_LOG("notify change fail, helper null, userId is %{public}d", GetUserId());
327 return;
328 }
329 GetDataShareHelperByUser(GetUserId())->NotifyChange(uri);
330 }
331
RegisterObserver(const Uri & uri,const sptr<AAFwk::IDataAbilityObserver> & dataObserver)332 void UserFileClient::RegisterObserver(const Uri &uri, const sptr<AAFwk::IDataAbilityObserver> &dataObserver)
333 {
334 if (!IsValid(GetUserId())) {
335 NAPI_ERR_LOG("register observer fail, helper null, userId is %{public}d", GetUserId());
336 return;
337 }
338 GetDataShareHelperByUser(GetUserId())->RegisterObserver(uri, dataObserver);
339 }
340
UnregisterObserver(const Uri & uri,const sptr<AAFwk::IDataAbilityObserver> & dataObserver)341 void UserFileClient::UnregisterObserver(const Uri &uri, const sptr<AAFwk::IDataAbilityObserver> &dataObserver)
342 {
343 if (!IsValid(GetUserId())) {
344 NAPI_ERR_LOG("unregister observer fail, helper null, userId is %{public}d", GetUserId());
345 return;
346 }
347 GetDataShareHelperByUser(GetUserId())->UnregisterObserver(uri, dataObserver);
348 }
349
OpenFile(Uri & uri,const std::string & mode,const int32_t userId)350 int UserFileClient::OpenFile(Uri &uri, const std::string &mode, const int32_t userId)
351 {
352 if (!IsValid(userId)) {
353 NAPI_ERR_LOG("Open file fail, helper null, userId is %{public}d", userId);
354 return E_FAIL;
355 }
356 uri = MultiUserUriRecognition(uri, userId);
357 return GetDataShareHelperByUser(userId)->OpenFile(uri, mode);
358 }
359
OpenFileWithErrCode(Uri & uri,const std::string & mode,int32_t & realErr,const int32_t userId)360 int UserFileClient::OpenFileWithErrCode(Uri &uri, const std::string &mode, int32_t &realErr, const int32_t userId)
361 {
362 if (!IsValid(userId)) {
363 NAPI_ERR_LOG("Open file fail, helper null, userId is %{public}d", userId);
364 return E_FAIL;
365 }
366 uri = MultiUserUriRecognition(uri, userId);
367 return GetDataShareHelperByUser(userId)->OpenFileWithErrCode(uri, mode, realErr);
368 }
369
Update(Uri & uri,const DataSharePredicates & predicates,const DataShareValuesBucket & value,const int32_t userId)370 int UserFileClient::Update(Uri &uri, const DataSharePredicates &predicates,
371 const DataShareValuesBucket &value, const int32_t userId)
372 {
373 if (!IsValid(userId)) {
374 NAPI_ERR_LOG("update fail, helper null, userId is %{public}d", userId);
375 return E_FAIL;
376 }
377 return GetDataShareHelperByUser(userId)->Update(uri, predicates, value);
378 }
379
RegisterObserverExt(const Uri & uri,shared_ptr<DataShare::DataShareObserver> dataObserver,bool isDescendants)380 void UserFileClient::RegisterObserverExt(const Uri &uri,
381 shared_ptr<DataShare::DataShareObserver> dataObserver, bool isDescendants)
382 {
383 if (!IsValid(GetUserId())) {
384 NAPI_ERR_LOG("register observer fail, helper null, userId is %{public}d", GetUserId());
385 return;
386 }
387 GetDataShareHelperByUser(GetUserId())->RegisterObserverExt(uri, std::move(dataObserver), isDescendants);
388 }
389
UnregisterObserverExt(const Uri & uri,std::shared_ptr<DataShare::DataShareObserver> dataObserver)390 void UserFileClient::UnregisterObserverExt(const Uri &uri, std::shared_ptr<DataShare::DataShareObserver> dataObserver)
391 {
392 if (!IsValid(GetUserId())) {
393 NAPI_ERR_LOG("unregister observer fail, helper null, userId is %{public}d", GetUserId());
394 return;
395 }
396 GetDataShareHelperByUser(GetUserId())->UnregisterObserverExt(uri, std::move(dataObserver));
397 }
398
GetType(Uri & uri)399 std::string UserFileClient::GetType(Uri &uri)
400 {
401 if (!IsValid(GetUserId())) {
402 NAPI_ERR_LOG("get type fail, helper null, userId is %{public}d", GetUserId());
403 return "";
404 }
405 return GetDataShareHelperByUser(GetUserId())->GetType(uri);
406 }
407
UserDefineFunc(MessageParcel & data,MessageParcel & reply,MessageOption & option)408 int32_t UserFileClient::UserDefineFunc(MessageParcel &data, MessageParcel &reply, MessageOption &option)
409 {
410 if (!IsValid(GetUserId())) {
411 NAPI_ERR_LOG("JS UserDefineFunc fail, helper null %{public}d", GetUserId());
412 return E_FAIL;
413 }
414 return GetDataShareHelperByUser(GetUserId())->UserDefineFunc(data, reply, option);
415 }
416
UserDefineFunc(const int32_t & userId,MessageParcel & data,MessageParcel & reply,MessageOption & option)417 int32_t UserFileClient::UserDefineFunc(const int32_t &userId, MessageParcel &data, MessageParcel &reply,
418 MessageOption &option)
419 {
420 if (!IsValid(userId)) {
421 NAPI_ERR_LOG("JS UserDefineFunc fail, helper null %{public}d", userId);
422 return E_FAIL;
423 }
424 return GetDataShareHelperByUser(userId)->UserDefineFunc(data, reply, option);
425 }
426
Clear()427 void UserFileClient::Clear()
428 {
429 dataShareHelperMap_.Clear();
430 }
431
SetUserId(const int32_t userId)432 void UserFileClient::SetUserId(const int32_t userId)
433 {
434 userId_ = userId;
435 }
436
GetUserId()437 int32_t UserFileClient::GetUserId()
438 {
439 return userId_;
440 }
441
GetSysBundleManager()442 sptr<AppExecFwk::IBundleMgr> UserFileClient::GetSysBundleManager()
443 {
444 if (bundleMgr_ != nullptr) {
445 return bundleMgr_;
446 }
447
448 lock_guard<mutex> lock(bundleMgrMutex_);
449 auto systemAbilityMgr = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
450 if (systemAbilityMgr == nullptr) {
451 NAPI_ERR_LOG("Failed to get SystemAbilityManager.");
452 return nullptr;
453 }
454
455 auto bundleObj = systemAbilityMgr->GetSystemAbility(BUNDLE_MGR_SERVICE_SYS_ABILITY_ID);
456 if (bundleObj == nullptr) {
457 NAPI_ERR_LOG("Remote object is nullptr.");
458 return nullptr;
459 }
460
461 auto bundleMgr = iface_cast<AppExecFwk::IBundleMgr>(bundleObj);
462 if (bundleMgr == nullptr) {
463 NAPI_ERR_LOG("Failed to iface_cast");
464 return nullptr;
465 }
466
467 return bundleMgr;
468 }
469
GetBundleName()470 string UserFileClient::GetBundleName()
471 {
472 if (bundleName_ != "") {
473 return bundleName_;
474 }
475 bundleMgr_ = GetSysBundleManager();
476 if (bundleMgr_ == nullptr) {
477 NAPI_ERR_LOG("bundleMgr_ is null");
478 return bundleName_;
479 }
480 int32_t uid = static_cast<int32_t>(getuid());
481 string bundleName;
482 auto result = bundleMgr_->GetBundleNameForUid(uid, bundleName);
483 if (!result) {
484 NAPI_ERR_LOG("result is false");
485 return bundleName_;
486 }
487 NAPI_INFO_LOG("hap bundleName: %{public}s", bundleName.c_str());
488 bundleName_ = bundleName;
489 return bundleName_;
490 }
491
RegisterObserverExtProvider(const Uri & uri,std::shared_ptr<DataShare::DataShareObserver> dataObserver,bool isDescendants)492 int32_t UserFileClient::RegisterObserverExtProvider(const Uri &uri,
493 std::shared_ptr<DataShare::DataShareObserver> dataObserver, bool isDescendants)
494 {
495 if (!IsValid(GetUserId())) {
496 NAPI_ERR_LOG("register observer fail, helper null, userId is %{public}d", GetUserId());
497 return E_FAIL;
498 }
499 return
500 GetDataShareHelperByUser(GetUserId())->RegisterObserverExtProvider(uri, std::move(dataObserver), isDescendants);
501 }
502
UnregisterObserverExtProvider(const Uri & uri,std::shared_ptr<DataShare::DataShareObserver> dataObserver)503 int32_t UserFileClient::UnregisterObserverExtProvider(const Uri &uri,
504 std::shared_ptr<DataShare::DataShareObserver> dataObserver)
505 {
506 if (!IsValid(GetUserId())) {
507 NAPI_ERR_LOG("unregister observer fail, helper null, userId is %{public}d", GetUserId());
508 return E_FAIL;
509 }
510 return GetDataShareHelperByUser(GetUserId())->UnregisterObserverExtProvider(uri, std::move(dataObserver));
511 }
512 }
513 }
514