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

quasar.fs.ReadFile.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

import quasar.Predef._
import quasar.fp.numeric.{Natural, Positive}
import eu.timepit.refined.auto._

import quasar._, RenderTree.ops._
import quasar.effect.LiftedOps
import quasar.fp._

import monocle.Iso
import scalaz._
import scalaz.std.anyVal._
import scalaz.std.tuple._
import scalaz.syntax.monad._
import scalaz.syntax.std.option._
import scalaz.stream._

sealed trait ReadFile[A]

object ReadFile {
  final case class ReadHandle(file: AFile, id: Long)

  object ReadHandle {
    val tupleIso: Iso[ReadHandle, (AFile, Long)] =
      Iso((h: ReadHandle) => (h.file, h.id))((ReadHandle(_, _)).tupled)

    implicit val readHandleShow: Show[ReadHandle] =
      Show.showFromToString

    // TODO: Switch to order once Order[Path[B,T,S]] exists
    implicit val readHandleEqual: Equal[ReadHandle] =
      Equal.equalBy(tupleIso.get)
  }

  final case class Open(file: AFile, offset: Natural, limit: Option[Positive])
    extends ReadFile[FileSystemError \/ ReadHandle]

  final case class Read(h: ReadHandle)
    extends ReadFile[FileSystemError \/ Vector[Data]]

  final case class Close(h: ReadHandle)
    extends ReadFile[Unit]

  final class Ops[S[_]](implicit S: Functor[S], val unsafe: Unsafe[S]) {
    type F[A] = unsafe.F[A]
    type M[A] = unsafe.M[A]

    /** Returns a process which produces data from the given file, beginning
      * at the specified offset. An optional limit may be supplied to restrict
      * the maximum amount of data read.
      */
    def scan(file: AFile, offset: Natural, limit: Option[Positive]): Process[M, Data] = {
      def closeHandle(h: ReadHandle): Process[M, Nothing] =
        Process.eval_[M, Unit](unsafe.close(h).liftM[FileSystemErrT])

      def readUntilEmpty(h: ReadHandle): Process[M, Data] =
        Process.await(unsafe.read(h)) { data =>
          if (data.isEmpty)
            Process.halt
          else
            Process.emitAll(data) ++ readUntilEmpty(h)
        }

      Process.bracket(unsafe.open(file, offset, limit))(closeHandle)(readUntilEmpty)
    }

    /** Returns a process that produces all the data contained in the
      * given file.
      */
    def scanAll(file: AFile): Process[M, Data] =
      scan(file, 0L, None)

    /** Returns a process that produces at most `limit` items from the beginning
      * of the given file.
      */
    def scanTo(file: AFile, limit: Positive): Process[M, Data] =
      scan(file, 0L, Some(limit))

    /** Returns a process that produces data from the given file, beginning
      * at the specified offset.
      */
    def scanFrom(file: AFile, offset: Natural): Process[M, Data] =
      scan(file, offset, None)
  }

  object Ops {
    implicit def apply[S[_]](implicit S: Functor[S], U: Unsafe[S]): Ops[S] =
      new Ops[S]
  }

  /** Low-level, unsafe operations. Clients are responsible for resource-safety
    * when using these.
    */
  @SuppressWarnings(Array("org.brianmckenna.wartremover.warts.NonUnitStatements"))
  final class Unsafe[S[_]](implicit S0: Functor[S], S1: ReadFileF :<: S)
    extends LiftedOps[ReadFile, S] {

    type M[A] = FileSystemErrT[F, A]

    /** Returns a read handle for the given file, positioned at the given
      * zero-indexed offset, that may be used to read chunks of data from the
      * file. An optional limit may be supplied to restrict the total amount of
      * data able to be read using the handle.
      *
      * Care must be taken to `close` the returned handle in order to avoid
      * potential resource leaks.
      */
    def open(file: AFile, offset: Natural, limit: Option[Positive]): M[ReadHandle] =
      EitherT(lift(Open(file, offset, limit)))

    /** Read a chunk of data from the file represented by the given handle.
      *
      * An empty `Vector` signals that all data has been read.
      */
    def read(rh: ReadHandle): M[Vector[Data]] =
      EitherT(lift(Read(rh)))

    /** Closes the given read handle, freeing any resources it was using. */
    def close(rh: ReadHandle): F[Unit] =
      lift(Close(rh))
  }

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

  implicit def renderTree[A]: RenderTree[ReadFile[A]] =
    new RenderTree[ReadFile[A]] {
      def render(rf: ReadFile[A]) = rf match {
        case Open(file, off, lim) => NonTerminal(List("Open"), None,
          file.render :: Terminal(List("Offset"), Some(off.toString)) ::
            lim.map(l => Terminal(List("Limit"), Some(l.toString))).toList)
        case Read(handle)         => Terminal(List("Read"), handle.toString.some)
        case Close(handle)        => Terminal(List("Close"), handle.toString.some)
      }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy