summaryrefslogtreecommitdiffstats
path: root/EssentialsUpdate/src/f00f/net/irc/martyr/services/AutoRegister.java
blob: f0cec08ba84c7574bd1cacc571cad02fbbbaa307 (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
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
package f00f.net.irc.martyr.services;

import java.util.Iterator;
import java.util.NoSuchElementException;

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.TimerTaskCommand;
import f00f.net.irc.martyr.clientstate.ClientState;
import f00f.net.irc.martyr.commands.NickCommand;
import f00f.net.irc.martyr.commands.UserCommand;
import f00f.net.irc.martyr.commands.PassCommand;
import f00f.net.irc.martyr.errors.NickInUseError;
import f00f.net.irc.martyr.util.FullNick;
import java.util.logging.Logger;

/**
 * <p>AutoRegister performs the task of registering the user with the server
 * once connected, including finding an appropriate nickname to use if the
 * desired one is taken.</p>
 *
 * <p>AutoRegister's default behaviour is to send the provided nickname.  If it
 * receives an ERR_NICKNAMEINUSE while unregistered, AutoRegister will try
 * again, with an _ appended to the nick.  If this fails five times,
 * AutoRegister will ask the IRCConnection to disconnect().  Note that if it
 * fails to connect it remains enabled, so that if IRCConnection.connect() is
 * called, it will re-try the same 5 NICKs.</p>
 *
 * <p>This default behaviour can be overridden by subclassing AutoRegister and
 * overriding the getNickIterator( String baseNick ) method.  It returns an
 * instance of the java.util.Iterator interface which supplies nicknames (each
 * object retreived from the Iterator is presumed to be a String).
 * AutoRegister will iterate through the nickname list until there are no more
 * items, at which point it will stop.  For simple tasks such as providing a
 * custom way to form new nicknames, overriding getNickIterator is
 * sufficient.</p>
 *
 * <p>AutoRegister will add itself as a state observer and as a command
 * observer.  It needs to receive the error.NickInUseError command so that
 * it can re-try the registration, and it needs to detect when we
 * transition into the UNREGISTERED state.</p>
 *
 * <p>AutoRegister should be created before the IRCConnection.connect()
 * is called.  AutoRegister can be disabled by calling the 'disable()'
 * method at any time.  This simply removes AutoRegister as an
 * observer for the state and commands.</p>
 *
 */
public class AutoRegister extends GenericAutoService
{
    static Logger log = Logger.getLogger(AutoRegister.class.getName());

    // I've lost track of why the timer stuff was in here.  I think the
    // original purpose was to make AutoRegister take control of the nick
    // more *after* registration occurred.  This code is now causing so
    // many problems *before* registration, that I think it may need to be
    // pulled out.  Maybe time to bring a "Manager" service into the
    // fold?
    private long nickTimerTaskDelay = 10*1000;
    private TimerTaskCommand nickTimerTask;

    // Kept so it can be passed to getNickIterator()
    private String originalNick;
    // Used to set the client state once we register properly.
    private String lastTryNick = null;
    // Passed to the server on login
    private String user;
    private String name;
    private String pass;
    // Our list of nicks.
    private Iterator nickIterator = null;
    // attempt is only used for the debug output.
    private int attempt = 0;

    public static final int MAX_ATTEMPTS = 5;

    public AutoRegister( IRCConnection connection, String nick,
        String user, String name )
    {
        super( connection );

        this.originalNick = nick;
        this.user = user;
        this.name = name;
        this.pass = null;

        enable();
    }

    public AutoRegister( IRCConnection connection, String nick,
        String user, String name, String pass)
    {
        super( connection );

        this.originalNick = nick;
        this.user = user;
        this.name = name;
        this.pass = pass;

        enable();
    }


    /**
     * <p>This method supplies an Iterator that generates nicknames.  Each successive
     * failed attempt to login to the server with a nickname will be met with a new
     * try using the next nickname in the iterator.  When there are no more
     * nicknames in the Iterator, AutoRegister gives up.  Defining the Iterator as
     * an anonymous class works well.</p>
     *
     * <p>The iterator should iterate over String objects.</p>
     *
     * @param baseNick The nickname passed into the constructor.
     * @return Iterator over other attempts of nicks to try
     */
    protected Iterator getNickIterator( final String baseNick )
    {
        // This is simple and clean.. define the nick generation scheme as an
        // anonymous class.
        return new UnderscoreIterator(baseNick);
    }

    private static class UnderscoreIterator implements Iterator
    {
        int count = 1;
        String nick;

        public UnderscoreIterator( String base )
        {
            this.nick = base;
        }

        public boolean hasNext()
        {
            return count <= MAX_ATTEMPTS;
        }

        public Object next()
        {
            if( hasNext() )
            {
                String result = nick;

                // Set up the next round
                nick = nick + "_";
                ++count;

                // return the value for this round.
                return result;
            }
            else
            {
                throw new NoSuchElementException("No more nicknames");
            }
        }

        public void remove()
        {
            throw new UnsupportedOperationException();
        }
    }

    protected void updateState( State state )
    {
        //log.debug("AutoRegister: Update with state " + state);
        if( state == State.UNREGISTERED )
        {
            // We need to do some registerin'!
            nickIterator = getNickIterator( originalNick );
            attempt = 0;
            doRegister();
        }
        else if( state == State.REGISTERED )
        {
            // We need to update the client state.
            ClientState clientState = getConnection().getClientState();
            clientState.setNick( new FullNick( lastTryNick ) );
            clientState.setName( name );
            clientState.setUser( user );
            clientState.setPass( pass );
        }

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

    protected void updateCommand( InCommand command )
    {
        // First, check the state, because if we are already registered
        // then none of this matters.
        //if( getConnection().getState() == State.REGISTERED )
        //{
        //	// We're registered.
        //	// No reason to continue.
        //	return;
        //}

        if( command instanceof NickInUseError)
        {
            // If we get an error, then try another nick
            NickInUseError nickErr = (NickInUseError)command;
            if(nickErr.getNick().getNick().equals(originalNick))
            {
                cancelNickAttempt(); // We don't want more than one of these

                scheduleNickAttempt();
            }
            if(getConnection().getState() == State.UNREGISTERED )
            {
                // re-register.
                doRegister();
            }
        }
        else if( command instanceof NickCommand )
        {
            // If we get a nick... then cancel a pending change
            NickCommand nickCmd = (NickCommand)command;
            if( nickCmd.getOldNick().equalsIgnoreCase( originalNick ) )
            {
                cancelNickAttempt();
            }
        }
    }

    /**
     *
     */
    private void scheduleNickAttempt()
    {
        if( getConnection().getState().equals(State.REGISTERED))
        {
            // We're already connected.
            // We're short-circuiting
            return;
        }
        if(nickTimerTask == null || !nickTimerTask.isScheduled())
        {
            nickTimerTask = new TimerTaskCommand(getConnection(), new NickCommand(originalNick));
            //TODO back off delay on repeated retries?
            getConnection().getCronManager().schedule(nickTimerTask, nickTimerTaskDelay);
        }
    }

    private void cancelNickAttempt()
    {
        if(nickTimerTask != null && nickTimerTask.isScheduled())
        {
            nickTimerTask.cancel();
        }
    }

    private void doRegister()
    {
        if( getConnection().getState() != State.UNREGISTERED )
        {
            log.severe("AutoRegister: Tried to register but we are not UNREGISTERED");
            return;
        }

        if( ! nickIterator.hasNext() )
        {
            log.info("AutoRegister: Failed to register.");
            getConnection().disconnect();
            return;
        }

        lastTryNick = (String)nickIterator.next();
        ++attempt;
        log.info("AutoRegister: Trying to register as " + lastTryNick);

        if (pass != null) {
            getConnection().sendCommand( new PassCommand( pass ));
        }
        getConnection().sendCommand( new NickCommand( lastTryNick ) );
        getConnection().sendCommand( new UserCommand( user, name, getConnection() ) );
    }

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

    // END AutoRegister
}