diff --git a/gradle.properties b/gradle.properties index ed12f09..24782ed 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 = diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventEnv.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventEnv.scala index 43a532f..9061a1f 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventEnv.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventEnv.scala @@ -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 diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListener.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListener.scala index 8834060..fa7c490 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListener.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListener.scala @@ -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 = {} diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListenerManager.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListenerManager.scala index 9643578..7a810f4 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListenerManager.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListenerManager.scala @@ -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") + } } } diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnUpdateTimestampOffsetLock.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnUpdateTimestampOffsetLock.scala index ae44432..734af34 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnUpdateTimestampOffsetLock.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/MornyOnUpdateTimestampOffsetLock.scala @@ -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) diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMe.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMe.scala index 5dbbb9f..5d303d2 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMe.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMe.scala @@ -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} diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnMedicationNotifyApply.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnMedicationNotifyApply.scala index ed24c3b..40f20c4 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnMedicationNotifyApply.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnMedicationNotifyApply.scala @@ -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 } } diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUniMeowTrigger.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUniMeowTrigger.scala index ccf140f..40e4fd3 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUniMeowTrigger.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnUniMeowTrigger.scala @@ -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) { diff --git a/src/main/scala/cc/sukazyo/cono/morny/daemon/MedicationTimer.scala b/src/main/scala/cc/sukazyo/cono/morny/daemon/MedicationTimer.scala index 1171379..8275614 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/daemon/MedicationTimer.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/daemon/MedicationTimer.scala @@ -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 } } diff --git a/src/main/scala/cc/sukazyo/cono/morny/daemon/MornyReport.scala b/src/main/scala/cc/sukazyo/cono/morny/daemon/MornyReport.scala index a7ee9f9..307401a 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/daemon/MornyReport.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/daemon/MornyReport.scala @@ -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""" - total event received: $eventTotal - | - event processed: (${eventTotal p processed}%) $processed | - event ignored: (${eventTotal p ignored}%) $ignored + | - event canceled: (${eventTotal p canceled}%) $canceled + | - event processed: (${eventTotal p processed}%) $processed | - processed time usage: |${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 => } }