ocelot-desktop/src/main/scala/ocelot/desktop/util/OcelotInterfaceLogStorage.scala
2025-09-03 11:06:40 +02:00

172 lines
5.0 KiB
Scala

package ocelot.desktop.util
import ocelot.desktop.entity.traits.OcelotInterface
import ocelot.desktop.ui.event.{BrainEvent, EventAware}
import ocelot.desktop.ui.widget.LogWidget.{LogEntry, TextLogEntry}
import ocelot.desktop.ui.widget.window.Windowed
import ocelot.desktop.util.OcelotInterfaceLogStorage._
import ocelot.desktop.windows.OcelotInterfaceWindow
import totoro.ocelot.brain.nbt.ExtendedNBT.extendNBTTagList
import totoro.ocelot.brain.nbt.{NBT, NBTBase, NBTTagCompound, NBTTagString}
import scala.collection.mutable
import scala.jdk.CollectionConverters.BufferHasAsJava
trait OcelotInterfaceLogStorage extends EventAware with Persistable with Windowed[OcelotInterfaceWindow] with Logging {
def ocelotInterface: OcelotInterface
def name: String
override def createWindow(): OcelotInterfaceWindow = new OcelotInterfaceWindow(this)
private var _messageLimit: Int = 1000
private val _entries = mutable.ArrayDeque.empty[LogEntry]
eventHandlers += {
case BrainEvent(OcelotInterface.LogEvent.Clear(_)) =>
clear()
case BrainEvent(OcelotInterface.LogEvent.CardToUser(_, message)) =>
addEntry(LogEntry.Rx(message))
case BrainEvent(OcelotInterface.LogEvent.UserToCard(_, message)) =>
addEntry(LogEntry.Tx(message))
}
private def loadEntry(nbt: NBTTagCompound): Either[String, LogEntry] = {
nbt.getString(EntryKindTag) match {
case EntryKindRx => Right(LogEntry.Rx(nbt.getString(EntryMessageTag)))
case EntryKindTx => Right(LogEntry.Tx(nbt.getString(EntryMessageTag)))
case "" => Left("entry kind not set")
case k => Left(s"unknown entry kind: $k")
}
}
private def saveEntry(entry: LogEntry): NBTTagCompound = {
val result = new NBTTagCompound()
entry match {
case LogEntry.Rx(message) =>
result.setString(EntryKindTag, EntryKindRx)
result.setString(EntryMessageTag, message)
case LogEntry.Tx(message) =>
result.setString(EntryKindTag, EntryKindTx)
result.setString(EntryMessageTag, message)
}
result
}
override def load(nbt: NBTTagCompound): Unit = {
super.load(nbt)
clear()
if (nbt.hasKey(MessageLimitTag)) {
messageLimit = nbt.getInteger(MessageLimitTag)
}
val entries = if (nbt.hasKey(EntriesTag)) {
nbt.getTagList(EntriesTag, NBT.TAG_COMPOUND).iterator[NBTTagCompound].zipWithIndex.flatMap {
case (entryNbt, idx) =>
loadEntry(entryNbt) match {
case Left(err) =>
logger.warn(
s"Could not restore log entry (idx $idx) of ocelot interface ${ocelotInterface.node.address}:" +
s" $err"
)
None
case Right(entry) => Some(entry)
}
}
} else {
// old save format: plain messages
nbt
.getTagList(MessagesTag, NBT.TAG_STRING)
.iterator[NBTTagString]
.map(entryNbt => LogEntry.Rx(entryNbt.getString))
}
addEntries(entries.toSeq)
}
override def save(nbt: NBTTagCompound): Unit = {
super.save(nbt)
nbt.setTagList(EntriesTag, _entries.map(saveEntry(_).asInstanceOf[NBTBase]).asJava)
nbt.setInteger(MessageLimitTag, _messageLimit)
}
def messageLimit: Int = _messageLimit
def messageLimit_=(limit: Int): Unit = {
require(limit > 0)
ensureFreeSpace(_entries.length - limit)
_messageLimit = limit
}
def entryCount: Int = {
_entries.length
}
def clear(): Unit = {
val count = _entries.length
_entries.clear()
window.onMessagesRemoved(count)
}
def getEntry(index: Int): LogEntry = _entries(index)
def findFirstTextEntry(exclude: String, start: Int): Int = {
_entries.indexWhere(it => it.isInstanceOf[TextLogEntry] && it.asInstanceOf[TextLogEntry].message != exclude, start)
}
def findLastTextEntry(exclude: String, end: Int): Int = {
_entries.lastIndexWhere(it => it.isInstanceOf[TextLogEntry] && it.asInstanceOf[TextLogEntry].message != exclude, end)
}
private def addEntry(entry: LogEntry): Unit = {
ensureFreeSpace(1)
_entries += entry
onMessagesAdded(Some(entry))
}
private def addEntries(entries: Seq[LogEntry]): Unit = {
ensureFreeSpace(entries.length)
val prevCount = _entries.length
_entries ++= entries.view.takeRight(messageLimit)
onMessagesAdded(_entries.view.takeRight(_entries.length - prevCount))
}
private def ensureFreeSpace(n: Int): Unit = {
val prevCount = _entries.length
_entries.takeRightInPlace(messageLimit - n)
val removedCount = prevCount - _entries.length
if (removedCount > 0) {
window.onMessagesRemoved(removedCount)
}
}
protected def onMessagesAdded(entries: => Iterable[LogEntry]): Unit = {
window.onMessagesAdded(entries)
}
}
object OcelotInterfaceLogStorage {
private val MessagesTag = "messages"
private val EntriesTag = "entries"
private val MessageLimitTag = "limit"
private val EntryKindTag = "kind"
private val EntryKindRx = "rx"
private val EntryKindTx = "tx"
private val EntryMessageTag = "msg"
}