Improvements to classroom.js module (added players/ directory into which players can drop their custom scripts when classroom.allowScripting(true) is called.

This commit is contained in:
walterhiggins 2014-02-10 20:55:32 +00:00
parent 39b459ab7f
commit 8453525da6
6 changed files with 327 additions and 87 deletions

View file

@ -68,6 +68,8 @@ Walter Higgins
* [utils.nicely() function](#utilsnicely-function)
* [utils.at() function](#utilsat-function)
* [utils.find() function](#utilsfind-function)
* [utils.serverAddress() function](#utilsserveraddress-function)
* [utils.watchFile() function](#utilswatchfile-function)
* [Drone Plugin](#drone-plugin)
* [TLDNR; (Just read this if you're impatient)](#tldnr-just-read-this-if-youre-impatient)
* [Constructing a Drone Object](#constructing-a-drone-object)
@ -1338,6 +1340,34 @@ var jsFiles = utils.find('./', function(dir,name){
});
```
### utils.serverAddress() function
The utils.serverAddress() function returns the IP(v4) address of the server.
```javascript
var utils = require('utils');
var serverAddress = utils.serverAddress();
console.log(serverAddress);
```
### utils.watchFile() function
Watches for changes to the given file or directory and calls the function provided
when the file changes.
#### Parameters
* File - the file to watch (can be a file or directory)
* Callback - The callback to invoke when the file has changed. The callback takes the
changed file as a parameter.
#### Example
```javascript
var utils = require('utils');
utils.watchFile( 'test.txt', function( file ) {
console.log( file + ' has changed');
});
```
## Drone Plugin
The Drone is a convenience class for building. It can be used for...
@ -2560,8 +2590,44 @@ quickly realise how to grant themselves and others operator privileges
once they have access to ScriptCraft).
The goal of this module is not so much to enforce restrictions
(security or otherwise) but to make it easier for tutors to setup a shared server
so students can learn Javascript.
(security or otherwise) but to make it easier for tutors to setup a
shared server so students can learn Javascript. When scripting is
turned on, every player who joins the server will have a dedicated
directory into which they can save scripts. All scripts in such
directories are automatically watched and loaded into a global
variable named after the player.
So for example, if player 'walterh' joins the server, a `walterh`
global variable is created. If a file `greet.js` with the following
content is dropped into the `plugins/scriptcraft/players/walterh`
directory...
```javascript
exports.hi = function( player ){
player.sendMessage('Hi ' + player.name);
};
```
... then it can be invoked like this: `/js walterh.hi( self )` . This
lets every player/student create their own functions without having
naming collisions.
It's strongly recommended that the
`craftbukkit/plugins/scriptcraft/players/` directory is shared so that
others can connect to it and drop .js files into their student
directories. On Ubuntu, select the folder in Nautilus (the default
file browser) then right-click and choose *Sharing Options*, check the
*Share this folder* checkbox and the *Allow others to create and
delete files* and *Guest access* checkboxes. Click *Create Share*
button to close the sharing options dialog. Students can then access
the shared folder as follows...
* Windows: Open Explorer, Go to \\{serverAddress}\players\
* Macintosh: Open Finder, Go to smb://{serverAddress}/players/
* Linux: Open Nautilus, Go to smb://{serverAddress}/players/
... where {serverAddress} is the ip address of the server (this is
displayed to whoever invokes the classroom.allowScripting() function.)
### classroom.allowScripting() function
@ -2569,6 +2635,11 @@ Allow or disallow anyone who connects to the server (or is already
connected) to use ScriptCraft. This function is preferable to granting 'ops' privileges
to every student in a Minecraft classroom environment.
Whenever any file is added/edited or removed from any of the players/
directories the contents are automatically reloaded. This is to
facilitate quick turnaround time for students getting to grips with
Javascript.
#### Parameters
* canScript : true or false

View file

@ -31,15 +31,7 @@ var _plugin = function(/* String */ moduleName, /* Object */ moduleObject, isPer
exports.plugin = _plugin;
var scriptCraftDir = null;
var pluginDir = null;
var dataDir = null;
exports.autoload = function( dir, logger ) {
scriptCraftDir = dir;
pluginDir = new File( dir, 'plugins' );
dataDir = new File( dir, 'data' );
exports.autoload = function( context, pluginDir, logger, options ) {
var _canonize = function( file ) {
return '' + file.canonicalPath.replaceAll('\\\\','/');
@ -76,22 +68,26 @@ exports.autoload = function( dir, logger ) {
var len = sourceFiles.length;
if ( config.verbose ) {
console.info( len + ' scriptcraft plugins found.' );
console.info( len + ' scriptcraft plugins found in ' + pluginDir );
}
for ( var i = 0; i < len; i++ ) {
pluginPath = _canonize( sourceFiles[i] );
module = {};
try {
module = require( pluginPath );
module = require( pluginPath , options);
for ( property in module ) {
/*
all exports in plugins become global
all exports in plugins become members of context object
*/
global[property] = module[property];
context[property] = module[property];
}
} catch ( e ) {
logger.severe( 'Plugin ' + pluginPath + ' ' + e );
if ( typeof logger != 'undefined' ) {
logger.severe( 'Plugin ' + pluginPath + ' ' + e );
} else {
java.lang.System.out.println( 'Error: Plugin ' + pluginPath + ' ' + e );
}
}
}
}(pluginDir));

View file

@ -136,57 +136,56 @@ When resolving module names to file paths, ScriptCraft uses the following rules.
***/
var resolveModuleToFile = function ( moduleName, parentDir ) {
var file = new File(moduleName);
var file = new File(moduleName),
i = 0,
pathWithJSExt,
resolvedFile;
if ( file.exists() ) {
return fileExists(file);
}
if ( moduleName.match( /^[^\.\/]/ ) ) {
// it's a module named like so ... 'events' , 'net/http'
//
var resolvedFile;
for (var i = 0;i < modulePaths.length; i++){
for ( ; i < modulePaths.length; i++ ) {
resolvedFile = new File(modulePaths[i] + moduleName);
if (resolvedFile.exists()){
if ( resolvedFile.exists() ) {
return fileExists(resolvedFile);
}else{
} else {
// try appending a .js to the end
resolvedFile = new File(modulePaths[i] + moduleName + '.js');
if (resolvedFile.exists())
if ( resolvedFile.exists() ) {
return resolvedFile;
}
}
}
} else {
// it's of the form ./path
file = new File(parentDir, moduleName);
if (file.exists()){
if ( file.exists() ) {
return fileExists(file);
}else {
} else {
// try appending a .js to the end
var pathWithJSExt = file.canonicalPath + '.js';
file = new File( parentDir, pathWithJSExt);
if (file.exists())
pathWithJSExt = file.canonicalPath + '.js';
file = new File( parentDir, pathWithJSExt );
if (file.exists()) {
return file;
else{
} else {
file = new File(pathWithJSExt);
if (file.exists())
if ( file.exists() ) {
return file;
}
}
}
}
return null;
};
/*
wph 20131215 Experimental
*/
var _loadedModules = {};
var _format = java.lang.String.format;
/*
require() function implementation
*/
var _require = function( parentFile, path ) {
var _require = function( parentFile, path, options ) {
var file,
canonizedFilename,
moduleInfo,
@ -194,7 +193,15 @@ When resolving module names to file paths, ScriptCraft uses the following rules.
head = '(function(exports,module,require,__filename,__dirname){ ',
code = '',
line = null;
if ( typeof options == 'undefined' ) {
options = { cache: true };
} else {
if ( typeof options.cache == 'undefined' ) {
options.cache = true;
}
}
file = resolveModuleToFile(path, parentFile);
if ( !file ) {
var errMsg = '' + _format("require() failed to find matching file for module '%s' " +
@ -205,10 +212,12 @@ When resolving module names to file paths, ScriptCraft uses the following rules.
throw errMsg;
}
canonizedFilename = _canonize(file);
moduleInfo = _loadedModules[canonizedFilename];
if ( moduleInfo ) {
return moduleInfo;
if ( options.cache ) {
return moduleInfo;
}
}
if ( hooks ) {
hooks.loading( canonizedFilename );
@ -228,7 +237,9 @@ When resolving module names to file paths, ScriptCraft uses the following rules.
var tail = '})';
code = head + code + tail;
_loadedModules[canonizedFilename] = moduleInfo;
if ( options.cache ) {
_loadedModules[canonizedFilename] = moduleInfo;
}
var compiledWrapper = null;
try {
compiledWrapper = eval(code);
@ -258,8 +269,8 @@ When resolving module names to file paths, ScriptCraft uses the following rules.
};
var _requireClosure = function( parent ) {
return function( path ) {
var module = _require( parent, path );
return function( path, options ) {
var module = _require( parent, path , options);
return module.exports;
};
};

View file

@ -491,7 +491,7 @@ function __onEnable ( __engine, __plugin, __script )
try {
while ( (r = br.readLine()) !== null ) {
code += r + '\n';
}
}
wrappedCode = '(' + code + ')';
result = __engine.eval( wrappedCode );
// issue #103 avoid side-effects of || operator on Mac Rhino
@ -566,7 +566,7 @@ function __onEnable ( __engine, __plugin, __script )
setup paths to search for modules
*/
var modulePaths = [ jsPluginsRootDirName + '/lib/',
jsPluginsRootDirName + '/modules/' ];
jsPluginsRootDirName + '/modules/' ];
if ( config.verbose ) {
logger.info( 'Setting up CommonJS-style module system. Root Directory: ' + jsPluginsRootDirName );
@ -612,27 +612,44 @@ function __onEnable ( __engine, __plugin, __script )
global.__onCommand = function( sender, cmd, label, args) {
var jsArgs = [];
var i = 0;
var jsArgs = [],
i = 0,
jsResult,
result,
cmdName,
fnBody;
for ( ; i < args.length ; i++ ) {
jsArgs.push( '' + args[i] );
}
var result = false;
var cmdName = ( '' + cmd.name ).toLowerCase();
result = false;
cmdName = ( '' + cmd.name ).toLowerCase();
if (cmdName == 'js')
{
result = true;
var fnBody = jsArgs.join(' ');
fnBody = jsArgs.join(' ');
global.self = sender;
global.__engine = __engine;
try {
var jsResult = __engine.eval(fnBody);
if ( jsResult ) {
sender.sendMessage(jsResult);
}
if ( typeof eval == 'undefined' ) {
jsResult = __engine.eval(fnBody);
} else {
/*
nashorn
https://bugs.openjdk.java.net/browse/JDK-8034055
*/
jsResult = eval( fnBody );
}
if ( typeof jsResult != 'undefined' ) {
if ( jsResult == null) {
sender.sendMessage('(null)');
} else {
sender.sendMessage(jsResult);
}
}
} catch ( e ) {
logger.severe( 'Error while trying to evaluate javascript: ' + fnBody + ', Error: '+ e );
sender.sendMessage( 'Error while trying to evaluate javascript: ' + fnBody + ', Error: '+ e );
throw e;
} finally {
delete global.self;
@ -646,7 +663,7 @@ function __onEnable ( __engine, __plugin, __script )
return result;
};
plugins.autoload( jsPluginsRootDir, logger );
plugins.autoload( global, new File(jsPluginsRootDir,'plugins'), logger );
/*
wph 20140102 - warn if legacy 'craftbukkit/js-plugins' or 'craftbukkit/scriptcraft' directories are present
*/
@ -655,21 +672,21 @@ function __onEnable ( __engine, __plugin, __script )
cbDir = new File(cbPluginsDir.canonicalPath).parentFile,
legacyExists = false,
legacyDirs = [new File( cbDir, 'js-plugins' ),
new File( cbDir, 'scriptcraft' )];
new File( cbDir, 'scriptcraft' )];
for ( var i = 0; i < legacyDirs.length; i++ ) {
if ( legacyDirs[i].exists()
&& legacyDirs[i].isDirectory() ) {
&& legacyDirs[i].isDirectory() ) {
legacyExists = true;
legacyExists = true;
console.warn('Legacy ScriptCraft directory %s was found. This directory is no longer used.',
console.warn('Legacy ScriptCraft directory %s was found. This directory is no longer used.',
legacyDirs[i].canonicalPath);
}
}
if ( legacyExists ) {
console.info( 'Please note that the working directory for %s is %s',
__plugin, jsPluginsRootDir.canonicalPath );
__plugin, jsPluginsRootDir.canonicalPath );
}
})();

View file

@ -459,3 +459,79 @@ exports.find = function( dir , filter ) {
recurse( dir, result );
return result;
};
/************************************************************************
### utils.serverAddress() function
The utils.serverAddress() function returns the IP(v4) address of the server.
```javascript
var utils = require('utils');
var serverAddress = utils.serverAddress();
console.log(serverAddress);
```
***/
exports.serverAddress = function() {
var interfaces = java.net.NetworkInterface.getNetworkInterfaces();
var current,
addresses,
current_addr;
while ( interfaces.hasMoreElements() ) {
current = interfaces.nextElement();
if ( ! current.isUp() || current.isLoopback() || current.isVirtual() ) {
continue;
}
addresses = current.getInetAddresses();
while (addresses.hasMoreElements()) {
current_addr = addresses.nextElement();
if ( current_addr.isLoopbackAddress() )
continue;
if ( current_addr instanceof java.net.Inet4Address)
return current_addr.getHostAddress();
}
}
return null;
};
/************************************************************************
### utils.watchFile() function
Watches for changes to the given file or directory and calls the function provided
when the file changes.
#### Parameters
* File - the file to watch (can be a file or directory)
* Callback - The callback to invoke when the file has changed. The callback takes the
changed file as a parameter.
#### Example
```javascript
var utils = require('utils');
utils.watchFile( 'test.txt', function( file ) {
console.log( file + ' has changed');
});
```
***/
var filesWatched = {};
exports.watchFile = function( file, callback ) {
var File = java.io.File;
if ( typeof file == 'string' ) {
file = new File(file);
}
filesWatched[file.canonicalPath] = {
callback: callback,
lastModified: file.lastModified()
};
};
function fileWatcher() {
for (var file in filesWatched) {
var fileObject = new File(file);
var lm = fileObject.lastModified();
if ( lm != filesWatched[file].lastModified ) {
filesWatched[file].lastModified = lm;
filesWatched[file].callback(fileObject);
}
}
setTimeout( fileWatcher, 5000 );
};
setTimeout( fileWatcher, 5000 );

View file

@ -1,4 +1,10 @@
var foreach = require('utils').foreach;
var utils = require('utils'),
autoload = require('plugin').autoload,
logger = __plugin.logger,
foreach = utils.foreach,
watchFile = utils.watchFile,
playersDir = __dirname + '/../../players/',
serverAddress = utils.serverAddress();
/************************************************************************
## Classroom Plugin
@ -14,8 +20,44 @@ quickly realise how to grant themselves and others operator privileges
once they have access to ScriptCraft).
The goal of this module is not so much to enforce restrictions
(security or otherwise) but to make it easier for tutors to setup a shared server
so students can learn Javascript.
(security or otherwise) but to make it easier for tutors to setup a
shared server so students can learn Javascript. When scripting is
turned on, every player who joins the server will have a dedicated
directory into which they can save scripts. All scripts in such
directories are automatically watched and loaded into a global
variable named after the player.
So for example, if player 'walterh' joins the server, a `walterh`
global variable is created. If a file `greet.js` with the following
content is dropped into the `plugins/scriptcraft/players/walterh`
directory...
```javascript
exports.hi = function( player ){
player.sendMessage('Hi ' + player.name);
};
```
... then it can be invoked like this: `/js walterh.hi( self )` . This
lets every player/student create their own functions without having
naming collisions.
It's strongly recommended that the
`craftbukkit/plugins/scriptcraft/players/` directory is shared so that
others can connect to it and drop .js files into their student
directories. On Ubuntu, select the folder in Nautilus (the default
file browser) then right-click and choose *Sharing Options*, check the
*Share this folder* checkbox and the *Allow others to create and
delete files* and *Guest access* checkboxes. Click *Create Share*
button to close the sharing options dialog. Students can then access
the shared folder as follows...
* Windows: Open Explorer, Go to \\{serverAddress}\players\
* Macintosh: Open Finder, Go to smb://{serverAddress}/players/
* Linux: Open Nautilus, Go to smb://{serverAddress}/players/
... where {serverAddress} is the ip address of the server (this is
displayed to whoever invokes the classroom.allowScripting() function.)
### classroom.allowScripting() function
@ -23,6 +65,11 @@ Allow or disallow anyone who connects to the server (or is already
connected) to use ScriptCraft. This function is preferable to granting 'ops' privileges
to every student in a Minecraft classroom environment.
Whenever any file is added/edited or removed from any of the players/
directories the contents are automatically reloaded. This is to
facilitate quick turnaround time for students getting to grips with
Javascript.
#### Parameters
* canScript : true or false
@ -42,51 +89,73 @@ Only ops users can run the classroom.allowScripting() function - this is so that
don't try to bar themselves and each other from scripting.
***/
var _store = { enableScripting: false };
var _store = { enableScripting: false },
File = java.io.File;
function revokeScripting ( player ) {
foreach( player.getEffectivePermissions(), function( perm ) {
if ( (''+perm.permission).indexOf( 'scriptcraft.' ) == 0 ) {
if ( perm.attachment ) {
perm.attachment.remove();
}
}
});
}
function grantScripting( player ) {
console.log('Enabling scripting for player ' + player.name);
var playerName = '' + player.name;
playerName = playerName.replace(/[^a-zA-Z0-9_\-]/g,'');
var playerDir = new File( playersDir + playerName );
playerDir.mkdirs();
player.addAttachment( __plugin, 'scriptcraft.*', true );
var playerContext = {};
autoload( playerContext, playerDir, logger, { cache: false });
global[playerName] = playerContext;
watchFile( playerDir, function( changedDir ){
autoload(playerContext, playerDir, logger, { cache: false });
});
/*
player.sendMessage('Create your own minecraft mods by adding javascript (.js) files');
player.sendMessage(' Windows: Open Explorer, go to \\\\' + serverAddress + '\\players\\' + player.name);
player.sendMessage(' Macintosh: Open Finder, Go to smb://' + serverAddress + '/players/' + player.name);
player.sendMessage(' Linux: Open Nautilus, Go to smb://' + serverAddress + '/players/' + player.name);
*/
}
var classroom = plugin('classroom', {
allowScripting: function (/* boolean: true or false */ canScript, sender ) {
if ( typeof sender == 'undefined' ) {
sender = utils.player(sender);
if ( !sender ) {
console.log( 'Attempt to set classroom scripting without credentials' );
console.log( 'classroom.allowScripting(boolean, sender)' );
return;
}
if ( !sender.op ) {
console.log( 'Attempt to set classroom scripting without credentials: ' + sender.name );
return;
}
/*
only operators should be allowed run this function
*/
if ( !sender.isOp() ) {
if ( !sender.op ) {
console.log( 'Attempt to set classroom scripting without credentials: ' + sender.name );
sender.sendMessage('Only operators can use this function');
return;
}
if ( canScript ) {
foreach( server.onlinePlayers, function( player ) {
player.addAttachment( __plugin, 'scriptcraft.*', true );
});
} else {
foreach( server.onlinePlayers, function( player ) {
foreach( player.getEffectivePermissions(), function( perm ) {
if ( (''+perm.permission).indexOf( 'scriptcraft.' ) == 0 ) {
if ( perm.attachment ) {
perm.attachment.remove();
}
}
});
});
}
foreach( server.onlinePlayers, canScript ? grantScripting : revokeScripting);
_store.enableScripting = canScript;
sender.sendMessage('Scripting turned ' + ( canScript ? 'on' : 'off' ) +
' for all players on server ' + serverAddress);
},
store: _store
}, true);
exports.classroom = classroom;
events.on( 'player.PlayerLoginEvent', function( listener, event ) {
var player = event.player;
events.on( 'player.PlayerJoinEvent', function( listener, event ) {
if ( _store.enableScripting ) {
player.addAttachment( __plugin, 'scriptcraft.*', true );
grantScripting(event.player);
}
}, 'HIGHEST');