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