/*
This file is part of leafdigital leafChat.
leafChat 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 3 of the License, or
(at your option) any later version.
leafChat 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 leafChat. If not, see <http://www.gnu.org/licenses/>.
Copyright 2012 Samuel Marshall.
*/
package com.leafdigital.irc;
import java.util.*;
import util.TimeUtils;
import com.leafdigital.irc.api.*;
import com.leafdigital.prefs.api.PreferencesGroup;
import leafchat.core.api.*;
/** Implementor of Connections interface to manage list of connected servers */
public class ConnectionsSingleton implements Connections,MsgOwner
{
private MessageDispatch dispatch;
private List<ServerConnection> servers = new LinkedList<ServerConnection>();
private Plugin p;
private PluginContext context;
PluginContext getPluginContext() { return context; }
Plugin getPlugin() { return p; }
private DefaultMessageDisplay dmd;
private int messageSequence=0;
/**
* Obtains the default message display handler (which must be set outside
* the plugin).
* @return The default message display handler
* @throws IllegalStateException If it hasn't been set yet
*/
public DefaultMessageDisplay getDefaultMessageDisplay() throws IllegalStateException
{
if(dmd==null) throw new IllegalStateException("Default message display not set");
return dmd;
}
ConnectionsSingleton(PluginContext context,Plugin p) throws GeneralException
{
this.context=context;
this.p=p;
context.registerMessageOwner(this);
}
@Override
public Server newServer()
{
return new ServerConnection(this,context);
}
void closeAll()
{
ServerConnection[] serverArray;
synchronized(this)
{
serverArray = servers.toArray(new ServerConnection[servers.size()]);
}
for(int i=0;i<serverArray.length;i++)
{
serverArray[i].disconnectGracefully();
}
}
synchronized void informConnected(final ServerConnection sc)
{
servers.add(sc);
ServerConnectedMsg scm=new ServerConnectedMsg(sc,messageSequence++);
dispatch.dispatchMessageHandleErrors(scm,false);
// Start connection-finished-idle timer.
final int
CHECKEVERY=250, // Check every 250ms...
AFTERMOTD=1000, // If there is 1000ms idle after the MOTD...
ANYWAY=5000; // ...or 5000ms pause after we have got
// the nickname at least (should be in first numeric after accepting
// nick), then we send the ConnectionFinished.
TimeUtils.addTimedEvent(new Runnable()
{
@Override
public void run()
{
synchronized(ConnectionsSingleton.this)
{
// If it was disconnected, abandon this process
if(!servers.contains(sc)) return;
long lastLine=sc.getLastLineTime();
if(lastLine!=0) // As long as we've received at least *some* line
{
long now=System.currentTimeMillis();
int actualDelay=(int)(now-lastLine);
if( !sc.deferConnectionFinished() &&
( (sc.hasGotEndOfMOTD() && actualDelay >AFTERMOTD) ||
(sc.getOurNick()!=null && actualDelay>ANYWAY)))
{
ServerConnectionFinishedMsg scfm=new ServerConnectionFinishedMsg(sc,messageSequence++);
dispatch.dispatchMessageHandleErrors(scfm,false);
return;
}
}
// Try again a little later
TimeUtils.addTimedEvent(this,CHECKEVERY,true);
}
}
},CHECKEVERY,true);
}
void informDisconnected(ServerConnection sc, Throwable t)
{
int thisSequence;
synchronized(this)
{
servers.remove(sc);
thisSequence = messageSequence;
messageSequence++;
}
NetworkException ne = (t==null || (t instanceof NetworkException))
? (NetworkException)t
: new NetworkException(t);
ServerDisconnectedMsg sdm = new ServerDisconnectedMsg(sc, ne, thisSequence,
sc.wasQuitRequested());
dispatch.dispatchMessageHandleErrors(sdm, false);
}
void informLine(ServerConnection sc,byte[] abLine)
{
int thisSequence;
synchronized(this)
{
thisSequence=messageSequence;
messageSequence++;
}
ServerLineMsg slm=new ServerLineMsg(sc,abLine,thisSequence);
dispatch.dispatchMessageHandleErrors(slm,false);
}
void informSend(ServerConnection sc,byte[] abLine)
{
ServerSendMsg ssm=new ServerSendMsg(sc,abLine);
dispatch.dispatchMessageHandleErrors(ssm,false);
}
int informRearrange(ServerRearrangeMsg srm)
{
int thisSequence;
synchronized(this)
{
thisSequence=messageSequence;
messageSequence++;
}
srm.updateSequence(thisSequence);
dispatch.dispatchMessageHandleErrors(srm,true);
return srm.getResult();
}
// MessageOwner
@Override
public void init(MessageDispatch mdp)
{
this.dispatch=mdp;
}
@Override
public String getFriendlyName()
{
return "Low-level server messages";
}
@Override
public Class<? extends Msg> getMessageClass()
{
return ServerMsg.class;
}
@Override
public boolean registerTarget(Object oTarget, Class<? extends Msg> cMessage,
MessageFilter mf,int iRequestID,int iPriority)
{
return true;
}
@Override
public void unregisterTarget(Object oTarget,int iRequestID)
{
}
@Override
public void manualDispatch(Msg m)
{
}
@Override
public boolean allowExternalDispatch(Msg m)
{
return false;
}
@Override
public synchronized Server getNumbered(int index) throws ArrayIndexOutOfBoundsException
{
if(index-1 < servers.size() && index-1 > 0)
return servers.get(index-1);
else
throw new ArrayIndexOutOfBoundsException("No connected server at index "+index);
}
@Override
public synchronized Server[] getConnected()
{
return servers.toArray(new Server[servers.size()]);
}
/**
* Obtains short name for a server. The shortname identifies a server uniquely
* with regard to other servers, i.e. if you only have one connection to a
* network, it'll be the network name.
* @param s Server
* @return Suitable name
*/
public synchronized String getShortName(Server s)
{
String
host=s.getReportedOrConnectedHost(),
fullHost=host+":"+s.getConnectedPort();
if(host.indexOf(':')!=-1) host=host.substring(0,host.indexOf(':'));
String network=s.getPreferences().getAnonParent().get(IRCPrefs.PREF_NETWORK,null);
boolean networkOK=network!=null,skipPortOK=true,skipNumberOK=true;
boolean gotUsYet=false;
int number=1;
for(Server compare : servers)
{
String compareHost=compare.getReportedOrConnectedHost();
String compareFullHost=compareHost+":"+compare.getConnectedPort();
if(compare==s)
{
gotUsYet=true;
continue;
}
if(!gotUsYet && compareFullHost.equals(fullHost)) number++;
PreferencesGroup pg=compare.getPreferences().getAnonParent();
String sCompareNetwork=pg==null ? null : pg.get(IRCPrefs.PREF_NETWORK,null);
if(networkOK && sCompareNetwork!=null && sCompareNetwork.equals(network)) networkOK=false;
if(skipPortOK && compareHost.equals(host)) skipPortOK=false;
if(skipNumberOK && compareFullHost.equals(fullHost)) skipNumberOK=false;
}
if(networkOK)
return network;
else if(skipPortOK)
return host;
else if(skipNumberOK)
return fullHost;
else
return fullHost+" \u2013 "+number;
}
@Override
public void setDefaultMessageDisplay(DefaultMessageDisplay dmd)
{
this.dmd=dmd;
}
}