Refactor handling of remaining time based triggers

This commit is contained in:
Jonas Lochmann 2021-01-11 01:00:00 +01:00
parent 5fd38f8ff1
commit 886ee14029
No known key found for this signature in database
GPG key ID: 8B8C9AEE10FA5B36
4 changed files with 113 additions and 75 deletions

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -57,6 +57,15 @@ data class UserRelatedData(
val categoryById: Map<String, CategoryRelatedData> by lazy { categories.associateBy { it.category.id } } val categoryById: Map<String, CategoryRelatedData> by lazy { categories.associateBy { it.category.id } }
val timeZone: TimeZone by lazy { user.getTimezone() } val timeZone: TimeZone by lazy { user.getTimezone() }
val preBlockSwitchPoints: Set<Long> by lazy {
mutableSetOf<Long>().also { result ->
categories.forEach { category ->
category.limitLoginCategories.forEach {
if (it.preBlockDuration > 0) result.add(it.preBlockDuration)
}
}
}
}
// O(n), but saves memory and index building time // O(n), but saves memory and index building time
// additionally a cache // additionally a cache

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -357,6 +357,77 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
.map { categoryHandlingCache.get(it) } .map { categoryHandlingCache.get(it) }
.filter { it.shouldCountTime } .filter { it.shouldCountTime }
fun timeToSubtractForCategory(categoryId: String): Int {
return if (usedTimeUpdateHelper.getCountedCategoryIds().contains(categoryId)) usedTimeUpdateHelper.getCountedTime() else 0
}
val triggerSync = kotlin.run {
var triggerSyncByLimitLoginCategory = false
var triggerSyncByTimeOver = false
categoryHandlingsToCount.forEach { handling ->
val category = handling.createdWithCategoryRelatedData.category
val categoryId = category.id
val timeToSubtractForCategory = timeToSubtractForCategory(categoryId)
val nowRemaining = handling.remainingTime ?: return@forEach // category is not limited anymore
val oldRemainingTime = nowRemaining.includingExtraTime - timeToSubtractForCategory
val newRemainingTime = oldRemainingTime - timeToSubtract
// trigger time warnings
if (oldRemainingTime / (1000 * 60) != newRemainingTime / (1000 * 60)) {
// eventually show remaining time warning
val roundedNewTime = ((newRemainingTime / (1000 * 60)) + 1) * (1000 * 60)
val flagIndex = CategoryTimeWarnings.durationToBitIndex[roundedNewTime]
if (flagIndex != null && category.timeWarnings and (1 shl flagIndex) != 0) {
appLogic.platformIntegration.showTimeWarningNotification(
title = appLogic.context.getString(R.string.time_warning_not_title, category.title),
text = TimeTextUtil.remaining(roundedNewTime.toInt(), appLogic.context)
)
}
}
// check if sync triggered by time over
if (oldRemainingTime > 0 && newRemainingTime <= 0) {
triggerSyncByTimeOver = true
}
// check if limit login triggered
val triggerSyncByLimitLoginCategoryForThisCategory = userRelatedData.preBlockSwitchPoints.let { switchPoints ->
if (switchPoints.isEmpty()) false else {
val newSessionDuration = handling.remainingSessionDuration?.let { it - timeToSubtractForCategory }
val limitLoginBySessionDuration = if (newSessionDuration != null) {
val oldSessionDuration = newSessionDuration + timeToSubtract
switchPoints.find { switchPoint -> oldSessionDuration >= switchPoint && newSessionDuration < switchPoint } != null
} else false
val limitLoginByRemainingTime = switchPoints.find { switchPoint -> oldRemainingTime >= switchPoint && newRemainingTime < switchPoint } != null
limitLoginBySessionDuration || limitLoginByRemainingTime
}
}
triggerSyncByLimitLoginCategory = triggerSyncByLimitLoginCategory || triggerSyncByLimitLoginCategoryForThisCategory
}
if (BuildConfig.DEBUG) {
if (triggerSyncByTimeOver) {
Log.d(LOG_TAG, "trigger sync because the time is over")
}
if (triggerSyncByLimitLoginCategory) {
Log.d(LOG_TAG, "trigger sync by the limit login category")
}
}
val triggerSync = triggerSyncByLimitLoginCategory || triggerSyncByTimeOver
triggerSync
}
if ( if (
usedTimeUpdateHelper.report( usedTimeUpdateHelper.report(
duration = timeToSubtract, duration = timeToSubtract,
@ -365,6 +436,10 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
handlings = categoryHandlingsToCount handlings = categoryHandlingsToCount
) )
) { ) {
if (BuildConfig.DEBUG) {
Log.d(LOG_TAG, "auto commit used times")
}
val newDeviceAndUserRelatedData = Threads.database.executeAndWait { val newDeviceAndUserRelatedData = Threads.database.executeAndWait {
appLogic.database.derivedDataDao().getUserAndDeviceRelatedDataSync() appLogic.database.derivedDataDao().getUserAndDeviceRelatedDataSync()
} }
@ -373,84 +448,30 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
newDeviceAndUserRelatedData?.userRelatedData?.user?.id != deviceAndUSerRelatedData.userRelatedData.user.id || newDeviceAndUserRelatedData?.userRelatedData?.user?.id != deviceAndUSerRelatedData.userRelatedData.user.id ||
newDeviceAndUserRelatedData.userRelatedData.categoryById.keys != deviceAndUSerRelatedData.userRelatedData.categoryById.keys newDeviceAndUserRelatedData.userRelatedData.categoryById.keys != deviceAndUSerRelatedData.userRelatedData.categoryById.keys
) { ) {
if (BuildConfig.DEBUG) {
Log.d(LOG_TAG, "restart the loop")
}
// start the loop directly again // start the loop directly again
continue continue
} }
val hasLimitLoginCategories = currentCategoryIds.find { userRelatedData.categoryById[it]?.limitLoginCategories?.isNotEmpty() ?: false } != null
val previousCategoryHandlings = if (hasLimitLoginCategories) {
currentCategoryIds.associateWith { categoryHandlingCache.get(it) }
} else null
reportStatusToCategoryHandlingCache(userRelatedData = newDeviceAndUserRelatedData.userRelatedData) reportStatusToCategoryHandlingCache(userRelatedData = newDeviceAndUserRelatedData.userRelatedData)
val triggerSyncByLimitLoginCategory = if (previousCategoryHandlings != null) {
val switchPoints = mutableSetOf<Long>()
currentCategoryIds.forEach {
userRelatedData.categoryById[it]?.let { category ->
category.limitLoginCategories.forEach {
if (it.preBlockDuration > 0) switchPoints.add(it.preBlockDuration)
}
}
}
currentCategoryIds.find { categoryId ->
val oldHandling = previousCategoryHandlings[categoryId]!!
val newHandling = categoryHandlingCache.get(categoryId)
switchPoints.find { switchPoint ->
(oldHandling.remainingTime != null && oldHandling.remainingTime.includingExtraTime >= switchPoint &&
(newHandling.remainingTime != null && newHandling.remainingTime.includingExtraTime < switchPoint)) ||
(oldHandling.remainingSessionDuration != null && oldHandling.remainingSessionDuration >= switchPoint &&
(newHandling.remainingSessionDuration != null && newHandling.remainingSessionDuration < switchPoint))
} != null
} != null
} else false
// eventually trigger sync
val allCategoriesWithRemainingTimeAfterSubtractingTime = currentCategoryIds.filter { categoryHandlingCache.get(it).hasRemainingTime }
val triggerSyncByTimeOver = allCategoriesWithRemainingTimeBeforeAddingUsedTime != allCategoriesWithRemainingTimeAfterSubtractingTime
val triggerSync = triggerSyncByLimitLoginCategory || triggerSyncByTimeOver
if (triggerSync) {
ApplyActionUtil.applyAppLogicAction(
action = ForceSyncAction,
appLogic = appLogic,
ignoreIfDeviceIsNotConfigured = true
)
}
} }
val categoriesToCount = categoryHandlingsToCount.map { it.createdWithCategoryRelatedData.category.id } // trigger sync when required
if (triggerSync) {
fun timeToSubtractForCategory(categoryId: String): Int { if (BuildConfig.DEBUG) {
return if (usedTimeUpdateHelper.getCountedCategoryIds().contains(categoryId)) usedTimeUpdateHelper.getCountedTime() else 0 Log.d(LOG_TAG, "trigger sync")
}
// trigger time warnings
categoriesToCount.forEach { categoryId ->
val category = userRelatedData.categoryById[categoryId]!!.category
val handling = categoryHandlingCache.get(categoryId)
val nowRemaining = handling.remainingTime ?: return@forEach // category is not limited anymore
val newRemainingTime = nowRemaining.includingExtraTime - timeToSubtractForCategory(categoryId)
val oldRemainingTime = newRemainingTime + timeToSubtract
if (oldRemainingTime / (1000 * 60) != newRemainingTime / (1000 * 60)) {
// eventually show remaining time warning
val roundedNewTime = ((newRemainingTime / (1000 * 60)) + 1) * (1000 * 60)
val flagIndex = CategoryTimeWarnings.durationToBitIndex[roundedNewTime]
if (flagIndex != null && category.timeWarnings and (1 shl flagIndex) != 0) {
appLogic.platformIntegration.showTimeWarningNotification(
title = appLogic.context.getString(R.string.time_warning_not_title, category.title),
text = TimeTextUtil.remaining(roundedNewTime.toInt(), appLogic.context)
)
}
} }
commitUsedTimeUpdaters()
ApplyActionUtil.applyAppLogicAction(
action = ForceSyncAction,
appLogic = appLogic,
ignoreIfDeviceIsNotConfigured = true
)
} }
// show notification // show notification

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -84,6 +84,10 @@ class UsedTimeUpdateHelper (private val appLogic: AppLogic) {
val makeCommit = makeCommitByDifferntHandling || makeCommitByDifferentBaseData || makeCommitByCountedTime val makeCommit = makeCommitByDifferntHandling || makeCommitByDifferentBaseData || makeCommitByCountedTime
val madeCommit = if (makeCommit) { val madeCommit = if (makeCommit) {
if (BuildConfig.DEBUG) {
Log.d(LOG_TAG, "makeCommitByDifferntHandling = $makeCommitByDifferntHandling; makeCommitByDifferentBaseData = $makeCommitByDifferentBaseData; makeCommitByCountedTime = $makeCommitByCountedTime")
}
doCommitPrivate() doCommitPrivate()
} else { } else {
false false

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2021 Jonas Lochmann
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -223,7 +223,11 @@ data class CategoryItselfHandling (
else else
remainingTime.default remainingTime.default
val maxTimeToAddBySessionDuration = remainingSessionDuration ?: Long.MAX_VALUE val maxTimeToAddBySessionDuration = remainingSessionDuration ?: Long.MAX_VALUE
val maxTimeToAdd = maxTimeToAddByRegularTime.coerceAtMost(maxTimeToAddBySessionDuration) val maxTimeToAdd = maxTimeToAddByRegularTime.coerceAtMost(maxTimeToAddBySessionDuration).let {
// use Long.MAX_VALUE if there is nothing remaining for the case that the blocking does not work
// to prevent flushing as often as possible
if (it > 0) it else Long.MAX_VALUE
}
val additionalTimeCountingSlots = if (shouldCountTime) val additionalTimeCountingSlots = if (shouldCountTime)
regularRelatedRules regularRelatedRules