]> Pileus Git - ~andy/spades/blob - src/Client.java
Fix .look bugs
[~andy/spades] / src / Client.java
1 package org.pileus.spades;
2
3 import java.io.*;
4 import java.net.*;
5
6 public class Client
7 {
8         /* Constnats */
9         enum State {
10                 INIT,
11                 SETUP,
12                 READY,
13                 ABORT,
14         };
15
16         /* Preference data */
17         public  String         server   = "irc.freenode.net";
18         public  int            port     = 6667;
19         public  int            timeout  = 240;
20         public  String         nickname = "SpadeGuest";
21         public  String         channel  = "#rhnoise";
22         public  boolean        usesasl  = false;
23         public  String         authname = "";
24         public  String         password = "";
25         public  String         username = "user";
26         public  String         hostname = "localhost";
27
28         /* Public data */
29         public  State          state    = State.INIT;
30         public  String         name     = "";
31
32         /* Connection data */
33         private boolean        pinging;
34         private Socket         socket;
35         private BufferedReader input;
36         private PrintWriter    output;
37
38         /* Private data */
39         private int            mangle;
40
41         /* Public Methods */
42         public Client()
43         {
44                 Os.debug("Client: create");
45         }
46
47         public void setServer(String server, int port)
48         {
49                 this.server = server;
50                 this.port   = port;
51         }
52
53         public void setAuth(boolean usesasl, String authname, String password)
54         {
55                 this.usesasl  = usesasl;
56                 this.authname = authname;
57                 this.password = password;
58         }
59
60         public void setUser(String nickname, String channel)
61         {
62                 this.nickname = nickname;
63                 this.channel  = channel;
64                 this.name     = nickname;
65         }
66
67         public boolean connect()
68         {
69                 Os.debug("Client: connect");
70
71                 try {
72                         this.state  = State.SETUP;
73                         this.socket = new Socket();
74                         this.socket.setSoTimeout(this.timeout/2 * 1000);
75                         this.socket.connect(new InetSocketAddress(this.server, this.port));
76                         this.input  = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
77                         this.output = new PrintWriter(this.socket.getOutputStream());
78                 } catch (Exception e) {
79                         Os.debug("Client: failed to create connection: " + e);
80                         return false;
81                 }
82
83                 Os.debug("Client: connected");
84                 if (this.usesasl)
85                         this.raw("CAP REQ :sasl");
86                 this.raw("USER "+this.username+" "+this.hostname+" "+this.server+" :"+this.name);
87                 this.raw("NICK "+this.name);
88
89                 return true;
90         }
91
92         public void abort()
93         {
94                 Os.debug("Client: abort");
95                 this.state = State.ABORT;
96                 this.validate();
97         }
98
99         public void reset()
100         {
101                 Os.debug("Client: reset");
102                 this.state = State.INIT;
103         }
104
105         public void raw(String line)
106         {
107                 try {
108                         if (this.validate() != State.SETUP &&
109                             this.validate() != State.READY)
110                                 return;
111                         Os.debug("< " + line);
112                         this.output.println(line);
113                         this.output.flush();
114                 } catch (Exception e) {
115                         Os.debug("Client: error writing line", e);
116                 }
117         }
118
119         public Message send(String dst, String txt)
120         {
121                 if (txt == null || txt.length() == 0)
122                         return null;
123                 if (this.validate() != State.READY)
124                         return null;
125                 Message msg = new Message(dst, this.name, txt);
126                 if (msg.type == Message.Type.JOIN)
127                         this.channel = msg.msg;
128                 this.raw(msg.line);
129                 return msg;
130         }
131
132         public Message send(String txt)
133         {
134                 return this.send(this.channel, txt);
135         }
136
137         public Message recv()
138         {
139                 while (true) try {
140                         if (this.validate() != State.SETUP &&
141                             this.validate() != State.READY)
142                                 return null;
143                         String line = this.input.readLine();
144                         if (line == null)
145                                 return null;
146                         Os.debug("> " + line);
147                         Message msg = new Message(line, this.name);
148                         this.process(msg);
149                         if (this.usesasl)
150                                 this.dosasl(msg);
151                         if (!msg.cmd.equals("PING") &&
152                             !msg.cmd.equals("PONG"))
153                                 return msg;
154                 }
155                 catch (SocketTimeoutException e) {
156                         if (this.pinging) {
157                                 this.abort();
158                                 return null;
159                         } else {
160                                 this.pinging = true;
161                                 this.raw("PING :" + hostname);
162                                 continue;
163                         }
164                 }
165                 catch (SocketException e) {
166                         this.state = State.INIT;
167                         return null;
168                 }
169                 catch (Exception e) {
170                         this.state = State.INIT;
171                         Os.debug("Client: error in recv", e);
172                         return null;
173                 }
174         }
175
176         /* Private methods */
177         private State validate()
178         {
179                 try {
180                         if (this.state == State.ABORT) {
181                                 if (this.socket != null) {
182                                         this.socket.close();
183                                         this.state = State.INIT;
184                                 }
185                         }
186                 } catch (Exception e) {
187                         Os.debug("Client: error closing socket", e);
188                 }
189                 return this.state;
190         }
191
192         private void process(Message msg)
193         {
194                 if (msg.cmd.equals("001") && msg.msg.matches("Welcome.*")) {
195                         this.raw("JOIN "  + this.channel);
196                         this.raw("TOPIC " + this.channel);
197                         this.state = State.READY;
198                 }
199                 if (msg.cmd.equals("433")) {
200                         this.name   = this.nickname + this.mangle;
201                         this.mangle = this.mangle + 11;
202                         this.raw("NICK "  + this.name);
203                 }
204                 if (msg.cmd.equals("PING")) {
205                         this.raw("PONG " + msg.msg);
206                 }
207                 if (msg.cmd.equals("PONG")) {
208                         this.pinging = false;
209                 }
210         }
211
212         private void dosasl(Message msg)
213         {
214                 switch (msg.type) {
215                         case CAP:
216                                 if (msg.msg.equals("sasl") && msg.arg.equals("ACK")) {
217                                         Os.debug("Client: sasl - starting auth");
218                                         this.raw("AUTHENTICATE PLAIN");
219                                 } else {
220                                         Os.debug("Client: sasl - Server does not support sasl");
221                                 }
222                                 break;
223                         case AUTH:
224                                 if (msg.arg.equals("+")) {
225                                         Os.debug("Client: sasl - performin authentication");
226                                         this.raw("AUTHENTICATE " + Os.base64(
227                                                                 this.authname + "\0" +
228                                                                 this.authname + "\0" +
229                                                                 this.password));
230                                 } else {
231                                         Os.debug("Client: sasl - unexpected authenticate response");
232                                 }
233                                 break;
234                         case AUTHOK:
235                                 Os.debug("Client: SASL Auth Successful");
236                                 this.raw("CAP END");
237                                 break;
238                         case AUTHFAIL:
239                                 Os.debug("Client: SASL Auth Failed");
240                                 this.raw("CAP END");
241                                 break;
242                 }
243         }
244 }