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

internal.ConnectionState.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.syntax.all.*
import fs2.concurrent.Signal
import fs2.concurrent.SignallingRef
import lepus.client.Connection.Status
import lepus.protocol.ConnectionClass
import lepus.protocol.ConnectionClass.Close
import lepus.protocol.Frame
import lepus.protocol.constants.ReplyCode
import lepus.protocol.domains.*

import ConnectionState.TerminalState

private[client] trait ConnectionState[F[_]] extends Signal[F, Status] {
  def onConnected(config: NegotiatedConfig): F[Unit]
  def onOpened: F[Unit]
  def onFailed(ex: Throwable): F[Unit]
  def onClosed: F[Unit] = onFailed(TerminalState)
  def onCloseRequest: F[Unit]
  def onCloseRequest(req: ConnectionClass.Close): F[Unit]
  def onHeartbeat: F[Unit]
  def onBlocked(msg: ShortString): F[Unit]
  def onUnblocked: F[Unit]

  def config: F[NegotiatedConfig]
  def awaitOpened: F[Unit]
  final def whenClosed: fs2.Stream[F, Boolean] =
    discrete.map(_ == Status.Closed)
}

private[client] object ConnectionState {
  def apply[F[_]](
      output: OutputWriter[F, Frame],
      dispatcher: FrameDispatcher[F],
      path: Path = Path("/")
  )(using F: Concurrent[F]): F[ConnectionState[F]] = for {
    underlying <- SignallingRef[F, Status](Status.Connecting)
    configDef <- F.deferred[Either[Throwable, NegotiatedConfig]]
    hasOpened <- F.deferred[Either[Throwable, Unit]]
  } yield new {

    override def onConnected(config: NegotiatedConfig): F[Unit] =
      underlying
        .modify {
          case Status.Connecting => (Status.Connected, true)
          case other             => (other, false)
        }
        .ifM(
          configDef.complete(Right(config)) *> output.write(
            Frame.Method(ChannelNumber(0), ConnectionClass.Open(path))
          ),
          F.raiseError(new IllegalStateException)
        )

    override def onCloseRequest(req: Close): F[Unit] =
      output.write(Frame.Method(ChannelNumber(0), ConnectionClass.CloseOk))

    override def onCloseRequest: F[Unit] =
      output.write(
        Frame.Method(
          ChannelNumber(0),
          ConnectionClass.Close(
            ReplyCode.ReplySuccess,
            ShortString(""),
            ClassId(0),
            MethodId(0)
          )
        )
      )

    override def onFailed(ex: Throwable): F[Unit] =
      hasOpened.complete(Left(ex)) *>
        configDef.complete(Left(ex)) *>
        output.onClose *>
        dispatcher.onClose *>
        underlying.set(Status.Closed)

    override def onOpened: F[Unit] = hasOpened.complete(Right(())) *> underlying
      .modify {
        case Status.Connected => (Status.Opened(), true)
        case other            => (other, false)
      }
      .ifM(F.unit, F.raiseError(new IllegalStateException))

    override def onHeartbeat: F[Unit] = underlying.get.flatMap {
      case Status.Opened(_) => output.write(Frame.Heartbeat)
      case _                => F.raiseError(new IllegalStateException)
    }

    override def onBlocked(msg: ShortString): F[Unit] = underlying.get.flatMap {
      case Status.Opened(_) => underlying.set(Status.Opened(true))
      case _                => F.raiseError(new IllegalStateException)
    }

    override def onUnblocked: F[Unit] = underlying.get.flatMap {
      case Status.Opened(_) => underlying.set(Status.Opened(false))
      case _                => F.raiseError(new IllegalStateException)
    }

    override def config: F[NegotiatedConfig] =
      configDef.get.flatMap(F.fromEither)

    override def awaitOpened: F[Unit] = hasOpened.get.flatMap(F.fromEither)

    export underlying.{get, discrete, continuous}

  }

  case object TerminalState
      extends Exception(
        "Connection is closed and waiting won't change anything!"
      )
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy