<commit_before>package lila.push

import akka.actor._
import java.io.InputStream
import scala.util.Failure

import play.api.libs.json._

private final class ApplePush(
    getDevice: String => Fu[Option[Device]],
    system: ActorSystem,
    certificate: InputStream,
    password: String) {

  private val actor = system.actorOf(Props(classOf[ApnsActor], certificate, password))

  def apply(userId: String)(data: => PushApi.Data): Funit =
    getDevice(userId) map {
      _ foreach { device =>
        actor ! ApplePush.Notification(device.deviceId, Json.obj(
          "alert" -> Json.obj(
            "title" -> data.title,
            "body" -> data.body),
          "data" -> data.payload))
      }
    }
}

object ApplePush {

  case class Notification(token: String, payload: JsObject)
}

// the damn API is blocking, so at least use only one thread at a time
private final class ApnsActor(certificate: InputStream, password: String) extends Actor {

  import com.relayrides.pushy.apns._, util._

  val logger = play.api.Logger("push")

  var manager: PushManager[SimpleApnsPushNotification] = _

  def getManager = Option(manager) getOrElse {
    val m = new PushManager[SimpleApnsPushNotification](
      ApnsEnvironment.getProductionEnvironment(),
      SSLContextUtil.createDefaultSSLContext(certificate, password),
      null, // Optional: custom event loop group
      null, // Optional: custom ExecutorService for calling listeners
      null, // Optional: custom BlockingQueue implementation
      new PushManagerConfiguration(),
      "ApplePushManager")

    m.registerRejectedNotificationListener(new RejectedNotificationListener[SimpleApnsPushNotification] {
      override def handleRejectedNotification(
        m: PushManager[_ <: SimpleApnsPushNotification],
        notification: SimpleApnsPushNotification,
        reason: RejectedNotificationReason) {
        logger.error(s"$notification was rejected with rejection reason $reason")
      }
    })
    m.registerFailedConnectionListener(new FailedConnectionListener[SimpleApnsPushNotification] {
      override def handleFailedConnection(
        m: PushManager[_ <: SimpleApnsPushNotification],
        cause: Throwable) {
        logger.error(s"Can't connect because $cause")
        cause match {
          case ssl: javax.net.ssl.SSLHandshakeException =>
            logger.error(s"This is probably a permanent failure, and we should shut down the manager")
          case _ =>
        }
      }
    })
    m.start()
    manager = m
    m
  }

  override def postStop() {
    Option(manager).foreach(_.shutdown())
  }

  def receive = {
    case ApplePush.Notification(token, payload) =>

      val payloadBuilder = new ApnsPayloadBuilder()

      payloadBuilder.setAlertBody(Json stringify payload)
      payloadBuilder.setBadgeNumber(1)

      val notif = new SimpleApnsPushNotification(
        TokenUtil.tokenStringToByteArray(token),
        payloadBuilder.buildWithDefaultMaximumLength())

      getManager.getQueue().put(notif)
  }
}<commit_msg>apple push: separate alert and payload (using custom property (?))<commit_after>package lila.push

import akka.actor._
import java.io.InputStream
import scala.util.Failure

import play.api.libs.json._

private final class ApplePush(
    getDevice: String => Fu[Option[Device]],
    system: ActorSystem,
    certificate: InputStream,
    password: String) {

  private val actor = system.actorOf(Props(classOf[ApnsActor], certificate, password))

  def apply(userId: String)(data: => PushApi.Data): Funit =
    getDevice(userId) map {
      _ foreach { device =>
        actor ! ApplePush.Notification(
          token = device.deviceId,
          alert = Json.obj(
            "title" -> data.title,
            "body" -> data.body),
          payload = data.payload)
      }
    }
}

object ApplePush {

  case class Notification(token: String, alert: JsObject, payload: JsObject)
}

// the damn API is blocking, so at least use only one thread at a time
private final class ApnsActor(certificate: InputStream, password: String) extends Actor {

  import com.relayrides.pushy.apns._, util._

  val logger = play.api.Logger("push")

  var manager: PushManager[SimpleApnsPushNotification] = _

  def getManager = Option(manager) getOrElse {
    val m = new PushManager[SimpleApnsPushNotification](
      ApnsEnvironment.getProductionEnvironment(),
      SSLContextUtil.createDefaultSSLContext(certificate, password),
      null, // Optional: custom event loop group
      null, // Optional: custom ExecutorService for calling listeners
      null, // Optional: custom BlockingQueue implementation
      new PushManagerConfiguration(),
      "ApplePushManager")

    m.registerRejectedNotificationListener(new RejectedNotificationListener[SimpleApnsPushNotification] {
      override def handleRejectedNotification(
        m: PushManager[_ <: SimpleApnsPushNotification],
        notification: SimpleApnsPushNotification,
        reason: RejectedNotificationReason) {
        logger.error(s"$notification was rejected with rejection reason $reason")
      }
    })
    m.registerFailedConnectionListener(new FailedConnectionListener[SimpleApnsPushNotification] {
      override def handleFailedConnection(
        m: PushManager[_ <: SimpleApnsPushNotification],
        cause: Throwable) {
        logger.error(s"Can't connect because $cause")
        cause match {
          case ssl: javax.net.ssl.SSLHandshakeException =>
            logger.error(s"This is probably a permanent failure, and we should shut down the manager")
          case _ =>
        }
      }
    })
    m.start()
    manager = m
    m
  }

  override def postStop() {
    Option(manager).foreach(_.shutdown())
  }

  def receive = {
    case ApplePush.Notification(token, alert, payload) =>

      val payloadBuilder = new ApnsPayloadBuilder()

      payloadBuilder.setAlertBody(Json stringify alert)
      payloadBuilder.setBadgeNumber(1)
      payloadBuilder.addCustomProperty("data", Json stringify payload)

      val notif = new SimpleApnsPushNotification(
        TokenUtil.tokenStringToByteArray(token),
        payloadBuilder.buildWithDefaultMaximumLength())

      logger.info(s"Sending alert=$alert, payload=$payload to $token")

      getManager.getQueue().put(notif)
  }
}
{'fraction_non_alphanumeric': 0.09216589861751152, 'fraction_numerical': 0.0003178134435086604, 'mean_word_length': 3.334710743801653, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 2, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7643646', 'n_tokens_mistral': 1730, 'n_tokens_neox': 1665, 'n_words': 426}