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

sss.openstar.rpc.RpcBridgeEventSource.scala Maven / Gradle / Ivy

package sss.openstar.rpc

import akka.NotUsed
import akka.actor.{Actor, ActorContext, ActorLogging, ActorRef, Cancellable, Props, Status, Terminated}
import akka.stream.scaladsl.{BroadcastHub, Keep, Source}
import akka.stream.{CompletionStrategy, OverflowStrategy}
import com.google.protobuf.ByteString
import sss.db.Db
import sss.openstar.ledger.LedgerId
import sss.openstar.message.MessageInBox
import sss.openstar.message.MessageInBoxActor.{InBoxAdd, InBoxUpdate}
import sss.openstar.network.MessageEventBus
import sss.openstar.rpc.RpcBridgeEventSource.{RpcBridgeEvent, RpcBridgeEventSourceShutdown, SendTimeout}
import sss.openstar.ui.rpc.Event.Event.GenericEvent
import sss.openstar.ui.rpc.EventPayload
import sss.openstar.ui.rpc.{ClientBalance, Event}
import sss.openstar.wallet.ClientWallet.NewClientBalance
import sss.openstar.wallet.UtxoTracker.NewBalance
import sss.openstar.{BusEvent, message}

import scala.concurrent.duration._
import scala.util.Try

object RpcBridgeEventSource {

  trait RpcBridgeEvent extends BusEvent {
    def eventType: String
    def payload: Array[Byte]
  }

  private case object SendTimeout
  case class RpcBridgeEventSourceShutdown(user: String) extends BusEvent

  def apply(bridgeUser: String,
            ledgerId: LedgerId)(implicit actorContext: ActorContext,
              events: MessageEventBus, db :Db
              ): Source[Event, NotUsed] = {

    //Kill old actor
    events publish(RpcBridgeEventSourceShutdown(bridgeUser))

    implicit val as = actorContext.system

    val completionMatcher: PartialFunction[Any, CompletionStrategy] = {
      case Status.Success => CompletionStrategy.draining
    }

    val failureMatcher: PartialFunction[Any, Throwable] = PartialFunction.empty

    val sourceActor = Source.actorRef(
      completionMatcher,
      failureMatcher,
      Int.MaxValue,
      OverflowStrategy.fail
    )

    val (sourceRef: ActorRef, outbound: Source[Event, NotUsed]) =
      sourceActor
        .toMat(BroadcastHub.sink[Event])(Keep.both)
        .run()

    actorContext.actorOf(
      Props(
        classOf[RpcBridgeEventSource],
        bridgeUser,
        ledgerId,
        sourceRef,
        events,
        db)
    )

    outbound
  }
}

private class RpcBridgeEventSource(bridgeUser:String,
                                   ledgerId: LedgerId,
                                   streamRef: ActorRef)
                                  (implicit events: MessageEventBus,
                                   db: Db) extends Actor with ActorLogging {


  //Stream will disconnect after 60s idle.
  private var timeoutHandle: Option[Cancellable] = None

  resetTimeout()

  private def resetTimeout() = {
    import context.dispatcher
    timeoutHandle.map(_.cancel())
    timeoutHandle = Some(context.system.scheduler.scheduleOnce(50.seconds, self, SendTimeout))
  }

  override def preStart(): Unit = {
    super.preStart()
    events subscribe classOf[NewClientBalance]
    events subscribe classOf[NewBalance]
    events subscribe classOf[InBoxUpdate]
    events subscribe classOf[InBoxAdd]
    events subscribe classOf[RpcBridgeEventSourceShutdown]
    // add new generic bridge event but keep support for
    // previous specific events (above)
    events subscribe classOf[RpcBridgeEvent]

  }

  val inBox = MessageInBox(bridgeUser)

  context watch streamRef

  log.info(s"EventSource started for $bridgeUser")

  override def postStop(): Unit =
    timeoutHandle.map(_.cancel())


  override def receive: Receive = {
    case NewBalance(`ledgerId`, `bridgeUser`, bal) =>
      streamRef ! Event(Event.Event.NewBalance(bal.value))
      resetTimeout()

    case NewClientBalance(`ledgerId`, `bridgeUser`, client, bal, delta) =>
      streamRef ! Event(Event.Event.NewClientBalance(ClientBalance(bal.value, client, delta.value)))
      resetTimeout()


    case InBoxAdd(`bridgeUser`, msg) =>
      fireMsgUpdate(msg)

    case InBoxUpdate(`bridgeUser`, msg) =>
      fireMsgUpdate(msg)

    case RpcBridgeEventSourceShutdown(`bridgeUser`) =>
      streamRef ! Status.Success
      context stop self

    case SendTimeout =>
      streamRef ! Event(Event.Event.Empty)
      resetTimeout()

    case e: RpcBridgeEvent =>
      streamRef ! Event(GenericEvent(EventPayload(e.eventType, ByteString.copyFrom(e.payload))))

    case Terminated(`streamRef`) =>
      context stop self

    case event =>
      //prevent dead letters
      log.debug(s"$event")
  }

  private def fireMsgUpdate(msg: message.Message): Unit = {
    Try(inBox.guidToId(msg.guid)) map { id =>
      streamRef ! Event(Event.Event.Msg(id))
    } recover {
      case e => log.error(e, "Got an InBox* for a bad guid?")
    }
    resetTimeout()
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy