diff --git a/.gitignore b/.gitignore index fda1310d78..f3abfa5f80 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ server/target/ sdk/target/ core/target/ -test-examples/resources/tmp/ \ No newline at end of file +test-examples/resources/tmp/ + diff --git a/build.sbt b/build.sbt index de4a25f9e2..5309489599 100644 --- a/build.sbt +++ b/build.sbt @@ -2,9 +2,10 @@ name := "optic-core" organization := "com.opticdev" -version := "0.1.0" +val appVersion = "0.1.1" + +version := appVersion -val appVersion = "0.1.0" val commonSettings: Seq[Def.Setting[_]] = Seq( version := appVersion, @@ -27,8 +28,16 @@ lazy val common = (project in file("common")). .settings(libraryDependencies ++= Dependencies.commonDependencies) lazy val sdk = (project in file("sdk")). + enablePlugins(BuildInfoPlugin). settings(commonSettings: _*) - .settings(libraryDependencies ++= Dependencies.sdkDependencies) + .settings( + libraryDependencies ++= Dependencies.sdkDependencies, + buildInfoKeys := Seq[BuildInfoKey]( + "opticMDTar" -> Constants.opticMDTar, + "opticMDTarSum" -> Constants.opticMDTarSum, + ), + buildInfoPackage := "com.opticdev.sdk" + ) .dependsOn(common) lazy val opm = (project in file("opm")). @@ -64,10 +73,10 @@ lazy val server = (project in file("server")). .dependsOn(arrow % "compile->compile;test->test") .settings( test in assembly := {}, + assemblyJarName in assembly := "server-assembly.jar", mainClass in assembly := Some("com.opticdev.server.http.Lifecycle"), mainClass in packageBin := Some("com.opticdev.server.http.Lifecycle") - ) + ) .enablePlugins(AssemblyPlugin) - concurrentRestrictions in Global += Tags.limit(Tags.Test, 1) \ No newline at end of file diff --git a/common/src/main/scala-2.12/com/opticdev/common/storage/PlatformConstants.scala b/common/src/main/scala-2.12/com/opticdev/common/PlatformConstants.scala similarity index 71% rename from common/src/main/scala-2.12/com/opticdev/common/storage/PlatformConstants.scala rename to common/src/main/scala-2.12/com/opticdev/common/PlatformConstants.scala index fe61cc4b45..58305649ce 100644 --- a/common/src/main/scala-2.12/com/opticdev/common/storage/PlatformConstants.scala +++ b/common/src/main/scala-2.12/com/opticdev/common/PlatformConstants.scala @@ -1,6 +1,7 @@ -package com.opticdev.common.storage +package com.opticdev.common import better.files.File +import com.opticdev.common.storage.{Linux, Mac, OS, Windows} import org.apache.commons.lang3.SystemUtils object PlatformConstants { @@ -22,4 +23,18 @@ object PlatformConstants { .createIfNotExists(asDirectory = true, createParents = false) } + + + + + + //Exec paths + val bashPath: String = platform match { + case Mac => "/bin/bash" + } + + val nodePath: String = platform match { + case Mac => "/usr/local/bin/node" + } + } \ No newline at end of file diff --git a/common/src/main/scala-2.12/com/opticdev/common/storage/DataDirectory.scala b/common/src/main/scala-2.12/com/opticdev/common/storage/DataDirectory.scala index 5f758aa5fb..3a296533d9 100644 --- a/common/src/main/scala-2.12/com/opticdev/common/storage/DataDirectory.scala +++ b/common/src/main/scala-2.12/com/opticdev/common/storage/DataDirectory.scala @@ -1,5 +1,8 @@ package com.opticdev.common.storage +import better.files.File +import com.opticdev.common.PlatformConstants + object DataDirectory { val root = PlatformConstants.dataDirectory @@ -7,7 +10,7 @@ object DataDirectory { val parsers = PlatformConstants.dataDirectory / "parsers" val compiled = PlatformConstants.dataDirectory / "compiled" val sourcegear = PlatformConstants.dataDirectory / "sourcegear" - + val bin = PlatformConstants.dataDirectory / "bin" def hasValidStructure = { root.isDirectory && @@ -15,6 +18,7 @@ object DataDirectory { packages.isDirectory && compiled.isDirectory && sourcegear.isDirectory + bin.isDirectory } def buildDirectoryStructure = { @@ -23,6 +27,7 @@ object DataDirectory { packages.createIfNotExists(asDirectory = true) compiled.createIfNotExists(asDirectory = true) sourcegear.createIfNotExists(asDirectory = true) + bin.createIfNotExists(asDirectory = true) } def delete = root.delete(true) @@ -32,4 +37,12 @@ object DataDirectory { buildDirectoryStructure } + def init : File = { + if (!hasValidStructure) { + reset + } + + DataDirectory.root + } + } diff --git a/common/src/test/scala-2.12/com/opticdev/common/storage/PlatformConstantsSpec.scala b/common/src/test/scala-2.12/com/opticdev/common/PlatformConstantsSpec.scala similarity index 88% rename from common/src/test/scala-2.12/com/opticdev/common/storage/PlatformConstantsSpec.scala rename to common/src/test/scala-2.12/com/opticdev/common/PlatformConstantsSpec.scala index c7474e4aed..e8cc5adc20 100644 --- a/common/src/test/scala-2.12/com/opticdev/common/storage/PlatformConstantsSpec.scala +++ b/common/src/test/scala-2.12/com/opticdev/common/PlatformConstantsSpec.scala @@ -1,8 +1,9 @@ -package com.opticdev.common.storage +package com.opticdev.common import better.files.File -import org.scalatest.FunSpec +import com.opticdev.common.storage.{Mac, OS} import org.apache.commons.lang3.SystemUtils +import org.scalatest.FunSpec class PlatformConstantsSpec extends FunSpec { diff --git a/common/src/test/scala-2.12/com/opticdev/common/storage/DataDirectorySpec.scala b/common/src/test/scala-2.12/com/opticdev/common/storage/DataDirectorySpec.scala index 91237e09c5..7b1f5009ff 100644 --- a/common/src/test/scala-2.12/com/opticdev/common/storage/DataDirectorySpec.scala +++ b/common/src/test/scala-2.12/com/opticdev/common/storage/DataDirectorySpec.scala @@ -1,6 +1,7 @@ package com.opticdev.common.storage import better.files.File +import com.opticdev.common.PlatformConstants import org.scalatest.FunSpec class DataDirectorySpec extends FunSpec { @@ -10,12 +11,20 @@ class DataDirectorySpec extends FunSpec { assert(PlatformConstants.dataDirectory.notExists) } + it("can init when missing or corrupt") { + DataDirectory.delete + DataDirectory.init + assert(DataDirectory.hasValidStructure) + } + describe("can detect invalid structure") { it("when empty") { + DataDirectory.root.list.foreach(_.delete(true)) assert(!DataDirectory.hasValidStructure) } it("when a folder is missing") { + DataDirectory.delete DataDirectory.root.createIfNotExists(asDirectory = true) DataDirectory.packages.createIfNotExists(asDirectory = true) DataDirectory.compiled.createIfNotExists(asDirectory = true) @@ -23,11 +32,5 @@ class DataDirectorySpec extends FunSpec { } } - - it("can create the directory with valid structure") { - DataDirectory.delete - DataDirectory.buildDirectoryStructure - assert(DataDirectory.hasValidStructure) - } } diff --git a/config.yaml b/config.yaml index 3916f0aa79..180b371737 100644 --- a/config.yaml +++ b/config.yaml @@ -1 +1 @@ -testParser: "server/src/main/resources/es7_2.12-0.1.0.jar" \ No newline at end of file +testParser: "server/src/main/resources/es7_2.12-0.1.1.jar" \ No newline at end of file diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/SourceGear.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/SourceGear.scala index 5222cc7535..e34ac28a9c 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/SourceGear.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/SourceGear.scala @@ -30,6 +30,7 @@ abstract class SourceGear { def findParser(parserRef: ParserRef) = parsers.find(_.languageName == parserRef.languageName) lazy val validExtensions: Set[String] = parsers.flatMap(_.fileExtensions) + lazy val excludedPaths: Seq[String] = parsers.flatMap(_.excludedPaths).toSeq def parseFile(file: File) (implicit project: OpticProject) : Try[FileParseResults] = Try(file.contentAsString).flatMap(i=> parseString(i)) diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/OpticProject.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/OpticProject.scala index 4cf55c2225..e414dced0f 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/OpticProject.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/OpticProject.scala @@ -13,7 +13,7 @@ import com.opticdev.core.sourcegear.SourceGear import com.opticdev.core.sourcegear.actors.ActorCluster import com.opticdev.core.sourcegear.graph.{ProjectGraph, ProjectGraphWrapper} import com.opticdev.core.sourcegear.project.config.ProjectFile -import com.opticdev.core.sourcegear.project.monitoring.{FileStateMonitor} +import com.opticdev.core.sourcegear.project.monitoring.{FileStateMonitor, ShouldWatch} import com.opticdev.core.sourcegear.project.status.ProjectStatus import com.opticdev.opm.providers.ProjectKnowledgeSearchPaths import net.jcazevedo.moultingyaml.YamlString @@ -37,7 +37,6 @@ abstract class OpticProject(val name: String, val baseDirectory: File)(implicit val projectStatus = projectStatusInstance.immutable def projectInfo : ProjectInfo = ProjectInfo(name, baseDirectory.pathAsString, projectStatus) - /* Normal Disk Monitoring */ val watcher: ActorRef = baseDirectory.newWatcher(recursive = true) @@ -95,7 +94,7 @@ abstract class OpticProject(val name: String, val baseDirectory: File)(implicit implicit val sourceGear = projectSourcegear projectStatusInstance.touch filesStateMonitor.markUpdated(file) - if (file === projectFile.file) { + if (file.isSameFileAs(projectFile.file)) { projectFile.reload } else { if (shouldWatchFile(file)) projectActor ! FileUpdated(file, this) @@ -131,12 +130,11 @@ abstract class OpticProject(val name: String, val baseDirectory: File)(implicit } /* Control logic for watching files */ - - def shouldWatchFile(file: File) : Boolean = { - file.isRegularFile && - file.extension.isDefined && - projectSourcegear.validExtensions.contains(file.extension.get) - } + //@todo profile this. Seems liable to cause big slowdowns with many files running through it + def shouldWatchFile(file: File) : Boolean = + ShouldWatch.file(file, + projectSourcegear.validExtensions, + projectFile.interface.get.exclude.value.map(i=> File(i.value)) ++ projectSourcegear.excludedPaths.map(i=> File(i))) def filesToWatch : Set[File] = baseDirectory.listRecursively.toVector.filter(shouldWatchFile).toSet } diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/Project.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/Project.scala index 7b75a4a055..acf562a321 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/Project.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/Project.scala @@ -14,6 +14,7 @@ import scala.util.Try class Project(name: String, baseDirectory: File)(implicit logToCli: Boolean = false, actorCluster: ActorCluster) extends OpticProject(name, baseDirectory) { private var sourceGear: SourceGear = { + projectStatusInstance.sourceGearStatus = Building projectFileChanged(projectFile) SourceGear.unloaded } @@ -23,6 +24,7 @@ class Project(name: String, baseDirectory: File)(implicit logToCli: Boolean = fa override def projectFileChanged(newPf: ProjectFile): Unit = { super.projectFileChanged(newPf) if (newPf.interface.isSuccess) { + projectStatusInstance.sourceGearStatus = Building SGConstructor.fromProjectFile(newPf).onComplete(i => { if (i.isSuccess) { sourceGear = i.get.inflate diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/config/ProjectFile.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/config/ProjectFile.scala index e76be93703..affba413b4 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/config/ProjectFile.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/config/ProjectFile.scala @@ -46,11 +46,11 @@ class ProjectFile(val file: File, createIfDoesNotExist : Boolean = true, onChang val knowledgePaths =PFListInterface.forKey[YamlString]("knowledge_paths", YamlArray(Vector()), yaml) - val ignored_files =PFListInterface.forKey[YamlString]("ignored_files", YamlArray(Vector()), yaml) + val exclude =PFListInterface.forKey[YamlString]("exclude", YamlArray(Vector()), yaml) lastHash = Crypto.createSha1(contents) // val relationships =PFListInterface.forKey[YamlString]("relationships", YamlArray(Vector()), yaml) - PFRootInterface(name, parsers, knowledge, knowledgePaths, ignored_files) + PFRootInterface(name, parsers, knowledge, knowledgePaths, exclude) } def interface = interfaceStore @@ -74,7 +74,7 @@ class ProjectFile(val file: File, createIfDoesNotExist : Boolean = true, onChang YamlString("parsers") -> interface.get.parsers.yamlValue, YamlString("knowledge") -> interface.get.knowledge.yamlValue, YamlString("knowledge_paths") -> interface.get.knowledgePaths.yamlValue, - YamlString("ignored_files") -> interface.get.ignored_files.yamlValue + YamlString("exclude") -> interface.get.exclude.yamlValue // YamlString("relationships") -> relationships.yamlValue ) diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/config/ProjectFileInterface.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/config/ProjectFileInterface.scala index 34e0445bb0..44086716f5 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/config/ProjectFileInterface.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/config/ProjectFileInterface.scala @@ -9,11 +9,11 @@ trait PFInterface { } case class PFRootInterface( - name: PFFieldInterface[YamlString], - parsers: PFListInterface[YamlString], - knowledge: PFListInterface[YamlString], - knowledgePaths: PFListInterface[YamlString], - ignored_files: PFListInterface[YamlString] + name: PFFieldInterface[YamlString], + parsers: PFListInterface[YamlString], + knowledge: PFListInterface[YamlString], + knowledgePaths: PFListInterface[YamlString], + exclude: PFListInterface[YamlString] ) case class PFListInterface[T <: YamlValue](initialValue: List[T]) extends PFInterface { diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/monitoring/ShouldWatch.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/monitoring/ShouldWatch.scala new file mode 100644 index 0000000000..1ec7043a1d --- /dev/null +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/monitoring/ShouldWatch.scala @@ -0,0 +1,26 @@ +package com.opticdev.core.sourcegear.project.monitoring + +import better.files.File +import com.opticdev.core.sourcegear.SourceGear + +object ShouldWatch { + def file(file: File, validExtensions: Set[String], excludedPaths: Seq[File]) : Boolean = { + val isValidFile = + file.isRegularFile && + file.extension.isDefined && + validExtensions.contains(file.extension.get) + + if (isValidFile) { + + excludedPaths.filter(_.exists).foldLeft(true) { + case (bool, path) => { + if (!bool) bool else { + !( path.isSameFileAs(file) || path.isParentOf(file)) + } + } + } + + } else false + + } +} diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/status/ImmutableProjectStatus.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/status/ImmutableProjectStatus.scala index 539e15ff9a..2107038877 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/status/ImmutableProjectStatus.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/status/ImmutableProjectStatus.scala @@ -1,6 +1,6 @@ package com.opticdev.core.sourcegear.project.status -import play.api.libs.json.{JsObject, JsString} +import play.api.libs.json.{JsArray, JsBoolean, JsObject, JsString} class ImmutableProjectStatus(projectStatus: ProjectStatus) { @@ -16,11 +16,14 @@ class ImmutableProjectStatus(projectStatus: ProjectStatus) { def monitoringChanged(callback: (MonitoringStatus)=> Unit) = projectStatus.monitoringChanged(callback) def configChanged(callback: (ConfigStatus)=> Unit) = projectStatus.configChanged(callback) def firstPassChanged(callback: (FirstPassStatus)=> Unit) = projectStatus.firstPassChanged(callback) + def statusChanged(callback: (ProjectStatusCase, ImmutableProjectStatus)=> Unit) = projectStatus.statusChanged(callback) def isValid: Boolean = projectStatus.isValid + def isLoading: Boolean = projectStatus.isLoading def isReady: Boolean = projectStatus.isReady def asJson = { + val errors = errorsAsJson JsObject(Seq( "loaded" -> JsString(loadedStatus.toString), "sourcegear" -> JsString(sourceGearStatus.toString), @@ -28,7 +31,31 @@ class ImmutableProjectStatus(projectStatus: ProjectStatus) { "config" -> JsString(configStatus.toString), "firstPass" -> JsString(firstPassStatus.toString), "lastUpdate" -> JsString(lastUpdate.time.toString), + + "isValid"-> JsBoolean(isValid), + "isLoading"-> JsBoolean(isLoading), + "hasErrors"-> JsBoolean(errors.value.nonEmpty), + "errors" -> errors )) } + def errorsAsJson : JsArray = { + var seq = Seq[JsString]() + + configStatus match { + case config: InvalidConfig => + seq = seq :+ JsString(config.error) + case _ => + } + + sourceGearStatus match { + case status: Invalid => + seq = seq :+ JsString(status.error) + case _ => + } + + JsArray(seq) + } + + override def toString: String = asJson.toString() } \ No newline at end of file diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/status/ProjectStatus.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/status/ProjectStatus.scala index 23f0b15659..c8671b2c91 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/status/ProjectStatus.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/status/ProjectStatus.scala @@ -56,6 +56,7 @@ class ProjectStatus(private var _loadedStatus: LoadedStatusCase = Loaded, def touch : Unit = lastUpdate = LastUpdateDate(Calendar.getInstance().getTime) def isValid: Boolean = configStatus == ValidConfig && sourceGearStatus == Valid + def isLoading: Boolean = configStatus == ValidConfig && sourceGearStatus == Building def isReady: Boolean = isValid && firstPassStatus == Complete private var sgChangedCallbacks = Set[(SourceGearStatus)=> Unit]() @@ -78,12 +79,22 @@ class ProjectStatus(private var _loadedStatus: LoadedStatusCase = Loaded, firstPassChangedCallbacks = firstPassChangedCallbacks + callback } - private def notify(status: ProjectStatusCase) = status match { - case a: SourceGearStatus => sgChangedCallbacks.foreach(i=> i(a)) - case a: MonitoringStatus => monitoringChangedCallbacks.foreach(i=> i(a)) - case a: ConfigStatus => configChangedCallbacks.foreach(i=> i(a)) - case a: FirstPassStatus => firstPassChangedCallbacks.foreach(i=> i(a)) - case _ => + private var statusChangedCallbacks = Set[(ProjectStatusCase, ImmutableProjectStatus)=> Unit]() + def statusChanged(callback: (ProjectStatusCase, ImmutableProjectStatus)=> Unit) = { + statusChangedCallbacks = statusChangedCallbacks + callback + } + + private def notify(status: ProjectStatusCase) = { + //send to specific callbacks + status match { + case a: SourceGearStatus => sgChangedCallbacks.foreach(i=> i(a)) + case a: MonitoringStatus => monitoringChangedCallbacks.foreach(i=> i(a)) + case a: ConfigStatus => configChangedCallbacks.foreach(i=> i(a)) + case a: FirstPassStatus => firstPassChangedCallbacks.foreach(i=> i(a)) + case _ => + } + //send to any status changed callbacks + statusChangedCallbacks.foreach(_.apply(status, immutable)) } lazy val immutable : ImmutableProjectStatus = new ImmutableProjectStatus(this) diff --git a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/status/package.scala b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/status/package.scala index cb49959dd9..9c7a5aa336 100644 --- a/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/status/package.scala +++ b/core/src/main/scala-2.12/com/opticdev/core/sourcegear/project/status/package.scala @@ -13,6 +13,7 @@ package object status { sealed trait SourceGearStatus extends ProjectStatusCase case object Empty extends SourceGearStatus case object Valid extends SourceGearStatus + case object Building extends SourceGearStatus case class Invalid(error: String) extends SourceGearStatus sealed trait MonitoringStatus extends ProjectStatusCase diff --git a/core/src/test/scala-2.12/com/opticdev/core/sourcegear/project/ProjectFileSpec.scala b/core/src/test/scala-2.12/com/opticdev/core/sourcegear/project/ProjectFileSpec.scala index 0b9cfa8017..bd338e032b 100644 --- a/core/src/test/scala-2.12/com/opticdev/core/sourcegear/project/ProjectFileSpec.scala +++ b/core/src/test/scala-2.12/com/opticdev/core/sourcegear/project/ProjectFileSpec.scala @@ -63,10 +63,10 @@ class ProjectFileSpec extends TestBase { assert(f.empty.interface.get.knowledgePaths.value.isEmpty) } - it("includes ignored files") { + it("includes excluded files") { val f = fixture - assert(f.defined.interface.get.ignored_files.value.size == 1) - assert(f.empty.interface.get.ignored_files.value.isEmpty) + assert(f.defined.interface.get.exclude.value.size == 1) + assert(f.empty.interface.get.exclude.value.isEmpty) } } @@ -92,7 +92,7 @@ class ProjectFileSpec extends TestBase { //@todo figure out why this TEST won't pass // it("as yaml") { // assert(f.defined.yamlValue.prettyPrint == -//// "name: new name\nknowledge_paths:\n- /docs\nignored_files:\n- node_modules/\nknowledge:\n- js:express\nparsers:\n- JavaScript") +//// "name: new name\nknowledge_paths:\n- /docs\nexclude:\n- node_modules/\nknowledge:\n- js:express\nparsers:\n- JavaScript") // } it("to a file") { diff --git a/core/src/test/scala-2.12/com/opticdev/core/sourcegear/project/ProjectSpec.scala b/core/src/test/scala-2.12/com/opticdev/core/sourcegear/project/ProjectSpec.scala index 8415848fef..73bc424832 100644 --- a/core/src/test/scala-2.12/com/opticdev/core/sourcegear/project/ProjectSpec.scala +++ b/core/src/test/scala-2.12/com/opticdev/core/sourcegear/project/ProjectSpec.scala @@ -118,7 +118,7 @@ class ProjectSpec extends AkkaTestFixture("ProjectTest") with GearUtils with Eve assert(status.firstPassStatus == NotStarted) assert(status.configStatus == ValidConfig) assert(status.monitoringStatus == NotWatching) - assert(status.sourceGearStatus == Empty) + assert(status.sourceGearStatus == Building) } it("creates a sourcegear instance from config") { diff --git a/core/src/test/scala-2.12/com/opticdev/core/sourcegear/project/monitoring/ShouldWatchSpec.scala b/core/src/test/scala-2.12/com/opticdev/core/sourcegear/project/monitoring/ShouldWatchSpec.scala new file mode 100644 index 0000000000..cf9000447e --- /dev/null +++ b/core/src/test/scala-2.12/com/opticdev/core/sourcegear/project/monitoring/ShouldWatchSpec.scala @@ -0,0 +1,36 @@ +package com.opticdev.core.sourcegear.project.monitoring + +import better.files.File +import com.opticdev.core.Fixture.TestBase +import com.opticdev.core.sourcegear.{GearSet, SourceGear} +import com.opticdev.parsers.{ParserBase, SourceParserManager} + +class ShouldWatchSpec extends TestBase { + + val validExtensions = Set(".js") + val excludedTestPaths = Seq( + File("test-examples/resources/example_source"), + File("test-examples/resources/example_source/LocationPlayground.js") + ) + + it("will accept files with the right extensions") { + assert(ShouldWatch.file(File("test-examples/resources/test_project/app.js"), validExtensions, excludedTestPaths)) + } + + it("will reject files that are children of an excluded path") { + assert(!ShouldWatch.file(File("test-examples/resources/example_source/ImportSource.js"), validExtensions, excludedTestPaths)) + } + + it("will reject files that are excluded explicitly") { + assert(!ShouldWatch.file(File("test-examples/resources/example_source/LocationPlayground.js"), validExtensions, excludedTestPaths)) + } + + it("will reject files with the wrong extensions") { + assert(!ShouldWatch.file(File("build.sbt"), validExtensions, excludedTestPaths)) + } + + it("will reject files that don't exist") { + assert(!ShouldWatch.file(File("notREAL.js"), validExtensions, excludedTestPaths)) + } + +} \ No newline at end of file diff --git a/core/src/test/scala-2.12/com/opticdev/core/sourcegear/project/status/ProjectStatusSpec.scala b/core/src/test/scala-2.12/com/opticdev/core/sourcegear/project/status/ProjectStatusSpec.scala index c5da4293f0..b8019c99ec 100644 --- a/core/src/test/scala-2.12/com/opticdev/core/sourcegear/project/status/ProjectStatusSpec.scala +++ b/core/src/test/scala-2.12/com/opticdev/core/sourcegear/project/status/ProjectStatusSpec.scala @@ -38,11 +38,12 @@ class ProjectStatusSpec extends FunSpec { } describe("callbacks") { - val projectStatus = new ProjectStatus() describe("register") { it("for sourcegear") { + val projectStatus = new ProjectStatus() + var didRun = false projectStatus.sourcegearChanged((a) => { didRun = true @@ -52,6 +53,8 @@ class ProjectStatusSpec extends FunSpec { } it("for file monitoring") { + val projectStatus = new ProjectStatus() + var didRun = false projectStatus.monitoringChanged((a) => { didRun = true @@ -61,6 +64,8 @@ class ProjectStatusSpec extends FunSpec { } it("for config") { + val projectStatus = new ProjectStatus() + var didRun = false projectStatus.configChanged((a) => { didRun = true @@ -70,6 +75,8 @@ class ProjectStatusSpec extends FunSpec { } it("for firstpass") { + val projectStatus = new ProjectStatus() + var didRun = false projectStatus.firstPassChanged((a) => { didRun = true @@ -78,6 +85,17 @@ class ProjectStatusSpec extends FunSpec { assert(didRun) } + it("for any") { + val projectStatus = new ProjectStatus() + + var didRun = false + projectStatus.statusChanged((a, s) => { + didRun = true + }) + projectStatus.firstPassStatus = Complete + assert(didRun) + } + } it("will not be called if no actual change") { diff --git a/opm/src/main/scala-2.12/com/opticdev/opm/packages/OpticMDPackage.scala b/opm/src/main/scala-2.12/com/opticdev/opm/packages/OpticMDPackage.scala index ade6c6e4c8..8ca4131bc4 100644 --- a/opm/src/main/scala-2.12/com/opticdev/opm/packages/OpticMDPackage.scala +++ b/opm/src/main/scala-2.12/com/opticdev/opm/packages/OpticMDPackage.scala @@ -2,12 +2,12 @@ package com.opticdev.opm.packages import better.files.File import com.opticdev.common.PackageRef -import com.opticdev.sdk.MarkdownParser import com.opticdev.sdk.descriptions._ import play.api.libs.json.{JsArray, JsObject, JsString, JsValue} import scala.util.Try import com.opticdev.opm.helpers.MDPackageResolveHelper._ +import com.opticdev.sdk.markdown.MarkdownParser case class OpticMDPackage(description: JsObject, dependencyMapping: DependencyMapping) extends OpticPackage{ private def objectValueForKey(key: String): Map[String, JsObject] = { diff --git a/opm/src/main/scala-2.12/com/opticdev/opm/packages/package.scala b/opm/src/main/scala-2.12/com/opticdev/opm/packages/package.scala index 61ad2b1fd6..e6278b46f1 100644 --- a/opm/src/main/scala-2.12/com/opticdev/opm/packages/package.scala +++ b/opm/src/main/scala-2.12/com/opticdev/opm/packages/package.scala @@ -2,7 +2,7 @@ package com.opticdev.opm.packages import better.files.File import com.opticdev.common.PackageRef -import com.opticdev.sdk.MarkdownParser +import com.opticdev.sdk.markdown.MarkdownParser import play.api.libs.json.{JsArray, JsObject, JsString, JsValue} import scala.util.{Success, Try} diff --git a/opm/src/test/scala-2.12/com/opticdev/opm/providers/LocalProviderSpec.scala b/opm/src/test/scala-2.12/com/opticdev/opm/providers/LocalProviderSpec.scala index 85c5f8ff6f..ab09ab2719 100644 --- a/opm/src/test/scala-2.12/com/opticdev/opm/providers/LocalProviderSpec.scala +++ b/opm/src/test/scala-2.12/com/opticdev/opm/providers/LocalProviderSpec.scala @@ -19,12 +19,12 @@ class LocalProviderSpec extends FunSpec { } it("can resolve a local package") { - val r = Await.result(localProvider.resolvePackages(PackageRef("optic:rest")), 5 seconds) + val r = Await.result(localProvider.resolvePackages(PackageRef("optic:rest")), 10 seconds) assert(r.foundAll) } it("will not resolve a non-existent local package") { - val r = Await.result(localProvider.resolvePackages(PackageRef("optic:dadasd")), 5 seconds) + val r = Await.result(localProvider.resolvePackages(PackageRef("optic:dadasd")), 10 seconds) assert(!r.foundAll) } diff --git a/opm/src/test/scala-2.12/com/opticdev/opm/storage/ParserStorageSpec.scala b/opm/src/test/scala-2.12/com/opticdev/opm/storage/ParserStorageSpec.scala index 0a17dfe2fa..42fa95f93c 100644 --- a/opm/src/test/scala-2.12/com/opticdev/opm/storage/ParserStorageSpec.scala +++ b/opm/src/test/scala-2.12/com/opticdev/opm/storage/ParserStorageSpec.scala @@ -7,39 +7,38 @@ import org.scalatest.FunSpec class ParserStorageSpec extends FunSpec { - lazy val fakeParserJar = File("test-examples/resources/example_parsers/fake-parser-0.1.0.jar") + lazy val parserJar = File("server/src/main/resources/es7_2.12-0.1.1.jar") it("can save parsers to local") { - val parserSaved = ParserStorage.writeToStorage(fakeParserJar) - println(parserSaved) + val parserSaved = ParserStorage.writeToStorage(parserJar) assert(parserSaved.isSuccess) } it("can lookup items from local") { - val parserLoad = ParserStorage.loadFromStorage(ParserRef("Fake", "0.1.0")) + val parserLoad = ParserStorage.loadFromStorage(ParserRef("es7", "0.1.1")) assert(parserLoad.isSuccess) - assert(parserLoad.get.languageName == "Fake") + assert(parserLoad.get.languageName == "es7") } it("can load parser by version 'latest'") { - val parserLoad = ParserStorage.loadFromStorage(ParserRef("Fake", "latest")) + val parserLoad = ParserStorage.loadFromStorage(ParserRef("es7", "latest")) assert(parserLoad.isSuccess) - assert(parserLoad.get.languageName == "Fake") - assert(parserLoad.get.parserVersion == "0.1.0") + assert(parserLoad.get.languageName == "es7") + assert(parserLoad.get.parserVersion == "0.1.1") } it("can clear all local parsers") { - ParserStorage.writeToStorage(fakeParserJar) + ParserStorage.writeToStorage(parserJar) ParserStorage.clearLocalParsers assert(ParserStorage.listAllParsers.isEmpty) } it("can load all parsers") { ParserStorage.clearLocalParsers - ParserStorage.writeToStorage(fakeParserJar) + ParserStorage.writeToStorage(parserJar) val listAll = ParserStorage.listAllParsers .mapValues(_.map(_.parserRef)) - assert(listAll == Map("Fake" -> Vector(ParserRef("Fake", "0.1.0")))) + assert(listAll == Map("es7" -> Vector(ParserRef("es7", "0.1.1")))) } } diff --git a/project/Constants.scala b/project/Constants.scala new file mode 100644 index 0000000000..29f031ecb0 --- /dev/null +++ b/project/Constants.scala @@ -0,0 +1,6 @@ + +object Constants { + val opticMDVersion = "0.1.2" + val opticMDTar = s"https://registry.npmjs.org/optic-markdown/-/optic-markdown-${opticMDVersion}.tgz" + val opticMDTarSum = "764f6cf924fdd8a899e26b8eb982d3a497878656" +} diff --git a/project/dependencies.scala b/project/dependencies.scala index ce1d920b1d..b3a657797e 100644 --- a/project/dependencies.scala +++ b/project/dependencies.scala @@ -8,7 +8,7 @@ object Dependencies { "org.scalactic" %% "scalactic" % "3.0.1", "org.scalatest" %% "scalatest" % "3.0.1" % "test", - "com.opticdev" %% "parser-foundation" % "0.1.0", + "com.opticdev" %% "parser-foundation" % "0.1.1", //file library "com.github.pathikrit" % "better-files_2.12" % "2.17.1", @@ -32,7 +32,8 @@ object Dependencies { val sdkDependencies: Seq[ModuleID] = sharedDependencies ++ Seq( "com.typesafe.play" %% "play-json" % "2.6.2", "com.github.fge" % "json-schema-validator" % "2.2.6", - "org.gnieh" %% "diffson-play-json" % "2.2.1" + "org.gnieh" %% "diffson-play-json" % "2.2.1", + "org.apache.commons" % "commons-compress" % "1.16.1" ) val serverDependencies : Seq[ModuleID] = sharedDependencies ++ Seq( diff --git a/project/plugins.sbt b/project/plugins.sbt index 442c8a4933..b26c76edbb 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,4 +2,4 @@ logLevel := Level.Warn addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.6") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.1") -addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.3") +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.8.0") diff --git a/sdk/src/main/scala-2.12/com.opticdev/sdk/MarkdownParser.scala b/sdk/src/main/scala-2.12/com.opticdev/sdk/markdown/MarkdownParser.scala similarity index 58% rename from sdk/src/main/scala-2.12/com.opticdev/sdk/MarkdownParser.scala rename to sdk/src/main/scala-2.12/com.opticdev/sdk/markdown/MarkdownParser.scala index 3665ccbad1..2a55e61c5c 100644 --- a/sdk/src/main/scala-2.12/com.opticdev/sdk/MarkdownParser.scala +++ b/sdk/src/main/scala-2.12/com.opticdev/sdk/markdown/MarkdownParser.scala @@ -1,11 +1,11 @@ -package com.opticdev.sdk +package com.opticdev.sdk.markdown import better.files.File import com.opticdev.sdk.descriptions.Schema import play.api.libs.json.{JsArray, JsObject, Json} +import scala.sys.process._ import scala.util.Try -import sys.process._ object MarkdownParser { @@ -29,22 +29,16 @@ object MarkdownParser { def noErrors = errors.value.isEmpty } - private val pathToExecutable = "/usr/local/bin/node /usr/local/lib/node_modules/optic-markdown/lib/cli.js" - def parseMarkdown(file: File) = Try[MDParseOutput] { - //check the version eventually - val version = pathToExecutable+ " --version" !! - - if (version.contains("not found")) throw new Error("optic-md is not installed. Run npm install optic-md -g") - - val result = pathToExecutable+ " " + file.pathAsString !! - - val parsedJson = Try(Json.parse(result)) - - if (parsedJson.isFailure || !outputSchema.validate(parsedJson.get)) { - throw new Error("Invalid output from markdown parser") - } else - MDParseOutput(parsedJson.get.as[JsObject]) + def parseMarkdown(file: File) : Try[MDParseOutput] = { + OpticMarkdownInstaller.getOrInstall + .map(i=> { + val result = i.parseFile(file.pathAsString) + if (!outputSchema.validate(result)) { + throw new Error("Invalid output from markdown parser") + } else + MDParseOutput(result) + }) } } diff --git a/sdk/src/main/scala-2.12/com.opticdev/sdk/markdown/OpticMarkdownInstaller.scala b/sdk/src/main/scala-2.12/com.opticdev/sdk/markdown/OpticMarkdownInstaller.scala new file mode 100644 index 0000000000..7831e21ad3 --- /dev/null +++ b/sdk/src/main/scala-2.12/com.opticdev/sdk/markdown/OpticMarkdownInstaller.scala @@ -0,0 +1,99 @@ +package com.opticdev.sdk.markdown + +import better.files.File +import com.opticdev.common.storage.DataDirectory +import com.opticdev.sdk.BuildInfo +import com.opticdev.common.PlatformConstants +import play.api.libs.json.{JsObject, Json} + +import scala.util.{Failure, Success, Try} +import sys.process._ + +object OpticMarkdownInstaller { + def getOrInstall : Try[CallOpticMarkdown.type] = { + synchronized { + if (CallOpticMarkdown.isInstalled) { + Success(CallOpticMarkdown) + } else { + download(BuildInfo.opticMDTar, BuildInfo.opticMDTarSum) + .flatMap(unzip) + .flatMap(npmInstall) match { + case Success(a) => Success(CallOpticMarkdown) + case Failure(f) => { + cleanupOnFail + Failure(f) + } + } + } + } + } + + protected case object CallOpticMarkdown { + + val script = DataDirectory.bin / "optic-markdown" / "lib" / "cli.js" + + def isInstalled : Boolean = Try { + val cmd = Seq(PlatformConstants.nodePath, script.pathAsString, "--version") + val result = cmd.!!(ProcessLogger(stdout append _, stderr append _)) + } match { + case Success(a) => true + case Failure(a) => false + } + + def parseFile(filePath: String) : JsObject = { + val cmd = Seq(PlatformConstants.nodePath, script.pathAsString, filePath) + val result = cmd.!!(ProcessLogger(stdout append _, stderr append _)) + Json.parse(result).as[JsObject] + } + +// def parseString : JsObject = { +// +// } + } + + private def download(opticMDTar: String, opticMDTarSum: String) : Try[File] = Try { + import java.net.URL + + val target = DataDirectory.bin / "optic-markdown.tgz" + + target.delete(true) + + //will throw if doesn't work + new URL(opticMDTar) #> target.toJava !!(ProcessLogger(stdout append _, stderr append _)) + + assert(target.sha1.toLowerCase == opticMDTarSum.toLowerCase, "optic-markdown sha does not match. Download failed") + + target + } + + private def unzip(file: File) : Try[File] = Try { + val mdDirectory: File = DataDirectory.bin / "optic-markdown" + val unzip: Seq[String] = Seq( + "tar", + "-xzf", + file.pathAsString, + "-C", + DataDirectory.bin.pathAsString, + ) + + unzip.!!(ProcessLogger(stdout append _, stderr append _)) + + val mv: Seq[String] = Seq( + "mv", + (DataDirectory.bin / "package").pathAsString, + mdDirectory.pathAsString + ) + mv.!!(ProcessLogger(stdout append _, stderr append _)) + + file.delete(true) + assert(mdDirectory.isDirectory, "Optic Markdown could not be unzipped.") + mdDirectory + } + + private def npmInstall(directory: File) : Try[Unit] = Try { + val process = sys.process.Process(Seq("npm", "install"), directory.toJava) + process.!(ProcessLogger(stdout append _, stderr append _)) + } + + private def cleanupOnFail = DataDirectory.bin.list.foreach(_.delete(true)) +} diff --git a/sdk/src/test/scala-2.12/com/opticdev/sdk/MarkdownParserSpec.scala b/sdk/src/test/scala-2.12/com/opticdev/sdk/markdown/MarkdownParserSpec.scala similarity index 93% rename from sdk/src/test/scala-2.12/com/opticdev/sdk/MarkdownParserSpec.scala rename to sdk/src/test/scala-2.12/com/opticdev/sdk/markdown/MarkdownParserSpec.scala index 7382c8353c..763cd800c0 100644 --- a/sdk/src/test/scala-2.12/com/opticdev/sdk/MarkdownParserSpec.scala +++ b/sdk/src/test/scala-2.12/com/opticdev/sdk/markdown/MarkdownParserSpec.scala @@ -1,4 +1,4 @@ -package com.opticdev.sdk +package com.opticdev.sdk.markdown import better.files.File import org.scalatest.FunSpec diff --git a/sdk/src/test/scala-2.12/com/opticdev/sdk/markdown/OpticMarkdownInstallerSpec.scala b/sdk/src/test/scala-2.12/com/opticdev/sdk/markdown/OpticMarkdownInstallerSpec.scala new file mode 100644 index 0000000000..46c20abc83 --- /dev/null +++ b/sdk/src/test/scala-2.12/com/opticdev/sdk/markdown/OpticMarkdownInstallerSpec.scala @@ -0,0 +1,126 @@ +package com.opticdev.sdk.markdown + +import akka.dispatch.Futures +import better.files.File +import com.opticdev.common.storage.DataDirectory +import org.scalatest.concurrent.Eventually +import org.scalatest.time.{Seconds, Span} +import org.scalatest.{BeforeAndAfterEach, FunSpec, PrivateMethodTester} +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.{Await, Future} +import scala.concurrent.duration._ +import scala.util.Try + +/* +This test is long running. It's really important to cover all these cases and make sure its threadsafe. + */ + +class OpticMarkdownInstallerSpec extends FunSpec with PrivateMethodTester with BeforeAndAfterEach with Eventually { + + override protected def beforeEach(): Unit = { + DataDirectory.bin.list.foreach(_.delete(true)) + } + + def download(tar: String, sum: String) = { + val download = PrivateMethod[Try[File]]('download) + OpticMarkdownInstaller invokePrivate download(tar, sum) + } + + describe("download") { + + it("works with valid url & checksum") { + val result = download( + "https://registry.npmjs.org/optic-markdown/-/optic-markdown-0.1.2.tgz", + "764f6cf924fdd8a899e26b8eb982d3a497878656" + ) + assert(result.isSuccess) + } + + it("fails if url is invalid") { + val result = download( + "NOT REAL", + "764f6cf924fdd8a899e26b8eb982d3a497878656" + ) + assert(result.isFailure) + } + + it("fails if checksum is wrong") { + val result = download( + "https://registry.npmjs.org/optic-markdown/-/optic-markdown-0.1.2.tgz", + "FAKE_WRONG" + ) + assert(result.isFailure) + } + + } + + def unzip(file: File) = { + val download = PrivateMethod[Try[File]]('unzip) + OpticMarkdownInstaller invokePrivate download(file) + } + + describe("unzip") { + + it("works with valid file piped in") { + val downloadResult = download( + "https://registry.npmjs.org/optic-markdown/-/optic-markdown-0.1.2.tgz", + "764f6cf924fdd8a899e26b8eb982d3a497878656" + ) + + val unzipResult = downloadResult.map(i=> unzip(i)).flatten + + assert(DataDirectory.bin.list.size == 1) + assert(unzipResult.get == (DataDirectory.bin / "optic-markdown")) + + } + + } + + def npmInstall(file: File) = { + val download = PrivateMethod[Try[File]]('npmInstall) + OpticMarkdownInstaller invokePrivate download(file) + } + + it("npm install works") { + val unzippedDirectory = download( + "https://registry.npmjs.org/optic-markdown/-/optic-markdown-0.1.2.tgz", + "764f6cf924fdd8a899e26b8eb982d3a497878656" + ).map(unzip).flatten + + val result = npmInstall(unzippedDirectory.get) + + assert(result.isSuccess) + + } + + describe("Get or install") { + + it("will install instance when none exists") { + assert(OpticMarkdownInstaller.getOrInstall.isSuccess) + } + + it("will get the instance that is already installed next time") { + + assert(OpticMarkdownInstaller.getOrInstall.isSuccess) + + eventually (timeout(Span(5, Seconds))) { + assert(OpticMarkdownInstaller.getOrInstall.isSuccess) + } + + } + + it("is thread safe") { + + val a = Future(OpticMarkdownInstaller.getOrInstall.isSuccess) + val b = Future(OpticMarkdownInstaller.getOrInstall.isSuccess) + val c = Future(OpticMarkdownInstaller.getOrInstall.isSuccess) + + + val result = Await.result(Future.sequence(Seq(a,b,c)), 1 minute) + assert(result.forall(_ == true)) + } + + + } + +} diff --git a/server/src/main/resources/es7_2.12-0.1.0.jar b/server/src/main/resources/es7_2.12-0.1.1.jar similarity index 88% rename from server/src/main/resources/es7_2.12-0.1.0.jar rename to server/src/main/resources/es7_2.12-0.1.1.jar index a002344a11..c3e3232b89 100644 Binary files a/server/src/main/resources/es7_2.12-0.1.0.jar and b/server/src/main/resources/es7_2.12-0.1.1.jar differ diff --git a/server/src/main/scala-2.12/com/opticdev/server/http/Lifecycle.scala b/server/src/main/scala-2.12/com/opticdev/server/http/Lifecycle.scala index ad7d5dd7d1..8cd321eecb 100644 --- a/server/src/main/scala-2.12/com/opticdev/server/http/Lifecycle.scala +++ b/server/src/main/scala-2.12/com/opticdev/server/http/Lifecycle.scala @@ -3,25 +3,32 @@ package com.opticdev.server.http import java.io.{BufferedReader, InputStreamReader} import better.files.File +import com.opticdev.common.storage.DataDirectory import com.opticdev.core.sourcegear.project.Project import com.opticdev.core.sourcegear.project.config.ProjectFile import com.opticdev.opm.PackageManager import com.opticdev.opm.providers.LocalProvider import com.opticdev.opm.storage.{PackageStorage, ParserStorage} import com.opticdev.parsers.SourceParserManager +import com.opticdev.sdk.markdown.OpticMarkdownInstaller import com.opticdev.server.state.ProjectsManager import scala.io.Source import scala.util.Try + object Lifecycle extends App { + + //init the data directory if missing + DataDirectory.init + //@todo load parsers dynamically. - val jar = this.getClass.getClassLoader.getResource("es7_2.12-0.1.0.jar").getFile + val jar = this.getClass.getClassLoader.getResource("es7_2.12-0.1.1.jar").getFile val parserBase = Try { ParserStorage.writeToStorage(File(jar)) SourceParserManager.installParser(jar).get }.getOrElse { val withoutFileExtension = jar.substring(5) - val pathAsString = (File(withoutFileExtension).parent.parent / "es7_2.12-0.1.0.jar").pathAsString + val pathAsString = (File(withoutFileExtension).parent.parent / "es7_2.12-0.1.1.jar").pathAsString ParserStorage.writeToStorage(File(pathAsString)) SourceParserManager.installParser(pathAsString).get } @@ -32,13 +39,13 @@ object Lifecycle extends App { implicit val projectsManager: ProjectsManager = new ProjectsManager() implicit val actorCluster = projectsManager.actorCluster - //manually adding projects for testing -// val project = Project.fromProjectFile(new ProjectFile(File("test-examples/resources/tmp/test_project/optic.yaml"))).get -// projectsManager.loadProject(project) startup def startup = { Server.start() + + //tap the OpticMarkdown Installer in case this is a fresh install + OpticMarkdownInstaller.getOrInstall } diff --git a/server/src/main/scala-2.12/com/opticdev/server/http/routes/socket/agents/AgentConnectionActor.scala b/server/src/main/scala-2.12/com/opticdev/server/http/routes/socket/agents/AgentConnectionActor.scala index ce53d1918e..7b1ebc9d2c 100644 --- a/server/src/main/scala-2.12/com/opticdev/server/http/routes/socket/agents/AgentConnectionActor.scala +++ b/server/src/main/scala-2.12/com/opticdev/server/http/routes/socket/agents/AgentConnectionActor.scala @@ -19,8 +19,10 @@ class AgentConnectionActor(slug: String, projectsManager: ProjectsManager) exten override def receive: Receive = { case Registered(actorRef) => { - println("worked correctly") +// println("worked correctly") connection = actorRef + //trigger a status update to send to Agent + projectsManager.sendMostRecentUpdate } case Terminated => { Status.Success(Unit) @@ -32,14 +34,6 @@ class AgentConnectionActor(slug: String, projectsManager: ProjectsManager) exten connection ! ErrorResponse("Invalid Request") } - case contextUpdate: ContextFound => { - connection ! contextUpdate - } - - case searchResults: SearchResults => { - connection ! searchResults - } - case search: AgentSearch => { ArrowQuery(search, projectsManager.lastProjectName)(projectsManager).executeToApiResponse.foreach(i=> { AgentConnection.broadcastUpdate( SearchResults(search.query, i.data, ignoreQueryUpdate = true) ) @@ -79,6 +73,10 @@ class AgentConnectionActor(slug: String, projectsManager: ProjectsManager) exten }) } + case updateAgentEvent: UpdateAgentEvent => { + connection ! updateAgentEvent + } + } } diff --git a/server/src/main/scala-2.12/com/opticdev/server/http/routes/socket/agents/package.scala b/server/src/main/scala-2.12/com/opticdev/server/http/routes/socket/agents/package.scala index a08b1596fe..ecf0199707 100644 --- a/server/src/main/scala-2.12/com/opticdev/server/http/routes/socket/agents/package.scala +++ b/server/src/main/scala-2.12/com/opticdev/server/http/routes/socket/agents/package.scala @@ -3,8 +3,10 @@ package com.opticdev.server.http.routes.socket import akka.actor.ActorRef import better.files.{File, Files} import com.opticdev.arrow.changes.ChangeGroup +import com.opticdev.core.sourcegear.project.status.ImmutableProjectStatus import play.api.libs.json._ import com.opticdev.server.data.ToJsonImplicits._ +import com.opticdev.server.http.routes.socket.agents.Protocol.UpdateAgentEvent import com.opticdev.server.http.routes.socket.editors.Protocol.EditorEvents package object agents { @@ -21,6 +23,7 @@ package object agents { case class AgentSearch(query: String, lastProjectName: Option[String], file: Option[File], range: Option[Range], contents: Option[String]) extends AgentEvents + //Sends trait UpdateAgentEvent extends OpticEvent case class ContextFound(filePath: String, range: Range, results: JsValue, isError: Boolean = false) extends OpticEvent with UpdateAgentEvent { @@ -51,4 +54,12 @@ package object agents { } + case class StatusUpdate(projectName: String, immutableProjectStatus: ImmutableProjectStatus) extends OpticEvent with UpdateAgentEvent { + def asJson = JsObject(Seq( + "event"-> JsString("status-update"), + "projectName"-> JsString(projectName), + "status"-> immutableProjectStatus.asJson + )) + } + } diff --git a/server/src/main/scala-2.12/com/opticdev/server/state/ProjectsManager.scala b/server/src/main/scala-2.12/com/opticdev/server/state/ProjectsManager.scala index cba4fd3069..b8d3be58f1 100644 --- a/server/src/main/scala-2.12/com/opticdev/server/state/ProjectsManager.scala +++ b/server/src/main/scala-2.12/com/opticdev/server/state/ProjectsManager.scala @@ -8,8 +8,9 @@ import com.opticdev.core.sourcegear.actors.ActorCluster import com.opticdev.server.storage.ServerStorage import com.opticdev.server import com.opticdev.core.actorSystem -import com.opticdev.core.sourcegear.project.status.ProjectStatus +import com.opticdev.core.sourcegear.project.status.{ImmutableProjectStatus, ProjectStatus} import com.opticdev.core.sourcegear.project.{OpticProject, Project, ProjectInfo} +import com.opticdev.server.http.routes.socket.agents.{AgentConnection, StatusUpdate} import scala.collection.mutable import scala.util.{Success, Try} @@ -33,6 +34,15 @@ class ProjectsManager { arrowStore = arrowStore + (project -> new Arrow(project)) }) + //attach a project status changed callback + project.projectStatus.statusChanged((changed, status)=> { + AgentConnection.broadcastUpdate(StatusUpdate(project.name, status)) + }) + + + //send an initial status update + AgentConnection.broadcastUpdate(StatusUpdate(project.name, project.projectStatus)) + projectsStore = projectsStore :+ project arrowStore = arrowStore + (project -> new Arrow(project)) if (projectsStore.size > MAX_PROJECTS) { @@ -125,4 +135,9 @@ class ProjectsManager { private var _lastProjectName : Option[String] = None def lastProjectName = _lastProjectName + def sendMostRecentUpdate = Try { + val project = lookupProject(_lastProjectName.get).get + AgentConnection.broadcastUpdate(StatusUpdate(_lastProjectName.get, project.projectStatus)) + } + } diff --git a/server/src/test/scala-2.12/com/opticdev/server/Scratch.scala b/server/src/test/scala-2.12/com/opticdev/server/Scratch.scala index 708965a9d9..d3148f9700 100644 --- a/server/src/test/scala-2.12/com/opticdev/server/Scratch.scala +++ b/server/src/test/scala-2.12/com/opticdev/server/Scratch.scala @@ -12,7 +12,8 @@ import net.jcazevedo.moultingyaml.YamlString import net.jcazevedo.moultingyaml._ import scala.util.Try - +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.duration._ object Scratch extends TestBase with TestPackageProviders { installProviders @@ -28,9 +29,13 @@ object Scratch extends TestBase with TestPackageProviders { implicit val projectsManager: ProjectsManager = new ProjectsManager() implicit val actorCluster = projectsManager.actorCluster - //manually adding projects for testing - val project = Project.fromProjectFile(new ProjectFile(File("test-examples/resources/tmp/test_project/optic.yaml"))).get - projectsManager.loadProject(project) + + + com.opticdev.core.actorSystem.scheduler.scheduleOnce(5 seconds) { + //manually adding projects for testing + val project = Project.fromProjectFile(new ProjectFile(File("test-examples/resources/tmp/test_project/optic.yaml"))).get + projectsManager.loadProject(project) + } def main(args: Array[String]): Unit = { Server.start() diff --git a/test-examples/resources/example_project_files/empty.yaml b/test-examples/resources/example_project_files/empty.yaml index 207676006f..531a3f1808 100644 --- a/test-examples/resources/example_project_files/empty.yaml +++ b/test-examples/resources/example_project_files/empty.yaml @@ -2,4 +2,4 @@ parsers: knowledge: -ignored_files: +exclude: diff --git a/test-examples/resources/example_project_files/project.yaml b/test-examples/resources/example_project_files/project.yaml index 8152ba90fb..3a7b14b101 100644 --- a/test-examples/resources/example_project_files/project.yaml +++ b/test-examples/resources/example_project_files/project.yaml @@ -8,5 +8,5 @@ knowledge: knowledge_paths: - /docs -ignored_files: +exclude: - node_modules/ \ No newline at end of file diff --git a/test-examples/resources/example_project_files/project2.yaml b/test-examples/resources/example_project_files/project2.yaml index c64bfa87ac..2b918bdde4 100644 --- a/test-examples/resources/example_project_files/project2.yaml +++ b/test-examples/resources/example_project_files/project2.yaml @@ -7,5 +7,5 @@ knowledge: - optic:express-js@0.1.0 - optic:react-js@1.0.0 -ignored_files: +exclude: - node_modules/ \ No newline at end of file diff --git a/test-examples/resources/example_project_files/project3.yaml b/test-examples/resources/example_project_files/project3.yaml index 690bbfb8a8..48ba64810d 100644 --- a/test-examples/resources/example_project_files/project3.yaml +++ b/test-examples/resources/example_project_files/project3.yaml @@ -8,5 +8,5 @@ knowledge: - optic:express-js@0.1.0 - optic:react-js@1.0.0 -ignored_files: +exclude: - node_modules/ \ No newline at end of file diff --git a/test-examples/resources/example_project_files/project4.yaml b/test-examples/resources/example_project_files/project4.yaml index 480282fd5e..4fd4ce6874 100644 --- a/test-examples/resources/example_project_files/project4.yaml +++ b/test-examples/resources/example_project_files/project4.yaml @@ -7,5 +7,5 @@ knowledge: - optic:express-js@0.1.0 - optic:react-js@@@1.0.0 -ignored_files: +exclude: - node_modules/ \ No newline at end of file diff --git a/test-examples/resources/example_source/optic.yaml b/test-examples/resources/example_source/optic.yaml index b7a672a85e..e94112116a 100644 --- a/test-examples/resources/example_source/optic.yaml +++ b/test-examples/resources/example_source/optic.yaml @@ -1,4 +1,4 @@ name: Unnamed Project parsers: [] knowledge: [] -ignored_files: [] +exclude: []