• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.rdb'
18import { EventColumns } from '@ohos/datastructure/src/main/ets/events/EventColumns'
19import { Events } from '@ohos/datastructure/src/main/ets/events/Events'
20import { EventIndexes, parseIndexes } from '@ohos/datastructure/src/main/ets/events/EventIndexes'
21import { parseEvents } from '@ohos/datastructure/src/main/ets/events/EventParser'
22import { InstancesColumns } from '@ohos/datastructure/src/main/ets/instances/InstancesColumns'
23import { RecurrenceSet } from '@ohos/rrule/src/main/ets/RecurrenceSet'
24import { Log } from '@ohos/common/src/main/ets/utils/Log'
25import { getJulianDayByMills } from '@ohos/common/src/main/ets/time/JulianDay'
26import { getMinutesByMills, HOURS_PER_DAY, MINUTES_PER_HOUR } from '@ohos/common/src/main/ets/utils/TimeUtils'
27
28const TAG = "InstanceExpandHelper"
29
30/**
31 * 删除所有Instances
32 * 如时区变化等场景,需要清空后重新扩展
33 */
34export async function deleteAllInstances(rdbStore): Promise<boolean> {
35  const predicate = new data_rdb.RdbPredicates(InstancesColumns.TABLE_NAME)
36  const result = await rdbStore.delete(predicate)
37  return result >= 0
38}
39
40/**
41 * 按时间范围扩展所有Instances
42 *
43 * @param beginTime 开始时间
44 * @param end 结束时间
45 */
46export async function expandAllByRange(rdbStore, beginTime: number, endTime: number): Promise<boolean> {
47  Log.info(TAG, `expandAllByRange called: ${beginTime}, ${endTime}, rdbStore=${rdbStore}`)
48  const queryPredicates = new data_rdb.RdbPredicates(EventColumns.TABLE_NAME)
49  queryPredicates.between(EventColumns.DTSTART, beginTime, endTime)
50  const columns = [EventColumns.ID, EventColumns.TITLE, EventColumns.DTSTART, EventColumns.DTEND, EventColumns.CREATOR]
51  const resultSet = await rdbStore.query(queryPredicates, columns)
52  Log.debug(TAG, `expandAllByRange query callback: ${resultSet}`)
53  if (resultSet !== null && resultSet !== undefined) {
54    if (resultSet.rowCount <= 0) {
55      Log.info(TAG, `expandAllByRange ${beginTime}/${endTime} no event to be expand`)
56      return true
57    }
58    return await expandInstancesByResultSet(rdbStore, resultSet)
59  }
60  Log.warn(TAG, "expandAllByRange end with empty cursor")
61  return false
62}
63
64/**
65 * 扩展单个日程的Instances
66 *
67 * @param eventId 日程ID
68 * @param beginTime 开始时间
69 * @param endTime 结束时间
70 */
71export function expandOneInRange(rdbStore, event: Events, expandedBegin: number, expandedEnd: number): boolean {
72  if (event === null || event === undefined) {
73    Log.warn(TAG, 'expandOneInRange get invalid params');
74    return false;
75  }
76  Log.info(TAG, `expandOneInRange id=${event.id}, range=${expandedBegin}/${expandedEnd}`);
77  const instancesMap: InstancesMap = new InstancesMap();
78  const rruleSet: RecurrenceSet = new RecurrenceSet(event.rRule, event.rDate, event.exRule, event.exDate);
79  const date = new Date();
80  if (rruleSet.hasRecurrence()) {
81    Log.warn(TAG, "generateInstancesMapFromValues not support rrule event yet");
82  } else {
83    if (event.dtStart > expandedEnd || event.dtEnd < expandedBegin) {
84      Log.debug(TAG, `expandOneInRange not in expandedRange:${event.dtStart}/${event.dtEnd}`);
85      return true;
86    }
87    const contentValues: data_rdb.ValuesBucket = getContentValuesFromEvent(date, event);
88    const syncIdKey = event.calendarId + "_" + event.id;
89    instancesMap.pushInstances(syncIdKey, contentValues);
90  }
91  if (instancesMap !== null && instancesMap !== undefined) {
92    batchInsertInstances(rdbStore, instancesMap);
93    return true;
94  }
95  return false;
96}
97
98/**
99 * 扩展单个日程的Instances
100 *
101 * @param eventId 日程ID
102 * @param beginTime 开始时间
103 * @param endTime 结束时间
104 */
105export function expandAllByPredicates(rdbStore, eventId: number, expandedBegin: number, expandedEnd: number): boolean {
106  return false
107}
108
109/**
110 * 根据数据库查询的ResultSet扩展生成Instances并插入数据库
111 *
112 * @param rdbStore 数据库实例
113 * @param resultSet Events表查询到的结果
114 * @return true 处理成功 false 处理失败
115 */
116async function expandInstancesByResultSet(rdbStore: data_rdb.RdbStore, resultSet): Promise<boolean> {
117  Log.debug(TAG, 'expandInstancesByResultSet generateInstancesMap begin')
118  const instancesMap = generateInstancesMapFromValues(resultSet)
119  Log.debug(TAG, `expandInstancesByResultSet generateInstancesMap end: ${instancesMap}`)
120  if (instancesMap !== null && instancesMap !== undefined) {
121    return await batchInsertInstances(rdbStore, instancesMap)
122  }
123  Log.warn(TAG, 'expandInstancesByResultSet maybe occurs some error')
124  return false
125}
126
127/**
128 * 根据日程信息,生成待插入的Instances列表数据
129 * 如果是重复日程,则列表中含多个Instances数据
130 * 如果不是重复日程,则列表中只含一个Instances数据
131 *
132 * @param resultSet db.query结果集
133 */
134function generateInstancesMapFromValues(resultSet): InstancesMap {
135  const moveToFirstSuccessful: boolean = resultSet.goToFirstRow()
136  if (!moveToFirstSuccessful) {
137    Log.error(TAG, "generateInstancesMapFromValues move to first failed")
138    return undefined
139  }
140  const instancesMap: InstancesMap = new InstancesMap()
141  const indexes: EventIndexes = parseIndexes(resultSet)
142  let event: Events
143  const date: Date = new Date()
144  do {
145    event = parseEvents(resultSet, indexes)
146    if (event === null || event === undefined) {
147      continue
148    }
149    const rruleSet: RecurrenceSet = new RecurrenceSet(event.rRule, event.rDate, event.exRule, event.exDate)
150    if (rruleSet.hasRecurrence()) {
151      Log.warn(TAG, "generateInstancesMapFromValues not support rrule event yet")
152    }
153    const contentValues: data_rdb.ValuesBucket = getContentValuesFromEvent(date, event)
154    const syncIdKey = event.calendarId + "_" + event.id
155    instancesMap.pushInstances(syncIdKey, contentValues)
156  } while (resultSet.goToNextRow())
157  return instancesMap
158}
159
160/**
161 * 根据Event数据生成Instances数据
162 *
163 * @param event Event数据
164 */
165function getContentValuesFromEvent(date: Date, event: Events): data_rdb.ValuesBucket {
166  const startDay = getJulianDayByMills(date, event.dtStart)
167  let endDay = getJulianDayByMills(date, event.dtEnd)
168  const startMinute = getMinutesByMills(date, event.dtStart)
169  let endMinute = getMinutesByMills(date, event.dtEnd)
170
171  // 午夜的特殊情况,当endMinute == 0.将前一天更改为+24小时
172  // Exception: 如果startMinute和endMinute在同一天都为0,只留下endMinute.
173  if (endMinute == 0 && endDay > startDay) {
174    endMinute = HOURS_PER_DAY * MINUTES_PER_HOUR;
175    endDay -= 1;
176  }
177  return {
178    [InstancesColumns.EVENT_ID]: event.id,
179    [InstancesColumns.BEGIN]: event.dtStart,
180    [InstancesColumns.END]: event.dtEnd,
181    [InstancesColumns.START_DAY]: startDay,
182    [InstancesColumns.END_DAY]: endDay,
183    [InstancesColumns.START_MINUTE]: startMinute,
184    [InstancesColumns.END_MINUTE]: endMinute,
185    [InstancesColumns.CREATOR]: event.creator
186  }
187}
188
189async function batchInsertInstances(rdbStore: data_rdb.RdbStore, instancesMap: InstancesMap): Promise<boolean> {
190  Log.debug(TAG, `batchInsertInstances begin`)
191  for (let valuesList of instancesMap.values()) {
192    for (let contentValues of valuesList) {
193      Log.debug(TAG, `InsertInstances begin: ${instancesValuesToString(contentValues)}`)
194      try {
195        const rowId = await rdbStore.insert(InstancesColumns.TABLE_NAME, contentValues)
196        if (rowId < 0) {
197          Log.warn(TAG, `InsertInstances failed ${instancesValuesToString(contentValues)}`)
198          return false
199        }
200      } catch (err) {
201        // 当eventId、begin、end三个字段与表中已有数据一致时,产生表约束冲突会抛出异常,此报错可忽略
202        // 为不影响其他Instances插入try-catch保护一下
203        Log.error(TAG, `InsertInstances get err:${err}`)
204      }
205      Log.debug(TAG, `InsertInstances end: ${instancesValuesToString(contentValues)}`)
206    }
207  }
208  Log.debug(TAG, `batchInsertInstances successful`)
209  return true
210}
211
212function instancesValuesToString(values): string {
213  return `eventId=${values.event_id},begin=${values.begin},end=${values.end}, startDay=${values.startDay}`
214  + `,endDay=${values.endDay},startMinute=${values.startMinute},endMinute=${values.endMinute}`
215}
216
217/**
218 * 存放Instances的集合Map
219 *
220 * @since 2022-05-28
221 */
222class InstancesMap extends Map<string, Array<data_rdb.ValuesBucket>> {
223  /**
224   * 以syncIdKey为key,将Instances对应的ValuesBucket放入集合Map中
225   * 注:每个key对应一个Instances列表
226   *
227   * @param syncIdKey Map集合分类的key
228   * @param values ValuesBucket数据,由此生成Instances实例
229   */
230  pushInstances(syncIdKey: string, values: data_rdb.ValuesBucket) {
231    let instancesList: Array<data_rdb.ValuesBucket> = undefined
232    if (this.has(syncIdKey)) {
233      instancesList = this.get(syncIdKey)
234    }
235    if (instancesList === null || instancesList === undefined) {
236      instancesList = new Array<data_rdb.ValuesBucket>()
237      this.set(syncIdKey, instancesList)
238    }
239    instancesList.push(values)
240  }
241}