All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jadex.platform.service.chat.ChatService Maven / Gradle / Ivy

package jadex.platform.service.chat;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import jadex.base.Starter;
import jadex.bridge.IComponentIdentifier;
import jadex.bridge.IConnection;
import jadex.bridge.IInputConnection;
import jadex.bridge.IInternalAccess;
import jadex.bridge.IOutputConnection;
import jadex.bridge.SFuture;
import jadex.bridge.ServiceCall;
import jadex.bridge.TimeoutResultListener;
import jadex.bridge.component.IArgumentsResultsFeature;
import jadex.bridge.service.RequiredServiceInfo;
import jadex.bridge.service.annotation.Reference;
import jadex.bridge.service.annotation.Service;
import jadex.bridge.service.annotation.ServiceComponent;
import jadex.bridge.service.annotation.ServiceShutdown;
import jadex.bridge.service.annotation.ServiceStart;
import jadex.bridge.service.component.IRequiredServicesFeature;
import jadex.bridge.service.types.chat.ChatEvent;
import jadex.bridge.service.types.chat.IChatGuiService;
import jadex.bridge.service.types.chat.IChatService;
import jadex.bridge.service.types.chat.TransferInfo;
import jadex.bridge.service.types.remote.ServiceInputConnection;
import jadex.bridge.service.types.settings.ISettingsService;
import jadex.commons.Base64;
import jadex.commons.IPropertiesProvider;
import jadex.commons.Properties;
import jadex.commons.Property;
import jadex.commons.SUtil;
import jadex.commons.Tuple3;
import jadex.commons.future.CollectionResultListener;
import jadex.commons.future.DelegationResultListener;
import jadex.commons.future.ExceptionDelegationResultListener;
import jadex.commons.future.Future;
import jadex.commons.future.FutureTerminatedException;
import jadex.commons.future.IFuture;
import jadex.commons.future.IIntermediateFuture;
import jadex.commons.future.IIntermediateResultListener;
import jadex.commons.future.IResultListener;
import jadex.commons.future.ISubscriptionIntermediateFuture;
import jadex.commons.future.ITerminableFuture;
import jadex.commons.future.ITerminableIntermediateFuture;
import jadex.commons.future.IntermediateDefaultResultListener;
import jadex.commons.future.IntermediateDelegationResultListener;
import jadex.commons.future.IntermediateFuture;
import jadex.commons.future.SubscriptionIntermediateFuture;
import jadex.commons.future.TerminableFuture;
import jadex.commons.future.TerminableIntermediateFuture;
import jadex.commons.future.TerminationCommand;


/**
 *  Chat service implementation.
 */
@Service
public class ChatService implements IChatService, IChatGuiService
{
	//-------- attributes --------
	
	/** The agent. */
	@ServiceComponent
	protected IInternalAccess agent;
	
	/** The service identifier. */
//	@ServiceIdentifier
//	protected IServiceIdentifier sid;
	
	/** The futures of active subscribers. */
	protected Set> subscribers;
	
	/** The local nick name. */
	protected String nick;
	
	/** The current status (idle, typing, away). */
	protected String status;
	
	/** The currently managed file transfers. */
	protected Map, IInputConnection>>	transfers;
	protected Map, IConnection>>	transfers2;
	
	/** Flag to avoid duplicate initialization/shutdown due to duplicate use of implementation. */
	protected boolean	running;
	
	/** The image. */
	protected byte[] image;
	
	//-------- initialization methods --------
	
	/**
	 *  Called on startup.
	 */
	@ServiceStart
	public IFuture start()
	{
		final Future ret = new Future();
		
		if(!running)
		{
			running	= true;
			status	= STATE_AWAY;	// Changes to idle only when a gui is connected.
			
			final PropProvider pp = new PropProvider();
			agent.getComponentFeature(IRequiredServicesFeature.class).searchService(ISettingsService.class, RequiredServiceInfo.SCOPE_PLATFORM)
				.addResultListener(new IResultListener()
			{
				public void resultAvailable(ISettingsService settings)
				{
					if(!(agent.getComponentFeature(IArgumentsResultsFeature.class).getArguments().get("nosave") instanceof Boolean)
						|| !((Boolean)agent.getComponentFeature(IArgumentsResultsFeature.class).getArguments().get("nosave")).booleanValue())
					{
						settings.registerPropertiesProvider(getSubname(), pp)
							.addResultListener(new DelegationResultListener(ret)
						{
							public void customResultAvailable(Void result)
							{
		//							pp.isCalled().addResultListener(new DelegationResultListener(ret)
		//							{
		//								public void customResultAvailable(Void result)
		//								{
										proceed();
		//								}
		//							});
							}
						});
					}
					else
					{
						proceed();
					}
				}
				
				public void exceptionOccurred(Exception exception)
				{
					// No settings service: ignore.
					proceed();
				}
				
				public void proceed()
				{
					if(nick==null)
						nick	= SUtil.createUniqueId("user", 3);
					transfers	= new LinkedHashMap, IInputConnection>>();
					transfers2	= new LinkedHashMap, IConnection>>();
					
					// Search and post status in background for not delaying platform startup.
					IIntermediateFuture	chatfut	= agent.getComponentFeature(IRequiredServicesFeature.class).getRequiredServices("chatservices");
					chatfut.addResultListener(new IntermediateDefaultResultListener()
					{
						public void intermediateResultAvailable(IChatService chat)
						{
							chat.status(nick, STATE_IDLE, null);
						}
						public void finished()
						{
							// ignore...
						}
						public void exceptionOccurred(Exception exception)
						{
							// ignore...
						}
					});
					
					ret.setResult(null);
				}
			});
		}
		else
		{
			ret.setResult(null);
		}
		
		return ret;
	}
	
	/**
	 *  Called on shutdown.
	 */
	@ServiceShutdown
	public IFuture shutdown()
	{
		if(!running)
		{
			return IFuture.DONE;
		}
		else
		{
			running	= false;
			final Future	ret	= new Future();
			
			if(subscribers!=null)
			{
				for(SubscriptionIntermediateFuture fut: subscribers)
				{
					fut.terminate();
				}
			}
			
			final Future done	= new Future();
			IIntermediateFuture	chatfut	= agent.getComponentFeature(IRequiredServicesFeature.class).getRequiredServices("chatservices");
			chatfut.addResultListener(new IntermediateDefaultResultListener()
			{
				public void intermediateResultAvailable(IChatService chat)
				{
					chat.status(nick, STATE_DEAD, null);
				}
				public void finished()
				{
					done.setResult(null);
				}
				public void exceptionOccurred(Exception exception)
				{
					done.setResult(null);
				}
			});
			
			agent.getComponentFeature(IRequiredServicesFeature.class).searchService(ISettingsService.class, RequiredServiceInfo.SCOPE_PLATFORM)
				.addResultListener(new IResultListener()
			{
				public void resultAvailable(ISettingsService settings)
				{
					if(!(agent.getComponentFeature(IArgumentsResultsFeature.class).getArguments().get("nosave") instanceof Boolean)
						|| !((Boolean)agent.getComponentFeature(IArgumentsResultsFeature.class).getArguments().get("nosave")).booleanValue())
					{
						settings.deregisterPropertiesProvider(getSubname())
							.addResultListener(new DelegationResultListener(ret)
						{
							public void customResultAvailable(Void result)
							{
								proceed();
							}
						});
					}
					else
					{
						proceed();
					}
				}
				
				public void exceptionOccurred(Exception exception)
				{
					// No settings service: ignore.
					proceed();
				}
				
				public void proceed()
				{
					
					// Only wait 2 secs for sending status before terminating the agent.
					done.addResultListener(new TimeoutResultListener(2000, agent.getExternalAccess(),
						new DelegationResultListener(ret)
					{
						public void exceptionOccurred(Exception exception)
						{
							super.resultAvailable(null);
						}
					}));
				}
			});
			
			return ret;
		}
	}

	/**
	 *  Get the "semi-qualified" sub name for settings.
	 */
	protected String	getSubname()
	{
		String	subname	= null;
		IComponentIdentifier	cid	= agent.getComponentIdentifier();
		while(cid.getParent()!=null)
		{
			subname	= subname==null ? cid.getLocalName() : subname+"."+cid.getLocalName();
			cid	= cid.getParent();
		}
		return subname;
	}
	
	//-------- IChatService interface --------
	
	/**
	 *  Post a message
	 *  @param text The text message.
	 */
	public IFuture	message(String nick, String text, boolean privatemessage)
	{
//		System.out.println("Timeout: "+ServiceCall.getInstance().getTimeout()+", "+ServiceCall.getInstance().isRealtime());
		boolean	published	= publishEvent(ChatEvent.TYPE_MESSAGE, nick, ServiceCall.getCurrentInvocation().getCaller(), text, privatemessage, null);
		return published ? IFuture.DONE : new Future(new RuntimeException("No GUI, message was discarded."));
	}

	/**
	 *  Post a status change.
	 *  @param status The new status.
	 */
	public IFuture	status(String nick, String status, byte[] image)
	{
		publishEvent(ChatEvent.TYPE_STATECHANGE, nick, ServiceCall.getCurrentInvocation().getCaller(), status, false, image);
		return IFuture.DONE;
	}
	
	/**
	 *  Get the current status.
	 */
	public IFuture	getStatus()
	{
		return new Future(status);
	}
	
	/**
	 *  Send a file.
	 *  
	 *  @param nick The sender's nick name.
	 *  @param filename The filename.
	 *  @param size The size of the file.
	 *  @param id An optional id to identify the transfer (e.g. for resume after error).
	 *  @param con The connection.
	 *  
	 *  @return The returned future publishes updates about the total number of bytes received.
	 *    Exception messages of the returned future correspond to file transfer states (aborted vs. error vs. rejected).
	 */
	public ITerminableIntermediateFuture sendFile(final String nick, String filename, long size, String id, final IInputConnection con)
	{
		final ServiceCall call = ServiceCall.getCurrentInvocation();
		
		// Hack!!! always assume real time for chat interaction.
		final TransferInfo	ti	= new TransferInfo(true, id, filename, null, call.getCaller(), size, System.currentTimeMillis() + call.getTimeout());
		ti.setState(TransferInfo.STATE_WAITING);
		
		final TerminableIntermediateFuture ret = new TerminableIntermediateFuture(new TerminationCommand()
		{
			public void terminated(Exception reason)
			{
				ti.setState(TransferInfo.STATE_ABORTED);
				publishEvent(ChatEvent.TYPE_FILE, nick, call.getCaller(), ti);
				transfers2.remove(ti.getId());
			}
		});
		
		transfers.put(ti.getId(), new Tuple3, IInputConnection>(ti, ret, con));
		publishEvent(ChatEvent.TYPE_FILE, nick, call.getCaller(), ti);
		
		return ret;
	}
	
	
	/**
	 *  Send a file. Alternative method signature.
	 *  
	 *  @param nick The sender's nick name.
	 *  @param filename The filename.
	 *  @param size The size of the file.
	 *  @param id An optional id to identify the transfer (e.g. for resume after error).
	 *  
	 *  @return When the upload is accepted, the output connection for sending the file is returned.
	 */
	public ITerminableFuture startUpload(final String nick, String filename, long size, String id)
	{
		final ServiceCall call = ServiceCall.getCurrentInvocation();
		
		final TransferInfo	ti	= new TransferInfo(true, id, filename, null, call.getCaller(), size, System.currentTimeMillis() + call.getTimeout());
		ti.setState(TransferInfo.STATE_WAITING);
		
		TerminableFuture ret = new TerminableFuture(new TerminationCommand()
		{
			public void terminated(Exception reason)
			{
				ti.setState(TransferInfo.STATE_ABORTED);
				publishEvent(ChatEvent.TYPE_FILE, nick, call.getCaller(), ti);
				transfers2.remove(ti.getId());
			}
		});
		
		transfers2.put(ti.getId(), new Tuple3, IConnection>(ti, ret, null));
		
		publishEvent(ChatEvent.TYPE_FILE, nick, call.getCaller(), ti);

		return ret;
	}


	//-------- IChatGuiService interface --------
	
	/**
	 *  Set the user name.
	 */
	public IFuture	setNickName(String nick)
	{
		this.nick	= nick;
		// Publish new nickname
		status(null, null, new IComponentIdentifier[0]);
		return IFuture.DONE;
	}
	
	/**
	 *  Get the user name.
	 */
	public IFuture	getNickName()
	{
		return new Future(nick);
	}
	
	/**
	 *  Set the image.
	 */
	public IFuture	setImage(byte[] image)
	{
		this.image = image;
		// Publish new image
		status(null, image, new IComponentIdentifier[0]);
		return IFuture.DONE;
	}
	
	/**
	 *  Get the image.
	 */
	public IFuture	getImage()
	{
		return new Future(image);
	}

	/**
	 *  Subscribe to events from the chat service.
	 *  @return A future publishing chat events as intermediate results.
	 */
	public ISubscriptionIntermediateFuture	subscribeToEvents()
	{
//		final SubscriptionIntermediateFuture	ret	= new SubscriptionIntermediateFuture();
		final SubscriptionIntermediateFuture	ret	= (SubscriptionIntermediateFuture)SFuture.getNoTimeoutFuture(SubscriptionIntermediateFuture.class, agent);

		if(subscribers==null)
		{
			subscribers	= new LinkedHashSet>();
		}
		subscribers.add(ret);
		ret.setTerminationCommand(new TerminationCommand()
		{
			public void terminated(Exception reason)
			{
				subscribers.remove(ret);
			}
		});
		
		return ret;		
	}
	
	/**
	 *  Search for available chat services.
	 *  @return The currently available remote services.
	 */
	public IIntermediateFuture findUsers()
	{
		IIntermediateFuture ret	= agent.getComponentFeature(IRequiredServicesFeature.class).getRequiredServices("chatservices");
//		ret.addResultListener(new DefaultResultListener>()
//		{
//			public void resultAvailable(Collection result)
//			{
//				System.out.println("Found chat users: "+result);
//			}
//		});
		return ret;
	}
	
	/**
	 *  Post a message.
	 *  Searches for available chat services and posts the message to all.
	 *  @param text The text message.
	 *  @return The remote services, to which the message was successfully posted.
	 */
	public IIntermediateFuture message(final String text, final IComponentIdentifier[] receivers, boolean self)
	{
		final IntermediateFuture	ret = (IntermediateFuture)SFuture.getNoTimeoutFuture(IntermediateFuture.class, agent);
//		final IntermediateFuture	ret	= new IntermediateFuture();
		
		if(receivers!=null && receivers.length>0)
		{
			boolean foundself = false;
			
			if(self)
			{
				for(int i=0; i lis = new CollectionResultListener(
				cnt, true, new IResultListener>()
			{
				public void resultAvailable(Collection result) 
				{
					ret.setFinished();
				}

				public void exceptionOccurred(Exception exception)
				{
					ret.setFinished();
				}
			});
			
			for(int i=0; i()
				{
					public void resultAvailable(IChatService result)
					{
						ret.addIntermediateResultIfUndone(result); // Might return after later exception in service search!?
						lis.resultAvailable(result);
					}
					
					public void exceptionOccurred(Exception exception)
					{
						lis.exceptionOccurred(exception);
					}
				});
			}
			
			if(self && !foundself)
			{
				sendTo(text, agent.getComponentIdentifier(), true).addResultListener(new IResultListener()
				{
					public void resultAvailable(IChatService result)
					{
						ret.addIntermediateResultIfUndone(result); // Might return after later exception in service search!?
						lis.resultAvailable(result);
					}
					
					public void exceptionOccurred(Exception exception)
					{
						lis.exceptionOccurred(exception);
					}
				});
			}
		}
		else //if(receivers.length==0)
		{
			final IIntermediateFuture ifut = agent.getComponentFeature(IRequiredServicesFeature.class).getRequiredServices("chatservices");
			
			ifut.addResultListener(new IntermediateDelegationResultListener(ret)
			{
				boolean	finished;
				int cnt;
				public void customIntermediateResultAvailable(final IChatService chat)
				{
					cnt++;
					chat.message(nick, text, false).addResultListener(new IResultListener()
					{
						public void resultAvailable(Void result)
						{
							ret.addIntermediateResultIfUndone(chat);	// Might be called after concurrent exception in service search!
							
							if(--cnt==0 && finished)
							{
								ret.setFinished();
							}
						}
						
						public void exceptionOccurred(Exception exception)
						{
							if(--cnt==0 && finished)
							{
								ret.setFinished();
							}
						}
					});
				}
				
				public void finished()
				{
					finished	= true;
					if(finished && cnt==0)
					{
						ret.setFinished();
					}
				}
			});
		}
		
		return ret;
	}
	
	/**
	 *  Helper method for sending message to cid.
	 */
	protected IFuture sendTo(final String text, IComponentIdentifier rec, final boolean privatemessage)
	{
		final Future ret = new Future();
		
		agent.getComponentFeature(IRequiredServicesFeature.class).searchService(IChatService.class, rec)
			.addResultListener(new DelegationResultListener(ret)
		{
			public void customResultAvailable(final IChatService chat)
			{
//				ret.setResult(chat);
				chat.message(nick, text, privatemessage).addResultListener(new ExceptionDelegationResultListener(ret)
				{
					public void customResultAvailable(Void result)
					{
						ret.setResult(chat);
					}
				});
			}
		});
		
		return ret;
	}
	
	/**
	 *  Post a status change.
	 *  @param status The new status or null for no change.
	 *  @param image The new avatar image or null for no change.
	 */
	public IIntermediateFuture status(final String status, final byte[] image, IComponentIdentifier[] receivers)
	{
		final IntermediateFuture	ret	= new IntermediateFuture();
		if(status!=null)
		{
			this.status	= status;
		}
		
		if(receivers!=null && receivers.length>0)
		{
			boolean foundself = false;
			
			for(int i=0; i lis = new CollectionResultListener(
				cnt, true, new IResultListener>()
			{
				public void resultAvailable(Collection result) 
				{
					ret.setFinished();
				}

				public void exceptionOccurred(Exception exception)
				{
					ret.setFinished();
				}
			});
			
			for(int i=0; i()
				{
					public void resultAvailable(IChatService result)
					{
						ret.addIntermediateResultIfUndone(result); // Might return after later exception in service search!?
						lis.resultAvailable(result);
					}
					
					public void exceptionOccurred(Exception exception)
					{
						lis.exceptionOccurred(exception);
					}
				});
			}
			
			if(!foundself)
			{
				statusTo(nick, status, image, agent.getComponentIdentifier()).addResultListener(new IResultListener()
				{
					public void resultAvailable(IChatService result)
					{
						ret.addIntermediateResultIfUndone(result); // Might return after later exception in service search!?
						lis.resultAvailable(result);
					}
					
					public void exceptionOccurred(Exception exception)
					{
						lis.exceptionOccurred(exception);
					}
				});
			}
		}
		else //if(receivers.length==0)
		{
			final IIntermediateFuture ifut = agent.getComponentFeature(IRequiredServicesFeature.class).getRequiredServices("chatservices");
			ifut.addResultListener(new IntermediateDelegationResultListener(ret)
			{
				boolean	finished;
				int cnt	= 0;
				public void customIntermediateResultAvailable(final IChatService chat)
				{
					cnt++;
					chat.status(nick, status, image).addResultListener(new IResultListener()
					{
						public void resultAvailable(Void result)
						{
							ret.addIntermediateResultIfUndone(chat);	// Might be called after concurrent exception in service search!
							
							if(--cnt==0 && finished)
							{
								ret.setFinishedIfUndone();
							}
						}
						
						public void exceptionOccurred(Exception exception)
						{
							if(--cnt==0 && finished)
							{
								ret.setFinishedIfUndone();
							}
						}
					});
				}
				public void finished()
				{
					finished	= true;
					if(finished && cnt==0)
					{
						ret.setFinishedIfUndone();
					}
				}
			});
		}
		
		return ret;
	}
	
	/**
	 *  Helper method for posting status to cid.
	 */
	protected IFuture statusTo(final String nick, final String status, final byte[] image, IComponentIdentifier rec)
	{
		final Future ret = new Future();
		
		agent.getComponentFeature(IRequiredServicesFeature.class).searchService(IChatService.class, rec)
			.addResultListener(new DelegationResultListener(ret)
		{
			public void customResultAvailable(final IChatService chat)
			{
//				ret.setResult(chat);
				chat.status(nick, status, image).addResultListener(new ExceptionDelegationResultListener(ret)
				{
					public void customResultAvailable(Void result)
					{
						ret.setResult(chat);
					}
				});
			}
		});
		
		return ret;
	}
	

	
	/**
	 *  Get a snapshot of the currently managed file transfers.
	 */
	public IIntermediateFuture	getFileTransfers()
	{
		List	ret	= new ArrayList();
		for(Tuple3, IInputConnection> tup: transfers.values())
		{
			ret.add(tup.getFirstEntity());
		}
		for(Tuple3, IConnection> tup: transfers2.values())
		{
			ret.add(tup.getFirstEntity());
		}
		return new IntermediateFuture(ret);
	}

	
	/**
	 *  Accept a waiting file transfer.
	 *  @param id	The transfer id. 
	 *  @param filepath	The location of the file (possibly changed by user). 
	 */
	public IFuture	acceptFile(String id, String filepath)
	{
		IFuture	ret;
		Tuple3, IInputConnection>	tup	= transfers.get(id);
		Tuple3, IConnection>	tup2	= transfers2.get(id);
		if(tup!=null)
		{
			TransferInfo	ti	= tup.getFirstEntity();
			if(TransferInfo.STATE_WAITING.equals(ti.getState()))
			{
				ti.setFileName(new File(filepath).getName());
				ti.setFilePath(filepath);
				doDownload(ti, tup.getSecondEntity(), tup.getThirdEntity());
				ret	= IFuture.DONE;
			}
			else if(TransferInfo.STATE_REJECTED.equals(ti.getState()))
			{
				ret	= new Future(new RuntimeException("Transfer already rejected."));				
			}
			else
			{
				// Already accepted -> ignore.
				ret	= IFuture.DONE;
			}
		}
		else if(tup2!=null)
		{
			TransferInfo	ti	= tup2.getFirstEntity();
			if(TransferInfo.STATE_WAITING.equals(ti.getState()))
			{
				ServiceInputConnection	sic	= new ServiceInputConnection();
				((Future)tup2.getSecondEntity()).setResultIfUndone(sic.getOutputConnection());
				ti.setFileName(new File(filepath).getName());
				ti.setFilePath(filepath);
				doDownload(ti, null, sic);
				ret	= IFuture.DONE;
			}
			else if(TransferInfo.STATE_REJECTED.equals(ti.getState()))
			{
				ret	= new Future(new RuntimeException("Transfer already rejected."));				
			}
			else
			{
				// Already accepted -> ignore.
				ret	= IFuture.DONE;
			}
		}
		else
		{
			ret	= new Future(new RuntimeException("No such file transfer."));
		}
		
		return ret;
	}
	
	/**
	 *  Reject a waiting file transfer.
	 *  @param id	The transfer id. 
	 */
	public IFuture	rejectFile(String id)
	{
		IFuture	ret;
		Tuple3, IInputConnection>	tup	= transfers.get(id);
		Tuple3, IConnection>	tup2	= transfers2.get(id);
		if(tup!=null)
		{
			TransferInfo	ti	= tup.getFirstEntity();
			if(TransferInfo.STATE_WAITING.equals(ti.getState()))
			{
				ti.setState(TransferInfo.STATE_REJECTED);
				publishEvent(ChatEvent.TYPE_FILE, null, ti.getOther(), ti);
				tup.getSecondEntity().setException(new RuntimeException(TransferInfo.STATE_REJECTED));
				transfers.remove(id);
				ret	= IFuture.DONE;
			}
			else if(TransferInfo.STATE_REJECTED.equals(ti.getState()))
			{
				// Already rejected -> ignore.
				ret	= IFuture.DONE;
			}
			else
			{
				ret	= new Future(new RuntimeException("Transfer already accepted."));				
			}
		}
		else if(tup2!=null)
		{
			TransferInfo	ti	= tup2.getFirstEntity();
			if(TransferInfo.STATE_WAITING.equals(ti.getState()))
			{
				ti.setState(TransferInfo.STATE_REJECTED);
				publishEvent(ChatEvent.TYPE_FILE, null, ti.getOther(), ti);
				((Future)tup2.getSecondEntity()).setException(new RuntimeException(TransferInfo.STATE_REJECTED));
				transfers2.remove(id);
				ret	= IFuture.DONE;
			}
			else if(TransferInfo.STATE_REJECTED.equals(ti.getState()))
			{
				// Already rejected -> ignore.
				ret	= IFuture.DONE;
			}
			else
			{
				ret	= new Future(new RuntimeException("Transfer already accepted."));				
			}
		}
		else
		{
			ret	= new Future(new RuntimeException("No such file transfer."));
		}
		
		return ret;	
	}
	
	/**
	 *  Cancel an ongoing file transfer.
	 *  @param id	The transfer id. 
	 */
	public IFuture	cancelTransfer(String id)
	{
		IFuture	ret;
		Tuple3, IInputConnection>	tup	= transfers.get(id);
		Tuple3, IConnection>	tup2	= transfers2.get(id);
		if(tup!=null)
		{
			TransferInfo	ti	= tup.getFirstEntity();
			if(TransferInfo.STATE_TRANSFERRING.equals(ti.getState()) || TransferInfo.STATE_WAITING.equals(ti.getState()))
			{
				ti.setState(TransferInfo.STATE_CANCELLING);
				publishEvent(ChatEvent.TYPE_FILE, null, null, ti);
				tup.getSecondEntity().terminate();
				ret	= IFuture.DONE;
			}
			else if(TransferInfo.STATE_COMPLETED.equals(ti.getState()))
			{
				ret	= new Future(new RuntimeException("Transfer already completed."));				
			}
			else
			{
				// Already aborted -> ignore.
				ret	= IFuture.DONE;
			}
		}
		else if(tup2!=null)
		{
			TransferInfo	ti	= tup2.getFirstEntity();
			if(TransferInfo.STATE_WAITING.equals(ti.getState()))
			{
				ti.setState(TransferInfo.STATE_CANCELLING);
				publishEvent(ChatEvent.TYPE_FILE, null, null, ti);
				tup2.getSecondEntity().terminate();
				ret	= IFuture.DONE;
			}
			else if(TransferInfo.STATE_TRANSFERRING.equals(ti.getState()))
			{
				ti.setState(TransferInfo.STATE_CANCELLING);
				publishEvent(ChatEvent.TYPE_FILE, null, null, ti);
				tup2.getThirdEntity().close();
				ret	= IFuture.DONE;
			}
			else if(TransferInfo.STATE_COMPLETED.equals(ti.getState()))
			{
				ret	= new Future(new RuntimeException("Transfer already completed."));				
			}
			else
			{
				// Already aborted -> ignore.
				ret	= IFuture.DONE;
			}
		}
		else
		{
			ret	= new Future(new RuntimeException("No such file transfer."));
		}
		
		return ret;
	}

	/**
	 *  Send a local file to the target component.
	 *  @param filepath	The file path, local to the chat component.
	 *  @param cid	The id of a remote chat component.
	 */
	public IFuture	sendFile(final String filepath, final IComponentIdentifier cid)
	{
		final Future ret = new Future();

		IFuture fut = agent.getComponentFeature(IRequiredServicesFeature.class).searchService(IChatService.class, cid);
		fut.addResultListener(new ExceptionDelegationResultListener(ret)
		{
			public void customResultAvailable(IChatService cs)
			{
				final File file = new File(filepath);
				final long size = file.length();
				
//				// Call chat service of receiver 
//				final ServiceOutputConnection ocon = new ServiceOutputConnection();
//				final IInputConnection icon = ocon.getInputConnection();
//				ITerminableIntermediateFuture fut = cs.sendFile(nick, file.getName(), size, fi.getId(), icon);
//
//				// Receives notifications how many bytes were received. 
//				fut.addResultListener(new IntermediateExceptionDelegationResultListener(ret)
//				{
//					boolean	started;
//					
//					public void intermediateResultAvailable(Long result)
//					{
////						System.out.println("rec: "+result);
//						// Start sending after first intermediate result was received
//						if(!started)
//						{
//							started = true;
//							ret.setResult(null);
//							doUpload(fi, ocon, cid);
//						}
//					}
//					
//					public void finished()
//					{
//						fi.setState(TransferInfo.STATE_COMPLETED);
//						publishEvent(ChatEvent.TYPE_FILE, null, cid, fi);
//					}
//					
//					public void customResultAvailable(Collection result)
//					{
//						fi.setState(TransferInfo.STATE_COMPLETED);
//						publishEvent(ChatEvent.TYPE_FILE, null, cid, fi);
//					}
//
//					public void exceptionOccurred(Exception exception)
//					{
//						if(exception instanceof FutureTerminatedException || TransferInfo.STATE_ABORTED.equals(exception.getMessage()))
//						{
//							fi.setState(TransferInfo.STATE_ABORTED);
//						}
//						else if(TransferInfo.STATE_REJECTED.equals(exception.getMessage()))
//						{	
//							fi.setState(TransferInfo.STATE_REJECTED);
//						}
//						else
//						{
//							fi.setState(TransferInfo.STATE_ERROR);
//						}
//						publishEvent(ChatEvent.TYPE_FILE, null, cid, fi);
//					}
//				});
				
				// Call chat service of receiver (alternative interface)
				final TransferInfo fi = new TransferInfo(false, null, file.getName(), filepath, cid, file.length(), System.currentTimeMillis() + // Hack!!! assume real time timeout.
					(cid.getRoot().equals(agent.getComponentIdentifier().getRoot()) ? Starter.getLocalDefaultTimeout(agent.getComponentIdentifier()) : Starter.getRemoteDefaultTimeout(agent.getComponentIdentifier())));	// Todo: actual timeout of method!?
				fi.setState(TransferInfo.STATE_WAITING);
				ITerminableFuture fut = cs.startUpload(nick, file.getName(), size, fi.getId());
				transfers2.put(fi.getId(), new Tuple3, IConnection>(fi, fut, null));
				publishEvent(ChatEvent.TYPE_FILE, null, cid, fi);
				fut.addResultListener(new ExceptionDelegationResultListener(ret)
				{
					public void customResultAvailable(IOutputConnection ocon)
					{
						try
						{
							FileInputStream fis = new FileInputStream(new File(fi.getFilePath()));
							doUpload(fi, fis, ocon, cid);
							ret.setResult(null);
						}
						catch(Exception e)
						{
							ret.setException(e);
						}
					}
					
					public void exceptionOccurred(Exception exception)
					{
						if(exception instanceof FutureTerminatedException || TransferInfo.STATE_ABORTED.equals(exception.getMessage())
							|| TransferInfo.STATE_CANCELLING.equals(fi.getState()))
						{
							fi.setState(TransferInfo.STATE_ABORTED);
						}
						else if(TransferInfo.STATE_REJECTED.equals(exception.getMessage()))
						{	
							fi.setState(TransferInfo.STATE_REJECTED);
						}
						else
						{
							fi.setState(TransferInfo.STATE_ERROR);
						}
						transfers2.remove(fi.getId());
						publishEvent(ChatEvent.TYPE_FILE, null, cid, fi);
					}
				});

			}					
		});
					
		return ret;
	}
	
	/**
	 *  Send a file to the target component via bytes.
	 *  @param filepath	The file path, local to the chat component.
	 *  @param cid	The id of a remote chat component.
	 */
	public IFuture sendFile(final String fname, final byte[] data, final IComponentIdentifier cid)
	{
		final Future ret = new Future();

		IFuture fut = agent.getComponentFeature(IRequiredServicesFeature.class).searchService(IChatService.class, cid);
		fut.addResultListener(new ExceptionDelegationResultListener(ret)
		{
			public void customResultAvailable(IChatService cs)
			{
				final long size = data.length;
				
				// Call chat service of receiver (alternative interface)
				String filepath = fname.indexOf(".")!=-1? fname.substring(fname.lastIndexOf(".")): null;
				String name = fname.indexOf(".")!=-1? fname.substring(0, fname.lastIndexOf(".")-1): fname;
				final TransferInfo fi = new TransferInfo(false, null, name, filepath, cid, size, System.currentTimeMillis() + // Hack!!! assume real time timeout.
					(cid.getRoot().equals(agent.getComponentIdentifier().getRoot()) ? Starter.getLocalDefaultTimeout(agent.getComponentIdentifier()) : Starter.getRemoteDefaultTimeout(agent.getComponentIdentifier())));	// Todo: actual timeout of method!?
				fi.setState(TransferInfo.STATE_WAITING);
				ITerminableFuture fut = cs.startUpload(nick, name, size, fi.getId());
				transfers2.put(fi.getId(), new Tuple3, IConnection>(fi, fut, null));
				publishEvent(ChatEvent.TYPE_FILE, null, cid, fi);
				
				fut.addResultListener(new ExceptionDelegationResultListener(ret)
				{
					public void customResultAvailable(IOutputConnection ocon)
					{
						ByteArrayInputStream bis = new ByteArrayInputStream(data);
						doUpload(fi, bis, ocon, cid);
						ret.setResult(null);
					}
					
					public void exceptionOccurred(Exception exception)
					{
						if(exception instanceof FutureTerminatedException || TransferInfo.STATE_ABORTED.equals(exception.getMessage())
							|| TransferInfo.STATE_CANCELLING.equals(fi.getState()))
						{
							fi.setState(TransferInfo.STATE_ABORTED);
						}
						else if(TransferInfo.STATE_REJECTED.equals(exception.getMessage()))
						{	
							fi.setState(TransferInfo.STATE_REJECTED);
						}
						else
						{
							fi.setState(TransferInfo.STATE_ERROR);
						}
						transfers2.remove(fi.getId());
						publishEvent(ChatEvent.TYPE_FILE, null, cid, fi);
					}
				});

			}					
		});
					
		return ret;
	}
	
	//-------- helper methods --------
	
	/**
	 *  Post an event to registered subscribers.
	 *  @param type	The event type.
	 *  @param nick	The nick name.
	 *  @param cid	The component ID.
	 *  @param value The event value.
	 */
	protected boolean	publishEvent(String type, String nick, IComponentIdentifier cid, Object value)
	{
		return publishEvent(type, nick, cid, value, false, null);
	}
	
	/**
	 *  Post an event to registered subscribers.
	 *  @param type	The event type.
	 *  @param nick	The nick name.
	 *  @param cid	The component ID.
	 *  @param value The event value.
	 */
	protected boolean	publishEvent(String type, String nick, IComponentIdentifier cid, Object value, boolean privatemessage, byte[] image)
	{
//		if(cid==null)
//		{
//			Thread.dumpStack();
//		}
//		
		boolean	ret	= false;
		if(subscribers!=null && cid!=null)	// Hack!!! why is cid null?
		{
			ChatEvent	ce	= new ChatEvent(type, nick, cid, value, privatemessage, image);
			for(Iterator> it=subscribers.iterator(); it.hasNext(); )
			{
				if(it.next().addIntermediateResultIfUndone(ce))
				{
					ret	= true;
				}
				else
				{
					it.remove();
				}
			}
			
			if(subscribers.isEmpty())
			{
				subscribers	= null;
			}
		}
		return ret;
	}

	/**
	 *  Perform a download.
	 */
	protected void	doDownload(final TransferInfo ti, final TerminableIntermediateFuture ret, final IInputConnection con)
	{
		assert TransferInfo.STATE_WAITING.equals(ti.getState());
		ti.setState(TransferInfo.STATE_TRANSFERRING);
		Tuple3, IConnection>	tup2	= transfers2.get(ti.getId());
		if(tup2!=null)
			transfers2.put(ti.getId(), new Tuple3, IConnection>(ti, tup2.getSecondEntity(), con));
		publishEvent(ChatEvent.TYPE_FILE, null, ti.getOther(), ti);
		
		try
		{
			// Enable sending
			if(ret!=null)
			{
				ret.addIntermediateResult(Long.valueOf(0));
			}
			
			final FileOutputStream fos = new FileOutputStream(ti.getFilePath());
			final ITerminableIntermediateFuture fut = con.writeToOutputStream(fos, agent.getExternalAccess());
			
			fut.addResultListener(new IIntermediateResultListener()
			{
				public void resultAvailable(Collection result)
				{
					finished();
				}
				public void intermediateResultAvailable(Long filesize)
				{
					if(TransferInfo.STATE_ABORTED.equals(ti.getState()))
					{
						fut.terminate();
					}
					if(ret!=null)
					{
						ret.addIntermediateResult(filesize);
					}
					if(ti.update(filesize))
					{
						publishEvent(ChatEvent.TYPE_FILE, null, ti.getOther(), ti);
					}
				}
				public void finished()
				{
					try
					{
						fos.close();
					}
					catch(Exception e)
					{
					}
					con.close();
					ti.setState(ti.getSize()==ti.getDone() ? TransferInfo.STATE_COMPLETED : TransferInfo.STATE_ABORTED);
					transfers.remove(ti.getId());
					transfers2.remove(ti.getId());
					publishEvent(ChatEvent.TYPE_FILE, null, ti.getOther(), ti);
				}
				public void exceptionOccurred(Exception exception)
				{
					try
					{
						fos.close();
					}
					catch(Exception e)
					{
					}
					con.close();
					ti.setState(TransferInfo.STATE_CANCELLING.equals(ti.getState()) ? TransferInfo.STATE_ABORTED : TransferInfo.STATE_ERROR);
					transfers.remove(ti.getId());
					transfers2.remove(ti.getId());
					publishEvent(ChatEvent.TYPE_FILE, null, ti.getOther(), ti);
				}
			});
		}
		catch(Exception e)
		{
			ti.setState(TransferInfo.STATE_ERROR);
			transfers.remove(ti.getId());
			transfers2.remove(ti.getId());
			publishEvent(ChatEvent.TYPE_FILE, null, ti.getOther(), ti);
			if(ret!=null)
			{
				ret.setExceptionIfUndone(new RuntimeException(TransferInfo.STATE_ERROR, e));
			} else {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 *  Perform an upload.
	 *  Called from file sender.
	 *  Writes bytes from file input stream to output connection.
	 */
	protected void	doUpload(final TransferInfo ti, final InputStream is, final IOutputConnection ocon, final IComponentIdentifier receiver)
	{
		assert TransferInfo.STATE_WAITING.equals(ti.getState()) : ti.getState();
		ti.setState(TransferInfo.STATE_TRANSFERRING);
		Tuple3, IConnection>	tup2	= transfers2.get(ti.getId());
		if(tup2!=null)
			transfers2.put(ti.getId(), new Tuple3, IConnection>(ti, tup2.getSecondEntity(), ocon));
		publishEvent(ChatEvent.TYPE_FILE, null, ti.getOther(), ti);
		
		try
		{
//			final FileInputStream fis = new FileInputStream(new File(ti.getFilePath()));
			
			final ISubscriptionIntermediateFuture fut = ocon.writeFromInputStream(is, agent.getExternalAccess());

			fut.addResultListener(new IIntermediateResultListener()
			{
				public void resultAvailable(Collection result)
				{
					finished();
				}
				public void intermediateResultAvailable(Long filesize)
				{
					if(TransferInfo.STATE_ABORTED.equals(ti.getState()))
					{
						ocon.close();
						fut.terminate();
					}
					if(ti.update(filesize))
					{
						publishEvent(ChatEvent.TYPE_FILE, null, ti.getOther(), ti);
					}
				}
				public void finished()
				{
					try
					{
						is.close();
					}
					catch(Exception e)
					{
					}
					ocon.close();
					ti.setState(TransferInfo.STATE_COMPLETED);
					transfers.remove(ti.getId());
					transfers2.remove(ti.getId());
					publishEvent(ChatEvent.TYPE_FILE, null, ti.getOther(), ti);			
				}
				public void exceptionOccurred(Exception exception)
				{
					try
					{
						is.close();
					}
					catch(Exception e)
					{
					}
					ocon.close();
					ti.setState(TransferInfo.STATE_CANCELLING.equals(ti.getState()) ? TransferInfo.STATE_ABORTED : TransferInfo.STATE_ERROR);
					transfers.remove(ti.getId());
					transfers2.remove(ti.getId());
					publishEvent(ChatEvent.TYPE_FILE, null, ti.getOther(), ti);
				}
			});
		}
		catch(Exception e)
		{
			ti.setState(TransferInfo.STATE_ERROR);
			transfers.remove(ti.getId());
			transfers2.remove(ti.getId());
			publishEvent(ChatEvent.TYPE_FILE, null, ti.getOther(), ti);
		}
	}
	
//	/**
//	 *  Perform an upload.
//	 *  Called from file sender.
//	 *  Writes bytes from file input stream to output connection.
//	 */
//	protected void	doUpload(final TransferInfo ti, final IOutputConnection ocon, final IComponentIdentifier receiver)
//	{
//		assert TransferInfo.STATE_WAITING.equals(ti.getState()) : ti.getState();
//		ti.setState(TransferInfo.STATE_TRANSFERRING);
//		Tuple3, IConnection>	tup2	= transfers2.get(ti.getId());
//		if(tup2!=null)
//			transfers2.put(ti.getId(), new Tuple3, IConnection>(ti, tup2.getSecondEntity(), ocon));
//		publishEvent(ChatEvent.TYPE_FILE, null, ti.getOther(), ti);
//		
//		try
//		{
//			final FileInputStream fis = new FileInputStream(new File(ti.getFilePath()));
//			
//			final ISubscriptionIntermediateFuture fut = ocon.writeFromInputStream(fis, agent.getExternalAccess());
//
//			fut.addResultListener(new IIntermediateResultListener()
//			{
//				public void resultAvailable(Collection result)
//				{
//					finished();
//				}
//				public void intermediateResultAvailable(Long filesize)
//				{
//					if(TransferInfo.STATE_ABORTED.equals(ti.getState()))
//					{
//						ocon.close();
//						fut.terminate();
//					}
//					if(ti.update(filesize))
//					{
//						publishEvent(ChatEvent.TYPE_FILE, null, ti.getOther(), ti);
//					}
//				}
//				public void finished()
//				{
//					try
//					{
//						fis.close();
//					}
//					catch(Exception e)
//					{
//					}
//					ocon.close();
//					ti.setState(TransferInfo.STATE_COMPLETED);
//					transfers.remove(ti.getId());
//					transfers2.remove(ti.getId());
//					publishEvent(ChatEvent.TYPE_FILE, null, ti.getOther(), ti);			
//				}
//				public void exceptionOccurred(Exception exception)
//				{
//					try
//					{
//						fis.close();
//					}
//					catch(Exception e)
//					{
//					}
//					ocon.close();
//					ti.setState(TransferInfo.STATE_CANCELLING.equals(ti.getState()) ? TransferInfo.STATE_ABORTED : TransferInfo.STATE_ERROR);
//					transfers.remove(ti.getId());
//					transfers2.remove(ti.getId());
//					publishEvent(ChatEvent.TYPE_FILE, null, ti.getOther(), ti);
//				}
//			});
//		}
//		catch(Exception e)
//		{
//			ti.setState(TransferInfo.STATE_ERROR);
//			transfers.remove(ti.getId());
//			transfers2.remove(ti.getId());
//			publishEvent(ChatEvent.TYPE_FILE, null, ti.getOther(), ti);
//		}
//	}
	
	//-------- IPropertiesProvider interface --------
	
	/**
	 * 
	 */
	@Reference
	public class PropProvider implements IPropertiesProvider
	{
		protected Future called = new Future();
		
		/**
		 * 
		 */
		public IFuture isCalled()
		{
			return called;
		}
		
		/**
		 *  Update from given properties.
		 */
		public IFuture setProperties(Properties props)
		{
			String tmp = props.getStringProperty("nickname");
			if(tmp!=null)
				setNickName(tmp);
			tmp = props.getStringProperty("image");
			if(tmp!=null)
			{
				try
				{
					setImage(Base64.decode(tmp.getBytes("UTF-8")));
				}
				catch(UnsupportedEncodingException e)
				{
					throw new RuntimeException(e);
				}
			}
			
			called.setResultIfUndone(null);
			
			return IFuture.DONE;
		}
		
		/**
		 *  Write current state into properties.
		 */
		public IFuture getProperties()
		{
			Properties	props	= new Properties();
			// Only save as executing when in normal mode.
			props.addProperty(new Property("nickname", nick));
			if(image!=null)
			{
				try
				{
					props.addProperty(new Property("image", new String(Base64.encode(image), "UTF-8")));
				}
				catch (UnsupportedEncodingException e)
				{
					throw new RuntimeException(e);
				}
			}
			return new Future(props);
		}
	}
	
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy