1/**
2 * @file Describe the file
3 * Copyright (c) 2023 Huawei Device Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import data_rdb from '@ohos.data.relationalStore';
18import dataSharePredicates from '@ohos.data.dataSharePredicates';
19import { BusinessError } from '@ohos.base';
20import { ValueType } from '@ohos.data.ValuesBucket';
21
22import { Log } from '@ohos/common/src/main/ets/utils/Log';
23import { hasNoError } from '@ohos/common/src/main/ets/utils/ErrorUtils';
24import { getBundleNameByUri } from '@ohos/common/src/main/ets/utils/UrlUtils';
25
26import { Calendars } from '@ohos/datastructure/src/main/ets/calendars/Calendars';
27import { AppName } from '@ohos/datastructure/src/main/ets/calendars/CalendarsColumns';
28import { CalendarsIndexes, parseCalendarsIndexes } from '@ohos/datastructure/src/main/ets/calendars/CalendarsIndexes';
29import { CalendarsColumns } from '@ohos/datastructure/src/main/ets/calendars/CalendarsColumns';
30import { EventColumns, UPDATE_INSTANCES_COLUMNS } from '@ohos/datastructure/src/main/ets/events/EventColumns';
31import { Events } from '@ohos/datastructure/src/main/ets/events/Events';
32import { EventIndexes, parseIndexes } from '@ohos/datastructure/src/main/ets/events/EventIndexes';
33import { parseCalendars } from '@ohos/datastructure/src/main/ets/calendars/CalendarsParser';
34import { parseEvents } from '@ohos/datastructure/src/main/ets/events/EventParser';
35
36import { ErrorCode } from '../../constants/ErrorCode';
37import { DefaultProcessor } from './../DefaultProcessor';
38import { initValueCreator, initPredicateCreator, deleteValueCreator } from '../DatabaseProcessorHelper';
39import { acquireExpandOne, isNeedRefreshInstances, acquireUpdateOne } from '../instances/InstancesProcessor';
40import { filterValuesBucket } from '@ohos/common/src/main/ets/utils/ValuesUtils';
41
42const TAG: string = 'EventsProcessor';
43const CHANNEL_ID: string = 'channel_id';
44
45/**
46 * the Events table processor
47 *
48 * @since 2022-06-24
49 */
50export class EventsProcessor extends DefaultProcessor {
51  async insertByHighAuthority(rdbStore: data_rdb.RdbStore, uri: string,
52                              value: data_rdb.ValuesBucket, callback: Function) {
53    let values = filterValuesBucket(value, CHANNEL_ID);
54    const callerName = getBundleNameByUri(uri);
55    initValueCreator(values, callerName);
56    const isCalendarExist = await isCalendarContainSameId(rdbStore, values);
57    if (isCalendarExist) {
58      this.insertEventWithInstanceExpand(rdbStore, uri, values, callback);
59    } else {
60      Log.warn(TAG, 'not support insert operation');
61      const err: BusinessError = {
62        code: ErrorCode.UN_SUPPORT_OPERATION,
63        name: 'UnSupportedOperationException',
64        message: 'The calling application cannot insert an event without its own calendar'
65      };
66      callback(err, -1);
67    }
68  }
69
70  async insertByLowAuthority(rdbStore: data_rdb.RdbStore, uri: string,
71                             value: data_rdb.ValuesBucket, callback: Function) {
72    let values = filterValuesBucket(value, CHANNEL_ID);
73    const callerName = getBundleNameByUri(uri);
74    initValueCreator(values, callerName);
75    const isCalendarCreatorExist = await isCalendarContainSameCreator(rdbStore, values);
76    if (isCalendarCreatorExist) {
77      this.insertEventWithInstanceExpand(rdbStore, uri, values, callback);
78    } else {
79      Log.warn(TAG, 'not support insert operation');
80      const err: BusinessError = {
81        code: ErrorCode.UN_SUPPORT_OPERATION,
82        name: 'UnSupportedOperationException',
83        message: 'The calling application cannot insert an event with different creator from calendar'
84      };
85      callback(err, -1);
86    }
87  }
88
89  async deleteByLowAuthority(rdbStore: data_rdb.RdbStore, uri: string,
90                             predicates: dataSharePredicates.DataSharePredicates, callback: Function) {
91    const callerName = getBundleNameByUri(uri);
92    initPredicateCreator(predicates, callerName);
93    this.doDelete(rdbStore, uri, predicates, callback);
94  }
95
96  async updateByHighAuthority(rdbStore: data_rdb.RdbStore, uri: string, value: data_rdb.ValuesBucket,
97                              predicates: dataSharePredicates.DataSharePredicates, callback: Function) {
98    let values = filterValuesBucket(value, CHANNEL_ID);
99    deleteValueCreator(values);
100    this.updateEventWithInstance(rdbStore, uri, values, predicates, callback);
101  }
102
103  async updateByLowAuthority(rdbStore: data_rdb.RdbStore, uri: string, value: data_rdb.ValuesBucket,
104                             predicates: dataSharePredicates.DataSharePredicates, callback: Function) {
105    let values = filterValuesBucket(value, CHANNEL_ID);
106    const callerName = getBundleNameByUri(uri);
107    deleteValueCreator(values);
108    initPredicateCreator(predicates, callerName);
109    this.updateEventWithInstance(rdbStore, uri, values, predicates, callback);
110  }
111
112  async queryByLowAuthority(rdbStore: data_rdb.RdbStore, uri: string, columns: Array<string>,
113                            predicates: dataSharePredicates.DataSharePredicates, callback: Function) {
114    const callerName = getBundleNameByUri(uri);
115    initPredicateCreator(predicates, callerName);
116    this.doQuery(rdbStore, uri, columns, predicates, callback);
117  }
118
119  /**
120   * 插入 Event 并扩展该 Event 的实例 Instance
121   *
122   * @param rdbStore rdb数据库
123   * @param uri DataShare的uri
124   * @param values 插入的数据
125   * @param callback 回调方法
126   */
127  async insertEventWithInstanceExpand(rdbStore: data_rdb.RdbStore, uri: string,
128                                      values: data_rdb.ValuesBucket, callback: Function) {
129    this.doInsert(rdbStore, uri, values, (err: BusinessError, rowId: number) => {
130      Log.debug(TAG, `before insert callback`);
131      if (hasNoError(err)) {
132        acquireExpandOne(rdbStore, rowId, values);
133      }
134      callback(err, rowId);
135      Log.debug(TAG, `after insert callback`);
136    });
137  }
138
139  /**
140   * 更新 Event,并同步更新 Instance
141   *
142   * @param rdbStore rdb数据库
143   * @param uri DataShare的uri
144   * @param values 更新的数据
145   * @param predicates 更新条件
146   * @param callback 回调方法
147   */
148  async updateEventWithInstance(rdbStore: data_rdb.RdbStore, uri: string, values: data_rdb.ValuesBucket,
149                                predicates: dataSharePredicates.DataSharePredicates, callback: Function) {
150    let needUpdatedEventIds: Array<number>;
151    if (isNeedRefreshInstances(values)) {
152      needUpdatedEventIds = await queryEventIdsByPredicate(rdbStore, predicates);
153    }
154    this.doUpdate(rdbStore, uri, values, predicates, (err: BusinessError, count: Function) => {
155      Log.debug(TAG, `before update callback`);
156      if (hasNoError(err)) {
157        updateInstances(rdbStore, needUpdatedEventIds);
158      }
159      callback(err, count);
160      Log.debug(TAG, `after update callback`);
161    })
162  }
163}
164
165/**
166 * 检查待插入的 event 与 calendar 表中相同 calendar_id 的元组是否拥有相同的 creator
167 *
168 * @param rdbStore rdb数据库
169 * @param values 插入操作的数据
170 * @return true 相同 false 不相同
171 */
172async function isCalendarContainSameCreator(rdbStore: data_rdb.RdbStore,
173                                            values: data_rdb.ValuesBucket): Promise<boolean> {
174  Log.debug(TAG, 'isCalendarContainSameCreator start');
175  const eventCreator = values[EventColumns.CREATOR];
176  let resultSet = await queryCalendarIdAndCreatorByEvent(rdbStore, values);
177  if (resultSet === null || resultSet === undefined) {
178    return false;
179  }
180  const calendarsIndexes: CalendarsIndexes | undefined = parseCalendarsIndexes(resultSet);
181  if (resultSet.goToFirstRow()) {
182    let calendars: Calendars | undefined = parseCalendars(resultSet, calendarsIndexes);
183    if (calendars === null || calendars === undefined) {
184      return false;
185    }
186    if (calendars.creator === eventCreator || calendars.creator === AppName.CALENDARDATA) {
187      return true;
188    }
189  }
190  return false;
191}
192
193/**
194 * 检查待插入的 event 与 calendar 表中是否存在相同 calendar_id 的元组
195 *
196 * @param rdbStore rdb数据库
197 * @param values 插入操作的数据
198 * @return true 相同 false 不相同
199 */
200async function isCalendarContainSameId(rdbStore: data_rdb.RdbStore,
201                                       values: data_rdb.ValuesBucket): Promise<boolean> {
202  Log.debug(TAG, 'isCalendarContainSameId start');
203  let resultSet = await queryCalendarIdAndCreatorByEvent(rdbStore, values);
204  if (resultSet === null || resultSet === undefined) {
205    return false;
206  }
207  if (resultSet.rowCount > 0) {
208    return true;
209  }
210  return false;
211}
212
213/**
214 * 查询待插入的 event 数据中 calendar_id 与 calendar 表相同的结果
215 *
216 * @param rdbStore rdb数据库
217 * @param values 插入操作的数据
218 * @return DataShareResultSet
219 */
220async function queryCalendarIdAndCreatorByEvent(rdbStore: data_rdb.RdbStore,
221                                                values: data_rdb.ValuesBucket): Promise<data_rdb.ResultSet> {
222  const calendarId = values[EventColumns.CALENDAR_ID] as ValueType;
223  const columns = [CalendarsColumns.ID, CalendarsColumns.CREATOR];
224  let predicates = new dataSharePredicates.DataSharePredicates();
225  predicates.equalTo(CalendarsColumns.ID, calendarId);
226  let resultSet: data_rdb.ResultSet = {} as data_rdb.ResultSet;
227  try {
228    resultSet = await rdbStore.query(CalendarsColumns.TABLE_NAME, predicates, columns);
229  } catch (err) {
230    Log.error(TAG, 'Calendars query data error');
231  }
232  return resultSet;
233}
234
235/**
236 * 根据Predicate查出待刷新的Events ID列表,用于后续刷新Instances的依据
237 *
238 * @param rdbStore rdb数据库
239 * @param predicates update条件,用于定位需要刷新Instances对应eventId的范围
240 */
241async function queryEventIdsByPredicate(rdbStore: data_rdb.RdbStore,
242                                        predicates: dataSharePredicates.DataSharePredicates): Promise<number[]> {
243  const resultArray: number[] = [];
244  const columns = [EventColumns.ID];
245  try {
246    const resultSet = await rdbStore.query(EventColumns.TABLE_NAME, predicates, columns);
247    if (resultSet === null || resultSet === undefined) {
248      Log.error(TAG, 'queryEventIdsByPredicate get invalid resultSet');
249      return resultArray;
250    }
251    if (resultSet.rowCount === 0 || !resultSet.goToFirstRow()) {
252      Log.info(TAG, `queryEventIdsByPredicate row count:${resultSet?.rowCount}`);
253      return resultArray;
254    }
255    do {
256      const eventId = resultSet.getLong(resultSet.getColumnIndex(EventColumns.ID));
257      resultArray.push(eventId);
258    } while (resultSet.goToNextRow());
259  } catch (err) {
260    Log.error(TAG, `queryEventIdsByPredicate get err: ${err?.message}`);
261  }
262  return resultArray;
263}
264
265/**
266 * 批量刷新Instances
267 *
268 * @param rdbStore rdb数据库
269 * @param updatedIds 待刷新的eventId列表
270 */
271async function updateInstances(rdbStore: data_rdb.RdbStore, updatedIds: Array<number>) {
272  if (updatedIds === null || updatedIds === undefined || updatedIds.length === 0) {
273    return;
274  }
275
276  // 1.先批量查询出全部Events数据
277  const eventsMap: Map<number, Events> = await queryEventsByIds(rdbStore, updatedIds);
278
279  if (eventsMap === null || eventsMap === undefined || eventsMap.size === 0) {
280    Log.warn(TAG, 'updateInstances with invalid eventsMap');
281    return;
282  }
283
284  // 2.再遍历生成Instances
285  for (let eventId of updatedIds) {
286    // 基于eventId单条刷新Instances,需传入events字段值数据
287    acquireUpdateOne(rdbStore, eventId, eventsMap.get(eventId));
288  }
289}
290
291/**
292 * 根据EventIds查询出Events数据,用于生成Instances,先批量查询避免在for循环中查询
293 *
294 * @param rdbStore rdb数据库
295 * @param eventIds 需查询的eventIds集合
296 * @return Map<number, Events> eventId-Events的map
297 */
298async function queryEventsByIds(rdbStore: data_rdb.RdbStore,
299                                eventIds: Array<number>): Promise<Map<number, Events>> {
300  const resultMap: Map<number, Events> = new Map();
301  const predicate = new dataSharePredicates.DataSharePredicates();
302  predicate.in(EventColumns.ID, eventIds);
303  const columns = UPDATE_INSTANCES_COLUMNS;
304  columns.push(EventColumns.ID);
305  columns.push(EventColumns.CREATOR);
306  try {
307    const resultSet = await rdbStore.query(EventColumns.TABLE_NAME, predicate, columns);
308    if (resultSet === null || resultSet === undefined) {
309      Log.error(TAG, 'queryEventsByIds get invalid resultSet');
310      return resultMap;
311    }
312    if (!resultSet.goToFirstRow()) {
313      Log.error(TAG, `queryEventsByIds goto first row failed ${resultSet?.rowCount}`);
314      return resultMap;
315    }
316    const indexes: EventIndexes | undefined = parseIndexes(resultSet);
317    do {
318      const event: Events | undefined = parseEvents(resultSet, indexes);
319      if (event != undefined) {
320        resultMap.set(event.id, event);
321      }
322    } while (resultSet.goToNextRow());
323  } catch (err) {
324    Log.error(TAG, `queryEventsByIds get err ${err?.message}`);
325  }
326  return resultMap;
327}