Skip to content

Commit

Permalink
Implemented GET and DELETE for REST resource /api/users/{user}/versio…
Browse files Browse the repository at this point in the history
…ns{id}; also in command line tool
  • Loading branch information
scalatron committed May 4, 2012
1 parent 4f8b432 commit f39efc0
Show file tree
Hide file tree
Showing 8 changed files with 324 additions and 74 deletions.
10 changes: 10 additions & 0 deletions Scalatron/Readme.txt
Expand Up @@ -54,6 +54,16 @@ in the public domain. Feel free to use, copy, and improve them!

## Version History

### Version 0.9.9.4 -- 2012-05-04

* RESTful web API and command line client now support these additional commands:
/api/users/{user}/versions/{versionId} GET Get Version Files
/api/users/{user}/versions/{versionId} DELETE Delete Version
* fixed: on Windows in IE and Firefox, code in the editor appeared truncated to a single line (issue #22).
* fixed: sign-out (sometimes silently) failed because it connected to the wrong REST resource (issue #27).
* fixed: in IE and Firefox links in the tutorial opened in a new tab instead of the tutorial panel (issue #21).


### Version 0.9.9.3 -- 2012-05-02

* new opcodes: "MarkCell()", "DrawLine()"; see Scalatron Protocol docs for details. Thanks Joachim Hofer, @johofer!
Expand Down
6 changes: 3 additions & 3 deletions Scalatron/devdoc/markdown/Scalatron APIs.md
Expand Up @@ -86,12 +86,12 @@ Important Note:

tested /api/users/{user}/sandboxes POST json json 200 401,415 Create Sandbox
untstd /api/users/{user}/sandboxes DELETE - - 204 401,404 Delete All Sandboxes
tested /api/users/{user}/sandboxes/{id}/{time} GET - json 200 401,404 Get Sandbox State
tested /api/users/{user}/sandboxes/{id}/{time} GET - json 200 401,404 Get Sandbox State

tested /api/users/{user}/versions GET - json 200 401,404 Get Existing Versions
tested /api/users/{user}/versions POST json url 201 401,415 Create Version
n/a /api/users/{user}/versions/{versionId} GET - json 200 401,404 Get Version Files
n/a /api/users/{user}/versions/{versionId} DELETE - - 204 401,404 Delete Version
tested /api/users/{user}/versions/{versionId} GET - json 200 401,404 Get Version Files
tested /api/users/{user}/versions/{versionId} DELETE - - 204 401,404 Delete Version

n/a /api/samples GET - json 200 - Get Existing Samples
n/a /api/samples POST json - 201 401,415 Create Sample
Expand Down
8 changes: 4 additions & 4 deletions Scalatron/src/scalatron/scalatron/api/Scalatron.scala
Expand Up @@ -9,6 +9,8 @@ import scalatron.scalatron.api.Scalatron.{Sample, User, SourceFileCollection}
import akka.actor.ActorSystem
import scala.io.Source
import java.io.{FileWriter, File}
import scalatron.scalatron.impl.FileUtil
import FileUtil.use


/** The trait representing the main API entry point of the Scalatron server. */
Expand Down Expand Up @@ -436,7 +438,7 @@ object Scalatron {
val filename = file.getName
val code = Source.fromFile(file).mkString
if(verbose) println("loaded source code from file: '%s'".format(file.getAbsolutePath))
Scalatron.SourceFile(filename, code)
SourceFile(filename, code)
})
}
}
Expand All @@ -453,9 +455,7 @@ object Scalatron {
def writeTo(directoryPath: String, sourceFileCollection: SourceFileCollection, verbose: Boolean = false) {
sourceFileCollection.foreach(sf => {
val path = directoryPath + "/" + sf.filename
val sourceFile = new FileWriter(path)
sourceFile.append(sf.code)
sourceFile.close()
use(new FileWriter(path)) { _.append(sf.code) }
if(verbose) println("wrote source file: " + path)
})
}
Expand Down
Expand Up @@ -70,6 +70,62 @@ class VersionsResource extends ResourceWithUser {
}
}


@GET
@Path("{id}")
def getVersionFiles(@PathParam("id") id: Int) = {
if(!userSession.isLoggedOnAsUserOrAdministrator(userName)) {
Response.status(CustomStatusType(HttpStatus.UNAUTHORIZED_401, "must be logged on as '" + userName + "' or '" + Scalatron.Constants.AdminUserName + "'")).build()
} else {
try {
scalatron.user(userName) match {
case Some(user) =>
user.version(id) match {
case None =>
Response.status(CustomStatusType(HttpStatus.NOT_FOUND_404, "version %d of user %s does not exist".format(id,userName))).build()
case Some(version) =>
val s = version.sourceFiles
val sourceFiles = s.map(sf => SourcesResource.SourceFile(sf.filename, sf.code)).toArray
SourcesResource.SourceFiles(sourceFiles)
}
case None =>
Response.status(CustomStatusType(HttpStatus.NOT_FOUND_404, "user '" + userName + "' does not exist")).build()
}
} catch {
case e: IOError =>
// source files could not be read
Response.status(CustomStatusType(HttpStatus.INTERNAL_SERVER_ERROR_500, e.getMessage)).build()
}
}
}


@DELETE
@Path("{id}")
def deleteVersion(@PathParam("id") id: Int) = {
if(!userSession.isLoggedOnAsUserOrAdministrator(userName)) {
Response.status(CustomStatusType(HttpStatus.UNAUTHORIZED_401, "must be logged on as '" + userName + "' or '" + Scalatron.Constants.AdminUserName + "'")).build()
} else {
try {
scalatron.user(userName) match {
case Some(user) =>
user.version(id) match {
case None =>
Response.status(CustomStatusType(HttpStatus.NOT_FOUND_404, "version %d of user %s does not exist".format(id,userName))).build()
case Some(version) =>
version.delete()
Response.noContent().build()
}
case None =>
Response.status(CustomStatusType(HttpStatus.NOT_FOUND_404, "user '" + userName + "' does not exist")).build()
}
} catch {
case e: IOError =>
// source files could not be read
Response.status(CustomStatusType(HttpStatus.INTERNAL_SERVER_ERROR_500, e.getMessage)).build()
}
}
}
}


Expand Down
154 changes: 104 additions & 50 deletions ScalatronCLI/src/scalatronCLI/cmdline/CommandLineProcessor.scala
Expand Up @@ -5,12 +5,12 @@
package scalatronCLI.cmdline

import scalatronRemote.api.ScalatronRemote
import scalatronRemote.api.ScalatronRemote.{ScalatronException, ConnectionConfig}
import java.io.{FileWriter, File}
import io.Source
import scalatronRemote.Version
import java.text.DateFormat
import java.util.Date
import scalatronRemote.api.ScalatronRemote.{SourceFileCollection, ScalatronException, ConnectionConfig}


/** A command line interface for interaction with a remote Scalatron server over the RESTful HTTP API.
Expand Down Expand Up @@ -73,6 +73,13 @@ object CommandLineProcessor {
println(" -sourceDir <path> the path of the local directory where the source files can be found")
println(" -label <name> the label to apply to the versions (default: empty)")
println("")
println(" getVersion retrieves the source code of the version with the given ID; as user only")
println(" -targetDir <path> the path of the local directory where the source files should be stored")
println(" -id <int> the version's ID")
println("")
println(" deleteVersion deletes the version with the given ID; as user only")
println(" -id <int> the version's ID")
println("")
println(" benchmark runs standard isolated-bot benchmark on given source files; as user only")
println(" -sourceDir <path> the path of the local directory where the source files can be found")
println("")
Expand All @@ -86,6 +93,8 @@ object CommandLineProcessor {
println(" java -jar ScalatronCLI.jar -user Frankie -password a -cmd build")
println(" java -jar ScalatronCLI.jar -user Frankie -password a -cmd versions")
println(" java -jar ScalatronCLI.jar -user Frankie -password a -cmd createVersion -sourceDir /tempsrc -label \"updated\"")
println(" java -jar ScalatronCLI.jar -user Frankie -password a -cmd getVersion -targetDir /tempsrc -id 1")
println(" java -jar ScalatronCLI.jar -user Frankie -password a -cmd deleteVersion -id 1")
println(" java -jar ScalatronCLI.jar -user Frankie -password a -cmd benchmark -sourceDir /tempsrc")
System.exit(0)
}
Expand Down Expand Up @@ -118,6 +127,8 @@ object CommandLineProcessor {
case "build" => cmd_buildSources(connectionConfig, argMap)
case "versions" => cmd_versions(connectionConfig, argMap)
case "createVersion" => cmd_createVersion(connectionConfig, argMap)
case "getVersion" => cmd_getVersion(connectionConfig, argMap)
case "deleteVersion" => cmd_deleteVersion(connectionConfig, argMap)
case "benchmark" => cmd_benchmark(connectionConfig, argMap)
case _ => System.err.println("unknown command: " + command)
}
Expand Down Expand Up @@ -316,25 +327,11 @@ object CommandLineProcessor {
(scalatron: ScalatronRemote, loggedonUser: ScalatronRemote.User, users: ScalatronRemote.UserList) => {
// get user source files (1 round-trip)
handleScalatronExceptionsFor {
val targetDir = new File(targetDirPath)
if(!targetDir.exists()) {
if(!targetDir.mkdirs()) {
System.err.println("error: cannot create local directory '%s'".format(targetDirPath))
System.exit(-1)
}
}

val sourceFiles = loggedonUser.getSourceFiles
sourceFiles.foreach(sf => {
val filename = sf.filename
val filePath = targetDir.getAbsolutePath + "/" + filename
val fileWriter = new FileWriter(filePath)
fileWriter.append(sf.code)
fileWriter.close()
})
val sourceFileCollection = loggedonUser.sourceFiles
SourceFileCollection.writeTo(targetDirPath, sourceFileCollection, connectionConfig.verbose)

if(connectionConfig.verbose)
println("Wrote %d source files to '%s'".format(sourceFiles.size, targetDirPath))
println("Wrote %d source files to '%s'".format(sourceFileCollection.size, targetDirPath))
}
}
)
Expand All @@ -358,12 +355,12 @@ object CommandLineProcessor {
(scalatron: ScalatronRemote, loggedonUser: ScalatronRemote.User, users: ScalatronRemote.UserList) => {
// update user source files (1 round-trip)
handleScalatronExceptionsFor {
val sourceFiles = readSourceFiles(sourceDirPath)
val sourceFileCollection = SourceFileCollection.loadFrom(sourceDirPath)

loggedonUser.updateSourceFiles(sourceFiles)
loggedonUser.updateSourceFiles(sourceFileCollection)

if(connectionConfig.verbose)
println("Updated %d source files from '%s'".format(sourceFiles.size, sourceDirPath))
println("Updated %d source files from '%s'".format(sourceFileCollection.size, sourceDirPath))
}
}
)
Expand Down Expand Up @@ -395,8 +392,7 @@ object CommandLineProcessor {
}


/** -command sources gets a source files from a user workspace; user or Administrator
* -targetDir path the path of the local directory where the source files should be stored
/** -command versions gets a list of versions for a specific user; as user only
*/
def cmd_versions(connectionConfig: ConnectionConfig, argMap: Map[String, String]) {
doAsUser(
Expand Down Expand Up @@ -431,12 +427,91 @@ object CommandLineProcessor {
// update user source files (1 round-trip)
handleScalatronExceptionsFor {
val label = argMap.getOrElse("-label", "")
val sourceFiles = readSourceFiles(sourceDirPath)
val sourceFileCollection = SourceFileCollection.loadFrom(sourceDirPath)

val version = loggedonUser.createVersion(label, sourceFiles)
val version = loggedonUser.createVersion(label, sourceFileCollection)

if(connectionConfig.verbose)
println("Create version #%d from %d source files from '%s'".format(version.id, sourceFiles.size, sourceDirPath))
println("Create version #%d from %d source files from '%s'".format(version.id, sourceFileCollection.size, sourceDirPath))
}
}
)
}
}


/** -command getVersion retrieves the source code of the version with the given ID; as user only
* -targetDir path the path of the local directory where the source files should be stored
* -id int the version's ID
*/
def cmd_getVersion(connectionConfig: ConnectionConfig, argMap: Map[String, String]) {
argMap.get("-targetDir") match {
case None =>
System.err.println("error: command 'getVersion' requires option '-targetDir'")
System.exit(-1)

case Some(targetDirPath) =>
argMap.get("-id") match {
case None =>
System.err.println("error: command 'getVersion' requires option '-id'")
System.exit(-1)

case Some(versionIdStr) =>
val versionId = versionIdStr.toInt
doAsUser(
connectionConfig,
argMap,
(scalatron: ScalatronRemote, loggedonUser: ScalatronRemote.User, users: ScalatronRemote.UserList) => {
// get user source files (1 round-trip)
handleScalatronExceptionsFor {
loggedonUser.version(versionId) match {
case None =>
System.err.println("error: cannot locate version with id %d".format(versionId))
System.exit(-1)

case Some(version) =>
val sourceFileCollection = version.sourceFiles
SourceFileCollection.writeTo(targetDirPath, sourceFileCollection, connectionConfig.verbose)

if(connectionConfig.verbose)
println("Wrote %d source files to '%s'".format(sourceFileCollection.size, targetDirPath))
}
}
}
)
}
}
}


/** -command deleteVersion deletes the version with the given ID; as user only
* -id int the version's ID
*/
def cmd_deleteVersion(connectionConfig: ConnectionConfig, argMap: Map[String, String]) {
argMap.get("-id") match {
case None =>
System.err.println("error: command 'deleteVersion' requires option '-id'")
System.exit(-1)

case Some(versionIdStr) =>
val versionId = versionIdStr.toInt
doAsUser(
connectionConfig,
argMap,
(scalatron: ScalatronRemote, loggedonUser: ScalatronRemote.User, users: ScalatronRemote.UserList) => {
// get user source files (1 round-trip)
handleScalatronExceptionsFor {
loggedonUser.version(versionId) match {
case None =>
System.err.println("error: cannot locate version with id %d".format(versionId))
System.exit(-1)

case Some(version) =>
version.delete()

if(connectionConfig.verbose)
println("Deleted version with id %d".format(versionId))
}
}
}
)
Expand All @@ -460,10 +535,10 @@ object CommandLineProcessor {
(scalatron: ScalatronRemote, loggedonUser: ScalatronRemote.User, users: ScalatronRemote.UserList) => {
handleScalatronExceptionsFor {
// update user source files (1 round-trip)
val sourceFiles = readSourceFiles(sourceDirPath)
loggedonUser.updateSourceFiles(sourceFiles)
val sourceFileCollection = SourceFileCollection.loadFrom(sourceDirPath)
loggedonUser.updateSourceFiles(sourceFileCollection)
if(connectionConfig.verbose)
println("Updated %d source files from '%s'".format(sourceFiles.size, sourceDirPath))
println("Updated %d source files from '%s'".format(sourceFileCollection.size, sourceDirPath))

// build uploaded user source files (1 round-trip)
val buildResult = loggedonUser.buildSources()
Expand Down Expand Up @@ -515,27 +590,6 @@ object CommandLineProcessor {
// helpers
//------------------------------------------------------------------------------------------------------------------

/** Reads a collection of source code files from disk, from a given directory.
*/
private def readSourceFiles(sourceDirPath: String) : Iterable[ScalatronRemote.SourceFile] = {
val sourceDir = new File(sourceDirPath)
if(!sourceDir.exists()) {
System.err.println("error: local source directory does not exist: '%s'".format(sourceDirPath))
System.exit(-1)
}

val fileList = sourceDir.listFiles()
if(fileList == null || fileList.isEmpty) {
System.err.println("error: local source directory is empty: '%s'".format(sourceDirPath))
System.exit(-1)
}
fileList.map(f => {
val code = Source.fromFile(f).getLines().mkString("\n")
ScalatronRemote.SourceFile(f.getName, code)
})
}


/** Accepts a closure; handles typical server exceptions. Exists with error code on such exceptions.
*/
private def handleScalatronExceptionsFor(action: => Unit) {
Expand Down

0 comments on commit f39efc0

Please sign in to comment.