add timers repo and timers handler

This commit is contained in:
InsanusMokrassar 2022-12-14 09:43:12 +06:00
parent 9403b133f9
commit c632a2ba14
5 changed files with 160 additions and 2 deletions

View File

@ -4,8 +4,11 @@ import com.soywiz.klock.DateTime
import com.soywiz.klock.DateTimeTz import com.soywiz.klock.DateTimeTz
import com.soywiz.klock.Month import com.soywiz.klock.Month
import com.soywiz.klock.Year import com.soywiz.klock.Year
import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.plaguposter.common.SuccessfulSymbol import dev.inmo.plaguposter.common.SuccessfulSymbol
import dev.inmo.plaguposter.posts.models.Post
import dev.inmo.plaguposter.posts.models.PostId import dev.inmo.plaguposter.posts.models.PostId
import dev.inmo.tgbotapi.extensions.api.answers.answer
import dev.inmo.tgbotapi.extensions.api.edit.edit import dev.inmo.tgbotapi.extensions.api.edit.edit
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMessageDataCallbackQuery import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMessageDataCallbackQuery
@ -38,7 +41,9 @@ object ButtonsBuilder {
dataButton(SuccessfulSymbol, "$changeDateDataPrefix $postId $unixMillis") dataButton(SuccessfulSymbol, "$changeDateDataPrefix $postId $unixMillis")
} }
suspend fun BehaviourContext.includeKeyboardHandling() { suspend fun BehaviourContext.includeKeyboardHandling(
onSavePublishingTime: suspend (PostId, DateTime) -> Boolean
) {
fun buildKeyboard( fun buildKeyboard(
prefix: String, prefix: String,
postId: PostId, postId: PostId,
@ -180,5 +185,21 @@ object ButtonsBuilder {
oldDateTime.offset oldDateTime.offset
) )
} }
onMessageDataCallbackQuery(Regex("$changeDateDataPrefix .*")) {
val (_, rawPostId, rawDateTimeMillis) = it.data.split(" ")
val currentMillis = rawDateTimeMillis.toLongOrNull() ?: return@onMessageDataCallbackQuery
val currentDateTime = DateTime(currentMillis)
val postId = PostId(rawPostId)
val success = runCatchingSafely {
onSavePublishingTime(postId, currentDateTime)
}.getOrElse { false }
answer(
it,
if (success) "Successfully set timer" else "Unable to set timer"
)
}
} }
} }

View File

@ -0,0 +1,57 @@
package dev.inmo.plaguposter.triggers.timer
import com.soywiz.klock.DateTime
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
import dev.inmo.micro_utils.coroutines.plus
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.micro_utils.repos.unset
import dev.inmo.plaguposter.posts.models.PostId
import dev.inmo.plaguposter.posts.sending.PostPublisher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
class TimersHandler(
private val timersRepo: TimersRepo,
private val publisher: PostPublisher,
private val scope: CoroutineScope
) {
private var currentPostAndJob: Pair<PostId, Job>? = null
private val currentJobMutex = Mutex()
init {
(flowOf(Unit) + timersRepo.onNewValue + timersRepo.onValueRemoved).subscribeSafelyWithoutExceptions(scope) {
refreshPublishingJob()
}
}
private suspend fun refreshPublishingJob() {
val minimal = timersRepo.getMinimalDateTimePost()
currentJobMutex.withLock {
if (minimal ?.first == currentPostAndJob ?.first) {
return@withLock
}
currentPostAndJob ?.second ?.cancel()
currentPostAndJob = minimal ?.let { (postId, dateTime) ->
postId to scope.launchSafelyWithoutExceptions {
val now = DateTime.now()
val span = dateTime - now
delay(span.millisecondsLong)
publisher.publish(postId)
timersRepo.unset(postId)
refreshPublishingJob()
}
}
}
}
}

View File

@ -0,0 +1,9 @@
package dev.inmo.plaguposter.triggers.timer
import com.soywiz.klock.DateTime
import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.plaguposter.posts.models.PostId
interface TimersRepo : KeyValueRepo<PostId, DateTime> {
suspend fun getMinimalDateTimePost(): Pair<PostId, DateTime>?
}

View File

@ -2,8 +2,10 @@ package dev.inmo.plaguposter.triggers.timer
import com.soywiz.klock.DateTime import com.soywiz.klock.DateTime
import dev.inmo.micro_utils.coroutines.runCatchingSafely import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.micro_utils.repos.set
import dev.inmo.plagubot.Plugin import dev.inmo.plagubot.Plugin
import dev.inmo.plaguposter.posts.repo.ReadPostsRepo import dev.inmo.plaguposter.posts.repo.ReadPostsRepo
import dev.inmo.plaguposter.triggers.timer.repo.ExposedTimersRepo
import dev.inmo.tgbotapi.extensions.api.edit.edit import dev.inmo.tgbotapi.extensions.api.edit.edit
import dev.inmo.tgbotapi.extensions.api.send.send import dev.inmo.tgbotapi.extensions.api.send.send
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
@ -12,15 +14,22 @@ import kotlinx.serialization.json.*
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
import org.koin.core.Koin import org.koin.core.Koin
import org.koin.core.module.Module import org.koin.core.module.Module
import org.koin.dsl.binds
object Plugin : Plugin { object Plugin : Plugin {
override fun Module.setupDI(database: Database, params: JsonObject) { override fun Module.setupDI(database: Database, params: JsonObject) {
single { ExposedTimersRepo(get(), get(), get()) } binds arrayOf(TimersRepo::class)
single(createdAtStart = true) { TimersHandler(get(), get(), get()) }
} }
override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) { override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {
val postsRepo = koin.get<ReadPostsRepo>() val postsRepo = koin.get<ReadPostsRepo>()
val timersRepo = koin.get<TimersRepo>()
with(ButtonsBuilder) { with(ButtonsBuilder) {
includeKeyboardHandling() includeKeyboardHandling { postId, dateTime ->
timersRepo.set(postId, dateTime)
true
}
} }
onCommand("test") { onCommand("test") {
val reply = it.replyTo ?: return@onCommand val reply = it.replyTo ?: return@onCommand

View File

@ -0,0 +1,62 @@
package dev.inmo.plaguposter.triggers.timer.repo
import com.soywiz.klock.DateTime
import dev.inmo.micro_utils.common.firstNotNull
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.micro_utils.pagination.paginate
import dev.inmo.micro_utils.repos.exposed.initTable
import dev.inmo.micro_utils.repos.exposed.keyvalue.AbstractExposedKeyValueRepo
import dev.inmo.micro_utils.repos.unset
import dev.inmo.plaguposter.posts.models.PostId
import dev.inmo.plaguposter.posts.repo.PostsRepo
import dev.inmo.plaguposter.triggers.timer.TimersRepo
import kotlinx.coroutines.CoroutineScope
import org.jetbrains.exposed.sql.Column
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.ISqlExpressionBuilder
import org.jetbrains.exposed.sql.Op
import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.statements.InsertStatement
import org.jetbrains.exposed.sql.statements.UpdateBuilder
import org.jetbrains.exposed.sql.transactions.transaction
class ExposedTimersRepo(
database: Database,
postsRepo: PostsRepo,
scope: CoroutineScope
) : TimersRepo, AbstractExposedKeyValueRepo<PostId, DateTime>(
database,
"timers"
) {
override val keyColumn = text("post_id")
private val dateTimeColumn = long("date_time")
override val selectById: ISqlExpressionBuilder.(PostId) -> Op<Boolean> = { keyColumn.eq(it.string) }
override val selectByValue: ISqlExpressionBuilder.(DateTime) -> Op<Boolean> = { dateTimeColumn.eq(it.unixMillisLong) }
override val ResultRow.asKey: PostId
get() = PostId(get(keyColumn))
override val ResultRow.asObject: DateTime
get() = DateTime(get(dateTimeColumn))
val postsRepoListeningJob = postsRepo.deletedObjectsIdsFlow.subscribeSafelyWithoutExceptions(scope) {
unset(it)
}
init {
initTable()
}
override fun update(k: PostId, v: DateTime, it: UpdateBuilder<Int>) {
it[dateTimeColumn] = v.unixMillisLong
}
override fun insertKey(k: PostId, v: DateTime, it: InsertStatement<Number>) {
it[keyColumn] = k.string
}
override suspend fun getMinimalDateTimePost(): Pair<PostId, DateTime>? = transaction(database) {
selectAll().orderBy(dateTimeColumn).limit(1).firstOrNull() ?.let {
PostId(it[keyColumn]) to DateTime(it[dateTimeColumn])
}
}
}