f@0
|
1 /*
|
f@0
|
2 CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
|
f@0
|
3
|
f@0
|
4 Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
|
f@0
|
5
|
f@0
|
6 This program is free software: you can redistribute it and/or modify
|
f@0
|
7 it under the terms of the GNU General Public License as published by
|
f@0
|
8 the Free Software Foundation, either version 3 of the License, or
|
f@0
|
9 (at your option) any later version.
|
f@0
|
10
|
f@0
|
11 This program is distributed in the hope that it will be useful,
|
f@0
|
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
|
f@0
|
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
f@0
|
14 GNU General Public License for more details.
|
f@0
|
15
|
f@0
|
16 You should have received a copy of the GNU General Public License
|
f@0
|
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
|
f@0
|
18 */
|
f@0
|
19
|
f@0
|
20 package uk.ac.qmul.eecs.ccmi.network;
|
f@0
|
21
|
f@0
|
22 import java.io.IOException;
|
f@0
|
23 import java.net.SocketException;
|
f@0
|
24 import java.nio.ByteBuffer;
|
f@0
|
25 import java.nio.channels.SocketChannel;
|
f@0
|
26 import java.util.ResourceBundle;
|
f@0
|
27
|
f@0
|
28 import uk.ac.qmul.eecs.ccmi.gui.DiagramEventSource;
|
f@0
|
29
|
f@0
|
30 import de.sciss.net.OSCBundle;
|
f@0
|
31 import de.sciss.net.OSCMessage;
|
f@0
|
32
|
f@0
|
33 /*
|
f@0
|
34 * An implementation of the Protocol interface which uses OSC messages
|
f@0
|
35 * streamed on a TCP connection.
|
f@0
|
36 *
|
f@0
|
37 */
|
f@0
|
38 class OscProtocol implements Protocol {
|
f@0
|
39
|
f@0
|
40 OscProtocol(){
|
f@0
|
41 buffer = ByteBuffer.allocate(DEFAULT_CAPACITY);
|
f@0
|
42 codec = new CCmIOSCPacketCodec();
|
f@0
|
43 }
|
f@0
|
44
|
f@0
|
45 @Override
|
f@0
|
46 public void send(SocketChannel channel, Command cmd) throws IOException {
|
f@0
|
47 /* OSC message args = [diagram, cmd.arg1, cmd.arg2, amd.arg2, ... , cmd.argN ] */
|
f@0
|
48 bundle = new CCmIOSCBundle(cmd.getTimestamp());
|
f@0
|
49 Object[] args = new Object[2+cmd.getArgNum()];
|
f@0
|
50 args[0] = cmd.getDiagram();
|
f@0
|
51 args[1] = cmd.getSource().toString();
|
f@0
|
52 for(int i=0; i<cmd.getArgNum();i++)
|
f@0
|
53 args[i+2] = cmd.getArgAt(i);
|
f@0
|
54
|
f@0
|
55 bundle.addPacket(new OSCMessage(OSC_NAME_PREFIX+cmd.getName().toString(),args));
|
f@0
|
56 try{
|
f@0
|
57 writeBundle(channel);
|
f@0
|
58 }catch(IOException u){
|
f@0
|
59 throw new IOException(
|
f@0
|
60 /* give a more user friendly message */
|
f@0
|
61 ResourceBundle.getBundle(Server.class.getName()).getString("dialog.error.no_send"),u);
|
f@0
|
62 }
|
f@0
|
63 }
|
f@0
|
64
|
f@0
|
65 @Override
|
f@0
|
66 public void send(SocketChannel channel, Reply reply) throws IOException {
|
f@0
|
67 /* OSC message args = [messageLen, diagram, message ] */
|
f@0
|
68 bundle = new CCmIOSCBundle(reply.getTimestamp());
|
f@0
|
69 bundle.addPacket(new OSCMessage(
|
f@0
|
70 OSC_NAME_PREFIX+reply.getName().toString(),
|
f@0
|
71 new Object[] {reply.getMessageLen(), reply.getDiagram() ,reply.getMessage(),reply.getSource().toString()}
|
f@0
|
72 ));
|
f@0
|
73 try{
|
f@0
|
74 writeBundle(channel);
|
f@0
|
75 }catch(IOException u){
|
f@0
|
76 throw new IOException(
|
f@0
|
77 /* give a more user friendly message */
|
f@0
|
78 ResourceBundle.getBundle(Server.class.getName()).getString("dialog.error.no_send"),u);
|
f@0
|
79 }
|
f@0
|
80 }
|
f@0
|
81
|
f@0
|
82 @Override
|
f@0
|
83 public void send(SocketChannel channel, LockMessage lockMessage) throws IOException {
|
f@0
|
84 /* OSC message args = [diagram, source, lock.arg1, lock.arg2, lock.arg2, ... , lock.argN ] */
|
f@0
|
85 bundle = new CCmIOSCBundle(lockMessage.getTimestamp());
|
f@0
|
86 Object[] args = new Object[2+lockMessage.getArgNum()];
|
f@0
|
87 args[0] = lockMessage.getDiagram();
|
f@0
|
88 args[1] = lockMessage.getSource().toString();
|
f@0
|
89 for(int i=0; i<lockMessage.getArgNum();i++)
|
f@0
|
90 args[i+2] = lockMessage.getArgAt(i);
|
f@0
|
91
|
f@0
|
92 bundle.addPacket(new OSCMessage(
|
f@0
|
93 OSC_NAME_PREFIX+lockMessage.getName().toString(),
|
f@0
|
94 args));
|
f@0
|
95 try{
|
f@0
|
96 writeBundle(channel);
|
f@0
|
97 }catch(IOException u){
|
f@0
|
98 throw new IOException(
|
f@0
|
99 /* give a more user friendly message */
|
f@0
|
100 ResourceBundle.getBundle(Server.class.getName()).getString("dialog.error.no_send"),u);
|
f@0
|
101 }
|
f@0
|
102 }
|
f@0
|
103
|
f@0
|
104 @Override
|
f@0
|
105 public void send(SocketChannel channel, AwarenessMessage awareMsg) throws IOException {
|
f@0
|
106 /* OSC message args = [diagram name, siagram event action source]*/
|
f@0
|
107 bundle = new CCmIOSCBundle(awareMsg.getTimestamp());
|
f@0
|
108 Object[] args = new Object[2];
|
f@0
|
109 args[0] = awareMsg.getDiagram();
|
f@0
|
110 args[1] = awareMsg.getSource().toString();
|
f@0
|
111
|
f@0
|
112 bundle.addPacket(new OSCMessage(
|
f@0
|
113 OSC_NAME_PREFIX+awareMsg.getName().toString(),
|
f@0
|
114 args));
|
f@0
|
115 try{
|
f@0
|
116 writeBundle(channel);
|
f@0
|
117 }catch(IOException u){
|
f@0
|
118 throw new IOException(
|
f@0
|
119 /* give a more user friendly message */
|
f@0
|
120 ResourceBundle.getBundle(Server.class.getName()).getString("dialog.error.no_send"),u);
|
f@0
|
121 }
|
f@0
|
122 }
|
f@0
|
123
|
f@0
|
124 // @Override
|
f@0
|
125 // public Command receiveCommand(SocketChannel channel) throws IOException {
|
f@0
|
126 // OSCBundle bundle = readBundle(channel);
|
f@0
|
127 // OSCMessage oscMessage = (OSCMessage)bundle.getPacket(0);
|
f@0
|
128 // String name = oscMessage.getName();
|
f@0
|
129 // assert(name.startsWith(""+OSC_NAME_PREFIX));
|
f@0
|
130 // name = name.substring(1); // chop off the trailing '/'
|
f@0
|
131 // Object args[] = new Object[oscMessage.getArgCount()-CMD_OFFSET];
|
f@0
|
132 // for(int i=0; i< args.length;i++)
|
f@0
|
133 // args[i] = oscMessage.getArg(i+CMD_OFFSET);
|
f@0
|
134 // return new Command(
|
f@0
|
135 // Command.valueOf(name),
|
f@0
|
136 // (String)oscMessage.getArg(CMD_DIAGRAM_INDEX),
|
f@0
|
137 // args,
|
f@0
|
138 // bundle.getTimeTag(),
|
f@0
|
139 // DiagramEventSource.valueOf((String)oscMessage.getArg(CMD_SOURCE_INDEX))
|
f@0
|
140 // );
|
f@0
|
141 // }
|
f@0
|
142
|
f@0
|
143 @Override
|
f@0
|
144 public Reply receiveReply(SocketChannel channel) throws IOException {
|
f@0
|
145 OSCBundle bundle = readBundle(channel);
|
f@0
|
146 OSCMessage oscMessage = (OSCMessage)bundle.getPacket(0);
|
f@0
|
147 String name = oscMessage.getName();
|
f@0
|
148 assert(name.startsWith(""+OSC_NAME_PREFIX));
|
f@0
|
149 name = name.substring(1); // chop off the trailing '/'
|
f@0
|
150 @SuppressWarnings("unused")
|
f@0
|
151 Integer len = (Integer)oscMessage.getArg(REPLY_LEN_INDEX); // of no use at the moment
|
f@0
|
152 return new Reply(
|
f@0
|
153 Reply.valueOf(name),
|
f@0
|
154 (String)oscMessage.getArg(REPLY_DIAGRAM_INDEX),
|
f@0
|
155 (String)oscMessage.getArg(REPLY_MESSAGE_INDEX),
|
f@0
|
156 bundle.getTimeTag(),
|
f@0
|
157 DiagramEventSource.valueOf((String)oscMessage.getArg(REPLY_SOURCE_INDEX)));
|
f@0
|
158 }
|
f@0
|
159
|
f@0
|
160 @Override
|
f@0
|
161 public LockMessage receiveLockMessage(SocketChannel channel) throws IOException {
|
f@0
|
162 OSCBundle bundle = readBundle(channel);
|
f@0
|
163 OSCMessage oscMessage = (OSCMessage)bundle.getPacket(0);
|
f@0
|
164 String name = oscMessage.getName();
|
f@0
|
165 name = name.substring(1); // chop off the trailing '/'
|
f@0
|
166 Object args[] = new Object[oscMessage.getArgCount()-LOCK_OFFSET];
|
f@0
|
167 for(int i=0; i< args.length;i++)
|
f@0
|
168 args[i] = oscMessage.getArg(i+LOCK_OFFSET);
|
f@0
|
169 return new LockMessage(
|
f@0
|
170 LockMessage.valueOf(name),
|
f@0
|
171 bundle.getTimeTag(),
|
f@0
|
172 (String)oscMessage.getArg(LOCK_DIAGRAM_INDEX),
|
f@0
|
173 args,
|
f@0
|
174 DiagramEventActionSource.valueOf((String)oscMessage.getArg(LOCK_SOURCE_INDEX))
|
f@0
|
175 );
|
f@0
|
176 }
|
f@0
|
177
|
f@0
|
178 public Message receiveMessage(SocketChannel channel) throws IOException {
|
f@0
|
179 OSCBundle bundle = readBundle(channel);
|
f@0
|
180 OSCMessage oscMessage = (OSCMessage)bundle.getPacket(0);
|
f@0
|
181 String name = oscMessage.getName();
|
f@0
|
182 assert(name.startsWith(""+OSC_NAME_PREFIX));
|
f@0
|
183 name = name.substring(1); // chop off the trailing '/'
|
f@0
|
184 if(name.endsWith(Reply.NAME_POSTFIX)){ // it's a reply
|
f@0
|
185 @SuppressWarnings("unused")
|
f@0
|
186 Integer len = (Integer)oscMessage.getArg(REPLY_LEN_INDEX); // of no use at the moment
|
f@0
|
187 Reply reply = new Reply(
|
f@0
|
188 Reply.valueOf(name),
|
f@0
|
189 (String)oscMessage.getArg(REPLY_DIAGRAM_INDEX),
|
f@0
|
190 (String)oscMessage.getArg(REPLY_MESSAGE_INDEX),
|
f@0
|
191 bundle.getTimeTag(),
|
f@0
|
192 DiagramEventSource.valueOf((String)oscMessage.getArg(REPLY_SOURCE_INDEX)));
|
f@0
|
193 return reply;
|
f@0
|
194 }else if (name.endsWith(LockMessage.NAME_POSTFIX)){
|
f@0
|
195 Object args[] = new Object[oscMessage.getArgCount()-LOCK_OFFSET];
|
f@0
|
196 for(int i=0; i< args.length;i++)
|
f@0
|
197 args[i] = oscMessage.getArg(i+LOCK_OFFSET);
|
f@0
|
198 return new LockMessage(
|
f@0
|
199 LockMessage.valueOf(name),
|
f@0
|
200 bundle.getTimeTag(),
|
f@0
|
201 (String)oscMessage.getArg(LOCK_DIAGRAM_INDEX),
|
f@0
|
202 args,
|
f@0
|
203 DiagramEventActionSource.valueOf((String)oscMessage.getArg(LOCK_SOURCE_INDEX))
|
f@0
|
204 );
|
f@0
|
205 }else if(name.endsWith(AwarenessMessage.NAME_POSTFIX)){ // it's an awareness message
|
f@0
|
206 AwarenessMessage.Name awName = AwarenessMessage.valueOf(name);
|
f@0
|
207 if(awName == AwarenessMessage.Name.USERNAME_A || awName == AwarenessMessage.Name.ERROR_A){
|
f@0
|
208 return new AwarenessMessage(awName,
|
f@0
|
209 (String)oscMessage.getArg(AWAR_DIAGRAM_INDEX),
|
f@0
|
210 (String)oscMessage.getArg(AWAR_SOURCE_INDEX)
|
f@0
|
211 );
|
f@0
|
212 }else {
|
f@0
|
213 return new AwarenessMessage(awName,
|
f@0
|
214 (String)oscMessage.getArg(AWAR_DIAGRAM_INDEX),
|
f@0
|
215 DiagramEventActionSource.valueOf((String)oscMessage.getArg(AWAR_SOURCE_INDEX))
|
f@0
|
216 );
|
f@0
|
217 }
|
f@0
|
218 }else{ // it's a command
|
f@0
|
219 Object args[] = new Object[oscMessage.getArgCount()-CMD_OFFSET];
|
f@0
|
220 for(int i=0; i< args.length;i++)
|
f@0
|
221 args[i] = oscMessage.getArg(i+CMD_OFFSET);
|
f@0
|
222 return new Command(
|
f@0
|
223 Command.valueOf(name),
|
f@0
|
224 (String)oscMessage.getArg(CMD_DIAGRAM_INDEX),
|
f@0
|
225 args,
|
f@0
|
226 bundle.getTimeTag(),
|
f@0
|
227 DiagramEventSource.valueOf((String)oscMessage.getArg(CMD_SOURCE_INDEX))
|
f@0
|
228 );
|
f@0
|
229 }
|
f@0
|
230 }
|
f@0
|
231
|
f@0
|
232 private OSCBundle readBundle(SocketChannel channel) throws IOException{
|
f@0
|
233 /* read the size of the OSC packet, first 4 bytes according to OSC specs */
|
f@0
|
234 buffer.rewind().limit(4);
|
f@0
|
235 while( buffer.hasRemaining() )
|
f@0
|
236 if( channel.read( buffer ) == -1 )
|
f@0
|
237 throw new SocketException(ResourceBundle.getBundle(Server.class.getName()).getString("error.connection_close"));
|
f@0
|
238
|
f@0
|
239 buffer.rewind();
|
f@0
|
240 int packetSize = buffer.getInt();
|
f@0
|
241 assert(packetSize > 0 );
|
f@0
|
242 ByteBuffer b = buffer;
|
f@0
|
243 /* if the packet is very big we must allocate an ad hoc temporary big big buffer */
|
f@0
|
244 if(packetSize <= DEFAULT_CAPACITY)
|
f@0
|
245 b.rewind().limit(packetSize);
|
f@0
|
246 else
|
f@0
|
247 b = ByteBuffer.allocate(packetSize);
|
f@0
|
248 /* read the packet, it must be a bundle containing only one message */
|
f@0
|
249 while( b.hasRemaining() )
|
f@0
|
250 if( channel.read( b ) == -1 )
|
f@0
|
251 throw new SocketException(ResourceBundle.getBundle(Server.class.getName()).getString("error.connection_close"));
|
f@0
|
252 b.rewind();
|
f@0
|
253 return (OSCBundle)codec.decode(b);
|
f@0
|
254 }
|
f@0
|
255
|
f@0
|
256 private void writeBundle(SocketChannel channel) throws IOException{
|
f@0
|
257 ByteBuffer b = buffer;
|
f@0
|
258 buffer.clear();
|
f@0
|
259 if(bundle.getSize() + 4 > DEFAULT_CAPACITY){
|
f@0
|
260 b = ByteBuffer.allocate(bundle.getSize() + 4);
|
f@0
|
261 }
|
f@0
|
262 b.position(4);
|
f@0
|
263 bundle.encode(codec,b);
|
f@0
|
264 int len = b.position() - 4;
|
f@0
|
265 b.putInt(0, len);
|
f@0
|
266 b.flip();
|
f@0
|
267 channel.write(b);
|
f@0
|
268 }
|
f@0
|
269
|
f@0
|
270 ByteBuffer buffer;
|
f@0
|
271 CCmIOSCBundle bundle;
|
f@0
|
272 CCmIOSCPacketCodec codec;
|
f@0
|
273
|
f@0
|
274 private static final int DEFAULT_CAPACITY = 1024;
|
f@0
|
275 private static final char OSC_NAME_PREFIX = '/';
|
f@0
|
276
|
f@0
|
277
|
f@0
|
278 private static final int REPLY_LEN_INDEX = 0;
|
f@0
|
279 /* position of the diagram Name in the OSC message */
|
f@0
|
280 private static final int REPLY_DIAGRAM_INDEX = 1;
|
f@0
|
281 private static final int REPLY_SOURCE_INDEX = 3;
|
f@0
|
282 private static final int CMD_DIAGRAM_INDEX = 0;
|
f@0
|
283 private static final int CMD_SOURCE_INDEX = 1;
|
f@0
|
284 private static final int CMD_OFFSET = 2;
|
f@0
|
285 private static final int LOCK_DIAGRAM_INDEX = 0;
|
f@0
|
286 private static final int LOCK_SOURCE_INDEX = 1;
|
f@0
|
287 private static final int LOCK_OFFSET = 2;
|
f@0
|
288 private static final int AWAR_DIAGRAM_INDEX = 0;
|
f@0
|
289 private static final int AWAR_SOURCE_INDEX = 1;
|
f@0
|
290 /* ------------------------------------------------*/
|
f@0
|
291 private static final int REPLY_MESSAGE_INDEX = 2;
|
f@0
|
292 }
|