mirror of
https://github.com/Eyre-S/Coeur-Morny-Cono.git
synced 2024-11-22 11:14:55 +08:00
for event, fix wrong OK stats, add CANCELED tag
- Now the status of EventEnv is a State array that infers the state history - State can be OK or CANCELED, and can be set multiple times - state method can get the last state set, and status method can get the state history - Default EventListener.executeFilter implementation is changed to true if stats is null - add consume[T](T=>Unit) for EventEnv, to simplifying the old consume[T](Class[T])(T=>Unit) - changed execution sort of EventListener in EventListenerManager. Now atEventPost method will be run after all events' normal listeners complete. - cha OnMedicationNotifyApply will only tag event as OK when the refresh function works (fixed part of the wrong OK state) - cha MornyOnUpdateTimestampOffsetLock tag event CANCELED but not OK to fix part of the wrong OK state
This commit is contained in:
parent
7ee4a0d3c5
commit
c5c6683459
@ -5,7 +5,7 @@ MORNY_ARCHIVE_NAME = morny-coeur
|
||||
MORNY_CODE_STORE = https://github.com/Eyre-S/Coeur-Morny-Cono
|
||||
MORNY_COMMIT_PATH = https://github.com/Eyre-S/Coeur-Morny-Cono/commit/%s
|
||||
|
||||
VERSION = 1.3.0-dev5
|
||||
VERSION = 1.3.0-dev6
|
||||
|
||||
USE_DELTA = false
|
||||
VERSION_DELTA =
|
||||
|
@ -1,9 +1,11 @@
|
||||
package cc.sukazyo.cono.morny.bot.api
|
||||
|
||||
import cc.sukazyo.cono.morny.util.EpochDateTime.EpochMillis
|
||||
import cc.sukazyo.messiva.utils.StackUtils
|
||||
import com.pengrad.telegrambot.model.Update
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.reflect.{classTag, ClassTag}
|
||||
|
||||
class EventEnv (
|
||||
|
||||
@ -11,15 +13,34 @@ class EventEnv (
|
||||
|
||||
) {
|
||||
|
||||
private var _isOk: Int = 0
|
||||
trait StateSource (val from: StackTraceElement)
|
||||
enum State:
|
||||
case OK (_from: StackTraceElement) extends State with StateSource(_from)
|
||||
case CANCELED (_from: StackTraceElement) extends State with StateSource(_from)
|
||||
|
||||
private val _status: mutable.ListBuffer[State] = mutable.ListBuffer.empty
|
||||
private val variables: mutable.HashMap[Class[?], Any] = mutable.HashMap.empty
|
||||
val timeStartup: EpochMillis = System.currentTimeMillis
|
||||
|
||||
def isEventOk: Boolean = _isOk > 0
|
||||
def isEventOk: Boolean = _status.lastOption match
|
||||
case Some(x) if x == State.OK => true
|
||||
case _ => false
|
||||
|
||||
//noinspection UnitMethodIsParameterless
|
||||
def setEventOk: Unit =
|
||||
_isOk = _isOk + 1
|
||||
_status += State.OK(StackUtils.getStackTrace(1)(1))
|
||||
|
||||
//noinspection UnitMethodIsParameterless
|
||||
def setEventCanceled: Unit =
|
||||
_status += State.CANCELED(StackUtils.getStackTrace(1)(1))
|
||||
|
||||
def state: State|Null =
|
||||
_status.lastOption match
|
||||
case Some(x) => x
|
||||
case None => null
|
||||
|
||||
def status: List[State] =
|
||||
_status.toList
|
||||
|
||||
def provide (i: Any): Unit =
|
||||
variables += (i.getClass -> i)
|
||||
@ -30,6 +51,11 @@ class EventEnv (
|
||||
case None => ConsumeResult(false)
|
||||
}
|
||||
|
||||
def consume [T: ClassTag] (consumer: T => Unit): ConsumeResult =
|
||||
variables get classTag[T].runtimeClass match
|
||||
case Some(i) => consumer(i.asInstanceOf[T]); ConsumeResult(true)
|
||||
case None => ConsumeResult(false)
|
||||
|
||||
class ConsumeResult (success: Boolean) {
|
||||
def onfail (processor: => Unit): Unit = {
|
||||
if !success then processor
|
||||
|
@ -17,8 +17,15 @@ trait EventListener () {
|
||||
* if it should not run.
|
||||
*/
|
||||
def executeFilter (using env: EventEnv): Boolean =
|
||||
if env.isEventOk then false else true
|
||||
if env.state == null then true else false
|
||||
|
||||
/** Run at all event listeners' listen methods done.
|
||||
*
|
||||
* Listen methods is the methods defined in [[EventListener this]]
|
||||
* trait starts with `on`.
|
||||
*
|
||||
* This method will always run no matter the result of [[executeFilter]]
|
||||
*/
|
||||
def atEventPost (using EventEnv): Unit = {}
|
||||
|
||||
def onMessage (using EventEnv): Unit = {}
|
||||
|
@ -9,7 +9,6 @@ import com.pengrad.telegrambot.UpdatesListener
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.language.postfixOps
|
||||
import scala.util.boundary
|
||||
|
||||
/** Contains a [[mutable.Queue]] of [[EventListener]], and delivery telegram [[Update]].
|
||||
*
|
||||
@ -30,59 +29,66 @@ class EventListenerManager (using coeur: MornyCoeur) extends UpdatesListener {
|
||||
this setName s"upd-${update.updateId()}-$t"
|
||||
|
||||
override def run (): Unit = {
|
||||
|
||||
given env: EventEnv = EventEnv(update)
|
||||
boundary { for (i <- listeners) {
|
||||
|
||||
if (i.executeFilter) try {
|
||||
|
||||
updateThreadName("message")
|
||||
if update.message ne null then i.onMessage
|
||||
updateThreadName("edited-message")
|
||||
if update.editedMessage ne null then i.onEditedMessage
|
||||
updateThreadName("channel-post")
|
||||
if update.channelPost ne null then i.onChannelPost
|
||||
updateThreadName("edited-channel-post")
|
||||
if update.editedChannelPost ne null then i.onEditedChannelPost
|
||||
updateThreadName("inline-query")
|
||||
if update.inlineQuery ne null then i.onInlineQuery
|
||||
updateThreadName("chosen-inline-result")
|
||||
if update.chosenInlineResult ne null then i.onChosenInlineResult
|
||||
updateThreadName("callback-query")
|
||||
if update.callbackQuery ne null then i.onCallbackQuery
|
||||
updateThreadName("shipping-query")
|
||||
if update.shippingQuery ne null then i.onShippingQuery
|
||||
updateThreadName("pre-checkout-query")
|
||||
if update.preCheckoutQuery ne null then i.onPreCheckoutQuery
|
||||
updateThreadName("poll")
|
||||
if update.poll ne null then i.onPoll
|
||||
updateThreadName("poll-answer")
|
||||
if update.pollAnswer ne null then i.onPollAnswer
|
||||
updateThreadName("my-chat-member")
|
||||
if update.myChatMember ne null then i.onMyChatMemberUpdated
|
||||
updateThreadName("chat-member")
|
||||
if update.chatMember ne null then i.onChatMemberUpdated
|
||||
updateThreadName("chat-join-request")
|
||||
if update.chatJoinRequest ne null then i.onChatJoinRequest
|
||||
|
||||
updateThreadName("#post")
|
||||
i.atEventPost
|
||||
|
||||
} catch case e => {
|
||||
val errorMessage = StringBuilder()
|
||||
errorMessage ++= "Event throws unexpected exception:\n"
|
||||
errorMessage ++= (exceptionLog(e) indent 4)
|
||||
e match
|
||||
case actionFailed: EventRuntimeException.ActionFailed =>
|
||||
errorMessage ++= "\ntg-api action: response track: "
|
||||
errorMessage ++= (GsonBuilder().setPrettyPrinting().create().toJson(
|
||||
actionFailed.response
|
||||
) indent 4) ++= "\n"
|
||||
case _ =>
|
||||
logger error errorMessage.toString
|
||||
coeur.daemons.reporter.exception(e, "on event running")
|
||||
}
|
||||
|
||||
}}
|
||||
|
||||
for (i <- listeners)
|
||||
if (i.executeFilter)
|
||||
runEventListener(i)
|
||||
for (i <- listeners)
|
||||
runEventPost(i)
|
||||
|
||||
}
|
||||
|
||||
private def runEventPost (i: EventListener)(using EventEnv): Unit = {
|
||||
updateThreadName("#post")
|
||||
i.atEventPost
|
||||
}
|
||||
|
||||
private def runEventListener (i: EventListener)(using EventEnv): Unit = {
|
||||
try {
|
||||
updateThreadName("message")
|
||||
if update.message ne null then i.onMessage
|
||||
updateThreadName("edited-message")
|
||||
if update.editedMessage ne null then i.onEditedMessage
|
||||
updateThreadName("channel-post")
|
||||
if update.channelPost ne null then i.onChannelPost
|
||||
updateThreadName("edited-channel-post")
|
||||
if update.editedChannelPost ne null then i.onEditedChannelPost
|
||||
updateThreadName("inline-query")
|
||||
if update.inlineQuery ne null then i.onInlineQuery
|
||||
updateThreadName("chosen-inline-result")
|
||||
if update.chosenInlineResult ne null then i.onChosenInlineResult
|
||||
updateThreadName("callback-query")
|
||||
if update.callbackQuery ne null then i.onCallbackQuery
|
||||
updateThreadName("shipping-query")
|
||||
if update.shippingQuery ne null then i.onShippingQuery
|
||||
updateThreadName("pre-checkout-query")
|
||||
if update.preCheckoutQuery ne null then i.onPreCheckoutQuery
|
||||
updateThreadName("poll")
|
||||
if update.poll ne null then i.onPoll
|
||||
updateThreadName("poll-answer")
|
||||
if update.pollAnswer ne null then i.onPollAnswer
|
||||
updateThreadName("my-chat-member")
|
||||
if update.myChatMember ne null then i.onMyChatMemberUpdated
|
||||
updateThreadName("chat-member")
|
||||
if update.chatMember ne null then i.onChatMemberUpdated
|
||||
updateThreadName("chat-join-request")
|
||||
if update.chatJoinRequest ne null then i.onChatJoinRequest
|
||||
} catch case e => {
|
||||
val errorMessage = StringBuilder()
|
||||
errorMessage ++= "Event throws unexpected exception:\n"
|
||||
errorMessage ++= (exceptionLog(e) indent 4)
|
||||
e match
|
||||
case actionFailed: EventRuntimeException.ActionFailed =>
|
||||
errorMessage ++= "\ntg-api action: response track: "
|
||||
errorMessage ++= (GsonBuilder().setPrettyPrinting().create().toJson(
|
||||
actionFailed.response
|
||||
) indent 4) ++= "\n"
|
||||
case _ =>
|
||||
logger error errorMessage.toString
|
||||
coeur.daemons.reporter.exception(e, "on event running")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,13 +2,12 @@ package cc.sukazyo.cono.morny.bot.event
|
||||
|
||||
import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener}
|
||||
import cc.sukazyo.cono.morny.MornyCoeur
|
||||
import com.pengrad.telegrambot.model.Update
|
||||
|
||||
class MornyOnUpdateTimestampOffsetLock (using coeur: MornyCoeur) extends EventListener {
|
||||
|
||||
private def checkOutdated (timestamp: Int)(using event: EventEnv): Unit =
|
||||
if coeur.config.eventIgnoreOutdated && (timestamp < (coeur.coeurStartTimestamp/1000)) then
|
||||
event.setEventOk
|
||||
event.setEventCanceled
|
||||
|
||||
override def onMessage (using event: EventEnv): Unit = checkOutdated(event.update.message.date)
|
||||
override def onEditedMessage (using event: EventEnv): Unit = checkOutdated(event.update.editedMessage.date)
|
||||
|
@ -5,7 +5,7 @@ import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener}
|
||||
import cc.sukazyo.cono.morny.data.TelegramStickers
|
||||
import cc.sukazyo.cono.morny.util.tgapi.formatting.TelegramFormatter.*
|
||||
import cc.sukazyo.cono.morny.util.tgapi.TelegramExtensions.Bot.exec
|
||||
import com.pengrad.telegrambot.model.{Chat, Message, Update, User}
|
||||
import com.pengrad.telegrambot.model.{Chat, Message, User}
|
||||
import com.pengrad.telegrambot.model.request.ParseMode
|
||||
import com.pengrad.telegrambot.request.{ForwardMessage, GetChat, SendMessage, SendSticker}
|
||||
|
||||
|
@ -2,8 +2,7 @@ package cc.sukazyo.cono.morny.bot.event
|
||||
|
||||
import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener}
|
||||
import cc.sukazyo.cono.morny.MornyCoeur
|
||||
import cc.sukazyo.cono.morny.daemon.{MedicationTimer, MornyDaemons}
|
||||
import com.pengrad.telegrambot.model.{Message, Update}
|
||||
import com.pengrad.telegrambot.model.Message
|
||||
|
||||
class OnMedicationNotifyApply (using coeur: MornyCoeur) extends EventListener {
|
||||
|
||||
@ -14,8 +13,8 @@ class OnMedicationNotifyApply (using coeur: MornyCoeur) extends EventListener {
|
||||
|
||||
private def editedMessageProcess (edited: Message)(using event: EventEnv): Unit = {
|
||||
if edited.chat.id != coeur.config.medicationNotifyToChat then return;
|
||||
coeur.daemons.medicationTimer.refreshNotificationWrite(edited)
|
||||
event.setEventOk
|
||||
if coeur.daemons.medicationTimer.refreshNotificationWrite(edited) then
|
||||
event.setEventOk
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,13 +4,12 @@ import cc.sukazyo.cono.morny.bot.api.{EventEnv, EventListener}
|
||||
import cc.sukazyo.cono.morny.bot.command.MornyCommands
|
||||
import cc.sukazyo.cono.morny.util.tgapi.InputCommand
|
||||
import cc.sukazyo.cono.morny.Log.logger
|
||||
import cc.sukazyo.cono.morny.MornyCoeur
|
||||
|
||||
class OnUniMeowTrigger (using commands: MornyCommands) (using coeur: MornyCoeur) extends EventListener {
|
||||
class OnUniMeowTrigger (using commands: MornyCommands) extends EventListener {
|
||||
|
||||
override def onMessage (using event: EventEnv): Unit = {
|
||||
|
||||
event.consume (classOf[InputCommand]) { input =>
|
||||
event.consume[InputCommand] { input =>
|
||||
logger trace s"got input command {$input} from event-context"
|
||||
|
||||
for ((name, command_instance) <- commands.commands_uni) {
|
||||
|
@ -69,8 +69,8 @@ class MedicationTimer (using coeur: MornyCoeur) {
|
||||
else lastNotify_messageId = None
|
||||
}
|
||||
|
||||
def refreshNotificationWrite (edited: Message): Unit = {
|
||||
if (lastNotify_messageId isEmpty) || (lastNotify_messageId.get != (edited.messageId toInt)) then return
|
||||
def refreshNotificationWrite (edited: Message): Boolean = {
|
||||
if (lastNotify_messageId isEmpty) || (lastNotify_messageId.get != (edited.messageId toInt)) then return false
|
||||
import cc.sukazyo.cono.morny.util.CommonFormat.formatDate
|
||||
val editTime = formatDate(edited.editDate*1000, use_timeZone.getTotalSeconds/60/60)
|
||||
val entities = ArrayBuffer.empty[MessageEntity]
|
||||
@ -82,6 +82,7 @@ class MedicationTimer (using coeur: MornyCoeur) {
|
||||
edited.text + s"\n-- $editTime --"
|
||||
).entities(entities toArray:_*)
|
||||
lastNotify_messageId = None
|
||||
true
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -134,10 +134,13 @@ class MornyReport (using coeur: MornyCoeur) {
|
||||
object EventStatistics {
|
||||
|
||||
private var eventTotal = 0
|
||||
private var eventCanceled = 0
|
||||
private val runningTime: NumericStatistics[DurationMillis] = NumericStatistics()
|
||||
|
||||
def reset (): Unit = {
|
||||
eventTotal = 0; runningTime.reset()
|
||||
eventTotal = 0
|
||||
eventCanceled = 0
|
||||
runningTime.reset()
|
||||
}
|
||||
|
||||
private def runningTimeStatisticsHTML: String =
|
||||
@ -154,11 +157,13 @@ class MornyReport (using coeur: MornyCoeur) {
|
||||
def eventStatisticsHTML: String =
|
||||
import cc.sukazyo.cono.morny.util.UseMath.percentageOf as p
|
||||
val processed = runningTime.count
|
||||
val ignored = eventTotal - processed
|
||||
val canceled = eventCanceled
|
||||
val ignored = eventTotal - processed - canceled
|
||||
// language=html
|
||||
s""" - <i>total event received</i>: <code>$eventTotal</code>
|
||||
| - <i>event processed</i>: (<code>${eventTotal p processed}%</code>) <code>$processed</code>
|
||||
| - <i>event ignored</i>: (<code>${eventTotal p ignored}%</code>) <code>$ignored</code>
|
||||
| - <i>event canceled</i>: (<code>${eventTotal p canceled}%</code>) <code>$canceled</code>
|
||||
| - <i>event processed</i>: (<code>${eventTotal p processed}%</code>) <code>$processed</code>
|
||||
| - <i>processed time usage</i>:
|
||||
|${runningTimeStatisticsHTML.indent(3)}""".stripMargin
|
||||
|
||||
@ -167,13 +172,23 @@ class MornyReport (using coeur: MornyCoeur) {
|
||||
//noinspection ScalaWeakerAccess
|
||||
case class EventTimeUsed (it: DurationMillis)
|
||||
override def atEventPost (using event: EventEnv): Unit = {
|
||||
import event.State
|
||||
eventTotal += 1
|
||||
if event.isEventOk then {
|
||||
val timeUsed = EventTimeUsed(System.currentTimeMillis - event.timeStartup)
|
||||
event provide timeUsed
|
||||
logger debug s"event consumed ${timeUsed.it}ms"
|
||||
runningTime ++ timeUsed.it
|
||||
}
|
||||
event.state match
|
||||
case State.OK(from) =>
|
||||
val timeUsed = EventTimeUsed(System.currentTimeMillis - event.timeStartup)
|
||||
event provide timeUsed
|
||||
logger debug
|
||||
s"""event done with OK
|
||||
| with time consumed ${timeUsed.it}ms
|
||||
| by $from""".stripMargin
|
||||
runningTime ++ timeUsed.it
|
||||
case State.CANCELED(from) =>
|
||||
eventCanceled += 1
|
||||
logger debug
|
||||
s"""event done with CANCELED"
|
||||
| by $from""".stripMargin
|
||||
case null =>
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user