jvcard remote support (initial commit, not ready for use yet)
[jvcard.git] / src / be / nikiroo / jvcard / remote / SimpleSocket.java
1 package be.nikiroo.jvcard.remote;
2
3 import java.io.BufferedReader;
4 import java.io.IOException;
5 import java.io.InputStreamReader;
6 import java.io.PrintWriter;
7 import java.net.InetAddress;
8 import java.net.Socket;
9 import java.util.Arrays;
10 import java.util.LinkedList;
11 import java.util.List;
12
13 import be.nikiroo.jvcard.remote.Command.Verb;
14
15 /**
16 * A client or server connection, that will allow you to connect to, send and
17 * receive data to/from a jVCard remote server.
18 *
19 *
20 *
21 * @author niki
22 */
23 public class SimpleSocket {
24 /**
25 * The current version of the network protocol.
26 */
27 static private final int CURRENT_VERSION = 1;
28
29 /**
30 * The end of block marker.
31 *
32 * An end of block marker needs to be on a line on itself to be valid, and
33 * will denote the end of a block of data.
34 */
35 static private String EOB = ".";
36
37 private Socket s;
38 private PrintWriter out;
39 private BufferedReader in;
40 private int version; // version of the OTHER end, not this one (this one is
41 // CURRENT_VERSION obviously)
42
43 private String label; // can be used for debugging
44
45 /**
46 * Create a new {@link SimpleSocket} with the given {@link Socket}.
47 *
48 * @param s
49 * the {@link Socket}
50 */
51 public SimpleSocket(Socket s, String label) {
52 this.s = s;
53 this.label = label;
54 }
55
56 /**
57 * Return the label of this {@link SimpleSocket}. This is mainly used for
58 * debugging purposes or user display if any. It is optional.
59 *
60 * @return the label
61 */
62 public String getLabel() {
63 if (label == null)
64 return "[no name]";
65 return label;
66 }
67
68 /**
69 * Open the {@link SimpleSocket} for reading/writing and negotiates the
70 * version.
71 *
72 * Note that you <b>MUST</b> call {@link SimpleSocket#close()} when you are
73 * done to release the acquired resources.
74 *
75 * @param client
76 * TRUE for clients, FALSE for servers (server speaks first)
77 *
78 * @throws IOException
79 * in case of IO error
80 */
81 public void open(boolean client) throws IOException {
82 out = new PrintWriter(s.getOutputStream(), false);
83 in = new BufferedReader(new InputStreamReader(s.getInputStream()));
84
85 if (client) {
86 version = new Command(receiveLine(), -1).getVersion();
87 sendLine(new Command(Command.Verb.VERSION, CURRENT_VERSION)
88 .toString());
89 } else {
90 send(new Command(Command.Verb.VERSION, CURRENT_VERSION).toString());
91 // TODO: i18n
92 send("[Some help info here]");
93 send("you need to reply with your VERSION + end of block");
94 send("please send HELP in a full block or help");
95 sendBlock();
96 version = new Command(receiveLine(), -1).getVersion();
97 }
98 }
99
100 /**
101 * Close the connection and release acquired resources.
102 *
103 * @return TRUE if everything was closed properly, FALSE if the connection
104 * was broken (in all cases, resources are released)
105 */
106 public boolean close() {
107 boolean broken = false;
108
109 try {
110 sendBlock();
111 broken = out.checkError();
112 } catch (IOException e) {
113 broken = true;
114 }
115
116 try {
117 s.close();
118 } catch (IOException e) {
119 e.printStackTrace();
120 broken = true;
121 try {
122 in.close();
123 } catch (IOException ee) {
124 }
125 out.close();
126 }
127
128 s = null;
129 in = null;
130 out = null;
131
132 return !broken;
133 }
134
135 /**
136 * Sends commands to the remote server. Do <b>NOT</b> sends the end-of-block
137 * marker.
138 *
139 * @param data
140 * the data to send
141 *
142 * @throws IOException
143 * in case of IO error
144 */
145 protected void send(String data) throws IOException {
146 if (data != null) {
147 out.write(data);
148 }
149
150 out.write("\n");
151
152 if (out.checkError())
153 throw new IOException();
154 }
155
156 /**
157 * Sends an end-of-block marker to the remote server.
158 *
159 * @throws IOException
160 * in case of IO error
161 */
162 public void sendBlock() throws IOException {
163 sendBlock((List<String>) null);
164 }
165
166 /**
167 * Sends commands to the remote server, then sends an end-of-block marker.
168 *
169 * @param data
170 * the data to send
171 *
172 * @throws IOException
173 * in case of IO error
174 */
175 public void sendLine(String data) throws IOException {
176 sendBlock(Arrays.asList(new String[] { data }));
177 }
178
179 /**
180 * Sends commands to the remote server, then sends an end-of-block marker.
181 *
182 * @param data
183 * the data to send
184 *
185 * @throws IOException
186 * in case of IO error
187 */
188 public void sendBlock(List<String> data) throws IOException {
189 if (data != null) {
190 for (String dataLine : data) {
191 send(dataLine);
192 }
193 }
194
195 send(EOB);
196 }
197
198 /**
199 * Sends commands to the remote server, then sends an end-of-block marker.
200 *
201 * @param verb
202 * the {@link Verb} to send
203 *
204 * @throws IOException
205 * in case of IO error
206 */
207 public void sendCommand(Command.Verb verb) throws IOException {
208 sendCommand(verb, null);
209 }
210
211 /**
212 * Sends commands to the remote server, then sends an end-of-block marker.
213 *
214 * @param verb
215 * the data to send
216 *
217 * @param param
218 * the parameter for this command if any
219 *
220 * @throws IOException
221 * in case of IO error
222 */
223 public void sendCommand(Command.Verb verb, String param) throws IOException {
224 sendLine(new Command(verb, param, CURRENT_VERSION).toString());
225 }
226
227 /**
228 * Read a line from the remote server.
229 *
230 * Do <b>NOT</b> read until the end-of-block marker, and can return said
231 * block without conversion.
232 *
233 * @return the read line
234 *
235 * @throws IOException
236 * in case of IO error
237 */
238 protected String receive() throws IOException {
239 String line = in.readLine();
240 return line;
241 }
242
243 /**
244 * Read lines from the remote server until the end-of-block ("\0\n") marker
245 * is detected.
246 *
247 * @return the read lines without the end marker, or NULL if nothing more to
248 * read
249 *
250 * @throws IOException
251 * in case of IO error
252 */
253 public List<String> receiveBlock() throws IOException {
254 List<String> result = new LinkedList<String>();
255
256 String line = receive();
257 for (; line != null && !line.equals(EOB); line = receive()) {
258 result.add(line);
259 }
260
261 if (line == null)
262 return null;
263
264 return result;
265 }
266
267 /**
268 * Read a line from the remote server then read until the next end-of-block
269 * marker.
270 *
271 * @return the parsed line, or NULL if nothing more to read
272 *
273 * @throws IOException
274 * in case of IO error
275 */
276 public String receiveLine() throws IOException {
277 List<String> lines = receiveBlock();
278
279 if (lines.size() > 0)
280 return lines.get(0);
281
282 return null;
283 }
284
285 /**
286 * Read a line from the remote server and convert it to a {@link Command},
287 * then read until the next end-of-block marker.
288 *
289 * @return the parsed {@link Command}
290 *
291 * @throws IOException
292 * in case of IO error
293 */
294 public Command receiveCommand() throws IOException {
295 String line = receive();
296 Command cmd = new Command(line, version);
297 receiveBlock();
298 return cmd;
299 }
300
301 @Override
302 public String toString() {
303 String source = "[not connected]";
304 InetAddress iadr = s.getInetAddress();
305 if (iadr != null)
306 source = iadr.getHostName();
307
308 return getLabel() + " (" + source + ")";
309 }
310 }