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

internal.FrameDispatcher.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2021 Hossein Naderi
 *
 * 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 lepus.client
package internal

import cats.effect.Concurrent
import cats.effect.kernel.Resource
import cats.effect.syntax.all.*
import cats.implicits.*
import fs2.concurrent.Signal
import fs2.concurrent.SignallingRef
import lepus.protocol.*
import lepus.protocol.constants.ReplyCode
import lepus.protocol.domains.*

private[client] trait FrameDispatcher[F[_]] {
  def header(h: Frame.Header): F[Unit]
  def body(b: Frame.Body): F[Unit]
  def invoke(m: Frame.Method): F[Unit]
  def onClose: F[Unit]

  def add[CHANNEL <: ChannelReceiver[F]](
      build: ChannelNumber => Resource[F, CHANNEL]
  ): Resource[F, CHANNEL]
  def channels: Signal[F, Set[ChannelNumber]]
}

private[client] object FrameDispatcher {
  private final case class State[F[_]](
      channels: Map[ChannelNumber, ChannelReceiver[F]] =
        Map.empty[ChannelNumber, ChannelReceiver[F]],
      nextChannel: ChannelNumber = ChannelNumber(1)
  )

  def apply[F[_]](using F: Concurrent[F]): F[FrameDispatcher[F]] = for {
    state <- SignallingRef[F].of(State[F]())
  } yield new {

    def onClose: F[Unit] =
      state.get.map(_.channels.values.toList).flatMap(_.traverse_(_.onClose))

    def header(h: Frame.Header): F[Unit] =
      call(h.channel)(_.header(h))

    def body(b: Frame.Body): F[Unit] =
      call(b.channel)(_.body(b))

    def invoke(m: Frame.Method): F[Unit] = call(m.channel)(ch =>
      m.value match {
        case d: (BasicClass.Deliver | BasicClass.Return) => ch.asyncContent(d)
        case d: (BasicClass.GetOk | BasicClass.GetEmpty.type) =>
          ch.syncContent(d)
        case other => ch.method(other)
      }
    )

    private def addRecvr(
        chNum: ChannelNumber,
        recvr: ChannelReceiver[F]
    ): Resource[F, Unit] =
      Resource.make(
        state.update(s => s.copy(channels = s.channels.updated(chNum, recvr)))
      )(_ => state.update(s => s.copy(channels = s.channels.removed(chNum))))

    private def call(
        ch: ChannelNumber
    )(f: ChannelReceiver[F] => F[Unit]): F[Unit] =
      state.get.map(_.channels.get(ch)).flatMap {
        case Some(r) => f(r)
        case None =>
          AMQPError(
            ReplyCode.NotFound,
            ShortString("No such channel found!"),
            ClassId(0),
            MethodId(0)
          ).raiseError
      }

    private def getNextChannelNumber = state.modify { s =>
      val nc = ChannelNumber((s.nextChannel + 1).toShort)
      val ns = s.copy(nextChannel = nc)

      (ns, s.nextChannel)
    }

    def channels: Signal[F, Set[ChannelNumber]] = state.map(_.channels.keySet)
    def add[CHANNEL <: ChannelReceiver[F]](
        build: ChannelNumber => Resource[F, CHANNEL]
    ): Resource[F, CHANNEL] = for {
      ch <- getNextChannelNumber.toResource
      out <- build(ch)
      _ <- addRecvr(ch, out)
    } yield out

  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy