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