coursier.install.Updatable.scala Maven / Gradle / Ivy
package coursier.install
import java.nio.channels.{FileChannel, FileLock}
import java.nio.file.{Files, Path, StandardOpenOption, StandardCopyOption}
import coursier.cache.CacheLocks
import coursier.install.error.NotAnApplication
import coursier.launcher.internal.FileUtil
import coursier.paths.CachePath
import scala.jdk.CollectionConverters._
import scala.util.control.NonFatal
object Updatable {
private final case class RelatedFiles(
dest: Path,
aux: Path,
tmpDest: Path,
tmpAux: Path
) {
def list: Seq[Path] =
Seq(dest, aux, tmpDest, tmpAux)
}
private def relatedFiles(dest: Path, auxExtension: String): RelatedFiles = {
val auxName0 = InstallDir.auxName(dest.getFileName.toString, auxExtension)
val dir = dest.getParent
val tmpDest = dir.resolve(s".${dest.getFileName}.part")
val aux = dir.resolve(auxName0)
val tmpAux = dir.resolve(s"$auxName0.part")
RelatedFiles(dest, aux, tmpDest, tmpAux)
}
def writing[T](
baseDir: Path,
dest: Path,
auxExtension: String,
verbosity: Int
)(f: (Path, Path) => T): Option[T] = {
// Ensuring we're the only process trying to write dest
// - acquiring a lock while writing
// - writing things to a temp file (tmpDest)
// - atomic move to final dest, so that no borked launcher are exposed at any time, even during launcher generation
// Things these moves aren't actually atomic, because we move two files sometimes, and REPLACE_EXISTING doesn't
// seem to work along with ATOMIC_MOVE.
val files = relatedFiles(dest, auxExtension)
val dir = dest.getParent
def get: T = {
Files.deleteIfExists(files.tmpDest)
Files.deleteIfExists(files.tmpAux)
val res = f(files.tmpDest, files.tmpAux)
val updated = Files.isRegularFile(files.tmpDest) || Files.isRegularFile(files.tmpAux)
if (Files.isRegularFile(files.tmpDest)) {
if (verbosity >= 2) {
System.err.println(s"Wrote ${files.tmpDest}")
System.err.println(s"Moving ${files.tmpDest} to $dest")
}
// StandardCopyOption.REPLACE_EXISTING doesn't seem to work along with ATOMIC_MOVE
Files.deleteIfExists(dest)
Files.move(
files.tmpDest,
dest,
StandardCopyOption.ATOMIC_MOVE
)
if (verbosity == 1)
System.err.println(s"Wrote $dest")
}
else if (updated)
Files.deleteIfExists(dest)
if (Files.isRegularFile(files.tmpAux)) {
if (verbosity >= 2) {
System.err.println(s"Wrote ${files.tmpAux}")
System.err.println(s"Moving ${files.tmpAux} to ${files.aux}")
}
FileUtil.tryHideWindows(files.tmpAux)
// StandardCopyOption.REPLACE_EXISTING doesn't seem to work along with ATOMIC_MOVE
Files.deleteIfExists(files.aux)
Files.move(
files.tmpAux,
files.aux,
StandardCopyOption.ATOMIC_MOVE
)
if (verbosity == 1)
System.err.println(s"Wrote ${files.aux}")
}
else if (updated)
Files.deleteIfExists(files.aux)
res
}
CacheLocks.withLockOr(baseDir.toFile, dest.toFile)(Some(get), Some(None))
}
def delete[T](
baseDir: Path,
dest: Path,
auxExtension: String,
verbosity: Int
): Option[Boolean] =
if (InfoFile.isInfoFile(dest)) {
val files = relatedFiles(dest, auxExtension)
def get: Boolean = {
val foundSomething = files.list.exists(Files.exists(_))
foundSomething && {
files.list.foreach(Files.deleteIfExists(_))
true
}
}
CacheLocks.withLockOr(baseDir.toFile, dest.toFile)(Some(get), Some(None))
}
else
throw new NotAnApplication(dest)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy