Skip to content

Commit

Permalink
Added client command line app
Browse files Browse the repository at this point in the history
  • Loading branch information
abrighton committed Nov 1, 2020
1 parent 73498c8 commit a0404c0
Show file tree
Hide file tree
Showing 28 changed files with 906 additions and 314 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
/.idea/
/.bsp/
/project/target/
/target/
target/
/esw-segment-db/target/
/esw-segment-db/xxx.json
10 changes: 10 additions & 0 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version = 2.6.4

project.excludeFilters = ["BuildInfo.scala", "target/"]

align.preset = more

docstrings = JavaDoc
indentOperator.preset = spray
maxColumn = 130
newlines.alwaysBeforeElseAfterCurlyIf = true
15 changes: 14 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,20 @@ import sbt._
import Dependencies._
import Settings._

lazy val `esw-segment-shared` = project
.settings(buildSettings: _*)
.settings(libraryDependencies ++= `esw-segment-shared-deps`)

lazy val `esw-segment-db` = project
.enablePlugins(DeployApp)
.enablePlugins(BuildInfoPlugin)
.settings(appSettings: _*)
.settings(libraryDependencies ++= `esw-segment-db-deps`)
.dependsOn(`esw-segment-shared` % "compile->compile;test->test")

lazy val `esw-segment-client` = project
.enablePlugins(DeployApp)
.enablePlugins(BuildInfoPlugin)
.settings(appSettings: _*)
.settings(libraryDependencies ++= `databaseTests-deps`)
.settings(libraryDependencies ++= `esw-segment-client-deps`)
.dependsOn(`esw-segment-shared` % "compile->compile;test->test")
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package esw.segment.client

import java.util.Date

object EswSegmentClientOptions {
val defaultPort = 9192
}

case class EswSegmentClientOptions(
host: String = "localhost",
port: Int = EswSegmentClientOptions.defaultPort,
date: Date = new Date(),
from: Date = new Date(),
to: Date = new Date(),
segmentId: Option[String] = None,
position: Option[Int] = None,
setPosition: Option[Unit] = None,
segmentPositions: Option[Unit] = None,
segmentIds: Option[Unit] = None,
newlyInstalledSegments: Option[Unit] = None,
currentPositions: Option[Unit] = None,
currentSegmentPosition: Option[Unit] = None,
currentSegmentAtPosition: Option[Unit] = None,
positionsOnDate: Option[Unit] = None,
segmentPositionOnDate: Option[Unit] = None,
segmentAtPositionOnDate: Option[Unit] = None
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package esw.segment.client

import java.util.Date

import akka.actor.ActorSystem
import buildinfo.BuildInfo
import esw.segment.shared.EswSegmentData._
import EswSegmentClientOptions._
import scopt.Read.reads
import scopt.Read

import scala.async.Async.{async, await}
import scala.concurrent.{Await, Future}
import scala.concurrent.duration._

object EswSegmentDbClient extends App {
val dateFormat = new java.text.SimpleDateFormat("yyyy-MM-dd")
implicit val system: ActorSystem = ActorSystem()

import system._

implicit val durationRead: Read[Date] =
reads {
dateFormat.parse
}

// Parser for the command line options
private val parser = new scopt.OptionParser[EswSegmentClientOptions]("esw-segment-db-client") {
head("esw-segment-db-client", BuildInfo.version)

opt[String]("host") valueName "<hostname>" action { (x, c) =>
c.copy(host = x)
} text s"The host name where the ESW Segment DB HTTP server is running (default: localhost)"

opt[Int]("port") valueName "<number>" action { (x, c) =>
c.copy(port = x)
} text s"The port number to use for the server (default: $defaultPort)"

opt[Date]('d', "date") valueName dateFormat.toPattern action { (x, c) =>
c.copy(date = x)
} text s"The date to use (default: current date)"

opt[Date]("from") valueName dateFormat.toPattern action { (x, c) =>
c.copy(from = x)
} text s"The starting date to use for a date range (default: current date)"

opt[Date]("to") valueName dateFormat.toPattern action { (x, c) =>
c.copy(to = x)
} text s"The ending date to use for a date range (default: current date)"

opt[String]('s', "segmentId") valueName "<id>" action { (x, c) =>
c.copy(segmentId = Some(x))
} text s"The segment id to use"

opt[Int]('p', "position") valueName "<number>" action { (x, c) =>
c.copy(position = Some(x))
} text s"The segment position to use (number in range 1 to 492)"

opt[Unit]("setPosition") action { (_, c) =>
c.copy(setPosition = Some(()))
} text s"Sets or updates the date and position of the given segment (Requires --position, --segmentId if segment is present)"

opt[Unit]("segmentPositions") action { (_, c) =>
c.copy(segmentPositions = Some(()))
} text s"Gets a list of segments positions for the given segment id in the given date range (Requires --segmentId)"

opt[Unit]("segmentIds") action { (_, c) =>
c.copy(segmentIds = Some(()))
} text s"Gets a list of segment ids that were in the given position in the given date range (Requires --position)"

opt[Unit]("newlyInstalledSegments") action { (_, c) =>
c.copy(newlyInstalledSegments = Some(()))
} text s"Gets a list of segments that were installed since the given date (Requires --date)"

opt[Unit]("currentPositions") action { (_, c) =>
c.copy(currentPositions = Some(()))
} text s"Gets the current segment positions, sorted by position"

opt[Unit]("currentSegmentPosition") action { (_, c) =>
c.copy(currentSegmentPosition = Some(()))
} text s"Gets the current segment position for the given segment id (Requires --segmentId)"

opt[Unit]("currentSegmentAtPosition") action { (_, c) =>
c.copy(currentSegmentAtPosition = Some(()))
} text s"Gets the id of the segment currently in the given position (Requires --position)"

opt[Unit]("positionsOnDate") action { (_, c) =>
c.copy(positionsOnDate = Some(()))
} text s"Gets the segment positions as they were on the given date, sorted by position"

opt[Unit]("segmentPositionOnDate") action { (_, c) =>
c.copy(segmentPositionOnDate = Some(()))
} text s"Gets the segment position for the given segment id on the given date (Requires --segmentId)"

opt[Unit]("segmentAtPositionOnDate") action { (_, c) =>
c.copy(segmentAtPositionOnDate = Some(()))
} text s"Gets the id of the segment that was installed in the given position on the given date (Requires --position)"

}

// Parse the command line options
parser.parse(args, EswSegmentClientOptions()) match {
case Some(options) =>
try {
Await.ready(run(options), 60.seconds)
}
catch {
case e: Throwable =>
e.printStackTrace()
System.exit(1)
}
case None => System.exit(1)
}

private def error(msg: String): Unit = {
println(msg)
System.exit(1)
}

private def showResults(result: List[SegmentToM1Pos]): Unit = {
result.foreach(r => println(s"${dateFormat.format(r.date)} ${r.maybeId.getOrElse("------")} ${r.pos}"))
}

// Run the application
private def run(options: EswSegmentClientOptions): Future[Unit] =
async {
import options._
val client = new EswSegmentHttpClient(host, port)

if (options.setPosition.isDefined) {
if (position.isEmpty) error("--position option is required")
val segmentToM1Pos = SegmentToM1Pos(date, segmentId, position.get)
val result = await(client.setPosition(segmentToM1Pos))
if (!result)
error(s"setPosition failed for date: $date, segmentId: ${segmentId.getOrElse("None")}, position: ${position.get}")
}

if (options.segmentPositions.isDefined) {
if (segmentId.isEmpty) error("--segmentId option is required")
showResults(await(client.segmentPositions(DateRange(from, to), segmentId.get)))
}

if (options.segmentIds.isDefined) {
if (position.isEmpty) error("--position option is required")
showResults(await(client.segmentIds(DateRange(from, to), position.get)))
}

if (options.newlyInstalledSegments.isDefined) {
showResults(await(client.newlyInstalledSegments(date)))
}

if (options.currentPositions.isDefined) {
showResults(await(client.currentPositions()))
}

if (options.currentSegmentPosition.isDefined) {
if (segmentId.isEmpty) error("--segmentId option is required")
showResults(await(client.currentSegmentPosition(segmentId.get)).toList)
}

if (options.currentSegmentAtPosition.isDefined) {
if (position.isEmpty) error("--position option is required")
showResults(await(client.currentSegmentAtPosition(position.get)).toList)
}

if (options.positionsOnDate.isDefined) {
showResults(await(client.positionsOnDate(date)))
}

if (options.segmentPositionOnDate.isDefined) {
if (segmentId.isEmpty) error("--segmentId option is required")
showResults(await(client.segmentPositionOnDate(date, segmentId.get)).toList)
}

if (options.segmentAtPositionOnDate.isDefined) {
if (position.isEmpty) error("--position option is required")
showResults(await(client.segmentAtPositionOnDate(date, position.get)).toList)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package esw.segment.client

import java.sql.Date

import esw.segment.shared.{EswSegmentData, JsonSupport, SegmentToM1Api}
import EswSegmentData._
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.StatusCodes.OK
import akka.http.scaladsl.model.{ContentTypes, HttpEntity, HttpMethods, HttpRequest, Uri}
import spray.json._
import akka.http.scaladsl.unmarshalling.Unmarshal
import EswSegmentClientOptions._

import scala.async.Async.{async, await}
import scala.concurrent.{ExecutionContextExecutor, Future}

/**
* HTTP Client for ESW Segment DB HTTP Server
*/
class EswSegmentHttpClient(host: String = "localhost", port: Int = defaultPort)(implicit
actorSystem: ActorSystem,
ec: ExecutionContextExecutor
) extends SegmentToM1Api
with JsonSupport {

private val baseUri = s"http://$host:$port"

private def postSet(uri: Uri, json: String): Future[Boolean] =
async {
val entity = HttpEntity(ContentTypes.`application/json`, json)
val request = HttpRequest(HttpMethods.POST, uri = uri, entity = entity)
val response = await(Http().singleRequest(request))
response.status == OK
}

private def postGetList(uri: Uri, json: String): Future[List[SegmentToM1Pos]] =
async {
val entity = HttpEntity(ContentTypes.`application/json`, json)
val request = HttpRequest(HttpMethods.POST, uri = uri, entity = entity)
val response = await(Http().singleRequest(request))
await(Unmarshal(response).to[List[SegmentToM1Pos]])
}

private def postGetOption(uri: Uri, json: String): Future[Option[SegmentToM1Pos]] =
async {
val entity = HttpEntity(ContentTypes.`application/json`, json)
val request = HttpRequest(HttpMethods.POST, uri = uri, entity = entity)
val response = await(Http().singleRequest(request))
await(Unmarshal(response).to[Option[SegmentToM1Pos]])
}

private def getOption(uri: Uri): Future[Option[SegmentToM1Pos]] =
async {
val request = HttpRequest(HttpMethods.GET, uri = uri)
val response = await(Http().singleRequest(request))
await(Unmarshal(response).to[Option[SegmentToM1Pos]])
}

// --

override def setPosition(segmentToM1Pos: SegmentToM1Pos): Future[Boolean] = {
postSet(Uri(s"$baseUri/setPosition"), segmentToM1Pos.toJson.compactPrint)
}

override def setPositions(date: Date, positions: List[(Option[String], Int)]): Future[Boolean] = {
postSet(Uri(s"$baseUri/setPositions"), SegmentToM1Positions(date, positions).toJson.compactPrint)
}

override def setAllPositions(date: Date, allPositions: List[Option[String]]): Future[Boolean] = {
postSet(Uri(s"$baseUri/setAllPositions"), AllSegmentPositions(date, allPositions).toJson.compactPrint)
}

override def segmentPositions(dateRange: DateRange, segmentId: String): Future[List[SegmentToM1Pos]] = {
postGetList(Uri(s"$baseUri/segmentPositions/$segmentId"), dateRange.toJson.compactPrint)
}

override def segmentIds(dateRange: DateRange, pos: Int): Future[List[SegmentToM1Pos]] = {
postGetList(Uri(s"$baseUri/segmentIds/$pos"), dateRange.toJson.compactPrint)
}

override def newlyInstalledSegments(since: Date): Future[List[SegmentToM1Pos]] = {
postGetList(Uri(s"$baseUri/newlyInstalledSegments"), since.toJson.compactPrint)
}

override def positionsOnDate(date: Date): Future[List[SegmentToM1Pos]] = {
postGetList(Uri(s"$baseUri/positionsOnDate"), date.toJson.compactPrint)
}

override def segmentPositionOnDate(date: Date, segmentId: String): Future[Option[SegmentToM1Pos]] = {
postGetOption(Uri(s"$baseUri/segmentPositionOnDate/$segmentId"), date.toJson.compactPrint)
}

override def segmentAtPositionOnDate(date: Date, pos: Int): Future[Option[SegmentToM1Pos]] = {
postGetOption(Uri(s"$baseUri/segmentAtPositionOnDate/$pos"), date.toJson.compactPrint)
}

override def currentPositions(): Future[List[SegmentToM1Pos]] =
async {
val uri = Uri(s"$baseUri/currentPositions")
val request = HttpRequest(HttpMethods.GET, uri = uri)
val response = await(Http().singleRequest(request))
await(Unmarshal(response).to[List[SegmentToM1Pos]])
}

override def currentSegmentPosition(segmentId: String): Future[Option[SegmentToM1Pos]] = {
getOption(Uri(s"$baseUri/currentSegmentPosition/$segmentId"))
}

override def currentSegmentAtPosition(pos: Int): Future[Option[SegmentToM1Pos]] = {
getOption(Uri(s"$baseUri/currentSegmentAtPosition/$pos"))
}

/**
* Drops and recreates the database tables (for testing)
*/
override def resetTables(): Future[Boolean] =
async {
val uri = Uri(s"$baseUri/resetTables")
val request = HttpRequest(HttpMethods.POST, uri = uri)
val response = await(Http().singleRequest(request))
response.status == OK
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package esw.segment.client

import akka.actor.ActorSystem
import esw.segment.shared.SegmentToM1ApiTestBase
import EswSegmentClientTest._

object EswSegmentClientTest {
implicit val system: ActorSystem = ActorSystem()
import system._
val client = new EswSegmentHttpClient()
}

class EswSegmentClientTest extends SegmentToM1ApiTestBase(client)
Loading

0 comments on commit a0404c0

Please sign in to comment.