summaryrefslogtreecommitdiffstats
path: root/EssentialsUpdate/src/f00f/net/irc/martyr/services/AutoReconnect.java
blob: 06ff33f70817cad34527ea5b45fdb9356ff43acf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
package f00f.net.irc.martyr.services;

import java.io.IOException;

import f00f.net.irc.martyr.IRCConnection;
import f00f.net.irc.martyr.InCommand;
import f00f.net.irc.martyr.GenericAutoService;
import f00f.net.irc.martyr.State;
import f00f.net.irc.martyr.clientstate.ClientState;
import f00f.net.irc.martyr.commands.QuitCommand;

/**
 * <p>AutoReconnect performs the job of reconnecting to the server, if
 * the connection is terminated unexpectedly.  AutoReconnect 
 * will try to reconnect 5 times, and then give up.  If AutoReconnect
 * intercepts a QUIT command before the state change that is issued by
 * us (bounced back from the server) then it will not try to
 * reconnect.</p>
 *
 * <p>Note that AutoReconnect has no play in nick negotiation, and as
 * such, a failed nick negotiation does not count as a connection
 * retry.</p>
 *
 * <p>Testing note: Does a server send a QUIT before a forceful
 * removal from the network?  Should AutoReconnect not intercept
 * QUITs? Certainly not all servers send QUITs even when you QUIT on
 * your own; this class should be put into a command-out chain as well.</p>
 */
public class AutoReconnect extends GenericAutoService
{
    //static Logger log = Logger.getLogger(AutoReconnect.class);

    private int attempt;
    private int maxAttempts;
    private int sleepTime;
    private boolean disableOnQuit;

    // How many times to try reconnecting?
    public static final int DEFAULT_MAX_ATTEMPTS = 5;
    // TODO This is a rather simplistic method, personally I would like to
    // see a version of this class that implements a backoff algorithm.

    // If we tried to connect, but failed, how long do we wait until we
    // try again (ms)?
    public static final int DEFAULT_CONNECT_SLEEPTIME = 1000;

    // If we get a QUIT command from the server notifying us that we have
    // QUIT, then self-disable so that we don't reconnect.
    public static final boolean DEFAULT_DISABLE_ON_QUIT = true;

    /**
     * @param connection The IRCConnection
     * @param maxAttempts The number of tries to do before giving up
     * @param sleepBetween Milliseconds to sleep between retries
     * @param disableOnQuit Automatically disable on quit event
     */
    public AutoReconnect( IRCConnection connection, int maxAttempts,
            int sleepBetween, boolean disableOnQuit )
    {
        super( connection );

        this.disableOnQuit = disableOnQuit;
        this.maxAttempts = maxAttempts;
        this.sleepTime = sleepBetween;
        this.attempt = 0;

        enable();
    }

    public AutoReconnect( IRCConnection connection, int maxAttempts,
            int sleepBetween )
    {
        this( connection, maxAttempts, sleepBetween, DEFAULT_DISABLE_ON_QUIT );
    }

    /**
     * Initializes with reasonable defaults.
     *
     * @param connection Connection we are associated with
     */
    public AutoReconnect( IRCConnection connection )
    {
        this( connection, DEFAULT_MAX_ATTEMPTS, DEFAULT_CONNECT_SLEEPTIME );
    }

    /**
     * Attempts to connect, returning only when a connection has been made
     * or repeated connections have failed.
     *
     * @param server Server to connect to
     * @param port Port to connect to
     * */
    public void go( String server, int port )
    {
        doConnectionLoop( server, port );
    }

    /**
     * Attempts a single connection to the server.  The connection is done
     * by asking the client state what server and port we are supposed to
     * be connected to, and then calling IRCConnection.connect(...).
     *
     * @throws IOException if we could not connect successfully
     * */
    protected void connect()
        throws IOException
    {
        ClientState cstate = getConnection().getClientState();
        connect( cstate.getServer(), cstate.getPort() );
    }

    /**
     * Attempts a single connection to the server.
     *
     * @param server Server to connect to
     * @param port Port to connect to
     * @throws IOException if we could not connect successfully
     * */
    protected void connect( String server, int port )
        throws IOException
    {
        getConnection().connect( server, port );
    }

    protected void updateState( State state )
    {
        //log.debug("AutoReconnect: Update with state " + state);
        if( state == State.UNCONNECTED )
        {
            // This should only happen if we were connected and then
            // disconnected.  Try connecting.

            // failedToConnect() prevents insane-reconnecting loop if
            // we never registered, however, it introduces a delay if we
            // were registered properly previously
            if (failedToConnect(null))
                doConnectionLoop();
        } else if( state == State.REGISTERED ) {
            this.attempt = 0;
        }

        //log.debug("AutoReconnect: Returned from " + state);
    }

    /**
     * Calls connect() followed by failedToConnect(...) until a connection
     * is made.  Gets the server and port from the client state, implying
     * that a connection was already attempted before.
     * */
    protected void doConnectionLoop()
    {
        // Tell called proc to use client state info
        doConnectionLoop( null, -1 );
    }

    /**
     * Calls connect() followed by failedToConnect(...) until a connection
     * is made, or the service is disabled.
     *
     * @param server Server to connect to
     * @param port Port to connect to
     * */
    protected void doConnectionLoop( String server, int port )
    {
        boolean keeptrying = true;

        while( keeptrying && enabled )
        {
            Exception error = null;
            try
            {
                if( server == null )
                {
                    // Try getting the info from the client state
                    connect();
                }
                else
                {
                    connect( server, port );
                }
                keeptrying = false;
            }
            catch( Exception e )
            {
                error = e;
                keeptrying = true;
            }

            if( keeptrying )
            {
                keeptrying = failedToConnect( error );
            }
        }
    }

    /**
     * Called when the final failure has occurred.  Default implementation
     * does nothing.
     * */
    protected void finalFailure()
    {
        //log.debug("AutoReconnect: Final failure.");
        this.attempt = 0;
    }

    /**
     * Called when a failure to connect occurs.  This method should do
     * whatever needs to be done between connection attempts; usually this
     * will be sleeping the current thread and checking if we should stop
     * trying to reconnect.
     *
     * @param error Exception that caused the failure
     * @return true if another attempt at connecting should occur
     * */
    protected boolean failedToConnect( Exception error )
    {
        //log.debug("AutoReconnect: Error connecting: " + error);

        this.attempt++;

        // abort if we've tried too many times
        if( attempt >= maxAttempts )
        {
            //log.debug("AutoReconnect: Tried " + attempt + " times, giving up.");
            finalFailure();
            return false;
        }

        // Well, try again damnit!
        // Sleep for a bit first.
        try
        {
            Thread.sleep( sleepTime );
        }
        catch( InterruptedException ie )
        {
            // And we're going to do what?
            return false;
        }

        return true;
    }

    /**
     * AutoReconnect will disable itself if it sees a quit command
     * generated by returned from the server.  This usually happens just
     * before the server terminates the connection.  Note that this would
     * really be better if we could intercept messages on their way out,
     * but Martyr doesn't do that.
     */
    protected void updateCommand( InCommand command )
    {
        if( disableOnQuit
                && command instanceof QuitCommand
                && ((QuitCommand)command).isOurQuit(getConnection().getClientState()) )
        {
            //log.debug("AutoReconnect: Disabling due to receiving own QUIT.");
            disable();
        }
    }

    public String toString()
    {
        return "AutoReconnect [" + attempt + "]";
    }

}