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

sbt.internal.nio.FileTreeRepositoryImpl.scala Maven / Gradle / Ivy

/*
 * sbt IO
 * Copyright Scala Center, Lightbend, and Mark Harrah
 *
 * Licensed under Apache License 2.0
 * SPDX-License-Identifier: Apache-2.0
 *
 * See the NOTICE file distributed with this work for
 * additional information regarding copyright ownership.
 */

package sbt.internal.nio

import java.io.IOException
import java.nio.file.{ Path => NioPath }
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicBoolean

import com.swoval.files.FileTreeDataViews.CacheObserver
import com.swoval.files.{ FileTreeDataViews, FileTreeRepositories, TypedPath => STypedPath }
import com.swoval.functional.Filters
import sbt.internal.nio.FileEvent.{ Creation, Deletion, Update }
import sbt.internal.nio.SwovalConverters._
import sbt.nio.file.{ FileAttributes, Glob }
import sbt.nio.file.Glob.GlobOps

import scala.collection.JavaConverters._
import scala.collection.immutable.VectorBuilder
import scala.util.Properties

/**
 * The default implementation of [[FileTreeRepository]]. It delegates all of its methods to the
 * [[https://swoval.github.io/files/jvm/com/swoval/files/FileTreeRepository.html swoval FileTreeRepository]].
 *
 * @tparam T the type of the values.
 */
private[sbt] class FileTreeRepositoryImpl[T] extends FileTreeRepository[FileAttributes] {
  private[this] val closed = new AtomicBoolean(false)
  private[this] val underlying = FileTreeRepositories.get[FileAttributes](
    (typedPath: STypedPath) => {
      FileAttributes(
        isDirectory = typedPath.isDirectory,
        isOther = false,
        isRegularFile = typedPath.isFile,
        isSymbolicLink = typedPath.isSymbolicLink
      )
    },
    true
  )
  private[this] val observers = new Observers[FileEvent[FileAttributes]]
  private[this] val registered = ConcurrentHashMap.newKeySet[NioPath].asScala
  private[this] val isMac = Properties.isMac

  underlying.addCacheObserver(new CacheObserver[FileAttributes] {
    override def onCreate(newEntry: FileTreeDataViews.Entry[FileAttributes]): Unit = {
      val path = newEntry.getTypedPath.getPath
      newEntry.getValue.asScala.foreach { v =>
        observers.onNext(Creation(path, v))
      }
      ()
    }
    override def onDelete(oldEntry: FileTreeDataViews.Entry[FileAttributes]): Unit = {
      val path = oldEntry.getTypedPath.getPath
      oldEntry.getValue.asScala.foreach { v =>
        observers.onNext(Deletion(path, v))
      }
      ()
    }
    override def onUpdate(
        oldEntry: FileTreeDataViews.Entry[FileAttributes],
        newEntry: FileTreeDataViews.Entry[FileAttributes]
    ): Unit = {
      val path = newEntry.getTypedPath.getPath
      val oldEither = oldEntry.getValue.asScala
      val newEither = newEntry.getValue.asScala
      oldEither match {
        case Right(o) =>
          newEither match {
            case Right(n) => observers.onNext(Update(path, o, n))
            case _        => observers.onNext(Deletion(path, o))
          }
        case _ =>
          newEither match {
            case Right(n) => observers.onNext(Creation(path, n))
            case _        =>
          }
      }
    }
    override def onError(exception: IOException): Unit = {}
  }: CacheObserver[FileAttributes])
  override def addObserver(observer: Observer[FileEvent[FileAttributes]]): AutoCloseable = {
    throwIfClosed("addObserver")
    observers.addObserver(observer)
  }
  override def list(path: NioPath): Seq[(NioPath, FileAttributes)] = {
    throwIfClosed("list")
    val res = new VectorBuilder[(NioPath, FileAttributes)]
    underlying
      .listEntries(path, 0, Filters.AllPass)
      .iterator
      .asScala
      .foreach { e =>
        val tp = e.getTypedPath
        val path = tp.getPath
        e.getValue.asScala match {
          case Right(t: FileAttributes @unchecked) => res += path -> t
          case _                                   =>
        }
      }
    res.result()
  }
  override def register(glob: Glob): Either[IOException, Observable[FileEvent[FileAttributes]]] = {
    throwIfClosed("register")
    val base = glob.base
    if (isMac) {
      // workaround for https://github.com/sbt/sbt/issues/4603
      val parent = glob.base.getParent
      if (!registered.contains(parent)) {
        if (
          registered.exists(path =>
            path.getParent == parent && {
              val leftFileName = path.getFileName.toString
              val rightFileName = base.getFileName.toString
              leftFileName != rightFileName && (leftFileName
                .startsWith(rightFileName) || rightFileName.startsWith(leftFileName))
            }
          )
        ) {
          register(Glob(parent))
        }
      }
    }
    underlying.register(base, glob.range.toSwovalDepth).asScala match {
      case Right(_) =>
        registered.add(base)
        new RegisterableObservable(observers).register(glob)
      case Left(ex) => Left(ex)
    }
  }
  override def close(): Unit = if (closed.compareAndSet(false, true)) {
    underlying.close()
  }
  private[this] def throwIfClosed(method: String): Unit =
    if (closed.get()) {
      val ex = new IllegalStateException(s"Tried to invoke $method on closed repostitory $this")
      ex.printStackTrace()
      throw ex
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy