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

quasar.fs.mount.view.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._
import quasar.effect._
import quasar.fp._
import quasar.fp.numeric._
import quasar.fs._, FileSystemError._, PathError._
import quasar.sql.Sql

import matryoshka._
import pathy.Path._
import scalaz._, Scalaz._

object view {
  /** Translate reads on view paths to the equivalent queries. */
  def readFile[S[_]: Functor]
      (implicit
        S0: ReadFileF :<: S,
        S1: QueryFileF :<: S,
        S2: MonotonicSeqF :<: S,
        S3: ViewStateF :<: S,
        S4: MountConfigsF :<: S
      ): ReadFile ~> Free[S, ?] = {
    import ReadFile._

    val readUnsafe = ReadFile.Unsafe[S]
    val queryUnsafe = QueryFile.Unsafe[S]
    val seq = MonotonicSeq.Ops[S]
    val viewState = ViewState.Ops[S]

    new (ReadFile ~> Free[S, ?]) {
      def apply[A](rf: ReadFile[A]): Free[S, A] = rf match {
        case Open(path, off, lim) =>
          val fOpen: Free[S, FileSystemError \/ ReadHandle] = {
            for {
              rh <- readUnsafe.open(path, off, lim)
              h  <- seq.next.map(ReadHandle(path, _)).liftM[FileSystemErrT]
              _  <- viewState.put(h, ResultSet.Read(rh)).liftM[FileSystemErrT]
            } yield h
          }.run

          def vOpen(e: Fix[Sql], v: Variables):
              Free[S, FileSystemError \/ ReadHandle] = {
            queryPlan(e, v, off, lim).run.value.fold[EitherT[queryUnsafe.F, FileSystemError, ReadHandle]](
              e => EitherT[Free[S, ?], FileSystemError, ReadHandle](
                // TODO: more sensible error?
                Free.point(pathErr(invalidPath(path, e.shows)).left[ReadHandle])),
              _.fold(
                data => (for {
                  h <- seq.next.map(ReadHandle(path, _))
                  _ <- viewState.put(h, ResultSet.Data(data.toVector))
                } yield h).liftM[FileSystemErrT],
                lp => for {
                  qh <- EitherT(queryUnsafe.eval(lp).run.value)
                  h  <- seq.next.map(ReadHandle(path, _)).liftM[FileSystemErrT]
                  _  <- viewState.put(h, ResultSet.Results(qh)).liftM[FileSystemErrT]
                } yield h))
          }.run

          ViewMounter.lookup[S](path).run.flatMap(_.cata((vOpen _).tupled, fOpen))

        case Read(handle) =>
          (for {
            v <- viewState.get(handle).toRight(unknownReadHandle(handle))
            d <- v match {
              case ResultSet.Data(values)    =>
                (values.point[Free[S, ?]] <*
                  viewState.put(handle, ResultSet.Data(Vector.empty)))
                  .liftM[FileSystemErrT]
              case ResultSet.Read(handle)    => readUnsafe.read(handle)
              case ResultSet.Results(handle) => queryUnsafe.more(handle)
            }
          } yield d).run

        case Close(handle) =>
          (for {
            v <- viewState.get(handle)
            _ <- viewState.delete(handle).liftM[OptionT]
            _ <- (v match {
              case ResultSet.Data(_)         => ().point[Free[S, ?]]
              case ResultSet.Read(handle)    => readUnsafe.close(handle)
              case ResultSet.Results(handle) => queryUnsafe.close(handle)
            }).liftM[OptionT]
          } yield ()).getOrElse(())
      }
    }
  }

  /** Intercept and fail any write to a view path; all others are passed untouched. */
  def writeFile[S[_]: Functor]
      (implicit
        S0: WriteFileF :<: S,
        S1: MountConfigsF :<: S
      ): WriteFile ~> Free[S, ?] = {
    import WriteFile._

    val writeUnsafe = WriteFile.Unsafe[S]

    new (WriteFile ~> Free[S, ?]) {
      def apply[A](wf: WriteFile[A]): Free[S, A] = wf match {
        case Open(p) =>
          ViewMounter.lookup[S](p).run.flatMap(_.cata(
            κ(emit[S, A](-\/(pathErr(invalidPath(p, "cannot write to view"))))),
            writeUnsafe.open(p).run))

        case Write(h, chunk) =>
          writeUnsafe.write(h, chunk)

        case Close(h) =>
          writeUnsafe.close(h)
      }
    }
  }


  /** Intercept and handle moves and deletes involving view path(s); all others are passed untouched. */
  def manageFile[S[_]: Functor]
      (implicit
        S0: ManageFileF :<: S,
        S1: QueryFileF :<: S,
        S2: MountConfigsF :<: S
      ): ManageFile ~> Free[S, ?] = {
    import ManageFile._
    import MoveSemantics._

    val manage = ManageFile.Ops[S]
    val query = QueryFile.Ops[S]

    def dirToDirMove(src: ADir, dst: ADir, semantics: MoveSemantics): Free[S, FileSystemError \/ Unit] = {
      implicit val m = EitherT.eitherTMonad[Free[S, ?], FileSystemError]

      def move(pathSegments: Set[PathSegment]): Free[S, FileSystemError \/ Unit] =
        pathSegments
          .toList
          .traverse_[EitherT[Free[S, ?], FileSystemError, ?]] { s =>
            EitherT[Free[S, ?], FileSystemError, Unit](s.fold(
              d => dirToDirMove(src  dir1(d), dst  dir1(d), semantics),
              f => fileToFileMove(src  file1(f), dst  file1(f), semantics)))
          }.run

      ViewMounter.ls[S](src).flatMap { ps =>
        if (ps.isEmpty)
          manage.moveDir(src, dst, semantics).run
        else
          manage.moveDir(src, dst, semantics).run *> move(ps)
      }
    }

    def fileToFileMove(src: AFile, dst: AFile, semantics: MoveSemantics): Free[S, FileSystemError \/ Unit] = {
      val move = manage.moveFile(src, dst, semantics).run

      (
        ViewMounter.lookup[S](src).run.map(_.isDefined) |@|
        ViewMounter.lookup[S](dst).run.map(_.isDefined) |@|
        query.fileExists(dst)
      ) .tupled
        .flatMap { case (srcViewExists, dstViewExists, dstFileExists) => semantics match {
          case FailIfExists if dstViewExists || dstFileExists =>
            emit[S, FileSystemError \/ Unit](-\/(pathErr(pathExists(dst))))
          case FailIfMissing if !(dstViewExists || dstFileExists) =>
            emit[S, FileSystemError \/ Unit](-\/(pathErr(pathNotFound(dst))))
          case _ if srcViewExists && dstFileExists =>
            manage.delete(dst).run *> ViewMounter.move[S](src, dst).map(_.right[FileSystemError])
          case _ if srcViewExists && !dstFileExists =>
            ViewMounter.move[S](src, dst).map(_.right[FileSystemError])
          case _ =>
            move
        }}
    }

    new (ManageFile ~> Free[S, ?]) {
      def apply[A](mf: ManageFile[A]) = mf match {
        case Move(scenario, semantics) =>
          scenario.fold(
            (src, dst) =>  dirToDirMove(src, dst, semantics),
            (src, dst) => fileToFileMove(src, dst, semantics))

        case Delete(path) =>
          val delete = manage.delete(path).run
          ViewMounter.viewPaths[S].flatMap { viewPaths =>
            refineType(path).fold(
              d => viewPaths
                .foldMap(f => f.relativeTo(d).map(κ(f)).toList)
                .foldMap(ViewMounter.unmount[S])
                *> delete,
              f => viewPaths.contains(f) ? ViewMounter.unmount[S](f).map(_.right[FileSystemError]) | delete)
          }

        case TempFile(nearTo) =>
          manage.tempFile(nearTo).run
      }
    }
  }


  /** Intercept and rewrite queries involving views, and overlay views when
    * enumerating files and directories. */
  def queryFile[S[_]: Functor]
      (implicit
        S0: QueryFileF :<: S,
        S1: MountConfigsF :<: S
      ): QueryFile ~> Free[S, ?] = {
    import QueryFile._

    val query = QueryFile.Ops[S]
    val queryUnsafe = QueryFile.Unsafe[S]

    def rewrite[A](lp: Fix[LogicalPlan], op: Fix[LogicalPlan] => query.transforms.ExecM[A]) =
      ViewMounter.rewrite[S](lp).run.flatMap(_.fold[Free[S, (PhaseResults, FileSystemError \/ A)]](
        e => (
            Vector.empty[PhaseResult],
            planningFailed(lp, Planner.InternalError(e.shows)).left[A]
          ).point[Free[S, ?]],
        p => op(p).run.run))

    new (QueryFile ~> Free[S, ?]) {
      def apply[A](qf: QueryFile[A]) = qf match {
        case ExecutePlan(lp, out) =>
          rewrite(lp, query.execute(_, out))

        case EvaluatePlan(lp) =>
          rewrite(lp, queryUnsafe.eval)

        case More(handle) =>
          queryUnsafe.more(handle).run

        case Close(handle) =>
          queryUnsafe.close(handle)

        case Explain(lp) =>
          rewrite(lp, query.explain)

        case ListContents(dir) =>
          (ViewMounter.ls[S](dir) |@| query.ls(dir).run) { (vls, qls) => qls match {
            case  \/-(ps) =>
              (ps ++ vls).right
            case -\/(err @ PathErr(PathNotFound(_))) =>
              if (vls.nonEmpty) vls.right
              else err.left
            case -\/(v) => v.left
          }}

        case FileExists(file) =>
          ViewMounter.lookup[S](file).run.flatMap(mc =>
            if(mc.isDefined) true.point[Free[S, ?]] else query.fileExists(file))
      }
    }
  }

  /** Translates requests which refer to any view path into operations
    * on an underlying filesystem, where references to views have been
    * rewritten as queries against actual files.
    */
  def fileSystem[S[_]: Functor]
      (implicit
        S0: ReadFileF :<: S,
        S1: WriteFileF :<: S,
        S2: ManageFileF :<: S,
        S3: QueryFileF :<: S,
        S4: MonotonicSeqF :<: S,
        S5: ViewStateF :<: S,
        S6: MountConfigsF :<: S
      ): FileSystem ~> Free[S, ?] = {
    interpretFileSystem[Free[S, ?]](
      queryFile,
      readFile,
      writeFile,
      manageFile)
  }


  // NB: wrapping this in a function seems to help the type checker
  // with the narrowed `A` type.
  private def emit[S[_]: Functor, A](a: A): Free[S, A] = a.point[Free[S, ?]]
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy