Package com.aelitis.azureus.plugins.net.buddy

Source Code of com.aelitis.azureus.plugins.net.buddy.BuddyPlugin

/*
* Created on Mar 19, 2008
* Created by Paul Gardner
*
* Copyright 2008 Vuze, Inc.  All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License only.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*/


package com.aelitis.azureus.plugins.net.buddy;

import java.io.*;
import java.net.*;
import java.util.*;


import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.download.DownloadManager;
import org.gudy.azureus2.core3.download.DownloadManagerState;
import org.gudy.azureus2.core3.util.*;
import org.gudy.azureus2.core3.util.protocol.azplug.AZPluginConnection;
import org.gudy.azureus2.core3.xml.util.XUXmlWriter;
import org.gudy.azureus2.plugins.*;
import org.gudy.azureus2.plugins.ddb.*;
import org.gudy.azureus2.plugins.disk.DiskManagerFileInfo;
import org.gudy.azureus2.plugins.download.Download;
import org.gudy.azureus2.plugins.download.DownloadException;
import org.gudy.azureus2.plugins.download.DownloadScrapeResult;
import org.gudy.azureus2.plugins.ipc.IPCException;
import org.gudy.azureus2.plugins.logging.LoggerChannel;
import org.gudy.azureus2.plugins.messaging.MessageException;
import org.gudy.azureus2.plugins.messaging.MessageManager;
import org.gudy.azureus2.plugins.messaging.generic.GenericMessageConnection;
import org.gudy.azureus2.plugins.messaging.generic.GenericMessageHandler;
import org.gudy.azureus2.plugins.messaging.generic.GenericMessageRegistration;
import org.gudy.azureus2.plugins.network.RateLimiter;
import org.gudy.azureus2.plugins.torrent.Torrent;
import org.gudy.azureus2.plugins.torrent.TorrentAttribute;
import org.gudy.azureus2.plugins.ui.UIInstance;
import org.gudy.azureus2.plugins.ui.UIManagerListener;
import org.gudy.azureus2.plugins.ui.config.*;
import org.gudy.azureus2.plugins.ui.menus.MenuItem;
import org.gudy.azureus2.plugins.ui.menus.MenuItemFillListener;
import org.gudy.azureus2.plugins.ui.menus.MenuItemListener;
import org.gudy.azureus2.plugins.ui.model.BasicPluginConfigModel;
import org.gudy.azureus2.plugins.ui.tables.TableContextMenuItem;
import org.gudy.azureus2.plugins.ui.tables.TableManager;
import org.gudy.azureus2.plugins.ui.tables.TableRow;
import org.gudy.azureus2.plugins.utils.*;
import org.gudy.azureus2.plugins.utils.security.SEPublicKey;
import org.gudy.azureus2.plugins.utils.security.SEPublicKeyLocator;
import org.gudy.azureus2.plugins.utils.security.SESecurityManager;
import org.gudy.azureus2.pluginsimpl.local.PluginCoreUtils;
import org.gudy.azureus2.ui.swt.plugins.UISWTInstance;

import com.aelitis.azureus.core.AzureusCoreFactory;
import com.aelitis.azureus.core.security.*;
import com.aelitis.azureus.core.util.AZ3Functions;
import com.aelitis.azureus.core.util.CopyOnWriteList;
import com.aelitis.azureus.core.util.bloom.BloomFilter;
import com.aelitis.azureus.core.util.bloom.BloomFilterFactory;
import com.aelitis.azureus.plugins.magnet.MagnetPlugin;
import com.aelitis.azureus.plugins.magnet.MagnetPluginProgressListener;
import com.aelitis.azureus.plugins.net.buddy.swt.BuddyPluginView;
import com.aelitis.azureus.plugins.net.buddy.tracker.BuddyPluginTracker;

public class
BuddyPlugin
  implements Plugin
{
  public static final boolean SUPPORT_ONLINE_STATUS    = true;
 
  public static final int VERSION_INITIAL  = 1;
  public static final int VERSION_CHAT  = 2;
  public static final int VERSION_CURRENT  = VERSION_CHAT;
 
 
  public static final int MT_V3_CHAT    = 1;
 
  private static final int FEED_UPDATE_MIN_MILLIS  = 6*60*60*1000;
 
  public static final int MAX_MESSAGE_SIZE  = 4*1024*1024;
 
  public static final int  SUBSYSTEM_INTERNAL  = 0;
  public static final int  SUBSYSTEM_AZ2    = 1;
  public static final int  SUBSYSTEM_AZ3    = 2;
 
  protected static final int  SUBSYSTEM_MSG_TYPE_BASE  = 1024;
 
  public static final int STATUS_ONLINE      = 0;
  public static final int STATUS_AWAY        = 1;
  public static final int STATUS_NOT_AVAILABLE  = 2;
  public static final int STATUS_BUSY        = 3;
  public static final int STATUS_APPEAR_OFFLINE  = 4;
 
  public static final String[] STATUS_VALUES   = { "0", "1", "2", "3", "4" };
 
  public static final String[] STATUS_KEYS = {
    "os_online", "os_away", "os_not_avail", "os_busy", "os_offline"
  };

  public static final String[] STATUS_STRINGS = new String[ STATUS_KEYS.length ];
 
  protected static final int RT_INTERNAL_REQUEST_PING    = 1;
  protected static final int RT_INTERNAL_REPLY_PING    = 2;
  protected static final int RT_INTERNAL_REQUEST_CLOSE  = 3;
  protected static final int RT_INTERNAL_REPLY_CLOSE    = 4;
  protected static final int RT_INTERNAL_FRAGMENT      = 5;
 
  protected static final boolean TRACE = false;

  private static final String VIEW_ID = "azbuddy";

  private static final int  INIT_UNKNOWN    = 0;
  private static final int  INIT_OK        = 1;
  private static final int  INIT_BAD      = 2;
 
  private static final int  MAX_UNAUTH_BUDDIES  = 16;
 
  public static final int  TIMER_PERIOD  = 10*1000;
 
  private static final int  BUDDY_STATUS_CHECK_PERIOD_MIN  = 3*60*1000;
  private static final int  BUDDY_STATUS_CHECK_PERIOD_INC  = 1*60*1000;
 
  protected static final int  STATUS_REPUBLISH_PERIOD    = 10*60*1000;
  private static final int  STATUS_REPUBLISH_TICKS    = STATUS_REPUBLISH_PERIOD/TIMER_PERIOD;

  private static final int  CHECK_YGM_PERIOD      = 5*60*1000;
  private static final int  CHECK_YGM_TICKS        = CHECK_YGM_PERIOD/TIMER_PERIOD;
  private static final int  YGM_BLOOM_LIFE_PERIOD    = 60*60*1000;
  private static final int  YGM_BLOOM_LIFE_TICKS    = YGM_BLOOM_LIFE_PERIOD/TIMER_PERIOD;

  private static final int  SAVE_CONFIG_PERIOD      = 60*1000;
  private static final int  SAVE_CONFIG_TICKS      = SAVE_CONFIG_PERIOD/TIMER_PERIOD;

  public static final int    PERSISTENT_MSG_RETRY_PERIOD    = 5*60*1000;
  private static final int  PERSISTENT_MSG_CHECK_PERIOD    = 60*1000;
  private static final int  PERSISTENT_MSG_CHECK_TICKS    = PERSISTENT_MSG_CHECK_PERIOD/TIMER_PERIOD;

  private static final int  UNAUTH_BLOOM_RECREATE    = 120*1000;
  private static final int  UNAUTH_BLOOM_CHUNK      = 1000;
  private static BloomFilter  unauth_bloom;
  private static long      unauth_bloom_create_time;

  private static final int  BLOOM_CHECK_PERIOD      = UNAUTH_BLOOM_RECREATE/2;
  private static final int  BLOOM_CHECK_TICKS      = BLOOM_CHECK_PERIOD/TIMER_PERIOD;

  private static BloomFilter  ygm_unauth_bloom;

  public static final int STREAM_CRYPTO   = MessageManager.STREAM_ENCRYPTION_RC4_REQUIRED;
  public static final int BLOCK_CRYPTO  = SESecurityManager.BLOCK_ENCRYPTION_AES;
 
  //public static final int STREAM_CRYPTO   = MessageManager.STREAM_ENCRYPTION_NONE;
  //public static final int BLOCK_CRYPTO  = SESecurityManager.BLOCK_ENCRYPTION_NONE;
 
  private volatile int   initialisation_state = INIT_UNKNOWN;
 
  private PluginInterface  plugin_interface;
 
  private LoggerChannel  logger;
 
  private BooleanParameter     enabled_param;
  private StringParameter     nick_name_param;
  private StringListParameter   online_status_param;
  private BooleanParameter     enable_chat_notifications;
  private StringParameter     cat_pub;

  private boolean      ready_to_publish;
  private publishDetails  current_publish    = new publishDetails();
  private publishDetails  latest_publish    = current_publish;
  private long      last_publish_start;
  private TimerEvent    republish_delay_event;
 
  private AsyncDispatcher  publish_dispatcher = new AsyncDispatcher();
 
  private  DistributedDatabase   ddb;
 
  private CryptoHandler ecc_handler = CryptoManagerFactory.getSingleton().getECCHandler();

  private List<BuddyPluginBuddy>  buddies   = new ArrayList<BuddyPluginBuddy>();
 
  private List<BuddyPluginBuddy>  connected_at_close;
 
  private Map<String,BuddyPluginBuddy>    buddies_map  = new HashMap<String,BuddyPluginBuddy>();
 
  private CopyOnWriteList<BuddyPluginListener>        listeners       = new CopyOnWriteList<BuddyPluginListener>();
  private CopyOnWriteList<BuddyPluginBuddyRequestListener>  request_listeners  = new CopyOnWriteList<BuddyPluginBuddyRequestListener>();
   
  private SESecurityManager  sec_man;

  private GenericMessageRegistration  msg_registration;
   
  private int  inbound_limit;
  private int  outbound_limit;
 
  private RateLimiter  inbound_limiter =
    new RateLimiter()
  {
    public String
    getName()
    {
      return( "buddy_up" );
    }

    public int
    getRateLimitBytesPerSecond()
    {
      return( inbound_limit );
    }
  };
   
  private RateLimiter  outbound_limiter =
    new RateLimiter()
  {
    public String
    getName()
    {
      return( "buddy_down" );
    }

    public int
    getRateLimitBytesPerSecond()
    {
      return( outbound_limit );
    }
  };
     
  private boolean    config_dirty;
 
  private Random  random = RandomUtils.SECURE_RANDOM;
 
  private BuddyPluginAZ2    az2_handler;
 
  private List<DistributedDatabaseContact>  publish_write_contacts = new ArrayList<DistributedDatabaseContact>();
 
  private int    status_seq;
 
  {
    while( status_seq == 0 ){
     
      status_seq = random.nextInt();
    }
  }
   
  private Set<BuddyPluginBuddy>      pd_preinit    = new HashSet<BuddyPluginBuddy>();
 
  private List<BuddyPluginBuddy>      pd_queue     = new ArrayList<BuddyPluginBuddy>();
  private AESemaphore            pd_queue_sem  = new AESemaphore( "BuddyPlugin:persistDispatch");
  private AEThread2            pd_thread;
 
  private boolean    bogus_ygm_written;
 
  private BuddyPluginTracker  buddy_tracker;
 
  private TorrentAttribute  ta_category;

  private Set<String>  public_categories = new HashSet<String>();
 
  public static void
  load(
    PluginInterface    plugin_interface )
  {
    String name =
      plugin_interface.getUtilities().getLocaleUtilities().getLocalisedMessageText( "Views.plugins." + VIEW_ID + ".title" );
   
    plugin_interface.getPluginProperties().setProperty( "plugin.version",   "1.0" );
    plugin_interface.getPluginProperties().setProperty( "plugin.name",     name );
  }

  public void
  initialize(
    final PluginInterface    _plugin_interface )
  {   
    plugin_interface  = _plugin_interface;
   
    ta_category    = plugin_interface.getTorrentManager().getAttribute( TorrentAttribute.TA_CATEGORY );

    az2_handler = new BuddyPluginAZ2( this );
       
    sec_man = plugin_interface.getUtilities().getSecurityManager();

    logger = plugin_interface.getLogger().getChannel( "Friends" );
   
    logger.setDiagnostic();
       
    final LocaleUtilities lu = plugin_interface.getUtilities().getLocaleUtilities();
   
    lu.addListener(
      new LocaleListener()
      {
        public void
        localeChanged(
          Locale    l )
        {
          updateLocale(lu);
        }
      });
   
    updateLocale(lu);
   
    BasicPluginConfigModel config = plugin_interface.getUIManager().createBasicPluginConfigModel( "Views.plugins." + VIEW_ID + ".title" );
     
      // enabled

    enabled_param = config.addBooleanParameter2( "azbuddy.enabled", "azbuddy.enabled", false );
       
      // nickname

    nick_name_param = config.addStringParameter2( "azbuddy.nickname", "azbuddy.nickname", "" );

    nick_name_param.setGenerateIntermediateEvents( false );
   
    nick_name_param.addListener(
        new ParameterListener()
        {
          public void
          parameterChanged(
            Parameter  param )
          {
            updateNickName( nick_name_param.getValue());
          }
        });
   
      // online status

    String[]  os_values   = STATUS_VALUES;
    String[]  os_labels  = STATUS_STRINGS;
   
    online_status_param = config.addStringListParameter2(
        "azbuddy.online_status", "azbuddy.online_status",
        os_values,
        os_labels,
        os_values[0] );
       
    online_status_param.addListener(
        new ParameterListener()
        {
          public void
          parameterChanged(
            Parameter  param )
          {
             int status = Integer.parseInt( online_status_param.getValue());
            
             updateOnlineStatus( status );
          }
        });
   
    online_status_param.setVisible( SUPPORT_ONLINE_STATUS  ); // If we add this then use proper message texts in the STATUS_STRINGS
   
      // protocol speed
   
    final IntParameter  protocol_speed = config.addIntParameter2( "azbuddy.protocolspeed", "azbuddy.protocolspeed", 32 );
   
    protocol_speed.setMinimumRequiredUserMode( Parameter.MODE_ADVANCED );
   
    inbound_limit = protocol_speed.getValue()*1024;
   
    protocol_speed.addListener(
        new ParameterListener()
        {
          public void
          parameterChanged(
            Parameter  param )
          {
            inbound_limit = protocol_speed.getValue()*1024;
          }
        });
   
      // chat notifications
   
    enable_chat_notifications = config.addBooleanParameter2( "azbuddy.enable_chat_notif", "azbuddy.enable_chat_notif", true );
   
      // default published cats
   
    cat_pub = config.addStringParameter2( "azbuddy.enable_cat_pub", "azbuddy.enable_cat_pub", "" );
   
    cat_pub.setGenerateIntermediateEvents( false );
   
    setPublicCats( cat_pub.getValue(), false );
   
    cat_pub.addListener(
      new ParameterListener()
      {
        public void
        parameterChanged(
          Parameter   param )
        {
          setPublicCats( cat_pub.getValue(), false);
        }
      });
   
      // config end
   
   
    final TableContextMenuItem menu_item_itorrents =
      plugin_interface.getUIManager().getTableManager().addContextMenuItem(TableManager.TABLE_MYTORRENTS_INCOMPLETE, "azbuddy.contextmenu");
    final TableContextMenuItem menu_item_ctorrents   =
      plugin_interface.getUIManager().getTableManager().addContextMenuItem(TableManager.TABLE_MYTORRENTS_COMPLETE, "azbuddy.contextmenu");
   
    menu_item_itorrents.setStyle(TableContextMenuItem.STYLE_MENU);
    menu_item_ctorrents.setStyle(TableContextMenuItem.STYLE_MENU);
   
    MenuItemFillListener  menu_fill_listener =
      new MenuItemFillListener()
      {
        public void
        menuWillBeShown(
          MenuItem  menu,
          Object    _target )
        {
          menu.removeAllChildItems();

          if ( !( isEnabled() && isAvailable())){
           
            menu.setEnabled( false );
           
            return;
          }
         
          final List<Torrent>  torrents = new ArrayList<Torrent>();
               
          if ( _target instanceof TableRow ){
           
            addDownload( torrents, (TableRow)_target );
 
          }else{
           
            TableRow[] rows = (TableRow[])_target;
              
            for ( TableRow row: rows ){
             
              addDownload( torrents, row );
            }
          }
         
          if ( torrents.size() == 0 ){
           
            menu.setEnabled( false );

          }else{
         
            List<BuddyPluginBuddy> buddies = getBuddies();
           
            boolean  incomplete = ((TableContextMenuItem)menu).getTableID() == TableManager.TABLE_MYTORRENTS_INCOMPLETE;
           
            TableContextMenuItem parent = incomplete?menu_item_itorrents:menu_item_ctorrents;
                       
            for (int i=0;i<buddies.size();i++){
             
              final BuddyPluginBuddy  buddy = (BuddyPluginBuddy)buddies.get(i);
             
              if ( buddy.isOnline( true )){
               
                TableContextMenuItem item =
                  plugin_interface.getUIManager().getTableManager().addContextMenuItem(
                    parent,
                    "!" + buddy.getName() + "!");
               
                item.addMultiListener(
                  new MenuItemListener()
                  {
                    public void
                    selected(
                      MenuItem   menu,
                      Object     target )
                    {
                      for ( Torrent torrent: torrents ){
                     
                        az2_handler.sendAZ2Torrent( torrent, buddy );
                      }
                    }
                  });
              }
            }
         
            menu.setEnabled( true );
          }
        }
       
        protected void
        addDownload(
          List<Torrent>    torrents,
          TableRow      row )
        {
          Object obj = row.getDataSource();
         
          Download  download;
         
          if ( obj instanceof Download ){
           
            download = (Download)obj;
           
          }else{
           
            DiskManagerFileInfo file = (DiskManagerFileInfo)obj;
           
            try{
              download  = file.getDownload();
             
            }catch( DownloadException e ){ 
             
              Debug.printStackTrace(e);
             
              return;
            }
          }
         
          Torrent torrent = download.getTorrent();
         
          if ( torrent != null && !TorrentUtils.isReallyPrivate( PluginCoreUtils.unwrap( torrent ))){
           
            torrents.add( torrent );
          }
        }
      };
     
    menu_item_itorrents.addFillListener( menu_fill_listener );
    menu_item_ctorrents.addFillListener( menu_fill_listener );
   
    buddy_tracker = new BuddyPluginTracker( this, config );
   
    plugin_interface.getUIManager().addUIListener(
      new UIManagerListener()
      {
        public void
        UIAttached(
          final UIInstance    instance )
        {
          if ( instance instanceof UISWTInstance ){
           
            UISWTInstance swt_ui = (UISWTInstance)instance;
           
            BuddyPluginView view = new BuddyPluginView( BuddyPlugin.this, swt_ui );

            swt_ui.addViewUISWTInstance.VIEW_MAIN, VIEW_ID, view );
           
            //swt_ui.openMainView( VIEW_ID, view, null );
          }
         
          setupDisablePrompt(instance);
        }

        public void
        UIDetached(
          UIInstance    instance )
        {
        }
      });
   
    ParameterListener enabled_listener =
      new ParameterListener()
      {
        public void
        parameterChanged(
          Parameter  param )
        {
          boolean enabled = enabled_param.getValue();

          nick_name_param.setEnabled( enabled );
         
            // only toggle overall state on a real change
         
          if ( param != null ){
         
            setEnabledInternal( enabled );
            fireEnabledStateChanged();
          }
        }
      };
   
    enabled_listener.parameterChanged( null );
     
    enabled_param.addListener( enabled_listener );
   
    loadConfig();
   
    registerMessageHandler();
   
    plugin_interface.addListener(
      new PluginListener()
      {
        public void
        initializationComplete()
        {
          final DelayedTask dt = plugin_interface.getUtilities().createDelayedTask(new Runnable()
            {
              public void
              run()
              {
                new AEThread2( "BuddyPlugin:init", true )
                {
                  public void
                  run()
                  {
                    startup();
                  }
                }.start();
              }
            });
         
          dt.queue();
        }
       
        public void
        closedownInitiated()
        { 
          saveConfig( true );
         
          closedown();
        }
       
        public void
        closedownComplete()
        {       
        }
      });
  }
   
  protected void
  updateLocale(
    LocaleUtilities  lu )
  {
    for ( int i=0;i<STATUS_STRINGS.length;i++){
     
      STATUS_STRINGS[i] = lu.getLocalisedMessageText( "azbuddy." + STATUS_KEYS[i] );
    }
   
    if ( online_status_param != null ){
     
      online_status_param.setLabels( STATUS_STRINGS );
    }
  }
 
  /**
   *
   *
   * @since 3.0.5.3
   */
  protected void
  setupDisablePrompt(
      final UIInstance ui)
  {
    if (plugin_interface == null) {
      return;
    }

    String enabledConfigID = "PluginInfo." + plugin_interface.getPluginID()
        + ".enabled";
    COConfigurationManager.addParameterListener(enabledConfigID,
        new org.gudy.azureus2.core3.config.ParameterListener() {
          public void parameterChanged(
              String parameterName)
          {
            fireEnabledStateChanged();
          }
        });
  }
 
  public void
  showConfig()
  {
    plugin_interface.getUIManager().showConfigSection("Views.plugins." + VIEW_ID + ".title");
  }
 
  protected void
  startup()
  {
    try{
      ddb = plugin_interface.getDistributedDatabase();
   
      if ( !ddb.isAvailable()){
       
        throw( new Exception( "DDB Unavailable" ));
      }
        // pick up initial values before enabling

      ddb.addListener(
        new DistributedDatabaseListener()
        {
          public void
          event(
            DistributedDatabaseEvent event )
          {
            if ( event.getType() == DistributedDatabaseEvent.ET_LOCAL_CONTACT_CHANGED ){
             
              updateIP();
            }
          }
        });
         
      updateIP();
     
      updateNickName( nick_name_param.getValue());
            
      updateOnlineStatus( Integer.parseInt( online_status_param.getValue()));
      
      COConfigurationManager.addAndFireParameterListeners(
          new String[]{
            "TCP.Listen.Port",
            "TCP.Listen.Port.Enable",
            "UDP.Listen.Port",
            "UDP.Listen.Port.Enable" },
          new org.gudy.azureus2.core3.config.ParameterListener()
          {
            public void
            parameterChanged(
              String parameterName )
            {
              updateListenPorts();
            }
          });
     
      CryptoManagerFactory.getSingleton().addKeyListener(
        new CryptoManagerKeyListener()
        {
          public void
          keyChanged(
            CryptoHandler handler )
          {
            updateKey();
          }
         
          public void
          keyLockStatusChanged(
            CryptoHandler    handler )
          { 
            boolean unlocked = handler.isUnlocked();
           
            if ( unlocked ){
             
              if ( latest_publish.isEnabled()){
               
                updatePublish( latest_publish );
              }
            }else{
             
              new AEThread2( "BuddyPlugin:disc", true )
              {
                public void
                run()
                {
                  List buddies = getAllBuddies();
                 
                  for (int i=0;i<buddies.size();i++){
                   
                    ((BuddyPluginBuddy)buddies.get(i)).disconnect();
                  }
                }
              }.start();
            }
          }
        });
     
      ready_to_publish  = true;
     
      setEnabledInternal( enabled_param.getValue());
     
      checkBuddiesAndRepublish();
     
      fireInitialised( true );
     
        // try to re-establish connection to previously connectd buddies
     
      List<BuddyPluginBuddy> buddies = getBuddies();
     
      for ( BuddyPluginBuddy buddy: buddies ){
       
        if ( buddy.getIP() != null && !buddy.isConnected()){
       
          log( "Attempting reconnect to " + buddy.getString());
         
          buddy.sendKeepAlive();
        }
      }
     
    }catch( Throwable e ){
   
      log( "Initialisation failed", e );
     
      fireInitialised( false );
    }
  }
 
  public boolean
  isEnabled()
  {
    if (enabled_param == null) {return false;}
    return( enabled_param.getValue());
  }
 
  public void
  setEnabled(
    boolean    enabled )
  {
    if (enabled_param == null) {return;}
    enabled_param.setValue( enabled );
  }
 
  protected void
  setEnabledInternal(
    boolean    _enabled )
  {
    synchronized( this ){
           
      if ( latest_publish.isEnabled() != _enabled ){
       
        publishDetails new_publish = latest_publish.getCopy();
       
        new_publish.setEnabled( _enabled );
       
        updatePublish( new_publish );
      }
    }
  }
 
  public BuddyPluginTracker
  getTracker()
  {
    return( buddy_tracker );
  }
 
  public String
  getNickname()
  {
    returnnick_name_param.getValue());
  }
 
  public void
  setNickname(
    String  str )
  {
    nick_name_param.setValue( str );
  }
 
  public void
  setOnlineStatus(
    int    status )
  {
    online_status_param.setValue( "" + status );
  }
 
  public int
  getOnlineStatus()
  {
    return( latest_publish.getOnlineStatus());
  }
 
  public BooleanParameter
  getEnableChatNotificationsParameter()
  {
    return( enable_chat_notifications );
  }
 
  protected String
  normaliseCat(
    String    str )
  {
    if ( str == null ){
     
      return( null );
     
    }else if ( str.toLowerCase().equals( "all" )){
     
      return( "All" );
     
    }else{
     
      return( str );
    }
  }
 
  protected void
  normaliseCats(
    Set<String>  cats )
  {
    if ( cats != null ){
     
      boolean  all_found = false;
     
      Iterator<String> it = cats.iterator();
     
      while( it.hasNext()){
       
        if ( it.next().toLowerCase().equals( "all" )){
         
          it.remove();
         
          all_found = true;
        }
      }
     
      if ( all_found ){
       
        cats.add( "All" );
      }
    }
  }
 
  public boolean
  isPublicCategory(
    String  cat )
  {
    cat = normaliseCat( cat );
   
    return( public_categories.contains( cat ));
  }
 
  public void
  addPublicCategory(
    String  cat )
  {
    cat = normaliseCat( cat );
   
    Set<String> new_cats = new HashSet( public_categories );
   
    if ( new_cats.add( cat )){
   
      setPublicCats( new_cats, true );
    }
  }
 
  public void
  removePublicCategory(
    String  cat )
  {
    cat = normaliseCat( cat );
   
    Set<String> new_cats = new HashSet( public_categories );
   
    if ( new_cats.remove( cat )){
   
      setPublicCats( new_cats, true );
    }
  }
 
  protected void
  setPublicCats(
    String  str,
    boolean  persist )
  {
    Set<String>  new_pub_cats = new HashSet<String>();
   
    String[]  bits = str.split(",");
   
    for (String s: bits ){
     
      s = s.trim();
     
      if ( bits.length > 0 ){
       
        new_pub_cats.add( normaliseCat( s ));
      }
    }
   
    setPublicCats( new_pub_cats, persist );
  }
 
  protected void
  setPublicCats(
    Set<String>  new_pub_cats,
    boolean    persist )
  {
    if ( !public_categories.equals( new_pub_cats )){
     
      Set<String> removed = new HashSet<String>( public_categories );
     
      removed.removeAll( new_pub_cats );
     
      public_categories = new_pub_cats;
     
      if ( persist ){
       
        String cat_str = "";
       
        for ( String s: public_categories ){
         
          cat_str += (cat_str.length()==0?"":",") + s;
        }
       
        cat_pub.setValue( cat_str );
      }
     
      List<BuddyPluginBuddy> buds = getBuddies();
     
      for ( BuddyPluginBuddy b: buds ){
       
        Set<String> local = b.getLocalAuthorisedRSSCategories();
       
        if ( local != null || new_pub_cats.size() > 0 ){
         
          if ( local == null ){
           
            local = new HashSet<String>();
           
          }else{
           
              // gotta clone else we're messing with stuff that ain't ours
           
            local = new HashSet<String>( local );
          }
         
          local.addAll( new_pub_cats );
       
          local.removeAll( removed );
       
          b.setLocalAuthorisedRSSCategories( local );
        }
      }
    }
  }
 
  protected void
  registerMessageHandler()
  {
    try{
      addRequestListener(
        new BuddyPluginBuddyRequestListener()
        {
          public Map
          requestReceived(
            BuddyPluginBuddy  from_buddy,
            int          subsystem,
            Map          request )
         
            throws BuddyPluginException
          {
            if ( subsystem == SUBSYSTEM_INTERNAL ){
           
              if ( !from_buddy.isAuthorised()){
             
                throw( new BuddyPluginException( "Unauthorised" ));
              }
             
              return( processInternalRequest( from_buddy, request ));             
            }

            return( null );
          }
         
          public void
          pendingMessages(
            BuddyPluginBuddy[]  from_buddies )
          {
          }
        });
     
      msg_registration =
        plugin_interface.getMessageManager().registerGenericMessageType(
          "AZBUDDY", "Buddy message handler",
          STREAM_CRYPTO,
          new GenericMessageHandler()
          {
            public boolean
            accept(
              GenericMessageConnection  connection )
           
              throws MessageException
            {
              if ( !isEnabled()){
               
                return( false );
              }
             
              final String originator = connection.getEndpoint().getNotionalAddress().getAddress().getHostAddress();
             
              if ( TRACE ){
                System.out.println( "accept " + originator );
              }
             
              try{
                String reason = "Friend: Incoming connection establishment (" + originator + ")";
                 
                addRateLimiters( connection );

                connection =
                  sec_man.getSTSConnection(
                      connection,
                      sec_man.getPublicKey( SEPublicKey.KEY_TYPE_ECC_192, reason ),
                      new SEPublicKeyLocator()
                      {
                        public boolean
                        accept(
                          Object    context,
                          SEPublicKey  other_key )
                        {
                          String  other_key_str = Base32.encode( other_key.encodeRawPublicKey());

                          if ( TRACE ){
                            System.out.println( "Incoming: acceptKey - " + other_key_str );
                          }
                         
                          try{
                            synchronized( BuddyPlugin.this ){
                               
                              int  unauth_count = 0;
                             
                              for (int i=0;i<buddies.size();i++){
                             
                                BuddyPluginBuddy  buddy = (BuddyPluginBuddy)buddies.get(i);
 
                                if ( buddy.getPublicKey().equals( other_key_str )){
                                 
                                    // don't accept a second or subsequent connection for unauth buddies
                                    // as they have a single chance to be processed
                                 
                                  if ( !buddy.isAuthorised()){
                                   
                                    log( "Incoming connection from " + originator + " failed as for unauthorised buddy" );
                                   
                                    return( false );
                                  }
                                 
                                  buddy.incomingConnection((GenericMessageConnection)context )
                                 
                                  return( true );
                                }
                               
                                if ( !buddy.isAuthorised()){
                                 
                                  unauth_count++;
                                }
                              }
                             
                                // no existing authorised buddy
                             
                              if ( unauth_count < MAX_UNAUTH_BUDDIES ){
                                   
                                if ( tooManyUnauthConnections( originator )){
                                 
                                  log( "Too many recent unauthorised connections from " + originator );
                                 
                                  return( false );
                                }
                               
                                BuddyPluginBuddy buddy = addBuddy( other_key_str, SUBSYSTEM_AZ2, false );
                               
                                buddy.incomingConnection((GenericMessageConnection)context )
                                 
                                return( true );
 
                              }
                            }
                           
                            log( "Incoming connection from " + originator + " failed due to pk mismatch" );
 
                            return( false );
                           
                          }catch( Throwable e ){
                           
                            log( "Incomming connection from " + originator + " failed", e );
                           
                            return( false );
                          }
                        }
                      },
                      reason,
                      BLOCK_CRYPTO );
             
              }catch( Throwable e ){
               
                connection.close();
               
                log( "Incoming connection from " + originator + " failed", e );
              }
             
              return( true );
            }
          });
         
    }catch( Throwable e ){
     
      log( "Failed to register message listener", e );
    }
  }
 
  protected void
  addRateLimiters(
    GenericMessageConnection  connection )
  {
    connection.addInboundRateLimiter( inbound_limiter );
    connection.addOutboundRateLimiter( outbound_limiter );
  }
 
  protected boolean
  tooManyUnauthConnections(
    String  originator )
  {
    synchronized( this ){
 
      if ( unauth_bloom == null ){
       
        unauth_bloom = BloomFilterFactory.createAddRemove4Bit( UNAUTH_BLOOM_CHUNK );
       
        unauth_bloom_create_time  = SystemTime.getCurrentTime();
      }
     
      int  hit_count = unauth_bloom.add( originator.getBytes());
     
      if ( hit_count >= 8 ){
             
        Debug.out( "Too many recent unauthorised connection attempts from " + originator );
        
        return( true );
      }
     
      return( false );
    }
  }
 
  protected void
  checkUnauthBloom()
  {
    synchronized( this ){
   
      if ( unauth_bloom != null ){
       
        long  now = SystemTime.getCurrentTime();
       
        if ( now < unauth_bloom_create_time ){
         
          unauth_bloom_create_time = now;
         
        }else if ( now - unauth_bloom_create_time > UNAUTH_BLOOM_RECREATE ){
         
          unauth_bloom = null;
        }
      }
    }
  }
 
  protected void
  checkMaxMessageSize(
    int    size )
 
    throws BuddyPluginException
  {
    if ( size > MAX_MESSAGE_SIZE ){
     
      throw( new BuddyPluginException( "Message is too large to send, limit is " + DisplayFormatters.formatByteCountToKiBEtc( MAX_MESSAGE_SIZE )));
    }
  }
 
  protected void
  checkPersistentDispatch()
  {
    List  buddies_copy;
   
    synchronized( this ){
   
      buddies_copy = new ArrayList( buddies );
    }
       
    for (int i=0;i<buddies_copy.size();i++){
     
      BuddyPluginBuddy  buddy = (BuddyPluginBuddy)buddies_copy.get(i);

      buddy.checkPersistentDispatch();
    }
  }
 
  protected void
  persistentDispatchInit()
  {
    Iterator it = pd_preinit.iterator();
   
    while( it.hasNext()){
   
      persistentDispatchPending((BuddyPluginBuddy)it.next());
    }
   
    pd_preinit = null;
  }
 
  protected void
  persistentDispatchPending(
    BuddyPluginBuddy  buddy )
  {
    synchronized( pd_queue ){
     
      if ( initialisation_state == INIT_UNKNOWN ){
       
        pd_preinit.add( buddy );
       
        return;
      }
     
      if ( !pd_queue.contains( buddy )){
       
        pd_queue.add( buddy );
       
        pd_queue_sem.release();
       
        if ( pd_thread == null ){
         
          pd_thread =
            new AEThread2( "BuddyPlugin:persistDispatch", true )
            {
              public void
              run()
              {
                while( true ){
                 
                  if ( !pd_queue_sem.reserve( 30*1000 )){
                   
                    synchronized( pd_queue ){
                     
                      if ( pd_queue.isEmpty()){
                       
                        pd_thread  = null;
                       
                        break;
                      }
                    }
                  }else{
                   
                    BuddyPluginBuddy  buddy;
                   
                    synchronized( pd_queue ){
                     
                      buddy = (BuddyPluginBuddy)pd_queue.remove(0);
                    }
                   
                    buddy.persistentDispatch();
                  }
                }
              }
            };
           
          pd_thread.start();
        }
      }
    }
  }
 
  protected Map
  processInternalRequest(
    BuddyPluginBuddy  from_buddy,
    Map          request )   
   
    throws BuddyPluginException
  {
    int  type = ((Long)request.get("type")).intValue();
   
    if ( type == RT_INTERNAL_REQUEST_PING ){
   
      Map  reply = new HashMap();
   
      reply.put( "type", new Long( RT_INTERNAL_REPLY_PING ));
   
      return( reply );
     
    }else if ( type == RT_INTERNAL_REQUEST_CLOSE ){
   
      from_buddy.receivedCloseRequest( request );
     
      Map  reply = new HashMap();
   
      reply.put( "type", new Long( RT_INTERNAL_REPLY_CLOSE ));
   
      return( reply );
     
    }else{
     
      throw( new BuddyPluginException( "Unrecognised request type " + type ));
    }
  }

  protected void
  updateListenPorts()
  {
    synchronized( this ){

      int  tcp_port = COConfigurationManager.getIntParameter( "TCP.Listen.Port" );
      boolean  tcp_enabled = COConfigurationManager.getBooleanParameter( "TCP.Listen.Port.Enable" );
      int  udp_port = COConfigurationManager.getIntParameter("UDP.Listen.Port" );
      boolean  udp_enabled = COConfigurationManager.getBooleanParameter( "UDP.Listen.Port.Enable" );
       
      if ( !tcp_enabled ){
       
        tcp_port = 0;
      }
     
      if ( !udp_enabled ){
       
        udp_port = 0;
      }
     
      if (   latest_publish.getTCPPort() != tcp_port ||
          latest_publish.getUDPPort() != udp_port ){
       
        publishDetails new_publish = latest_publish.getCopy();
       
        new_publish.setTCPPort( tcp_port );
        new_publish.setUDPPort( udp_port );
       
        updatePublish( new_publish );
      }
    }
  }
 
  protected void
  updateIP()
  {
    if ( ddb == null || !ddb.isAvailable()){
     
      return;
    }
       
    synchronized( this ){

      InetAddress public_ip = ddb.getLocalContact().getAddress().getAddress();
       
      if (   latest_publish.getIP() == null ||
          !latest_publish.getIP().equals( public_ip )){
         
        publishDetails new_publish = latest_publish.getCopy();
       
        new_publish.setIP( public_ip );
       
        updatePublish( new_publish );
      }
    }
  }
 
  protected void
  updateNickName(
    String    new_nick )
  {
    new_nick = new_nick.trim();
   
    if ( new_nick.length() == 0 ){
     
      new_nick = null;
    }
   
    synchronized( this ){

      String  old_nick = latest_publish.getNickName();
     
      if ( !stringsEqual( new_nick, old_nick )){
     
        publishDetails new_publish = latest_publish.getCopy();
         
        new_publish.setNickName( new_nick );
         
        updatePublish( new_publish );
      }
    }
  }
 
  protected void
  updateOnlineStatus(
    int    new_status )
  {
    boolean  changed;
   
    synchronized( this ){

      int  old_status = latest_publish.getOnlineStatus();
     
      changed = old_status != new_status;
     
      if ( changed ){
     
        publishDetails new_publish = latest_publish.getCopy();
         
        new_publish.setOnlineStatus( new_status );
         
        updatePublish( new_publish );
      }
    }
   
    if ( changed ){
     
      List  buddies = getAllBuddies();
           
      for (int i=0;i<buddies.size();i++){
       
        BuddyPluginBuddy  buddy = (BuddyPluginBuddy)buddies.get(i);
       
        if ( buddy.isConnected()){
       
          buddy.sendKeepAlive();
        }
      }
    }
  }
 
  public String
  getOnlineStatus(
    int    status )
  {
    if ( status >= STATUS_STRINGS.length || status < 0 ){
   
      status = 0;
    }
   
    return( STATUS_STRINGS[status] );
  }
 
  protected boolean
  stringsEqual(
    String  s1,
    String  s2 )
  {
    if ( s1 == null && s2 == null ){
     
      return( true );
    }
   
    if ( s1 == null || s2 == null ){
     
      return( false );
    }
   
    return( s1.equals( s2 ));
  }
 
  protected void
  updateKey()
  {
    synchronized( this ){

      publishDetails new_publish = latest_publish.getCopy();
       
      new_publish.setPublicKey( null );
       
      updatePublish( new_publish );
    }
  }
 
  protected void
  updatePublish(
    final publishDetails  details )
  {
    latest_publish = details;
   
    if ( ddb == null || !ready_to_publish ){
     
      return;
    }
   
    publish_dispatcher.dispatch(
      new AERunnable()
      {
        public void
        runSupport()
        {
            // only execute the most recent publish
         
          if ( publish_dispatcher.getQueueSize() > 0 ){
           
            return;
          }
         
          updatePublishSupport( details );
        }
      });
  }
 
  protected void
  updatePublishSupport(
    publishDetails  details )
  {
    byte[]  key_to_remove = null;
   
    publishDetails  existing_details;
   
    boolean  log_this;
   
    synchronized( this ){

      log_this = !current_publish.getString().equals( details.getString());
     
      existing_details = current_publish;
     
      if ( !details.isEnabled()){
       
        if ( current_publish.isPublished()){
         
          key_to_remove  = current_publish.getPublicKey();
        }
      }else{
               
        if ( details.getPublicKey() == null ){
         
          try{
            details.setPublicKey( ecc_handler.getPublicKey( "Creating online status key" ));
           
          }catch( Throwable e ){
           
            log( "Failed to publish details", e );
           
            return;
          }     
        }
       
        if ( current_publish.isPublished()){
         
          byte[]  existing_key = current_publish.getPublicKey();
       
          if ( !Arrays.equals( existing_key, details.getPublicKey())){
           
            key_to_remove = existing_key;
          }
        }
      }
     
      current_publish = details;
    }
   
    if ( key_to_remove != null ){
     
      log( "Removing old status publish: " + existing_details.getString());
     
      try{
        ddb.delete(
          new DistributedDatabaseListener()
          {
            public void
            event(
              DistributedDatabaseEvent    event )
            {
            }
          },
          getStatusKey( key_to_remove, "Friend status de-registration for old key" ));
       
      }catch( Throwable e ){ 
     
        log( "Failed to remove existing publish", e );
      }
    }
   
    if ( details.isEnabled()){
     
        // ensure we have a sensible ip
     
      InetAddress ip = details.getIP();
     
      if ( ip.isLoopbackAddress() || ip.isLinkLocalAddress() || ip.isSiteLocalAddress()){
       
        log( "Can't publish as ip address is invalid: " + details.getString());
       
        return;
      }
     
      details.setPublished( true );
     
      Map  payload = new HashMap();
     
      if ( details.getTCPPort() > 0 ){
     
        payload.put( "t", new Longdetails.getTCPPort() ));
      }
     
      if details.getUDPPort() > 0 ){
       
        payload.put( "u", new Long( details.getUDPPort() ));
      }
           
      payload.put( "i", ip.getAddress());
     
      String  nick = details.getNickName();
     
      if ( nick != null ){
       
        if ( nick.length() > 32 ){
         
          nick = nick.substring( 0, 32 );
        }
       
        payload.put( "n", nick );
      }
     
      payload.put( "o", new Long( details.getOnlineStatus()));
     
      int  next_seq = ++status_seq;
     
      if ( next_seq == 0 ){
       
        next_seq = ++status_seq;
      }
     
      details.setSequence( next_seq );
     
      payload.put( "s", new Long( next_seq ));
     
      payload.put( "v", new Long( VERSION_CURRENT ));
     
      boolean  failed_to_get_key = true;
     
      try{
        byte[] data = BEncoder.encode( payload );
                   
        DistributedDatabaseKey  key = getStatusKey( details.getPublicKey(), "My buddy status registration " + payload );
 
        byte[] signature = ecc_handler.sign( data, "Friend online status" );
     
        failed_to_get_key = false;
       
        byte[]  signed_payload = new byte[ 1 + signature.length + data.length ];
       
        signed_payload[0] = (byte)signature.length;
       
        System.arraycopy( signature, 0, signed_payload, 1, signature.length );
        System.arraycopy( data, 0, signed_payload, 1 + signature.length, data.length );   
       
        DistributedDatabaseValue  value = ddb.createValue( signed_payload );
       
        final AESemaphore  sem = new AESemaphore( "BuddyPlugin:reg" );
       
        if ( log_this ){
         
          logMessage( "Publishing status starts: " + details.getString());
        }
       
        last_publish_start = SystemTime.getMonotonousTime();
       
        ddb.write(
          new DistributedDatabaseListener()
          {
            private List<DistributedDatabaseContact>  write_contacts = new ArrayList<DistributedDatabaseContact>();
           
            public void
            event(
              DistributedDatabaseEvent    event )
            {
              int  type = event.getType();
           
              if ( type == DistributedDatabaseEvent.ET_VALUE_WRITTEN ){
               
                write_contacts.add( event.getContact());
               
              }else if (   type == DistributedDatabaseEvent.ET_OPERATION_TIMEOUT ||
                    type == DistributedDatabaseEvent.ET_OPERATION_COMPLETE ){

                synchronized( publish_write_contacts ){
                 
                  publish_write_contacts.clear();
                 
                  publish_write_contacts.addAll( write_contacts );
                }
               
                sem.release();
              }
            }
          },
          key,
          value );
       
        sem.reserve();
       
        if ( log_this ){
       
          logMessage( "My status publish complete" );
        }
      }catch( Throwable e ){
       
        logMessage( "Failed to publish online status", e );
       
        if ( failed_to_get_key ){
         
          synchronized( this ){
           
            if ( republish_delay_event != null ){
             
              return;
            }
           
            if (   last_publish_start == 0 ||
                SystemTime.getMonotonousTime() - last_publish_start > STATUS_REPUBLISH_PERIOD ){
           
              log( "Rescheduling publish as failed to get key" );
           
              republish_delay_event = SimpleTimer.addEvent(
                "BuddyPlugin:republish",
                SystemTime.getCurrentTime() + 60*1000,
                new TimerEventPerformer()
                {
                  public void
                  perform(
                    TimerEvent event)
                  {
                    synchronized( BuddyPlugin.this ){
                     
                      republish_delay_event = null;
                    }
                   
                    if (   last_publish_start == 0 ||
                        SystemTime.getMonotonousTime() - last_publish_start > STATUS_REPUBLISH_PERIOD ){
                   
                      if ( latest_publish.isEnabled()){
                       
                        updatePublish( latest_publish );
                      }
                    }
                  }
                });
               
           
          }
        }
      }
    }
  }
 
  protected int
  getCurrentStatusSeq()
  {
    return( current_publish.getSequence());
  }
 
  protected void
  closedown()
  {
    logMessage( "Closing down" );

    List<BuddyPluginBuddy>  buddies = getAllBuddies();
   
    synchronized( this ){
     
      connected_at_close = new ArrayList<BuddyPluginBuddy>();
     
      for ( BuddyPluginBuddy buddy: buddies ){
       
        if ( buddy.isConnected()){
         
          connected_at_close.add( buddy );
        }
      }
    }
   
    if ( ddb != null ){
     
      boolean  restarting = AzureusCoreFactory.isCoreAvailable() ? AzureusCoreFactory.getSingleton().isRestarting() : false;
     
      logMessage( "   closing buddy connections" );
     
      for (int i=0;i<buddies.size();i++){
       
        ((BuddyPluginBuddy)buddies.get(i)).sendCloseRequest( restarting );
      }
     
      if ( !restarting ){
       
        logMessage( "   updating online status" );
       
        List  contacts = new ArrayList();
       
        synchronized( publish_write_contacts ){
         
          contacts.addAll( publish_write_contacts );
        }
       
        byte[] key_to_remove;
       
        synchronized( this ){
 
          key_to_remove  = current_publish.getPublicKey();
        }
       
        if ( contacts.size() == 0 || key_to_remove == null ){
         
          return;
        }
       
        DistributedDatabaseContact[] contact_a = new DistributedDatabaseContact[contacts.size()];
       
        contacts.toArray( contact_a );
       
        try{
          ddb.delete(
            new DistributedDatabaseListener()
            {
              public void
              event(
                DistributedDatabaseEvent    event )
              {
                if ( event.getType() == DistributedDatabaseEvent.ET_VALUE_DELETED ){
 
                  // System.out.println( "Deleted status from " + event.getContact().getName());
                }
              }
            },
            getStatusKey( key_to_remove, "Friend status de-registration for closedown" ),
            contact_a );
         
        }catch( Throwable e ){ 
       
          log( "Failed to remove existing publish", e );
        }
      }
    }
  }
 
  protected DistributedDatabaseKey
  getStatusKey(
    byte[]  public_key,
    String  reason )
 
    throws Exception
  {
    byte[]  key_prefix = "azbuddy:status".getBytes();
   
    byte[]  key_bytes = new byte[ key_prefix.length + public_key.length ];
   
    System.arraycopy( key_prefix, 0, key_bytes, 0, key_prefix.length );
    System.arraycopy( public_key, 0, key_bytes, key_prefix.length, public_key.length );
   
    DistributedDatabaseKey key = ddb.createKey( key_bytes, reason );
   
    return( key );
  }

  protected DistributedDatabaseKey
  getYGMKey(
    byte[]  public_key,
    String  reason )
 
    throws Exception
  {
    byte[]  key_prefix = "azbuddy:ygm".getBytes();
   
    byte[]  key_bytes = new byte[ key_prefix.length + public_key.length ];
   
    System.arraycopy( key_prefix, 0, key_bytes, 0, key_prefix.length );
    System.arraycopy( public_key, 0, key_bytes, key_prefix.length, public_key.length );
   
    DistributedDatabaseKey key = ddb.createKey( key_bytes, reason );
   
    return( key );
  }
 
  protected void
  setConfigDirty()
  {
    synchronized( this ){
     
      config_dirty = true;
    }
  }
   
  protected void
  loadConfig()
  {
    long  now = SystemTime.getCurrentTime();
   
    synchronized( this ){
     
      Map map = readConfig();
         
      List  buddies_config = (List)map.get( "friends" );
       
      if ( buddies_config != null ){
         
        if ( buddies_config.size() == 0 ){
         
          deleteConfig();
         
        }else{
          for (int i=0;i<buddies_config.size();i++){
           
            Object o = buddies_config.get(i);
     
            if ( o instanceof Map ){
             
              Map  details = (Map)o;
             
              Long  l_ct = (Long)details.get( "ct" );
             
              long  created_time = l_ct==null?now:l_ct.longValue();
             
              if ( created_time > now ){
               
                created_time = now;
              }
             
              String  key = new String((byte[])details.get( "pk" ));
             
              List  recent_ygm = (List)details.get( "ygm" );
                       
              String  nick = decodeString((byte[])details.get( "n" ));
             
              Long  l_seq = (Long)details.get( "ls" );
             
              int  last_seq = l_seq==null?0:l_seq.intValue();
             
              Long  l_lo = (Long)details.get( "lo" );
             
              long  last_time_online = l_lo==null?0:l_lo.longValue();
           
              if ( last_time_online > now ){
               
                last_time_online = now;
              }
             
              Long l_subsystem = (Long)details.get( "ss" );
             
              int  subsystem = l_subsystem==null?SUBSYSTEM_AZ2:l_subsystem.intValue();
             
              if (subsystem == SUBSYSTEM_AZ3) {
                continue;
              }
             
              Long l_ver = (Long)details.get("v");
             
              int  ver = l_ver==null?VERSION_INITIAL:l_ver.intValue();
                           
              String  loc_cat = decodeString((byte[])details.get( "lc" ));
              String  rem_cat = decodeString((byte[])details.get( "rc" ));
             
              BuddyPluginBuddy buddy = new BuddyPluginBuddy( this, created_time, subsystem, true, key, nick, ver, loc_cat, rem_cat, last_seq, last_time_online, recent_ygm );
             
              byte[]  ip_bytes = (byte[])details.get( "ip" );
             
              if ( ip_bytes != null ){
               
                try{
                  InetAddress ip = InetAddress.getByAddress( ip_bytes );
                 
                  int  tcp_port = ((Long)details.get( "tcp" )).intValue();
                  int  udp_port = ((Long)details.get( "udp" )).intValue();
                 
                  buddy.setCachedStatus( ip, tcp_port, udp_port );
                 
                }catch( Throwable e ){
                }
              }
             
              logMessage( "Loaded buddy " + buddy.getString());
             
              buddies.add( buddy );
             
              buddies_map.put( key, buddy );
            }
          }
        }
      }
     
      int  num_buddies = buddies.size();
     
      for ( BuddyPluginBuddy b: buddies ){
       
        b.setInitialStatus( now, num_buddies );
      }
    }
  }
 
  protected String
  decodeString(
    byte[]    bytes )
  {
    if bytes == null ){
     
      return( null );
    }
   
    try{
      return( new String( bytes, "UTF8" ));
     
    }catch( Throwable e ){
     
      return( null );
    }
  }
 
  protected void
  saveConfig()
  {
    saveConfig( false );
  }
 
  protected void
  saveConfig(
    boolean  force )
  {
    synchronized( this ){

      if ( config_dirty || force ){
       
        List buddies_config = new ArrayList();
   
        for (int i=0;i<buddies.size();i++){
         
          BuddyPluginBuddy buddy = (BuddyPluginBuddy)buddies.get(i);
   
          if ( !buddy.isAuthorised()){
           
            continue;
          }
         
          Map  map = new HashMap();
       
          map.put( "ct", new Long( buddy.getCreatedTime()));
         
          map.put( "pk", buddy.getPublicKey());
       
          List  ygm = buddy.getYGMMarkers();
         
          if ( ygm != null ){
           
            map.put( "ygm", ygm );
          }
         
          String  nick = buddy.getNickName();
         
          if ( nick != null ){
           
            map.put( "n", nick );
          }
         
          map.put( "ls", new Long( buddy.getLastStatusSeq()));
         
          map.put( "lo", new Long( buddy.getLastTimeOnline()));
         
          map.put( "ss", new Long( buddy.getSubsystem()));
         
          map.put( "v", new Long( buddy.getVersion()));
         
          if ( buddy.getLocalAuthorisedRSSCategoriesAsString() != null ){
            map.put( "lc", buddy.getLocalAuthorisedRSSCategoriesAsString());
          }
         
          if ( buddy.getRemoteAuthorisedRSSCategoriesAsString() != null ){
            map.put( "rc", buddy.getRemoteAuthorisedRSSCategoriesAsString());
          }

          boolean connected =
            buddy.isConnected() ||
            ( connected_at_close != null && connected_at_close.contains( buddy ));
         
          if ( connected ){
           
            InetAddress  ip       = buddy.getIP();
            int      tcp_port  = buddy.getTCPPort();
            int      udp_port  = buddy.getUDPPort();
           
            if ( ip != null ){
             
              map.put( "ip", ip.getAddress());
              map.put( "tcp", new Long( tcp_port ));
              map.put( "udp", new Long( udp_port ));
            }
          }
         
          buddies_config.add( map );
        }
       
        Map  map = new HashMap();
       
        if ( buddies_config.size() > 0 ){
       
          map.put( "friends", buddies_config );
       
          writeConfig( map );
         
        }else{
         
          deleteConfig();
        }
       
        config_dirty = false;
      }
    }
  }
 
  public BuddyPluginBuddy
  addBuddy(
    String    key,
    int      subsystem )
 
  {
    return( addBuddy( key, subsystem, true ));
  }
 
  protected BuddyPluginBuddy
  addBuddy(
    String    key,
    int      subsystem,
    boolean    authorised )
  {
    if ( key.length() == 0 || !verifyPublicKey( key )){
     
      return( null );
    }
       
    BuddyPluginBuddy  buddy_to_return = null;
   
      // buddy may be already present as unauthorised in which case we pick it up
      // and authorise it and send the added event (we don't fire added events for
      // unauthorised buddies)
   
    synchronized( this ){
           
      for (int i=0;i<buddies.size();i++){
       
        BuddyPluginBuddy buddy = (BuddyPluginBuddy)buddies.get(i);
       
        if ( buddy.getPublicKey().equals( key )){
         
          if ( buddy.getSubsystem() != subsystem ){
           
            log( "Buddy " + buddy.getString() + ": subsystem changed from " + buddy.getSubsystem() + " to " + subsystem );
           
            buddy.setSubsystem( subsystem );
           
            saveConfig( true );
          }
         
          if ( authorised && !buddy.isAuthorised()){
           
            log( "Buddy " + buddy.getString() + ": no authorised" );
           
            buddy.setAuthorised( true );
           
            buddy_to_return  = buddy;
           
          }else{
         
            return( buddy );
          }
        }
      }
     
      if ( buddy_to_return == null ){
       
        buddy_to_return =
          new BuddyPluginBuddy( this, SystemTime.getCurrentTime(), subsystem, authorised, key, null, VERSION_CURRENT, null, null, 0, 0, null );
       
        buddies.add( buddy_to_return );
       
        buddies_map.put( key, buddy_to_return );
       
        if ( !authorised ){
         
          log( "Added unauthorised buddy: " + buddy_to_return.getString());
        }
      }
     
      if ( buddy_to_return.isAuthorised()){
       
        logMessage( "Added buddy " + buddy_to_return.getString());
 
        saveConfig( true );
      }
    }
       
    fireAdded( buddy_to_return );
   
    return( buddy_to_return );
  }
 
  protected void
  removeBuddy(
    BuddyPluginBuddy   buddy )
  {
    synchronized( this ){

      if ( !buddies.remove( buddy )){
       
        return;
      }
   
      buddies_map.remove( buddy.getPublicKey());
           
      logMessage( "Removed friend " + buddy.getString());

      saveConfig( true );
    }
   
    buddy.destroy();
       
    fireRemoved( buddy );
  }
 
  protected Map
  readConfig()
  {
    File  config_file = new File( plugin_interface.getUtilities().getAzureusUserDir(), "friends.config" );
   
    return( readConfigFile( config_file ));
  }
 
  protected void
  writeConfig(
    Map    map )
  {
    File  config_file = new File( plugin_interface.getUtilities().getAzureusUserDir(), "friends.config" );
   
    writeConfigFile( config_file, map );
  }
 
  protected void
  deleteConfig()
  {
    File  config_file = new File( plugin_interface.getUtilities().getAzureusUserDir(), "friends.config" );
   
    Utilities utils = plugin_interface.getUtilities();
     
    plugin_interface.getUtilities().deleteResilientBEncodedFile(
        config_file.getParentFile(), config_file.getName(), true );

  }
 
  protected Map
  readConfigFile(
    File    name )
  {
    Utilities utils = plugin_interface.getUtilities();
   
    Map map = utils.readResilientBEncodedFile(
            name.getParentFile(), name.getName(), true );
   
    if ( map == null ){
     
      map = new HashMap();
    }
   
    return( map );
  }
 
  protected boolean
  writeConfigFile(
    File    name,
    Map      data )
  {
    Utilities utils = plugin_interface.getUtilities();
     
    plugin_interface.getUtilities().writeResilientBEncodedFile(
      name.getParentFile(), name.getName(), data, true );
   
    return( name.exists());
  }
 
  protected File
  getBuddyConfigDir()
  {
    return( new File( plugin_interface.getUtilities().getAzureusUserDir(), "friends" ));
  }
 
  public BuddyPluginAZ2
  getAZ2Handler()
  {
    return( az2_handler );
  }
 
  public String
  getPublicKey()
  {
    try{
      return( Base32.encode(ecc_handler.getPublicKey( "Friend get key" )));
     
    }catch( Throwable e ){
     
      logMessage( "Failed to access public key", e );
     
      return( null );
    }
  }
 
  public boolean
  verifyPublicKey(
    String    key )
  {
    return( ecc_handler.verifyPublicKey( Base32.decode( key )));
  }
 
  protected void
  checkBuddiesAndRepublish()
  {
    updateBuddys();
   
    plugin_interface.getUtilities().createTimer( "Buddy checker" ).addPeriodicEvent(
      TIMER_PERIOD,
      new UTTimerEventPerformer()
      {
        int  tick_count;
       
        public void
        perform(
          UTTimerEvent event )
        {
          tick_count++;
         
          if ( !isEnabled()){
           
            return;
          }
                       
          updateBuddys();
         
          if ( tick_count % STATUS_REPUBLISH_TICKS == 0 ){
             
            if ( latest_publish.isEnabled()){
               
              updatePublish( latest_publish );
            }
          }
         
          if ( tick_count % CHECK_YGM_TICKS == 0 ){

            checkMessagePending( tick_count );
          }
         
          if ( tick_count % BLOOM_CHECK_TICKS == 0 ){
           
            checkUnauthBloom();
          }
         
          if ( tick_count % SAVE_CONFIG_TICKS == 0 ){

            saveConfig();
          }
         
          if ( tick_count % PERSISTENT_MSG_CHECK_TICKS == 0 ){
           
            checkPersistentDispatch();
          }
         
          if ( buddy_tracker != null ){
           
            buddy_tracker.tick( tick_count );
          }
        }
      });
  }
 
  protected void
  updateBuddys()
  {
    List  buddies_copy;
   
    synchronized( this ){
   
      buddies_copy = new ArrayList( buddies );
    }
   
    long  now = SystemTime.getCurrentTime();
   
    Random random = new Random();
   
    for (int i=0;i<buddies_copy.size();i++){
     
      BuddyPluginBuddy  buddy = (BuddyPluginBuddy)buddies_copy.get(i);
     
      long  last_check = buddy.getLastStatusCheckTime();
     
      buddy.checkTimeouts();
     
      int  period = BUDDY_STATUS_CHECK_PERIOD_MIN + BUDDY_STATUS_CHECK_PERIOD_INC*buddies_copy.size()/5;
     
        // randomise a bit
     
      period += random.nextInt( 2*60*1000 );
     
        // last check may be in the future as we defer checks for seemingly inactive buddies
     
      if ( now - last_check > period ){
       
        if ( !buddy.statusCheckActive()){
     
          if ( buddy.isAuthorised()){
         
            updateBuddyStatus( buddy );
          }
        }
      }
    }
   
      // trim any non-authorised buddies that have gone idle

    synchronized( this ){
     
      for (int i=0;i<buddies_copy.size();i++){
     
        BuddyPluginBuddy  buddy = (BuddyPluginBuddy)buddies_copy.get(i);
       
        if ( buddy.isIdle() && !buddy.isAuthorised()){
         
          removeBuddy( buddy );
        }
      }
    }
  }
 
  protected void
  updateBuddyStatus(
    final BuddyPluginBuddy  buddy )
  { 
    if ( !buddy.statusCheckStarts()){
     
      return;
    }
   
    log( "Updating buddy status: " + buddy.getString());

    try{             
      final byte[]  public_key = buddy.getRawPublicKey();

      DistributedDatabaseKey  key =
        getStatusKey( public_key, "Friend status check for " + buddy.getName());
     
      ddb.read(
        new DistributedDatabaseListener()
        {
          private long  latest_time;
          private Map    status;
         
          public void
          event(
            DistributedDatabaseEvent    event )
          {
            int  type = event.getType();
           
            if ( type == DistributedDatabaseEvent.ET_VALUE_READ ){
             
              try{
                DistributedDatabaseValue value = event.getValue();
               
                long time = value.getCreationTime();
               
                if ( time > latest_time ){
               
                  byte[] signed_stuff = (byte[])value.getValue( byte[].class );
               
                  Map  new_status = verifyAndExtract( signed_stuff, public_key );
                 
                  if ( new_status != null ){
 
                    status = new_status;
                                                                             
                    latest_time = time;
                  }
                }
              }catch( Throwable e ){
               
                log( "Read failed", e );
              }
            }else if (   type == DistributedDatabaseEvent.ET_OPERATION_TIMEOUT ||
                  type == DistributedDatabaseEvent.ET_OPERATION_COMPLETE ){
             
              if ( status == null ){
                               
                buddy.statusCheckFailed();
               
              }else{
               
                try{
                  int  tcp_port = ((Long)status.get( "t" )).intValue();
                  int udp_port = ((Long)status.get( "u" )).intValue();
                 
                  InetAddress ip = InetAddress.getByAddress((byte[])status.get("i"));
                 
                  String  nick = decodeString((byte[])status.get( "n" ));
                 
                  Long  l_seq = (Long)status.get( "s" );
                 
                  int    seq = l_seq==null?0:l_seq.intValue();
                 
                  Long  l_os = (Long)status.get( "o" );
                 
                  int    os = l_os==null?BuddyPlugin.STATUS_ONLINE:l_os.intValue();
                     
                  Long  l_ver = (Long)status.get( "v" );
                 
                  int    ver = l_ver==null?VERSION_INITIAL:l_ver.intValue();
                     
                  buddy.statusCheckComplete( latest_time, ip, tcp_port, udp_port, nick, os, seq, ver );
                 
                }catch( Throwable e ){
                 
                  buddy.statusCheckFailed();
                 
                  log( "Status decode failed", e );
                }
              }
            }
          }
        },
        key,
        120*1000 );
     
    }catch( Throwable e ){
     
      buddy.statusCheckFailed();
     
      log( "Friend status update failed: " + buddy.getString(), e );
    }
  }
 
  protected Map
  verifyAndExtract(
    byte[]    signed_stuff,
    byte[]    public_key )
 
    throws BuddyPluginException
  {
    int  signature_length = ((int)signed_stuff[0])&0xff;
   
    byte[]  signature   = new byte[ signature_length ];
    byte[]  data    = new byte[ signed_stuff.length - 1 - signature_length];
   
    System.arraycopy( signed_stuff, 1, signature, 0, signature_length );
    System.arraycopy( signed_stuff, 1 + signature_length, data, 0, data.length );
     
    try{
      if ( ecc_handler.verify( public_key, data, signature )){                         
 
        return( BDecoder.decode( data ));
                                                                 
      }else{
       
        logMessage( "Signature verification failed" );
       
        return( null );
      }
    }catch( Throwable e ){
     
      rethrow( "Verification failed", e );
     
      return( null );
    }
  }
 
  protected byte[]
  signAndInsert(
    Map    plain_stuff,
    String  reason )
 
    throws BuddyPluginException
  {
    try{
      byte[] data = BEncoder.encode( plain_stuff );
     
      byte[] signature = ecc_handler.sign( data, reason );
   
      byte[]  signed_payload = new byte[ 1 + signature.length + data.length ];
     
      signed_payload[0] = (byte)signature.length;
     
      System.arraycopy( signature, 0, signed_payload, 1, signature.length );
      System.arraycopy( data, 0, signed_payload, 1 + signature.length, data.length );   
 
      return( signed_payload );
     
    }catch( Throwable e ){
     
      rethrow( "Signing failed", e );
     
      return( null );
    }
  }
 
  public boolean
  verify(
    String        pk,
    byte[]        payload,
    byte[]        signature )
 
    throws BuddyPluginException
  {
    return( verify( Base32.decode( pk ), payload, signature ));
  }
 
  protected boolean
  verify(
    BuddyPluginBuddy  buddy,
    byte[]        payload,
    byte[]        signature )
 
    throws BuddyPluginException
  {
    return( verify( buddy.getRawPublicKey(), payload, signature ));
  }
 
  protected boolean
  verify(
    byte[]        pk,
    byte[]        payload,
    byte[]        signature )
 
    throws BuddyPluginException
  {
    try{
   
      return( ecc_handler.verify( pk, payload, signature ));
     
    }catch( Throwable e ){
     
      rethrow( "Verification failed", e );
     
      return( false );
    }
  }
 
  public byte[]
     sign(
       byte[]    payload )
           
       throws BuddyPluginException
  {
    try{
   
      return( ecc_handler.sign( payload, "Friend message signing" ));

    }catch( Throwable e ){
     
      rethrow( "Signing failed", e );
     
      return( null );
    }
  }

  protected cryptoResult
  encrypt(
    BuddyPluginBuddy  buddy,
    byte[]        payload )
 
    throws BuddyPluginException
  {
    return encrypt(buddy.getPublicKey(), payload, buddy.getName());
  }

  public cryptoResult
  encrypt(
    String        pk,
    byte[]        payload,
    String        forWho )
 
    throws BuddyPluginException
  {
   
    try{
      byte[]  hash = new byte[20];
     
      random.nextBytes( hash );
     
      Map  content = new HashMap();
     
      content.put( "h", hash );
      content.put( "p", payload );
     
      final byte[] encrypted = ecc_handler.encrypt( Base32.decode(pk), BEncoder.encode( content ), "Encrypting message for " + forWho);
     
      final byte[] sha1_hash = new SHA1Simple().calculateHash( hash );
     
      return(
        new cryptoResult()
        {
          public byte[]
            getChallenge()
          {
            return( sha1_hash );
          }
           
            public byte[]
            getPayload()
            {
              return( encrypted );
            }
        });
     
    }catch( Throwable e ){
     
      rethrow( "Encryption failed", e );
     
      return( null );
    }
  }
 
  protected cryptoResult
  decrypt(
    BuddyPluginBuddy  buddy,
    byte[]        content,
    String forName)
 
    throws BuddyPluginException
  {
   
    try{
      final byte[] decrypted = ecc_handler.decrypt( buddy.getRawPublicKey(), content, "Decrypting message for " + buddy.getName());
     
      final Map  map = BDecoder.decode( decrypted );
     
      return(
        new cryptoResult()
        {
          public byte[]
            getChallenge()
          {
            return((byte[])map.get("h"));
          }
           
            public byte[]
            getPayload()
            {
              return((byte[])map.get("p"));
            }
        });
     
    }catch( Throwable e ){
     
      rethrow( "Decryption failed", e );
     
      return( null );
    }
  }

  public cryptoResult
  decrypt(
    String        public_key,
    byte[]        content )
 
    throws BuddyPluginException
  {
   
    try{
      final byte[] decrypted = ecc_handler.decrypt( Base32.decode(public_key), content, "Decrypting message for " + public_key);
     
      final Map  map = BDecoder.decode( decrypted );
     
      return(
        new cryptoResult()
        {
          public byte[]
            getChallenge()
          {
            return((byte[])map.get("h"));
          }
           
            public byte[]
            getPayload()
            {
              return((byte[])map.get("p"));
            }
        });
     
    }catch( Throwable e ){
     
      rethrow( "Decryption failed", e );
     
      return( null );
    }
  }

  protected void
  setMessagePending(
    BuddyPluginBuddy      buddy,
    final operationListener    listener )
 
    throws BuddyPluginException
  {   
    try{
      checkAvailable();

      final String  reason = "Friend YGM write for " + buddy.getName();
     
      Map  payload = new HashMap();
     
      payload.put( "r", new Long( random.nextLong()));
     
      byte[] signed_payload = signAndInsert( payload, reason);
     
      Map  envelope = new HashMap();
     
      envelope.put( "pk", ecc_handler.getPublicKey( reason ));
      envelope.put( "ss", signed_payload );
     
      DistributedDatabaseValue  value = ddb.createValue( BEncoder.encode( envelope ));
                   
      logMessage( reason + " starts: " + payload );
     
      DistributedDatabaseKey  key = getYGMKey( buddy.getRawPublicKey(), reason );

      ddb.write(
        new DistributedDatabaseListener()
        {
          public void
          event(
            DistributedDatabaseEvent    event )
          {
            int  type = event.getType();
         
            if (   type == DistributedDatabaseEvent.ET_OPERATION_TIMEOUT ||
                type == DistributedDatabaseEvent.ET_OPERATION_COMPLETE ){
             
              logMessage( reason + " complete"  );

              listener.complete();
            }
          }
        },
        key,
        value );

    }catch( Throwable e ){
     
      try{       
        rethrow( "Failed to publish YGM", e );
               
      }finally{
       
        listener.complete();
      }
    }
  }
 
  public void
  checkMessagePending(
    int  tick_count )
  {
    log( "Checking YGM" );

    if ( tick_count % YGM_BLOOM_LIFE_TICKS == 0 ){
     
      synchronized( this ){
       
        ygm_unauth_bloom = null;
      }
    }

    try
      String  reason = "Friend YGM check";
     
      byte[] public_key = ecc_handler.getPublicKey( reason );

      DistributedDatabaseKey  key = getYGMKey( public_key, reason );
     
      ddb.read(
        new DistributedDatabaseListener()
        { 
          private List    new_ygm_buddies = new ArrayList();
          private boolean     unauth_permitted = false;;
         
          public void
          event(
            DistributedDatabaseEvent    event )
          {
            int  type = event.getType();
           
            if ( type == DistributedDatabaseEvent.ET_VALUE_READ ){
             
              try{
                DistributedDatabaseValue value = event.getValue();
                               
                byte[]  envelope = (byte[])value.getValue( byte[].class );
               
                Map  map = BDecoder.decode( envelope );
               
                byte[]  pk = (byte[])map.get( "pk" );
               
                if ( pk == null ){
                 
                  return;
                }
               
                String  pk_str = Base32.encode( pk );
               
                BuddyPluginBuddy buddy = getBuddyFromPublicKey( pk_str );
                               
                if ( buddy == null || !buddy.isAuthorised() ){
                 
                  if ( buddy == null ){
                 
                    log( "YGM entry from unknown friend '" + pk_str + "' - ignoring" );
                   
                  }else{               
                 
                    log( "YGM entry from unauthorised friend '" + pk_str + "' - ignoring" );
                  }

                  byte[] address = event.getContact().getAddress().getAddress().getAddress();
                 
                  synchronized( BuddyPlugin.this ){
                 
                    if ( ygm_unauth_bloom == null ){
                   
                      ygm_unauth_bloom = BloomFilterFactory.createAddOnly(512);
                    }
                   
                    if ( !ygm_unauth_bloom.contains( address )){
                     
                      ygm_unauth_bloom.add( address );
                     
                      unauth_permitted = true;
                    }
                  }
                }else{
                 
                  byte[]  signed_stuff = (byte[])map.get( "ss" );
                 
                  Map  payload = verifyAndExtract( signed_stuff, pk );
                 
                  if ( payload != null ){
                   
                    long  rand = ((Long)payload.get("r")).longValue();
                   
                    if ( buddy.addYGMMarker( rand )){
                     
                      new_ygm_buddies.add( buddy );
                    }
                  }
                }
              }catch( Throwable e ){
               
                log( "Read failed", e );
              }
            }else if (   type == DistributedDatabaseEvent.ET_OPERATION_TIMEOUT ||
                  type == DistributedDatabaseEvent.ET_OPERATION_COMPLETE ){
             
              if ( new_ygm_buddies.size() > 0 || unauth_permitted ){
               
                BuddyPluginBuddy[] b = new BuddyPluginBuddy[new_ygm_buddies.size()];
               
                new_ygm_buddies.toArray( b );
               
                fireYGM( b );
              }
            }
          }
        },
        key,
        120*1000,
        DistributedDatabase.OP_EXHAUSTIVE_READ );
     
      boolean  write_bogus_ygm = false;
     
      synchronized( this ){
     
        if ( !bogus_ygm_written ){
         
          bogus_ygm_written = write_bogus_ygm = true;
        }
      }
     
      if ( write_bogus_ygm ){
       
        final String  reason2 = "Friend YGM write for myself";
       
        Map  envelope = new HashMap();
               
        DistributedDatabaseValue  value = ddb.createValue( BEncoder.encode( envelope ));
                     
        logMessage( reason2 + " starts" );
       
        ddb.write(
          new DistributedDatabaseListener()
          {
            public void
            event(
              DistributedDatabaseEvent    event )
            {
              int  type = event.getType();
           
              if ( type == DistributedDatabaseEvent.ET_OPERATION_COMPLETE ){
               
                logMessage( reason2 + " complete"  );
              }
            }
          },
          key,
          value );
      }
     
    }catch( Throwable e ){
           
      logMessage( "YGM check failed", e );
    }
  }
 
  public BuddyPluginBuddy
  getBuddyFromPublicKey(
    String    key )
  {
    synchronized( this ){
     
      return((BuddyPluginBuddy)buddies_map.get( key ));
    }
  }
 
  public PluginInterface
  getPluginInterface()
  {
    return( plugin_interface );
  }
 
  protected SESecurityManager
  getSecurityManager()
  {
    return( sec_man );
  }
 
  protected GenericMessageRegistration
  getMessageRegistration()
  {
    return( msg_registration );
  }
 
    /**
     * Returns authorised buddies only
     */
 
  public List<BuddyPluginBuddy>
  getBuddies()
  {
    synchronized( this ){
     
      List<BuddyPluginBuddy>  result = new ArrayList<BuddyPluginBuddy>();
     
      for (int i=0;i<buddies.size();i++){
       
        BuddyPluginBuddy  buddy = (BuddyPluginBuddy)buddies.get(i);
       
        if ( buddy.isAuthorised()){
         
          result.add( buddy );
        }
      }
     
      return( result );
    }
  }
 
  protected List<BuddyPluginBuddy>
  getAllBuddies()
  {
    synchronized( this ){
     
      return( new ArrayList<BuddyPluginBuddy>( buddies ));
    }
  }
 
  public boolean
  isAvailable()
  {
    try{
      checkAvailable();
     
      return( true );
     
    }catch( Throwable e ){
     
      return( false );
    }
  }
 
  protected void
  checkAvailable()
 
    throws BuddyPluginException
  {
    if ( initialisation_state == INIT_UNKNOWN ){
     
      throw( new BuddyPluginException( "Plugin not yet initialised" ));
     
    }else if ( initialisation_state == INIT_BAD ){
     
      throw( new BuddyPluginException( "Plugin unavailable" ));

    }
  }
 

  protected void
  fireInitialised(
    boolean    ok )
  {
    if ( ok ){
     
      initialisation_state = INIT_OK;
       
    }else{
     
      initialisation_state = INIT_BAD;
    }
         
    persistentDispatchInit();
   
    if ( ok ){
     
      buddy_tracker.initialise();
    }
   
    List   listeners_ref = listeners.getList();
   
    for (int i=0;i<listeners_ref.size();i++){

      try{
        ((BuddyPluginListener)listeners_ref.get(i)).initialised( ok );
       
      }catch( Throwable e ){
       
        Debug.printStackTrace(e);
      }
    } 
  }
 
  public void
  addListener(
    BuddyPluginListener  listener )
  {
    if ( listeners.contains(listener) ){
      return;
    }

    listeners.add( listener );
   
    if ( initialisation_state != INIT_UNKNOWN ){
     
      listener.initialised( initialisation_state == INIT_OK );
    }
  }
 
  public void
  removeListener(
    BuddyPluginListener  listener )
  {
    listeners.remove( listener );
  }
 
  protected Map
  requestReceived(
    BuddyPluginBuddy    from_buddy,
    int            subsystem,
    Map            content )
 
    throws BuddyPluginException
  {
    List   listeners_ref = request_listeners.getList();
   
    for (int i=0;i<listeners_ref.size();i++){
     
      try{
        Map reply = ((BuddyPluginBuddyRequestListener)listeners_ref.get(i)).requestReceived(from_buddy, subsystem, content);
       
        if ( reply != null ){
         
          return( reply );
        }
      }catch( BuddyPluginException e ){
       
        throw( e );
       
      }catch( Throwable e ){
       
        Debug.printStackTrace( e );
       
        throw( new BuddyPluginException( "Request processing failed", e ));
      }
    }
   
    return( null );
  }
 
  protected void
     fireAdded(
       BuddyPluginBuddy    buddy )
     {
    if ( buddy.isAuthorised()){

      buddy.setLocalAuthorisedRSSCategories( public_categories );
     
         List   listeners_ref = listeners.getList();
        
         for (int i=0;i<listeners_ref.size();i++){
          
           try{
             ((BuddyPluginListener)listeners_ref.get(i)).buddyAdded( buddy );
  
           }catch( Throwable e ){
            
             Debug.printStackTrace( e );
           }
         }
    }
     }
 
  protected void
     fireRemoved(
       BuddyPluginBuddy    buddy )
     {
    if ( buddy.isAuthorised()){
     
         List   listeners_ref = listeners.getList();
        
         for (int i=0;i<listeners_ref.size();i++){
          
           try{
             ((BuddyPluginListener)listeners_ref.get(i)).buddyRemoved( buddy );
  
           }catch( Throwable e ){
            
             Debug.printStackTrace( e );
           }
         }
    }
     }
 
  protected void
     fireDetailsChanged(
       BuddyPluginBuddy    buddy )
     {
    if ( buddy.isAuthorised()){
     
         List   listeners_ref = listeners.getList();
        
         for (int i=0;i<listeners_ref.size();i++){
          
           try{
             ((BuddyPluginListener)listeners_ref.get(i)).buddyChanged( buddy );
  
           }catch( Throwable e ){
            
             Debug.printStackTrace( e );
           }
         }
    }
     }
 
  protected void
     fireYGM(
       BuddyPluginBuddy[]    from_buddies )
     {
       List   listeners_ref = request_listeners.getList();
      
       for (int i=0;i<listeners_ref.size();i++){
        
         try{
           ((BuddyPluginBuddyRequestListener)listeners_ref.get(i)).pendingMessages( from_buddies );
         }catch( Throwable e ){
          
           Debug.printStackTrace( e );
         }
       }
     }

  protected void
   fireEnabledStateChanged()
   {
    final boolean enabled = !plugin_interface.getPluginState().isDisabled() && isEnabled();

     List   listeners_ref = listeners.getList();
    
     for (int i=0;i<listeners_ref.size();i++){
      
       try{
         ((BuddyPluginListener)listeners_ref.get(i)).enabledStateChanged( enabled );

       }catch( Throwable e ){
        
         Debug.printStackTrace( e );
       }
     }
   }
 
  protected void
  rethrow(
    String    reason,
    Throwable  e )
 
    throws BuddyPluginException
  {
    logMessage( reason, e );

    if ( e instanceof CryptoManagerPasswordException ){
   
   
      throw( new BuddyPluginPasswordException(((CryptoManagerPasswordException)e).wasIncorrect(), reason, e ));
   
    }else{
   
      throw( new BuddyPluginException( reason, e ));
    }
  }
 

  public InputStream
  handleURLProtocol(
     AZPluginConnection      connection,
    String            arg_str )
 
    throws IPCException
  {
    String[]  args = arg_str.split( "&" );
   
    String    pk       = null;
    String    category  = "All";
    byte[]    hash    = null;
   
    for (String arg: args ){
     
      String[]  bits = arg.split( "=" );
     
      String  lhs = bits[0];
      String  rhs  = UrlUtils.decode( bits[1] );
     
      if ( lhs.equals( "pk" )){
       
        pk    = rhs;
       
      }else if ( lhs.equals( "cat" )){
       
        category = rhs;
       
      }else if ( lhs.equals( "hash" )){
       
        hash  = Base32.decode(rhs);
      }
    }
   
    if ( pk == null ){
     
      throw( new IPCException( "Public key missing from '" + arg_str + "'" ));
    }
   
    BuddyPluginBuddy  buddy  = getBuddyFromPublicKey( pk );

    if ( buddy == null ){
     
      throw( new IPCException( "Buddy with public key '" + pk + "' not found" ));
    }
   
    if ( hash == null ){
     
      return( handleUPRSS( connection, buddy, category ));
     
    }else{
     
      return( handleUPTorrent( connection, buddy, category, hash ));
    }
  }
 
  public InputStream
  handleUPRSS(
    final AZPluginConnection  connection,
    BuddyPluginBuddy      buddy,
    String            category )
 
    throws IPCException
  {
    if ( !buddy.isOnline( true )){
     
      throw( new IPCException( "Buddy isn't online" ));
    }
   
    Map<String,Object>  msg = new HashMap<String, Object>();

    final String if_mod   = connection.getRequestProperty( "If-Modified-Since" );

    try{
      msg.put( "cat", category.getBytes( "UTF-8" ));
                 
      if ( if_mod != null ){
       
        msg.put( "if_mod", if_mod );
      }
     
      // String etag    = connection.getRequestProperty( "If-None-Match" );
         
    }catch( Throwable e ){
     
      Debug.out( e );
    }
   
    final Object[]     result     = { null };
    final AESemaphore  result_sem   = new AESemaphore( "BuddyPlugin:rss" );
       
    final String  etag = buddy.getPublicKey() + "-" + category;
   
    az2_handler.sendAZ2RSSMessage(
      buddy,
      msg,
      new BuddyPluginAZ2TrackerListener()
      {
        public Map
        messageReceived(
          BuddyPluginBuddy  buddy,
          Map          message )
        {
          try{           
            byte[] bytes = (byte[])message.get( "rss" );
         
            ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
           
            result[0] = bais;
           
            connection.setHeaderField( "ETag", etag );
           
            byte[] b_last_mod = (byte[])message.get( "last_mod" );
           
            if ( b_last_mod != null ){
           
              String  last_mod = new String( b_last_mod, "UTF-8" );
             
              connection.setHeaderField( "Last-Modified", last_mod );
             
              if ( if_mod != null && if_mod.equals( last_mod ) && bytes.length == 0 ){
               
                connection.setResponse( HttpURLConnection.HTTP_NOT_MODIFIED, "Not Modified" );
              }
            }
           
            result_sem.release();
           
          }catch( Throwable e ){
         
            messageFailed( buddy, e );
          }
         
          return( null );
        }
       
        public void
        messageFailed(
          BuddyPluginBuddy  buddy,
          Throwable      cause )
        {         
          result[0] = new IPCException( "Read failed", cause );
         
          result_sem.release();
        }
      });
   
    result_sem.reserve( 60*1000 );
   
    if ( result[0] == null ){
     
      throw( new IPCException( "Timeout" ));
     
    }else if ( result[0] instanceof InputStream ){
     
      return((InputStream)result[0]);
     
    }else{
     
      throw((IPCException)result[0]);
    }
  }

  public InputStream
  handleUPTorrent(
    final AZPluginConnection  connection,
    final BuddyPluginBuddy    buddy,
    String            category,
    final byte[]        hash )
 
    throws IPCException
  {
    final long timeout = 120*1000;

    final Object[]     result     = { null };
    final AESemaphore  result_sem   = new AESemaphore( "BuddyPlugin:upt" );
     
    log( "Attempting to download torrent for " + Base32.encode( hash ));
   
      // first try and get torrent direct from the buddy
   
    if ( buddy.isOnline( true )){

      try{
       
        Map<String,Object>  msg = new HashMap<String, Object>();
     
        try{
          msg.put( "cat", category.getBytes( "UTF-8" ));
                 
          msg.put( "hash", hash );
             
        }catch( Throwable e ){
         
          Debug.out( e );
        }
             
        az2_handler.sendAZ2RSSMessage(
          buddy,
          msg,
          new BuddyPluginAZ2TrackerListener()
          {
            private boolean  result_set;
         
            public Map
            messageReceived(
              BuddyPluginBuddy  buddy,
              Map          message )
            {
              try{           
                byte[] bytes = (byte[])message.get( "torrent" );
                 
                log( "    torrent downloaded from buddy" );
               
                setResult( bytes );
                               
              }catch( Throwable e ){
             
                messageFailed( buddy, e );
              }
             
              return( null );
            }
           
            public void
            messageFailed(
              BuddyPluginBuddy  buddy,
              Throwable      cause )
            {         
              setResult( new IPCException( "Read failed", cause ));
            }
           
            protected void
            setResult(
              Object    obj )
            {
              synchronized( result ){
               
                if ( result_set ){
                 
                  return;
                }
               
                result_set = true;
               
                if ( !( result[0] instanceof byte[] )){
               
                  result[0] = obj;
                }
               
                result_sem.release();
              }
            }
          });
      }catch( Throwable e ){
       
        result[0] = new IPCException( "Buddy torrent get failed", e );
       
        result_sem.release();
      }
    }else{
     
      result[0] = new IPCException( "Buddy is offline" );

      result_sem.release();
    }
   
      // second try and get via magnet
   
    final MagnetPlugin  magnet_plugin = getMagnetPlugin();
   
    if ( magnet_plugin == null ){
   
      synchronized( result ){
       
        if ( result[0] == null ){
         
          result[0] = new IPCException( "Magnet plugin unavailable" );
        }
      }
       
      result_sem.release();
     
    }else{

      new AEThread2( "BuddyPlugin:mag", true )
      {
        private boolean result_set;
       
        public void
        run()
        {
          try{
           
            if ( buddy.isOnline( true )){
             
              Thread.sleep(10*1000);
            }
           
            synchronized( result ){

              if ( result[0] instanceof byte[] ){
               
                setResult( null );
               
                return;
              }
            }
           
            byte[] torrent_data = magnet_plugin.download(
              new MagnetPluginProgressListener()
              {
                public void
                reportSize(
                  long  size )
                {
                }
               
                public void
                reportActivity(
                  String  str )
                {
                  log( "    MagnetDownload: " + str );
                }
               
                public void
                reportCompleteness(
                  int    percent )
                {
                }
               
                public void
                reportContributor(
                  InetSocketAddress  address )
                {
                }
               
                public boolean
                verbose()
                {
                  return( false );
                }
              },
              hash,
              "",
              new InetSocketAddress[0],
              timeout );
           
            if ( torrent_data == null ){
             
              setResult( new IPCException( "Magnet timeout" ));
                         
            }else{         
             
              log( "    torrent downloaded from magnet" );

              setResult( torrent_data );
            }
          }catch( Throwable e ){
           
            setResult( new IPCException( "Magnet get failed", e ));
          }
        }
       
        protected void
        setResult(
          Object    obj )
        {
          synchronized( result ){
           
            if ( result_set ){
             
              return;
            }
           
            result_set = true;
           
            if ( obj != null ){
             
              if (   result[0] == null ||
                  ( obj instanceof byte[] && !( result[0] instanceof byte[] ))){
               
                result[0] = obj;
              }
            }
             
            result_sem.release();
          }
        }
      }.start();
    }
   
    long  start = SystemTime.getMonotonousTime();
       
    if ( result_sem.reserve(timeout )){
     
      if ( !( result[0] instanceof byte[] )){
   
        long  rem = timeout - ( SystemTime.getMonotonousTime() - start );
       
        if ( rem > 0 ){
       
          result_sem.reserve(rem );
        }
      }
    }
   
    if ( result[0] == null ){
     
      log( "    torrent download timeout" );

      throw( new IPCException( "Timeout" ));
     
    }else if ( result[0] instanceof byte[] ){
           
      return( new ByteArrayInputStream((byte[])result[0]));
     
    }else{
     
      IPCException error = (IPCException)result[0];
     
      log( "    torrent downloaded failed: " + Debug.getNestedExceptionMessage( error ));

      throw( error );
    }
  }

  protected MagnetPlugin
  getMagnetPlugin()
  {
    PluginInterface  pi  = AzureusCoreFactory.getSingleton().getPluginManager().getPluginInterfaceByClass( MagnetPlugin.class );
 
    if ( pi == null ){
     
      return( null );
    }
   
    return((MagnetPlugin)pi.getPlugin());
  }
 
  public feedDetails
  getRSS(
    BuddyPluginBuddy    buddy,
    String          category,
    String          if_mod )
 
    throws BuddyPluginException
  {
    if ( !buddy.isLocalRSSCategoryAuthorised( category )){
     
      throw( new BuddyPluginException( "Unauthorised category '" + category + "'" ));
    }
   
    buddy.localRSSCategoryRead( category );
   
    Download[] downloads = plugin_interface.getDownloadManager().getDownloads();
   
    List<Download>  selected_dls = new ArrayList<Download>();
   
    long  fingerprint  = 0;
   
    for (int i=0;i<downloads.length;i++){
     
      Download download = downloads[i];
     
      Torrent torrent = download.getTorrent();
     
      if ( torrent == null ){
       
        continue;
      }
     
      String dl_cat = download.getAttribute( ta_category );
   
      if (   category.equalsIgnoreCase( "all" ) ||
          ( dl_cat != null && dl_cat.equals( category ))){
       
        if ( !TorrentUtils.isReallyPrivate( PluginCoreUtils.unwrap( torrent ))){
         
          selected_dls.add( download );
         
          byte[] hash = torrent.getHash();
         
          int  num = (hash[0]<<24)&0xff000000 | (hash[1] << 16)&0x00ff0000 | (hash[2] << 8)&0x0000ff00 | hash[3]&0x000000ff;

          fingerprint += num;
        }
      }
    }
   
    PluginConfig pc = plugin_interface.getPluginconfig();

    String  feed_finger_key = "feed_finger.category." + category;
    String  feed_date_key   = "feed_date.category." + category;

    long  existing_fingerprint   = pc.getPluginLongParameter( feed_finger_key, 0 );
    long  feed_date         = pc.getPluginLongParameter( feed_date_key, 0 );

    long  now = SystemTime.getCurrentTime();

    if ( existing_fingerprint == fingerprint ){
     
        // update every now and then to pick up new peer/seed values
     
      if ( selected_dls.size() > 0 ){

        if (   now < feed_date ||
            now - feed_date > FEED_UPDATE_MIN_MILLIS ){
         
          feed_date = now;
       
          pc.setPluginParameter( feed_date_key, feed_date );
        }
      }
    }else{
     
      pc.setPluginParameter( feed_finger_key, fingerprint );
     
     
        // ensure feed date goes up
     
      if ( now <= feed_date ){
       
        feed_date++;
       
      }else{
       
        feed_date = now;
      }
     
      pc.setPluginParameter( feed_date_key, feed_date );
    }
   
    String last_modified = TimeFormatter.getHTTPDate( feed_date );

    if ( if_mod != null && if_mod.equals( last_modified )){
     
      return( new feedDetails( new byte[0], last_modified ));
    }
       
    ByteArrayOutputStream  os = new ByteArrayOutputStream();
     
    try{
      PrintWriter pw = new PrintWriter(new OutputStreamWriter( os, "UTF-8" ));
     
      pw.println( "<?xml version=\"1.0\" encoding=\"utf-8\"?>" );
     
      pw.println( "<rss version=\"2.0\" xmlns:vuze=\"http://www.vuze.com\">" );
     
      pw.println( "<channel>" );
     
      pw.println( "<title>" + escape( category ) + "</title>" );
     
      Collections.sort(
        selected_dls,
        new Comparator<Download>()
        {
          public int
          compare(
            Download d1,
            Download d2)
          {
            long  added1 = getAddedTime( d1 )/1000;
            long  added2 = getAddedTime( d2 )/1000;
   
            return((int)(added2 - added1 ));
          }
        });
               
             
      pw.println"<pubDate>" + last_modified + "</pubDate>" );
   
      for (int i=0;i<selected_dls.size();i++){
       
        Download download = (Download)selected_dls.get( i );
       
        DownloadManager  core_download = PluginCoreUtils.unwrap( download );
       
        Torrent torrent = download.getTorrent();
       
        String  hash_str = Base32.encode( torrent.getHash());
       
        pw.println( "<item>" );
       
        pw.println( "<title>" + escape( download.getName()) + "</title>" );
       
        pw.println( "<guid>" + hash_str + "</guid>" );
       
        long added = core_download.getDownloadState().getLongParameter(DownloadManagerState.PARAM_DOWNLOAD_ADDED_TIME);
       
        pw.println"<pubDate>" + TimeFormatter.getHTTPDate( added ) + "</pubDate>" );
       
        pw.println"<vuze:size>" + torrent.getSize()+ "</vuze:size>" );
        pw.println"<vuze:assethash>" + hash_str + "</vuze:assethash>" );
       
        String url = "azplug:?id=azbuddy&name=Friends&arg=";
       
        String arg = "pk=" + getPublicKey() + "&cat=" + category + "&hash=" + Base32.encode(torrent.getHash());

        url += URLEncoder.encode( arg, "UTF-8" );
     
        pw.println( "<vuze:downloadurl>" + escape( url ) + "</vuze:downloadurl>" );
   
        DownloadScrapeResult scrape = download.getLastScrapeResult();
       
        if ( scrape != null && scrape.getResponseType() == DownloadScrapeResult.RT_SUCCESS ){
         
          pw.println"<vuze:seeds>" + scrape.getSeedCount() + "</vuze:seeds>" );
          pw.println"<vuze:peers>" + scrape.getNonSeedCount() + "</vuze:peers>" );
        }
       
        pw.println( "</item>" );
      }
     
      pw.println( "</channel>" );
     
      pw.println( "</rss>" );
   
      pw.flush();
     
      return( new feedDetails( os.toByteArray(), last_modified ));
     
    }catch( IOException e ){
     
      throw( new BuddyPluginException( "", e ));
    }
  }
 
  public byte[]
  getRSSTorrent(
    BuddyPluginBuddy    buddy,
    String          category,
    byte[]          hash )
 
    throws BuddyPluginException
  {
    if ( !buddy.isLocalRSSCategoryAuthorised( category )){
     
      throw( new BuddyPluginException( "Unauthorised category '" + category + "'" ));
    }
   
    try{
      Download download = plugin_interface.getDownloadManager().getDownload( hash );
     
      if ( download != null ){
       
        Torrent  torrent = download.getTorrent();
     
        if ( torrent != null ){
               
          String dl_cat = download.getAttribute( ta_category );
         
          if (   category.equalsIgnoreCase( "all" ) ||
              ( dl_cat != null && dl_cat.equals( category ))){
           
            if ( !TorrentUtils.isReallyPrivate( PluginCoreUtils.unwrap( torrent ))){
             
              torrent = torrent.removeAdditionalProperties();
             
              return( torrent.writeToBEncodedData());
            }
          }
        }
      }
    }catch( Throwable e ){
     
      throw( new BuddyPluginException( "getTorrent failed", e ));
    }
   
    throw( new BuddyPluginException( "Not found" ));
  }   

  protected long
  getAddedTime(
    Download  download )
  {
    DownloadManager  core_download = PluginCoreUtils.unwrap( download );
   
    return( core_download.getDownloadState().getLongParameter(DownloadManagerState.PARAM_DOWNLOAD_ADDED_TIME));
  }
 
  protected String
  escape(
    String  str )
  {
    return( XUXmlWriter.escapeXML(str));
  }
 
  public void
  addRequestListener(
    BuddyPluginBuddyRequestListener  listener )
  {
    request_listeners.add( listener );
  }
 
  public void
  removeRequestListener(
    BuddyPluginBuddyRequestListener  listener )
  {
    request_listeners.remove( listener );
  }
 
  public void
  logMessage(
    String    str,
    Throwable  e )
  {
    logMessage( str + ": " + Debug.getNestedExceptionMessage(e), true );
  }
 
  public void
  logMessage(
    String    str )
  {
    logMessage( str, false );
  }
 
  public void
  logMessage(
    String    str,
    boolean    is_error )
  {
    log( str );
   
    Iterator it = listeners.iterator();
   
    while( it.hasNext()){
     
      try{
        ((BuddyPluginListener)it.next()).messageLogged( str, is_error );
       
      }catch( Throwable e ){
       
        Debug.printStackTrace( e );
      }
    }
  }
 
  public void
  log(
    String    str )
  {
    logger.log( str );
  }
 
  public void
  log(
    String    str,
    Throwable  e )
  {
    logger.log( str + ": " + Debug.getNestedExceptionMessageAndStack( e ));
  }

  private class
  publishDetails
    implements Cloneable
  {
    private byte[]      public_key;
    private InetAddress    ip;
    private int        tcp_port;
    private int        udp_port;
    private String      nick_name;
    private int        online_status    = STATUS_ONLINE;
   
    private boolean      enabled;
    private boolean      published;
   
    private int        sequence;
   
    protected publishDetails
    getCopy()
    {
      try{
        publishDetails copy = (publishDetails)clone();
       
        copy.published = false;
       
        return( copy );
       
      }catch( Throwable e ){
       
        return( null);
      }
    }
   
    protected boolean
    isPublished()
    {
      return( published );
    }
   
    protected void
    setPublished(
      boolean    b )
    {
      published  = b;
    }
   
    protected boolean
    isEnabled()
    {
      return( enabled );
    }
   
    protected void
    setEnabled(
      boolean  _enabled )
    {
      enabled  = _enabled;
    }
   
    protected void
    setSequence(
      int    seq )
    {
      sequence = seq;
    }
   
    protected int
    getSequence()
    {
      return( sequence );
    }
   
    protected byte[]
    getPublicKey()
    {
      return( public_key );
    }
   
    protected void
    setPublicKey(
      byte[]    k )
    {
      public_key  = k;
    }
   
    protected InetAddress
    getIP()
    {
      return( ip );
    }
   
    protected void
    setIP(
      InetAddress  _ip )
    {
      ip  = _ip;
    }
   
    protected int
    getTCPPort()
    {
      return( tcp_port );
    }
   
    protected void
    setTCPPort(
      int    _port )
    {
      tcp_port = _port;
    }
   
    protected int
    getUDPPort()
    {
      return( udp_port );
    }
   
    protected void
    setUDPPort(
      int    _port )
    {
      udp_port = _port;
    }
   
    protected String
    getNickName()
    {
      return( nick_name );
    }
   
    protected void
    setNickName(
      String     n )
    {
      nick_name  = n;
    }
   
    protected int
    getOnlineStatus()
    {
      return( online_status );
    }
   
    protected void
    setOnlineStatus(
      int    _status )
    {
      online_status = _status;
    }
   
    protected String
    getString()
    {
      return( "enabled=" + enabled + ",ip=" + ip + ",tcp=" + tcp_port + ",udp=" + udp_port + ",stat=" + online_status + ",key=" + (public_key==null?"<none>":Base32.encode( public_key )));
    }
  }
 
  protected interface
  operationListener
  {
    public void
    complete();
  }
 
  public interface
  cryptoResult
  {
    public byte[]
    getChallenge();
   
    public byte[]
    getPayload();
  }
 
  protected class
  feedDetails
  {
    private byte[]    contents;
    private String    last_modified;
   
    protected
    feedDetails(
      byte[]    _contents,
      String    _last_modified )
    {
      contents    = _contents;
      last_modified  = _last_modified;
    }
   
    protected byte[]
    getContent()
    {
      return( contents );
    }
   
    protected String
    getLastModified()
    {
      return( last_modified );
    }
  }
}
TOP

Related Classes of com.aelitis.azureus.plugins.net.buddy.BuddyPlugin

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.