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

scala.build.internals.CustomProgressBarRefreshDisplay.scala Maven / Gradle / Ivy

The newest version!
package scala.build.internal

// Same as ProgressBarRefreshDisplay in coursier, but allowing not to keep progress
// bars on screen

import coursier.cache.internal.ConsoleDim
import coursier.cache.loggers._

import java.io.Writer
import java.sql.Timestamp

import scala.concurrent.duration.{Duration, DurationInt}

class CustomProgressBarRefreshDisplay(
  /** Whether to keep details on screen after this display is stopped */
  keepOnScreen: Boolean,
  beforeOutput: => Unit,
  afterOutput: => Unit
) extends RefreshDisplay {

  import coursier.cache.internal.Terminal.Ansi
  import CustomProgressBarRefreshDisplay.display

  val refreshInterval: Duration =
    20.millis

  private var printedAnything0 = false
  private var currentHeight    = 0

  override def stop(out: Writer): Unit = {

    for (_ <- 1 to 2; _ <- 0 until currentHeight) {
      out.clearLine(2)
      out.down(1)
    }
    for (_ <- 0 until currentHeight)
      out.up(2)

    out.flush()

    if (printedAnything0) {
      afterOutput
      printedAnything0 = false
    }

    currentHeight = 0
  }

  private def truncatedPrintln(out: Writer, s: String, width: Int): Unit = {
    out.clearLine(2)
    out.write(RefreshDisplay.truncated(s, width))
    out.write('\n')
  }

  def update(
    out: Writer,
    done: Seq[(String, RefreshInfo)],
    downloads: Seq[(String, RefreshInfo)],
    changed: Boolean
  ): Unit =
    if (changed) {

      val width = ConsoleDim.width()

      val done0 = done
        .filter {
          case (url, _) =>
            !url.endsWith(".sha1") &&
            !url.endsWith(".sha256") &&
            !url.endsWith(".md5") &&
            !url.endsWith("/")
        }

      val elems = done0.iterator.map((_, true)) ++ downloads.iterator.map((_, false))
      for (((url, info), isDone) <- elems) {
        assert(info != null, s"Incoherent state ($url)")

        if (!printedAnything0) {
          beforeOutput
          printedAnything0 = true
        }

        truncatedPrintln(out, url, width)
        out.clearLine(2)
        out.write(s"  ${display(info, isDone)}" + System.lineSeparator())
      }

      val displayedCount = done0.length + downloads.length

      if (displayedCount < currentHeight) {
        for (_ <- 1 to 2; _ <- displayedCount until currentHeight) {
          out.clearLine(2)
          out.down(1)
        }

        for (_ <- displayedCount until currentHeight)
          out.up(2)
      }

      for (_ <- downloads.indices)
        out.up(2)
      if (!keepOnScreen)
        for (_ <- done0.indices)
          out.up(2)

      out.left(10000)

      out.flush()

      currentHeight =
        if (keepOnScreen) downloads.length
        else displayedCount
    }

}

object CustomProgressBarRefreshDisplay {

  def create(): CustomProgressBarRefreshDisplay =
    new CustomProgressBarRefreshDisplay(keepOnScreen = true, (), ())

  def create(
    beforeOutput: => Unit,
    afterOutput: => Unit
  ): CustomProgressBarRefreshDisplay =
    new CustomProgressBarRefreshDisplay(keepOnScreen = true, beforeOutput, afterOutput)

  def create(
    keepOnScreen: Boolean,
    beforeOutput: => Unit,
    afterOutput: => Unit
  ): CustomProgressBarRefreshDisplay =
    new CustomProgressBarRefreshDisplay(keepOnScreen, beforeOutput, afterOutput)

  // Scala version of http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java/3758880#3758880
  def byteCount(bytes: Long, si: Boolean = false) = {
    val unit = if (si) 1000 else 1024
    if (bytes < unit)
      bytes.toString + "B"
    else {
      val prefixes = if (si) "kMGTPE" else "KMGTPE"
      val exp      = (math.log(bytes.toDouble) / math.log(unit.toDouble)).toInt min prefixes.length
      val pre      = prefixes.charAt(exp - 1).toString + (if (si) "" else "i")
      f"${bytes / math.pow(unit.toDouble, exp.toDouble)}%.1f ${pre}B"
    }
  }

  private val format =
    new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
  private def formatTimestamp(ts: Long): String =
    format.format(new Timestamp(ts))

  private def display(info: RefreshInfo, isDone: Boolean): String =
    info match {
      case d: RefreshInfo.DownloadInfo =>
        val actualFraction = d.fraction
          .orElse(if (isDone) Some(1.0) else None)
          .orElse(if (d.downloaded == 0L) Some(0.0) else None)

        val start =
          actualFraction match {
            case None =>
              "       [          ] "
            case Some(frac) =>
              val elem = if (d.watching) "." else "#"

              val decile = (10.0 * frac).toInt
              assert(decile >= 0)
              assert(decile <= 10)

              f"${100.0 * frac}%5.1f%%" +
                " [" + (elem * decile) + (" " * (10 - decile)) + "] "
          }

        start +
          byteCount(d.downloaded) +
          d.rate().fold("")(r => s" (${byteCount(r.toLong)} / s)")

      case c: RefreshInfo.CheckUpdateInfo =>
        if (isDone)
          (c.currentTimeOpt, c.remoteTimeOpt) match {
            case (Some(current), Some(remote)) =>
              if (current < remote)
                s"Updated since ${formatTimestamp(current)} (${formatTimestamp(remote)})"
              else if (current == remote)
                s"No new update since ${formatTimestamp(current)}"
              else
                s"Warning: local copy newer than remote one (${formatTimestamp(current)} > ${formatTimestamp(remote)})"
            case (Some(_), None) =>
              // FIXME Likely a 404 Not found, that should be taken into account by the cache
              "No modified time in response"
            case (None, Some(remote)) =>
              s"Last update: ${formatTimestamp(remote)}"
            case (None, None) =>
              "" // ???
          }
        else
          c.currentTimeOpt match {
            case Some(current) =>
              s"Checking for updates since ${formatTimestamp(current)}"
            case None =>
              "" // ???
          }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy