@@ -27,20 +27,21 @@ import cats.effect.kernel.{Async, Resource, Sync}
27
27
import cats .syntax .all ._
28
28
29
29
import java .nio .channels .{FileChannel , SeekableByteChannel }
30
- import java .nio .file .{Files => JFiles , Path => JPath , _ }
30
+ import java .nio .file .{Files => JFiles , Path => JPath , FileSystemLoopException => _ , _ }
31
31
import java .nio .file .attribute .{
32
32
BasicFileAttributeView ,
33
33
BasicFileAttributes => JBasicFileAttributes ,
34
34
PosixFileAttributes => JPosixFileAttributes ,
35
- PosixFilePermissions
35
+ PosixFilePermissions ,
36
+ FileTime
36
37
}
37
38
import java .security .Principal
38
39
import java .util .stream .{Stream => JStream }
39
40
40
41
import scala .concurrent .duration ._
42
+ import scala .util .control .NonFatal
41
43
42
44
import fs2 .io .CollectionCompat ._
43
- import java .nio .file .attribute .FileTime
44
45
45
46
private [file] trait FilesPlatform [F [_]] extends DeprecatedFilesApi [F ] { self : Files [F ] =>
46
47
@@ -91,7 +92,8 @@ private[file] trait FilesCompanionPlatform {
91
92
private case class NioFileKey (value : AnyRef ) extends FileKey
92
93
93
94
private final class AsyncFiles [F [_]](protected implicit val F : Async [F ])
94
- extends Files .UnsealedFiles [F ] {
95
+ extends Files .UnsealedFiles [F ]
96
+ with AsyncFilesPlatform [F ] {
95
97
96
98
def copy (source : Path , target : Path , flags : CopyFlags ): F [Unit ] =
97
99
Sync [F ].blocking {
@@ -389,6 +391,140 @@ private[file] trait FilesCompanionPlatform {
389
391
.resource(Resource .fromAutoCloseable(javaCollection))
390
392
.flatMap(ds => Stream .fromBlockingIterator[F ](collectionIterator(ds), pathStreamChunkSize))
391
393
394
+ protected def walkEager (start : Path , options : WalkOptions ): Stream [F , Path ] = {
395
+ val doWalk = Sync [F ].interruptible {
396
+ val bldr = Vector .newBuilder[Path ]
397
+ JFiles .walkFileTree(
398
+ start.toNioPath,
399
+ if (options.followLinks) Set (FileVisitOption .FOLLOW_LINKS ).asJava else Set .empty.asJava,
400
+ options.maxDepth,
401
+ new SimpleFileVisitor [JPath ] {
402
+ private def enqueue (path : JPath ): FileVisitResult = {
403
+ bldr += Path .fromNioPath(path)
404
+ FileVisitResult .CONTINUE
405
+ }
406
+
407
+ override def visitFile (file : JPath , attrs : JBasicFileAttributes ): FileVisitResult =
408
+ if (Thread .interrupted()) FileVisitResult .TERMINATE else enqueue(file)
409
+
410
+ override def visitFileFailed (file : JPath , t : IOException ): FileVisitResult =
411
+ t match {
412
+ case _ : FileSystemLoopException =>
413
+ if (options.allowCycles) enqueue(file) else throw t
414
+ case _ => FileVisitResult .CONTINUE
415
+ }
416
+
417
+ override def preVisitDirectory (
418
+ dir : JPath ,
419
+ attrs : JBasicFileAttributes
420
+ ): FileVisitResult =
421
+ if (Thread .interrupted()) FileVisitResult .TERMINATE else enqueue(dir)
422
+
423
+ override def postVisitDirectory (dir : JPath , t : IOException ): FileVisitResult =
424
+ if (Thread .interrupted()) FileVisitResult .TERMINATE else FileVisitResult .CONTINUE
425
+ }
426
+ )
427
+ Chunk .from(bldr.result())
428
+ }
429
+ Stream .eval(doWalk).flatMap(Stream .chunk)
430
+ }
431
+
432
+ private case class WalkEntry (
433
+ path : Path ,
434
+ attr : JBasicFileAttributes ,
435
+ depth : Int ,
436
+ ancestry : List [Either [Path , NioFileKey ]]
437
+ )
438
+
439
+ protected def walkJustInTime (
440
+ start : Path ,
441
+ options : WalkOptions
442
+ ): Stream [F , Path ] = {
443
+ import scala .collection .immutable .Queue
444
+
445
+ def loop (toWalk0 : Queue [WalkEntry ]): Stream [F , Path ] = {
446
+ val partialWalk = Sync [F ].interruptible {
447
+ var acc = Vector .empty[Path ]
448
+ var toWalk = toWalk0
449
+
450
+ while (acc.size < options.chunkSize && toWalk.nonEmpty && ! Thread .interrupted()) {
451
+ val entry = toWalk.head
452
+ toWalk = toWalk.drop(1 )
453
+ acc = acc :+ entry.path
454
+ if (entry.depth < options.maxDepth) {
455
+ val dir =
456
+ if (entry.attr.isDirectory) entry.path
457
+ else if (options.followLinks && entry.attr.isSymbolicLink) {
458
+ try {
459
+ val targetAttr =
460
+ JFiles .readAttributes(entry.path.toNioPath, classOf [JBasicFileAttributes ])
461
+ val fileKey = Option (targetAttr.fileKey).map(NioFileKey (_))
462
+ val isCycle = entry.ancestry.exists {
463
+ case Right (ancestorKey) =>
464
+ fileKey.contains(ancestorKey)
465
+ case Left (ancestorPath) =>
466
+ JFiles .isSameFile(entry.path.toNioPath, ancestorPath.toNioPath)
467
+ }
468
+ if (isCycle)
469
+ if (options.allowCycles) null
470
+ else throw new FileSystemLoopException (entry.path.toString)
471
+ else entry.path
472
+ } catch {
473
+ case t : FileSystemLoopException => throw t
474
+ case NonFatal (_) => null
475
+ }
476
+ } else null
477
+ if (dir ne null ) {
478
+ try {
479
+ val listing = JFiles .list(dir.toNioPath)
480
+ try {
481
+ val descendants = listing.iterator.asScala.flatMap { p =>
482
+ try
483
+ Some (
484
+ WalkEntry (
485
+ Path .fromNioPath(p),
486
+ JFiles .readAttributes(
487
+ p,
488
+ classOf [JBasicFileAttributes ],
489
+ LinkOption .NOFOLLOW_LINKS
490
+ ),
491
+ entry.depth + 1 ,
492
+ Option (entry.attr.fileKey)
493
+ .map(NioFileKey (_))
494
+ .toRight(entry.path) :: entry.ancestry
495
+ )
496
+ )
497
+ catch {
498
+ case NonFatal (_) => None
499
+ }
500
+ }
501
+ toWalk = Queue .empty ++ descendants ++ toWalk
502
+ } finally listing.close()
503
+ } catch {
504
+ case NonFatal (_) => ()
505
+ }
506
+ }
507
+ }
508
+ }
509
+
510
+ Stream .chunk(Chunk .from(acc)) ++ (if (toWalk.isEmpty) Stream .empty else loop(toWalk))
511
+ }
512
+ Stream .eval(partialWalk).flatten
513
+ }
514
+
515
+ Stream
516
+ .eval(Sync [F ].interruptible {
517
+ WalkEntry (
518
+ start,
519
+ JFiles .readAttributes(start.toNioPath, classOf [JBasicFileAttributes ]),
520
+ 0 ,
521
+ Nil
522
+ )
523
+ })
524
+ .mask
525
+ .flatMap(w => loop(Queue (w)))
526
+ }
527
+
392
528
def createWatcher : Resource [F , Watcher [F ]] = Watcher .default(this , F )
393
529
394
530
def watch (
0 commit comments