Commit | Line | Data |
---|---|---|
a046fa49 NR |
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 | } |