JavaScript

From VuzeWiki
Jump to: navigation, search

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

From Vuze 5701 onward the intent is to add the ability to script operations within Vuze 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.

As the JavaScript run time is not available for earlier Java versions (currently Vuze supports Java version 1.5 and higher) a plugin is used to deliver the integration (obviously the plugin is only available if your Java run time is 1.8+)

Writing JavaScript[edit]

Where[edit]

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 Vuze 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[edit]

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[edit]

Scripts can be executed from various contexts within Vuze, 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 Vuze - this is the base object used by plugins to navigate through Vuze features and data model.

Tag Constraints[edit]

Two bindings are available within a tag constraint script:

download    - the Download object being tested for tag membershipt
tag         - the Tag object

Tag Action-On-Assign[edit]

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[edit]

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[edit]

Imports, Getting Downloads, Adding a Listener[edit]

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[edit]

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[edit]

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[edit]

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[edit]

This shows how to add a delay before executing, say, a 'downloading is complete' Vuze shutdown (e.g. you want to leave Vuze 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[edit]

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[edit]

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[edit]

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.

The logic to extract a download's last-active time has been copied from DateLastActiveItem

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[edit]

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