Skip to content

Commit f39efc0

Browse files
committed
Implemented GET and DELETE for REST resource /api/users/{user}/versions{id}; also in command line tool
1 parent 4f8b432 commit f39efc0

File tree

8 files changed

+324
-74
lines changed

8 files changed

+324
-74
lines changed

Scalatron/Readme.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,16 @@ in the public domain. Feel free to use, copy, and improve them!
5454

5555
## Version History
5656

57+
### Version 0.9.9.4 -- 2012-05-04
58+
59+
* RESTful web API and command line client now support these additional commands:
60+
/api/users/{user}/versions/{versionId} GET Get Version Files
61+
/api/users/{user}/versions/{versionId} DELETE Delete Version
62+
* fixed: on Windows in IE and Firefox, code in the editor appeared truncated to a single line (issue #22).
63+
* fixed: sign-out (sometimes silently) failed because it connected to the wrong REST resource (issue #27).
64+
* fixed: in IE and Firefox links in the tutorial opened in a new tab instead of the tutorial panel (issue #21).
65+
66+
5767
### Version 0.9.9.3 -- 2012-05-02
5868

5969
* new opcodes: "MarkCell()", "DrawLine()"; see Scalatron Protocol docs for details. Thanks Joachim Hofer, @johofer!

Scalatron/devdoc/markdown/Scalatron APIs.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,12 @@ Important Note:
8686

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

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

9696
n/a /api/samples GET - json 200 - Get Existing Samples
9797
n/a /api/samples POST json - 201 401,415 Create Sample

Scalatron/src/scalatron/scalatron/api/Scalatron.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import scalatron.scalatron.api.Scalatron.{Sample, User, SourceFileCollection}
99
import akka.actor.ActorSystem
1010
import scala.io.Source
1111
import java.io.{FileWriter, File}
12+
import scalatron.scalatron.impl.FileUtil
13+
import FileUtil.use
1214

1315

1416
/** The trait representing the main API entry point of the Scalatron server. */
@@ -436,7 +438,7 @@ object Scalatron {
436438
val filename = file.getName
437439
val code = Source.fromFile(file).mkString
438440
if(verbose) println("loaded source code from file: '%s'".format(file.getAbsolutePath))
439-
Scalatron.SourceFile(filename, code)
441+
SourceFile(filename, code)
440442
})
441443
}
442444
}
@@ -453,9 +455,7 @@ object Scalatron {
453455
def writeTo(directoryPath: String, sourceFileCollection: SourceFileCollection, verbose: Boolean = false) {
454456
sourceFileCollection.foreach(sf => {
455457
val path = directoryPath + "/" + sf.filename
456-
val sourceFile = new FileWriter(path)
457-
sourceFile.append(sf.code)
458-
sourceFile.close()
458+
use(new FileWriter(path)) { _.append(sf.code) }
459459
if(verbose) println("wrote source file: " + path)
460460
})
461461
}

Scalatron/src/scalatron/webServer/rest/resources/VersionsResource.scala

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,62 @@ class VersionsResource extends ResourceWithUser {
7070
}
7171
}
7272

73+
74+
@GET
75+
@Path("{id}")
76+
def getVersionFiles(@PathParam("id") id: Int) = {
77+
if(!userSession.isLoggedOnAsUserOrAdministrator(userName)) {
78+
Response.status(CustomStatusType(HttpStatus.UNAUTHORIZED_401, "must be logged on as '" + userName + "' or '" + Scalatron.Constants.AdminUserName + "'")).build()
79+
} else {
80+
try {
81+
scalatron.user(userName) match {
82+
case Some(user) =>
83+
user.version(id) match {
84+
case None =>
85+
Response.status(CustomStatusType(HttpStatus.NOT_FOUND_404, "version %d of user %s does not exist".format(id,userName))).build()
86+
case Some(version) =>
87+
val s = version.sourceFiles
88+
val sourceFiles = s.map(sf => SourcesResource.SourceFile(sf.filename, sf.code)).toArray
89+
SourcesResource.SourceFiles(sourceFiles)
90+
}
91+
case None =>
92+
Response.status(CustomStatusType(HttpStatus.NOT_FOUND_404, "user '" + userName + "' does not exist")).build()
93+
}
94+
} catch {
95+
case e: IOError =>
96+
// source files could not be read
97+
Response.status(CustomStatusType(HttpStatus.INTERNAL_SERVER_ERROR_500, e.getMessage)).build()
98+
}
99+
}
100+
}
101+
102+
103+
@DELETE
104+
@Path("{id}")
105+
def deleteVersion(@PathParam("id") id: Int) = {
106+
if(!userSession.isLoggedOnAsUserOrAdministrator(userName)) {
107+
Response.status(CustomStatusType(HttpStatus.UNAUTHORIZED_401, "must be logged on as '" + userName + "' or '" + Scalatron.Constants.AdminUserName + "'")).build()
108+
} else {
109+
try {
110+
scalatron.user(userName) match {
111+
case Some(user) =>
112+
user.version(id) match {
113+
case None =>
114+
Response.status(CustomStatusType(HttpStatus.NOT_FOUND_404, "version %d of user %s does not exist".format(id,userName))).build()
115+
case Some(version) =>
116+
version.delete()
117+
Response.noContent().build()
118+
}
119+
case None =>
120+
Response.status(CustomStatusType(HttpStatus.NOT_FOUND_404, "user '" + userName + "' does not exist")).build()
121+
}
122+
} catch {
123+
case e: IOError =>
124+
// source files could not be read
125+
Response.status(CustomStatusType(HttpStatus.INTERNAL_SERVER_ERROR_500, e.getMessage)).build()
126+
}
127+
}
128+
}
73129
}
74130

75131

ScalatronCLI/src/scalatronCLI/cmdline/CommandLineProcessor.scala

Lines changed: 104 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
package scalatronCLI.cmdline
66

77
import scalatronRemote.api.ScalatronRemote
8-
import scalatronRemote.api.ScalatronRemote.{ScalatronException, ConnectionConfig}
98
import java.io.{FileWriter, File}
109
import io.Source
1110
import scalatronRemote.Version
1211
import java.text.DateFormat
1312
import java.util.Date
13+
import scalatronRemote.api.ScalatronRemote.{SourceFileCollection, ScalatronException, ConnectionConfig}
1414

1515

1616
/** A command line interface for interaction with a remote Scalatron server over the RESTful HTTP API.
@@ -73,6 +73,13 @@ object CommandLineProcessor {
7373
println(" -sourceDir <path> the path of the local directory where the source files can be found")
7474
println(" -label <name> the label to apply to the versions (default: empty)")
7575
println("")
76+
println(" getVersion retrieves the source code of the version with the given ID; as user only")
77+
println(" -targetDir <path> the path of the local directory where the source files should be stored")
78+
println(" -id <int> the version's ID")
79+
println("")
80+
println(" deleteVersion deletes the version with the given ID; as user only")
81+
println(" -id <int> the version's ID")
82+
println("")
7683
println(" benchmark runs standard isolated-bot benchmark on given source files; as user only")
7784
println(" -sourceDir <path> the path of the local directory where the source files can be found")
7885
println("")
@@ -86,6 +93,8 @@ object CommandLineProcessor {
8693
println(" java -jar ScalatronCLI.jar -user Frankie -password a -cmd build")
8794
println(" java -jar ScalatronCLI.jar -user Frankie -password a -cmd versions")
8895
println(" java -jar ScalatronCLI.jar -user Frankie -password a -cmd createVersion -sourceDir /tempsrc -label \"updated\"")
96+
println(" java -jar ScalatronCLI.jar -user Frankie -password a -cmd getVersion -targetDir /tempsrc -id 1")
97+
println(" java -jar ScalatronCLI.jar -user Frankie -password a -cmd deleteVersion -id 1")
8998
println(" java -jar ScalatronCLI.jar -user Frankie -password a -cmd benchmark -sourceDir /tempsrc")
9099
System.exit(0)
91100
}
@@ -118,6 +127,8 @@ object CommandLineProcessor {
118127
case "build" => cmd_buildSources(connectionConfig, argMap)
119128
case "versions" => cmd_versions(connectionConfig, argMap)
120129
case "createVersion" => cmd_createVersion(connectionConfig, argMap)
130+
case "getVersion" => cmd_getVersion(connectionConfig, argMap)
131+
case "deleteVersion" => cmd_deleteVersion(connectionConfig, argMap)
121132
case "benchmark" => cmd_benchmark(connectionConfig, argMap)
122133
case _ => System.err.println("unknown command: " + command)
123134
}
@@ -316,25 +327,11 @@ object CommandLineProcessor {
316327
(scalatron: ScalatronRemote, loggedonUser: ScalatronRemote.User, users: ScalatronRemote.UserList) => {
317328
// get user source files (1 round-trip)
318329
handleScalatronExceptionsFor {
319-
val targetDir = new File(targetDirPath)
320-
if(!targetDir.exists()) {
321-
if(!targetDir.mkdirs()) {
322-
System.err.println("error: cannot create local directory '%s'".format(targetDirPath))
323-
System.exit(-1)
324-
}
325-
}
326-
327-
val sourceFiles = loggedonUser.getSourceFiles
328-
sourceFiles.foreach(sf => {
329-
val filename = sf.filename
330-
val filePath = targetDir.getAbsolutePath + "/" + filename
331-
val fileWriter = new FileWriter(filePath)
332-
fileWriter.append(sf.code)
333-
fileWriter.close()
334-
})
330+
val sourceFileCollection = loggedonUser.sourceFiles
331+
SourceFileCollection.writeTo(targetDirPath, sourceFileCollection, connectionConfig.verbose)
335332

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

363-
loggedonUser.updateSourceFiles(sourceFiles)
360+
loggedonUser.updateSourceFiles(sourceFileCollection)
364361

365362
if(connectionConfig.verbose)
366-
println("Updated %d source files from '%s'".format(sourceFiles.size, sourceDirPath))
363+
println("Updated %d source files from '%s'".format(sourceFileCollection.size, sourceDirPath))
367364
}
368365
}
369366
)
@@ -395,8 +392,7 @@ object CommandLineProcessor {
395392
}
396393

397394

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

436-
val version = loggedonUser.createVersion(label, sourceFiles)
432+
val version = loggedonUser.createVersion(label, sourceFileCollection)
437433

438434
if(connectionConfig.verbose)
439-
println("Create version #%d from %d source files from '%s'".format(version.id, sourceFiles.size, sourceDirPath))
435+
println("Create version #%d from %d source files from '%s'".format(version.id, sourceFileCollection.size, sourceDirPath))
436+
}
437+
}
438+
)
439+
}
440+
}
441+
442+
443+
/** -command getVersion retrieves the source code of the version with the given ID; as user only
444+
* -targetDir path the path of the local directory where the source files should be stored
445+
* -id int the version's ID
446+
*/
447+
def cmd_getVersion(connectionConfig: ConnectionConfig, argMap: Map[String, String]) {
448+
argMap.get("-targetDir") match {
449+
case None =>
450+
System.err.println("error: command 'getVersion' requires option '-targetDir'")
451+
System.exit(-1)
452+
453+
case Some(targetDirPath) =>
454+
argMap.get("-id") match {
455+
case None =>
456+
System.err.println("error: command 'getVersion' requires option '-id'")
457+
System.exit(-1)
458+
459+
case Some(versionIdStr) =>
460+
val versionId = versionIdStr.toInt
461+
doAsUser(
462+
connectionConfig,
463+
argMap,
464+
(scalatron: ScalatronRemote, loggedonUser: ScalatronRemote.User, users: ScalatronRemote.UserList) => {
465+
// get user source files (1 round-trip)
466+
handleScalatronExceptionsFor {
467+
loggedonUser.version(versionId) match {
468+
case None =>
469+
System.err.println("error: cannot locate version with id %d".format(versionId))
470+
System.exit(-1)
471+
472+
case Some(version) =>
473+
val sourceFileCollection = version.sourceFiles
474+
SourceFileCollection.writeTo(targetDirPath, sourceFileCollection, connectionConfig.verbose)
475+
476+
if(connectionConfig.verbose)
477+
println("Wrote %d source files to '%s'".format(sourceFileCollection.size, targetDirPath))
478+
}
479+
}
480+
}
481+
)
482+
}
483+
}
484+
}
485+
486+
487+
/** -command deleteVersion deletes the version with the given ID; as user only
488+
* -id int the version's ID
489+
*/
490+
def cmd_deleteVersion(connectionConfig: ConnectionConfig, argMap: Map[String, String]) {
491+
argMap.get("-id") match {
492+
case None =>
493+
System.err.println("error: command 'deleteVersion' requires option '-id'")
494+
System.exit(-1)
495+
496+
case Some(versionIdStr) =>
497+
val versionId = versionIdStr.toInt
498+
doAsUser(
499+
connectionConfig,
500+
argMap,
501+
(scalatron: ScalatronRemote, loggedonUser: ScalatronRemote.User, users: ScalatronRemote.UserList) => {
502+
// get user source files (1 round-trip)
503+
handleScalatronExceptionsFor {
504+
loggedonUser.version(versionId) match {
505+
case None =>
506+
System.err.println("error: cannot locate version with id %d".format(versionId))
507+
System.exit(-1)
508+
509+
case Some(version) =>
510+
version.delete()
511+
512+
if(connectionConfig.verbose)
513+
println("Deleted version with id %d".format(versionId))
514+
}
440515
}
441516
}
442517
)
@@ -460,10 +535,10 @@ object CommandLineProcessor {
460535
(scalatron: ScalatronRemote, loggedonUser: ScalatronRemote.User, users: ScalatronRemote.UserList) => {
461536
handleScalatronExceptionsFor {
462537
// update user source files (1 round-trip)
463-
val sourceFiles = readSourceFiles(sourceDirPath)
464-
loggedonUser.updateSourceFiles(sourceFiles)
538+
val sourceFileCollection = SourceFileCollection.loadFrom(sourceDirPath)
539+
loggedonUser.updateSourceFiles(sourceFileCollection)
465540
if(connectionConfig.verbose)
466-
println("Updated %d source files from '%s'".format(sourceFiles.size, sourceDirPath))
541+
println("Updated %d source files from '%s'".format(sourceFileCollection.size, sourceDirPath))
467542

468543
// build uploaded user source files (1 round-trip)
469544
val buildResult = loggedonUser.buildSources()
@@ -515,27 +590,6 @@ object CommandLineProcessor {
515590
// helpers
516591
//------------------------------------------------------------------------------------------------------------------
517592

518-
/** Reads a collection of source code files from disk, from a given directory.
519-
*/
520-
private def readSourceFiles(sourceDirPath: String) : Iterable[ScalatronRemote.SourceFile] = {
521-
val sourceDir = new File(sourceDirPath)
522-
if(!sourceDir.exists()) {
523-
System.err.println("error: local source directory does not exist: '%s'".format(sourceDirPath))
524-
System.exit(-1)
525-
}
526-
527-
val fileList = sourceDir.listFiles()
528-
if(fileList == null || fileList.isEmpty) {
529-
System.err.println("error: local source directory is empty: '%s'".format(sourceDirPath))
530-
System.exit(-1)
531-
}
532-
fileList.map(f => {
533-
val code = Source.fromFile(f).getLines().mkString("\n")
534-
ScalatronRemote.SourceFile(f.getName, code)
535-
})
536-
}
537-
538-
539593
/** Accepts a closure; handles typical server exceptions. Exists with error code on such exceptions.
540594
*/
541595
private def handleScalatronExceptionsFor(action: => Unit) {

0 commit comments

Comments
 (0)