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

coursier.install.internal.PrebuiltApp.scala Maven / Gradle / Ivy

The newest version!
package coursier.install.internal

import coursier.cache.{ArchiveCache, ArchiveType, ArtifactError, Cache}
import coursier.cache.loggers.ProgressBarRefreshDisplay
import coursier.install.{AppDescriptor, InstallDir}
import coursier.install.error.DownloadError
import coursier.util.{Artifact, Task}

import java.io.File
import java.nio.file.Files

sealed abstract class PrebuiltApp extends Product with Serializable {
  def artifact: Artifact
  def file: File
}

object PrebuiltApp {

  final case class Uncompressed(
    artifact: Artifact,
    file: File
  ) extends PrebuiltApp

  final case class Compressed(
    artifact: Artifact,
    file: File,
    archiveType: ArchiveType,
    pathInArchiveOpt: Option[String]
  ) extends PrebuiltApp

  final case class ExtractedArchive(
    artifact: Artifact,
    archiveRoot: File,
    pathInArchive: String,
    file: File
  ) extends PrebuiltApp

  def get(
    desc: AppDescriptor,
    cache: Cache[Task],
    archiveCache: ArchiveCache[Task],
    verbosity: Int,
    platform: Option[String],
    platformExtensions: Seq[String],
    preferPrebuilt: Boolean
  ): Either[Seq[String], PrebuiltApp] = {

    def downloadArtifacts(
      artifacts: Seq[(Artifact, Option[(ArchiveType, Option[String])])]
    ): Iterator[PrebuiltApp] =
      artifacts.iterator.flatMap {
        case (artifact, archiveTypeOpt) =>
          if (verbosity >= 2)
            System.err.println(s"Checking prebuilt launcher at ${artifact.url}")
          def maybeFileIt: Iterator[File] = {
            cache.loggerOpt.foreach(_.init())
            val maybeFile =
              try cache.file(artifact).run.unsafeRun()(cache.ec)
              finally cache.loggerOpt.foreach(_.stop())
            handleArtifactErrors(maybeFile, artifact, verbosity)
              .iterator
          }
          def maybeExtractedArchiveIt: Iterator[File] = {
            cache.loggerOpt.foreach(_.init())
            val maybeDir =
              try archiveCache.get(artifact).unsafeRun()(cache.ec)
              finally cache.loggerOpt.foreach(_.stop())
            handleArtifactErrors(maybeDir, artifact, verbosity)
              .iterator
          }
          archiveTypeOpt match {
            case None =>
              maybeFileIt.map { f =>
                Uncompressed(artifact, f)
              }
            case Some((archiveType, None)) =>
              maybeFileIt.map { f =>
                Compressed(
                  artifact,
                  f,
                  archiveType,
                  None
                )
              }
            case Some((archiveType, Some(pathInArchive))) =>
              maybeExtractedArchiveIt.flatMap { f =>
                val innerF = new File(f, pathInArchive)
                if (innerF.exists())
                  Iterator.single(ExtractedArchive(
                    artifact,
                    f,
                    pathInArchive,
                    innerF
                  ))
                else
                  Iterator.empty
              }
          }
      }

    candidatePrebuiltArtifacts(
      desc,
      cache,
      verbosity,
      platform,
      platformExtensions,
      preferPrebuilt
    ).toRight(Nil).flatMap { artifacts =>
      val iterator = downloadArtifacts(artifacts)
      if (iterator.hasNext) Right(iterator.next())
      else Left(artifacts.map(_._1.url))
    }
  }

  private[coursier] def handleArtifactErrors(
    maybeFile: Either[ArtifactError, File],
    artifact: Artifact,
    verbosity: Int
  ): Option[File] =
    maybeFile match {
      case Left(e: ArtifactError.NotFound) =>
        if (verbosity >= 2)
          System.err.println(s"No prebuilt launcher found at ${artifact.url}")
        None
      case Left(e: ArtifactError.DownloadError)
          if e.getCause.isInstanceOf[javax.net.ssl.SSLHandshakeException] =>
        // These seem to happen on Windows for non existing artifacts, only from the native launcher apparently???
        // Interpreting these errors as not-found-errors too.
        if (verbosity >= 2)
          System.err.println(
            s"No prebuilt launcher found at ${artifact.url} (SSL handshake exception)"
          )
        None
      case Left(e) =>
        // FIXME Ignore some other kind of errors too? Just warn about them?
        throw new DownloadError(artifact.url, e)
      case Right(f) =>
        if (verbosity >= 1) {
          val size = ProgressBarRefreshDisplay.byteCount(Files.size(f.toPath))
          System.err.println(s"Found prebuilt launcher at ${artifact.url} ($size)")
        }
        Some(f)
    }

  private def candidatePrebuiltArtifacts(
    desc: AppDescriptor,
    cache: Cache[Task],
    verbosity: Int,
    platform: Option[String],
    platformExtensions: Seq[String],
    preferPrebuilt: Boolean
  ): Option[Seq[(Artifact, Option[(ArchiveType, Option[String])])]] = {

    def mainVersionsIterator(): Iterator[String] = {
      val it0 = desc.candidateMainVersions(cache, verbosity)
      val it =
        if (it0.hasNext) it0
        else desc.mainVersionOpt.iterator
      // check the latest 5 versions if preferPrebuilt is true
      // FIXME Don't hardcode that number?
      it.take(if (preferPrebuilt) 5 else 1)
    }

    def urlArchiveType(url: String): (String, Option[(ArchiveType, Option[String])]) = {
      val idx = url.indexOf('+')
      if (idx < 0) (url, None)
      else
        ArchiveType.parse(url.take(idx)) match {
          case Some(tpe) =>
            val url0         = url.drop(idx + 1)
            val subPathIndex = url0.indexOf('!')
            if (subPathIndex < 0)
              (url0, Some((tpe, None)))
            else {
              val subPath = url0.drop(subPathIndex + 1)
              (url0.take(subPathIndex), Some((tpe, Some(subPath))))
            }
          case None =>
            (url, None)
        }
    }

    def patternArtifacts(
      pattern: String
    ): Seq[(Artifact, Option[(ArchiveType, Option[String])])] = {

      val artifactsIt = for {
        version <- mainVersionsIterator()
        isSnapshot = version.endsWith("SNAPSHOT")
        baseUrl0 = pattern
          .replace("${version}", version)
          .replace("${platform}", platform.getOrElse(""))
        (baseUrl, archiveTypeAndPathOpt) = urlArchiveType(baseUrl0)
        ext <-
          if (archiveTypeAndPathOpt.forall(_._2.nonEmpty))
            platformExtensions.iterator ++ Iterator("")
          else Iterator("")
        (url, archiveTypeAndPathOpt0) = archiveTypeAndPathOpt match {
          case None                  => (baseUrl + ext, archiveTypeAndPathOpt)
          case Some((tpe, subPath0)) => (baseUrl, Some((tpe, subPath0.map(_ + ext))))
        }
      } yield (Artifact(url).withChanging(isSnapshot), archiveTypeAndPathOpt0)

      artifactsIt.toVector
    }

    if (desc.launcherType.isNative)
      desc.prebuiltLauncher
        .orElse(platform.flatMap(desc.prebuiltBinaries.get))
        .map(patternArtifacts)
    else
      None
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy