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

coursier.install.InfoFile.scala Maven / Gradle / Ivy

The newest version!
package coursier.install

import java.io.OutputStream
import java.nio.charset.StandardCharsets
import java.nio.file.attribute.FileTime
import java.nio.file.{Files, Path}
import java.time.Instant
import java.util.zip.{ZipEntry, ZipFile, ZipOutputStream}

import coursier.cache.internal.FileUtil
import coursier.launcher.Preamble

import scala.util.control.NonFatal
import java.util.zip.ZipException

object InfoFile {

  private[coursier] def jsonSourceFilePath = "META-INF/coursier/info-source.json"
  private[coursier] def jsonDescFilePath   = "META-INF/coursier/info.json"
  private[coursier] def lockFilePath       = "META-INF/coursier/lock-file"
  private[coursier] def sharedLockFilePath = "META-INF/coursier/shared-deps-lock-file"

  def isInfoFile(p: Path): Boolean = {

    var zf: ZipFile = null

    try {
      zf = new ZipFile(p.toFile)
      zf.getEntry(jsonDescFilePath) != null
    }
    catch {
      case e: ZipException =>
        false
    }
    finally if (zf != null)
        zf.close()
  }

  def readSource(f: Path): Option[(Source, Array[Byte])] = {

    var zf: ZipFile = null

    try {
      zf = new ZipFile(f.toFile)
      val entOpt = Option(zf.getEntry(jsonSourceFilePath))

      entOpt.map { ent =>
        val content = FileUtil.readFully(zf.getInputStream(ent))
        val e = RawSource.parse(new String(content, StandardCharsets.UTF_8))
          .left.map(err => new ErrorParsingSource(s"$f!$jsonSourceFilePath", err))
          .flatMap { r =>
            r.source
              .toEither
              .left.map { errors =>
                new ErrorProcessingSource(s"$f!$jsonSourceFilePath", errors.toList.mkString(", "))
              }
          }
        val source = e.fold(throw _, identity)
        (source, content)
      }
    }
    catch {
      case NonFatal(e) =>
        throw new Exception(s"Reading $f", e)
    }
    finally if (zf != null)
        zf.close()
  }

  private[coursier] def appDescriptor(path: String, content: Array[Byte]) =
    RawAppDescriptor.parse(new String(content, StandardCharsets.UTF_8))
      .left.map(err => new ErrorParsingAppDescription(path, err))
      .flatMap { r =>
        r.appDescriptor
          .toEither
          .left.map { errors =>
            new ErrorProcessingAppDescription(path, errors.toList.mkString(", "))
          }
      }

  def readAppDescriptor(f: Path): Option[(AppDescriptor, Array[Byte])] = {

    var zf: ZipFile = null

    try {
      zf = new ZipFile(f.toFile)
      val entOpt = Option(zf.getEntry(jsonDescFilePath))

      entOpt.map { ent =>
        val content = FileUtil.readFully(zf.getInputStream(ent))
        val e       = appDescriptor(s"$f!$jsonDescFilePath", content)
        val desc    = e.fold(throw _, identity) // meh
        (desc, content)
      }
    }
    catch {
      case NonFatal(e) =>
        throw new Exception(s"Reading $f", e)
    }
    finally if (zf != null)
        zf.close()
  }

  def extraEntries(
    lock: ArtifactsLock,
    sharedLockOpt: Option[ArtifactsLock],
    descRepr: Array[Byte],
    sourceReprOpt: Option[Array[Byte]],
    currentTime: Instant
  ): Seq[(ZipEntry, Array[Byte])] = {

    val lockFileEntry = {
      val e = new ZipEntry(lockFilePath)
      e.setLastModifiedTime(FileTime.from(currentTime))
      e.setCompressedSize(-1L)
      val content = lock.repr.getBytes(StandardCharsets.UTF_8)
      e -> content
    }

    val sharedLockFileEntryOpt = sharedLockOpt.map { lock0 =>
      val e = new ZipEntry(sharedLockFilePath)
      e.setLastModifiedTime(FileTime.from(currentTime))
      e.setCompressedSize(-1L)
      val content = lock0.repr.getBytes(StandardCharsets.UTF_8)
      e -> content
    }

    val descEntry = {
      val e = new ZipEntry(jsonDescFilePath)
      e.setLastModifiedTime(FileTime.from(currentTime))
      e.setCompressedSize(-1L)
      e -> descRepr
    }

    val sourceEntryOpt = sourceReprOpt.map { sourceRepr =>
      val e = new ZipEntry(jsonSourceFilePath)
      e.setLastModifiedTime(FileTime.from(currentTime))
      e.setCompressedSize(-1L)
      e -> sourceRepr
    }

    Seq(descEntry, lockFileEntry) ++ sourceEntryOpt.toSeq ++ sharedLockFileEntryOpt.toSeq
  }

  def upToDate(
    infoFile: Path,
    lock: ArtifactsLock,
    sharedLockOpt: Option[ArtifactsLock],
    descRepr: Array[Byte],
    sourceReprOpt: Option[Array[Byte]]
  ) = Files.exists(infoFile) && {

    var f: ZipFile = null
    try {
      f = new ZipFile(infoFile.toFile)
      val lockEntryOpt       = Option(f.getEntry(lockFilePath))
      val sharedLockEntryOpt = Option(f.getEntry(sharedLockFilePath))
      val descFileEntryOpt   = Option(f.getEntry(jsonDescFilePath))
      val sourceFileEntryOpt = Option(f.getEntry(jsonSourceFilePath))

      def read(ent: ZipEntry): Array[Byte] =
        FileUtil.readFully(f.getInputStream(ent))

      def readLock(ent: ZipEntry): ArtifactsLock = {
        // FIXME Don't just throw in case of malformed file?
        val s = new String(read(ent), StandardCharsets.UTF_8)
        ArtifactsLock.read(s) match {
          case Left(err) => throw new ErrorReadingLockFile(s"$infoFile!${ent.getName}", err)
          case Right(l)  => l
        }
      }

      val initialAppDesc = descFileEntryOpt.map(read(_).toSeq)
      val initialSource  = sourceFileEntryOpt.map(read(_).toSeq)

      val initialLockOpt: Option[ArtifactsLock]       = lockEntryOpt.map(readLock)
      val initialSharedLockOpt: Option[ArtifactsLock] = sharedLockEntryOpt.map(readLock)

      initialLockOpt.contains(lock) &&
      initialSharedLockOpt == sharedLockOpt &&
      initialAppDesc.contains(descRepr.toSeq) &&
      initialSource == sourceReprOpt.map(_.toSeq)
    }
    catch {
      case NonFatal(e) =>
        throw new Exception(s"Reading $infoFile", e)
    }
    finally if (f != null)
        f.close()
  }

  def writeInfoFile(
    dest: Path,
    preamble: Option[Preamble],
    entries: Seq[(ZipEntry, Array[Byte])]
  ): Unit = {
    val preambleDataOpt = preamble.map(_.value)

    var fos: OutputStream    = null
    var zos: ZipOutputStream = null
    try {
      fos = Files.newOutputStream(dest)

      for (b <- preambleDataOpt)
        fos.write(b)

      zos = new ZipOutputStream(fos)
      for ((ent, b) <- entries) {
        zos.putNextEntry(ent)
        zos.write(b)
        zos.closeEntry()
      }
    }
    finally {
      if (zos != null)
        zos.close()
      if (fos != null)
        fos.close()
    }
  }

  sealed abstract class InfoFileException(message: String, cause: Throwable = null)
      extends Exception(message, cause)

  final class ErrorParsingSource(path: String, details: String)
      extends InfoFileException(s"Error parsing source $path: $details")
  final class ErrorProcessingSource(path: String, details: String)
      extends InfoFileException(s"Error processing source $path: $details")

  final class ErrorParsingAppDescription(path: String, details: String)
      extends InfoFileException(s"Error parsing app description $path: $details")
  final class ErrorProcessingAppDescription(path: String, details: String)
      extends InfoFileException(s"Error processing app description $path: $details")

  final class ErrorReadingLockFile(path: String, details: String)
      extends InfoFileException(s"Error reading lock file $path: $details")

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy