-
Notifications
You must be signed in to change notification settings - Fork 154
JavaScript
Java has supported an integrated JavaScript runtime since version 1.8 (See https://en.wikipedia.org/wiki/Nashorn_(JavaScript_engine) and https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/nashorn/)
The intent is to add the ability to script operations within BiglyBT by using JavaScript directly - previously separate plugins had to be developed in Java to implement complex functionality.
As an initial integration point Tag Constraints have been extended to allow their specification in JavaScript.
Java script can be written in three places:
- Files
- External files can be loaded by using the
loadScript( absolute_path )
orloadPluginScript( relative_path )
functions. - By default the 'init.js' script located in the plugin installation directory is loaded, this contains useful functions for use by other scripts. Do not be tempted to edit this file as it will be overwritten on plugin update.
- External files can be loaded by using the
- Plugin Configuration
- Within the plugin configuration there is a field where script can be entered and evaluated. This is useful for debugging as errors are shown in the log window. Any code entered here is also automatically loaded and can therefore be used by other scripts.
- Invocation Points
- Whenever there is an opportunity to enter JavaScript within BiglyBT at an 'invocation point' (e.g. within a Tag constraint) arbitrary JavaScript can also be entered. However, in general it is suggested that for anything other than fairly trivial script this is not used, rather call a function defined within either the plugin configuration script or in an external file.
There is a global variable available called 'pi' which is a reference to a PluginInterface object - your script should contain itself to working within the existing Plugin Interface framework to ensure future compatability. Scripts will work from this (or from some other context, see below) to fulfill their function.
Scripts can be executed from various contexts within BiglyBT, for example when evaluating a tag constraint. The script will generally need access to various context-relative variables and these are provided to the script by associated bindings to global variables.
One such binding that is always available is the pi
variable - this is a reference to a PluginInterface object within BiglyBT - this is the base object used by plugins to navigate through BiglyBT features and data model.
Two bindings are available within a tag constraint script:
Binding | Contents |
---|---|
download
|
The Download object being tested for tag membership |
tag
|
The Tag object |
When a script is configured as an 'action on assign' option for a tag the evaluation context is the same as for a tag constraint: download
and tag
Under Tools->Options->Startup & Shutdown: shutdown
various actions can be defined to be executed on downloading or seeding complete. The 'script' option allows selection of a file to be run or alternatively for JavaScript to be run by entering javascript( .... )
.
One binding is available, is_downloading_complete
, a boolean indicating if the action has been triggered by a downloading complete event or not.
var imports = new JavaImporter(
org.gudy.azureus2.plugins,
org.gudy.azureus2.plugins.download );
with( imports ){
var download_manager = pi.getDownloadManager();
var downloads = Java.from( download_manager.getDownloads());
downloads.forEach(function(i){
print(">>> " + i.getName());
});
download_manager.addListener(
new DownloadManagerListener({
downloadAdded: function( download ) {
print( "Added: " + download.getName() );
},
downloadRemoved: function( download ) {
print( "Removed: " + download.getName() );
}
}));
}
The following code will prompt the user and block until they respond:
var uis = pi.getUIManager().getUIInstances();
var options = ["one","two","three"];
var result = uis[0].promptUser( "Decide!", "Pick your poison", options, 0 );
print( result );
With a small amount of wrapper code it is straight forward to write a plugin itself in JavaScript. The code below shows how to evaluate a print statement that accesses the plugin interface of the wrapper plugin. A more realistic scenario would have the plugin code in a separate file distributed with the plugin and loaded via a loadPluginScript
call.
public class
JSPlugin
implements Plugin
{
@Override
public void
initialize(
PluginInterface pi )
throws PluginException
{
List<ScriptProvider> providers = pi.getUtilities().getScriptProviders();
for ( ScriptProvider provider: providers ){
if ( provider.getScriptType() == ScriptProvider.ST_JAVASCRIPT ){
String script = "print( \"hello world\" + pi.getPluginName())";
Map<String,Object> bindings = new HashMap<String, Object>();
bindings.put( "pi", pi );
try{
provider.eval(
script,
bindings );
}catch( Throwable e ){
e.printStackTrace();
}
}
}
}
}
This example shows how to attach a listener to a Tag and act on associated Tag events. It keeps track of listeners added so that they can be removed if the script is reloaded for testing purposes.
At the bottom of the script there is a test case that assigns a maximum active value of 1 to a Tag named 'maxtest' - change this and add additional calls as required.
var tag_data = ( typeof tag_data === 'undefined' )?new Array():tag_data;
for ( var i in tag_data ){
var data = tag_data[i];
data['tag'].removeListener( data['listener'] );
}
tag_data.length=0;
with( vuzeimports ){
function orderDownloads( downloads )
{
downloads.sort(
function(d1,d2){
var state1 = d1.getState();
var state2 = d2.getState();
var stopped1 = (state1 == 7 || state1 == 8 ) && !d1.isPaused();
var stopped2 = (state2 == 7 || state2 == 8 ) && !d2.isPaused();
var pos1 = d1.getPosition();
var pos2 = d2.getPosition();
if ( stopped1 == stopped2 ){
if ( stopped1 ){
return( pos1 - pos2 );
}else{
var comp1 = d1.isComplete();
var comp2 = d2.isComplete();
if ( comp1 == comp2 ){
if ( comp1 ){
var rank1 = d1.getSeedingRank();
var rank2 = d2.getSeedingRank();
var res = rank2 - rank1;
if ( res == 0 ){
return( pos1 - pos2 );
}else{
return( res );
}
}else{
return( pos1 - pos2 );
}
}else if ( comp1 ){
return( 1 );
}else{
return( -1 );
}
}
}else if ( stopped1 ){
return( 1 );
}else{
return( -1 );
}
});
}
function checkMax( tag )
{
for ( var i in tag_data ){
var data = tag_data[i];
if ( data['tag'] == tag ){
var max_active = data['max'];
//print( "check: " + data['tag'].getTagName() + " -> " + max_active );
var jdownloads = tag.getTaggables();
var downloads = Java.from( jdownloads );
orderDownloads( downloads );
for ( var d in downloads ){
var download = downloads[d];
//print( " " + download.getName());
var state = download.getState();
if ( d < max_active ){
if ( download.isPaused()){
download.resume();
}
}else{
if ( state != 7 && state != 8 && !download.isPaused()){
download.pause();
}
}
}
return;
}
}
tag.removeListener( listener );
}
function tagMaxActive( tag_name, max )
{
var tag = pi.getUtilities().lookupTag( tag_name );
for ( var i in tag_data ){
var data = tag_data[i];
if ( data['tag'] == tag ){
data['max'] = max;
found = true;
return
}
}
var listener =
new TagListener({
taggableAdded: function( tag, taggable ){
checkMax( tag );
},
taggableRemoved: function( tag, taggable ){
checkMax( tag );
},
taggableSync: function( tag ){
checkMax( tag );
}});
tag_data.push( { 'tag': tag, 'listener': listener, 'max': max });
tag.addListener( listener );
}
tagMaxActive( "maxtest", 1 )
}
This shows how to add a delay before executing, say, a 'downloading is complete' BiglyBT shutdown (e.g. you want to leave BiglyBT running for a couple of hours after this event)
Set the 'shutdown' action in Tools->Options->Startup & Shutdown: shutdown
to be a script and enter (say) the following as the script to execute:
javascript( downloadingComplete())
Next define the function that will be called in the JavaScript plugin's 'General Script' area (note that the schedule period is in milliseconds)
with( vuzeimports ){
function downloadingComplete()
{
var timer = new java.util.Timer();
timer.schedule(
function(){
pi.getPluginManager().executeCloseAction( PluginManager.CA_QUIT_VUZE );
}, 2*60*60*1000 );
}
}
Sometimes it can take a while to download magnet links and if the operation completes when you are away from the computer it might be nice to auto-accept the options and start downloading, rather than have it sitting there waiting for your input for hours. The script below shows how to hook into the options process, starts a timer (10 seconds for testing) which then assigns a tag 'kimchi' to the torrent and auto-accepts it.
with( vuzeimports ){
var timer = new java.util.Timer();
var torrent_manager = pi.getTorrentManager();
var tag_manager = pi.getUtilities().getTagManager();
var tag = tag_manager.lookupTag( 'kimchi' );
if ( tag == null ){
tag = tag_manager.createTag( 'kimchi' );
}
torrent_manager.addListener(
new TorrentManagerListener(
function( ev )
{
var type = ev.getType();
if ( type == org.gudy.azureus2.plugins.torrent.TorrentManagerEvent.ET_TORRENT_OPTIONS_CREATED ){
var options = ev.getData();
timer.schedule(
function()
{
options.addTag( tag );
options.accept();
}, 10*1000 );
}
}));
}
This script disconnects clients with the word "Torrent" in their name when they connect.
with( vuzeimports ){
var dm_listener
var dl_peer_listener
var download_manager = pi.getDownloadManager()
if ( typeof dm_listener !== 'undefined' ){
download_manager.removeListener( dm_listener )
if ( typeof dl_peer_listener !== 'undefined' ){
var downloads = Java.from( download_manager.getDownloads());
downloads.forEach(function(download){
download.removePeerListener( dl_peer_listener )
})
}
}
var pm_listener =
new PeerManagerListener2({
eventOccurred: function( pm_event ){
if ( pm_event.getType() == org.gudy.azureus2.plugins.peers.PeerManagerEvent.ET_PEER_ADDED ){
var peer = pm_event getPeer();
var peer_listener =
new org.gudy.azureus2.plugins.peers.PeerListener2({
eventOccurred: function( peer_event ){
if ( peer_event.getType() == org.gudy.azureus2.plugins.peers.PeerEvent.ET_STATE_CHANGED ){
if ( peer_event.getData() == org.gudy.azureus2.plugins.peers.Peer.TRANSFERING ){
var client = peer.getClient();
if ( client.contains( "Torrent" )){
peer.getManager().removePeer( peer )
}
}
}
}
})
peer.addListener( peer_listener )
}
}
})
dl_peer_listener =
new DownloadPeerListener({
peerManagerAdded: function( download, peer_manager ){
peer_manager.addListener( pm_listener )
},
peerManagerRemoved: function( download, peer_manager ) {
}
})
dm_listener =
new DownloadManagerListener({
downloadAdded: function( download ){
download.addPeerListener( dl_peer_listener );
},
downloadRemoved: function( download ) {
}
})
download_manager.addListener( dm_listener )
}
This demonstrates how to assign downloads to a Tag based on when they were last active (since June 2016 in the example). It uses access to some core (non-plugin) interfaces that are not guaranteed to be stable over time, so be aware that things may break in the future.
Insert this function into the JavaScript area of the plugin's configuration page (or load from an external file there)
function testLastActive()
{
var dm_state = org.gudy.azureus2.pluginsimpl.local.PluginCoreUtils.unwrap( download ).getDownloadState();
var timestamp = dm_state.getLongParameter(org.gudy.azureus2.core3.download.DownloadManagerState.PARAM_DOWNLOAD_LAST_ACTIVE_TIME);
if ( timestamp == 0 ){
timestamp = dm_state.getLongParameter(org.gudy.azureus2.core3.download.DownloadManagerState.PARAM_DOWNLOAD_COMPLETED_TIME);
}
if ( timestamp == 0 ){
timestamp = dm_state.getLongParameter(org.gudy.azureus2.core3.download.DownloadManagerState.PARAM_DOWNLOAD_ADDED_TIME);
}
var time = new java.text.SimpleDateFormat( "yyyy/MM/dd" ).parse( "2016/06/01" ).getTime();
return( timestamp >= time );
}
Set the tag's constraint to be javascript( "testLastActive()" )
This code will restart downloads in an error state (8), initially after 1 minute and then every 5 minutes after that.
Insert this function into the JavaScript area of the plugin's configuration page (or load from an external file there)
with( vuzeimports ){
var timer = new java.util.Timer();
timer.schedule(
function(){
var download_manager = pi.getDownloadManager();
var downloads = Java.from( download_manager.getDownloads());
downloads.forEach(function(download){
if ( download.getState() == 8 ){
try{
download.stopAndQueue()
}catch( error ){
}
}
});
}, 60*1000, 5*60*1000 );
}
bigly help