This is an automated email from the ASF dual-hosted git repository.

aradzinski pushed a commit to branch NLPCRAFT-108
in repository https://gitbox.apache.org/repos/asf/incubator-nlpcraft.git


The following commit(s) were added to refs/heads/NLPCRAFT-108 by this push:
     new ee4fcae  WIP.
ee4fcae is described below

commit ee4fcaef3d991de74632d40342460567c4d50b77
Author: Aaron Radzinski <[email protected]>
AuthorDate: Sun Oct 4 23:03:49 2020 -0700

    WIP.
---
 .../nlpcraft/common/ansi/NCAnsiProgressBar.scala   | 140 ++++++++++++++++
 .../nlpcraft/common/ansi/NCAnsiSpinner.scala       |  72 ++++----
 .../scala/org/apache/nlpcraft/common/package.scala |  75 +++++++++
 .../org/apache/nlpcraft/common/util/NCUtils.scala  |  81 ---------
 .../nlpcraft/model/tools/cmdline/NCCli.scala       | 185 ++++++++++++---------
 .../nlpcraft/server/probe/NCProbeManager.scala     |   2 +-
 6 files changed, 366 insertions(+), 189 deletions(-)

diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/ansi/NCAnsiProgressBar.scala
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/ansi/NCAnsiProgressBar.scala
new file mode 100644
index 0000000..73659b2
--- /dev/null
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/ansi/NCAnsiProgressBar.scala
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nlpcraft.common.ansi
+
+import java.io.PrintWriter
+
+import NCAnsi._
+import org.apache.nlpcraft.common.ansi.NCAnsiProgressBar._
+
+/**
+ * Forward-only, bound ANSI-based progress bar.
+ *
+ * @param out
+ * @param totalTicks Number of ticks to complete.
+ * @param dispSize Visual size of the progress bar.
+ * @param clearOnComplete
+ * @param useAnsi
+ */
+class NCAnsiProgressBar(
+    out: PrintWriter,
+    totalTicks: Int,
+    dispSize: Int,
+    clearOnComplete: Boolean = true,
+    useAnsi: Boolean = true) {
+    require(dispSize <= totalTicks)
+
+    @volatile private var tick = 0
+
+    //noinspection ZeroIndexToHead
+    private final val PB_LEFT = s"$ansiBlueFg${CHAR_SET(0)}$ansiReset"
+    private final val PB_RIGHT = s"$ansiBlueFg${CHAR_SET(3)}$ansiReset"
+    private final val PB_EMPTY =s"$ansiWhiteFg${CHAR_SET(2)}$ansiReset"
+    private final val PB_FULL = s"$ansiRedFg$ansiBold${CHAR_SET(1)}$ansiReset"
+
+    /**
+     *
+     */
+    private def clean(): Unit = {
+        out.print(ansiCursorLeft * (dispSize + 2))
+        out.print(ansiClearLineAfter)
+        out.flush()
+    }
+
+    /**
+     * Starts progress bar.
+     */
+    def start(): Unit = {
+        tick = 0
+
+        if (useAnsi) {
+            // Hide cursor to avoid blinking.
+            out.print(ansiCursorHide)
+
+            out.print(PB_LEFT)
+            out.print(PB_EMPTY * dispSize)
+            out.print(PB_RIGHT)
+
+            out.flush()
+        }
+    }
+
+    /**
+     * Ticks progress bar one tick at a time.
+     */
+    def ticked(): Unit = {
+        tick += 1
+
+        if (useAnsi) {
+            clean()
+
+            val bar = Math.round((tick.toFloat / totalTicks.toFloat) * 
dispSize)
+
+            out.print(PB_LEFT)
+            for (i ← 0 until dispSize)
+                out.print(if (i < bar) PB_FULL else PB_EMPTY)
+            out.print(PB_RIGHT)
+            out.flush()
+        }
+        else if (tick % (totalTicks / dispSize) == 0) {
+            out.print(NON_ANSI_CHAR)
+            out.flush()
+        }
+    }
+
+    /**
+     * Whether progress is complete.
+     *
+     * @return
+     */
+    def completed: Boolean =
+        tick == totalTicks
+
+    /**
+     * Stops progress bar.
+     */
+    def stop(): Unit = {
+        if (useAnsi && clearOnComplete) {
+            clean()
+
+            // Show cursor.
+            out.print(ansiCursorShow)
+            out.flush()
+        }
+    }
+}
+
+/**
+ *
+ */
+object NCAnsiProgressBar{
+    // Set of UNICODE charsets options for the progress bar.
+    private final val PB_CHAR_SETS = Seq(
+        Seq('[', '=', '.', ']'),
+        Seq('/', '▰', '▱', '/'),
+        Seq('[', '▰', '▱', ']'),
+        Seq('[', '◼', '◽', ']'),
+        Seq('[', '█', '_', ']'),
+        Seq('⟮', '▰', '.', '⟯')
+    )
+
+    private final val NON_ANSI_CHAR = '='
+
+    // Active charset to use.
+    private val CHAR_SET = PB_CHAR_SETS(5)
+}
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/ansi/NCAnsiSpinner.scala 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/ansi/NCAnsiSpinner.scala
index b1ae4bb..fe8733c 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/ansi/NCAnsiSpinner.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/ansi/NCAnsiSpinner.scala
@@ -18,38 +18,23 @@
 package org.apache.nlpcraft.common.ansi
 
 import java.io.PrintWriter
-import java.util.Random
 
 import NCAnsi._
 import org.apache.nlpcraft.common._
-import org.apache.nlpcraft.common.ansi.NCAnsiSpinner.RND
+import org.apache.nlpcraft.common.ansi.NCAnsiSpinner._
 
 /**
+ * ANSI-based hourglass spinner.
  *
+ * @param out
+ * @param useAnsi
  */
-class NCAnsiSpinner(out: PrintWriter, ansiColor: String = ansiCyanFg, useAnsi: 
Boolean = true) {
-    @volatile var thread: Thread = _
-
-    final val SPIN_CHAR_SETS = Seq(
-        Seq('-', '\\', '|', '/'),
-        Seq('.', 'o', 'O', '@', '*'),
-        Seq('←', '↖', '↑', '↗', '→', '↘', '↓', '↙'),
-        Seq('▁', '▂', '▃', '▄', '▅', '▆', '▇', '█', '▇', '▆', '▅', '▄', '▃', 
'▁'),
-        Seq('▖', '▘', '▝', '▗'),
-        Seq('┤', '┘', '┴', '└', '├', '┌', '┬', '┐'),
-        Seq('◢', '◣', '◤', '◥'),
-        Seq('◰', '◳', '◲', '◱'),
-        Seq('◴', '◷', '◶', '◵'),
-        Seq('◐', '◓', '◑', '◒'),
-        Seq('◡', '⊙', '◠', '⊙'),
-        Seq('⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷'),
-        Seq('⠁', '⠂', '⠄', '⡀', '⢀', '⠠', '⠐', '⠈')
-    )
-
-    private var suffix = ""
-    private var prefix = ""
-    private var lastLength = 0
-    private var frame = 0
+class NCAnsiSpinner(out: PrintWriter, useAnsi: Boolean = true) {
+    @volatile private var thread: Thread = _
+    @volatile private var suffix = ""
+    @volatile private var prefix = ""
+    @volatile private var lastLength = 0
+    @volatile private var frame = 0
 
     /**
      *
@@ -75,7 +60,7 @@ class NCAnsiSpinner(out: PrintWriter, ansiColor: String = 
ansiCyanFg, useAnsi: B
     }
 
     /**
-     *
+     * Starts spinner.
      */
     def start(): Unit =
         if (useAnsi) {
@@ -83,8 +68,6 @@ class NCAnsiSpinner(out: PrintWriter, ansiColor: String = 
ansiCyanFg, useAnsi: B
                 frame = 0
                 lastLength = 0
 
-                val chars = SPIN_CHAR_SETS(RND.nextInt(SPIN_CHAR_SETS.size))
-
                 // Hide cursor to avoid blinking.
                 out.print(ansiCursorHide)
                 out.flush()
@@ -93,24 +76,26 @@ class NCAnsiSpinner(out: PrintWriter, ansiColor: String = 
ansiCyanFg, useAnsi: B
                     if (frame > 0)
                         clean()
 
-                    out.print(s"$prefix$ansiColor${chars(frame % 
chars.size)}$ansiReset$suffix")
+                    out.print(s"$prefix$ansiCyanFg${CHAR_SET(frame % 
CHAR_SET.size)}$ansiReset$suffix")
                     out.flush()
 
                     lastLength = U.stripAnsi(prefix).length + 1 + 
U.stripAnsi(suffix).length
 
                     frame += 1
 
-                    Thread.sleep(200)
+                    Thread.sleep(CHAR_SET.size.fps) // Full rotation per 
second.
                 }
             }
 
             thread.start()
         }
-        else
+        else {
             out.print("... ")
+            out.flush()
+        }
 
     /**
-     *
+     * Stops spinner.
      */
     def stop(): Unit = {
         U.stopThread(thread)
@@ -125,6 +110,27 @@ class NCAnsiSpinner(out: PrintWriter, ansiColor: String = 
ansiCyanFg, useAnsi: B
     }
 }
 
+/**
+ *
+ */
 object NCAnsiSpinner {
-    private val RND = new Random()
+    // Set of UNICODE charset options for the spinner.
+    private final val SPIN_CHAR_SETS = Seq(
+        Seq('-', '\\', '|', '/'),
+        Seq('.', 'o', 'O', '@', '*'),
+        Seq('←', '↖', '↑', '↗', '→', '↘', '↓', '↙'),
+        Seq('▁', '▂', '▃', '▄', '▅', '▆', '▇', '█', '▇', '▆', '▅', '▄', '▃', 
'▁'),
+        Seq('▖', '▘', '▝', '▗'),
+        Seq('┤', '┘', '┴', '└', '├', '┌', '┬', '┐'),
+        Seq('◢', '◣', '◤', '◥'),
+        Seq('◰', '◳', '◲', '◱'),
+        Seq('◴', '◷', '◶', '◵'),
+        Seq('◐', '◓', '◑', '◒'),
+        Seq('◡', '⊙', '◠', '⊙'),
+        Seq('⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷'),
+        Seq('⠁', '⠂', '⠄', '⡀', '⢀', '⠠', '⠐', '⠈')
+    )
+
+    // An active charset to use.
+    private final val CHAR_SET = SPIN_CHAR_SETS.head
 }
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/package.scala 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/package.scala
index fadf539..d266c4b 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/package.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/package.scala
@@ -64,6 +64,81 @@ package object common {
     def bo(s: Any): String = s"$ansiBold${s.toString}$ansiReset"
 
     /**
+     * Pimps integers with KB, MB, GB units of measure.
+     *
+     * @param v Integer value.
+     */
+    implicit class IntMemoryUnits(v: Int) {
+        def TB: Int = v * 1024 * 1024 * 1024 * 1024
+        def GB: Int = v * 1024 * 1024 * 1024
+        def MB: Int = v * 1024 * 1024
+        def KB: Int = v * 1024
+        def tb: Int = TB
+        def gb: Int = GB
+        def mb: Int = MB
+        def kb: Int = KB
+    }
+
+    /**
+     * Pimps longs with KB, MB, GB units of measure.
+     *
+     * @param v Long value.
+     */
+    implicit class LongMemoryUnits(v: Long) {
+        def TB: Long = v * 1024 * 1024 * 1024 * 1024
+        def GB: Long = v * 1024 * 1024 * 1024
+        def MB: Long = v * 1024 * 1024
+        def KB: Long = v * 1024
+        def tb: Long = TB
+        def gb: Long = GB
+        def mb: Long = MB
+        def kb: Long = KB
+    }
+
+
+    /**
+     * Pimps integers with time units.
+     *
+     * @param v Integer value.
+     */
+    implicit class IntTimeUnits(v: Int) {
+        def MSECS: Int = v
+        def MS: Int = v
+        def SECS: Int = v * 1000
+        def MINS: Int = v * 1000 * 60
+        def HOURS: Int = v * 1000 * 60 * 60
+        def DAYS: Int = v * 1000 * 60 * 60 * 24
+        def FPS: Int = 1000 / v
+        def ms: Int = MS
+        def secs: Int = SECS
+        def mins: Int = MINS
+        def hours: Int = HOURS
+        def days: Int = DAYS
+        def fps: Int = 1000 / v
+    }
+
+    /**
+     * Pimps long with time units.
+     *
+     * @param v Long value.
+     */
+    implicit class LongTimeUnits(v: Long) {
+        def MSECS: Long = v
+        def MS: Long = v
+        def SECS: Long = v * 1000
+        def MINS: Long = v * 1000 * 60
+        def HOURS: Long = v * 1000 * 60 * 60
+        def DAYS: Long = v * 1000 * 60 * 60 * 24
+        def FPS: Long = 1000 / v
+        def ms: Long = MS
+        def secs: Long = SECS
+        def mins: Long = MINS
+        def hours: Long = HOURS
+        def days: Long = DAYS
+        def fps: Long = 1000 / v
+    }
+
+    /**
      * 
      * @param f
      * @tparam T
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/util/NCUtils.scala 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/util/NCUtils.scala
index 4e37c20..1742da9 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/util/NCUtils.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/util/NCUtils.scala
@@ -1053,87 +1053,6 @@ object NCUtils extends LazyLogging {
         IOUtils.toString(new GIS(new 
ByteArrayInputStream(Base64.decodeBase64(zipStr))), Charset.defaultCharset())
 
     /**
-      * Pimps integers with KB, MB, GB units of measure.
-      *
-      * @param v Integer value.
-      */
-    implicit class IntMemoryUnits(v: Int) {
-        def TB: Int = v * 1024 * 1024 * 1024 * 1024
-        def GB: Int = v * 1024 * 1024 * 1024
-        def MB: Int = v * 1024 * 1024
-        def KB: Int = v * 1024
-        def tb: Int = TB
-        def gb: Int = GB
-        def mb: Int = MB
-        def kb: Int = KB
-    }
-
-    /**
-      * Pimps longs with KB, MB, GB units of measure.
-      *
-      * @param v Long value.
-      */
-    implicit class LongMemoryUnits(v: Long) {
-        def TB: Long = v * 1024 * 1024 * 1024 * 1024
-        def GB: Long = v * 1024 * 1024 * 1024
-        def MB: Long = v * 1024 * 1024
-        def KB: Long = v * 1024
-        def tb: Long = TB
-        def gb: Long = GB
-        def mb: Long = MB
-        def kb: Long = KB
-    }
-
-
-    /**
-      * Pimps integers with time units.
-      *
-      * @param v Integer value.
-      */
-    implicit class IntTimeUnits(v: Int) {
-        def MSECS: Int = v
-        def MS: Int = v
-        def SECS: Int = v * 1000
-        def MINS: Int = v * 1000 * 60
-        def HOURS: Int = v * 1000 * 60 * 60
-        def DAYS: Int = v * 1000 * 60 * 60 * 24
-        def ms: Int = MS
-        def secs: Int = SECS
-        def mins: Int = MINS
-        def hours: Int = HOURS
-        def days: Int = DAYS
-    }
-
-    /**
-      * Pimps long with time units.
-      *
-      * @param v Long value.
-      */
-    implicit class LongTimeUnits(v: Long) {
-        def MSECS: Long = v
-
-        def MS: Long = v
-
-        def SECS: Long = v * 1000
-
-        def MINS: Long = v * 1000 * 60
-
-        def HOURS: Long = v * 1000 * 60 * 60
-
-        def DAYS: Long = v * 1000 * 60 * 60 * 24
-
-        def ms: Long = MS
-
-        def secs: Long = SECS
-
-        def mins: Long = MINS
-
-        def hours: Long = HOURS
-
-        def days: Long = DAYS
-    }
-
-    /**
       * Sleeps number of milliseconds properly handling exceptions.
       *
       * @param delay Number of milliseconds to sleep.
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/cmdline/NCCli.scala 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/cmdline/NCCli.scala
index dcf8811..9363703 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/cmdline/NCCli.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/tools/cmdline/NCCli.scala
@@ -17,8 +17,6 @@
 
 package org.apache.nlpcraft.model.tools.cmdline
 
-import java.io.{File, FileInputStream, IOException, ObjectInputStream}
-
 import com.google.gson._
 import javax.net.ssl.SSLException
 import org.apache.commons.lang3.SystemUtils
@@ -30,7 +28,7 @@ import org.apache.http.entity.StringEntity
 import org.apache.http.impl.client.HttpClients
 import org.apache.nlpcraft.common.ascii.NCAsciiTable
 import org.apache.nlpcraft.common._
-import org.apache.nlpcraft.common.ansi.{NCAnsi, NCAnsiSpinner}
+import org.apache.nlpcraft.common.ansi.{NCAnsi, NCAnsiProgressBar, 
NCAnsiSpinner}
 import org.apache.nlpcraft.common.ansi.NCAnsi._
 import org.apache.nlpcraft.common.version.NCVersion
 import java.lang.ProcessBuilder.Redirect
@@ -38,9 +36,10 @@ import java.lang.management.ManagementFactory
 import java.text.DateFormat
 import java.util
 import java.util.Date
-import java.util.regex.PatternSyntaxException
+import java.io._
+import java.util.regex.{Pattern, PatternSyntaxException}
 
-import org.apache.nlpcraft.common.util.NCUtils.IntTimeUnits
+import org.apache.commons.io.input.{Tailer, TailerListenerAdapter}
 import org.jline.reader.Completer
 import org.jline.reader.impl.DefaultParser
 import org.jline.terminal.{Terminal, TerminalBuilder}
@@ -63,8 +62,18 @@ import scala.util.control.Exception.ignoring
 object NCCli extends App {
     private final val NAME = "Apache NLPCraft CLI"
 
+    //noinspection RegExpRedundantEscape\
+    private final val TAILER_PTRN = Pattern.compile("^.*NC[a-zA-Z0-9]+ started 
\\[[\\d]+ms\\]$")
+
+    // Number of server services that need to be started.
+    // Used for progress bar functionality.
+    // +==================================================================+
+    // | MAKE SURE TO UPDATE THIS VAR WHEN NUMBER OF SERVICES IS CHANGED. |
+    // +==================================================================+
+    private final val NUM_SRV_SERVICES = 30
+
     private final val SRV_BEACON_PATH = ".nlpcraft/server_beacon"
-    private final val HIST_PATH = ".nlpcraft/cli_history"
+    private final val HIST_PATH = ".nlpcraft/.cli_history"
 
     private final lazy val VER = NCVersion.getCurrent
     private final lazy val JAVA = U.sysEnv("NLPCRAFT_CLI_JAVA").getOrElse(new 
File(SystemUtils.getJavaHome,s"bin/java${if (SystemUtils.IS_OS_UNIX) "" else 
".exe"}").getAbsolutePath)
@@ -88,12 +97,13 @@ object NCCli extends App {
 
     case class SplitError(index: Int) extends Exception
 
-    case class State(
-        var isServer: Boolean,
-        var accessToken: Option[String]
+    case class ReplState(
+        var isServerOnline: Boolean = false,
+        var accessToken: Option[String] = None,
+        var serverOutput: Option[File] = None
     )
 
-    private val state = State(isServer = false, None)
+    private val replState = ReplState()
 
     // Single CLI command.
     case class Command(
@@ -215,15 +225,6 @@ object NCCli extends App {
                         s"provide an alternative path."
                 ),
                 Parameter(
-                    id = "output",
-                    names = Seq("--output-path", "-o"),
-                    value = Some("path"),
-                    optional = true,
-                    desc =
-                        "File path for both REST server stdout and stderr 
output. If not provided, the REST server" +
-                        s"output will be piped into 
${y("${USER_HOME}/.nlpcraft/server-output-xxx.txt")}' file."
-                ),
-                Parameter(
                     id = "noWait",
                     names = Seq("--no-wait"),
                     optional = true,
@@ -469,19 +470,24 @@ object NCCli extends App {
         val cfgPath = args.find(_.parameter.id == "config")
         val igniteCfgPath = args.find(_.parameter.id == "igniteConfig")
         val noWait = args.exists(_.parameter.id == "noWait")
-        val output = args.find(_.parameter.id == "output") match {
-            case Some(arg) ⇒ new File(stripQuotes(arg.value.get))
-            case None ⇒ new File(SystemUtils.getUserHome, 
s".nlpcraft/server-output-$currentTime.txt")
-        }
 
         checkFilePath(cfgPath)
         checkFilePath(igniteCfgPath)
 
+        // Ensure that there isn't another local server running.
         loadServerBeacon() match {
             case Some(b) ⇒ throw new IllegalStateException(s"Existing local 
server (pid ${c(b.pid)}) detected.")
             case None ⇒ ()
         }
 
+        val logTstamp = currentTime
+
+        // Server log redirect.
+        val output = new File(SystemUtils.getUserHome, 
s".nlpcraft/server_log_$logTstamp.txt")
+
+        // Store in REPL state right away.
+        replState.serverOutput = Some(output)
+
         val srvPb = new ProcessBuilder(
             JAVA,
             "-ea",
@@ -525,59 +531,98 @@ object NCCli extends App {
         bleachPb.redirectOutput(Redirect.appendTo(output))
 
         try {
-            // Start the 'server | bleach' process pipeline.
-            ProcessBuilder.startPipeline(Seq(srvPb, bleachPb).asJava)
+            // Start the 'server | bleach > server log output' process 
pipeline.
+            val procs = ProcessBuilder.startPipeline(Seq(srvPb, 
bleachPb).asJava)
+
+            val srvPid = procs.get(0).pid()
+
+            // Store mapping file between PID and timestamp (once we have 
server PID).
+            // Note that the same timestamp is used in server log file.
+            ignoring(classOf[IOException]) {
+                new File(SystemUtils.getUserHome, 
s".nlpcraft/.pid_${srvPid}_tstamp_$logTstamp").createNewFile()
+            }
+
+            logln(s"Server output ⇒ ${c(output.getAbsolutePath)}")
 
-            logln(s"Server output > ${c(output.getAbsolutePath)}")
+            /**
+             *
+             */
+            def showTip(): Unit = {
+                val tbl = new NCAsciiTable()
+
+                tbl += (s"${g("stop-server")}", "Stop the server.")
+                tbl += (s"${g("get-server")}", "Get server information.")
+                tbl += (s"${g("restart-server")}", "Restart the server.")
+                tbl += (s"${g("less-server")}", "Tail server log.")
+                tbl += (s"${g("ping-server")}", "Ping the server.")
+
+                logln(s"Handy commands:\n${tbl.toString}")
+            }
 
-            if (noWait)
+            if (noWait) {
                 logln(s"Server is starting...")
+
+                showTip()
+            }
             else {
+                val progressBar = new NCAnsiProgressBar(
+                    term.writer(),
+                    NUM_SRV_SERVICES,
+                    15,
+                    true,
+                    // ANSI is NOT disabled & we ARE NOT running from IDEA or 
Eclipse...
+                    NCAnsi.isEnabled && IS_SCRIPT
+                )
+
                 log(s"Server is starting ")
 
-                var beacon = loadServerBeacon().orNull
-                var online = false
-                val spinner = mkSpinner()
-                val timeout = currentTime + 5.mins
-                val warnTimeout = currentTime + 60.secs
+                progressBar.start()
+
+                U.mkThread("server-start-progress-bar") { _ ⇒
+                    Tailer.create(
+                        replState.serverOutput.get,
+                        new TailerListenerAdapter {
+                            override def handle(line: String): Unit =
+                                if (TAILER_PTRN.matcher(line).matches())
+                                    progressBar.ticked()
+                        },
+                        500.ms
+                    )
+                }
+                .start()
 
-                spinner.start()
+                var beacon: NCCliServerBeacon = null
+                var online = false
+                val endOfWait = currentTime + 3.mins // We try for 3 mins max.
 
-                while (currentTime < timeout && !online) {
-                    if (beacon == null)
-                        beacon = loadServerBeacon().orNull
-                    else
-                        online = Try(restHealth("http://"; + 
beacon.restEndpoint) == 200).getOrElse(false)
+                while (currentTime < endOfWait && !online) {
+                    if (progressBar.completed) {
+                        // First, load the beacon, if any.
+                        if (beacon == null)
+                            beacon = loadServerBeacon().orNull
 
-                    if (!online) {
-                        if (currentTime > warnTimeout)
-                            // Warn if it's taking too long.
-                            spinner.setSuffix(s" ${r("(taking too long - check 
logs)")}")
+                        // Once beacon is loaded, ensure that REST endpoint is 
live.
+                        if (beacon != null)
+                            online = Try(restHealth("http://"; + 
beacon.restEndpoint) == 200).getOrElse(false)
+                    }
 
+                    if (!online)
                         Thread.sleep(2.secs) // Check every 2 secs.
-                    }
                 }
 
-                spinner.stop()
+                progressBar.stop()
 
                 if (!online) {
-                    logln()
-                    error(s"Cannot detect live server.")
-                    error(s"Check output for errors: 
${c(output.getAbsolutePath)}")
+                    logln(r(" [Error]"))
+                    error(s"Failed to start server, check output for errors.")
                 }
                 else {
-                    logln(g("OK"))
+                    logln(g(" [OK]"))
                     logln(mkServerBeaconTable(beacon).toString)
+
+                    showTip()
                 }
             }
-
-            val tbl = new NCAsciiTable()
-
-            tbl += (s"${g("stop-server")}", "Stop the server.")
-            tbl += (s"${g("ping-server")}", "Ping the server.")
-            tbl += (s"${g("get-server")}", "Get server information.")
-
-            logln(s"Handy commands:\n${tbl.toString}")
         }
         catch {
             case e: Exception ⇒ error(s"Server failed to start: 
${y(e.getLocalizedMessage)}")
@@ -585,18 +630,6 @@ object NCCli extends App {
     }
 
     /**
-     * Makes default spinner.
-     *
-     * @return
-     */
-    private def mkSpinner() = new NCAnsiSpinner(
-        term.writer(),
-        ansiCyanFg,
-        // ANSI is NOT disabled & we ARE NOT running from IDEA or Eclipse...
-        NCAnsi.isEnabled && IS_SCRIPT
-    )
-
-    /**
      *
      * @return
      */
@@ -630,7 +663,11 @@ object NCCli extends App {
         while (i < num) {
             log(s"(${i + 1} of $num) pinging REST server at ${b(endpoint)} ")
 
-            val spinner = mkSpinner()
+            val spinner = new NCAnsiSpinner(
+                term.writer(),
+                // ANSI is NOT disabled & we ARE NOT running from IDEA or 
Eclipse...
+                NCAnsi.isEnabled && IS_SCRIPT
+            )
 
             spinner.start()
 
@@ -704,7 +741,7 @@ object NCCli extends App {
             case _: Exception ⇒ None
         }
 
-        state.isServer = beacon.isDefined
+        replState.isServerOnline = beacon.isDefined
 
         beacon
     }
@@ -732,7 +769,7 @@ object NCCli extends App {
                     logln(s"Local REST server (pid ${c(pid)}) has been 
stopped.")
 
                     // Update state right away.
-                    state.isServer = false
+                    replState.isServerOnline = false
                 } else
                     error(s"Failed to stop the local REST server (pid 
${c(pid)}).")
 
@@ -1040,8 +1077,8 @@ object NCCli extends App {
 
         while (!exit) {
             val rawLine = try {
-                val srvStr = bo(s"${if (state.isServer) s"ON " else s"OFF "}")
-                val acsTokStr = bo(s"${state.accessToken.getOrElse("")} ")
+                val srvStr = bo(s"${if (replState.isServerOnline) s"ON " else 
s"OFF "}")
+                val acsTokStr = bo(s"${replState.accessToken.getOrElse("")} ")
 
                 reader.printAbove("\n" + rb(w(s" server: $srvStr")) + wb(k(s" 
acsTok: $acsTokStr")))
                 reader.readLine(s"${g("\u25b6")} ")
@@ -1146,7 +1183,7 @@ object NCCli extends App {
      */
     private def unknownCommand(cmd: String): Unit = {
         error(s"Unknown command: ${y(cmd)}")
-        error(s"Use '${c("help")}' command to read the manual.")
+        error(s"Type '${c("help")}' to read the manual.")
     }
 
     /**
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/probe/NCProbeManager.scala 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/probe/NCProbeManager.scala
index aad300e..bb19c26 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/probe/NCProbeManager.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/probe/NCProbeManager.scala
@@ -360,7 +360,7 @@ object NCProbeManager extends NCService {
 
                         srv.bind(new InetSocketAddress(host, port))
                         
-                        logger.info(s"$name connection is on '$host:$port'")
+                        logger.trace(s"$name connection is on '$host:$port'")
                         
                         srv.setSoTimeout(Config.soTimeoutMs)
                         

Reply via email to