/*
* Created on 18-Feb-2005
* Created by Paul Gardner
* Copyright (C) 2004, 2005, 2006 Aelitis, 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; either version 2
* of the License, or (at your option) any later version.
* 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.
*
* AELITIS, SAS au capital de 46,603.30 euros
* 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
*
*/
package org.gudy.azureus2.pluginsimpl.local.ddb;
import java.net.InetSocketAddress;
import java.util.*;
import org.gudy.azureus2.core3.util.AEMonitor;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.HashWrapper;
import org.gudy.azureus2.core3.util.SHA1Simple;
import org.gudy.azureus2.core3.util.SystemTime;
import org.gudy.azureus2.plugins.PluginInterface;
import org.gudy.azureus2.plugins.ddb.*;
import com.aelitis.azureus.core.AzureusCore;
import com.aelitis.azureus.core.util.CopyOnWriteList;
import com.aelitis.azureus.plugins.dht.DHTPlugin;
import com.aelitis.azureus.plugins.dht.DHTPluginContact;
import com.aelitis.azureus.plugins.dht.DHTPluginKeyStats;
import com.aelitis.azureus.plugins.dht.DHTPluginListener;
import com.aelitis.azureus.plugins.dht.DHTPluginOperationListener;
import com.aelitis.azureus.plugins.dht.DHTPluginProgressListener;
import com.aelitis.azureus.plugins.dht.DHTPluginTransferHandler;
import com.aelitis.azureus.plugins.dht.DHTPluginValue;
/**
* @author parg
*
*/
public class
DDBaseImpl
implements DistributedDatabase
{
private static DDBaseImpl singleton;
protected static AEMonitor class_mon = new AEMonitor( "DDBaseImpl:class");
protected static Map transfer_map = new HashMap();
private static DDBaseTTTorrent torrent_transfer;
public static DDBaseImpl
getSingleton(
AzureusCore azureus_core )
{
try{
class_mon.enter();
if ( singleton == null ){
singleton = new DDBaseImpl( azureus_core );
}
}finally{
class_mon.exit();
}
return( singleton );
}
private AzureusCore azureus_core;
private DHTPlugin dht_use_accessor;
private CopyOnWriteList listeners = new CopyOnWriteList();
protected
DDBaseImpl(
final AzureusCore _azureus_core )
{
azureus_core = _azureus_core;
torrent_transfer = new DDBaseTTTorrent( this );
grabDHT();
}
public DDBaseTTTorrent
getTTTorrent()
{
return( torrent_transfer );
}
protected DHTPlugin
grabDHT()
{
if ( dht_use_accessor != null ){
return( dht_use_accessor );
}
try{
class_mon.enter();
if( dht_use_accessor == null ){
PluginInterface dht_pi =
azureus_core.getPluginManager().getPluginInterfaceByClass(
DHTPlugin.class );
if ( dht_pi != null ){
dht_use_accessor = (DHTPlugin)dht_pi.getPlugin();
if ( dht_use_accessor.isEnabled()){
dht_use_accessor.addListener(
new DHTPluginListener()
{
public void
localAddressChanged(
DHTPluginContact local_contact )
{
List l = listeners.getList();
dbEvent ev = new dbEvent( DistributedDatabaseEvent.ET_LOCAL_CONTACT_CHANGED );
for (int i=0;i<l.size();i++){
((DistributedDatabaseListener)l.get(i)).event( ev );
}
}
});
try{
addTransferHandler( torrent_transfer, torrent_transfer );
}catch( Throwable e ){
Debug.printStackTrace(e);
}
}
}
}
}finally{
class_mon.exit();
}
return( dht_use_accessor );
}
public boolean
isAvailable()
{
DHTPlugin dht = grabDHT();
if ( dht == null ){
return( false );
}
return( dht.isEnabled());
}
public boolean
isExtendedUseAllowed()
{
DHTPlugin dht = grabDHT();
if ( dht == null ){
return( false );
}
return( dht.isExtendedUseAllowed());
}
public DistributedDatabaseContact
getLocalContact()
{
DHTPlugin dht = grabDHT();
if ( dht == null ){
return( null );
}
return( new DDBaseContactImpl( this, dht.getLocalAddress()));
}
protected void
throwIfNotAvailable()
throws DistributedDatabaseException
{
if ( !isAvailable()){
throw( new DistributedDatabaseException( "DHT not available" ));
}
}
protected DHTPlugin
getDHT()
throws DistributedDatabaseException
{
throwIfNotAvailable();
return( grabDHT());
}
protected void
log(
String str )
{
DHTPlugin dht = grabDHT();
if ( dht != null ){
dht.log( str );
}
}
public DistributedDatabaseKey
createKey(
Object key )
throws DistributedDatabaseException
{
throwIfNotAvailable();
return( new DDBaseKeyImpl( key ));
}
public DistributedDatabaseKey
createKey(
Object key,
String description )
throws DistributedDatabaseException
{
throwIfNotAvailable();
return( new DDBaseKeyImpl( key, description ));
}
public DistributedDatabaseValue
createValue(
Object value )
throws DistributedDatabaseException
{
throwIfNotAvailable();
return( new DDBaseValueImpl( new DDBaseContactImpl( this, getDHT().getLocalAddress()), value, SystemTime.getCurrentTime(), -1));
}
public DistributedDatabaseContact
importContact(
InetSocketAddress address )
throws DistributedDatabaseException
{
throwIfNotAvailable();
DHTPluginContact contact = getDHT().importContact( address );
if ( contact == null ){
throw( new DistributedDatabaseException( "import of '" + address + "' failed" ));
}
return( new DDBaseContactImpl( this, contact));
}
public DistributedDatabaseContact
importContact(
InetSocketAddress address,
byte version )
throws DistributedDatabaseException
{
throwIfNotAvailable();
DHTPluginContact contact = getDHT().importContact( address, version );
if ( contact == null ){
throw( new DistributedDatabaseException( "import of '" + address + "' failed" ));
}
return( new DDBaseContactImpl( this, contact));
}
public void
write(
DistributedDatabaseListener listener,
DistributedDatabaseKey key,
DistributedDatabaseValue value )
throws DistributedDatabaseException
{
write( listener, key, new DistributedDatabaseValue[]{ value } );
}
public void
write(
final DistributedDatabaseListener listener,
final DistributedDatabaseKey key,
final DistributedDatabaseValue values[] )
throws DistributedDatabaseException
{
throwIfNotAvailable();
for (int i=0;i<values.length;i++){
if (((DDBaseValueImpl)values[i]).getBytes().length > DDBaseValueImpl.MAX_VALUE_SIZE ){
throw( new DistributedDatabaseException("Value size limited to " + DDBaseValueImpl.MAX_VALUE_SIZE + " bytes" ));
}
}
if ( values.length == 0 ){
delete( listener, key );
}else if ( values.length == 1 ){
getDHT().put(
((DDBaseKeyImpl)key).getBytes(),
key.getDescription(),
((DDBaseValueImpl)values[0]).getBytes(),
DHTPlugin.FLAG_SINGLE_VALUE,
new listenerMapper( listener, DistributedDatabaseEvent.ET_VALUE_WRITTEN, key, 0, false, false ));
}else{
// TODO: optimise re-publishing to avoid republishing everything each time
/*
DHTPluginValue old_value = dht.getLocalValue( ((DDBaseKeyImpl)key).getBytes());
List old_values = new ArrayList();
if ( old_value != null ){
if (( old_value.getFlags() & DHTPlugin.FLAG_MULTI_VALUE ) == 0 ){
old_values.add( old_value.getValue());
}else{
byte[] encoded = old_value.getValue();
}
}
*/
byte[] current_key = ((DDBaseKeyImpl)key).getBytes();
// format is: <continuation> <len><len><data>
byte[] payload = new byte[DHTPlugin.MAX_VALUE_SIZE];
int payload_length = 1;
int pos = 0;
while( pos < values.length ){
DDBaseValueImpl value = (DDBaseValueImpl)values[pos];
byte[] bytes = value.getBytes();
int len = bytes.length;
if ( payload_length + len < payload.length - 2 ){
payload[payload_length++] = (byte)(( len & 0x0000ff00 ) >> 8);
payload[payload_length++] = (byte) ( len & 0x000000ff );
System.arraycopy( bytes, 0, payload, payload_length, len );
payload_length += len;
pos++;
}else{
payload[0] = 1;
final byte[] copy = new byte[payload_length];
System.arraycopy( payload, 0, copy, 0, copy.length );
final byte[] f_current_key = current_key;
getDHT().put(
f_current_key,
key.getDescription(),
copy,
DHTPlugin.FLAG_MULTI_VALUE,
new listenerMapper( listener, DistributedDatabaseEvent.ET_VALUE_WRITTEN, key, 0, false, false ));
payload_length = 1;
current_key = new SHA1Simple().calculateHash( current_key );
}
}
if ( payload_length > 1 ){
payload[0] = 0;
final byte[] copy = new byte[payload_length];
System.arraycopy( payload, 0, copy, 0, copy.length );
final byte[] f_current_key = current_key;
getDHT().put(
f_current_key,
key.getDescription(),
copy,
DHTPlugin.FLAG_MULTI_VALUE,
new listenerMapper( listener, DistributedDatabaseEvent.ET_VALUE_WRITTEN, key, 0, false, false ));
}
}
}
public void
read(
DistributedDatabaseListener listener,
DistributedDatabaseKey key,
long timeout )
throws DistributedDatabaseException
{
read( listener, key, timeout, OP_NONE );
}
public void
read(
final DistributedDatabaseListener listener,
final DistributedDatabaseKey key,
final long timeout,
int options )
throws DistributedDatabaseException
{
throwIfNotAvailable();
boolean exhaustive = (options&OP_EXHAUSTIVE_READ)!=0;
boolean high_priority = (options&OP_PRIORITY_HIGH)!=0;
// TODO: max values?
getDHT().get(
((DDBaseKeyImpl)key).getBytes(),
key.getDescription(),
(byte)0,
256,
timeout,
exhaustive,
high_priority,
new listenerMapper( listener, DistributedDatabaseEvent.ET_VALUE_READ, key, timeout, exhaustive, high_priority ));
}
public void
readKeyStats(
DistributedDatabaseListener listener,
DistributedDatabaseKey key,
long timeout )
throws DistributedDatabaseException
{
throwIfNotAvailable();
getDHT().get(
((DDBaseKeyImpl)key).getBytes(),
key.getDescription(),
DHTPlugin.FLAG_STATS,
256,
timeout,
false,
false,
new listenerMapper( listener, DistributedDatabaseEvent.ET_KEY_STATS_READ, key, timeout, false, false ));
}
public void
delete(
final DistributedDatabaseListener listener,
final DistributedDatabaseKey key )
throws DistributedDatabaseException
{
throwIfNotAvailable();
getDHT().remove(
((DDBaseKeyImpl)key).getBytes(),
key.getDescription(),
new listenerMapper( listener, DistributedDatabaseEvent.ET_VALUE_DELETED, key, 0, false, false ));
}
public void
delete(
DistributedDatabaseListener listener,
DistributedDatabaseKey key,
DistributedDatabaseContact[] targets )
throws DistributedDatabaseException
{
throwIfNotAvailable();
DHTPluginContact[] plugin_targets = new DHTPluginContact[ targets.length ];
for (int i=0;i<targets.length;i++){
plugin_targets[i] = ((DDBaseContactImpl)targets[i]).getContact();
}
getDHT().remove(
plugin_targets,
((DDBaseKeyImpl)key).getBytes(),
key.getDescription(),
new listenerMapper( listener, DistributedDatabaseEvent.ET_VALUE_DELETED, key, 0, false, false ));
}
public void
addTransferHandler(
final DistributedDatabaseTransferType type,
final DistributedDatabaseTransferHandler handler )
throws DistributedDatabaseException
{
throwIfNotAvailable();
final HashWrapper type_key = DDBaseHelpers.getKey( type.getClass());
if ( transfer_map.get( type_key ) != null ){
throw( new DistributedDatabaseException( "Handler for class '" + type.getClass().getName() + "' already defined" ));
}
transfer_map.put( type_key, handler );
final String handler_name;
if ( type == torrent_transfer ){
handler_name = "Torrent Transfer";
}else{
String class_name = type.getClass().getName();
int pos = class_name.indexOf( '$' );
if ( pos != -1 ){
class_name = class_name.substring( pos+1 );
}else{
pos = class_name.lastIndexOf( '.' );
if ( pos != -1 ){
class_name = class_name.substring( pos+1 );
}
}
handler_name = "Plugin Defined (" + class_name + ")";
}
getDHT().registerHandler(
type_key.getHash(),
new DHTPluginTransferHandler()
{
public String
getName()
{
return( handler_name );
}
public byte[]
handleRead(
DHTPluginContact originator,
byte[] xfer_key )
{
try{
DDBaseValueImpl res = (DDBaseValueImpl)
handler.read(
new DDBaseContactImpl( DDBaseImpl.this, originator ),
type,
new DDBaseKeyImpl( xfer_key ));
if ( res == null ){
return( null );
}
return( res.getBytes());
}catch( Throwable e ){
Debug.printStackTrace(e);
return( null );
}
}
public void
handleWrite(
DHTPluginContact originator,
byte[] xfer_key,
byte[] value )
{
try{
DDBaseContactImpl contact = new DDBaseContactImpl( DDBaseImpl.this, originator );
handler.write(
contact,
type,
new DDBaseKeyImpl( xfer_key ),
new DDBaseValueImpl( contact, value, SystemTime.getCurrentTime(), -1));
}catch( Throwable e ){
Debug.printStackTrace(e);
}
}
});
}
public DistributedDatabaseTransferType
getStandardTransferType(
int standard_type )
throws DistributedDatabaseException
{
if ( standard_type == DistributedDatabaseTransferType.ST_TORRENT ){
return( torrent_transfer );
}
throw( new DistributedDatabaseException( "unknown type" ));
}
protected DistributedDatabaseValue
read(
DDBaseContactImpl contact,
final DistributedDatabaseProgressListener listener,
DistributedDatabaseTransferType type,
DistributedDatabaseKey key,
long timeout )
throws DistributedDatabaseException
{
if ( type == torrent_transfer ){
return( torrent_transfer.read( contact, listener, type, key, timeout ));
}else{
DHTPluginContact plugin_contact = contact.getContact();
byte[] data = plugin_contact.read(
new DHTPluginProgressListener()
{
public void
reportSize(
long size )
{
listener.reportSize( size );
}
public void
reportActivity(
String str )
{
listener.reportActivity( str );
}
public void
reportCompleteness(
int percent )
{
listener.reportCompleteness( percent );
}
},
DDBaseHelpers.getKey(type.getClass()).getHash(),
((DDBaseKeyImpl)key).getBytes(),
timeout );
if ( data == null ){
return( null );
}
return( new DDBaseValueImpl( contact, data, SystemTime.getCurrentTime(), -1));
}
}
public void
addListener(
DistributedDatabaseListener l )
{
listeners.add( l );
}
public void
removeListener(
DistributedDatabaseListener l )
{
listeners.remove( l );
}
protected class
listenerMapper
implements DHTPluginOperationListener
{
private DistributedDatabaseListener listener;
private int type;
private DistributedDatabaseKey key;
private byte[] key_bytes;
private long timeout;
private boolean complete_disabled;
private boolean exhaustive;
private boolean high_priority;
private int continuation_num;
protected
listenerMapper(
DistributedDatabaseListener _listener,
int _type,
DistributedDatabaseKey _key,
long _timeout,
boolean _exhaustive,
boolean _high_priority )
{
listener = _listener;
type = _type;
key = _key;
key_bytes = ((DDBaseKeyImpl)key).getBytes();
timeout = _timeout;
exhaustive = _exhaustive;
high_priority = _high_priority;
continuation_num = 1;
}
private
listenerMapper(
DistributedDatabaseListener _listener,
int _type,
DistributedDatabaseKey _key,
byte[] _key_bytes,
long _timeout,
int _continuation_num )
{
listener = _listener;
type = _type;
key = _key;
key_bytes = _key_bytes;
timeout = _timeout;
continuation_num = _continuation_num;
}
public void
diversified()
{
}
public void
starts(
byte[] _key )
{
listener.event( new dbEvent( DistributedDatabaseEvent.ET_OPERATION_STARTS, key ));
}
public void
valueRead(
DHTPluginContact originator,
DHTPluginValue _value )
{
if ( type == DistributedDatabaseEvent.ET_KEY_STATS_READ ){
if (( _value.getFlags() & DHTPlugin.FLAG_STATS ) == 0 ){
// skip, old impl
return;
}
try{
final DHTPluginKeyStats stats = getDHT().decodeStats( _value );
DistributedDatabaseKeyStats ddb_stats = new
DistributedDatabaseKeyStats()
{
public int
getEntryCount()
{
return( stats.getEntryCount());
}
public int
getSize()
{
return( stats.getSize());
}
public int
getReadsPerMinute()
{
return( stats.getReadsPerMinute());
}
public byte
getDiversification()
{
return( stats.getDiversification());
}
};
listener.event( new dbEvent( type, key, originator, ddb_stats ));
}catch( Throwable e ){
Debug.printStackTrace(e);
}
}else{
byte[] value = _value.getValue();
if ( _value.getFlags() == DHTPlugin.FLAG_MULTI_VALUE ){
int pos = 1;
while( pos < value.length ){
int len = ( ( value[pos++]<<8 ) & 0x0000ff00 )+
( value[pos++] & 0x000000ff );
if ( len > value.length - pos ){
Debug.out( "Invalid length: len = " + len + ", remaining = " + (value.length - pos ));
break;
}
byte[] d = new byte[len];
System.arraycopy( value, pos, d, 0, len );
listener.event( new dbEvent( type, key, originator, d, _value.getCreationTime(), _value.getVersion()));
pos += len;
}
if ( value[0] == 1 ){
// continuation exists
final byte[] next_key_bytes = new SHA1Simple().calculateHash( key_bytes );
complete_disabled = true;
grabDHT().get(
next_key_bytes,
key.getDescription() + " [continuation " + continuation_num + "]",
(byte)0,
256,
timeout,
exhaustive,
high_priority,
new listenerMapper( listener, DistributedDatabaseEvent.ET_VALUE_READ, key, next_key_bytes, timeout, continuation_num+1 ));
}
}else{
listener.event( new dbEvent( type, key, originator, _value ));
}
}
}
public void
valueWritten(
DHTPluginContact target,
DHTPluginValue value )
{
listener.event( new dbEvent( type, key, target, value ));
}
public void
complete(
byte[] timeout_key,
boolean timeout_occurred )
{
if ( !complete_disabled ){
listener.event(
new dbEvent(
timeout_occurred?DistributedDatabaseEvent.ET_OPERATION_TIMEOUT:DistributedDatabaseEvent.ET_OPERATION_COMPLETE,
key ));
}
}
}
protected class
dbEvent
implements DistributedDatabaseEvent
{
private int type;
private DistributedDatabaseKey key;
private DistributedDatabaseKeyStats key_stats;
private DistributedDatabaseValue value;
private DDBaseContactImpl contact;
protected
dbEvent(
int _type )
{
type = _type;
}
protected
dbEvent(
int _type,
DistributedDatabaseKey _key )
{
type = _type;
key = _key;
}
protected
dbEvent(
int _type,
DistributedDatabaseKey _key,
DHTPluginContact _contact,
DHTPluginValue _value )
{
type = _type;
key = _key;
contact = new DDBaseContactImpl( DDBaseImpl.this, _contact );
value = new DDBaseValueImpl( contact, _value.getValue(), _value.getCreationTime(), _value.getVersion());
}
protected
dbEvent(
int _type,
DistributedDatabaseKey _key,
DHTPluginContact _contact,
DistributedDatabaseKeyStats _key_stats )
{
type = _type;
key = _key;
contact = new DDBaseContactImpl( DDBaseImpl.this, _contact );
key_stats = _key_stats;
}
protected
dbEvent(
int _type,
DistributedDatabaseKey _key,
DHTPluginContact _contact,
byte[] _value,
long _ct,
long _v )
{
type = _type;
key = _key;
contact = new DDBaseContactImpl( DDBaseImpl.this, _contact );
value = new DDBaseValueImpl( contact, _value, _ct, _v );
}
public int
getType()
{
return( type );
}
public DistributedDatabaseKey
getKey()
{
return( key );
}
public DistributedDatabaseKeyStats
getKeyStats()
{
return( key_stats );
}
public DistributedDatabaseValue
getValue()
{
return( value );
}
public DistributedDatabaseContact
getContact()
{
return( contact );
}
}
}