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

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

package sss.openstar.message

import akka.actor.{Actor, ActorContext, ActorLogging, ActorRef, Props, Status}
import akka.pattern.pipe
import sss.ancillary.Guid
import sss.db.Db
import sss.openstar.chains.TxWriterActor.InternalCommit
import sss.openstar.contacts.ContactService
import sss.openstar.message.IncomingMessageProcessorUtil.defaultAsyncIncomingMessageConsumer
import sss.openstar.message.MessageDownloadActor._
import sss.openstar.message.MessageInBoxActor.{ConsumeResultsWrapper, NewPostPaywallMessageDownloaded, SaveOutgoingMessage}
import sss.openstar.message.MessagePayloadDecoder.MessagePayloadType
import sss.openstar.message.payloads.PaywalledMessage
import sss.openstar.network.MessageEventBus
import sss.openstar.tools.SendTxSupport.SendTx
import sss.openstar.util.ActorTryAndLog
import sss.openstar.{BusEvent, UniqueNodeIdentifier}

import scala.concurrent.Promise

object MessageInBoxActor {

  case class SaveOutgoingMessage(from: UniqueNodeIdentifier, msg: Message, promise: Promise[Unit])
      extends BusEvent

  private case class ConsumeResultsWrapper(wrapped: ConsumeResults, guid: Guid)

  private case class NewPostPaywallMessageDownloaded(forWho: UniqueNodeIdentifier, msg: Message)

  case class InBoxAddSent(forWho: UniqueNodeIdentifier, msg: Message) extends BusEvent
  case class InBoxAdd(forWho: UniqueNodeIdentifier, msg: Message) extends BusEvent
  case class InBoxUpdate(forWho: UniqueNodeIdentifier, msg: Message) extends BusEvent
  case class CheckedProps(p: Props, name: String)

  def name(userId: UniqueNodeIdentifier): String = s"MessageInBoxActor_$userId"

  def props(messageProcessor: AsyncMessageProcessor,
            userId: UniqueNodeIdentifier,
            watch: UtxoWatch,
            personalPaywall: PaywallChargeRedeemer
           )(implicit
             db: Db,
             events: MessageEventBus,
             sendTx: SendTx,
             contactService: ContactService,
             outgoingMessageProcessorUtil: OutgoingMessageProcessorUtil,
             utxoQuery: UtxoQuery
           ): CheckedProps = CheckedProps(Props(
    classOf[MessageInBoxActor],
    messageProcessor,
    userId,
    watch,
    personalPaywall,
    outgoingMessageProcessorUtil,
    db,
    events,
    sendTx,
    contactService,
    utxoQuery
  ), name(userId))

  def apply(props: CheckedProps)(implicit context: ActorContext): ActorRef =
    context.actorOf(props.p, props.name)
}

class MessageInBoxActor(
  messageProcessor: AsyncMessageProcessor,
  userId: UniqueNodeIdentifier,
  watch: UtxoWatch,
  personalPaywall: PaywallChargeRedeemer,
  outgoingMessageProcessorUtil: OutgoingMessageProcessorUtil
)(implicit
  db: Db,
  events: MessageEventBus,
  sendTx: SendTx,
  contactService: ContactService,
  utxoQuery: UtxoQuery
) extends Actor
    with ActorLogging
    with ActorTryAndLog {

  override def preStart(): Unit = {
    super.preStart()
    events subscribe classOf[NewMessageDownloaded]
    events subscribe classOf[SaveOutgoingMessage]
  }

  private case class MessageTxTracker(msg: Message)

  log.info("MessageInBox actor has started...")

  import context.dispatcher

  private val messageConsumer = (messageProcessor orElse defaultAsyncIncomingMessageConsumer).lift

  private val who = userId
  private implicit val inBox = MessageInBox(who)
  implicit val utxoWatch = watch

  private val paywalledType = MessagePayloadType.PaywalledMessageType.id.toByte

  override def receive: Receive = {

    case NewMessageDownloaded(`who`, m@Message(MessagePayload(`paywalledType`, _), guid, parentOpt)) =>
      tryAndLog {

        DecodedMessageCache(m).map {
          case PaywalledMessage(from, indexOpt, payload) =>

            personalPaywall(from, who)(indexOpt) map {
              case None =>
                //no paywall payment required
                self ! NewPostPaywallMessageDownloaded(who, Message(payload, guid, parentOpt))
              case Some(ledgerItemF) =>
                ledgerItemF flatMap { ledgerItem =>
                  sendTx(ledgerItem).whenCommitted.map(_.internalCommit) map {
                    case _: InternalCommit =>
                      self ! NewPostPaywallMessageDownloaded(who, Message(payload, guid, parentOpt))
                    case e =>
                      //see if it already processed.
                      indexOpt.map(index => utxoQuery(index) match {
                        case Some(_) =>
                          log.warning("Error processing paywall amount {}", e)
                        case None =>
                        //try again //TODO
                      })
                  }
                }
            } recover { case e =>
              log.warning("Error getting paywall amount {}", e)
            }

        }
      }.getOrElse(throw new RuntimeException(s"Failed to decode message? ${m.guid}"))



    case NewMessageDownloaded(`who`, m) =>
      log.error(s"No non paywalled messages are allowed - this is message type ${m.msgPayload.payloadType}")

    case NewPostPaywallMessageDownloaded(`who`, m @ Message(msgPayload, guid, parentGuidOpt)) =>
      tryAndLog {

        parentGuidOpt.foreach { pg =>
          if (inBox.find(pg).isEmpty) {
            log.warning(s"$who Parent $pg can't be found for $guid ? '")
          }
        }

        DecodedMessageCache(m).map { childDecoded =>
          val expandedMessage =
            ExpandedMessage(
              ExpandedComposite(
                childDecoded,
                guid,
                msgPayload
              ),
              parentGuidOpt
                .flatMap(inBox.find)
                .flatMap { parentMessage =>
                  DecodedMessageCache(parentMessage.msg)
                    .map(p =>
                      ExpandedComposite(
                        p,
                        parentMessage.msg.guid,
                        parentMessage.msg.msgPayload
                      )
                    )
                }
            )

          //TODO this will result in all kinds
          // of race conditions. Must rewrite.
          messageConsumer(expandedMessage)
            .map(_ map (ConsumeResultsWrapper(_, guid)))
            .map(_ pipeTo self)

        }.getOrElse(log.warning(s"Could not decode ${m.msgPayload.payloadType}"))

      }

    case Status.Failure(e) =>
      log.warning("Failed to consume results {}", e)

    case ConsumeResultsWrapper(results, guid) =>
      tryAndLog {
        val dedup = results.distinct
        //log.warning(results.toString)
        if (dedup.size != results.size) {
          log.warning(s"Dedup removed ${results.size - dedup.size} elements")
          log.warning(results.toString)
        }
        MessageProcessorUtils.defaultHandleConsumeResults(dedup)
        events publish MessageProcessed(who, guid)
      }

    case SaveOutgoingMessage(`who`, msg, promise) =>
      promise.complete(
        outgoingMessageProcessorUtil.processOutgoing(who, msg)
      )
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy