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}