All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sss.openstar.message.MessageInBox.scala Maven / Gradle / Ivy

package sss.openstar.message

import java.sql.SQLIntegrityConstraintViolationException
import java.time.LocalDateTime
import sss.ancillary.{Guid, Logging}
import sss.db._
import sss.openstar.UniqueNodeIdentifier
import sss.openstar.schemamigration.SqlSchemaNames.ColumnNames._
import sss.openstar.message.MessageInBox.MessageQueryType.MessageQueryType
import sss.openstar.util.DateOps._
import sss.db.ops.DbOps.{DbRunOps, OptFutureTxOps}
import sss.openstar.schemamigration.SqlSchemaNames.TableNames.messageInboxTableName

object MessageInBox {

  object MessageQueryType extends Enumeration {
    type MessageQueryType = MessageQuery
    val NewestFirst = Value("NewestFirst", where(s"$parentGuidCol IS null") orderBy(OrderDesc(updatedAtCol)))
    val DefaultOrder = Value("DefaultOrder", where(s"$parentGuidCol IS null"))
    val DefaultOrderAll = Value("DefaultOrder", where())
    class MessageQuery private[MessageQueryType](name: String, val where : Where) extends Val(nextId, name)
    protected final def Value(name: String, where : Where): MessageQuery = new MessageQuery(name, where)
  }


  trait SavedMessage {
    val index: Long
    val msg: Message
    val savedAt: LocalDateTime
    val owner: UniqueNodeIdentifier
  }

  object SavedMessage {

    def apply(
      indx: Long,
      mesg: Message,
      svedAt: LocalDateTime,
      ownr: UniqueNodeIdentifier
    ): SavedMessage = new SavedMessage {
      override val index: Long = indx
      override val msg: Message = mesg
      override val savedAt: LocalDateTime = svedAt
      override val owner: UniqueNodeIdentifier = ownr
    }
  }

  object MsgStatus extends Enumeration {
    type MsgStatus = Value
    val New = Value(0)
    val Archived = Value(1)
    val SentConfirmed = Value(2)
    val Deleted = Value(3)
    val Junk = Value(4)
  }

  private var messageInBoxTable: Table = _
  private val messageInBoxTableLock = new Object()

  def apply(identity: UniqueNodeIdentifier)(implicit db: Db): MessageInBox = {
    if (messageInBoxTable eq null) {
      messageInBoxTableLock.synchronized {
        if (messageInBoxTable eq null) {
          messageInBoxTable = createTable
        }
      }
    }
    new MessageInBox(identity, messageInBoxTable)
  }

  class MessagePage[M](page: FutureTx[Option[Page]], f: Row => M)(implicit db:Db) {
    lazy val hasNext: Boolean = page.flatMap(_.map(_.hasNext).toFutureTxOpt).dbRunSyncGet.contains(true)
    lazy val hasPrev: Boolean = page.flatMap(_.map(_.hasPrev).toFutureTxOpt).dbRunSyncGet.contains(true)
    val messages: Seq[M] = page.dbRunSyncGet.map(_.rows map f).getOrElse(Seq.empty)

    lazy val next: MessagePage[M] = new MessagePage[M](page.flatMap(_.map(_.next).toFutureTxOpt).map(_.flatten), f)
    lazy val prev: MessagePage[M] = new MessagePage[M](page.flatMap(_.map(_.prev).toFutureTxOpt).map(_.flatten), f)
  }

  private def createTable(implicit db: Db): Table = db.table(messageInboxTableName )
}

class MessageInBox(
  val owner: UniqueNodeIdentifier,
  private val table: Table
)(implicit db: Db) extends Logging {

  import MessageInBox.MsgStatus._
  import MessageInBox._

  private def toMsg(r: Row): Message = Message(
    r.arrayByte(messageCol).toMessagePayload,
    Guid(r.arrayByte(guidCol)),
    r.arrayByteOpt(parentGuidCol) map Guid.apply
  )

  private def toSavedMessage(r: Row): SavedMessage = SavedMessage(
    r.long(idCol),
    toMsg(r),
    r.long(updatedAtCol).toLocalDateTime,
    r.string(ownerCol)
  )

  def guidToId(guid: Guid): Long = {
    table
      .find(where(ownerCol -> owner, guidCol -> guid.value))
      .map(_.map(_.id).getOrElse(throw new IllegalArgumentException(s"Guid $guid not in message inbox of $owner")))
      .dbRunSyncGet
  }

  def idToGuid(id: Long): Option[Guid] = {
    table
      .find(where(ownerCol -> owner, idCol -> id))
      .map(_.map(r => Guid(r.arrayByte(guidCol))))
      .dbRunSyncGet
  }

  def find(guid: Guid): Option[SavedMessage] = {
    findFTx(guid).dbRunSyncGet
  }

  def findFTx(guid: Guid): FutureTx[Option[SavedMessage]] = {
    table
      .find(where(ownerCol -> owner, guidCol -> guid.value))
      .map(_.map(toSavedMessage))
  }

  def get(id: Long): SavedMessage = {
    table
      .find(where(idCol -> id))
      .map(_.map(toSavedMessage).getOrElse(throw new IllegalArgumentException(s"No suchrow for id $id")))
      .dbRunSyncGet
  }

  def update(msg: Message): Unit = {
    table
      .update(
        Map(
          messageCol -> msg.msgPayload.toBytes,
          updatedAtCol -> LocalDateTime.now().toMillis
        ),
        where(ownerCol -> owner, guidCol -> msg.guid.value)
      )
      .dbRunSyncGet
  }

  def page(
    offset: Long,
    limit: Int,
    query: MessageQueryType
  ): Seq[SavedMessage] = {
    table
      .filter(
        query
          .where
          .and(where(ownerCol -> owner))
          .limit(offset, limit)
      )
      .map(_.map(toSavedMessage))
      .dbRunSyncGet
  }

  def count(q: MessageQueryType): Long = {
    table
      .filter(q.where.and(where(ownerCol -> owner)))
      .dbRunSyncGet
      .size
  }

  def addNew(msg: Message): Either[SavedMessage,SavedMessage] = add(msg, New)

  def addJunk(msg: Message): Either[SavedMessage,SavedMessage] = add(msg, Junk)

  private def add(msg: Message, status: MsgStatus): Either[SavedMessage, SavedMessage] = {

    val bs = msg.msgPayload.toBytes

    val mp = Map(
      ownerCol -> owner,
      statusCol -> status,
      messageCol -> bs,
      guidCol -> msg.guid.value,
      parentGuidCol -> msg.parentGuid.map(_.value),
      updatedAtCol -> LocalDateTime.now().toMillis
    )

    table
      .insert(mp)
      .dbRunSync
      .map(toSavedMessage)
      .map(Right(_))
      .recover {
        case e: SQLIntegrityConstraintViolationException =>
          find(msg.guid).map(Left(_)).getOrElse(throw e)
      }
      .get
  }


  def inBoxPager(pageSize: Int): MessagePage[SavedMessage] =
    new MessagePage(
      PagedView(
        table,
        pageSize,
        where(ownerCol -> owner, statusCol -> New) orderDesc updatedAtCol,
      ).lastPage,
      toSavedMessage
    )

  def archivedPager(pageSize: Int): MessagePage[SavedMessage] =
    new MessagePage(
      PagedView(
        table,
        pageSize,
        where(ownerCol -> owner, statusCol -> Archived) orderDesc updatedAtCol,
      ).lastPage,
      toSavedMessage
    )

  def junkPager(pageSize: Int): MessagePage[SavedMessage] =
    new MessagePage(
      PagedView(
        table,
        pageSize,
        where(ownerCol -> owner, statusCol -> Junk) orderDesc updatedAtCol,
      ).lastPage,
      toSavedMessage
    )

  def archive(saved: SavedMessage): Unit = {
    table
      .update(
        Map(statusCol -> Archived),
        where(idCol -> saved.index)
      )
      .dbRunSyncGet
  }

  def deleteSent(saved: SavedMessage): Unit = {
    table
      .delete(where(idCol -> saved.index))
      .dbRunSyncGet
  }

  def delete(guid: Guid): Boolean = {
    val numDeleted = table
      .delete(where(ownerCol -> owner, guidCol -> guid.value))
      .dbRunSyncGet

    require(numDeleted == 1 || numDeleted == 0, s"More than 1 guid deleted? $numDeleted $guid")
    numDeleted == 1
  }

  def delete(saved: SavedMessage): Boolean = {
    val numDeleted = table
      .delete(where(idCol -> saved.index))
      .dbRunSyncGet

    require(numDeleted == 1 || numDeleted == 0, s"More than 1 message deleted? $numDeleted $saved")
    numDeleted == 1
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy