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}