Commit 3fb6dc861087ed3d2ded6c6e14660fc6fd5b773f
1 parent
81beb05d
Add WebPrefs framework, detail: https://github.com/Mumfrey/webprefs
Showing
33 changed files
with
2734 additions
and
0 deletions
src/client/java/com/mumfrey/webprefs/WebPreferences.java
0 → 100644
1 | +package com.mumfrey.webprefs; | ||
2 | + | ||
3 | +import java.util.HashMap; | ||
4 | +import java.util.HashSet; | ||
5 | +import java.util.Map; | ||
6 | +import java.util.Set; | ||
7 | +import java.util.concurrent.ConcurrentHashMap; | ||
8 | +import java.util.regex.Pattern; | ||
9 | + | ||
10 | +import com.mumfrey.liteloader.util.log.LiteLoaderLogger; | ||
11 | +import com.mumfrey.webprefs.exceptions.InvalidKeyException; | ||
12 | +import com.mumfrey.webprefs.exceptions.InvalidValueException; | ||
13 | +import com.mumfrey.webprefs.exceptions.ReadOnlyPreferencesException; | ||
14 | +import com.mumfrey.webprefs.framework.RequestFailureReason; | ||
15 | +import com.mumfrey.webprefs.interfaces.IWebPreferences; | ||
16 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesClient; | ||
17 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesProvider; | ||
18 | + | ||
19 | +class WebPreferences implements IWebPreferences | ||
20 | +{ | ||
21 | + /** | ||
22 | + * The update frequency to use when operating normally, this is the | ||
23 | + * frequency that new requests will be submitted to the remote request queue | ||
24 | + */ | ||
25 | + private static final int UPDATE_FREQUENCY_TICKS = 20; // 1 second | ||
26 | + | ||
27 | + /** | ||
28 | + * Number of ticks to wait before a request is assumed to have timed out | ||
29 | + */ | ||
30 | + private static final int REQUEST_TIMEOUT_TICKS = 20 * 60; // 1 minute | ||
31 | + | ||
32 | + /** | ||
33 | + * Number of ticks to wait on any communication error (request failed at the | ||
34 | + * server, request failed to be submitted, request timed out, etc.) | ||
35 | + */ | ||
36 | + private static final int UPDATE_ERROR_SUSPEND_TICKS = 20 * 60; // 1 minute | ||
37 | + | ||
38 | + /** | ||
39 | + * Pattern for validating keys | ||
40 | + */ | ||
41 | + private static final Pattern keyPattern = Pattern.compile("^[a-z0-9_\\-\\.]{1,32}$"); | ||
42 | + | ||
43 | + /** | ||
44 | + * Preferences client, this is a delegate which is used to communicate with | ||
45 | + * the preferences provider | ||
46 | + * | ||
47 | + * @author Adam Mummery-Smith | ||
48 | + */ | ||
49 | + class Client implements IWebPreferencesClient | ||
50 | + { | ||
51 | + @Override | ||
52 | + public void onGetRequestSuccess(String uuid, Map<String, String> values) | ||
53 | + { | ||
54 | + if (!WebPreferences.this.uuid.equals(uuid)) | ||
55 | + { | ||
56 | + throw new RuntimeException("Received unsolicited response"); | ||
57 | + } | ||
58 | + | ||
59 | + WebPreferences.this.onGetRequestSuccess(values); | ||
60 | + } | ||
61 | + | ||
62 | + @Override | ||
63 | + public void onSetRequestSuccess(String uuid, Set<String> keys) | ||
64 | + { | ||
65 | + if (!WebPreferences.this.uuid.equals(uuid)) | ||
66 | + { | ||
67 | + throw new RuntimeException("Received unsolicited response"); | ||
68 | + } | ||
69 | + | ||
70 | + WebPreferences.this.onSetRequestSuccess(keys); | ||
71 | + } | ||
72 | + | ||
73 | + @Override | ||
74 | + public void onGetRequestFailed(String uuid, Set<String> keys, RequestFailureReason reason) | ||
75 | + { | ||
76 | + if (!WebPreferences.this.uuid.equals(uuid)) | ||
77 | + { | ||
78 | + throw new RuntimeException("Received unsolicited response"); | ||
79 | + } | ||
80 | + | ||
81 | + WebPreferences.this.onGetRequestFailed(keys, reason); | ||
82 | + } | ||
83 | + | ||
84 | + @Override | ||
85 | + public void onSetRequestFailed(String uuid, Set<String> keys, RequestFailureReason reason) | ||
86 | + { | ||
87 | + if (!WebPreferences.this.uuid.equals(uuid)) | ||
88 | + { | ||
89 | + throw new RuntimeException("Received unsolicited response"); | ||
90 | + } | ||
91 | + | ||
92 | + WebPreferences.this.onSetRequestFailed(keys, reason); | ||
93 | + } | ||
94 | + } | ||
95 | + | ||
96 | + /** | ||
97 | + * Preferences provider | ||
98 | + */ | ||
99 | + private final IWebPreferencesProvider provider; | ||
100 | + | ||
101 | + /** | ||
102 | + * Preferences delegate | ||
103 | + */ | ||
104 | + private final IWebPreferencesClient client; | ||
105 | + | ||
106 | + /** | ||
107 | + * Our UUID | ||
108 | + */ | ||
109 | + protected final String uuid; | ||
110 | + | ||
111 | + /** | ||
112 | + * True if we are a private settings set | ||
113 | + */ | ||
114 | + protected final boolean isPrivate; | ||
115 | + | ||
116 | + protected final boolean isReadOnly; | ||
117 | + | ||
118 | + /** | ||
119 | + * Current key/value pairs | ||
120 | + */ | ||
121 | + protected final Map<String, String> prefs = new ConcurrentHashMap<String, String>(); | ||
122 | + | ||
123 | + /** | ||
124 | + * Keys which have been requested by a consumer but not requested from the | ||
125 | + * server yet | ||
126 | + */ | ||
127 | + protected final Set<String> requestedPrefs = new HashSet<String>(); | ||
128 | + | ||
129 | + /** | ||
130 | + * Keys which have been requested from the server but not received yet | ||
131 | + */ | ||
132 | + protected final Set<String> pendingPrefs = new HashSet<String>(); | ||
133 | + | ||
134 | + /** | ||
135 | + * Keys which have been set by a consumer but not sent to the server yet | ||
136 | + */ | ||
137 | + protected final Set<String> dirtyPrefs = new HashSet<String>(); | ||
138 | + | ||
139 | + /** | ||
140 | + * Concurrency lock | ||
141 | + */ | ||
142 | + protected final Object lock = new Object(); | ||
143 | + | ||
144 | + /** | ||
145 | + * True when any kind of | ||
146 | + */ | ||
147 | + protected volatile boolean dirty = false; | ||
148 | + | ||
149 | + private volatile int updateCheckTimer = 1; | ||
150 | + | ||
151 | + protected int requestTimeoutTimer = 0; | ||
152 | + | ||
153 | + WebPreferences(IWebPreferencesProvider provider, String uuid, boolean isPrivate, boolean isReadOnly) | ||
154 | + { | ||
155 | + this.provider = provider; | ||
156 | + this.uuid = uuid; | ||
157 | + this.isPrivate = isPrivate; | ||
158 | + this.isReadOnly = isReadOnly; | ||
159 | + this.client = new Client(); | ||
160 | + } | ||
161 | + | ||
162 | + /* (non-Javadoc) | ||
163 | + * @see com.mumfrey.webprefs.interfaces.IWebPreferences#getUUID() | ||
164 | + */ | ||
165 | + @Override | ||
166 | + public String getUUID() | ||
167 | + { | ||
168 | + return this.uuid; | ||
169 | + } | ||
170 | + | ||
171 | + /* (non-Javadoc) | ||
172 | + * @see com.mumfrey.webprefs.interfaces.IWebPreferences#isPrivate() | ||
173 | + */ | ||
174 | + @Override | ||
175 | + public boolean isPrivate() | ||
176 | + { | ||
177 | + return this.isPrivate; | ||
178 | + } | ||
179 | + | ||
180 | + /* (non-Javadoc) | ||
181 | + * @see com.mumfrey.webprefs.interfaces.IWebPreferences#isReadOnly() | ||
182 | + */ | ||
183 | + @Override | ||
184 | + public boolean isReadOnly() | ||
185 | + { | ||
186 | + return this.isReadOnly; | ||
187 | + } | ||
188 | + | ||
189 | + void onTick() | ||
190 | + { | ||
191 | + if (this.updateCheckTimer > 0) | ||
192 | + { | ||
193 | + this.updateCheckTimer--; | ||
194 | + if (this.updateCheckTimer < 1) | ||
195 | + { | ||
196 | + this.update(); | ||
197 | + } | ||
198 | + } | ||
199 | + | ||
200 | + if (this.requestTimeoutTimer > 0) | ||
201 | + { | ||
202 | + this.requestTimeoutTimer--; | ||
203 | + if (this.requestTimeoutTimer < 1) | ||
204 | + { | ||
205 | + this.handleTimeout(); | ||
206 | + } | ||
207 | + } | ||
208 | + } | ||
209 | + | ||
210 | + /** | ||
211 | + * Handle server requests on a periodic basis | ||
212 | + */ | ||
213 | + private void update() | ||
214 | + { | ||
215 | + this.updateCheckTimer = WebPreferences.UPDATE_FREQUENCY_TICKS; | ||
216 | + | ||
217 | + if (!this.dirty || !this.provider.isActive()) | ||
218 | + { | ||
219 | + return; | ||
220 | + } | ||
221 | + | ||
222 | + synchronized (this.lock) | ||
223 | + { | ||
224 | + this.dirty = false; | ||
225 | + | ||
226 | + if (this.requestedPrefs.size() > 0) | ||
227 | + { | ||
228 | + LiteLoaderLogger.debug("Preferences for " + this.uuid + " is submitting a request for " | ||
229 | + + this.requestedPrefs.size() + " requested preferences"); | ||
230 | + if (this.provider.requestGet(this.client, this.uuid, new HashSet<String>(this.requestedPrefs), this.isPrivate)) | ||
231 | + { | ||
232 | + this.requestTimeoutTimer = WebPreferences.REQUEST_TIMEOUT_TICKS; | ||
233 | + this.pendingPrefs.addAll(this.requestedPrefs); | ||
234 | + this.requestedPrefs.clear(); | ||
235 | + } | ||
236 | + else | ||
237 | + { | ||
238 | + this.dirty = true; | ||
239 | + } | ||
240 | + } | ||
241 | + | ||
242 | + } | ||
243 | + | ||
244 | + this.commit(false); | ||
245 | + } | ||
246 | + | ||
247 | + /** | ||
248 | + * Called when a pending request is deemed to have timed out | ||
249 | + */ | ||
250 | + private void handleTimeout() | ||
251 | + { | ||
252 | + this.updateCheckTimer = WebPreferences.UPDATE_ERROR_SUSPEND_TICKS; | ||
253 | + | ||
254 | + synchronized (this.lock) | ||
255 | + { | ||
256 | + this.requestedPrefs.addAll(this.pendingPrefs); | ||
257 | + this.pendingPrefs.clear(); | ||
258 | + this.dirty = true; | ||
259 | + } | ||
260 | + } | ||
261 | + | ||
262 | + /* (non-Javadoc) | ||
263 | + * @see com.mumfrey.webprefs.interfaces.IWebPreferences | ||
264 | + * #request(java.lang.String) | ||
265 | + */ | ||
266 | + @Override | ||
267 | + public void request(String key) | ||
268 | + { | ||
269 | + WebPreferences.validateKey(key); | ||
270 | + | ||
271 | + synchronized (this.lock) | ||
272 | + { | ||
273 | + this.dirty |= this.addRequestedKey(key); | ||
274 | + } | ||
275 | + } | ||
276 | + | ||
277 | + /* (non-Javadoc) | ||
278 | + * @see com.mumfrey.webprefs.interfaces.IWebPreferences | ||
279 | + * #request(java.lang.String[]) | ||
280 | + */ | ||
281 | + @Override | ||
282 | + public void request(String... keys) | ||
283 | + { | ||
284 | + if (keys.length < 1) return; | ||
285 | + if (keys.length == 1) this.request(keys[0]); | ||
286 | + | ||
287 | + synchronized (this.lock) | ||
288 | + { | ||
289 | + boolean dirty = false; | ||
290 | + | ||
291 | + for (String key : keys) | ||
292 | + { | ||
293 | + WebPreferences.validateKey(key); | ||
294 | + dirty |= this.addRequestedKey(key); | ||
295 | + } | ||
296 | + | ||
297 | + this.dirty |= dirty; | ||
298 | + } | ||
299 | + } | ||
300 | + | ||
301 | + /* (non-Javadoc) | ||
302 | + * @see com.mumfrey.webprefs.interfaces.IWebPreferences | ||
303 | + * #request(java.util.Set) | ||
304 | + */ | ||
305 | + @Override | ||
306 | + public void request(Set<String> keys) | ||
307 | + { | ||
308 | + if (keys == null || keys.size() < 1) return; | ||
309 | + | ||
310 | + synchronized (this.lock) | ||
311 | + { | ||
312 | + boolean dirty = false; | ||
313 | + | ||
314 | + for (String key : keys) | ||
315 | + { | ||
316 | + WebPreferences.validateKey(key); | ||
317 | + dirty |= this.addRequestedKey(key); | ||
318 | + } | ||
319 | + | ||
320 | + this.dirty |= dirty; | ||
321 | + } | ||
322 | + } | ||
323 | + | ||
324 | + /* (non-Javadoc) | ||
325 | + * @see com.mumfrey.webprefs.interfaces.IWebPreferences#poll() | ||
326 | + */ | ||
327 | + @Override | ||
328 | + public void poll() | ||
329 | + { | ||
330 | + synchronized (this.lock) | ||
331 | + { | ||
332 | + this.requestedPrefs.addAll(this.prefs.keySet()); | ||
333 | + this.requestedPrefs.removeAll(this.pendingPrefs); | ||
334 | + this.dirty = true; | ||
335 | + } | ||
336 | + } | ||
337 | + | ||
338 | + /* (non-Javadoc) | ||
339 | + * @see com.mumfrey.webprefs.interfaces.IWebPreferences#commit(boolean) | ||
340 | + */ | ||
341 | + @Override | ||
342 | + public void commit(boolean force) | ||
343 | + { | ||
344 | + synchronized (this.lock) | ||
345 | + { | ||
346 | + // Permanent error condition | ||
347 | + if (this.updateCheckTimer < 0) | ||
348 | + { | ||
349 | + return; | ||
350 | + } | ||
351 | + | ||
352 | + if (force) | ||
353 | + { | ||
354 | + this.dirtyPrefs.addAll(this.prefs.keySet()); | ||
355 | + } | ||
356 | + | ||
357 | + if (this.dirtyPrefs.size() > 0) | ||
358 | + { | ||
359 | + Map<String, String> outgoingPrefs = new HashMap<String, String>(); | ||
360 | + for (String key : this.dirtyPrefs) | ||
361 | + { | ||
362 | + outgoingPrefs.put(key, this.prefs.get(key)); | ||
363 | + } | ||
364 | + | ||
365 | + LiteLoaderLogger.debug("Preferences for " + this.uuid + " is submitting a SET for " + outgoingPrefs.size() + " dirty preferences"); | ||
366 | + if (this.provider.requestSet(this.client, this.uuid, outgoingPrefs, this.isPrivate)) | ||
367 | + { | ||
368 | + this.dirtyPrefs.clear(); | ||
369 | + } | ||
370 | + else | ||
371 | + { | ||
372 | + this.dirty = true; | ||
373 | + } | ||
374 | + } | ||
375 | + } | ||
376 | + } | ||
377 | + | ||
378 | + /* (non-Javadoc) | ||
379 | + * @see com.mumfrey.webprefs.interfaces.IWebPreferences | ||
380 | + * #has(java.lang.String) | ||
381 | + */ | ||
382 | + @Override | ||
383 | + public boolean has(String key) | ||
384 | + { | ||
385 | + WebPreferences.validateKey(key); | ||
386 | + | ||
387 | + return this.get(key) != null; | ||
388 | + } | ||
389 | + | ||
390 | + /* (non-Javadoc) | ||
391 | + * @see com.mumfrey.webprefs.interfaces.IWebPreferences | ||
392 | + * #get(java.lang.String) | ||
393 | + */ | ||
394 | + @Override | ||
395 | + public String get(String key) | ||
396 | + { | ||
397 | + WebPreferences.validateKey(key); | ||
398 | + | ||
399 | + // .get() can be outside of the synchronisation lock because we are using ConcurrentHashSet | ||
400 | + String value = this.prefs.get(key); | ||
401 | + | ||
402 | + if (value == null) | ||
403 | + { | ||
404 | + synchronized (this.lock) | ||
405 | + { | ||
406 | + this.dirty |= this.addRequestedKey(key); | ||
407 | + } | ||
408 | + } | ||
409 | + | ||
410 | + return value; | ||
411 | + } | ||
412 | + | ||
413 | + /* (non-Javadoc) | ||
414 | + * @see com.mumfrey.webprefs.interfaces.IWebPreferences | ||
415 | + * #get(java.lang.String, java.lang.String) | ||
416 | + */ | ||
417 | + @Override | ||
418 | + public String get(String key, String defaultValue) | ||
419 | + { | ||
420 | + WebPreferences.validateKey(key); | ||
421 | + | ||
422 | + String value = this.get(key); | ||
423 | + return value != null ? value : defaultValue; | ||
424 | + } | ||
425 | + | ||
426 | + /* (non-Javadoc) | ||
427 | + * @see com.mumfrey.webprefs.interfaces.IWebPreferences | ||
428 | + * #set(java.lang.String, java.lang.String) | ||
429 | + */ | ||
430 | + @Override | ||
431 | + public void set(String key, String value) | ||
432 | + { | ||
433 | + if (this.isReadOnly()) | ||
434 | + { | ||
435 | + throw new ReadOnlyPreferencesException("Preference collection for " + this.uuid + " is read-only"); | ||
436 | + } | ||
437 | + | ||
438 | + WebPreferences.validateKV(key, value); | ||
439 | + | ||
440 | + synchronized (this.lock) | ||
441 | + { | ||
442 | + String oldValue = this.prefs.get(key); | ||
443 | + if (value.equals(oldValue)) return; | ||
444 | + | ||
445 | + this.prefs.put(key, value); | ||
446 | + this.dirtyPrefs.add(key); | ||
447 | + this.requestedPrefs.remove(key); | ||
448 | + this.dirty = true; | ||
449 | + } | ||
450 | + } | ||
451 | + | ||
452 | + /* (non-Javadoc) | ||
453 | + * @see com.mumfrey.webprefs.interfaces.IWebPreferences | ||
454 | + * #remove(java.lang.String) | ||
455 | + */ | ||
456 | + @Override | ||
457 | + public void remove(String key) | ||
458 | + { | ||
459 | + this.set(key, ""); | ||
460 | + } | ||
461 | + | ||
462 | + /** | ||
463 | + * Add a key to the current request set, the key will be requested from the | ||
464 | + * server on the next {@link #update()} | ||
465 | + * | ||
466 | + * @param key | ||
467 | + * @return | ||
468 | + */ | ||
469 | + private boolean addRequestedKey(String key) | ||
470 | + { | ||
471 | + if (key != null && !this.pendingPrefs.contains(key)) | ||
472 | + { | ||
473 | + this.requestedPrefs.add(key); | ||
474 | + return true; | ||
475 | + } | ||
476 | + | ||
477 | + return false; | ||
478 | + } | ||
479 | + | ||
480 | + /** | ||
481 | + * Callback from the preferences provider | ||
482 | + */ | ||
483 | + void onGetRequestSuccess(Map<String, String> values) | ||
484 | + { | ||
485 | + this.requestTimeoutTimer = 0; | ||
486 | + | ||
487 | + synchronized (this.lock) | ||
488 | + { | ||
489 | + this.prefs.putAll(values); | ||
490 | + | ||
491 | + Set<String> keys = values.keySet(); | ||
492 | + this.dirtyPrefs.removeAll(keys); | ||
493 | + this.pendingPrefs.removeAll(keys); | ||
494 | + this.requestedPrefs.removeAll(keys); | ||
495 | + } | ||
496 | + } | ||
497 | + | ||
498 | + /** | ||
499 | + * Callback from the preferences provider | ||
500 | + */ | ||
501 | + void onSetRequestSuccess(Set<String> keys) | ||
502 | + { | ||
503 | + this.requestTimeoutTimer = 0; | ||
504 | + | ||
505 | + synchronized (this.lock) | ||
506 | + { | ||
507 | + this.dirtyPrefs.removeAll(keys); | ||
508 | + this.requestedPrefs.removeAll(keys); | ||
509 | + this.dirty = (this.dirtyPrefs.size() > 0 || this.requestedPrefs.size() > 0); | ||
510 | + } | ||
511 | + } | ||
512 | + | ||
513 | + /** | ||
514 | + * Callback from the preferences provider | ||
515 | + * @param reason | ||
516 | + */ | ||
517 | + void onGetRequestFailed(Set<String> keys, RequestFailureReason reason) | ||
518 | + { | ||
519 | + this.requestTimeoutTimer = 0; | ||
520 | + this.handleFailedRequest(reason); | ||
521 | + | ||
522 | + synchronized (this.lock) | ||
523 | + { | ||
524 | + this.dirtyPrefs.addAll(keys); | ||
525 | + this.pendingPrefs.removeAll(keys); | ||
526 | + this.dirty = true; | ||
527 | + } | ||
528 | + } | ||
529 | + | ||
530 | + /** | ||
531 | + * Callback from the preferences provider | ||
532 | + * @param reason | ||
533 | + */ | ||
534 | + void onSetRequestFailed(Set<String> keys, RequestFailureReason reason) | ||
535 | + { | ||
536 | + this.requestTimeoutTimer = 0; | ||
537 | + this.handleFailedRequest(reason); | ||
538 | + | ||
539 | + synchronized (this.lock) | ||
540 | + { | ||
541 | + this.requestedPrefs.addAll(keys); | ||
542 | + this.pendingPrefs.removeAll(keys); | ||
543 | + this.dirty = true; | ||
544 | + } | ||
545 | + } | ||
546 | + | ||
547 | + /** | ||
548 | + * @param reason | ||
549 | + */ | ||
550 | + private void handleFailedRequest(RequestFailureReason reason) | ||
551 | + { | ||
552 | + if (reason.isPermanent()) | ||
553 | + { | ||
554 | + LiteLoaderLogger.debug("Halting update of preferences for " + this.uuid + " permanently because " + reason); | ||
555 | + this.updateCheckTimer = -1; | ||
556 | + } | ||
557 | + | ||
558 | + int suspendUpdateFor = WebPreferences.UPDATE_ERROR_SUSPEND_TICKS * Math.max(1, reason.getSeverity()); | ||
559 | + LiteLoaderLogger.debug("Suspending update of preferences for " + this.uuid + " for " + suspendUpdateFor + " because " + reason); | ||
560 | + this.updateCheckTimer = suspendUpdateFor; | ||
561 | + } | ||
562 | + | ||
563 | + /** | ||
564 | + * @param key | ||
565 | + */ | ||
566 | + protected static final void validateKey(String key) | ||
567 | + { | ||
568 | + if (key == null || !WebPreferences.keyPattern.matcher(key).matches()) | ||
569 | + { | ||
570 | + throw new InvalidKeyException("The specified key [" + key + "] is not valid"); | ||
571 | + } | ||
572 | + } | ||
573 | + | ||
574 | + /** | ||
575 | + * @param key | ||
576 | + * @param value | ||
577 | + */ | ||
578 | + protected static final void validateKV(String key, String value) | ||
579 | + { | ||
580 | + WebPreferences.validateKey(key); | ||
581 | + | ||
582 | + if (value == null || value.length() > 255) | ||
583 | + { | ||
584 | + throw new InvalidValueException("The specified value [" + value + "] for key [" + key + "] is not valid"); | ||
585 | + } | ||
586 | + } | ||
587 | +} |
src/client/java/com/mumfrey/webprefs/WebPreferencesManager.java
0 → 100644
1 | +package com.mumfrey.webprefs; | ||
2 | + | ||
3 | +import java.io.File; | ||
4 | +import java.net.Proxy; | ||
5 | +import java.net.URI; | ||
6 | +import java.net.URISyntaxException; | ||
7 | +import java.util.HashMap; | ||
8 | +import java.util.LinkedHashMap; | ||
9 | +import java.util.LinkedList; | ||
10 | +import java.util.List; | ||
11 | +import java.util.Map; | ||
12 | +import java.util.UUID; | ||
13 | +import java.util.regex.Matcher; | ||
14 | +import java.util.regex.Pattern; | ||
15 | + | ||
16 | +import com.mojang.authlib.GameProfile; | ||
17 | +import com.mojang.realmsclient.dto.RealmsServer; | ||
18 | +import com.mumfrey.liteloader.JoinGameListener; | ||
19 | +import com.mumfrey.liteloader.Tickable; | ||
20 | +import com.mumfrey.liteloader.core.LiteLoader; | ||
21 | +import com.mumfrey.webprefs.exceptions.InvalidServiceException; | ||
22 | +import com.mumfrey.webprefs.exceptions.InvalidUUIDException; | ||
23 | +import com.mumfrey.webprefs.framework.WebPreferencesProvider; | ||
24 | +import com.mumfrey.webprefs.interfaces.IWebPreferences; | ||
25 | + | ||
26 | +import net.minecraft.client.Minecraft; | ||
27 | +import net.minecraft.client.multiplayer.ServerData; | ||
28 | +import net.minecraft.entity.player.EntityPlayer; | ||
29 | +import net.minecraft.network.INetHandler; | ||
30 | +import net.minecraft.network.play.server.SPacketJoinGame; | ||
31 | +import net.minecraft.util.Session; | ||
32 | + | ||
33 | +/** | ||
34 | + * WebPreferences Service Manager, acts as a central registry for all web | ||
35 | + * preference collections. | ||
36 | + * | ||
37 | + * <p>To access preferences on a service, first request the service using | ||
38 | + * {@link #get(String)} or {@link #getDefault()}, you can then request | ||
39 | + * preferences objects from the service by calling the various overloads of | ||
40 | + * {@link #getPreferences}.</p> | ||
41 | + * | ||
42 | + * @author Adam Mummery-Smith | ||
43 | + */ | ||
44 | +public final class WebPreferencesManager | ||
45 | +{ | ||
46 | + /** | ||
47 | + * WebPreferences Manager Update Daemon is injected into LiteLoader to | ||
48 | + * facilitate passing events to the WebPreferences Manager without having to | ||
49 | + * expose public callback methods. | ||
50 | + * | ||
51 | + * @author Adam Mummery-Smith | ||
52 | + */ | ||
53 | + static class WebPreferencesUpdateDeamon implements Tickable, JoinGameListener | ||
54 | + { | ||
55 | + @Override | ||
56 | + public String getName() | ||
57 | + { | ||
58 | + return "Web Preferences Update Daemon"; | ||
59 | + } | ||
60 | + | ||
61 | + @Override | ||
62 | + public String getVersion() | ||
63 | + { | ||
64 | + return "N/A"; | ||
65 | + } | ||
66 | + | ||
67 | + @Override | ||
68 | + public void init(File configPath) | ||
69 | + { | ||
70 | + } | ||
71 | + | ||
72 | + @Override | ||
73 | + public void upgradeSettings(String version, File configPath, File oldConfigPath) | ||
74 | + { | ||
75 | + } | ||
76 | + | ||
77 | + @Override | ||
78 | + public void onTick(Minecraft minecraft, float partialTicks, boolean inGame, boolean clock) | ||
79 | + { | ||
80 | + if (clock) | ||
81 | + { | ||
82 | + for (WebPreferencesManager manager : WebPreferencesManager.managers.values()) | ||
83 | + { | ||
84 | + manager.onTick(); | ||
85 | + } | ||
86 | + } | ||
87 | + } | ||
88 | + | ||
89 | + @Override | ||
90 | + public void onJoinGame(INetHandler netHandler, SPacketJoinGame joinGamePacket, ServerData serverData, RealmsServer realmsServer) | ||
91 | + { | ||
92 | + for (WebPreferencesManager manager : WebPreferencesManager.managers.values()) | ||
93 | + { | ||
94 | + manager.onJoinGame(); | ||
95 | + } | ||
96 | + } | ||
97 | + } | ||
98 | + | ||
99 | + /** | ||
100 | + * Default KV api hostname to connect to | ||
101 | + */ | ||
102 | + private static final String DEFAULT_HOSTNAME = "kv.liteloader.com"; | ||
103 | + | ||
104 | + /** | ||
105 | + * Regex for validating UUIDs | ||
106 | + */ | ||
107 | + private static final Pattern uuidPattern = Pattern.compile("^[a-f0-9]{32}$"); | ||
108 | + | ||
109 | + /** | ||
110 | + * Mapping of hostnames to managers | ||
111 | + */ | ||
112 | + static final Map<String, WebPreferencesManager> managers = new LinkedHashMap<String, WebPreferencesManager>(); | ||
113 | + | ||
114 | + /** | ||
115 | + * Update daemon | ||
116 | + */ | ||
117 | + private static WebPreferencesUpdateDeamon updateDeamon; | ||
118 | + | ||
119 | + /** | ||
120 | + * Session for this instance | ||
121 | + */ | ||
122 | + private final Session session; | ||
123 | + | ||
124 | + /** | ||
125 | + * Preference provider, manages queueing requests and passing responses back | ||
126 | + * to clients | ||
127 | + */ | ||
128 | + private final WebPreferencesProvider provider; | ||
129 | + | ||
130 | + /** | ||
131 | + * All preference sets, for iteration purposes | ||
132 | + */ | ||
133 | + private final List<WebPreferences> allPreferences = new LinkedList<WebPreferences>(); | ||
134 | + | ||
135 | + /** | ||
136 | + * All public preference sets, mapped by UUID | ||
137 | + */ | ||
138 | + private final Map<String, IWebPreferences> preferencesPublic = new HashMap<String, IWebPreferences>(); | ||
139 | + | ||
140 | + /** | ||
141 | + * All private preference sets, mapped by UUID | ||
142 | + */ | ||
143 | + private final Map<String, IWebPreferences> preferencesPrivate = new HashMap<String, IWebPreferences>(); | ||
144 | + | ||
145 | + private WebPreferencesManager(Proxy proxy, Session session, String hostName) | ||
146 | + { | ||
147 | + this.session = session; | ||
148 | + this.provider = new WebPreferencesProvider(proxy, session, hostName, 50); | ||
149 | + } | ||
150 | + | ||
151 | + void onTick() | ||
152 | + { | ||
153 | + this.provider.onTick(); | ||
154 | + | ||
155 | + for (WebPreferences prefs : this.allPreferences) | ||
156 | + { | ||
157 | + try | ||
158 | + { | ||
159 | + prefs.onTick(); | ||
160 | + } | ||
161 | + catch (Exception ex) {} | ||
162 | + } | ||
163 | + } | ||
164 | + | ||
165 | + void onJoinGame() | ||
166 | + { | ||
167 | + for (WebPreferences prefs : this.allPreferences) | ||
168 | + { | ||
169 | + try | ||
170 | + { | ||
171 | + prefs.poll(); | ||
172 | + } | ||
173 | + catch (Exception ex) {} | ||
174 | + } | ||
175 | + } | ||
176 | + | ||
177 | + | ||
178 | + /** | ||
179 | + * Get a public or private preferences collection for the local player | ||
180 | + * | ||
181 | + * @param privatePrefs true to fetch the player's private preferences, false | ||
182 | + * to fetch public preferences | ||
183 | + * @return player's preference collection, creates if necessary | ||
184 | + */ | ||
185 | + public IWebPreferences getLocalPreferences(boolean privatePrefs) | ||
186 | + { | ||
187 | + return this.getPreferences(this.session.getPlayerID(), privatePrefs); | ||
188 | + } | ||
189 | + | ||
190 | + /** | ||
191 | + * Get a public preferences collection for the specified player. | ||
192 | + * | ||
193 | + * @param player Player to fetch preferences for | ||
194 | + * @param privatePrefs True to fetch the player's private preferences, false | ||
195 | + * to fetch the public preferences | ||
196 | + * @return Preference collection or <tt>null</tt> if the player's profile | ||
197 | + * cannot be retrieved | ||
198 | + */ | ||
199 | + public IWebPreferences getPreferences(EntityPlayer player) | ||
200 | + { | ||
201 | + return this.getPreferences(player, false); | ||
202 | + } | ||
203 | + | ||
204 | + /** | ||
205 | + * Get a public or private preferences collection for the specified player, | ||
206 | + * note that accessing a private collection for another player is likely | ||
207 | + * to be prohibited by the service. | ||
208 | + * | ||
209 | + * @param player Player to fetch preferences for | ||
210 | + * @param privatePrefs True to fetch the player's private preferences, false | ||
211 | + * to fetch the public preferences | ||
212 | + * @return Preference collection or <tt>null</tt> if the player's profile | ||
213 | + * cannot be retrieved | ||
214 | + */ | ||
215 | + public IWebPreferences getPreferences(EntityPlayer player, boolean privatePrefs) | ||
216 | + { | ||
217 | + GameProfile gameProfile = player.getGameProfile(); | ||
218 | + return gameProfile != null ? this.getPreferences(gameProfile, privatePrefs) : null; | ||
219 | + } | ||
220 | + | ||
221 | + /** | ||
222 | + * Get a public preferences collection for the specified game profile. | ||
223 | + * | ||
224 | + * @param gameProfile game profile to fetch preferences for | ||
225 | + * @return Preference collection or <tt>null</tt> if the supplied profile is | ||
226 | + * null | ||
227 | + */ | ||
228 | + public IWebPreferences getPreferences(GameProfile gameProfile) | ||
229 | + { | ||
230 | + return gameProfile != null ? this.getPreferences(gameProfile, false) : null; | ||
231 | + } | ||
232 | + | ||
233 | + /** | ||
234 | + * Get a public or private preferences collection for the specified game | ||
235 | + * profile, note that accessing a private collection for another player is | ||
236 | + * likely to be prohibited by the service. | ||
237 | + * | ||
238 | + * @param gameProfile game profile to fetch preferences for | ||
239 | + * @param privatePrefs True to fetch the player's private preferences, false | ||
240 | + * to fetch the public preferences | ||
241 | + * @return Preference collection or <tt>null</tt> if the supplied profile is | ||
242 | + * null | ||
243 | + */ | ||
244 | + public IWebPreferences getPreferences(GameProfile gameProfile, boolean privatePrefs) | ||
245 | + { | ||
246 | + return gameProfile != null ? this.getPreferences(gameProfile.getId(), privatePrefs) : null; | ||
247 | + } | ||
248 | + | ||
249 | + /** | ||
250 | + * Get a public preferences collection for the specified player UUID. | ||
251 | + * | ||
252 | + * @param uuid UUID to fetch preferences for | ||
253 | + * @return Preference collection or <tt>null</tt> if the supplied UUID is | ||
254 | + * null | ||
255 | + */ | ||
256 | + public IWebPreferences getPreferences(UUID uuid) | ||
257 | + { | ||
258 | + return uuid != null ? this.getPreferences(uuid, false) : null; | ||
259 | + } | ||
260 | + | ||
261 | + /** | ||
262 | + * Get a public or private preferences collection for the specified player | ||
263 | + * UUID, note that accessing a private collection for another player is | ||
264 | + * likely to be prohibited by the service. | ||
265 | + * | ||
266 | + * @param uuid UUID to fetch preferences for | ||
267 | + * @param privatePrefs True to fetch the player's private preferences, false | ||
268 | + * to fetch the public preferences | ||
269 | + * @return Preference collection or <tt>null</tt> if the supplied UUID is | ||
270 | + * null | ||
271 | + */ | ||
272 | + public IWebPreferences getPreferences(UUID uuid, boolean privatePrefs) | ||
273 | + { | ||
274 | + return uuid != null ? this.getPreferences(uuid.toString(), privatePrefs) : null; | ||
275 | + } | ||
276 | + | ||
277 | + public IWebPreferences getPreferences(String uuid, boolean privatePrefs) | ||
278 | + { | ||
279 | + uuid = this.sanitiseUUID(uuid); | ||
280 | + | ||
281 | + Map<String, IWebPreferences> preferences = privatePrefs ? this.preferencesPrivate : this.preferencesPublic; | ||
282 | + | ||
283 | + IWebPreferences prefs = preferences.get(uuid); | ||
284 | + | ||
285 | + if (prefs == null) | ||
286 | + { | ||
287 | + WebPreferences newPrefs = new WebPreferences(this.provider, uuid, privatePrefs, !uuid.equals(this.session.getPlayerID())); | ||
288 | + this.allPreferences.add(newPrefs); | ||
289 | + preferences.put(uuid, newPrefs); | ||
290 | + prefs = newPrefs; | ||
291 | + } | ||
292 | + | ||
293 | + return prefs; | ||
294 | + } | ||
295 | + | ||
296 | + private String sanitiseUUID(String uuid) | ||
297 | + { | ||
298 | + if (uuid == null) | ||
299 | + { | ||
300 | + throw new InvalidUUIDException("The UUID was null"); | ||
301 | + } | ||
302 | + | ||
303 | + uuid = uuid.toLowerCase().replace("-", "").trim(); | ||
304 | + Matcher uuidPatternMatcher = WebPreferencesManager.uuidPattern.matcher(uuid); | ||
305 | + if (!uuidPatternMatcher.matches()) | ||
306 | + { | ||
307 | + throw new InvalidUUIDException("The specified string [" + uuid + "] is not a valid UUID"); | ||
308 | + } | ||
309 | + | ||
310 | + return uuid; | ||
311 | + } | ||
312 | + | ||
313 | + /** | ||
314 | + * Get the default preferences manager (kv.liteloader.com) | ||
315 | + * | ||
316 | + * @return default preferences manager | ||
317 | + */ | ||
318 | + public static WebPreferencesManager getDefault() | ||
319 | + { | ||
320 | + return WebPreferencesManager.get(WebPreferencesManager.DEFAULT_HOSTNAME); | ||
321 | + } | ||
322 | + | ||
323 | + /** | ||
324 | + * Get a preferences manager for the specified service hostname | ||
325 | + * | ||
326 | + * @param hostName service hostname (bare hostname only, no protocol) | ||
327 | + * @return preferences manager | ||
328 | + * @throws InvalidServiceException if the specified host name is invalid | ||
329 | + */ | ||
330 | + @SuppressWarnings("unused") | ||
331 | + public static WebPreferencesManager get(String hostName) throws InvalidServiceException | ||
332 | + { | ||
333 | + try | ||
334 | + { | ||
335 | + new URI(String.format("http://%s/", hostName)); | ||
336 | + } | ||
337 | + catch (URISyntaxException ex) | ||
338 | + { | ||
339 | + throw new InvalidServiceException("The specified service host was not valid: " + hostName, ex); | ||
340 | + } | ||
341 | + | ||
342 | + if (WebPreferencesManager.updateDeamon == null) | ||
343 | + { | ||
344 | + WebPreferencesManager.updateDeamon = new WebPreferencesUpdateDeamon(); | ||
345 | + LiteLoader.getInterfaceManager().registerListener(WebPreferencesManager.updateDeamon); | ||
346 | + } | ||
347 | + | ||
348 | + WebPreferencesManager manager = WebPreferencesManager.managers.get(hostName); | ||
349 | + | ||
350 | + if (manager == null) | ||
351 | + { | ||
352 | + Minecraft minecraft = Minecraft.getMinecraft(); | ||
353 | + | ||
354 | + Proxy proxy = minecraft.getProxy(); | ||
355 | + Session session = minecraft.getSession(); | ||
356 | + | ||
357 | + manager = new WebPreferencesManager(proxy, session, hostName); | ||
358 | + WebPreferencesManager.managers.put(hostName, manager); | ||
359 | + } | ||
360 | + | ||
361 | + return manager; | ||
362 | + } | ||
363 | +} |
src/client/java/com/mumfrey/webprefs/exceptions/InvalidKeyException.java
0 → 100644
1 | +package com.mumfrey.webprefs.exceptions; | ||
2 | + | ||
3 | +public class InvalidKeyException extends RuntimeException | ||
4 | +{ | ||
5 | + private static final long serialVersionUID = 1L; | ||
6 | + | ||
7 | + public InvalidKeyException() | ||
8 | + { | ||
9 | + } | ||
10 | + | ||
11 | + public InvalidKeyException(String message) | ||
12 | + { | ||
13 | + super(message); | ||
14 | + } | ||
15 | + | ||
16 | + public InvalidKeyException(Throwable cause) | ||
17 | + { | ||
18 | + super(cause); | ||
19 | + } | ||
20 | + | ||
21 | + public InvalidKeyException(String message, Throwable cause) | ||
22 | + { | ||
23 | + super(message, cause); | ||
24 | + } | ||
25 | +} |
src/client/java/com/mumfrey/webprefs/exceptions/InvalidPreferenceOperationException.java
0 → 100644
1 | +package com.mumfrey.webprefs.exceptions; | ||
2 | + | ||
3 | +public class InvalidPreferenceOperationException extends RuntimeException | ||
4 | +{ | ||
5 | + private static final long serialVersionUID = 1L; | ||
6 | + | ||
7 | + public InvalidPreferenceOperationException() | ||
8 | + { | ||
9 | + } | ||
10 | + | ||
11 | + public InvalidPreferenceOperationException(String message) | ||
12 | + { | ||
13 | + super(message); | ||
14 | + } | ||
15 | + | ||
16 | + public InvalidPreferenceOperationException(Throwable cause) | ||
17 | + { | ||
18 | + super(cause); | ||
19 | + } | ||
20 | + | ||
21 | + public InvalidPreferenceOperationException(String message, Throwable cause) | ||
22 | + { | ||
23 | + super(message, cause); | ||
24 | + } | ||
25 | +} |
src/client/java/com/mumfrey/webprefs/exceptions/InvalidRequestException.java
0 → 100644
1 | +package com.mumfrey.webprefs.exceptions; | ||
2 | + | ||
3 | +import com.mumfrey.webprefs.framework.RequestFailureReason; | ||
4 | + | ||
5 | +public class InvalidRequestException extends RuntimeException | ||
6 | +{ | ||
7 | + private static final long serialVersionUID = 1L; | ||
8 | + | ||
9 | + private final RequestFailureReason reason; | ||
10 | + | ||
11 | + public InvalidRequestException(RequestFailureReason reason) | ||
12 | + { | ||
13 | + this.reason = reason; | ||
14 | + } | ||
15 | + | ||
16 | + public InvalidRequestException(RequestFailureReason reason, String message) | ||
17 | + { | ||
18 | + super(message); | ||
19 | + this.reason = reason; | ||
20 | + } | ||
21 | + | ||
22 | + public InvalidRequestException(RequestFailureReason reason, Throwable cause) | ||
23 | + { | ||
24 | + super(cause); | ||
25 | + this.reason = reason; | ||
26 | + } | ||
27 | + | ||
28 | + public InvalidRequestException(RequestFailureReason reason, String message, Throwable cause) | ||
29 | + { | ||
30 | + super(message, cause); | ||
31 | + this.reason = reason; | ||
32 | + } | ||
33 | + | ||
34 | + public RequestFailureReason getReason() | ||
35 | + { | ||
36 | + return this.reason; | ||
37 | + } | ||
38 | +} |
src/client/java/com/mumfrey/webprefs/exceptions/InvalidRequestKeyException.java
0 → 100644
1 | +package com.mumfrey.webprefs.exceptions; | ||
2 | + | ||
3 | +import com.mumfrey.webprefs.framework.RequestFailureReason; | ||
4 | + | ||
5 | +public class InvalidRequestKeyException extends InvalidRequestException | ||
6 | +{ | ||
7 | + private static final long serialVersionUID = 1L; | ||
8 | + | ||
9 | + public InvalidRequestKeyException() | ||
10 | + { | ||
11 | + super(RequestFailureReason.BAD_PARAMS); | ||
12 | + } | ||
13 | + | ||
14 | + public InvalidRequestKeyException(String message) | ||
15 | + { | ||
16 | + super(RequestFailureReason.BAD_PARAMS, message); | ||
17 | + } | ||
18 | + | ||
19 | + public InvalidRequestKeyException(Throwable cause) | ||
20 | + { | ||
21 | + super(RequestFailureReason.BAD_PARAMS, cause); | ||
22 | + } | ||
23 | + | ||
24 | + public InvalidRequestKeyException(String message, Throwable cause) | ||
25 | + { | ||
26 | + super(RequestFailureReason.BAD_PARAMS, message, cause); | ||
27 | + } | ||
28 | +} |
src/client/java/com/mumfrey/webprefs/exceptions/InvalidRequestValueException.java
0 → 100644
1 | +package com.mumfrey.webprefs.exceptions; | ||
2 | + | ||
3 | +import com.mumfrey.webprefs.framework.RequestFailureReason; | ||
4 | + | ||
5 | +public class InvalidRequestValueException extends InvalidRequestException | ||
6 | +{ | ||
7 | + private static final long serialVersionUID = 1L; | ||
8 | + | ||
9 | + public InvalidRequestValueException() | ||
10 | + { | ||
11 | + super(RequestFailureReason.BAD_PARAMS); | ||
12 | + } | ||
13 | + | ||
14 | + public InvalidRequestValueException(String message) | ||
15 | + { | ||
16 | + super(RequestFailureReason.BAD_PARAMS, message); | ||
17 | + } | ||
18 | + | ||
19 | + public InvalidRequestValueException(Throwable cause) | ||
20 | + { | ||
21 | + super(RequestFailureReason.BAD_PARAMS, cause); | ||
22 | + } | ||
23 | + | ||
24 | + public InvalidRequestValueException(String message, Throwable cause) | ||
25 | + { | ||
26 | + super(RequestFailureReason.BAD_PARAMS, message, cause); | ||
27 | + } | ||
28 | +} |
src/client/java/com/mumfrey/webprefs/exceptions/InvalidResponseException.java
0 → 100644
1 | +package com.mumfrey.webprefs.exceptions; | ||
2 | + | ||
3 | +import com.mumfrey.webprefs.framework.RequestFailureReason; | ||
4 | + | ||
5 | +public class InvalidResponseException extends RuntimeException | ||
6 | +{ | ||
7 | + private static final long serialVersionUID = 1L; | ||
8 | + | ||
9 | + private final RequestFailureReason response; | ||
10 | + | ||
11 | + public InvalidResponseException(RequestFailureReason response) | ||
12 | + { | ||
13 | + this.response = response; | ||
14 | + } | ||
15 | + | ||
16 | + public InvalidResponseException(RequestFailureReason response, String message) | ||
17 | + { | ||
18 | + super(message); | ||
19 | + this.response = response; | ||
20 | + } | ||
21 | + | ||
22 | + public InvalidResponseException(RequestFailureReason response, Throwable cause) | ||
23 | + { | ||
24 | + super(cause); | ||
25 | + this.response = response; | ||
26 | + } | ||
27 | + | ||
28 | + public InvalidResponseException(RequestFailureReason response, String message, Throwable cause) | ||
29 | + { | ||
30 | + super(message, cause); | ||
31 | + this.response = response; | ||
32 | + } | ||
33 | + | ||
34 | + public RequestFailureReason getReason() | ||
35 | + { | ||
36 | + return this.response; | ||
37 | + } | ||
38 | +} |
src/client/java/com/mumfrey/webprefs/exceptions/InvalidServiceException.java
0 → 100644
1 | +package com.mumfrey.webprefs.exceptions; | ||
2 | + | ||
3 | +public class InvalidServiceException extends RuntimeException | ||
4 | +{ | ||
5 | + private static final long serialVersionUID = 1L; | ||
6 | + | ||
7 | + public InvalidServiceException() | ||
8 | + { | ||
9 | + } | ||
10 | + | ||
11 | + public InvalidServiceException(String message) | ||
12 | + { | ||
13 | + super(message); | ||
14 | + } | ||
15 | + | ||
16 | + public InvalidServiceException(Throwable cause) | ||
17 | + { | ||
18 | + super(cause); | ||
19 | + } | ||
20 | + | ||
21 | + public InvalidServiceException(String message, Throwable cause) | ||
22 | + { | ||
23 | + super(message, cause); | ||
24 | + } | ||
25 | + | ||
26 | +} |
src/client/java/com/mumfrey/webprefs/exceptions/InvalidUUIDException.java
0 → 100644
1 | +package com.mumfrey.webprefs.exceptions; | ||
2 | + | ||
3 | +public class InvalidUUIDException extends RuntimeException | ||
4 | +{ | ||
5 | + private static final long serialVersionUID = 1L; | ||
6 | + | ||
7 | + public InvalidUUIDException() | ||
8 | + { | ||
9 | + } | ||
10 | + | ||
11 | + public InvalidUUIDException(String message) | ||
12 | + { | ||
13 | + super(message); | ||
14 | + } | ||
15 | + | ||
16 | + public InvalidUUIDException(Throwable cause) | ||
17 | + { | ||
18 | + super(cause); | ||
19 | + } | ||
20 | + | ||
21 | + public InvalidUUIDException(String message, Throwable cause) | ||
22 | + { | ||
23 | + super(message, cause); | ||
24 | + } | ||
25 | +} |
src/client/java/com/mumfrey/webprefs/exceptions/InvalidValueException.java
0 → 100644
1 | +package com.mumfrey.webprefs.exceptions; | ||
2 | + | ||
3 | +public class InvalidValueException extends RuntimeException | ||
4 | +{ | ||
5 | + private static final long serialVersionUID = 1L; | ||
6 | + | ||
7 | + public InvalidValueException() | ||
8 | + { | ||
9 | + } | ||
10 | + | ||
11 | + public InvalidValueException(String message) | ||
12 | + { | ||
13 | + super(message); | ||
14 | + } | ||
15 | + | ||
16 | + public InvalidValueException(Throwable cause) | ||
17 | + { | ||
18 | + super(cause); | ||
19 | + } | ||
20 | + | ||
21 | + public InvalidValueException(String message, Throwable cause) | ||
22 | + { | ||
23 | + super(message, cause); | ||
24 | + } | ||
25 | +} |
src/client/java/com/mumfrey/webprefs/exceptions/ReadOnlyPreferencesException.java
0 → 100644
1 | +package com.mumfrey.webprefs.exceptions; | ||
2 | + | ||
3 | +public class ReadOnlyPreferencesException extends InvalidPreferenceOperationException | ||
4 | +{ | ||
5 | + private static final long serialVersionUID = 1L; | ||
6 | + | ||
7 | + public ReadOnlyPreferencesException() | ||
8 | + { | ||
9 | + } | ||
10 | + | ||
11 | + public ReadOnlyPreferencesException(String message) | ||
12 | + { | ||
13 | + super(message); | ||
14 | + } | ||
15 | + | ||
16 | + public ReadOnlyPreferencesException(Throwable cause) | ||
17 | + { | ||
18 | + super(cause); | ||
19 | + } | ||
20 | + | ||
21 | + public ReadOnlyPreferencesException(String message, Throwable cause) | ||
22 | + { | ||
23 | + super(message, cause); | ||
24 | + } | ||
25 | +} |
src/client/java/com/mumfrey/webprefs/framework/RequestFailureReason.java
0 → 100644
1 | +package com.mumfrey.webprefs.framework; | ||
2 | + | ||
3 | +public enum RequestFailureReason | ||
4 | +{ | ||
5 | + UNKNOWN(1), | ||
6 | + BAD_PARAMS(1), | ||
7 | + NO_SESSION(100), | ||
8 | + SERVER_ERROR(3), | ||
9 | + UNAUTHORISED(5), | ||
10 | + THROTTLED(2), | ||
11 | + UUID_MISMATCH(10), | ||
12 | + BAD_DATA(1); | ||
13 | + | ||
14 | + private final int severity; | ||
15 | + | ||
16 | + private RequestFailureReason(int severity) | ||
17 | + { | ||
18 | + this.severity = severity; | ||
19 | + } | ||
20 | + | ||
21 | + public int getSeverity() | ||
22 | + { | ||
23 | + return this.severity; | ||
24 | + } | ||
25 | + | ||
26 | + public boolean isPermanent() | ||
27 | + { | ||
28 | + return this.severity > 99; | ||
29 | + } | ||
30 | +} |
src/client/java/com/mumfrey/webprefs/framework/WebPreferencesProvider.java
0 → 100644
1 | +package com.mumfrey.webprefs.framework; | ||
2 | + | ||
3 | +import java.net.Proxy; | ||
4 | +import java.util.Map; | ||
5 | +import java.util.Set; | ||
6 | +import java.util.concurrent.BlockingQueue; | ||
7 | +import java.util.concurrent.LinkedBlockingQueue; | ||
8 | + | ||
9 | +import net.minecraft.util.Session; | ||
10 | + | ||
11 | +import com.mumfrey.liteloader.util.log.LiteLoaderLogger; | ||
12 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesClient; | ||
13 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesProvider; | ||
14 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesService; | ||
15 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesServiceMonitor; | ||
16 | + | ||
17 | +public class WebPreferencesProvider extends Thread implements IWebPreferencesProvider, IWebPreferencesServiceMonitor | ||
18 | +{ | ||
19 | + private final IWebPreferencesService service; | ||
20 | + | ||
21 | + private final String hostName; | ||
22 | + | ||
23 | + private final Session session; | ||
24 | + | ||
25 | + private final int failureThreshold; | ||
26 | + | ||
27 | + private int failureCount = 0; | ||
28 | + | ||
29 | + private volatile boolean active = true; | ||
30 | + | ||
31 | + private final BlockingQueue<WebPreferencesServiceTask> tasks = new LinkedBlockingQueue<WebPreferencesServiceTask>(2048); | ||
32 | + | ||
33 | + public WebPreferencesProvider(Proxy proxy, Session session, String hostName, int maxFailedRequestsCount) | ||
34 | + { | ||
35 | + this.service = new WebPreferencesService(proxy, session); | ||
36 | + this.service.addMonitor(this); | ||
37 | + | ||
38 | + this.hostName = hostName; | ||
39 | + this.session = session; | ||
40 | + this.failureThreshold = maxFailedRequestsCount; | ||
41 | + | ||
42 | + this.setName("WebPreferencesProvider daemon thread [" + hostName + "]"); | ||
43 | + this.setDaemon(true); | ||
44 | + this.start(); | ||
45 | + } | ||
46 | + | ||
47 | + @Override | ||
48 | + public boolean isActive() | ||
49 | + { | ||
50 | + return this.active; | ||
51 | + } | ||
52 | + | ||
53 | + public void onTick() | ||
54 | + { | ||
55 | + } | ||
56 | + | ||
57 | + @Override | ||
58 | + public void run() | ||
59 | + { | ||
60 | + try | ||
61 | + { | ||
62 | + while (this.active) | ||
63 | + { | ||
64 | + WebPreferencesServiceTask task = this.tasks.take(); | ||
65 | + try | ||
66 | + { | ||
67 | + LiteLoaderLogger.debug("WebPreferencesProvider [%s] is processing %s for %s", this.hostName, | ||
68 | + task.getClass().getSimpleName(), task.getRequest().getUUID()); | ||
69 | + this.service.submit(task.getRequest()); | ||
70 | + } | ||
71 | + catch (Throwable th) | ||
72 | + { | ||
73 | + if (th instanceof InterruptedException) throw (InterruptedException)th; | ||
74 | + th.printStackTrace(); | ||
75 | + | ||
76 | + this.onRequestFailed(th, 1); | ||
77 | + } | ||
78 | + } | ||
79 | + } | ||
80 | + catch (InterruptedException ex) | ||
81 | + { | ||
82 | + ex.printStackTrace(); | ||
83 | + } | ||
84 | + } | ||
85 | + | ||
86 | + @Override | ||
87 | + public void onKeyRequestFailed() | ||
88 | + { | ||
89 | + this.registerError(this.failureThreshold / 2); | ||
90 | + } | ||
91 | + | ||
92 | + @Override | ||
93 | + public void onRequestFailed(Throwable th, int severity) | ||
94 | + { | ||
95 | + this.registerError(severity); | ||
96 | + } | ||
97 | + | ||
98 | + private void registerError(int severity) | ||
99 | + { | ||
100 | + this.failureCount += severity; | ||
101 | + if (this.failureCount >= this.failureThreshold) | ||
102 | + { | ||
103 | + LiteLoaderLogger.warning("WebPreferencesProvider for " + this.hostName + " is terminating. Too many failed requests."); | ||
104 | + this.active = false; | ||
105 | + this.tasks.clear(); | ||
106 | + this.interrupt(); | ||
107 | + } | ||
108 | + } | ||
109 | + | ||
110 | + @Override | ||
111 | + public boolean requestGet(IWebPreferencesClient client, String uuid, Set<String> keys, boolean getPrivate) | ||
112 | + { | ||
113 | + if (!this.isActive()) | ||
114 | + { | ||
115 | + return false; | ||
116 | + } | ||
117 | + | ||
118 | + WebPreferencesServiceTask task = new WebPreferencesServiceTaskGet(this, client); | ||
119 | + task.setRequest(new WebPreferencesRequestGet(task, uuid, keys, getPrivate)); | ||
120 | + return this.tasks.offer(task); | ||
121 | + } | ||
122 | + | ||
123 | + @Override | ||
124 | + public boolean requestSet(IWebPreferencesClient client, String uuid, Map<String, String> values, boolean setPrivate) | ||
125 | + { | ||
126 | + if (!this.isActive()) | ||
127 | + { | ||
128 | + return false; | ||
129 | + } | ||
130 | + | ||
131 | + WebPreferencesServiceTask task = new WebPreferencesServiceTaskSet(this, client); | ||
132 | + task.setRequest(new WebPreferencesRequestSet(task, uuid, values, setPrivate)); | ||
133 | + return this.tasks.offer(task); | ||
134 | + } | ||
135 | + | ||
136 | + @Override | ||
137 | + public String getHostName() | ||
138 | + { | ||
139 | + return this.hostName; | ||
140 | + } | ||
141 | + | ||
142 | + @Override | ||
143 | + public Session getSession() | ||
144 | + { | ||
145 | + return this.session; | ||
146 | + } | ||
147 | + | ||
148 | + @Override | ||
149 | + public IWebPreferencesService getService() | ||
150 | + { | ||
151 | + return this.service; | ||
152 | + } | ||
153 | +} |
src/client/java/com/mumfrey/webprefs/framework/WebPreferencesRequestAbstract.java
0 → 100644
1 | +package com.mumfrey.webprefs.framework; | ||
2 | + | ||
3 | +import java.net.URI; | ||
4 | +import java.util.HashMap; | ||
5 | +import java.util.Map; | ||
6 | +import java.util.regex.Pattern; | ||
7 | + | ||
8 | +import net.minecraft.util.Session; | ||
9 | + | ||
10 | +import com.google.gson.Gson; | ||
11 | +import com.mumfrey.webprefs.exceptions.InvalidRequestException; | ||
12 | +import com.mumfrey.webprefs.exceptions.InvalidRequestKeyException; | ||
13 | +import com.mumfrey.webprefs.exceptions.InvalidRequestValueException; | ||
14 | +import com.mumfrey.webprefs.exceptions.InvalidResponseException; | ||
15 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesRequest; | ||
16 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesResponse; | ||
17 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesServiceDelegate; | ||
18 | + | ||
19 | +abstract class WebPreferencesRequestAbstract implements IWebPreferencesRequest | ||
20 | +{ | ||
21 | + private static final long serialVersionUID = 1L; | ||
22 | + | ||
23 | + private static final Pattern keyPattern = Pattern.compile("^[a-z0-9_\\-\\.]{1,32}$"); | ||
24 | + | ||
25 | + private static final Gson gson = new Gson(); | ||
26 | + | ||
27 | + private final transient URI uri; | ||
28 | + | ||
29 | + private final transient IWebPreferencesServiceDelegate delegate; | ||
30 | + | ||
31 | + private final transient String uuid; | ||
32 | + | ||
33 | + public WebPreferencesRequestAbstract(IWebPreferencesServiceDelegate delegate, String uuid) | ||
34 | + { | ||
35 | + if (delegate == null) | ||
36 | + { | ||
37 | + throw new IllegalArgumentException("Attempted to create a request with no delegate"); | ||
38 | + } | ||
39 | + | ||
40 | + this.uri = URI.create(String.format("http://%s%s", delegate.getHostName(), this.getPath())); | ||
41 | + | ||
42 | + this.delegate = delegate; | ||
43 | + this.uuid = uuid; | ||
44 | + } | ||
45 | + | ||
46 | + protected abstract String getPath(); | ||
47 | + | ||
48 | + @Override | ||
49 | + public IWebPreferencesServiceDelegate getDelegate() | ||
50 | + { | ||
51 | + return this.delegate; | ||
52 | + } | ||
53 | + | ||
54 | + @Override | ||
55 | + public URI getRequestURI() | ||
56 | + { | ||
57 | + return this.uri; | ||
58 | + } | ||
59 | + | ||
60 | + @Override | ||
61 | + public String getUUID() | ||
62 | + { | ||
63 | + return this.uuid; | ||
64 | + } | ||
65 | + | ||
66 | + @Override | ||
67 | + public Map<String, String> getPostVars() | ||
68 | + { | ||
69 | + Map<String, String> params = new HashMap<String, String>(); | ||
70 | + this.addParams(params); | ||
71 | + return params; | ||
72 | + } | ||
73 | + | ||
74 | + protected void addParams(Map<String, String> params) | ||
75 | + { | ||
76 | + if (this.isValidationRequired()) | ||
77 | + { | ||
78 | + Session session = this.getDelegate().getSession(); | ||
79 | + if (session == null) | ||
80 | + { | ||
81 | + throw new InvalidRequestException(RequestFailureReason.NO_SESSION, "Request has no session"); | ||
82 | + } | ||
83 | + | ||
84 | + params.put("u", session.getUsername()); | ||
85 | + } | ||
86 | + | ||
87 | + params.put("i", this.uuid); | ||
88 | + params.put("j", this.toJson()); | ||
89 | + } | ||
90 | + | ||
91 | + @Override | ||
92 | + public final void onReceivedResponse(IWebPreferencesResponse response) | ||
93 | + { | ||
94 | + if (response == null) | ||
95 | + { | ||
96 | + throw new InvalidResponseException(null, "Error reading server response"); | ||
97 | + } | ||
98 | + | ||
99 | + if (response.getResponse().startsWith("500")) | ||
100 | + { | ||
101 | + throw new InvalidResponseException(RequestFailureReason.SERVER_ERROR, | ||
102 | + "The server returned an invalid resonse: " + response.getResponse(), response.getThrowable()); | ||
103 | + } | ||
104 | + | ||
105 | + if (!response.getResponse().startsWith("200")) | ||
106 | + { | ||
107 | + RequestFailureReason reason = RequestFailureReason.UNKNOWN; | ||
108 | + | ||
109 | + if (response.getResponse().startsWith("429")) reason = RequestFailureReason.THROTTLED; | ||
110 | + if (response.getResponse().startsWith("401")) reason = RequestFailureReason.UNAUTHORISED; | ||
111 | + | ||
112 | + String message = response.getMessage(); | ||
113 | + throw new InvalidResponseException(reason, | ||
114 | + "The server responsed with " + response.getResponse() + (message != null ? " \"" + message + "\"" : "")); | ||
115 | + } | ||
116 | + | ||
117 | + if (!this.getUUID().equals(response.getUUID())) | ||
118 | + { | ||
119 | + throw new InvalidResponseException(RequestFailureReason.UUID_MISMATCH, "The response UUID did not match the request"); | ||
120 | + } | ||
121 | + | ||
122 | + this.validateResponse(response); | ||
123 | + } | ||
124 | + | ||
125 | + protected abstract void validateResponse(IWebPreferencesResponse response); | ||
126 | + | ||
127 | + protected final void validateKey(String key) | ||
128 | + { | ||
129 | + if (key == null || !WebPreferencesRequestAbstract.keyPattern.matcher(key).matches()) | ||
130 | + { | ||
131 | + throw new InvalidRequestKeyException("The specified key [" + key + "] is not valid"); | ||
132 | + } | ||
133 | + } | ||
134 | + | ||
135 | + protected final void validateValue(String key, String value) | ||
136 | + { | ||
137 | + if (value == null || value.length() > 255) | ||
138 | + { | ||
139 | + throw new InvalidRequestValueException("The specified value [" + value + "] for key [" + key + "] is not valid"); | ||
140 | + } | ||
141 | + } | ||
142 | + | ||
143 | + public String toJson() | ||
144 | + { | ||
145 | + return WebPreferencesRequestAbstract.gson.toJson(this); | ||
146 | + } | ||
147 | + | ||
148 | + @Override | ||
149 | + public String toString() | ||
150 | + { | ||
151 | + try | ||
152 | + { | ||
153 | + return WebPreferencesRequestAbstract.gson.toJson(this); | ||
154 | + } | ||
155 | + catch (Throwable th) | ||
156 | + { | ||
157 | + return "{\"Invalid JSON\"}"; | ||
158 | + } | ||
159 | + } | ||
160 | +} |
src/client/java/com/mumfrey/webprefs/framework/WebPreferencesRequestGet.java
0 → 100644
1 | +package com.mumfrey.webprefs.framework; | ||
2 | + | ||
3 | +import java.util.HashSet; | ||
4 | +import java.util.Set; | ||
5 | + | ||
6 | +import com.google.gson.annotations.Expose; | ||
7 | +import com.google.gson.annotations.SerializedName; | ||
8 | +import com.mumfrey.webprefs.exceptions.InvalidRequestException; | ||
9 | +import com.mumfrey.webprefs.exceptions.InvalidResponseException; | ||
10 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesResponse; | ||
11 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesServiceDelegate; | ||
12 | + | ||
13 | +class WebPreferencesRequestGet extends WebPreferencesRequestAbstract | ||
14 | +{ | ||
15 | + private static final long serialVersionUID = 1L; | ||
16 | + | ||
17 | + @Expose @SerializedName("get") | ||
18 | + private final Set<String> keys = new HashSet<String>(); | ||
19 | + | ||
20 | + @Expose @SerializedName("private") | ||
21 | + private boolean isPrivate; | ||
22 | + | ||
23 | + public WebPreferencesRequestGet(IWebPreferencesServiceDelegate delegate, String uuid, Set<String> keys) | ||
24 | + { | ||
25 | + this(delegate, uuid, keys, false); | ||
26 | + } | ||
27 | + | ||
28 | + public WebPreferencesRequestGet(IWebPreferencesServiceDelegate delegate, String uuid, Set<String> keys, boolean isPrivate) | ||
29 | + { | ||
30 | + super(delegate, uuid); | ||
31 | + | ||
32 | + if (isPrivate && delegate.getSession() == null) | ||
33 | + { | ||
34 | + throw new InvalidRequestException(RequestFailureReason.NO_SESSION, "Cannot request private values without supplying a session"); | ||
35 | + } | ||
36 | + | ||
37 | + this.validate(keys); | ||
38 | + | ||
39 | + this.keys.addAll(keys); | ||
40 | + this.isPrivate = isPrivate; | ||
41 | + } | ||
42 | + | ||
43 | + @Override | ||
44 | + protected String getPath() | ||
45 | + { | ||
46 | + return "/get"; | ||
47 | + } | ||
48 | + | ||
49 | + @Override | ||
50 | + public boolean isValidationRequired() | ||
51 | + { | ||
52 | + return this.isPrivate; | ||
53 | + } | ||
54 | + | ||
55 | + @Override | ||
56 | + public Set<String> getKeys() | ||
57 | + { | ||
58 | + return this.keys; | ||
59 | + } | ||
60 | + | ||
61 | + @Override | ||
62 | + protected void validateResponse(IWebPreferencesResponse response) | ||
63 | + { | ||
64 | + if (response.hasValues()) | ||
65 | + { | ||
66 | + Set<String> responseKeys = response.getValues().keySet(); | ||
67 | + for (String key : this.keys) | ||
68 | + { | ||
69 | + if (!responseKeys.contains(key)) | ||
70 | + { | ||
71 | + throw new InvalidResponseException(RequestFailureReason.BAD_DATA, | ||
72 | + "The server responded with an incomplete key set, missing key [" + key + "]"); | ||
73 | + } | ||
74 | + } | ||
75 | + } | ||
76 | + } | ||
77 | + | ||
78 | + private void validate(Set<String> keys) | ||
79 | + { | ||
80 | + if (keys == null || keys.isEmpty()) | ||
81 | + { | ||
82 | + throw new InvalidRequestException(RequestFailureReason.BAD_PARAMS, "Cannot request an empty set"); | ||
83 | + } | ||
84 | + | ||
85 | + for (String key : keys) | ||
86 | + { | ||
87 | + this.validateKey(key); | ||
88 | + } | ||
89 | + } | ||
90 | +} |
src/client/java/com/mumfrey/webprefs/framework/WebPreferencesRequestKey.java
0 → 100644
1 | +package com.mumfrey.webprefs.framework; | ||
2 | + | ||
3 | +import java.util.HashSet; | ||
4 | +import java.util.Set; | ||
5 | + | ||
6 | +import net.minecraft.util.Session; | ||
7 | + | ||
8 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesRequest; | ||
9 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesResponse; | ||
10 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesServiceDelegate; | ||
11 | + | ||
12 | +public class WebPreferencesRequestKey extends WebPreferencesRequestAbstract | ||
13 | +{ | ||
14 | + private static final long serialVersionUID = 1L; | ||
15 | + | ||
16 | + protected final transient WebPreferencesService server; | ||
17 | + | ||
18 | + public WebPreferencesRequestKey(final WebPreferencesService server, final Session session, final String hostName) | ||
19 | + { | ||
20 | + super(new IWebPreferencesServiceDelegate() | ||
21 | + { | ||
22 | + @Override | ||
23 | + public void onRequestFailed(IWebPreferencesRequest request, Throwable th, RequestFailureReason reason) | ||
24 | + { | ||
25 | + server.handleKeyRequestFailed(th); | ||
26 | + } | ||
27 | + | ||
28 | + @Override | ||
29 | + public void onReceivedResponse(IWebPreferencesRequest request, IWebPreferencesResponse response) | ||
30 | + { | ||
31 | + server.handleKeyRequestCompleted(response); | ||
32 | + } | ||
33 | + | ||
34 | + @Override | ||
35 | + public Session getSession() | ||
36 | + { | ||
37 | + return session; | ||
38 | + } | ||
39 | + | ||
40 | + @Override | ||
41 | + public String getHostName() | ||
42 | + { | ||
43 | + return hostName; | ||
44 | + } | ||
45 | + }, session.getPlayerID()); | ||
46 | + | ||
47 | + this.server = server; | ||
48 | + } | ||
49 | + | ||
50 | + @Override | ||
51 | + public boolean isValidationRequired() | ||
52 | + { | ||
53 | + return true; | ||
54 | + } | ||
55 | + | ||
56 | + @Override | ||
57 | + protected String getPath() | ||
58 | + { | ||
59 | + return "/key"; | ||
60 | + } | ||
61 | + | ||
62 | + @Override | ||
63 | + protected void validateResponse(IWebPreferencesResponse response) | ||
64 | + { | ||
65 | + } | ||
66 | + | ||
67 | + @Override | ||
68 | + public Set<String> getKeys() | ||
69 | + { | ||
70 | + return new HashSet<String>(); | ||
71 | + } | ||
72 | +} |
src/client/java/com/mumfrey/webprefs/framework/WebPreferencesRequestSet.java
0 → 100644
1 | +package com.mumfrey.webprefs.framework; | ||
2 | + | ||
3 | +import java.util.HashMap; | ||
4 | +import java.util.Map; | ||
5 | +import java.util.Map.Entry; | ||
6 | +import java.util.Set; | ||
7 | + | ||
8 | +import com.google.gson.annotations.Expose; | ||
9 | +import com.google.gson.annotations.SerializedName; | ||
10 | +import com.mumfrey.webprefs.exceptions.InvalidRequestException; | ||
11 | +import com.mumfrey.webprefs.exceptions.InvalidResponseException; | ||
12 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesResponse; | ||
13 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesServiceDelegate; | ||
14 | + | ||
15 | +class WebPreferencesRequestSet extends WebPreferencesRequestAbstract | ||
16 | +{ | ||
17 | + private static final long serialVersionUID = 1L; | ||
18 | + | ||
19 | + @Expose @SerializedName("set") | ||
20 | + private final Map<String, String> map = new HashMap<String, String>(); | ||
21 | + | ||
22 | + @Expose @SerializedName("private") | ||
23 | + private boolean isPrivate; | ||
24 | + | ||
25 | + public WebPreferencesRequestSet(IWebPreferencesServiceDelegate delegate, String uuid, Map<String, String> values) | ||
26 | + { | ||
27 | + this(delegate, uuid, values, false); | ||
28 | + } | ||
29 | + | ||
30 | + public WebPreferencesRequestSet(IWebPreferencesServiceDelegate delegate, String uuid, Map<String, String> values, boolean isPrivate) | ||
31 | + { | ||
32 | + super(delegate, uuid); | ||
33 | + | ||
34 | + if (isPrivate && delegate.getSession() == null) | ||
35 | + { | ||
36 | + throw new InvalidRequestException(RequestFailureReason.NO_SESSION, "Cannot request private values without supplying a session"); | ||
37 | + } | ||
38 | + | ||
39 | + this.validate(values); | ||
40 | + | ||
41 | + this.map.putAll(values); | ||
42 | + this.isPrivate = isPrivate; | ||
43 | + } | ||
44 | + | ||
45 | + @Override | ||
46 | + protected String getPath() | ||
47 | + { | ||
48 | + return "/set"; | ||
49 | + } | ||
50 | + | ||
51 | + @Override | ||
52 | + public boolean isValidationRequired() | ||
53 | + { | ||
54 | + return true; | ||
55 | + } | ||
56 | + | ||
57 | + @Override | ||
58 | + public Set<String> getKeys() | ||
59 | + { | ||
60 | + return this.map.keySet(); | ||
61 | + } | ||
62 | + | ||
63 | + public Map<String, String> getMap() | ||
64 | + { | ||
65 | + return this.map; | ||
66 | + } | ||
67 | + | ||
68 | + @Override | ||
69 | + protected void validateResponse(IWebPreferencesResponse response) | ||
70 | + { | ||
71 | + if (response.hasSetters()) | ||
72 | + { | ||
73 | + Set<String> responseKeys = response.getSetters(); | ||
74 | + for (String key : this.map.keySet()) | ||
75 | + { | ||
76 | + if (!responseKeys.contains(key)) | ||
77 | + { | ||
78 | + throw new InvalidResponseException(RequestFailureReason.BAD_DATA, | ||
79 | + "The server responded with an incomplete key set, missing key [" + key + "]"); | ||
80 | + } | ||
81 | + } | ||
82 | + } | ||
83 | + } | ||
84 | + | ||
85 | + private void validate(Map<String, String> set) | ||
86 | + { | ||
87 | + for (Entry<String, String> entry : set.entrySet()) | ||
88 | + { | ||
89 | + this.validateKey(entry.getKey()); | ||
90 | + this.validateValue(entry.getKey(), entry.getValue()); | ||
91 | + } | ||
92 | + } | ||
93 | +} |
src/client/java/com/mumfrey/webprefs/framework/WebPreferencesResponse.java
0 → 100644
1 | +package com.mumfrey.webprefs.framework; | ||
2 | + | ||
3 | +import java.util.HashSet; | ||
4 | +import java.util.List; | ||
5 | +import java.util.Map; | ||
6 | +import java.util.Set; | ||
7 | + | ||
8 | +import com.google.gson.Gson; | ||
9 | +import com.google.gson.GsonBuilder; | ||
10 | +import com.google.gson.JsonSyntaxException; | ||
11 | +import com.google.gson.annotations.Expose; | ||
12 | +import com.google.gson.annotations.SerializedName; | ||
13 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesResponse; | ||
14 | + | ||
15 | +class WebPreferencesResponse implements IWebPreferencesResponse | ||
16 | +{ | ||
17 | + private static final long serialVersionUID = 1L; | ||
18 | + | ||
19 | + private static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); | ||
20 | + | ||
21 | + @Expose @SerializedName("response") | ||
22 | + private String response; | ||
23 | + | ||
24 | + @Expose @SerializedName("message") | ||
25 | + private String message; | ||
26 | + | ||
27 | + @Expose @SerializedName("uuid") | ||
28 | + private String uuid; | ||
29 | + | ||
30 | + @Expose @SerializedName("serverid") | ||
31 | + private String serverId; | ||
32 | + | ||
33 | + @Expose @SerializedName("rate") | ||
34 | + private int rateLimit; | ||
35 | + | ||
36 | + @Expose @SerializedName("get") | ||
37 | + private Map<String, String> get; | ||
38 | + | ||
39 | + @Expose @SerializedName("set") | ||
40 | + private List<String> set; | ||
41 | + | ||
42 | + private transient Throwable th; | ||
43 | + | ||
44 | + public WebPreferencesResponse() {} | ||
45 | + | ||
46 | + private WebPreferencesResponse(String response, Throwable th) | ||
47 | + { | ||
48 | + this.response = response; | ||
49 | + this.th = th; | ||
50 | + } | ||
51 | + | ||
52 | + @Override | ||
53 | + public String getResponse() | ||
54 | + { | ||
55 | + return this.response; | ||
56 | + } | ||
57 | + | ||
58 | + @Override | ||
59 | + public String getMessage() | ||
60 | + { | ||
61 | + return this.message; | ||
62 | + } | ||
63 | + | ||
64 | + @Override | ||
65 | + public Throwable getThrowable() | ||
66 | + { | ||
67 | + return this.th; | ||
68 | + } | ||
69 | + | ||
70 | + @Override | ||
71 | + public String getUUID() | ||
72 | + { | ||
73 | + return this.uuid; | ||
74 | + } | ||
75 | + | ||
76 | + @Override | ||
77 | + public String getServerId() | ||
78 | + { | ||
79 | + return this.serverId; | ||
80 | + } | ||
81 | + | ||
82 | + @Override | ||
83 | + public boolean hasValues() | ||
84 | + { | ||
85 | + return this.get != null; | ||
86 | + } | ||
87 | + | ||
88 | + @Override | ||
89 | + public Map<String, String> getValues() | ||
90 | + { | ||
91 | + return this.get; | ||
92 | + } | ||
93 | + | ||
94 | + @Override | ||
95 | + public boolean hasSetters() | ||
96 | + { | ||
97 | + return this.set != null; | ||
98 | + } | ||
99 | + | ||
100 | + @Override | ||
101 | + public Set<String> getSetters() | ||
102 | + { | ||
103 | + return new HashSet<String>(this.set); | ||
104 | + } | ||
105 | + | ||
106 | + public static IWebPreferencesResponse fromJson(String json) | ||
107 | + { | ||
108 | + try | ||
109 | + { | ||
110 | + return WebPreferencesResponse.gson.fromJson(json, WebPreferencesResponse.class); | ||
111 | + } | ||
112 | + catch (JsonSyntaxException ex) | ||
113 | + { | ||
114 | + return new WebPreferencesResponse("500 Invalid JSON", ex); | ||
115 | + } | ||
116 | + catch (Throwable th) | ||
117 | + { | ||
118 | + return new WebPreferencesResponse("500 Invalid JSON", th); | ||
119 | + } | ||
120 | + } | ||
121 | + | ||
122 | + @Override | ||
123 | + public String toString() | ||
124 | + { | ||
125 | + try | ||
126 | + { | ||
127 | + return WebPreferencesResponse.gson.toJson(this); | ||
128 | + } | ||
129 | + catch (Throwable th) | ||
130 | + { | ||
131 | + return "{\"Invalid JSON\"}"; | ||
132 | + } | ||
133 | + } | ||
134 | +} |
src/client/java/com/mumfrey/webprefs/framework/WebPreferencesService.java
0 → 100644
1 | +package com.mumfrey.webprefs.framework; | ||
2 | + | ||
3 | +import java.io.BufferedReader; | ||
4 | +import java.io.IOException; | ||
5 | +import java.io.InputStream; | ||
6 | +import java.io.InputStreamReader; | ||
7 | +import java.io.OutputStream; | ||
8 | +import java.io.UnsupportedEncodingException; | ||
9 | +import java.net.HttpURLConnection; | ||
10 | +import java.net.Proxy; | ||
11 | +import java.net.URI; | ||
12 | +import java.net.URL; | ||
13 | +import java.net.URLEncoder; | ||
14 | +import java.util.ArrayList; | ||
15 | +import java.util.HashMap; | ||
16 | +import java.util.List; | ||
17 | +import java.util.Map; | ||
18 | +import java.util.Map.Entry; | ||
19 | + | ||
20 | +import net.minecraft.util.Session; | ||
21 | + | ||
22 | +import org.apache.commons.io.IOUtils; | ||
23 | + | ||
24 | +import com.google.common.base.Charsets; | ||
25 | +import com.google.gson.Gson; | ||
26 | +import com.mumfrey.liteloader.util.log.LiteLoaderLogger; | ||
27 | +import com.mumfrey.webprefs.exceptions.InvalidRequestException; | ||
28 | +import com.mumfrey.webprefs.exceptions.InvalidResponseException; | ||
29 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesRequest; | ||
30 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesResponse; | ||
31 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesService; | ||
32 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesServiceMonitor; | ||
33 | + | ||
34 | +class WebPreferencesService implements IWebPreferencesService | ||
35 | +{ | ||
36 | + private static final int TIMEOUT_MSEC = 5000; | ||
37 | + | ||
38 | + private final Proxy proxy; | ||
39 | + | ||
40 | + private final Session session; | ||
41 | + | ||
42 | + private final Map<String, String> serverKeys = new HashMap<String, String>(); | ||
43 | + | ||
44 | + private final List<IWebPreferencesServiceMonitor> monitors = new ArrayList<IWebPreferencesServiceMonitor>(); | ||
45 | + | ||
46 | + private long lastMojangAuth = 0L; | ||
47 | + | ||
48 | + WebPreferencesService(Proxy proxy, Session session) | ||
49 | + { | ||
50 | + this.proxy = proxy; | ||
51 | + this.session = session; | ||
52 | + } | ||
53 | + | ||
54 | + @Override | ||
55 | + public void addMonitor(IWebPreferencesServiceMonitor monitor) | ||
56 | + { | ||
57 | + if (!this.monitors.contains(monitor)) | ||
58 | + { | ||
59 | + this.monitors.add(monitor); | ||
60 | + } | ||
61 | + } | ||
62 | + | ||
63 | + void handleKeyRequestFailed(Throwable th) | ||
64 | + { | ||
65 | + LiteLoaderLogger.debug(th, "Key request failed with message %s", th.getMessage()); | ||
66 | + | ||
67 | + for (IWebPreferencesServiceMonitor monitor : this.monitors) | ||
68 | + { | ||
69 | + monitor.onKeyRequestFailed(); | ||
70 | + } | ||
71 | + } | ||
72 | + | ||
73 | + void handleKeyRequestCompleted(IWebPreferencesResponse response) | ||
74 | + { | ||
75 | + } | ||
76 | + | ||
77 | + @Override | ||
78 | + public void submit(IWebPreferencesRequest request) | ||
79 | + { | ||
80 | + try | ||
81 | + { | ||
82 | + this.beginProcessingRequest(request); | ||
83 | + } | ||
84 | + catch (InvalidRequestException ex) | ||
85 | + { | ||
86 | + request.getDelegate().onRequestFailed(request, ex, ex.getReason()); | ||
87 | + } | ||
88 | + } | ||
89 | + | ||
90 | + private IWebPreferencesResponse beginProcessingRequest(IWebPreferencesRequest request) throws InvalidRequestException | ||
91 | + { | ||
92 | + LiteLoaderLogger.debug("WebPreferencesService is processing %s for %s", request.getClass().getSimpleName(), request.getUUID()); | ||
93 | + | ||
94 | + if (request.isValidationRequired()) | ||
95 | + { | ||
96 | + String requestClass = request.getClass().getSimpleName(); | ||
97 | + | ||
98 | + Session session = request.getDelegate().getSession(); | ||
99 | + if (session == null) | ||
100 | + { | ||
101 | + throw new InvalidRequestException(RequestFailureReason.NO_SESSION, | ||
102 | + "Validation is required for " + requestClass + " but no session was provided."); | ||
103 | + } | ||
104 | + | ||
105 | + String serverId = this.getServerIdForRequest(request); | ||
106 | + | ||
107 | + if (!this.registerServerConnection(session, serverId)) | ||
108 | + { | ||
109 | + throw new InvalidRequestException(RequestFailureReason.NO_SESSION, | ||
110 | + "Validation is required for " + requestClass + " but no session was provided or session validation failed"); | ||
111 | + } | ||
112 | + } | ||
113 | + | ||
114 | + return this.processRequest(request); | ||
115 | + } | ||
116 | + | ||
117 | + private IWebPreferencesResponse processRequest(IWebPreferencesRequest request) | ||
118 | + { | ||
119 | + try | ||
120 | + { | ||
121 | + String data = this.httpPost(request.getRequestURI(), request.getPostVars()); | ||
122 | + IWebPreferencesResponse response = WebPreferencesResponse.fromJson(data); | ||
123 | + | ||
124 | + LiteLoaderLogger.debug("Response: %s", response); | ||
125 | + request.onReceivedResponse(response); | ||
126 | + | ||
127 | + request.getDelegate().onReceivedResponse(request, response); | ||
128 | + return response; | ||
129 | + } | ||
130 | + catch (InvalidResponseException ex) | ||
131 | + { | ||
132 | + request.getDelegate().onRequestFailed(request, ex, ex.getReason()); | ||
133 | + | ||
134 | + for (IWebPreferencesServiceMonitor monitor : this.monitors) | ||
135 | + { | ||
136 | + monitor.onRequestFailed(ex, ex.getReason().getSeverity()); | ||
137 | + } | ||
138 | + } | ||
139 | + catch (IOException ex) | ||
140 | + { | ||
141 | + request.getDelegate().onRequestFailed(request, ex, RequestFailureReason.SERVER_ERROR); | ||
142 | + | ||
143 | + for (IWebPreferencesServiceMonitor monitor : this.monitors) | ||
144 | + { | ||
145 | + monitor.onRequestFailed(ex, RequestFailureReason.SERVER_ERROR.getSeverity()); | ||
146 | + } | ||
147 | + } | ||
148 | + catch (Exception ex) | ||
149 | + { | ||
150 | + for (IWebPreferencesServiceMonitor monitor : this.monitors) | ||
151 | + { | ||
152 | + monitor.onRequestFailed(ex, RequestFailureReason.UNKNOWN.getSeverity()); | ||
153 | + } | ||
154 | + } | ||
155 | + | ||
156 | + return null; | ||
157 | + } | ||
158 | + | ||
159 | + private String getServerIdForRequest(IWebPreferencesRequest request) | ||
160 | + { | ||
161 | + if (request.getDelegate().getSession() == null) | ||
162 | + { | ||
163 | + return null; | ||
164 | + } | ||
165 | + | ||
166 | + String hostName = request.getDelegate().getHostName(); | ||
167 | + String serverId = this.serverKeys.get(hostName); | ||
168 | + | ||
169 | + if (serverId == null) | ||
170 | + { | ||
171 | + LiteLoaderLogger.info("Looking up server ID for " + hostName); | ||
172 | + WebPreferencesRequestKey keyRequest = new WebPreferencesRequestKey(this, this.session, hostName); | ||
173 | + IWebPreferencesResponse response = this.processRequest(keyRequest); | ||
174 | + if (response == null || response.getServerId() == null) | ||
175 | + { | ||
176 | + throw new InvalidRequestException(RequestFailureReason.SERVER_ERROR, "Could not retrieve server ID for " + hostName); | ||
177 | + } | ||
178 | + | ||
179 | + serverId = response.getServerId(); | ||
180 | + this.serverKeys.put(hostName, serverId); | ||
181 | + | ||
182 | + LiteLoaderLogger.info("Got server ID for " + hostName + " [" + serverId + "]"); | ||
183 | + } | ||
184 | + | ||
185 | + return serverId; | ||
186 | + } | ||
187 | + | ||
188 | + public String httpPost(URI uri, Map<String, String> params) throws IOException | ||
189 | + { | ||
190 | + String query = this.buildQuery(params); | ||
191 | + byte[] queryBytes = query.getBytes(Charsets.UTF_8); | ||
192 | + | ||
193 | + LiteLoaderLogger.debug("Connecting to " + uri); | ||
194 | + HttpURLConnection http = (HttpURLConnection)uri.toURL().openConnection(this.proxy); | ||
195 | + http.setConnectTimeout(WebPreferencesService.TIMEOUT_MSEC); | ||
196 | + http.setReadTimeout(WebPreferencesService.TIMEOUT_MSEC); | ||
197 | + http.setUseCaches(false); | ||
198 | + http.setDoOutput(true); | ||
199 | + | ||
200 | + http.addRequestProperty("Content-type", "application/x-www-form-urlencoded"); | ||
201 | + http.setRequestProperty("Content-Length", "" + queryBytes.length); | ||
202 | + | ||
203 | + OutputStream outputStream = null; | ||
204 | + | ||
205 | + try | ||
206 | + { | ||
207 | + outputStream = http.getOutputStream(); | ||
208 | + IOUtils.write(queryBytes, outputStream); | ||
209 | + } | ||
210 | + finally | ||
211 | + { | ||
212 | + IOUtils.closeQuietly(outputStream); | ||
213 | + } | ||
214 | + | ||
215 | + try | ||
216 | + { | ||
217 | + String debugMessages = http.getHeaderField("X-Debug-Message"); | ||
218 | + if (debugMessages != null) | ||
219 | + { | ||
220 | + String[] messages = new Gson().fromJson(debugMessages, String[].class); | ||
221 | + for (String message : messages) | ||
222 | + { | ||
223 | + LiteLoaderLogger.debug("[SERVER] %s", message); | ||
224 | + } | ||
225 | + } | ||
226 | + } | ||
227 | + catch (Exception ex) {} | ||
228 | + | ||
229 | + InputStream inputStream = null; | ||
230 | + | ||
231 | + try | ||
232 | + { | ||
233 | + try | ||
234 | + { | ||
235 | + inputStream = http.getInputStream(); | ||
236 | + String response = IOUtils.toString(inputStream, Charsets.UTF_8); | ||
237 | + return response; | ||
238 | + } | ||
239 | + catch (IOException ex) | ||
240 | + { | ||
241 | + IOUtils.closeQuietly(inputStream); | ||
242 | + inputStream = http.getErrorStream(); | ||
243 | + if (inputStream == null) | ||
244 | + { | ||
245 | + return this.formatErrorAsJson(http.getResponseCode() + " " + http.getResponseMessage(), ex.getMessage()); | ||
246 | + } | ||
247 | + | ||
248 | + String response = IOUtils.toString(inputStream, Charsets.UTF_8); | ||
249 | + | ||
250 | + String contentType = http.getHeaderField("Content-type"); | ||
251 | + if (!"application/json".equals(contentType)) | ||
252 | + { | ||
253 | + System.err.println(response); | ||
254 | + return this.formatErrorAsJson(http.getResponseCode() + " " + http.getResponseMessage(), "Invalid content type " + contentType); | ||
255 | + } | ||
256 | + | ||
257 | + return response; | ||
258 | + } | ||
259 | + } | ||
260 | + finally | ||
261 | + { | ||
262 | + IOUtils.closeQuietly(inputStream); | ||
263 | + } | ||
264 | + } | ||
265 | + | ||
266 | + private String formatErrorAsJson(String response, String message) | ||
267 | + { | ||
268 | + return String.format("{\"response\":\"%s\",\"message\":\"%s\"}", response, message); | ||
269 | + } | ||
270 | + | ||
271 | + private String buildQuery(Map<String, String> params) | ||
272 | + { | ||
273 | + StringBuilder sb = new StringBuilder(); | ||
274 | + | ||
275 | + try | ||
276 | + { | ||
277 | + String separator = ""; | ||
278 | + for (Entry<String, String> postValue : params.entrySet()) | ||
279 | + { | ||
280 | + sb.append(separator).append(postValue.getKey()).append("=").append(URLEncoder.encode(postValue.getValue(), "UTF-8")); | ||
281 | + separator = "&"; | ||
282 | + } | ||
283 | + } | ||
284 | + catch (UnsupportedEncodingException ex) | ||
285 | + { | ||
286 | + ex.printStackTrace(); | ||
287 | + } | ||
288 | + | ||
289 | + return sb.toString(); | ||
290 | + } | ||
291 | + | ||
292 | + private boolean registerServerConnection(Session session, String serverId) | ||
293 | + { | ||
294 | + if (session == null || serverId == null) | ||
295 | + { | ||
296 | + return false; | ||
297 | + } | ||
298 | + | ||
299 | + if (System.currentTimeMillis() - this.lastMojangAuth < 300000L) | ||
300 | + { | ||
301 | + LiteLoaderLogger.debug("Mojang connection is still fresh, using existing ticket"); | ||
302 | + return true; | ||
303 | + } | ||
304 | + | ||
305 | + try | ||
306 | + { | ||
307 | + LiteLoaderLogger.debug("Creating Mojang session ticket..."); | ||
308 | + URL checkServerUrl = new URL("http://session.minecraft.net/game/joinserver.jsp?user=" + URLEncoder.encode(session.getUsername(), "UTF-8") + "&sessionId=" + URLEncoder.encode(session.getSessionID(), "UTF-8") + "&serverId=" + URLEncoder.encode(serverId, "UTF-8")); | ||
309 | + BufferedReader responseReader = new BufferedReader(new InputStreamReader(checkServerUrl.openStream())); | ||
310 | + String response = responseReader.readLine(); | ||
311 | + responseReader.close(); | ||
312 | + boolean joinSuccess = "OK".equals(response); | ||
313 | + if (joinSuccess) | ||
314 | + { | ||
315 | + this.lastMojangAuth = System.currentTimeMillis(); | ||
316 | + return true; | ||
317 | + } | ||
318 | + } | ||
319 | + catch (IOException ex) | ||
320 | + { | ||
321 | + ex.printStackTrace(); | ||
322 | + LiteLoaderLogger.debug("Failed to log on to invoke joinserver, connection to mojang failed"); | ||
323 | + throw new InvalidRequestException(RequestFailureReason.SERVER_ERROR, "Failed registering server connection with Mojang"); | ||
324 | + } | ||
325 | + | ||
326 | + return false; | ||
327 | + } | ||
328 | +} |
src/client/java/com/mumfrey/webprefs/framework/WebPreferencesServiceTask.java
0 → 100644
1 | +package com.mumfrey.webprefs.framework; | ||
2 | + | ||
3 | +import net.minecraft.util.Session; | ||
4 | + | ||
5 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesClient; | ||
6 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesProvider; | ||
7 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesRequest; | ||
8 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesServiceDelegate; | ||
9 | + | ||
10 | +abstract class WebPreferencesServiceTask implements IWebPreferencesServiceDelegate | ||
11 | +{ | ||
12 | + private final IWebPreferencesProvider provider; | ||
13 | + | ||
14 | + private final IWebPreferencesClient client; | ||
15 | + | ||
16 | + private IWebPreferencesRequest request; | ||
17 | + | ||
18 | + WebPreferencesServiceTask(IWebPreferencesProvider provider, IWebPreferencesClient client) | ||
19 | + { | ||
20 | + this.provider = provider; | ||
21 | + this.client = client; | ||
22 | + } | ||
23 | + | ||
24 | + public IWebPreferencesClient getClient() | ||
25 | + { | ||
26 | + return this.client; | ||
27 | + } | ||
28 | + | ||
29 | + public IWebPreferencesRequest getRequest() | ||
30 | + { | ||
31 | + return this.request; | ||
32 | + } | ||
33 | + | ||
34 | + public void setRequest(IWebPreferencesRequest request) | ||
35 | + { | ||
36 | + this.request = request; | ||
37 | + } | ||
38 | + | ||
39 | + @Override | ||
40 | + public String getHostName() | ||
41 | + { | ||
42 | + return this.provider.getHostName(); | ||
43 | + } | ||
44 | + | ||
45 | + @Override | ||
46 | + public Session getSession() | ||
47 | + { | ||
48 | + return this.provider.getSession(); | ||
49 | + } | ||
50 | + | ||
51 | + @Override | ||
52 | + public String toString() | ||
53 | + { | ||
54 | + return String.format("%s[%s]", this.getClass().getSimpleName(), this.request); | ||
55 | + } | ||
56 | +} |
src/client/java/com/mumfrey/webprefs/framework/WebPreferencesServiceTaskGet.java
0 → 100644
1 | +package com.mumfrey.webprefs.framework; | ||
2 | + | ||
3 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesClient; | ||
4 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesProvider; | ||
5 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesRequest; | ||
6 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesResponse; | ||
7 | + | ||
8 | +class WebPreferencesServiceTaskGet extends WebPreferencesServiceTask | ||
9 | +{ | ||
10 | + WebPreferencesServiceTaskGet(IWebPreferencesProvider provider, IWebPreferencesClient client) | ||
11 | + { | ||
12 | + super(provider, client); | ||
13 | + } | ||
14 | + | ||
15 | + @Override | ||
16 | + public void onReceivedResponse(IWebPreferencesRequest request, IWebPreferencesResponse response) | ||
17 | + { | ||
18 | + IWebPreferencesClient client = this.getClient(); | ||
19 | + if (client != null && response.hasValues()) | ||
20 | + { | ||
21 | + client.onGetRequestSuccess(response.getUUID(), response.getValues()); | ||
22 | + } | ||
23 | + } | ||
24 | + | ||
25 | + @Override | ||
26 | + public void onRequestFailed(IWebPreferencesRequest request, Throwable th, RequestFailureReason reason) | ||
27 | + { | ||
28 | + IWebPreferencesClient client = this.getClient(); | ||
29 | + if (client != null) | ||
30 | + { | ||
31 | + client.onGetRequestFailed(request.getUUID(), request.getKeys(), reason); | ||
32 | + } | ||
33 | + } | ||
34 | +} |
src/client/java/com/mumfrey/webprefs/framework/WebPreferencesServiceTaskSet.java
0 → 100644
1 | +package com.mumfrey.webprefs.framework; | ||
2 | + | ||
3 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesClient; | ||
4 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesProvider; | ||
5 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesRequest; | ||
6 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesResponse; | ||
7 | + | ||
8 | +class WebPreferencesServiceTaskSet extends WebPreferencesServiceTask | ||
9 | +{ | ||
10 | + WebPreferencesServiceTaskSet(IWebPreferencesProvider provider, IWebPreferencesClient client) | ||
11 | + { | ||
12 | + super(provider, client); | ||
13 | + } | ||
14 | + | ||
15 | + @Override | ||
16 | + public void onReceivedResponse(IWebPreferencesRequest request, IWebPreferencesResponse response) | ||
17 | + { | ||
18 | + IWebPreferencesClient client = this.getClient(); | ||
19 | + if (client != null && response.hasSetters()) | ||
20 | + { | ||
21 | + client.onSetRequestSuccess(response.getUUID(), response.getSetters()); | ||
22 | + } | ||
23 | + } | ||
24 | + | ||
25 | + @Override | ||
26 | + public void onRequestFailed(IWebPreferencesRequest request, Throwable th, RequestFailureReason reason) | ||
27 | + { | ||
28 | + IWebPreferencesClient client = this.getClient(); | ||
29 | + if (client != null) | ||
30 | + { | ||
31 | + client.onSetRequestFailed(request.getUUID(), request.getKeys(), reason); | ||
32 | + } | ||
33 | + } | ||
34 | +} |
src/client/java/com/mumfrey/webprefs/interfaces/IReliableWebPreferences.java
0 → 100644
1 | +package com.mumfrey.webprefs.interfaces; | ||
2 | + | ||
3 | +/** | ||
4 | + * Reliable WebPreferences, no this isn't implemented yet | ||
5 | + * | ||
6 | + * @author Adam Mummery-Smith | ||
7 | + */ | ||
8 | +public interface IReliableWebPreferences extends IWebPreferences | ||
9 | +{ | ||
10 | + public void isSynchronised(String key); | ||
11 | + | ||
12 | + public void setWithNotify(String key, String value, IWebPreferencesListener listener); | ||
13 | +} |
src/client/java/com/mumfrey/webprefs/interfaces/IWebPreferences.java
0 → 100644
1 | +package com.mumfrey.webprefs.interfaces; | ||
2 | + | ||
3 | +import java.util.Set; | ||
4 | + | ||
5 | +/** | ||
6 | + * Web-based preferences, objects implementing this interface represent a remote | ||
7 | + * asychronous Key/Value store which fetches and commits values on a best-effort | ||
8 | + * basis. | ||
9 | + * | ||
10 | + * <p>Values in this store are not guaranteed to be correct, nor are values | ||
11 | + * written to the store guaranteed to be successfully written to the store. | ||
12 | + * | ||
13 | + * <p>In general, consumers should call {@link #get} and {@link #set} with the | ||
14 | + * assumption that reads and writes will happen asynchronously, eg. they should | ||
15 | + * continually poll {@link #get} rather than caching any responses returned | ||
16 | + * from the preferences object.</p> | ||
17 | + * | ||
18 | + * <p>Behaviour is such that any call to {@link #has} or {@link #get} will | ||
19 | + * trigger asynchronous retrieval of properties from the backend server. | ||
20 | + * Likewise calls to {@link #set} will trigger asychronous commit of dirty | ||
21 | + * values (both reads and sets are batched and sent to the server as a single | ||
22 | + * request where possible).</p> | ||
23 | + * | ||
24 | + * <p>Keys in the collection are restricted to lowercase letters and the period | ||
25 | + * symbol only, failure to adhere to this format will cause any accessor methods | ||
26 | + * to throw InvalidKeyException. Values in the collection are strings and are | ||
27 | + * limited to 255 characters and must not be null, failure to adhere to these | ||
28 | + * restrictions will cause accessors to throw {@link InvalidValueException}.</p> | ||
29 | + * | ||
30 | + * <p>Consumers can trigger synchronisation events on the collection using | ||
31 | + * convenience methods, these methods do not ensure propagation of settings but | ||
32 | + * act can be used to alter the normal behaviour of the set when necessary, for | ||
33 | + * example to poll for updated values periodically. The methods request(), | ||
34 | + * poll() and commit() can be used to trigger synchronisation events, consult | ||
35 | + * the javadoc for each method for more details.</p> | ||
36 | + * | ||
37 | + * @author Adam Mummery-Smith | ||
38 | + */ | ||
39 | +public interface IWebPreferences | ||
40 | +{ | ||
41 | + /** | ||
42 | + * Get the UUID assoicated with this preference collection | ||
43 | + */ | ||
44 | + public abstract String getUUID(); | ||
45 | + | ||
46 | + /** | ||
47 | + * Get whether this collection is private or public | ||
48 | + */ | ||
49 | + public abstract boolean isPrivate(); | ||
50 | + | ||
51 | + /** | ||
52 | + * Get whether this collection is read-only. Only the player's local | ||
53 | + * preferences are writable. Any attempt to write to a read-only collection | ||
54 | + * will throw a ReadOnlyPreferencesException. | ||
55 | + */ | ||
56 | + public abstract boolean isReadOnly(); | ||
57 | + | ||
58 | + /** | ||
59 | + * Indicate to the set that the value specified by key is required an | ||
60 | + * should be fetched from the server in the next batch. In general, | ||
61 | + * consumers should use get() or has() to access the values in this | ||
62 | + * collection, and expect that the collection will manage fetching the | ||
63 | + * values from the server as part of the normal update cycle. However this | ||
64 | + * method has two uses:</p> | ||
65 | + * | ||
66 | + * <ul> | ||
67 | + * <li>You may choose to call {@link #request} when a handle to a | ||
68 | + * preferences set is obtained, to indicate to the preferences set that | ||
69 | + * you will require the value in the future and it should attempt to fetch | ||
70 | + * the value so that it is ready for queries against the set later.</li> | ||
71 | + * <li>You may wish to indicate to the set that it should request an | ||
72 | + * updated value from the server for a key which it already has (normally | ||
73 | + * values are cached indefinitely).</li> | ||
74 | + * </ul> | ||
75 | + * | ||
76 | + * <p>In both cases, calls to request() should generally be limited to | ||
77 | + * periodic request for values, to avoid constantly polling the server for | ||
78 | + * values, and potentially running foul of request throttling at the server | ||
79 | + * side</p> | ||
80 | + * | ||
81 | + * @param key key to request from the server | ||
82 | + */ | ||
83 | + public abstract void request(String key); | ||
84 | + | ||
85 | + /** | ||
86 | + * Works exactly as {@link IWebPreferences#request(String)} but allows | ||
87 | + * multiple values to be requested in a single invocation | ||
88 | + * | ||
89 | + * @param keys keys to request | ||
90 | + */ | ||
91 | + public abstract void request(String... keys); | ||
92 | + | ||
93 | + /** | ||
94 | + * Works exactly as {@link IWebPreferences#request(String)} but allows | ||
95 | + * multiple values to be requested in a single invocation | ||
96 | + * | ||
97 | + * @param keys keys to request | ||
98 | + */ | ||
99 | + public abstract void request(Set<String> keys); | ||
100 | + | ||
101 | + /** | ||
102 | + * Calling <tt>poll()</tt> is essentially the same as calling | ||
103 | + * {@link #request} for every key currently in the collection, this method | ||
104 | + * can essentially be used to refresh the entire collection and should be | ||
105 | + * called only in exceptional circumstances. It is called automatically by | ||
106 | + * the preferences manager when connecting to a server. | ||
107 | + * | ||
108 | + * <p>Invoking <tt>poll()</tt> causes all of the values in the collection to | ||
109 | + * be requested as a single batch</p> | ||
110 | + */ | ||
111 | + public abstract void poll(); | ||
112 | + | ||
113 | + /** | ||
114 | + * Similar to {@link poll} except for property writes instead of property | ||
115 | + * reads. Under normal circumstances it should not be necessary to call this | ||
116 | + * method, since commits are handled asychronously by the update loop. | ||
117 | + * However it can be used to forcibly commit even "clean" values to the | ||
118 | + * server. This is useful if you wish to ensure that values on the server be | ||
119 | + * set to match the current values in the collection, regardless of | ||
120 | + * comodification elsewhere. | ||
121 | + * | ||
122 | + * @param force If set to false, only dirty properties are batched. If true, | ||
123 | + * all properties are batched. | ||
124 | + */ | ||
125 | + public abstract void commit(boolean force); | ||
126 | + | ||
127 | + /** | ||
128 | + * Get whether this collection has a value for the specified key, and | ||
129 | + * triggers asnchronous retrieval if not | ||
130 | + * | ||
131 | + * @param key Key to check for | ||
132 | + * @return | ||
133 | + */ | ||
134 | + public abstract boolean has(String key); | ||
135 | + | ||
136 | + /** | ||
137 | + * Get the value for the specified key from this collection. Invoking this | ||
138 | + * method causes asynchronous retrieval of the specified key if it is not | ||
139 | + * found in the collection and returns null. If the key is found, then the | ||
140 | + * value is returned and the collection remains unchanged. | ||
141 | + * | ||
142 | + * @param key | ||
143 | + * @return | ||
144 | + */ | ||
145 | + public abstract String get(String key); | ||
146 | + | ||
147 | + /** | ||
148 | + * Works exactly like get(String), including triggering asynchronous | ||
149 | + * retrieval of the property if the property is not found in the collection, | ||
150 | + * except that if the property is not found the specified defaultValue is | ||
151 | + * returned instead of returning null. | ||
152 | + * | ||
153 | + * @param key | ||
154 | + * @param defaultValue | ||
155 | + * @return | ||
156 | + */ | ||
157 | + public abstract String get(String key, String defaultValue); | ||
158 | + | ||
159 | + /** | ||
160 | + * Sets a value in the collection and marks it for asynchronous commit to | ||
161 | + * the server. | ||
162 | + * | ||
163 | + * @param key | ||
164 | + * @param value | ||
165 | + */ | ||
166 | + public abstract void set(String key, String value); | ||
167 | + | ||
168 | + /** | ||
169 | + * Remove a key from this collection. Marks the key to be deleted from the | ||
170 | + * server as well. | ||
171 | + * | ||
172 | + * @param key | ||
173 | + */ | ||
174 | + public abstract void remove(String key); | ||
175 | +} |
src/client/java/com/mumfrey/webprefs/interfaces/IWebPreferencesClient.java
0 → 100644
1 | +package com.mumfrey.webprefs.interfaces; | ||
2 | + | ||
3 | +import java.util.Map; | ||
4 | +import java.util.Set; | ||
5 | + | ||
6 | +import com.mumfrey.webprefs.framework.RequestFailureReason; | ||
7 | + | ||
8 | +public interface IWebPreferencesClient | ||
9 | +{ | ||
10 | + public abstract void onGetRequestSuccess(String uuid, Map<String, String> values); | ||
11 | + | ||
12 | + public abstract void onSetRequestSuccess(String uuid, Set<String> keys); | ||
13 | + | ||
14 | + public abstract void onGetRequestFailed(String uuid, Set<String> keys, RequestFailureReason reason); | ||
15 | + | ||
16 | + public abstract void onSetRequestFailed(String uuid, Set<String> keys, RequestFailureReason reason); | ||
17 | +} |
src/client/java/com/mumfrey/webprefs/interfaces/IWebPreferencesListener.java
0 → 100644
1 | +package com.mumfrey.webprefs.interfaces; | ||
2 | + | ||
3 | +import java.util.Set; | ||
4 | + | ||
5 | +public interface IWebPreferencesListener | ||
6 | +{ | ||
7 | + public abstract void onCommitSuccess(IReliableWebPreferences preferences, Set<String> keys); | ||
8 | + | ||
9 | + public abstract void onCommitFailed(IReliableWebPreferences preferences, Set<String> keys); | ||
10 | +} |
src/client/java/com/mumfrey/webprefs/interfaces/IWebPreferencesProvider.java
0 → 100644
1 | +package com.mumfrey.webprefs.interfaces; | ||
2 | + | ||
3 | +import java.util.Map; | ||
4 | +import java.util.Set; | ||
5 | + | ||
6 | +import net.minecraft.util.Session; | ||
7 | + | ||
8 | +public interface IWebPreferencesProvider | ||
9 | +{ | ||
10 | + public abstract boolean isActive(); | ||
11 | + | ||
12 | + public abstract String getHostName(); | ||
13 | + | ||
14 | + public abstract Session getSession(); | ||
15 | + | ||
16 | + public abstract IWebPreferencesService getService(); | ||
17 | + | ||
18 | + public boolean requestGet(IWebPreferencesClient client, String uuid, Set<String> keys, boolean getPrivate); | ||
19 | + | ||
20 | + public boolean requestSet(IWebPreferencesClient client, String uuid, Map<String, String> values, boolean setPrivate); | ||
21 | +} |
src/client/java/com/mumfrey/webprefs/interfaces/IWebPreferencesRequest.java
0 → 100644
1 | +package com.mumfrey.webprefs.interfaces; | ||
2 | + | ||
3 | +import java.io.Serializable; | ||
4 | +import java.net.URI; | ||
5 | +import java.util.Map; | ||
6 | +import java.util.Set; | ||
7 | + | ||
8 | +public interface IWebPreferencesRequest extends Serializable | ||
9 | +{ | ||
10 | + public abstract IWebPreferencesServiceDelegate getDelegate(); | ||
11 | + | ||
12 | + public abstract boolean isValidationRequired(); | ||
13 | + | ||
14 | + public abstract URI getRequestURI(); | ||
15 | + | ||
16 | + public abstract String getUUID(); | ||
17 | + | ||
18 | + public abstract Set<String> getKeys(); | ||
19 | + | ||
20 | + public abstract Map<String, String> getPostVars(); | ||
21 | + | ||
22 | + public abstract void onReceivedResponse(IWebPreferencesResponse response); | ||
23 | +} |
src/client/java/com/mumfrey/webprefs/interfaces/IWebPreferencesResponse.java
0 → 100644
1 | +package com.mumfrey.webprefs.interfaces; | ||
2 | + | ||
3 | +import java.io.Serializable; | ||
4 | +import java.util.Map; | ||
5 | +import java.util.Set; | ||
6 | + | ||
7 | +public interface IWebPreferencesResponse extends Serializable | ||
8 | +{ | ||
9 | + public abstract String getResponse(); | ||
10 | + | ||
11 | + public abstract String getMessage(); | ||
12 | + | ||
13 | + public abstract Throwable getThrowable(); | ||
14 | + | ||
15 | + public abstract String getUUID(); | ||
16 | + | ||
17 | + public abstract String getServerId(); | ||
18 | + | ||
19 | + public abstract boolean hasSetters(); | ||
20 | + | ||
21 | + public abstract Set<String> getSetters(); | ||
22 | + | ||
23 | + public abstract boolean hasValues(); | ||
24 | + | ||
25 | + public abstract Map<String, String> getValues(); | ||
26 | +} |
src/client/java/com/mumfrey/webprefs/interfaces/IWebPreferencesService.java
0 → 100644
src/client/java/com/mumfrey/webprefs/interfaces/IWebPreferencesServiceDelegate.java
0 → 100644
1 | +package com.mumfrey.webprefs.interfaces; | ||
2 | + | ||
3 | +import net.minecraft.util.Session; | ||
4 | + | ||
5 | +import com.mumfrey.webprefs.framework.RequestFailureReason; | ||
6 | + | ||
7 | +public interface IWebPreferencesServiceDelegate | ||
8 | +{ | ||
9 | + public abstract String getHostName(); | ||
10 | + | ||
11 | + public abstract Session getSession(); | ||
12 | + | ||
13 | + public abstract void onReceivedResponse(IWebPreferencesRequest request, IWebPreferencesResponse response); | ||
14 | + | ||
15 | + public abstract void onRequestFailed(IWebPreferencesRequest request, Throwable th, RequestFailureReason reason); | ||
16 | +} |
src/client/java/com/mumfrey/webprefs/interfaces/IWebPreferencesServiceMonitor.java
0 → 100644