From 0f9e1c50e6cde8fc0071d6c16775dcec14421654 Mon Sep 17 00:00:00 2001 From: Jonas Lochmann Date: Mon, 8 Mar 2021 01:00:00 +0100 Subject: [PATCH] Refactor adding used time to avoid exceptions --- .../dispatch-app-logic-action/addusedtime.ts | 48 +++++++++++++------ .../dispatch-app-logic-action/addusedtime2.ts | 45 +++++++++++------ 2 files changed, 64 insertions(+), 29 deletions(-) diff --git a/src/function/sync/apply-actions/dispatch-app-logic-action/addusedtime.ts b/src/function/sync/apply-actions/dispatch-app-logic-action/addusedtime.ts index 3370b19..1a5a5c2 100644 --- a/src/function/sync/apply-actions/dispatch-app-logic-action/addusedtime.ts +++ b/src/function/sync/apply-actions/dispatch-app-logic-action/addusedtime.ts @@ -1,6 +1,6 @@ /* * server component for the TimeLimit App - * Copyright (C) 2019 - 2020 Jonas Lochmann + * Copyright (C) 2019 - 2021 Jonas Lochmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -15,10 +15,10 @@ * along with this program. If not, see . */ -import * as Sequelize from 'sequelize' import { AddUsedTimeAction } from '../../../../action' import { MinuteOfDay } from '../../../../util/minuteofday' import { Cache } from '../cache' +import { IllegalStateException } from '../exception/illegal-state' import { MissingCategoryException } from '../exception/missing-item' export const getRoundedTimestamp = () => { @@ -65,17 +65,10 @@ export async function dispatchAddUsedTime ({ action, cache }: { currentExtraTime: number }) => { if (action.timeToAdd !== 0) { - const maxOperator = cache.database.dialect === 'sqlite' ? 'MAX' : 'GREATEST' - const minOperator = cache.database.dialect === 'sqlite' ? 'MIN' : 'LEAST' - - // try to update first - const [updatedRows] = await cache.database.usedTime.update({ - usedTime: Sequelize.literal(`${maxOperator}(0, ${minOperator}(usedTime + ${action.timeToAdd}, ${dayLengthInMs}))`) as any, - lastUpdate: roundedTimestamp - }, { + const oldItem = await cache.database.usedTime.findOne({ where: { familyId: cache.familyId, - categoryId: categoryId, + categoryId: action.categoryId, dayOfEpoch: action.dayOfEpoch, startMinuteOfDay: MinuteOfDay.MIN, endMinuteOfDay: MinuteOfDay.MAX @@ -83,13 +76,38 @@ export async function dispatchAddUsedTime ({ action, cache }: { transaction: cache.transaction }) - // otherwise create - if (updatedRows === 0) { + if (oldItem) { + const oldUsedTime = oldItem.usedTime + const newUsedTime = Math.max(0, Math.min(oldUsedTime + action.timeToAdd, dayLengthInMs)) + + const oldLastUpdate = parseInt(oldItem.lastUpdate, 10) + const newLastUpdate = parseInt(roundedTimestamp, 10) + + if (oldUsedTime !== newUsedTime || oldLastUpdate !== newLastUpdate) { + const [updatedRows] = await cache.database.usedTime.update({ + usedTime: newUsedTime, + lastUpdate: newLastUpdate.toString(10) + }, { + where: { + familyId: cache.familyId, + categoryId: action.categoryId, + dayOfEpoch: action.dayOfEpoch, + startMinuteOfDay: MinuteOfDay.MIN, + endMinuteOfDay: MinuteOfDay.MAX + }, + transaction: cache.transaction + }) + + if (updatedRows === 0) { + throw new IllegalStateException({ staticMessage: 'could not update fetched row' }) + } + } + } else { await cache.database.usedTime.create({ familyId: cache.familyId, - categoryId: categoryId, + categoryId: action.categoryId, dayOfEpoch: action.dayOfEpoch, - usedTime: Math.min(action.timeToAdd, dayLengthInMs), + usedTime: Math.max(0, Math.min(action.timeToAdd, dayLengthInMs)), lastUpdate: roundedTimestamp, startMinuteOfDay: MinuteOfDay.MIN, endMinuteOfDay: MinuteOfDay.MAX diff --git a/src/function/sync/apply-actions/dispatch-app-logic-action/addusedtime2.ts b/src/function/sync/apply-actions/dispatch-app-logic-action/addusedtime2.ts index 68568ce..c1e8ed1 100644 --- a/src/function/sync/apply-actions/dispatch-app-logic-action/addusedtime2.ts +++ b/src/function/sync/apply-actions/dispatch-app-logic-action/addusedtime2.ts @@ -1,6 +1,6 @@ /* * server component for the TimeLimit App - * Copyright (C) 2019 - 2020 Jonas Lochmann + * Copyright (C) 2019 - 2021 Jonas Lochmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -15,12 +15,11 @@ * along with this program. If not, see . */ -import * as Sequelize from 'sequelize' import { AddUsedTimeActionVersion2 } from '../../../../action' import { EventHandler } from '../../../../monitoring/eventhandler' import { MinuteOfDay } from '../../../../util/minuteofday' import { Cache } from '../cache' -import { SourceDeviceNotFoundException } from '../exception/illegal-state' +import { IllegalStateException, SourceDeviceNotFoundException } from '../exception/illegal-state' import { getRoundedTimestamp as getRoundedTimestampForUsedTime } from './addusedtime' export const getRoundedTimestampForSessionDuration = () => { @@ -92,14 +91,7 @@ export async function dispatchAddUsedTimeVersion2 ({ deviceId, action, cache, ev const lengthInMinutes = (end - start) + 1 const lengthInMs = lengthInMinutes * 1000 * 60 - const maxOperator = cache.database.dialect === 'sqlite' ? 'MAX' : 'GREATEST' - const minOperator = cache.database.dialect === 'sqlite' ? 'MIN' : 'LEAST' - - // try to update first - const [updatedRows] = await cache.database.usedTime.update({ - usedTime: Sequelize.literal(`${maxOperator}(0, ${minOperator}(usedTime + ${item.timeToAdd}, ${lengthInMs}))`) as any, - lastUpdate: roundedTimestampForUsedTime - }, { + const oldItem = await cache.database.usedTime.findOne({ where: { familyId: cache.familyId, categoryId: item.categoryId, @@ -110,13 +102,38 @@ export async function dispatchAddUsedTimeVersion2 ({ deviceId, action, cache, ev transaction: cache.transaction }) - // otherwise create - if (updatedRows === 0) { + if (oldItem) { + const oldUsedTime = oldItem.usedTime + const newUsedTime = Math.max(0, Math.min(oldUsedTime + item.timeToAdd, lengthInMs)) + + const oldLastUpdate = parseInt(oldItem.lastUpdate, 10) + const newLastUpdate = parseInt(roundedTimestampForUsedTime, 10) + + if (oldUsedTime !== newUsedTime || oldLastUpdate !== newLastUpdate) { + const [updatedRows] = await cache.database.usedTime.update({ + usedTime: newUsedTime, + lastUpdate: newLastUpdate.toString(10) + }, { + where: { + familyId: cache.familyId, + categoryId: item.categoryId, + dayOfEpoch: action.dayOfEpoch, + startMinuteOfDay: start, + endMinuteOfDay: end + }, + transaction: cache.transaction + }) + + if (updatedRows === 0) { + throw new IllegalStateException({ staticMessage: 'could not update fetched row' }) + } + } + } else { await cache.database.usedTime.create({ familyId: cache.familyId, categoryId: item.categoryId, dayOfEpoch: action.dayOfEpoch, - usedTime: Math.min(item.timeToAdd, lengthInMs), + usedTime: Math.max(0, Math.min(item.timeToAdd, lengthInMs)), lastUpdate: roundedTimestampForUsedTime, startMinuteOfDay: start, endMinuteOfDay: end