source: osm/applications/editors/josm/plugins/geochat/src/geochat/ChatServerConnection.java@ 35160

Last change on this file since 35160 was 35160, checked in by donvip, 5 years ago

use lambdas for Runnable

File size: 17.1 KB
Line 
1// License: WTFPL. For details, see LICENSE file.
2package geochat;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.IOException;
7import java.io.UnsupportedEncodingException;
8import java.net.URLEncoder;
9import java.util.ArrayList;
10import java.util.Date;
11import java.util.HashMap;
12import java.util.HashSet;
13import java.util.List;
14import java.util.Map;
15import java.util.Set;
16
17import javax.json.JsonArray;
18import javax.json.JsonException;
19import javax.json.JsonObject;
20
21import org.openstreetmap.josm.data.coor.LatLon;
22import org.openstreetmap.josm.data.coor.conversion.DecimalDegreesCoordinateFormat;
23import org.openstreetmap.josm.data.projection.Projection;
24import org.openstreetmap.josm.data.projection.ProjectionRegistry;
25import org.openstreetmap.josm.gui.MainApplication;
26import org.openstreetmap.josm.gui.MapView;
27import org.openstreetmap.josm.spi.preferences.Config;
28import org.openstreetmap.josm.tools.Logging;
29
30/**
31 * This class holds all the chat data and periodically polls the server.
32 *
33 * @author zverik
34 */
35final class ChatServerConnection {
36 public static final String TOKEN_PREFIX = "=";
37 private static final String TOKEN_PATTERN = "^[a-zA-Z0-9]{10}$";
38
39 private int userId;
40 private String userName;
41 private static ChatServerConnection instance;
42 private Set<ChatServerConnectionListener> listeners;
43 private LogRequest requestThread;
44
45 private ChatServerConnection() {
46 userId = 0;
47 userName = null;
48 listeners = new HashSet<>();
49 requestThread = new LogRequest();
50 new Thread(requestThread).start();
51 }
52
53 public static ChatServerConnection getInstance() {
54 if (instance == null)
55 instance = new ChatServerConnection();
56 return instance;
57 }
58
59 public void addListener(ChatServerConnectionListener listener) {
60 listeners.add(listener);
61 }
62
63 public void removeListener(ChatServerConnectionListener listener) {
64 listeners.remove(listener);
65 }
66
67 public boolean isActive() {
68 return isLoggedIn() && getPosition() != null;
69 }
70
71 public boolean isLoggedIn() {
72 return userId > 0;
73 }
74
75 public String getUserName() {
76 return userName;
77 }
78
79 /**
80 * Test that userId is still active, log out otherwise.
81 */
82 public void checkLogin() {
83 autoLogin(null);
84 }
85
86 /**
87 * Test that userId is still active, if not, tries to login with given user name.
88 * Does not autologin, if userName is null, obviously.
89 */
90 public void autoLogin(final String userName) {
91 final int uid = Config.getPref().getInt("geochat.lastuid", 0);
92 if (uid <= 0) {
93 if (userName != null && userName.length() > 1)
94 login(userName);
95 } else {
96 String query = "whoami&uid=" + uid;
97 JsonQueryUtil.queryAsync(query, new JsonQueryCallback() {
98 @Override
99 public void processJson(JsonObject json) {
100 if (json != null && json.get("name") != null)
101 login(uid, json.getString("name"));
102 else if (userName != null && userName.length() > 1)
103 login(userName);
104 }
105 });
106 }
107 }
108
109 /**
110 * Waits until {@link #getPosition()} is not null, then calls {@link #autoLogin(java.lang.String)}.
111 * If two seconds have passed, stops the waiting. Doesn't wait if userName is empty.
112 */
113 public void autoLoginWithDelay(final String userName) {
114 if (userName == null || userName.length() == 0) {
115 checkLogin();
116 return;
117 }
118 new Thread(() -> {
119 try {
120 int cnt = 10;
121 while (getPosition() == null && cnt-- > 0) {
122 Thread.sleep(200);
123 }
124 } catch (InterruptedException e) {
125 Logging.warn(e);
126 }
127 autoLogin(userName);
128 }).start();
129 }
130
131 public void login(final String userName) {
132 if (userName == null)
133 throw new IllegalArgumentException("userName is null");
134 LatLon pos = getPosition();
135 if (pos == null) {
136 fireLoginFailed("Zoom level is too low");
137 return;
138 }
139 String token = userName.startsWith(TOKEN_PREFIX) ? userName.substring(TOKEN_PREFIX.length()) : null;
140 if (token != null && !token.matches(TOKEN_PATTERN)) {
141 fireLoginFailed("Incorrect token format");
142 return;
143 }
144
145 try {
146 String nameAttr = token != null ? "&token=" + token : "&name=" + URLEncoder.encode(userName, "UTF-8");
147 String query = "register&lat=" + DecimalDegreesCoordinateFormat.INSTANCE.latToString(pos)
148 + "&lon=" + DecimalDegreesCoordinateFormat.INSTANCE.lonToString(pos)
149 + nameAttr;
150 JsonQueryUtil.queryAsync(query, new JsonQueryCallback() {
151 @Override
152 public void processJson(JsonObject json) {
153 if (json == null)
154 fireLoginFailed(tr("Could not get server response, check logs"));
155 else if (json.get("error") != null)
156 fireLoginFailed(tr("Failed to login as {0}:", userName) + "\n" + json.getString("error"));
157 else if (json.get("uid") == null)
158 fireLoginFailed(tr("The server did not return user ID"));
159 else {
160 String name = json.get("name") != null ? json.getString("name") : userName;
161 login(json.getInt("uid"), name);
162 }
163 }
164 });
165 } catch (UnsupportedEncodingException e) {
166 Logging.error(e);
167 }
168 }
169
170 private void login(int userId, String userName) {
171 this.userId = userId;
172 this.userName = userName;
173 Config.getPref().putInt("geochat.lastuid", userId);
174 for (ChatServerConnectionListener listener : listeners) {
175 listener.loggedIn(userName);
176 }
177 }
178
179 private void logoutIntl() {
180 ChatServerConnection.this.userId = 0;
181 ChatServerConnection.this.userName = null;
182 Config.getPref().put("geochat.lastuid", null);
183 for (ChatServerConnectionListener listener : listeners) {
184 listener.notLoggedIn(null);
185 }
186 }
187
188 private void fireLoginFailed(String reason) {
189 for (ChatServerConnectionListener listener : listeners) {
190 listener.notLoggedIn(reason);
191 }
192 }
193
194 /**
195 * Unregister the current user.
196 */
197 public void logout() {
198 if (!isLoggedIn())
199 return;
200 String query = "logout&uid=" + userId;
201 JsonQueryUtil.queryAsync(query, new JsonQueryCallback() {
202 @Override
203 public void processJson(JsonObject json) {
204 if (json != null && json.get("message") != null) {
205 logoutIntl();
206 }
207 }
208 });
209 }
210
211 /**
212 * Unregister the current user and do not call listeners.
213 * Makes synchronous request to the server.
214 */
215 public void bruteLogout() throws IOException {
216 if (isLoggedIn())
217 JsonQueryUtil.query("logout&uid=" + userId);
218 }
219
220 private void fireMessageFailed(String reason) {
221 for (ChatServerConnectionListener listener : listeners) {
222 listener.messageSendFailed(reason);
223 }
224 }
225
226 /**
227 * Posts message to the main channel.
228 * @param message Message string.
229 * @see #postMessage(java.lang.String, java.lang.String)
230 */
231 public void postMessage(String message) {
232 postMessage(message, null);
233 }
234
235 /**
236 * Posts message to the main channel or to a specific user.
237 * Calls listener on fail.
238 * @param message Message string.
239 * @param targetUser null if sending to everyone, name of user otherwise.
240 */
241 public void postMessage(String message, String targetUser) {
242 if (!isLoggedIn()) {
243 fireMessageFailed("Not logged in");
244 return;
245 }
246 LatLon pos = getPosition();
247 if (pos == null) {
248 fireMessageFailed("Zoom level is too low");
249 return;
250 }
251 try {
252 String query = "post&lat=" + DecimalDegreesCoordinateFormat.INSTANCE.latToString(pos)
253 + "&lon=" + DecimalDegreesCoordinateFormat.INSTANCE.lonToString(pos)
254 + "&uid=" + userId
255 + "&message=" + URLEncoder.encode(message, "UTF8");
256 if (targetUser != null && targetUser.length() > 0)
257 query += "&to=" + URLEncoder.encode(targetUser, "UTF8");
258 JsonQueryUtil.queryAsync(query, new JsonQueryCallback() {
259 @Override
260 public void processJson(JsonObject json) {
261 if (json == null)
262 fireMessageFailed(tr("Could not get server response, check logs"));
263 else if (json.get("error") != null)
264 fireMessageFailed(json.getString("error"));
265 }
266 });
267 } catch (UnsupportedEncodingException e) {
268 Logging.error(e);
269 }
270 }
271
272 /**
273 * Returns current coordinates or null if there is no map, or zoom is too low.
274 */
275 private static LatLon getPosition() {
276 if (!MainApplication.isDisplayingMapView())
277 return null;
278 if (getCurrentZoom() < 10)
279 return null;
280 Projection proj = ProjectionRegistry.getProjection();
281 return proj.eastNorth2latlon(MainApplication.getMap().mapView.getCenter());
282 }
283
284 // Following three methods were snatched from TMSLayer
285 private static double latToTileY(double lat, int zoom) {
286 double l = lat / 180 * Math.PI;
287 double pf = Math.log(Math.tan(l) + (1 / Math.cos(l)));
288 return Math.pow(2.0, zoom - 1) * (Math.PI - pf) / Math.PI;
289 }
290
291 private static double lonToTileX(double lon, int zoom) {
292 return Math.pow(2.0, zoom - 3) * (lon + 180.0) / 45.0;
293 }
294
295 public static int getCurrentZoom() {
296 if (!MainApplication.isDisplayingMapView()) {
297 return 1;
298 }
299 MapView mv = MainApplication.getMap().mapView;
300 LatLon topLeft = mv.getLatLon(0, 0);
301 LatLon botRight = mv.getLatLon(mv.getWidth(), mv.getHeight());
302 double x1 = lonToTileX(topLeft.lon(), 1);
303 double y1 = latToTileY(topLeft.lat(), 1);
304 double x2 = lonToTileX(botRight.lon(), 1);
305 double y2 = latToTileY(botRight.lat(), 1);
306
307 int screenPixels = mv.getWidth() * mv.getHeight();
308 double tilePixels = Math.abs((y2 - y1) * (x2 - x1) * 256 * 256);
309 if (screenPixels == 0 || tilePixels == 0) {
310 return 1;
311 }
312 double factor = screenPixels / tilePixels;
313 double result = Math.log(factor) / Math.log(2) / 2 + 1;
314 int intResult = (int) Math.floor(result);
315 return intResult;
316 }
317
318 private class LogRequest implements Runnable {
319 private static final int MAX_JUMP = 20000; // in meters
320 private LatLon lastPosition = null;
321 private long lastUserId = 0;
322 private long lastId = 0;
323 private boolean lastStatus = false;
324 private boolean stopping = false;
325
326 @Override
327 public void run() {
328 // lastId = Config.getPref().getLong("geochat.lastid", 0);
329 int interval = Config.getPref().getInt("geochat.interval", 2);
330 while (!stopping) {
331 process();
332 try {
333 Thread.sleep(interval * 1000);
334 } catch (InterruptedException e) {
335 stopping = true;
336 }
337 }
338 }
339
340 public void stop() {
341 stopping = true;
342 }
343
344 public void process() {
345 if (!isLoggedIn()) {
346 fireStatusChanged(false);
347 return;
348 }
349
350 LatLon pos = getPosition();
351 if (pos == null) {
352 fireStatusChanged(false);
353 return;
354 }
355 fireStatusChanged(true);
356
357 final boolean needReset;
358 final boolean needFullReset = lastUserId != userId;
359 if (needFullReset || (lastPosition != null && pos.greatCircleDistance(lastPosition) > MAX_JUMP)) {
360 // reset messages
361 lastId = 0;
362 // Config.getPref().put("geochat.lastid", null);
363 needReset = true;
364 } else
365 needReset = false;
366 lastUserId = userId;
367 lastPosition = pos;
368
369 String query = "get&lat=" + DecimalDegreesCoordinateFormat.INSTANCE.latToString(pos)
370 + "&lon=" + DecimalDegreesCoordinateFormat.INSTANCE.lonToString(pos)
371 + "&uid=" + userId + "&last=" + lastId;
372 JsonObject json;
373 try {
374 json = JsonQueryUtil.query(query);
375 } catch (IOException ex) {
376 json = null; // ?
377 }
378 if (json == null) {
379 // do nothing?
380 // fireLoginFailed(tr("Could not get server response, check logs"));
381 // logoutIntl(); // todo: uncomment?
382 } else if (json.get("error") != null) {
383 fireLoginFailed(tr("Failed to get messages as {0}:", userName) + "\n" + json.getString("error"));
384 logoutIntl();
385 } else {
386 if (json.get("users") != null) {
387 Map<String, LatLon> users = parseUsers(json.getJsonArray("users"));
388 for (ChatServerConnectionListener listener : listeners) {
389 listener.updateUsers(users);
390 }
391 }
392 if (json.get("messages") != null) {
393 List<ChatMessage> messages = parseMessages(json.getJsonArray("messages"), false);
394 for (ChatMessage m : messages) {
395 if (m.getId() > lastId)
396 lastId = m.getId();
397 }
398 for (ChatServerConnectionListener listener : listeners) {
399 listener.receivedMessages(needReset, messages);
400 }
401 }
402 if (json.get("private") != null) {
403 List<ChatMessage> messages = parseMessages(json.getJsonArray("private"), true);
404 for (ChatMessage m : messages) {
405 if (m.getId() > lastId)
406 lastId = m.getId();
407 }
408 for (ChatServerConnectionListener listener : listeners) {
409 listener.receivedPrivateMessages(needFullReset, messages);
410 }
411 }
412 }
413 // if (lastId > 0 && Config.getPref().getBoolean("geochat.store.lastid", true) )
414 // Config.getPref().putLong("geochat.lastid", lastId);
415 }
416
417 private List<ChatMessage> parseMessages(JsonArray messages, boolean priv) {
418 List<ChatMessage> result = new ArrayList<>();
419 for (int i = 0; i < messages.size(); i++) {
420 try {
421 JsonObject msg = messages.getJsonObject(i);
422 long id = Long.parseLong(msg.getString("id"));
423 double lat = Double.parseDouble(msg.getString("lat"));
424 double lon = Double.parseDouble(msg.getString("lon"));
425 long timeStamp = Long.parseLong(msg.getString("timestamp"));
426 String author = msg.getString("author");
427 String message = msg.getString("message");
428 boolean incoming = msg.getBoolean("incoming");
429 ChatMessage cm = new ChatMessage(id, new LatLon(lat, lon), author,
430 incoming, message, new Date(timeStamp * 1000));
431 cm.setPrivate(priv);
432 if (msg.get("recipient") != null && !incoming)
433 cm.setRecipient(msg.getString("recipient"));
434 result.add(cm);
435 } catch (JsonException e) {
436 Logging.trace(e);
437 }
438 }
439 return result;
440 }
441
442 private Map<String, LatLon> parseUsers(JsonArray users) {
443 Map<String, LatLon> result = new HashMap<>();
444 for (int i = 0; i < users.size(); i++) {
445 try {
446 JsonObject user = users.getJsonObject(i);
447 String name = user.getString("user");
448 double lat = Double.parseDouble(user.getString("lat"));
449 double lon = Double.parseDouble(user.getString("lon"));
450 result.put(name, new LatLon(lat, lon));
451 } catch (JsonException e) {
452 Logging.trace(e);
453 }
454 }
455 return result;
456 }
457
458 private void fireStatusChanged(boolean newStatus) {
459 if (newStatus == lastStatus)
460 return;
461 lastStatus = newStatus;
462 for (ChatServerConnectionListener listener : listeners) {
463 listener.statusChanged(newStatus);
464 }
465 }
466 }
467}
Note: See TracBrowser for help on using the repository browser.