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

quasar.fs.mount.Mounting.scala Maven / Gradle / Ivy

There is a newer version: 28.1.6
Show newest version
/*
 * Copyright 2014–2016 SlamData Inc.
 *
 * 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 quasar.fs.mount

import quasar.Predef._
import quasar.Variables
import quasar.effect.LiftedOps
import quasar.fp._
import quasar.fs._
import quasar.sql.Sql

import matryoshka.Fix
import monocle.Prism
import monocle.std.{disjunction => D}
import pathy._, Path._
import scalaz._, Scalaz._

sealed trait Mounting[A]

object Mounting {

  final case object ViewType

  final case class LookupType(path: APath)
    extends Mounting[Option[ViewType.type \/ FileSystemType]]

  final case class Lookup(path: APath)
    extends Mounting[Option[MountConfig]]

  final case class MountView(loc: AFile, query: Fix[Sql], vars: Variables)
    extends Mounting[MountingError \/ Unit]

  final case class MountFileSystem(loc: ADir, typ: FileSystemType, uri: ConnectionUri)
    extends Mounting[MountingError \/ Unit]

  final case class Unmount(path: APath)
    extends Mounting[MountingError \/ Unit]

  /** Indicates the wrong type of path (file vs. dir) was supplied to the `mount`
    * convenience function.
    */
  final case class PathTypeMismatch(path: APath) extends scala.AnyVal

  object PathTypeMismatch {
    implicit val pathTypeMismatchShow: Show[PathTypeMismatch] =
      Show.shows { v =>
        val expectedType = refineType(v.path).fold(κ("file"), κ("directory"))
        s"Expected ${expectedType} path instead of '${posixCodec.printPath(v.path)}'"
      }
  }

  @SuppressWarnings(Array("org.brianmckenna.wartremover.warts.NonUnitStatements"))
  final class Ops[S[_]](implicit S0: Functor[S], S1: MountingF :<: S)
    extends LiftedOps[Mounting, S] {

    import MountConfig._

    type M[A] = EitherT[F, MountingError, A]

    def lookupType(path: APath): OptionT[F, ViewType.type \/ FileSystemType] =
      OptionT(lift(LookupType(path)))

    /** Returns the mount configuration for the given mount path or nothing
      * if the path does not refer to a mount.
      */
    def lookup(path: APath): OptionT[F, MountConfig] =
      OptionT(lift(Lookup(path)))

    /** Create a view mount at the given location. */
    def mountView(loc: AFile, query: Fix[Sql], vars: Variables): M[Unit] =
      EitherT(lift(MountView(loc, query, vars)))

    /** Create a filesystem mount at the given location. */
    def mountFileSystem(loc: ADir, typ: FileSystemType, uri: ConnectionUri): M[Unit] =
      EitherT(lift(MountFileSystem(loc, typ, uri)))

    /** Attempt to create a mount described by the given configuration at the
      * given location.
      */
    def mount(loc: APath, config: MountConfig): M[PathTypeMismatch \/ Unit] =
      config match {
        case ViewConfig(query, vars) =>
          D.right.getOption(refineType(loc)) cata (
            file => mountView(file, query, vars).map(_.right),
            PathTypeMismatch(loc).left.point[M])

        case FileSystemConfig(typ, uri) =>
          D.left.getOption(refineType(loc)) cata (
            dir => mountFileSystem(dir, typ, uri).map(_.right),
            PathTypeMismatch(loc).left.point[M])
      }

    /** Remount `src` at `dst`, results in an error if there is no mount at
      * `src`.
      */
    def remount[T](src: Path[Abs,T,Sandboxed], dst: Path[Abs,T,Sandboxed]): M[Unit] =
      modify(src, dst, ι).void

    /** Replace the mount at the given path with one described by the
      * provided config.
      */
    def replace(loc: APath, config: MountConfig): M[PathTypeMismatch \/ Unit] =
      modify(loc, loc, κ(config))

    /** Remove the mount at the given path. */
    def unmount(path: APath): M[Unit] =
      EitherT(lift(Unmount(path)))

    ////

    private type ErrFM[A] = EitherT[F, MountingError \/ PathTypeMismatch, A]

    private def bifold[G[_]: Functor, E, A, EE, AA](v: EitherT[G,E,A])(e: E => EE \/ AA, a: A => EE \/ AA): EitherT[G, EE, AA] =
      EitherT[G,EE,AA](v.run.map(_.fold(e, a)))

    private def toLeft[G[_]: Functor, E1, E2, A](v: EitherT[G, E1, E2 \/ A]): EitherT[G, E1 \/ E2, A] =
      bifold(v)(_.left.left, _.fold(_.right.left, _.right))

    private def toRight[G[_]: Functor, E1, E2, A](v: EitherT[G, E1 \/ E2, A]): EitherT[G, E1, E2 \/ A] =
      bifold(v)(_.fold(_.left, _.left.right), _.right.right)

    private val notFound: Prism[MountingError, APath] =
      MountingError.pathError composePrism PathError.pathNotFound

    private val invalidPath: Prism[MountingError, (APath, String)] =
      MountingError.pathError composePrism PathError.invalidPath

    private def modify[T](
      src: Path[Abs,T,Sandboxed],
      dst: Path[Abs,T,Sandboxed],
      f: MountConfig => MountConfig
    ): M[PathTypeMismatch \/ Unit] = {
      val mErr = MonadError[ErrFM, MountingError \/ PathTypeMismatch]
      import mErr._

      for {
        cfg <- lookup(src) toRight notFound(src)
        _   <- unmount(src)
        rez <- toRight(handleError(toLeft(mount(dst, f(cfg)))) { err =>
                 toLeft(mount(src, cfg)) *> raiseError(err)
               })
      } yield rez
    }
  }

  object Ops {
    implicit def apply[S[_]](implicit S0: Functor[S], S1: MountingF :<: S): Ops[S] =
      new Ops[S]
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy