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

epus-client_native0.4_3.0.5.3.source-code.Connection.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

import cats.effect.*
import cats.effect.implicits.*
import cats.effect.kernel.Resource.ExitCase.*
import cats.effect.std.Queue
import fs2.Pipe
import fs2.Stream
import fs2.concurrent.Signal
import lepus.client.Connection.Status
import lepus.client.apis.*
import lepus.protocol.*
import lepus.protocol.domains.ChannelNumber
import lepus.protocol.domains.Path

import scala.concurrent.duration.*

import internal.*

trait Connection[F[_]] {
  def channel: Resource[F, Channel[F, NormalMessagingChannel[F]]]
  def reliableChannel
      : Resource[F, Channel[F, ReliablePublishingMessagingChannel[F]]]
  def transactionalChannel
      : Resource[F, Channel[F, TransactionalMessagingChannel[F]]]

  def status: Signal[F, Connection.Status]
  def channels: Signal[F, Set[ChannelNumber]]
  def capabilities: Capabilities
}

object Connection {
  def from[F[_]: Temporal](
      transport: Transport[F],
      auth: AuthenticationConfig[F],
      path: Path = Path("/"),
      config: ConnectionConfig
  ): Resource[F, Connection[F]] = for {
    sendQ <- Resource.eval(Queue.bounded[F, Frame](config.frameBufSize))
    negotiation <- StartupNegotiation(
      auth,
      path,
      connectionName = config.name
    ).toResource
    dispatcher <- FrameDispatcher[F].toResource
    output <- OutputWriter(sendQ.offer).toResource
    state <- ConnectionState(output, dispatcher, path).toResource
    newChannel = ChannelBuilder(
      output,
      state,
      dispatcher,
      LowlevelChannel.from[F](config.globalChannelConfig)
    )

    transfer = Stream
      .fromQueueUnterminated(sendQ)
      .through(transport)
      .through(negotiation.pipe(output.write))
      .through(receive(state, dispatcher))
    life = lifetime(negotiation.config, state)
    _ <- transfer.merge(life).compile.drain.background
    caps <- negotiation.capabilities.toResource
  } yield new {

    override def capabilities: Capabilities = caps

    override def channels: Signal[F, Set[ChannelNumber]] = dispatcher.channels

    override def status: Signal[F, Status] = state

    override def channel: Resource[F, Channel[F, NormalMessagingChannel[F]]] =
      newChannel.map(Channel.normal)

    override def reliableChannel
        : Resource[F, Channel[F, ReliablePublishingMessagingChannel[F]]] =
      newChannel.evalMap(Channel.reliable)

    override def transactionalChannel
        : Resource[F, Channel[F, TransactionalMessagingChannel[F]]] =
      newChannel.evalMap(Channel.transactional)

  }

  private[client] def receive[F[_]: Concurrent](
      state: ConnectionState[F],
      dispatcher: FrameDispatcher[F]
  ): Pipe[F, Frame, Nothing] = _.foreach {
    case b: Frame.Body   => dispatcher.body(b)
    case h: Frame.Header => dispatcher.header(h)
    case Frame.Method(0, value) =>
      value match {
        case m @ ConnectionClass.OpenOk   => state.onOpened
        case m @ ConnectionClass.CloseOk  => state.onClosed
        case m: ConnectionClass.Close     => state.onCloseRequest(m)
        case ConnectionClass.Blocked(msg) => state.onBlocked(msg)
        case ConnectionClass.Unblocked    => state.onUnblocked
        case _                            => ???
      }
    case m: Frame.Method => dispatcher.invoke(m)
    case Frame.Heartbeat => state.onHeartbeat
  }.onFinalizeCase {
    case Succeeded  => state.onClosed
    case Errored(e) => state.onFailed(e)
    case Canceled   => state.onClosed
  }.interruptWhen(state.whenClosed)

  private[client] def lifetime[F[_]: Temporal](
      config: F[NegotiatedConfig],
      state: ConnectionState[F]
  ): Stream[F, Nothing] = {
    import fs2.Stream.*

    def heartbeats(config: NegotiatedConfig) =
      awakeEvery(config.heartbeat.toInt.seconds)
        .foreach(_ => state.onHeartbeat)

    eval(config)
      .flatMap(config =>
        eval(state.onConnected(config)) >>
          eval(state.awaitOpened) >>
          heartbeats(config)
      )
      .onFinalize(state.onCloseRequest)
      .interruptWhen(state.whenClosed)
  }

  enum Status {
    case Connecting
    case Connected
    case Opened(blocked: Boolean = false)
    case Closed
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy