Commit 3fb6dc861087ed3d2ded6c6e14660fc6fd5b773f

Authored by Mumfrey
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
  1 +package com.mumfrey.webprefs.interfaces;
  2 +
  3 +public interface IWebPreferencesService
  4 +{
  5 + public abstract void addMonitor(IWebPreferencesServiceMonitor monitor);
  6 +
  7 + public abstract void submit(IWebPreferencesRequest request);
  8 +}
... ...
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
  1 +package com.mumfrey.webprefs.interfaces;
  2 +
  3 +public interface IWebPreferencesServiceMonitor
  4 +{
  5 + public abstract void onKeyRequestFailed();
  6 +
  7 + public abstract void onRequestFailed(Throwable th, int severity);
  8 +}
... ...