/* * Created on Apr 23, 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.File; import java.util.*; import org.gudy.azureus2.core3.util.BDecoder; import org.gudy.azureus2.core3.util.BEncoder; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.core3.util.SystemTime; import com.aelitis.azureus.core.util.CopyOnWriteList; public class BuddyPluginBuddyMessageHandler { private BuddyPluginBuddy buddy; private File store; private Map config_map; private int message_count; private int pending_deletes; private int next_message_id; private CopyOnWriteList listeners = new CopyOnWriteList(); private BuddyPluginBuddyMessage active_message; private long last_failure; private long last_pending_success; protected BuddyPluginBuddyMessageHandler( BuddyPluginBuddy _buddy, File _store ) { buddy = _buddy; store = _store; loadConfig(); if ( message_count > 0 ){ buddy.persistentDispatchPending(); } } public BuddyPluginBuddy getBuddy() { return( buddy ); } public BuddyPluginBuddyMessage queueMessage( int subsystem, Map content, int timeout_millis ) throws BuddyPluginException { BuddyPluginBuddyMessage message; boolean dispatch_pending; synchronized( this ){ int id = next_message_id++; message = new BuddyPluginBuddyMessage( this, id, subsystem, content, timeout_millis, SystemTime.getCurrentTime()); storeMessage( message ); dispatch_pending = message_count == 1; } Iterator it = listeners.iterator(); while( it.hasNext()){ try{ ((BuddyPluginBuddyMessageListener)it.next()).messageQueued( message ); }catch( Throwable e ){ Debug.printStackTrace(e); } } if ( dispatch_pending ){ buddy.persistentDispatchPending(); } return( message ); } protected void checkPersistentDispatch() { boolean request_dispatch = false; synchronized( this ){ long now = SystemTime.getCurrentTime(); if ( now < last_failure ){ last_failure = now; } if ( now < last_pending_success ){ last_pending_success = now; } if ( last_pending_success > 0 && now - last_pending_success >= BuddyPlugin.PERSISTENT_MSG_RETRY_PERIOD ){ request_dispatch = true; }else if ( active_message != null || message_count == 0 || last_failure == 0 ){ // no messages pending }else{ request_dispatch = now - last_failure >= BuddyPlugin.PERSISTENT_MSG_RETRY_PERIOD; } } if ( request_dispatch ){ buddy.persistentDispatchPending(); } } protected void persistentDispatch() { checkPendingSuccess(); synchronized( this ){ if ( active_message != null || message_count == 0 ){ return; } List messages = (List)config_map.get( "messages" ); Map map = (Map)messages.get(0); try{ active_message = restoreMessage( map ); }catch( Throwable e ){ // should never happen... Debug.out( "Failed to restore message, deleting it", e ); messages.remove(0); try{ saveConfig(); }catch( Throwable f ){ buddy.log( "Config save failed during delete of bad message", f ); } } } boolean request_ok = false; try{ Map request = active_message.getRequest(); request_ok = true; buddy.sendMessage( active_message.getSubsystem(), request, active_message.getTimeout(), new BuddyPluginBuddyReplyListener() { public void replyReceived( BuddyPluginBuddy from_buddy, Map reply ) { BuddyPluginBuddyMessage message = active_message; // inform listeners before deleting message as it gives them one // last chance to do something with the message if they so desire Iterator it = listeners.iterator(); boolean processing_ok = true; // prematurely reduce message count when informing listeners // so they see the "correct" value try{ synchronized( BuddyPluginBuddyMessageHandler.this ){ pending_deletes++; } while( it.hasNext()){ try{ if ( !((BuddyPluginBuddyMessageListener)it.next()).deliverySucceeded( message, reply )){ processing_ok = false; } }catch( Throwable e ){ Debug.printStackTrace(e); } } }finally{ synchronized( BuddyPluginBuddyMessageHandler.this ){ pending_deletes--; } } if ( processing_ok ){ message.delete(); }else{ synchronized( BuddyPluginBuddyMessageHandler.this ){ boolean found = false; List messages = (List)config_map.get( "messages" ); if ( messages != null ){ for ( int i=0;i<messages.size();i++){ Map msg = (Map)messages.get(i); if ( message.getID() == ((Long)msg.get( "id")).intValue()){ found = true; messages.remove(i); try{ writeReply( message, reply ); List pending_success = (List)config_map.get( "pending_success" ); if ( pending_success == null ){ pending_success = new ArrayList(); config_map.put( "pending_success", pending_success ); } pending_success.add( msg ); last_pending_success = SystemTime.getCurrentTime(); buddy.log( "Message moved to pending success queue after listener failed" ); saveConfig(); }catch( Throwable e ){ buddy.log( "Config save failed during message pending queueing", e ); } break; } } } if ( !found ){ buddy.log( "Failed to find message " + message.getID()); } } } boolean messages_queued; synchronized( BuddyPluginBuddyMessageHandler.this ){ active_message = null; messages_queued = message_count > 0; last_failure = 0; } if ( messages_queued ){ buddy.persistentDispatchPending(); } } public void sendFailed( BuddyPluginBuddy to_buddy, BuddyPluginException cause ) { BuddyPluginBuddyMessage message = active_message; synchronized( BuddyPluginBuddyMessageHandler.this ){ active_message = null; last_failure = SystemTime.getCurrentTime(); } reportFailed( message, cause, true ); } }); }catch( Throwable cause ){ BuddyPluginBuddyMessage message = active_message; synchronized( this ){ active_message = null; last_failure = SystemTime.getCurrentTime(); } boolean do_subsequent = true; if ( !request_ok && !( cause instanceof BuddyPluginPasswordException )){ buddy.logMessage( "Message request unavailable, deleting message" ); message.delete(); boolean messages_queued = false; synchronized( this ){ last_failure = 0; messages_queued = message_count > 0; } if ( messages_queued ){ do_subsequent = false; buddy.persistentDispatchPending(); } } reportFailed( message, cause, do_subsequent ); } } protected void reportFailed( BuddyPluginBuddyMessage message, Throwable cause, boolean do_subsequent ) { BuddyPluginException b_cause; if ( cause instanceof BuddyPluginException ){ b_cause = (BuddyPluginException)cause; }else{ b_cause = new BuddyPluginException( "Failed to send message", cause ); } reportFailedSupport( message, b_cause ); if ( do_subsequent ){ List other_messages = new ArrayList(); synchronized( this ){ List messages = (List)config_map.get( "messages" ); for (int i=0;i<messages.size();i++){ try{ BuddyPluginBuddyMessage msg = restoreMessage((Map)messages.get(i)); if ( msg.getID() != message.getID()){ other_messages.add( msg ); } }catch( Throwable e ){ } } } if ( other_messages.size() > 0 ){ BuddyPluginException o_cause = new BuddyPluginException( "Reporting probable failure to subsequent messages" ); for (int i=0;i<other_messages.size();i++){ reportFailedSupport((BuddyPluginBuddyMessage)other_messages.get(i), o_cause ); } } } } protected void reportFailedSupport( BuddyPluginBuddyMessage message, BuddyPluginException cause ) { Iterator it = listeners.iterator(); while( it.hasNext()){ try{ ((BuddyPluginBuddyMessageListener)it.next()).deliveryFailed( message, cause ); }catch( Throwable e ){ Debug.printStackTrace(e); } } } protected void checkPendingSuccess() { List pending_messages = new ArrayList(); boolean save_pending = false; synchronized( this ){ last_pending_success = 0; List pending_success = (List)config_map.get( "pending_success" ); if ( pending_success == null || pending_success.size() == 0 ){ return; } Iterator it = pending_success.iterator(); while( it.hasNext()){ Map map = (Map)it.next(); try{ pending_messages.add( restoreMessage( map )); }catch( Throwable e ){ buddy.log( "Failed to restore message from pending success queue", e ); it.remove(); save_pending = true; } } } for ( int i=0;i<pending_messages.size();i++){ BuddyPluginBuddyMessage message = (BuddyPluginBuddyMessage)pending_messages.get(i); try{ Map reply = message.getReply(); Iterator it = listeners.iterator(); boolean processing_ok = true; while( it.hasNext()){ try{ if ( !((BuddyPluginBuddyMessageListener)it.next()).deliverySucceeded( message, reply )){ processing_ok = false; } }catch( Throwable e ){ Debug.printStackTrace(e); } } if ( processing_ok ){ message.delete(); }else{ synchronized( this ){ last_pending_success = SystemTime.getCurrentTime(); } } }catch( BuddyPluginPasswordException e ){ buddy.log( "Failed to restore message reply", e ); // we don't want to delete the message if failed due to password issue }catch( Throwable e ){ buddy.log( "Failed to restore message reply - deleting message", e ); message.delete(); } } if ( save_pending ){ try{ saveConfig(); }catch( Throwable e ){ buddy.log( "Save failed during pending success processing", e ); } } } public int getMessageCount() { synchronized( this ){ return( message_count - pending_deletes ); } } protected void deleteMessage( BuddyPluginBuddyMessage message ) { Iterator it = listeners.iterator(); while( it.hasNext()){ try{ ((BuddyPluginBuddyMessageListener)it.next()).messageDeleted( message ); }catch( Throwable e ){ Debug.printStackTrace(e); } } synchronized( this ){ String[] keys = { "messages", "pending_success", "explicit" }; for (int i=0;i<keys.length;i++){ List messages = (List)config_map.get( keys[i] ); if ( messages != null ){ boolean found = false; for ( int j=0;j<messages.size();j++){ Map msg = (Map)messages.get(j); if ( message.getID() == ((Long)msg.get( "id")).intValue()){ messages.remove(j); found = true; break; } } if ( found ){ deleteRequest( message ); deleteReply( message ); try{ saveConfig(); }catch( Throwable e ){ buddy.log( "Config save failed during message delete", e ); } return; } } } } } protected void destroy() { synchronized( this ){ config_map.clear(); try{ saveConfig(); }catch( Throwable e ){ buddy.log( "Config save failed during destroy", e ); } } } protected void writeRequest( BuddyPluginBuddyMessage message, Map content ) throws BuddyPluginException { writeContent( message.getID() + ".req.dat", content ); } protected Map readRequest( BuddyPluginBuddyMessage message ) throws BuddyPluginException { return( readContent( message.getID() + ".req.dat" )); } protected void writeReply( BuddyPluginBuddyMessage message, Map content ) throws BuddyPluginException { writeContent( message.getID() + ".rep.dat", content ); } protected Map readReply( BuddyPluginBuddyMessage message ) throws BuddyPluginException { return( readContent( message.getID() + ".rep.dat" )); } protected void writeContent( String target_str, Map content ) throws BuddyPluginException { if ( !store.exists()){ if ( !store.mkdirs()){ throw( new BuddyPluginException( "Failed to create " + store )); } } File target = new File( store, target_str ); try{ BuddyPlugin.cryptoResult result = buddy.encrypt( BEncoder.encode( content )); Map store_map = new HashMap(); store_map.put( "pk", buddy.getPlugin().getPublicKey()); store_map.put( "data", result.getPayload()); if ( !buddy.writeConfigFile( target, store_map )){ throw( new BuddyPluginException( "failed to write " + target )); } }catch( BuddyPluginException e ){ throw( e ); }catch( Throwable e ){ throw( new BuddyPluginException( "Failed to write message", e )); } } protected Map readContent( String target_str ) throws BuddyPluginException { File target = new File( store, target_str ); if ( !target.exists()){ throw( new BuddyPluginException( "Failed to read persisted message - " + target + " doesn't exist" )); } Map map = buddy.readConfigFile( target ); if ( map.size() == 0 ){ throw( new BuddyPluginException( "Failed to read persisted message file " + target )); } try{ String pk = new String((byte[])map.get("pk")); if ( !pk.equals( buddy.getPlugin().getPublicKey())){ throw( new BuddyPluginException( "Can't decrypt message as key changed" )); } byte[] data = (byte[])map.get( "data" ); return( BDecoder.decode( buddy.decrypt( data ).getPayload())); }catch( BuddyPluginException e ){ throw( e ); }catch( Throwable e ){ throw( new BuddyPluginException( "Failed to read message", e )); } } protected void deleteRequest( BuddyPluginBuddyMessage message ) { deleteRequest( message.getID()); } protected void deleteRequest( int id ) { File target = new File( store, id + ".req.dat" ); if ( target.exists()){ if ( !target.delete()){ Debug.out( "Failed to delete " + target ); } } } protected void deleteReply( BuddyPluginBuddyMessage message ) { deleteReply( message.getID()); } protected void deleteReply( int id ) { File target = new File( store, id + ".rep.dat" ); if ( target.exists()){ if ( !target.delete()){ Debug.out( "Failed to delete " + target ); } } } public BuddyPluginBuddyMessage storeExplicitMessage( int type, Map msg ) { BuddyPluginBuddyMessage message; synchronized( this ){ int id = next_message_id++; try{ message = new BuddyPluginBuddyMessage( this, id, BuddyPlugin.SUBSYSTEM_MSG_TYPE_BASE + type, msg, 0, SystemTime.getCurrentTime()); storeExplicitMessage( message ); }catch( Throwable e ){ buddy.log( "Failed to store explicit message", e ); return( null ); } } return( message ); } public List<BuddyPluginBuddyMessage> retrieveExplicitMessages( int type ) { List<BuddyPluginBuddyMessage> result = new ArrayList<BuddyPluginBuddyMessage>(); synchronized( this ){ List<Map<String,Object>> messages = (List<Map<String,Object>>)config_map.get( "explicit" ); if ( messages != null ){ for (int i=0;i<messages.size();i++){ try{ BuddyPluginBuddyMessage msg = restoreMessage(messages.get(i)); if ( msg.getSubsystem() == BuddyPlugin.SUBSYSTEM_MSG_TYPE_BASE + type ){ result.add( msg ); } }catch( Throwable e ){ buddy.log( "Failed to restore message", e ); } } } } return( result ); } protected void storeExplicitMessage( BuddyPluginBuddyMessage msg ) throws BuddyPluginException { storeMessageSupport( msg, "explicit" ); } protected void storeMessage( BuddyPluginBuddyMessage msg ) throws BuddyPluginException { storeMessageSupport( msg, "messages" ); } protected void storeMessageSupport( BuddyPluginBuddyMessage msg, String key ) throws BuddyPluginException { List messages = (List)config_map.get( key ); if ( messages == null ){ messages = new ArrayList(); config_map.put( key, messages ); } Map map = new HashMap(); map.put( "id", new Long( msg.getID())); map.put( "ss", new Long( msg.getSubsystem())); map.put( "to", new Long( msg.getTimeout())); map.put( "cr", new Long( msg.getCreateTime())); messages.add( map ); saveConfig(); } protected BuddyPluginBuddyMessage restoreMessage( Map map ) throws BuddyPluginException { int id = ((Long)map.get( "id" )).intValue(); int ss = ((Long)map.get( "ss" )).intValue(); int to = ((Long)map.get( "to" )).intValue(); long cr = ((Long)map.get( "cr" )).longValue(); return( new BuddyPluginBuddyMessage( this, id, ss, null, to, cr )); } protected void loadConfig() { File config_file = new File( store, "messages.dat" ); if ( config_file.exists()){ config_map = buddy.readConfigFile( config_file ); }else{ config_map = new HashMap(); } List messages = (List)config_map.get( "messages" ); if ( messages != null ){ message_count = messages.size(); if ( message_count > 0 ){ Map last_msg = (Map)messages.get( message_count - 1 ); next_message_id = ((Long)last_msg.get( "id")).intValue() + 1; } } List pending_success = (List)config_map.get( "pending_success" ); if ( pending_success != null ){ int ps_count = pending_success.size(); if ( ps_count > 0 ){ Map last_msg = (Map)pending_success.get( ps_count - 1 ); next_message_id = Math.max( next_message_id, ((Long)last_msg.get( "id")).intValue() + 1 ); synchronized( this ){ last_pending_success = SystemTime.getCurrentTime(); } } } List explicit = (List)config_map.get( "explicit" ); if ( explicit != null ){ int exp_count = explicit.size(); if ( exp_count > 0 ){ Map last_msg = (Map)explicit.get( exp_count - 1 ); next_message_id = Math.max( next_message_id, ((Long)last_msg.get( "id")).intValue() + 1 ); } } } protected void saveConfig() throws BuddyPluginException { File config_file = new File( store, "messages.dat" ); List messages = (List)config_map.get( "messages" ); List pending = (List)config_map.get( "pending_success" ); List explicit = (List)config_map.get( "explicit" ); if ( ( messages == null || messages.size() == 0 ) && ( pending == null || pending.size() == 0 ) && ( explicit == null || explicit.size() == 0 )){ if ( store.exists()){ File[] files = store.listFiles(); for (int i=0;i<files.length;i++ ){ files[i].delete(); } store.delete(); } message_count = 0; next_message_id = 0; }else{ if ( !store.exists()){ if ( !store.mkdirs()){ throw( new BuddyPluginException( "Failed to create " + store )); } } if ( !buddy.writeConfigFile( config_file, config_map )){ throw( new BuddyPluginException( "Failed to write" + config_file )); } message_count = messages==null?0:messages.size(); } } public void addListener( BuddyPluginBuddyMessageListener listener ) { listeners.add( listener ); } public void removeListener( BuddyPluginBuddyMessageListener listener ) { listeners.remove( listener ); } }