forked from enso-org/developer-docs
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
650 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
project-manager { | ||
server { | ||
host = "0.0.0.0" | ||
port = 30535 | ||
timeout = 10 seconds | ||
} | ||
|
||
storage { | ||
projects-root = ${user.home}/enso | ||
temporary-projects-path = ${project-manager.storage.projects-root}/tmp | ||
local-projects-path = ${project-manager.storage.projects-root}/projects | ||
tutorials-path = ${project-manager.storage.projects-root}/tutorials | ||
tutorials-cache-path = ${project-manager.storage.projects-root}/.tutorials-cache | ||
} | ||
|
||
tutorials { | ||
github-organisation = "luna-packages" | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
project-manager/src/main/scala/org/enso/projectmanager/RouteHelper.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package org.enso.projectmanager | ||
|
||
import java.util.UUID | ||
|
||
import akka.http.scaladsl.model.Uri | ||
import akka.http.scaladsl.model.Uri.Path | ||
import akka.http.scaladsl.server.PathMatcher0 | ||
import akka.http.scaladsl.server.PathMatcher1 | ||
import akka.http.scaladsl.server.PathMatchers.JavaUUID | ||
import org.enso.projectmanager.model.ProjectId | ||
|
||
class RouteHelper { | ||
|
||
val tutorials: String = "tutorials" | ||
val projects: String = "projects" | ||
val thumb: String = "thumb" | ||
|
||
val tutorialsPath: Path = Path / tutorials | ||
val tutorialsPathMatcher: PathMatcher0 = tutorials | ||
|
||
val projectsPath: Path = Path / projects | ||
val projectsPathMatcher: PathMatcher0 = projects | ||
|
||
def projectPath(id: ProjectId): Path = projectsPath / id.toString | ||
|
||
val projectPathMatcher: PathMatcher1[ProjectId] = | ||
(projectsPathMatcher / JavaUUID).map(ProjectId) | ||
|
||
def thumbPath(id: ProjectId): Path = projectPath(id) / thumb | ||
val thumbPathMatcher: PathMatcher1[ProjectId] = projectPathMatcher / thumb | ||
|
||
def uriFor(base: Uri, path: Path): Uri = base.withPath(path) | ||
} |
189 changes: 189 additions & 0 deletions
189
project-manager/src/main/scala/org/enso/projectmanager/Server.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
package org.enso.projectmanager | ||
|
||
import java.io.File | ||
import java.util.UUID | ||
import java.util.concurrent.TimeUnit | ||
|
||
import akka.actor.ActorSystem | ||
import akka.actor.Scheduler | ||
import com.typesafe.config.ConfigFactory | ||
import akka.actor.typed.ActorRef | ||
import akka.actor.typed.scaladsl.adapter._ | ||
import akka.actor.typed.scaladsl.AskPattern._ | ||
import akka.http.scaladsl.Http | ||
import akka.http.scaladsl.model.HttpResponse | ||
import akka.http.scaladsl.model.StatusCodes | ||
import akka.http.scaladsl.model.Uri | ||
import akka.http.scaladsl.server.Directives | ||
import akka.http.scaladsl.server.Route | ||
import akka.stream.ActorMaterializer | ||
import akka.util.Timeout | ||
import org.enso.projectmanager.api.ProjectFactory | ||
import org.enso.projectmanager.api.ProjectJsonSupport | ||
import org.enso.projectmanager.model.Project | ||
import org.enso.projectmanager.model.ProjectId | ||
import org.enso.projectmanager.services.CreateTemporary | ||
import org.enso.projectmanager.services.CreateTemporaryResponse | ||
import org.enso.projectmanager.services.GetProjectById | ||
import org.enso.projectmanager.services.GetProjectResponse | ||
import org.enso.projectmanager.services.ListProjectsRequest | ||
import org.enso.projectmanager.services.ListProjectsResponse | ||
import org.enso.projectmanager.services.ListTutorialsRequest | ||
import org.enso.projectmanager.services.ProjectsCommand | ||
import org.enso.projectmanager.services.ProjectsService | ||
import org.enso.projectmanager.services.StorageManager | ||
import org.enso.projectmanager.services.TutorialsDownloader | ||
|
||
import scala.concurrent.ExecutionContext | ||
import scala.concurrent.Future | ||
import scala.concurrent.duration._ | ||
import scala.util.Failure | ||
import scala.util.Success | ||
|
||
case class Server( | ||
host: String, | ||
port: Int, | ||
repository: ActorRef[ProjectsCommand], | ||
routeHelper: RouteHelper, | ||
apiFactory: ProjectFactory | ||
)(implicit val system: ActorSystem, | ||
implicit val executor: ExecutionContext, | ||
implicit val materializer: ActorMaterializer, | ||
implicit val askTimeout: Timeout) | ||
extends Directives | ||
with ProjectJsonSupport { | ||
|
||
implicit val scheduler: Scheduler = system.scheduler | ||
|
||
def projectDoesNotExistResponse(id: ProjectId): HttpResponse = | ||
HttpResponse(StatusCodes.NotFound, entity = s"Project $id does not exist") | ||
|
||
def thumbDoesNotExistResponse: HttpResponse = | ||
HttpResponse(StatusCodes.NotFound, entity = "Thumbnail does not exist") | ||
|
||
def withSuccess[T]( | ||
fut: Future[T], | ||
errorResponse: HttpResponse = HttpResponse(StatusCodes.InternalServerError) | ||
)(successHandler: T => Route | ||
): Route = { | ||
onComplete(fut) { | ||
case Success(r) => successHandler(r) | ||
case Failure(_) => complete(errorResponse) | ||
} | ||
} | ||
|
||
def withProject(id: ProjectId)(route: Project => Route): Route = { | ||
val projectFuture = | ||
repository | ||
.ask( | ||
(ref: ActorRef[GetProjectResponse]) => GetProjectById(id, ref) | ||
) | ||
.map(_.project) | ||
withSuccess(projectFuture) { | ||
case Some(project) => route(project) | ||
case None => complete(projectDoesNotExistResponse(id)) | ||
} | ||
} | ||
|
||
def listProjectsWith( | ||
reqBuilder: ActorRef[ListProjectsResponse] => ProjectsCommand | ||
)(baseUri: Uri | ||
): Route = { | ||
val projectsFuture = repository.ask(reqBuilder) | ||
withSuccess(projectsFuture) { projectsResponse => | ||
val response = projectsResponse.projects.toSeq.map { | ||
case (id, project) => apiFactory.fromModel(id, project, baseUri) | ||
} | ||
complete(response) | ||
} | ||
} | ||
|
||
def createProject(baseUri: Uri): Route = { | ||
val projectFuture = repository.ask( | ||
(ref: ActorRef[CreateTemporaryResponse]) => | ||
CreateTemporary("NewProject", ref) | ||
) | ||
withSuccess(projectFuture) { response => | ||
complete(apiFactory.fromModel(response.id, response.project, baseUri)) | ||
} | ||
} | ||
|
||
def getThumb(projectId: ProjectId): Route = { | ||
withProject(projectId) { project => | ||
if (project.pkg.hasThumb) getFromFile(project.pkg.thumbFile) | ||
else complete(thumbDoesNotExistResponse) | ||
} | ||
} | ||
|
||
val route: Route = ignoreTrailingSlash { | ||
path(routeHelper.projectsPathMatcher)( | ||
(get & extractUri)(listProjectsWith(ListProjectsRequest)) ~ | ||
(post & extractUri)(createProject) | ||
) ~ | ||
(get & path(routeHelper.tutorialsPathMatcher) & extractUri)( | ||
listProjectsWith(ListTutorialsRequest) | ||
) ~ | ||
(get & path(routeHelper.thumbPathMatcher))(getThumb) | ||
} | ||
|
||
def serve: Future[Http.ServerBinding] = { | ||
Http().bindAndHandle(route, host, port) | ||
} | ||
} | ||
|
||
object Server { | ||
|
||
def main(args: Array[String]) { | ||
|
||
val config = ConfigFactory.load.getConfig("project-manager") | ||
val serverConfig = config.getConfig("server") | ||
val storageConfig = config.getConfig("storage") | ||
|
||
val host = serverConfig.getString("host") | ||
val port = serverConfig.getInt("port") | ||
|
||
val timeout = | ||
FiniteDuration( | ||
serverConfig.getDuration("timeout").toNanos, | ||
TimeUnit.NANOSECONDS | ||
) | ||
|
||
implicit val system: ActorSystem = ActorSystem("project-manager") | ||
implicit val executor: ExecutionContext = system.dispatcher | ||
implicit val materializer: ActorMaterializer = ActorMaterializer() | ||
implicit val askTimeout: Timeout = new Timeout(timeout) | ||
|
||
val localProjectsPath = | ||
new File(storageConfig.getString("local-projects-path")) | ||
val tmpProjectsPath = new File( | ||
storageConfig.getString("temporary-projects-path") | ||
) | ||
val tutorialsPath = | ||
new File(storageConfig.getString("tutorials-path")) | ||
val tutorialsCachePath = | ||
new File(storageConfig.getString("tutorials-cache-path")) | ||
|
||
val tutorialsDownloader = | ||
TutorialsDownloader( | ||
tutorialsPath, | ||
tutorialsCachePath, | ||
config.getString("tutorials.github-organisation") | ||
) | ||
val storageManager = StorageManager( | ||
localProjectsPath, | ||
tmpProjectsPath, | ||
tutorialsPath | ||
) | ||
|
||
val repoActor = system.spawn( | ||
ProjectsService.behavior(storageManager, tutorialsDownloader), | ||
"projects-repository" | ||
) | ||
|
||
val routeHelper = new RouteHelper | ||
val apiFactory = ProjectFactory(routeHelper) | ||
|
||
val server = Server(host, port, repoActor, routeHelper, apiFactory) | ||
server.serve | ||
} | ||
} |
42 changes: 42 additions & 0 deletions
42
project-manager/src/main/scala/org/enso/projectmanager/api/Project.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package org.enso.projectmanager.api | ||
|
||
import java.util.UUID | ||
|
||
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport | ||
import akka.http.scaladsl.model.Uri | ||
import org.enso.projectmanager.RouteHelper | ||
import org.enso.projectmanager.model | ||
import org.enso.projectmanager.model.ProjectId | ||
import spray.json.DefaultJsonProtocol | ||
|
||
case class Project( | ||
id: String, | ||
name: String, | ||
path: String, | ||
thumb: Option[String], | ||
persisted: Boolean) | ||
|
||
case class ProjectFactory(routeHelper: RouteHelper) { | ||
|
||
def fromModel( | ||
id: ProjectId, | ||
project: model.Project, | ||
baseUri: Uri | ||
): Project = { | ||
val thumbUri = | ||
if (project.hasThumb) | ||
Some(routeHelper.uriFor(baseUri, routeHelper.thumbPath(id))) | ||
else None | ||
Project( | ||
id.toString, | ||
project.pkg.name, | ||
project.pkg.root.getAbsolutePath, | ||
thumbUri.map(_.toString), | ||
project.isPersistent | ||
) | ||
} | ||
} | ||
|
||
trait ProjectJsonSupport extends SprayJsonSupport with DefaultJsonProtocol { | ||
implicit val projectFormat = jsonFormat5(Project.apply) | ||
} |
51 changes: 51 additions & 0 deletions
51
project-manager/src/main/scala/org/enso/projectmanager/model/Project.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package org.enso.projectmanager.model | ||
|
||
import java.util.UUID | ||
|
||
import org.enso.pkg.Package | ||
|
||
import scala.collection.immutable.HashMap | ||
|
||
sealed trait ProjectType { | ||
def isPersistent: Boolean | ||
} | ||
case object Local extends ProjectType { | ||
override def isPersistent: Boolean = true | ||
} | ||
case object Tutorial extends ProjectType { | ||
override def isPersistent: Boolean = false | ||
} | ||
case object Temporary extends ProjectType { | ||
override def isPersistent: Boolean = false | ||
} | ||
|
||
case class ProjectId(uid: UUID) { | ||
override def toString: String = uid.toString | ||
} | ||
|
||
case class Project(kind: ProjectType, pkg: Package) { | ||
def isPersistent: Boolean = kind.isPersistent | ||
def hasThumb: Boolean = pkg.hasThumb | ||
} | ||
|
||
case class ProjectsRepository(projects: HashMap[ProjectId, Project]) { | ||
|
||
def getById(id: ProjectId): Option[Project] = { | ||
projects.get(id) | ||
} | ||
|
||
def insert(project: Project): (ProjectId, ProjectsRepository) = { | ||
val id = ProjectsRepository.generateId | ||
val newRepo = copy(projects = projects + (id -> project)) | ||
(id, newRepo) | ||
} | ||
} | ||
|
||
case object ProjectsRepository { | ||
|
||
def apply(projects: Seq[Project]): ProjectsRepository = { | ||
ProjectsRepository(HashMap(projects.map(generateId -> _): _*)) | ||
} | ||
|
||
def generateId: ProjectId = ProjectId(UUID.randomUUID) | ||
} |
Oops, something went wrong.