Skip to content

Commit bd34230

Browse files
committed
Minor enhancements to auto-backup code to support versioning imp by chilicat
1 parent 3e5af4c commit bd34230

File tree

4 files changed

+104
-74
lines changed

4 files changed

+104
-74
lines changed

Scalatron/Readme.txt

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

5555
## Version History
5656

57+
### Version 0.9.9.5 -- 2012-05-06
58+
59+
* version control is now available in the browser UI via "Save..." and "Revert..." buttons. Thanks @daniel_kuffner!
60+
61+
5762
### Version 0.9.9.4 -- 2012-05-04
5863

5964
* RESTful web API and command line client now support these additional commands:

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

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -303,11 +303,15 @@ object Scalatron {
303303

304304
/** Returns a sorted collection of version identifiers of the bot sources, as tuples of
305305
* (id, label, date). The versions are enumerated by listing the directories below the
306-
* 'versions' directory, e.g. at "/Scalatron/users/{user}/versions/{versionId}"
306+
* 'versions' directory, e.g. at "/Scalatron/users/{user}/versions/{versionId}", then sorted by
307+
* ID, with lowest ID first.
307308
* @throws IOError if version directory cannot be read from disk, etc.
308309
*/
309310
def versions: Iterable[Version]
310311

312+
/** Returns the version with the highest version ID, if any. */
313+
def latestVersion: Option[Version] = versions.lastOption
314+
311315
/** Returns the version with the given ID, as a Some(Version) if it exists or None if it
312316
* does not exist. Will attempt to locate the version in the approriate directory below
313317
* the 'versions' directory, e.g. at "/Scalatron/users/{user}/versions/{versionId}" */
@@ -326,17 +330,22 @@ object Scalatron {
326330
* code directory of the user if the given version creation policy requires it. This method is intended as
327331
* a convenience call you can perform before overwriting the source code in the user's workspace with
328332
* new, updated source files.
333+
* The function implements the following logic:
334+
* - if policy == Always, a backup version is created, no matter what
335+
* - if policy == Never, no backup version is created, no matter what
336+
* - if policy == IfDifferent, a backup version is created if and only if:
337+
* (a) the current version is different from the incoming, updated version AND
338+
* (b) the current version is different from the latest version in the history, or the history is empty
329339
* @param policy the version creation policy: IfDifferent, Always, Never.
330-
* @param label an optional label to apply to the version (may be empty).
331-
* @param sourceFiles the source files that will be used to determine whether the files on disk are
340+
* @param label an optional label to apply to the version IF it is created (may be empty).
341+
* @param b the incoming, updated source files that will be used to determine whether the files on disk are
332342
* different in the policy "IfDifferent". Note that theses are NOT the files that
333343
* will be stored in the version!
334344
* @return an optional Version object, valid if a version was actually created.
335-
* @throws IllegalStateException if version (base) directory could not be created or source
336-
* directory could not be read.
345+
* @throws IllegalStateException if version (base) directory could not be created or source directory could not be read.
337346
* @throws IOError if source files cannot be written to disk, etc.
338347
* */
339-
def createBackupVersion(policy: VersionPolicy, label: String, sourceFiles: SourceFileCollection): Option[Version]
348+
def createBackupVersion(policy: VersionPolicy, label: String, b: SourceFileCollection): Option[Version]
340349

341350

342351
//----------------------------------------------------------------------------------------------
@@ -410,6 +419,47 @@ object Scalatron {
410419
"}\n"
411420
))
412421

422+
423+
def areEqual(as: SourceFileCollection, bs: SourceFileCollection, verbose: Boolean) : Boolean =
424+
(as.size == bs.size) && // same number of files?
425+
as.forall(a => {
426+
bs.find(_.filename == a.filename) match {
427+
case None =>
428+
if(verbose) println(" SourceFileCollection.areEqual: file exists in as but not bs: " + a.filename)
429+
false // file present in a, but not in b => different
430+
431+
case Some(b) =>
432+
if(verbose) {
433+
// 2012-05-04 temp debugging information
434+
println(" SourceFileCollection.areEqual: file still exists: " + a.filename)
435+
if(b.code != a.code) {
436+
println(" file contents different")
437+
438+
if(b.code.length != a.code.length) {
439+
println(" file sizes are different: a %d vs b %d".format(a.code.length, b.code.length))
440+
}
441+
442+
val aLineCount = a.code.lines.length
443+
val bLineCount = b.code.lines.length
444+
if(bLineCount > aLineCount) {
445+
println(" file b contains more lines: b %d vs a %d".format(bLineCount, aLineCount))
446+
val bLines = b.code.lines
447+
val aLines = a.code.lines
448+
val linesWithIndex = bLines.zip(aLines).zipWithIndex.map(l => "%04d: %60s %60s".format(l._2, l._1._1, l._1._2))
449+
println(linesWithIndex.mkString("\n"))
450+
} else
451+
if(bLineCount < aLineCount) {
452+
println(" file a contains more lines: b %d vs a %d".format(bLineCount, aLineCount))
453+
} else {
454+
println(" files a and b contain same number of lines")
455+
}
456+
}
457+
}
458+
459+
a.code == b.code // file present in a and b, so compare code
460+
}
461+
})
462+
413463
/** Returns a collection of SourceFile objects representing source code files residing in the given directory.
414464
* Excludes directories and files that have the same name as the config files ("config.txt").
415465
* If the directory does not exist, is empty or does not contain valid files, an empty collection is returned.

Scalatron/src/scalatron/scalatron/impl/ScalatronUser.scala

Lines changed: 34 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -193,14 +193,18 @@ case class ScalatronUser(name: String, scalatron: ScalatronImpl) extends Scalatr
193193
// no versions there yet!
194194
Iterable.empty
195195
} else {
196-
versionDirectories.filter(_.isDirectory).map(dir => {
196+
versionDirectories
197+
.filter(_.isDirectory)
198+
.map(dir => {
197199
val versionId = dir.getName.toInt
198200
val versionConfigFilename = dir.getAbsolutePath + "/" + ConfigFilename
199201
val paramMap = loadConfigFile(versionConfigFilename)
200202
val label = paramMap.getOrElse("label", "")
201203
val date = paramMap.getOrElse("date", "").toLong
202204
ScalatronVersion(versionId, label, date, this)
203205
})
206+
.toArray
207+
.sortBy(_.id)
204208
}
205209
}
206210

@@ -256,20 +260,20 @@ case class ScalatronUser(name: String, scalatron: ScalatronImpl) extends Scalatr
256260
// write the given source files into the version directory
257261
SourceFileCollection.writeTo(versionDirectoryPath, sourceFiles, scalatron.verbose)
258262

259-
/*
260-
// 2012-04-16 was: copy the current contents of the source directory into the version directory
261-
val sourceDirectory = new File(sourceDirectoryPath)
262-
val sourceFiles = sourceDirectory.listFiles()
263-
if( sourceFiles == null || sourceFiles.isEmpty ) {
264-
// no source files there yet! -> nothing to do
265-
} else {
266-
sourceFiles.foreach(sourceFile => {
267-
val destPath = versionDirectoryPath + "/" + sourceFile.getName
268-
copyFile(sourceFile.getAbsolutePath, destPath)
269-
if( scalatron.verbose ) println("copied user source file for '" + name + "' to version: " + destPath)
270-
})
271-
}
272-
*/
263+
/*
264+
// 2012-04-16 was: copy the current contents of the source directory into the version directory
265+
val sourceDirectory = new File(sourceDirectoryPath)
266+
val sourceFiles = sourceDirectory.listFiles()
267+
if( sourceFiles == null || sourceFiles.isEmpty ) {
268+
// no source files there yet! -> nothing to do
269+
} else {
270+
sourceFiles.foreach(sourceFile => {
271+
val destPath = versionDirectoryPath + "/" + sourceFile.getName
272+
copyFile(sourceFile.getAbsolutePath, destPath)
273+
if( scalatron.verbose ) println("copied user source file for '" + name + "' to version: " + destPath)
274+
})
275+
}
276+
*/
273277

274278
ScalatronVersion(versionId, label, date, this)
275279
}
@@ -278,51 +282,24 @@ case class ScalatronUser(name: String, scalatron: ScalatronImpl) extends Scalatr
278282
def createBackupVersion(policy: VersionPolicy, label: String, updatedSourceFiles: SourceFileCollection) =
279283
policy match {
280284
case VersionPolicy.IfDifferent =>
281-
val oldSourceFiles = sourceFiles
282-
val different =
283-
(oldSourceFiles.size == updatedSourceFiles.size) &&
284-
(oldSourceFiles.forall(oldSF => {
285-
updatedSourceFiles.find(_.filename == oldSF.filename) match {
286-
case None =>
287-
if(scalatron.verbose) println("VersionPolicy.IfDifferent: file no longer exists: " + oldSF.filename)
288-
true // file present in old, but not in new => different
289-
case Some(newSF) =>
290-
if(scalatron.verbose) {
291-
// 2012-05-04 temp debugging information
292-
println("VersionPolicy.IfDifferent: file still exists: " + oldSF.filename)
293-
if(newSF.code != oldSF.code) {
294-
println(" file contents different")
295-
296-
if(newSF.code.length != oldSF.code.length) {
297-
println(" file sizes are different: old %d vs new %d".format(oldSF.code.length, newSF.code.length))
298-
}
299-
300-
val newLineCount = newSF.code.lines.length
301-
val oldLineCount = oldSF.code.lines.length
302-
if(newLineCount > oldLineCount) {
303-
println(" new file contains more lines: %d vs %d".format(newLineCount, oldLineCount))
304-
val newLines = newSF.code.lines
305-
val oldLines = oldSF.code.lines
306-
val linesWithIndex = newLines.zip(oldLines).zipWithIndex.map(l => "%04d: %60s %60s".format(l._2, l._1._1, l._1._2))
307-
println(linesWithIndex.mkString("\n"))
308-
} else
309-
if(newLineCount < oldLineCount) {
310-
println(" old file contains more lines: %d vs %d".format(newLineCount, oldLineCount))
311-
} else {
312-
println(" old file and new file contain same number of lines")
313-
}
314-
}
315-
}
316-
newSF.code != oldSF.code // file present in old and new, so compare code
317-
}
318-
}))
285+
val sourceFilesCurrentlyInWorkspace = sourceFiles
319286

320-
if(different) {
321-
if(scalatron.verbose) println("VersionPolicy.IfDifferent, files are different => creating backup version")
322-
Some(createVersion(label, oldSourceFiles)) // backup old files as a version
323-
} else {
287+
val workspaceEqualsUpdated = SourceFileCollection.areEqual(sourceFilesCurrentlyInWorkspace, updatedSourceFiles, scalatron.verbose)
288+
289+
val workspaceEqualsLatestVersion = latestVersion match {
290+
case None =>
291+
false
292+
case Some(latestVersionInHistory) =>
293+
val sourceFilesOfLatestVersion = latestVersionInHistory.sourceFiles
294+
SourceFileCollection.areEqual(sourceFilesCurrentlyInWorkspace, sourceFilesOfLatestVersion, scalatron.verbose)
295+
}
296+
297+
if(workspaceEqualsUpdated || workspaceEqualsLatestVersion) {
324298
if(scalatron.verbose) println("VersionPolicy.IfDifferent, files are unchanged => not creating backup version")
325299
None
300+
} else {
301+
if(scalatron.verbose) println("VersionPolicy.IfDifferent, files are different => creating backup version")
302+
Some(createVersion(label, sourceFilesCurrentlyInWorkspace)) // backup old files as a version
326303
}
327304

328305
case VersionPolicy.Always =>

Scalatron/webui/client/EditorToolbar.js

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
var botCode = Editor.getContent();
3131

3232
if (botCode) {
33-
3433
Events.fireEvent("progressUpdate", { message: "Saving sources" });
3534

3635
API.updateSourceFiles({
@@ -75,14 +74,14 @@
7574
});
7675
}
7776

78-
var buildAction = createPublishBuildAction("Build", "before Build");
77+
var buildAction = createPublishBuildAction("Build", "auto-backup before Build");
7978

80-
var buildAndPubAction = createPublishBuildAction('Publish into Tournament', "before Publish into Tournament", function () {
79+
var buildAndPubAction = createPublishBuildAction('Publish into Tournament', "auto-backup before Publish into Tournament", function () {
8180
Events.fireEvent("progressUpdate", { message: "Publishing" });
8281
API.publish({});
8382
});
8483

85-
var sandbox = createPublishBuildAction('Run in Sandbox', "before Run in Sandbox", function () {
84+
var sandbox = createPublishBuildAction('Run in Sandbox', "auto-backup before Run in Sandbox", function () {
8685
Events.fireEvent("progressUpdate", { message: "Creating new sandbox" });
8786
API.createSandbox({
8887
jsonData:{
@@ -122,13 +121,13 @@
122121
disableActions(true);
123122
var botCode = Editor.getContent();
124123
if (botCode) {
124+
var backupLabel = "auto-backup before Save of '" + label + "'";
125125
API.updateSourceFiles({
126-
// Create a version of the previouse content - if different.
127-
versionLabel: "before Save",
128-
versionPolicy: "ifDifferent",
129-
// --
130-
131-
jsonData:{ files: [ { filename: "Bot.scala", code: botCode} ] },
126+
// Create a version of the previous content - if different.
127+
jsonData:{
128+
versionLabel: backupLabel,
129+
versionPolicy: "ifDifferent",
130+
files: [ { filename: "Bot.scala", code: botCode} ] },
132131
success:function () {
133132
disableActions(false);
134133
Events.fireEvent("documentSaved");
@@ -154,7 +153,6 @@
154153
},
155154

156155
handler:function (c) {
157-
158156
Ext.MessageBox.prompt('Save...', 'Please enter label name:', function(btn, label) {
159157
if(btn = "ok") {
160158
c.saveHandler(label);

0 commit comments

Comments
 (0)