* Copyright (c) 2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "contacts_sync_operations.h"
#include "contacts_api.h"
#include "contacts_napi_common.h"
#include "contacts_napi_utils.h"
#include "hilog_wrapper_api.h"
#include "result_convert.h"
#include "sync_json_helper.h"
#include "datashare_helper.h"
#include "../../ability/common/include/contacts_columns.h"
#include "kit_bundle_mgr_helper.h"
namespace OHOS {
namespace ContactsApi {
void querySyncInfoRecord(ExecuteHelper *executeHelper, std::string &completedBatchesStr)
{
if (executeHelper->dataShareHelper == nullptr) {
return;
}
DataShare::DataSharePredicates queryPredicates;
queryPredicates.EqualTo("bundle_name", executeHelper->callerBundleName);
queryPredicates.EqualTo("sync_id", executeHelper->syncId);
std::vector<std::string> columns;
columns.push_back("completed_batches");
columns.push_back("total_batches");
Uri queryUri("datashare:///com.ohos.contactsdataability/contacts/kit_contacts_sync_info");
auto queryResult = executeHelper->dataShareHelper->Query(queryUri, queryPredicates, columns);
if (queryResult == nullptr) {
HILOG_WARN("CheckDeleteSyncInfoRecord query failed, syncId=%{public}d", executeHelper->syncId);
return;
}
int rowCount = 0;
queryResult->GetRowCount(rowCount);
if (rowCount == 0) {
queryResult->Close();
HILOG_WARN("CheckDeleteSyncInfoRecord no record found, syncId=%{public}d", executeHelper->syncId);
return;
}
queryResult->GoToFirstRow();
completedBatchesStr.clear();
int completedIdx = 0;
if (queryResult->GetColumnIndex("completed_batches", completedIdx) == 0) {
queryResult->GetString(completedIdx, completedBatchesStr);
}
int totalBatches = executeHelper->totalBatches;
int totalBatchesIdx = 0;
if (queryResult->GetColumnIndex("total_batches", totalBatchesIdx) == 0) {
queryResult->GetInt(totalBatchesIdx, totalBatches);
}
queryResult->Close();
HILOG_INFO("CheckDeleteSyncInfoRecord syncId=%{public}d, completed_batches=%{public}s, total_batches=%{public}d",
executeHelper->syncId, completedBatchesStr.c_str(), totalBatches);
}
* @brief Delete sync_info record when all batches have completed
* Only deletes if all batches (1 through totalBatches) are in completed_batches
*/
void CheckDeleteSyncInfoRecord(ExecuteHelper *executeHelper)
{
if (executeHelper == nullptr || executeHelper->dataShareHelper == nullptr) {
return;
}
DataShare::DataSharePredicates deletePredicates;
deletePredicates.EqualTo("bundle_name", executeHelper->callerBundleName);
deletePredicates.EqualTo("sync_id", executeHelper->syncId);
Uri deleteUri("datashare:///com.ohos.contactsdataability/contacts/kit_contacts_sync_info");
auto deleteResult = executeHelper->dataShareHelper->Delete(deleteUri, deletePredicates);
HILOG_INFO("CheckDeleteSyncInfoRecord syncId=%{public}d, deleted=%{public}d",
executeHelper->syncId, deleteResult);
}
* @brief Insert sync_info record (for first batch)
*/
void InsertSyncInfoRecord(ExecuteHelper *executeHelper, bool confirmResult)
{
if (executeHelper == nullptr || executeHelper->dataShareHelper == nullptr) {
return;
}
int userSelectInt = confirmResult ? CONFIRM_RESULT_YES : CONFIRM_RESULT_NO;
int64_t nowTime = static_cast<int64_t>(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()));
DataShare::DataShareValuesBucket insertValues;
insertValues.Put("bundle_name", executeHelper->callerBundleName);
insertValues.Put("sync_mode", executeHelper->syncMode);
insertValues.Put("sync_id", executeHelper->syncId);
insertValues.Put("first_sync_time", nowTime);
insertValues.Put("last_sync_time", nowTime);
insertValues.Put("current_batch", executeHelper->currentBatch);
insertValues.Put("total_batches", executeHelper->totalBatches);
insertValues.Put("completed_batches", MakeInitialCompletedBatches(executeHelper->currentBatch));
insertValues.Put("user_confirm_result", userSelectInt);
int insertStatus = (executeHelper->resultData == DataShare::ExecErrorCode::EXEC_SUCCESS)
? BATCH_INSERT_SUCCESS : BATCH_INSERT_FAILED;
insertValues.Put("insert_status", insertStatus);
Uri insertUri("datashare:///com.ohos.contactsdataability/contacts/kit_contacts_sync_info");
auto insertResult = executeHelper->dataShareHelper->Insert(insertUri, insertValues);
HILOG_INFO("InsertSyncInfoRecord syncId=%{public}d, status=%{public}d, userSelectInt=%{public}d"
"inserted=%{public}d", executeHelper->syncId, insertStatus, userSelectInt, insertResult);
}
* @brief Verify update result by re-querying and check for mismatch
*/
static void VerifyUpdateResult(ExecuteHelper *executeHelper, const std::string &newCompletedBatchesStr)
{
Uri queryUri("datashare:///com.ohos.contactsdataability/contacts/kit_contacts_sync_info");
DataShare::DataSharePredicates predicates;
predicates.EqualTo("bundle_name", executeHelper->callerBundleName);
predicates.EqualTo("sync_id", executeHelper->syncId);
std::vector<std::string> columns;
columns.push_back("completed_batches");
auto result = executeHelper->dataShareHelper->Query(queryUri, predicates, columns);
if (result == nullptr) {
return;
}
int rowCount = 0;
result->GetRowCount(rowCount);
if (rowCount <= 0) {
result->Close();
return;
}
result->GoToFirstRow();
std::string verifiedBatches;
int idx = 0;
if (result->GetColumnIndex("completed_batches", idx) == 0) {
result->GetString(idx, verifiedBatches);
}
result->Close();
HILOG_INFO("UpdateSyncInfoRecord verified=%{public}s", verifiedBatches.c_str());
if (verifiedBatches != newCompletedBatchesStr) {
HILOG_WARN("UpdateSyncInfoRecord verification mismatch, expected=%{public}s, got=%{public}s",
newCompletedBatchesStr.c_str(), verifiedBatches.c_str());
Uri delUri("datashare:///com.ohos.contactsdataability/contacts/kit_contacts_sync_info");
executeHelper->dataShareHelper->Delete(delUri, predicates);
InsertSyncInfoRecord(executeHelper, true);
}
}
* @brief Update sync_info record (for subsequent batches)
*/
static void UpdateSyncInfoRecord(ExecuteHelper *executeHelper, const std::string &newCompletedBatchesStr)
{
if (executeHelper->dataShareHelper == nullptr) {
return;
}
int64_t nowTime = static_cast<int64_t>(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()));
int insertStatus = (executeHelper->resultData == DataShare::ExecErrorCode::EXEC_SUCCESS)
? BATCH_INSERT_SUCCESS : BATCH_INSERT_FAILED;
DataShare::DataSharePredicates predicates;
predicates.EqualTo("bundle_name", executeHelper->callerBundleName);
predicates.EqualTo("sync_id", executeHelper->syncId);
DataShare::DataShareValuesBucket values;
values.Put("last_sync_time", nowTime);
values.Put("current_batch", executeHelper->currentBatch);
values.Put("completed_batches", newCompletedBatchesStr);
values.Put("insert_status", insertStatus);
Uri updateUri("datashare:///com.ohos.contactsdataability/contacts/kit_contacts_sync_info");
auto updateResult = executeHelper->dataShareHelper->Update(updateUri, predicates, values);
HILOG_INFO("UpdateSyncInfoRecord syncId=%{public}d, updated=%{public}d", executeHelper->syncId, updateResult);
if (updateResult <= 0) {
HILOG_WARN("UpdateSyncInfoRecord Update returned %{public}d, falling back to Insert", updateResult);
InsertSyncInfoRecord(executeHelper, true);
return;
}
VerifyUpdateResult(executeHelper, newCompletedBatchesStr);
}
void LocalExecuteSyncContacts(napi_env env, ExecuteHelper *executeHelper)
{
if (executeHelper->syncMode != MODE_INCREMENTAL && executeHelper->syncMode != MODE_CLOUD_BASED) {
HILOG_ERROR("LocalExecuteSyncContacts invalid syncMode:%{public}d", executeHelper->syncMode);
executeHelper->resultData = VERIFICATION_PARAMETER_ERROR;
return;
}
HILOG_INFO("LocalExecuteSyncContacts syncMode:%{public}d, batch:%{public}d/%{public}d, "
"statements:%{public}zu",
executeHelper->syncMode, executeHelper->currentBatch, executeHelper->totalBatches,
executeHelper->operationStatements.size());
if (executeHelper->operationStatements.empty()) {
HILOG_INFO("LocalExecuteSyncContacts no operations");
executeHelper->resultData = DataShare::ExecErrorCode::EXEC_SUCCESS;
} else {
ProcessInsertContactsWithLimit(env, executeHelper);
}
afterExecuteSyncContacts(env, executeHelper, true);
HILOG_INFO("LocalExecuteSyncContacts end, resultData:%{public}d", executeHelper->resultData);
}
void HandleSyncContactsResult(napi_env env, ExecuteHelper *executeHelper, napi_value &result)
{
std::string syncModeStr = executeHelper->syncMode == MODE_INCREMENTAL ? "MODE_INCREMENTAL" : "MODE_CLOUD_BASED";
std::string resultStr = executeHelper->resultData == 0 ? "SUCCESS" : "FAILED";
HILOG_INFO("HandleSyncContactsResult syncMode:%{public}s, result:%{public}s",
syncModeStr.c_str(), resultStr.c_str());
HandleAddContactsResult(env, executeHelper, result);
}
void LocalExecuteQueryContactSyncInfo(napi_env env, ExecuteHelper *executeHelper)
{
if (executeHelper->dataShareHelper == nullptr) {
return;
}
HILOG_INFO("LocalExecuteQueryContactSyncInfo start");
ContactsControl contactsControl;
std::string bundleName;
ContactBundleMgrHelper::GetBundleNameForSelf(bundleName);
DataShare::DataSharePredicates predicates;
predicates.EqualTo("bundle_name", bundleName);
std::vector<std::string> columns;
Uri queryUri("datashare:///com.ohos.contactsdataability/contacts/kit_contacts_sync_info");
auto resultSet = executeHelper->dataShareHelper->Query(queryUri, predicates, columns);
if (resultSet == nullptr) {
HILOG_ERROR("LocalExecuteQueryContactSyncInfo query failed, resultSet is null");
executeHelper->resultData = -1;
} else {
executeHelper->resultSet = resultSet;
executeHelper->resultData = 0;
HILOG_INFO("LocalExecuteQueryContactSyncInfo query success, bundleName=%{public}s", bundleName.c_str());
}
}
* @brief Set a string property from ResultSet column to JS object
*/
static void SetSyncStringProperty(napi_env env, napi_value &obj, const char *key,
const std::shared_ptr<DataShare::DataShareResultSet> &resultSet, int colIndex)
{
std::string value;
if (colIndex >= 0) {
resultSet->GetString(colIndex, value);
}
napi_value jsValue;
napi_create_string_utf8(env, value.c_str(), NAPI_AUTO_LENGTH, &jsValue);
napi_set_named_property(env, obj, key, jsValue);
}
* @brief Set an int property from ResultSet column to JS object
*/
static void SetSyncIntProperty(napi_env env, napi_value &obj, const char *key,
const std::shared_ptr<DataShare::DataShareResultSet> &resultSet, int colIndex)
{
int value = 0;
if (colIndex >= 0) {
resultSet->GetInt(colIndex, value);
}
napi_value jsValue;
napi_create_int32(env, value, &jsValue);
napi_set_named_property(env, obj, key, jsValue);
}
* @brief Set an int64 property from ResultSet column to JS object
*/
static void SetSyncInt64Property(napi_env env, napi_value &obj, const char *key,
const std::shared_ptr<DataShare::DataShareResultSet> &resultSet, int colIndex)
{
int64_t value = 0;
if (colIndex >= 0) {
resultSet->GetLong(colIndex, value);
}
napi_value jsValue;
napi_create_int64(env, value, &jsValue);
napi_set_named_property(env, obj, key, jsValue);
}
* @brief Set completedBatches array property from JSON string to JS array
*/
static void SetSyncCompletedBatchesProperty(napi_env env, napi_value &obj,
const std::shared_ptr<DataShare::DataShareResultSet> &resultSet, int colIndex)
{
std::string completedBatchesStr;
if (colIndex >= 0) {
resultSet->GetString(colIndex, completedBatchesStr);
}
std::vector<int> batches = ParseCompletedBatches(completedBatchesStr);
napi_value jsArray;
napi_create_array(env, &jsArray);
for (size_t i = 0; i < batches.size(); i++) {
napi_value batchValue;
napi_create_int32(env, batches[i], &batchValue);
napi_set_element(env, jsArray, i, batchValue);
}
napi_set_named_property(env, obj, "completedBatches", jsArray);
}
* @brief Column indices for ContactSyncInfo result set
*/
struct SyncInfoColumnIndices {
int colBundleName = -1;
int colMode = -1;
int colSyncId = -1;
int colFirstSyncTime = -1;
int colLastSyncTime = -1;
int colUserConfirmResult = -1;
int colCurrentBatch = -1;
int colCompletedBatches = -1;
int colTotalBatches = -1;
};
* @brief Get column indices from result set for ContactSyncInfo fields
*/
static SyncInfoColumnIndices GetSyncInfoColumnIndices(
const std::shared_ptr<DataShare::DataShareResultSet> &resultSet)
{
SyncInfoColumnIndices idx;
resultSet->GetColumnIndex(::OHOS::Contacts::KitContactsSyncInfoColumns::BUNDLE_NAME, idx.colBundleName);
resultSet->GetColumnIndex(::OHOS::Contacts::KitContactsSyncInfoColumns::SYNC_MODE, idx.colMode);
resultSet->GetColumnIndex(::OHOS::Contacts::KitContactsSyncInfoColumns::SYNC_ID, idx.colSyncId);
resultSet->GetColumnIndex(::OHOS::Contacts::KitContactsSyncInfoColumns::FIRST_SYNC_TIME, idx.colFirstSyncTime);
resultSet->GetColumnIndex(::OHOS::Contacts::KitContactsSyncInfoColumns::LAST_SYNC_TIME, idx.colLastSyncTime);
resultSet->GetColumnIndex(::OHOS::Contacts::KitContactsSyncInfoColumns::USER_CONFIRM_RESULT,
idx.colUserConfirmResult);
resultSet->GetColumnIndex(::OHOS::Contacts::KitContactsSyncInfoColumns::CURRENT_BATCH, idx.colCurrentBatch);
resultSet->GetColumnIndex(::OHOS::Contacts::KitContactsSyncInfoColumns::COMPLETED_BATCHES,
idx.colCompletedBatches);
resultSet->GetColumnIndex(::OHOS::Contacts::KitContactsSyncInfoColumns::TOTAL_BATCHES, idx.colTotalBatches);
return idx;
}
* @brief Build a single ContactSyncInfo JS object from a ResultSet row
*/
static void BuildContactSyncInfoFromRow(napi_env env, ExecuteHelper *executeHelper,
napi_value &syncInfoObj, const SyncInfoColumnIndices &idx)
{
SetSyncStringProperty(env, syncInfoObj, "bundleName",
executeHelper->resultSet, idx.colBundleName);
SetSyncIntProperty(env, syncInfoObj, "mode",
executeHelper->resultSet, idx.colMode);
SetSyncIntProperty(env, syncInfoObj, "syncId",
executeHelper->resultSet, idx.colSyncId);
SetSyncInt64Property(env, syncInfoObj, "firstSyncTime",
executeHelper->resultSet, idx.colFirstSyncTime);
SetSyncInt64Property(env, syncInfoObj, "lastSyncTime",
executeHelper->resultSet, idx.colLastSyncTime);
SetSyncIntProperty(env, syncInfoObj, "userConfirmResult",
executeHelper->resultSet, idx.colUserConfirmResult);
SetSyncIntProperty(env, syncInfoObj, "currentBatch",
executeHelper->resultSet, idx.colCurrentBatch);
SetSyncIntProperty(env, syncInfoObj, "totalBatches",
executeHelper->resultSet, idx.colTotalBatches);
SetSyncCompletedBatchesProperty(env, syncInfoObj,
executeHelper->resultSet, idx.colCompletedBatches);
}
void HandleQueryContactSyncInfoResult(napi_env env, ExecuteHelper *executeHelper, napi_value &result)
{
napi_create_array(env, &result);
if (executeHelper->resultSet == nullptr) {
HILOG_INFO("HandleQueryContactSyncInfoResult no result, return empty array");
return;
}
if (executeHelper->resultSet->GoToFirstRow() != OHOS::NativeRdb::E_OK) {
HILOG_INFO("HandleQueryContactSyncInfoResult GoToFirstRow failed, return empty array");
return;
}
SyncInfoColumnIndices idx = GetSyncInfoColumnIndices(executeHelper->resultSet);
if (idx.colMode < 0 || idx.colSyncId < 0 || idx.colBundleName < 0) {
HILOG_WARN("HandleQueryContactSyncInfoResult some required columns not found");
}
uint32_t arrayIndex = 0;
do {
napi_value syncInfoObj = nullptr;
napi_create_object(env, &syncInfoObj);
BuildContactSyncInfoFromRow(env, executeHelper, syncInfoObj, idx);
napi_set_element(env, result, arrayIndex++, syncInfoObj);
} while (executeHelper->resultSet->GoToNextRow() == OHOS::NativeRdb::E_OK);
HILOG_INFO("HandleQueryContactSyncInfoResult array size=%{public}u", arrayIndex);
}
* @brief Parse sync mode and progress from Scheduling's pre-converted params
*/
void BuildSyncContactsConvertParams(napi_env env, ExecuteHelper *executeHelper)
{
ContactsBuild contactsBuild;
bool isFA = (executeHelper->abilityContext == nullptr);
int modeIndex = isFA ? 1 : 0;
if (executeHelper->argc > static_cast<unsigned int>(modeIndex) &&
executeHelper->argv[modeIndex] != nullptr) {
executeHelper->syncMode = contactsBuild.GetInt(env, executeHelper->argv[modeIndex]);
}
int progressIndex = modeIndex + 1;
if (executeHelper->argc > static_cast<unsigned int>(progressIndex) &&
executeHelper->argv[progressIndex] != nullptr) {
napi_value progressObj = executeHelper->argv[progressIndex];
napi_value propValue = nullptr;
napi_valuetype valueType = napi_undefined;
if (napi_get_named_property(env, progressObj, "syncId", &propValue) == napi_ok && propValue != nullptr) {
if (napi_typeof(env, propValue, &valueType) == napi_ok && valueType == napi_number) {
executeHelper->syncId = contactsBuild.GetInt(env, propValue);
}
}
propValue = nullptr;
if (napi_get_named_property(env, progressObj, "currentBatch", &propValue) == napi_ok && propValue != nullptr) {
if (napi_typeof(env, propValue, &valueType) == napi_ok && valueType == napi_number) {
executeHelper->currentBatch = contactsBuild.GetInt(env, propValue);
}
}
propValue = nullptr;
if (napi_get_named_property(env, progressObj, "totalBatches", &propValue) == napi_ok && propValue != nullptr) {
if (napi_typeof(env, propValue, &valueType) == napi_ok && valueType == napi_number) {
executeHelper->totalBatches = contactsBuild.GetInt(env, propValue);
}
}
}
HILOG_INFO("BuildSyncContactsConvertParams syncMode:%{public}d, syncId:%{public}d, "
"currentBatch:%{public}d, totalBatches:%{public}d, isFA:%{public}d",
executeHelper->syncMode, executeHelper->syncId, executeHelper->currentBatch,
executeHelper->totalBatches, isFA);
}
void afterExecuteSyncContacts(napi_env env, ExecuteHelper *executeHelper, bool userConfirmed)
{
std::string completedBatchesStr = "";
querySyncInfoRecord(executeHelper, completedBatchesStr);
HILOG_INFO("UpdateSyncInfoRecord before: syncId=%{public}d, batch=%{public}d, existing=%{public}s,"
"total=%{public}d", executeHelper->syncId, executeHelper->currentBatch, completedBatchesStr.c_str(),
executeHelper->totalBatches);
bool mergedSuccess = false;
std::string newCompletedBatchesStr = MergeCompletedBatches(completedBatchesStr,
executeHelper->currentBatch, mergedSuccess);
if (!mergedSuccess) {
HILOG_ERROR("afterExecuteSyncContacts: existing completed_batches is corrupt '%{public}s', "
"retrying with empty base for batch=%{public}d",
completedBatchesStr.c_str(), executeHelper->currentBatch);
newCompletedBatchesStr = MergeCompletedBatches("", executeHelper->currentBatch, mergedSuccess);
}
bool isdeleteSyncInfo = AreAllBatchesCompleted(newCompletedBatchesStr, executeHelper->totalBatches);
if (isdeleteSyncInfo) {
CheckDeleteSyncInfoRecord(executeHelper);
return;
}
if (executeHelper->isFirstSync) {
InsertSyncInfoRecord(executeHelper, userConfirmed);
} else {
UpdateSyncInfoRecord(executeHelper, newCompletedBatchesStr);
}
HILOG_INFO("UpdateSyncInfoRecord after: newCompletedBatches=%{public}s", newCompletedBatchesStr.c_str());
}
}
}