View Javadoc

1   /**
2    *  BlueCove - Java library for Bluetooth
3    *  Copyright (C) 2007-2008 Vlad Skarzhevskyy
4    *
5    *  Licensed to the Apache Software Foundation (ASF) under one
6    *  or more contributor license agreements.  See the NOTICE file
7    *  distributed with this work for additional information
8    *  regarding copyright ownership.  The ASF licenses this file
9    *  to you under the Apache License, Version 2.0 (the
10   *  "License"); you may not use this file except in compliance
11   *  with the License.  You may obtain a copy of the License at
12   *
13   *    http://www.apache.org/licenses/LICENSE-2.0
14   *
15   *  Unless required by applicable law or agreed to in writing,
16   *  software distributed under the License is distributed on an
17   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18   *  KIND, either express or implied.  See the License for the
19   *  specific language governing permissions and limitations
20   *  under the License.
21   *
22   *  @author vlads
23   *  @version $Id: OBEXServer.java 2478 2008-12-03 04:57:15Z skarzhevskyy $
24   */
25  package net.sf.bluecove.obex.server;
26  
27  import java.io.File;
28  import java.io.FileOutputStream;
29  import java.io.IOException;
30  import java.io.InputStream;
31  import java.io.InterruptedIOException;
32  import java.util.Timer;
33  import java.util.TimerTask;
34  
35  import javax.bluetooth.DataElement;
36  import javax.bluetooth.DiscoveryAgent;
37  import javax.bluetooth.LocalDevice;
38  import javax.bluetooth.ServiceRecord;
39  import javax.bluetooth.UUID;
40  import javax.microedition.io.Connection;
41  import javax.microedition.io.Connector;
42  import javax.obex.HeaderSet;
43  import javax.obex.Operation;
44  import javax.obex.ResponseCodes;
45  import javax.obex.ServerRequestHandler;
46  import javax.obex.SessionNotifier;
47  
48  /**
49   * 
50   */
51  public class OBEXServer implements Runnable {
52  
53  	private SessionNotifier serverConnection;
54  
55  	private boolean isStoped = false;
56  
57  	private boolean isRunning = false;
58  
59  	public final UUID OBEX_OBJECT_PUSH = new UUID(0x1105);
60  
61  	public static final String SERVER_NAME = "OBEX Object Push";
62  
63  	private UserInteraction interaction;
64  
65  	private OBEXServer(UserInteraction interaction) {
66  		this.interaction = interaction;
67  	}
68  
69  	public static OBEXServer startServer(UserInteraction interaction) {
70  		OBEXServer srv = new OBEXServer(interaction);
71  		Thread thread = new Thread(srv);
72  		thread.start();
73  		while (!srv.isRunning && !srv.isStoped) {
74  			try {
75  				Thread.sleep(100);
76  			} catch (InterruptedException e) {
77  				throw new Error(e);
78  			}
79  		}
80  		if (!srv.isRunning) {
81  			throw new Error("Can't start server");
82  		}
83  		return srv;
84  	}
85  
86  	/*
87  	 * (non-Javadoc)
88  	 * 
89  	 * @see java.lang.Runnable#run()
90  	 */
91  	public void run() {
92  		isStoped = false;
93  		LocalDevice localDevice;
94  		try {
95  			localDevice = LocalDevice.getLocalDevice();
96  			if (!localDevice.setDiscoverable(DiscoveryAgent.GIAC)) {
97  				Logger.error("Fail to set LocalDevice Discoverable");
98  			}
99  			serverConnection = (SessionNotifier) Connector.open("btgoep://localhost:" + OBEX_OBJECT_PUSH + ";name="
100 					+ SERVER_NAME);
101 		} catch (Throwable e) {
102 			Logger.error("OBEX Server start error", e);
103 			isStoped = true;
104 			return;
105 		}
106 
107 		try {
108 			ServiceRecord record = localDevice.getRecord(serverConnection);
109 			String url = record.getConnectionURL(ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
110 			Logger.debug("BT server url: " + url);
111             
112 			final int OBJECT_TRANSFER_SERVICE = 0x100000;
113 
114 			try {
115 				record.setDeviceServiceClasses(OBJECT_TRANSFER_SERVICE);
116 			} catch (Throwable e) {
117 				Logger.debug("setDeviceServiceClasses", e);
118 			}
119 
120 			DataElement bluetoothProfileDescriptorList = new DataElement(DataElement.DATSEQ);
121 			DataElement obbexPushProfileDescriptor = new DataElement(DataElement.DATSEQ);
122 			obbexPushProfileDescriptor.addElement(new DataElement(DataElement.UUID, OBEX_OBJECT_PUSH));
123 			obbexPushProfileDescriptor.addElement(new DataElement(DataElement.U_INT_2, 0x100));
124 			bluetoothProfileDescriptorList.addElement(obbexPushProfileDescriptor);
125 			record.setAttributeValue(0x0009, bluetoothProfileDescriptorList);
126 
127 			final short ATTR_SUPPORTED_FORMAT_LIST_LIST = 0x0303;
128 			DataElement supportedFormatList = new DataElement(DataElement.DATSEQ);
129 			// any type of object.
130 			supportedFormatList.addElement(new DataElement(DataElement.U_INT_1, 0xFF));
131 			record.setAttributeValue(ATTR_SUPPORTED_FORMAT_LIST_LIST, supportedFormatList);
132 
133 			final short UUID_PUBLICBROWSE_GROUP = 0x1002;
134 			final short ATTR_BROWSE_GRP_LIST = 0x0005;
135 			DataElement browseClassIDList = new DataElement(DataElement.DATSEQ);
136 			UUID browseClassUUID = new UUID(UUID_PUBLICBROWSE_GROUP);
137 			browseClassIDList.addElement(new DataElement(DataElement.UUID, browseClassUUID));
138 			record.setAttributeValue(ATTR_BROWSE_GRP_LIST, browseClassIDList);
139 
140 			localDevice.updateRecord(record);
141 		} catch (Throwable e) {
142 			Logger.error("Updating SDP", e);
143 		}
144 
145 		try {
146 			int errorCount = 0;
147 			int count = 0;
148 			isRunning = true;
149 			while (!isStoped) {
150 				RequestHandler handler = new RequestHandler();
151 				try {
152 					count++;
153 					Logger.debug("Accepting OBEX connections");
154 					handler.connectionAccepted(serverConnection.acceptAndOpen(handler));
155 				} catch (InterruptedIOException e) {
156 					isStoped = true;
157 					break;
158 				} catch (Throwable e) {
159 					if ("Stack closed".equals(e.getMessage())) {
160 						isStoped = true;
161 					}
162 					if (isStoped) {
163 						return;
164 					}
165 					errorCount++;
166 					Logger.error("acceptAndOpen ", e);
167 					continue;
168 				}
169 				errorCount = 0;
170 			}
171 		} finally {
172 			close();
173 			Logger.debug("OBEX Server finished!");
174 			isRunning = false;
175 		}
176 	}
177 
178 	public void close() {
179 		isStoped = true;
180 		try {
181 			if (serverConnection != null) {
182 				serverConnection.close();
183 			}
184 			Logger.debug("OBEX ServerConnection closed");
185 		} catch (Throwable e) {
186 			Logger.error("OBEX Server stop error", e);
187 		}
188 	}
189 
190 	private static File homePath() {
191 		String path = "bluetooth";
192 		boolean isWindows = false;
193 		String sysName = System.getProperty("os.name");
194 		if (sysName != null) {
195 			sysName = sysName.toLowerCase();
196 			if (sysName.indexOf("windows") != -1) {
197 				isWindows = true;
198 				path = "My Documents";
199 			}
200 		}
201 		File dir;
202 		try {
203 			dir = new File(System.getProperty("user.home"), path);
204 			if (!dir.exists()) {
205 				if (!dir.mkdirs()) {
206 					throw new SecurityException();
207 				}
208 			}
209 		} catch (SecurityException e) {
210 			dir = new File(new File(System.getProperty("java.io.tmpdir"), System.getProperty("user.name")), path);
211 		}
212 		if (isWindows) {
213 			dir = new File(dir, "Bluetooth Exchange Folder");
214 		}
215 		if (!dir.exists()) {
216 			if (!dir.mkdirs()) {
217 				return null;
218 			}
219 		} else if (!dir.isDirectory()) {
220 			dir.delete();
221 			if (!dir.mkdirs()) {
222 				return null;
223 			}
224 		}
225 		return dir;
226 	}
227 
228 	private void showStatus(final String message) {
229 		interaction.showStatus(message);
230 	}
231 
232 	private class RequestHandler extends ServerRequestHandler {
233 
234 		Timer notConnectedTimer = new Timer();
235 
236 		boolean isConnected = false;
237 
238 		boolean receivedOk = false;
239 
240 		Connection cconn;
241 
242 		void connectionAccepted(Connection cconn) {
243 			Logger.debug("Received OBEX connection");
244 			showStatus("Client connected");
245 			this.cconn = cconn;
246 			if (!isConnected) {
247 				notConnectedTimer.schedule(new TimerTask() {
248 					public void run() {
249 						notConnectedClose();
250 					}
251 				}, 1000 * 30);
252 			}
253 		}
254 
255 		void notConnectedClose() {
256 			if (!isConnected) {
257 				Logger.debug("OBEX connection timeout");
258 				try {
259 					cconn.close();
260 				} catch (IOException e) {
261 				}
262 				if (!receivedOk) {
263 					showStatus("Disconnected");
264 				}
265 			}
266 		}
267 
268 		public int onConnect(HeaderSet request, HeaderSet reply) {
269 			isConnected = true;
270 			notConnectedTimer.cancel();
271 			Logger.debug("OBEX onConnect");
272 			return ResponseCodes.OBEX_HTTP_OK;
273 		}
274 
275 		public void onDisconnect(HeaderSet request, HeaderSet reply) {
276 			Logger.debug("OBEX onDisconnect");
277 			if (!receivedOk) {
278 				showStatus("Disconnected");
279 			}
280 		}
281 
282 		public int onSetPath(HeaderSet request, HeaderSet reply, boolean backup, boolean create) {
283 			Logger.debug("OBEX onSetPath");
284 			return super.onSetPath(request, reply, backup, create);
285 		}
286 
287 		public int onDelete(HeaderSet request, HeaderSet reply) {
288 			Logger.debug("OBEX onDelete");
289 			return super.onDelete(request, reply);
290 		}
291 
292 		public int onPut(Operation op) {
293 			Logger.debug("OBEX onPut");
294 			try {
295 				HeaderSet hs = op.getReceivedHeaders();
296 				String name = (String) hs.getHeader(HeaderSet.NAME);
297 				if (name != null) {
298 					Logger.debug("name:" + name);
299 					showStatus("Receiving " + name);
300 				} else {
301 					name = "xxx.xx";
302 					showStatus("Receiving file");
303 				}
304 				Long len = (Long) hs.getHeader(HeaderSet.LENGTH);
305 				if (len != null) {
306 					Logger.debug("file lenght:" + len);
307 					interaction.setProgressValue(0);
308 					interaction.setProgressMaximum(len.intValue());
309 				}
310 				File f = new File(homePath(), name);
311 				FileOutputStream out = new FileOutputStream(f);
312 				InputStream is = op.openInputStream();
313 
314 				int received = 0;
315 
316 				while (!isStoped) {
317 					int data = is.read();
318 					if (data == -1) {
319 						Logger.debug("EOS received");
320 						break;
321 					}
322 					out.write(data);
323 					received++;
324 					if ((len != null) && (received % 100 == 0)) {
325 						interaction.setProgressValue(received);
326 					}
327 				}
328 				op.close();
329 				out.close();
330 				Logger.debug("file saved:" + f.getAbsolutePath());
331 				showStatus("Received " + name);
332 				receivedOk = true;
333 				return ResponseCodes.OBEX_HTTP_OK;
334 			} catch (IOException e) {
335 				Logger.error("OBEX Server onPut error", e);
336 				return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
337 			} finally {
338 				Logger.debug("OBEX onPut ends");
339 				interaction.setProgressDone();
340 			}
341 		}
342 
343 		public int onGet(Operation op) {
344 			Logger.debug("OBEX onGet");
345 			try {
346 				HeaderSet hs = op.getReceivedHeaders();
347 				String name = (String) hs.getHeader(HeaderSet.NAME);
348 
349 				return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
350 
351 			} catch (IOException e) {
352 				Logger.error("OBEX Server onGet error", e);
353 				return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
354 			} finally {
355 				Logger.debug("OBEX onGet ends");
356 			}
357 		}
358 
359 		public void onAuthenticationFailure(byte[] userName) {
360 			Logger.debug("OBEX AuthFailure " + new String(userName));
361 		}
362 
363 	}
364 }