package uc.protocols.client; import helpers.GH; import helpers.IObservable; import helpers.PreferenceChangedAdapter; import helpers.StatusObject; import helpers.Observable.IObserver; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import logger.LoggerFactory; import org.apache.log4j.Logger; import uc.ConnectionHandler; import uc.DCClient; import uc.IHasUser; import uc.IHub; import uc.IUser; import uc.IUserChangedListener; import uc.Identity; import uc.LanguageKeys; import uc.PI; import uc.files.IDownloadable; import uc.files.IHasDownloadable; import uc.files.transfer.FileTransferInformation; /** * a StateMachine that will have some overview over the running connections of an user * * it will do Bookkeeping of the states of an user of whom we want to download * also this will be used as model to show information * * @author Quicksilver * */ public class ClientProtocolStateMachine implements IObserver<StatusObject> ,IHasUser , IUserChangedListener, IHasDownloadable, Runnable { private static final Logger logger = LoggerFactory.make(); private static volatile int MaxDOWNLOADS; static { new PreferenceChangedAdapter(PI.get(),PI.maxSimDownloads) { @Override public void preferenceChanged(String preference, String oldValue, String newValue) { updateMaxDown(); } }; updateMaxDown(); } private static void updateMaxDown() { MaxDOWNLOADS = PI.getInt(PI.maxSimDownloads); if (MaxDOWNLOADS <= 0) { MaxDOWNLOADS = Integer.MAX_VALUE; } } /** * when ever the protocol waits for some x seconds .. this will be used for counting * so it can be shown in the gui */ private volatile int sleepCounter; private volatile boolean running = true; /** * a task that is to be executed when the sleep counter reaches zero.. */ private volatile Runnable sleepTask; /** * * antipattern? remmove? * */ public static class CPSMManager { private final CopyOnWriteArrayList<ClientProtocolStateMachine> all = new CopyOnWriteArrayList<ClientProtocolStateMachine>(); private final DCClient dcc; private ScheduledFuture<?> task; public CPSMManager(DCClient dcc) { this.dcc = dcc; } public void start() { task = dcc.getSchedulerDir().scheduleAtFixedRate(new Runnable() { public void run() { for (ClientProtocolStateMachine m: all) { synchronized(m) { if (--m.sleepCounter < 0 && m.sleepTask != null) { Runnable r = m.sleepTask; m.sleepTask = null; dcc.executeDir( r ); } if (m.sleepCounter % 10 == 0) { logger.debug("sleepCounter: "+ m.sleepCounter+" "+m.user); } } } } } , 1, 1 , TimeUnit.SECONDS); } public void stop() { if (task != null) { task.cancel(false); } } public boolean remove(Object o) { return all.remove(o); } public boolean addIfAbsent(ClientProtocolStateMachine element) { return all.addIfAbsent(element); } public int size() { return all.size(); } } private final IUser user; /** * the connection handler.. * so we can unregister us there when the user is no longer interesting.. */ private final ConnectionHandler ch; /** * currently active client protocol * null if none.. */ private ClientProtocol current = null; /** * describes what file has been downloaded last */ private FileTransferInformation lastDownload = null; /** * here the current state is stored */ private volatile CPSMState state = CPSMState.IDLE; private final String token; /** * * @param usr - the user from which we want to download. */ public ClientProtocolStateMachine(IUser usr,ConnectionHandler ch) { this.user = usr; this.ch = ch; logger.debug("created statemachine: "+ch.getCpsmManager().size()); token = Integer.toHexString(Math.abs(GH.nextInt())); ch.getDCC().getPopulation().registerUserChangedListener(this, usr.getUserid()); ch.addStatemachine(usr, this); } public void update(IObservable<StatusObject> o, StatusObject arg) { if (arg.getDetailObject() == this) { //check if notification is for us logger.debug("calling statemachine: "+user); switch(arg.getDetail()) { case ConnectionHandler.USER_IDENTIFIED_IN_CONNECTION: current = (ClientProtocol)arg.getValue(); sleepTask = null; //clear sleeptask.. ch.getCpsmManager().remove(this); break; case ConnectionHandler.CONNECTION_CLOSED: case ConnectionHandler.STATEMACHINE_CREATED: if (current != null) { lastDownload = current.getFti(); setState(CPSMState.CLOSED); sleepCounter = ((ClientProtocol)current).isImmediateReconnect()? 0 : 60 ; } else { sleepCounter = 0; } logger.debug("sleep counter is set to: "+sleepCounter); sleepTask = this; ch.getCpsmManager().addIfAbsent(this); break; } if (!user.weWantSomethingFromUser() || !user.isOnline()) { //remove immediately after disconnect..if nothing more is needed..by this user.. logger.debug("removeing statemachine1: "+user); remove(); } } } public synchronized void run() { final IHub hub = user.getHub(); Identity id = hub != null? hub.getIdentity(): null; if (getState() == CPSMState.WAITING_FOR_CONNECT && id != null && id.isActive() ) { id.getConnectionDeterminator().connectionTimedOut(user, user.hasSupportForEncryption() && id.currentlyTLSSupport()); } logger.debug("StateMachine. executing sleeptask: "+ch.getCpsmManager().size()); //TODO user.resolveDQE returns null if user has download running.. this is unclear.. if (null == user.resolveDQEToUser() || ch.getNrOfRunningDownloads() >= MaxDOWNLOADS) { logger.debug("no DQE found.." +user); if (user.weWantSomethingFromUser()) { // if we want nothing ..we break out of this //if we want something.. but not now..just go to sleep sleepCounter = 10; sleepTask = this; setState(CPSMState.IDLE); } else { remove(); } } else { // if we need something send a ctm or rcm logger.debug("StateMachine found item: "+user + " dqe: "); if (hub != null && user.isOnline()) { hub.requestConnection(user, token); //we expect a connect.. if don't get one.. redo this sleepCounter = 60; sleepTask = this; setState(CPSMState.WAITING_FOR_CONNECT); } else { setState(CPSMState.USER_OFFLINE); remove(); } } } public IDownloadable getDownloadable() { FileTransferInformation fti = getLastDownload(); if (fti != null && fti.getDqe() != null) { return fti.getDqe().getDownloadable(); } return null; } /* * when ever the status of the connection changes.. * delete the sleepTask.. * and set a new appropriate task * usually used for timeout.. and reconnecting.. * public synchronized void statusChanged(ConnectionState newStatus, ConnectionProtocol cp) { logger.debug("called statusChanged on CPStatemeachine"); sleepTask = null; switch(newStatus) { case LOGGEDIN: logger.debug("called LOGeDIN"); if (cp instanceof ClientProtocol) { current = (ClientProtocol)cp; } break; case TRANSFERSTARTED: logger.debug("called TRANSFERRUNNING"); break; case DESTROYED: if (current != null) { lastDownload = current.getFti(); setState(CPSMState.CLOSED); } logger.debug("called CLOsED"); //normally sleep 60 seconds.. but connect immediately if wished if (cp instanceof ClientProtocol) { sleepCounter = ((ClientProtocol)cp).isImmediateReconnect()? 0 : 60 ; } else { sleepCounter = 0; } logger.debug("sleep counter is set to: "+sleepCounter); sleepTask = new Runnable() { public void run() { synchronized(ClientProtocolStateMachine.this) { if (getState() == CPSMState.WAITING_FOR_CONNECT && ch.getDCC().isActive()) { //if this is called in waiting for connect it means no //successful connection was created.. //TODO may be wrong if an upload was running.. ch.getDCC().getConnectionDeterminator().connectionTimedOut(user, user.hasSupportFoEncryption() && ch.getDCC().currentlyTLSSupport()); } logger.debug("StateMachine. executing sleeptask: "+all.size()); if (null == user.resolveDQEToUser() || ch.getNrOfRunningDownloads() >= MaxDOWNLOADS) { logger.debug("no DQE found.." ); if (user.weWantSomethingFromUser()) { // if we want nothing ..we break out of this //if we want something.. but not now..just go to sleep sleepCounter = 10; sleepTask = this; setState(CPSMState.IDLE); } else { remove(); } } else { // if we need something send a ctm or rcm logger.debug("StateMachine found item"); IHub othershub = user.getHub(); if (othershub != null && user.isOnline()) { boolean nmdc = othershub.isNMDC(); boolean encryption = user.hasSupportFoEncryption() && ch.getDCC().currentlyTLSSupport(); CPType protocol = CPType.get(encryption, nmdc); if (ch.getDCC().isActive()) { logger.debug("sending CTM "+user+ " " + protocol+" "+ ch.getDCC().getConnectionDeterminator(). getPublicIP().getHostAddress()); othershub.sendCTM(user, protocol ,token); } else { logger.debug("sending RCM "+user); othershub.sendRCM(user, protocol ,token); } //we expect a connect.. if don't get one.. redo this sleepCounter = 60; sleepTask = this; setState(CPSMState.WAITING_FOR_CONNECT); } else { setState(CPSMState.USER_OFFLINE); remove(); } } } } }; break; } if (!user.weWantSomethingFromUser() || !user.isOnline()) { //remove immediately after disconnect..if nothing more is needed..by this user.. remove(); } } */ /** * just removes user when he disconnects.. */ public void changed(UserChangeEvent uce) { switch (uce.getType()) { case CHANGED: switch(uce.getDetail()) { case UserChangeEvent.DOWNLOADQUEUE_ENTRY_POST_REMOVE_LAST: remove(); break; } break; case QUIT: remove(); break; } } /** * disable this StateMachine * usage is calling when here should no longer be done any downloads with the user.. * */ private synchronized void remove() { if (ch.getCpsmManager().remove(this) ) { running = false; ch.removeStatemachine(user,this); ch.getDCC().getPopulation().unregisterUserChangedListener(this, user.getUserid()); logger.debug("removed statemachine"); } } public boolean isActive() { return running; } /** * the user for this StateMachine */ public IUser getUser() { return user; } public ClientProtocol getCurrent() { return current; } public void clearTime() { sleepCounter = 0; if (sleepTask == null) { logger.warn("sleep task failure"); /*DCClient.execute(new Runnable() { public void run() { statusChanged(ConnectionState.CLOSED, null); } });*/ } } /** * * @return true if a ConnectionProtocol is running * for this StateMachine */ public boolean hasConnectionProtocol() { return current != null; } /** * * @return last FileTransfer that was done,, */ public FileTransferInformation getLastDownload() { return lastDownload; } public static enum CPSMState { IDLE(LanguageKeys.Idle), USER_OFFLINE(LanguageKeys.UserOffline), WAITING_FOR_CONNECT(LanguageKeys.WaitingForConnect), CLOSED(LanguageKeys.Closed); CPSMState(String languageKey) { this.languageKey = languageKey; } private final String languageKey; public String toString() { return languageKey; } } public String getToken() { return token; } public CPSMState getState() { return state; } private void setState(CPSMState state) { this.state = state; ch.notifyOfChange(ConnectionHandler.STATEMACHINE_CHANGED, null, this); //ch.notifyObservers(new StatusObject(this,ChangeType.CHANGED)); } public int getSleepCounter() { return sleepCounter; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((user == null) ? 0 : user.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; final ClientProtocolStateMachine other = (ClientProtocolStateMachine) obj; if (user == null) { if (other.user != null) return false; } else if (!user.equals(other.user)) return false; return true; } }