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

dev.profunktor.redis4cats.pubsub.internals.PubSubInternals.scala Maven / Gradle / Ivy

There is a newer version: 1.7.1
Show newest version
/*
 * Copyright 2018-2021 ProfunKtor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package dev.profunktor.redis4cats.pubsub.internals

import scala.util.control.NoStackTrace

import cats.effect.kernel.{ Async, Ref, Resource, Sync }
import cats.effect.std.Dispatcher
import cats.syntax.all._
import dev.profunktor.redis4cats.data.RedisChannel
import dev.profunktor.redis4cats.data.RedisPattern
import dev.profunktor.redis4cats.data.RedisPatternEvent
import dev.profunktor.redis4cats.effect.Log
import fs2.concurrent.Topic
import io.lettuce.core.pubsub.{ RedisPubSubListener, StatefulRedisPubSubConnection }
import io.lettuce.core.pubsub.RedisPubSubAdapter

object PubSubInternals {
  case class DispatcherAlreadyShutdown() extends NoStackTrace

  private[redis4cats] def channelListener[F[_]: Async, K, V](
      channel: RedisChannel[K],
      topic: Topic[F, Option[V]],
      dispatcher: Dispatcher[F]
  ): RedisPubSubListener[K, V] =
    new RedisPubSubAdapter[K, V] {
      override def message(ch: K, msg: V): Unit =
        if (ch == channel.underlying) {
          try {
            dispatcher.unsafeRunSync(topic.publish1(Option(msg)).void)
          } catch {
            case _: IllegalStateException => throw DispatcherAlreadyShutdown()
          }
        }
      override def message(pattern: K, channel: K, message: V): Unit = this.message(channel, message)
    }
  private[redis4cats] def patternListener[F[_]: Async, K, V](
      redisPattern: RedisPattern[K],
      topic: Topic[F, Option[RedisPatternEvent[K, V]]],
      dispatcher: Dispatcher[F]
  ): RedisPubSubListener[K, V] =
    new RedisPubSubAdapter[K, V] {
      override def message(pattern: K, channel: K, message: V): Unit =
        if (pattern == redisPattern.underlying) {
          try {
            dispatcher.unsafeRunSync(topic.publish1(Option(RedisPatternEvent(pattern, channel, message))).void)
          } catch {
            case _: IllegalStateException => throw DispatcherAlreadyShutdown()
          }
        }
    }

  private[redis4cats] def channel[F[_]: Async: Log, K, V](
      state: Ref[F, PubSubState[F, K, V]],
      subConnection: StatefulRedisPubSubConnection[K, V]
  ): GetOrCreateTopicListener[F, K, V] = { channel => st =>
    st.channels
      .get(channel.underlying)
      .fold {
        for {
          dispatcher <- Dispatcher.parallel[F]
          topic <- Resource.eval(Topic[F, Option[V]])
          _ <- Resource.eval(Log[F].info(s"Creating listener for channel: $channel"))
          listener = channelListener(channel, topic, dispatcher)
          _ <- Resource.make {
                Sync[F].delay(subConnection.addListener(listener)) *>
                  state.update(s => s.copy(channels = s.channels.updated(channel.underlying, topic)))
              } { _ =>
                Sync[F].delay(subConnection.removeListener(listener)) *>
                  state.update(s => s.copy(channels = s.channels - channel.underlying))
              }
        } yield topic
      }(Resource.pure)
  }

  private[redis4cats] def pattern[F[_]: Async: Log, K, V](
      state: Ref[F, PubSubState[F, K, V]],
      subConnection: StatefulRedisPubSubConnection[K, V]
  ): GetOrCreatePatternListener[F, K, V] = { channel => st =>
    st.patterns
      .get(channel.underlying)
      .fold {
        for {
          dispatcher <- Dispatcher.parallel[F]
          topic <- Resource.eval(Topic[F, Option[RedisPatternEvent[K, V]]])
          _ <- Resource.eval(Log[F].info(s"Creating listener for pattern: $channel"))
          listener = patternListener(channel, topic, dispatcher)
          _ <- Resource.make {
                Sync[F].delay(subConnection.addListener(listener)) *>
                  state.update(s => s.copy(patterns = s.patterns.updated(channel.underlying, topic)))
              } { _ =>
                Sync[F].delay(subConnection.removeListener(listener)) *>
                  state.update(s => s.copy(patterns = s.patterns - channel.underlying))
              }
        } yield topic
      }(Resource.pure)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy