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

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

package sss.openstar.message

import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicReference
import akka.actor.{Actor, ActorContext, ActorLogging, ActorRef, Cancellable, Props}
import sss.ancillary.{Guid, ShortSessionKey}
import sss.openstar.chains.Chains.GlobalChainIdMask
import sss.openstar.controller.Send
import sss.openstar.identityledger.MessageStoreProviders
import sss.openstar.message.MessageDownloadActor._
import sss.openstar.network.MessageEventBus
import sss.openstar.network.MessageEventBus.IncomingMessage
import sss.openstar.util.ActorTryAndLog
import sss.openstar.{BusEvent, ExtraMessageKeys, MessageKeys, UniqueNodeIdentifier}

import scala.collection.concurrent.TrieMap
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.concurrent.duration._

/**
  * Created by alan on 6/8/16.
  */

object MessageDownloadActor {

  case class PublishUnProcessedMessages(forWho: UniqueNodeIdentifier, delayOpt: Option[FiniteDuration] = None) extends BusEvent

  case class NewMessageDownloaded(forWho: UniqueNodeIdentifier, msg: Message) extends BusEvent

  private case class CheckForMessages(provider: UniqueNodeIdentifier)

  case class MessageProcessed(whoProcessed: String, msgGuid: Guid) extends BusEvent

  //This is here because I couldn't organise the UI to reliably create a single instance per user
  private val onePerNodeId: AtomicNodeActorRefMap = new AtomicReference(Set())
  private val refMap: TrieMap[String, ActorRef] = TrieMap()

  def apply(
             who: UniqueNodeIdentifier,
             persistCache: MessageDownloadPersistCache,
             sessionKey: ShortSessionKey = ShortSessionKey()
           )(implicit actorSystem: ActorContext,
             messageEventBus: MessageEventBus,
             send: Send,
             chainId: GlobalChainIdMask): ActorRef = {


    def makeMessageDownloadActor(): ActorRef = actorSystem.actorOf(
      Props(classOf[MessageDownloadActor],
        onePerNodeId,
        who,
        persistCache,
        sessionKey,
        messageEventBus,
        send,
        chainId), s"MessageDownloadActor_$who"
    )

    val beforeAdd: Set[UniqueNodeIdentifier] = onePerNodeId.getAndUpdate(all => all + who)

    //BUG[psgs-589] TODO FIXME race condition.
    if (!beforeAdd(who)) {
      refMap.put(who, makeMessageDownloadActor())
    }
    refMap(who)
  }
}

class MessageDownloadActor(
                            refMap: AtomicNodeActorRefMap,
                            userId: UniqueNodeIdentifier,
                            persistCache: MessageDownloadPersistCache,
                            //userWallet: Wallet,
                            sessionKey: ShortSessionKey
                          )(implicit events: MessageEventBus,
                            send: Send,
                            chainId: GlobalChainIdMask)
  extends Actor
    with ActorLogging
    with ActorTryAndLog {

  override def preStart(): Unit = {
    super.preStart()
    events subscribe ExtraMessageKeys.PagedMessageMsg
    events subscribe ExtraMessageKeys.EndMessageQuery
    events subscribe ExtraMessageKeys.EndMessagePage
    events subscribe classOf[MessageStoreProviders]
    events subscribe classOf[MessageProcessed]
    events subscribe classOf[PublishUnProcessedMessages]
  }

  private var isQuietMap: Map[UniqueNodeIdentifier, Boolean] = Map.empty

  private case class MessageTxTracker(msg: Message, provider: String)

  //private var tracker: Map[String, MessageTxTracker] = Map()

  private var cancellables: Seq[Cancellable] = Seq.empty
  private var republishCancellable: Option[Cancellable] = None

  log.info("MessageDownload actor ({}) has started...", userId)


  override def postStop(): Unit = {

    log.info("MessageDownload actor {} down", userId)

    cancellables.foreach(_.cancel())

    //BUG[psgs-589] TODO FIXME race condition.
    refMap.getAndUpdate(refs =>
      refs - userId
    )
  }

  private val who = userId


  private def createQuery(provider: UniqueNodeIdentifier): Future[MessageQuery] =
    persistCache.mostRecentUniqueIncreasing(who, provider)
      .map(lastId => {
        //log.debug(s"Last id from $provider for $who is $lastId")
        MessageQuery(who, lastId, 25, sessionKey)
      })

  override def receive: Receive = {

    case PublishUnProcessedMessages(`who`, None) =>
      republishCancellable = None
      persistCache.findUnprocessed(who).foreach(msg => events publish NewMessageDownloaded(who, msg))

    case PublishUnProcessedMessages(`who`, Some(delay)) if republishCancellable.isEmpty =>
      republishCancellable = Some(context.system.scheduler.scheduleOnce(delay, self, PublishUnProcessedMessages(who)))

    case PublishUnProcessedMessages(`who`, Some(_)) =>
        //ignore as scheduled republish already exists.

    case MessageStoreProviders(`who`, providers) => tryAndLog {
      val providersPlusSelf = (providers + who).toSeq
      isQuietMap = providersPlusSelf.map(_ -> true).toMap
      var stagger = 0
      cancellables = providersPlusSelf map { provider =>
        val cancellable = context.system.scheduler.scheduleAtFixedRate(
          FiniteDuration(stagger, TimeUnit.SECONDS),
          FiniteDuration(5, TimeUnit.SECONDS),
          self,
          CheckForMessages(provider))
        stagger += 1
        cancellable
      }
    }

    case CheckForMessages(provider) => tryAndLog(

      isQuietMap.get(provider) foreach {
        case true =>
          createQuery(provider)
            .map { query =>
              send(
                ExtraMessageKeys.MessageQuery,
                query,
                provider)
            }
        case false =>
      }
    )

    case IncomingMessage(_, _, provider, EndMessagePage(`sessionKey`)) =>
      self ! CheckForMessages(provider)
      isQuietMap += provider -> false


    case IncomingMessage(_, _, provider, EndMessageQuery(`sessionKey`)) =>
      isQuietMap += provider -> true

    case IncomingMessage(_, _, provider, PagedMessage(`sessionKey`, uniqueIncreasing, msg: Message)) => tryAndLog {

      msg.parentGuid match {
        case None => log.info(s"${msg.guid} no parent")
        case Some(p) => log.info(s"${msg.guid} HAS parent $p")
      }
      isQuietMap += provider -> false

      persistCache.saveMessage(
        who,
        provider,
        uniqueIncreasing,
        msg
      ) foreach { futureSuccess =>
        log.debug(s"About to publish for $who provider: $provider $uniqueIncreasing")
        events publish NewMessageDownloaded(who, msg)
      }
    }

    case MessageProcessed(`who`, guid) =>
      persistCache.messageProcessed(who, guid)

    case x => //log.debug("Ignored {}", x)

  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy