diff --git a/src/main/scala/de/upb/cs/swt/delphi/cli/Config.scala b/src/main/scala/de/upb/cs/swt/delphi/cli/Config.scala index 47f75c5..cc5b016 100644 --- a/src/main/scala/de/upb/cs/swt/delphi/cli/Config.scala +++ b/src/main/scala/de/upb/cs/swt/delphi/cli/Config.scala @@ -33,6 +33,7 @@ case class Config(server: String = sys.env.getOrElse("DELPHI_SERVER", "https://d query : String = "", limit : Option[Int] = None, id : String = "", + timeout : Option[Int] = None, args: List[String] = List(), opts: List[String] = List()) { diff --git a/src/main/scala/de/upb/cs/swt/delphi/cli/DelphiCLI.scala b/src/main/scala/de/upb/cs/swt/delphi/cli/DelphiCLI.scala index 64e4d14..1c5be43 100644 --- a/src/main/scala/de/upb/cs/swt/delphi/cli/DelphiCLI.scala +++ b/src/main/scala/de/upb/cs/swt/delphi/cli/DelphiCLI.scala @@ -18,7 +18,6 @@ package de.upb.cs.swt.delphi.cli import akka.actor.ActorSystem import akka.http.scaladsl.Http -import akka.stream.ActorMaterializer import de.upb.cs.swt.delphi.cli.commands.{RetrieveCommand, SearchCommand, TestCommand} import scala.concurrent.duration.Duration @@ -53,18 +52,19 @@ object DelphiCLI extends App { .text("Retrieve a project's description, specified by ID.") .children( arg[String]("id").action((x, c) => c.copy(id = x)).text("The ID of the project to retrieve"), + opt[String]("csv").action((x, c) => c.copy(csv = x)).text("Path to the output .csv file (overwrites existing file)"), opt[Unit]('f', "file").action((_, c) => c.copy(opts = List("file"))).text("Use to load the ID from file, " + - "with the filepath given in place of the ID"), - opt[String]("csv").action((x, c) => c.copy(csv = x)).text("Path to the output .csv file (overwrites existing file)") + "with the filepath given in place of the ID") ) cmd("search").action((s, c) => c.copy(mode = "search")) .text("Search artifact using a query.") .children( arg[String]("query").action((x,c) => c.copy(query = x)).text("The query to be used."), + opt[String]("csv").action((x, c) => c.copy(csv = x)).text("Path to the output .csv file (overwrites existing file)"), opt[Int]("limit").action((x, c) => c.copy(limit = Some(x))).text("The maximal number of results returned."), opt[Unit](name="list").action((_, c) => c.copy(list = true)).text("Output results as list (raw option overrides this)"), - opt[String]("csv").action((x, c) => c.copy(csv = x)).text("Path to the output .csv file (overwrites existing file)") + opt[Int]("timeout").action((x, c) => c.copy(timeout = Some(x))).text("Timeout in seconds.") ) } } diff --git a/src/main/scala/de/upb/cs/swt/delphi/cli/commands/SearchCommand.scala b/src/main/scala/de/upb/cs/swt/delphi/cli/commands/SearchCommand.scala index 55aed07..24b6683 100644 --- a/src/main/scala/de/upb/cs/swt/delphi/cli/commands/SearchCommand.scala +++ b/src/main/scala/de/upb/cs/swt/delphi/cli/commands/SearchCommand.scala @@ -16,7 +16,7 @@ package de.upb.cs.swt.delphi.cli.commands -import java.util.concurrent.TimeUnit +import java.util.concurrent.{TimeUnit, TimeoutException} import akka.actor.ActorSystem import akka.http.scaladsl.Http @@ -29,14 +29,16 @@ import akka.util.ByteString import de.upb.cs.swt.delphi.cli.Config import de.upb.cs.swt.delphi.cli.artifacts.SearchResult import de.upb.cs.swt.delphi.cli.artifacts.SearchResultJson._ -import de.upb.cs.swt.delphi.cli.commands.RetrieveCommand.information import spray.json.DefaultJsonProtocol import scala.concurrent.duration._ -import scala.concurrent.{Await, Future} -import scala.util.{Failure, Success} +import scala.concurrent.{Await, ExecutionContextExecutor, Future} +import scala.util.{Failure, Success, Try} object SearchCommand extends Command with SprayJsonSupport with DefaultJsonProtocol { + + val searchTimeout: Int = 10 + /** * Executes the command implementation * @@ -59,7 +61,20 @@ object SearchCommand extends Command with SprayJsonSupport with DefaultJsonProto Http().singleRequest(HttpRequest(uri = searchUri, method = HttpMethods.POST, entity = entity)) } - val response = Await.result(responseFuture, 10 seconds) + Try(Await.result(responseFuture, Duration(config.timeout.getOrElse(searchTimeout) + " seconds"))). + map(response => parseResponse(response, config, start)(ec, materializer)). + recover { + case e : TimeoutException => { + error(config)("The query timed out after " + (System.nanoTime() - start).nanos.toUnit(TimeUnit.SECONDS) + + " seconds. To set a longer timeout, use the --timeout option.") + Failure(e) + } + } + } + + private def parseResponse(response: HttpResponse, config: Config, start: Long) + (implicit ec: ExecutionContextExecutor, materializer: ActorMaterializer): Unit = { + val resultFuture: Future[String] = response match { case HttpResponse(StatusCodes.OK, headers, entity, _) => entity.dataBytes.runFold(ByteString(""))(_ ++ _).map { body =>