Skip to content

JavaScript

Frank Dana edited this page Jun 15, 2018 · 4 revisions

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.

Table of Contents

Writing JavaScript

Where

Java script can be written in three places:

  • Files
External files can be loaded by using the loadScript( absolute_path ) or loadPluginScript( 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.
  • 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.

How

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.

Evaluation Contexts

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.

Tag Constraints

Two bindings are available within a tag constraint script:

Binding Contents
download The Download object being tested for tag membership
tag The Tag object

Tag Action-On-Assign

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

Auto-Shutdown Action

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.

Examples

Imports, Getting Downloads, Adding a Listener

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() );
             }
        }));
}

Prompt User

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 );

Writing a Plugin in JavaScript

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();
				}
			}
		}
	}
}

Apply Queue Limits to a Tag

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 )
}

Adding a Delay to Auto-Shutdown

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 );
	}
}

Auto-Accepting Torrent Options Dialogs

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 );
				}
			}));
}

Disconnecting Peers Based on Client Name

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 )
}

Assigning to a Tag based on Last Active

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()" )

Periodically Restart Downloads in Error State

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 );
}