diff --git a/io/js/src/main/scala/fs2/io/file/FilesPlatform.scala b/io/js/src/main/scala/fs2/io/file/FilesPlatform.scala index f895046adb..6fee6275c1 100644 --- a/io/js/src/main/scala/fs2/io/file/FilesPlatform.scala +++ b/io/js/src/main/scala/fs2/io/file/FilesPlatform.scala @@ -23,8 +23,8 @@ package fs2 package io package file -import cats.effect.kernel.Async -import cats.effect.kernel.Resource +import cats.Traverse +import cats.effect.kernel.{Async, Resource} import cats.syntax.all._ import fs2.io.file.Files.UnsealedFiles import fs2.io.internal.facade @@ -369,6 +369,54 @@ private[fs2] trait FilesCompanionPlatform { override def size(path: Path): F[Long] = stat(path).map(_.size.toString.toLong) + override def walkWithAttributes(start: Path, options: WalkOptions): Stream[F, PathInfo] = { + + def go( + start: Path, + maxDepth: Int, + ancestry: List[Either[Path, FileKey]] + ): Stream[F, PathInfo] = + Stream.eval(getBasicFileAttributes(start, followLinks = false)).mask.flatMap { attr => + Stream.emit(PathInfo(start, attr)) ++ { + if (maxDepth == 0) Stream.empty + else if (attr.isDirectory) + list(start).mask.flatMap { path => + go(path, maxDepth - 1, attr.fileKey.toRight(start) :: ancestry) + } + else if (attr.isSymbolicLink && options.followLinks) + Stream.eval(getBasicFileAttributes(start, followLinks = true)).mask.flatMap { attr => + val fileKey = attr.fileKey + val isCycle = Traverse[List].existsM(ancestry) { + case Right(ancestorKey) => F.pure(fileKey.contains(ancestorKey)) + case Left(ancestorPath) => isSameFile(start, ancestorPath) + } + + Stream.eval(isCycle).flatMap { isCycle => + if (!isCycle) + list(start).mask.flatMap { path => + go(path, maxDepth - 1, attr.fileKey.toRight(start) :: ancestry) + } + else if (options.allowCycles) + Stream.empty + else + Stream.raiseError(new FileSystemLoopException(start.toString)) + } + + } + else + Stream.empty + } + } + + go( + start, + options.maxDepth, + Nil + ) + .chunkN(options.chunkSize) + .flatMap(Stream.chunk) + } + override def writeAll(path: Path, _flags: Flags): Pipe[F, Byte, Nothing] = in => in.through { diff --git a/io/jvm-native/src/main/scala/fs2/io/file/FilesPlatform.scala b/io/jvm-native/src/main/scala/fs2/io/file/FilesPlatform.scala index 7b3e6c2f82..2b0af08b81 100644 --- a/io/jvm-native/src/main/scala/fs2/io/file/FilesPlatform.scala +++ b/io/jvm-native/src/main/scala/fs2/io/file/FilesPlatform.scala @@ -92,8 +92,7 @@ private[file] trait FilesCompanionPlatform { private case class NioFileKey(value: AnyRef) extends FileKey private final class AsyncFiles[F[_]](protected implicit val F: Async[F]) - extends Files.UnsealedFiles[F] - with AsyncFilesPlatform[F] { + extends Files.UnsealedFiles[F] { def copy(source: Path, target: Path, flags: CopyFlags): F[Unit] = Sync[F].blocking { @@ -391,53 +390,6 @@ private[file] trait FilesCompanionPlatform { .resource(Resource.fromAutoCloseable(javaCollection)) .flatMap(ds => Stream.fromBlockingIterator[F](collectionIterator(ds), pathStreamChunkSize)) - protected def walkEager(start: Path, options: WalkOptions): Stream[F, PathInfo] = { - val doWalk = Sync[F].interruptible { - val bldr = Vector.newBuilder[PathInfo] - JFiles.walkFileTree( - start.toNioPath, - if (options.followLinks) Set(FileVisitOption.FOLLOW_LINKS).asJava else Set.empty.asJava, - options.maxDepth, - new SimpleFileVisitor[JPath] { - private def enqueue(path: JPath, attrs: JBasicFileAttributes): FileVisitResult = { - bldr += PathInfo(Path.fromNioPath(path), new DelegatingBasicFileAttributes(attrs)) - FileVisitResult.CONTINUE - } - - override def visitFile(file: JPath, attrs: JBasicFileAttributes): FileVisitResult = - if (Thread.interrupted()) FileVisitResult.TERMINATE else enqueue(file, attrs) - - override def visitFileFailed(file: JPath, t: IOException): FileVisitResult = - t match { - case _: FileSystemLoopException => - if (options.allowCycles) - enqueue( - file, - JFiles.readAttributes( - file, - classOf[JBasicFileAttributes], - LinkOption.NOFOLLOW_LINKS - ) - ) - else throw t - case _ => FileVisitResult.CONTINUE - } - - override def preVisitDirectory( - dir: JPath, - attrs: JBasicFileAttributes - ): FileVisitResult = - if (Thread.interrupted()) FileVisitResult.TERMINATE else enqueue(dir, attrs) - - override def postVisitDirectory(dir: JPath, t: IOException): FileVisitResult = - if (Thread.interrupted()) FileVisitResult.TERMINATE else FileVisitResult.CONTINUE - } - ) - Chunk.from(bldr.result()) - } - Stream.eval(doWalk).flatMap(Stream.chunk) - } - private case class WalkEntry( path: Path, attr: JBasicFileAttributes, @@ -445,7 +397,7 @@ private[file] trait FilesCompanionPlatform { ancestry: List[Either[Path, NioFileKey]] ) - protected def walkJustInTime( + override def walkWithAttributes( start: Path, options: WalkOptions ): Stream[F, PathInfo] = { diff --git a/io/jvm/src/main/scala/fs2/io/file/AsyncFilesPlatform.scala b/io/jvm/src/main/scala/fs2/io/file/AsyncFilesPlatform.scala deleted file mode 100644 index 46b1be40fb..0000000000 --- a/io/jvm/src/main/scala/fs2/io/file/AsyncFilesPlatform.scala +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2013 Functional Streams for Scala - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package fs2 -package io -package file - -private[file] trait AsyncFilesPlatform[F[_]] { self: Files.UnsealedFiles[F] => - - override def walkWithAttributes( - start: Path, - options: WalkOptions - ): Stream[F, PathInfo] = - if (options.chunkSize == Int.MaxValue) walkEager(start, options) - else walkJustInTime(start, options) - - protected def walkEager(start: Path, options: WalkOptions): Stream[F, PathInfo] - - protected def walkJustInTime( - start: Path, - options: WalkOptions - ): Stream[F, PathInfo] -} diff --git a/io/native/src/main/scala/fs2/io/file/AsyncFilesPlatform.scala b/io/native/src/main/scala/fs2/io/file/AsyncFilesPlatform.scala deleted file mode 100644 index 87796c2134..0000000000 --- a/io/native/src/main/scala/fs2/io/file/AsyncFilesPlatform.scala +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2013 Functional Streams for Scala - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package fs2 -package io -package file - -private[file] trait AsyncFilesPlatform[F[_]] { self: Files.UnsealedFiles[F] => - override def walkWithAttributes( - start: Path, - options: WalkOptions - ): Stream[F, PathInfo] = - // Disable eager walks until https://github.com/scala-native/scala-native/issues/3744 - walkJustInTime(start, options) - - protected def walkJustInTime( - start: Path, - options: WalkOptions - ): Stream[F, PathInfo] -} diff --git a/io/shared/src/main/scala/fs2/io/file/Files.scala b/io/shared/src/main/scala/fs2/io/file/Files.scala index b4f4c0fc9b..f8aaca72c9 100644 --- a/io/shared/src/main/scala/fs2/io/file/Files.scala +++ b/io/shared/src/main/scala/fs2/io/file/Files.scala @@ -31,7 +31,6 @@ import cats.effect.std.Hotswap import cats.syntax.all._ import scala.concurrent.duration._ -import cats.Traverse /** Provides operations related to working with files in the effect `F`. * @@ -525,54 +524,6 @@ object Files extends FilesCompanionPlatform with FilesLowPriority { case _: NoSuchFileException => () }) - def walkWithAttributes(start: Path, options: WalkOptions): Stream[F, PathInfo] = { - - def go( - start: Path, - maxDepth: Int, - ancestry: List[Either[Path, FileKey]] - ): Stream[F, PathInfo] = - Stream.eval(getBasicFileAttributes(start, followLinks = false)).mask.flatMap { attr => - Stream.emit(PathInfo(start, attr)) ++ { - if (maxDepth == 0) Stream.empty - else if (attr.isDirectory) - list(start).mask.flatMap { path => - go(path, maxDepth - 1, attr.fileKey.toRight(start) :: ancestry) - } - else if (attr.isSymbolicLink && options.followLinks) - Stream.eval(getBasicFileAttributes(start, followLinks = true)).mask.flatMap { attr => - val fileKey = attr.fileKey - val isCycle = Traverse[List].existsM(ancestry) { - case Right(ancestorKey) => F.pure(fileKey.contains(ancestorKey)) - case Left(ancestorPath) => isSameFile(start, ancestorPath) - } - - Stream.eval(isCycle).flatMap { isCycle => - if (!isCycle) - list(start).mask.flatMap { path => - go(path, maxDepth - 1, attr.fileKey.toRight(start) :: ancestry) - } - else if (options.allowCycles) - Stream.empty - else - Stream.raiseError(new FileSystemLoopException(start.toString)) - } - - } - else - Stream.empty - } - } - - go( - start, - options.maxDepth, - Nil - ) - .chunkN(options.chunkSize) - .flatMap(Stream.chunk) - } - def writeAll( path: Path, flags: Flags