Commit 4c081d78ef6e3915d0f598543661c6f2747fe949

Authored by Mumfrey
1 parent d2870158

Add offline support to webprefs, closes #7

src/client/java/com/mumfrey/liteloader/client/mixin/MixinSession.java
... ... @@ -5,8 +5,6 @@
5 5 */
6 6 package com.mumfrey.liteloader.client.mixin;
7 7  
8   -import java.util.UUID;
9   -
10 8 import org.spongepowered.asm.mixin.Mixin;
11 9 import org.spongepowered.asm.mixin.Shadow;
12 10 import org.spongepowered.asm.mixin.injection.At;
... ... @@ -30,7 +28,6 @@ public abstract class MixinSession
30 28 ))
31 29 private void generateGameProfile(CallbackInfoReturnable<GameProfile> ci)
32 30 {
33   - UUID uuid = EntityPlayer.getUUID(new GameProfile((UUID)null, this.getUsername()));
34   - ci.setReturnValue(new GameProfile(uuid, this.getUsername()));
  31 + ci.setReturnValue(new GameProfile(EntityPlayer.getOfflineUUID(this.getUsername()), this.getUsername()));
35 32 }
36 33 }
... ...
src/client/java/com/mumfrey/webprefs/AbstractWebPreferences.java 0 → 100644
  1 +package com.mumfrey.webprefs;
  2 +
  3 +import java.util.Set;
  4 +import java.util.UUID;
  5 +
  6 +import com.mumfrey.webprefs.interfaces.IWebPreferences;
  7 +
  8 +/**
  9 + * Common base class for online/offline web preferences
  10 + */
  11 +abstract class AbstractWebPreferences implements IWebPreferences
  12 +{
  13 + /**
  14 + * Our UUID
  15 + */
  16 + protected final String uuid;
  17 +
  18 + /**
  19 + * True if we are a private settings set
  20 + */
  21 + protected final boolean isPrivate;
  22 +
  23 + protected final boolean isReadOnly;
  24 +
  25 + AbstractWebPreferences(UUID uuid, boolean isPrivate, boolean isReadOnly)
  26 + {
  27 + this(uuid.toString(), isPrivate, isReadOnly);
  28 + }
  29 +
  30 + AbstractWebPreferences(String uuid, boolean isPrivate, boolean isReadOnly)
  31 + {
  32 + this.uuid = uuid;
  33 + this.isPrivate = isPrivate;
  34 + this.isReadOnly = isReadOnly;
  35 + }
  36 +
  37 + void onTick()
  38 + {
  39 + // stub for subclasses
  40 + }
  41 +
  42 + /* (non-Javadoc)
  43 + * @see com.mumfrey.webprefs.interfaces.IWebPreferences#getUUID()
  44 + */
  45 + @Override
  46 + public final String getUUID()
  47 + {
  48 + return this.uuid;
  49 + }
  50 +
  51 + /* (non-Javadoc)
  52 + * @see com.mumfrey.webprefs.interfaces.IWebPreferences#isPrivate()
  53 + */
  54 + @Override
  55 + public final boolean isPrivate()
  56 + {
  57 + return this.isPrivate;
  58 + }
  59 +
  60 + /* (non-Javadoc)
  61 + * @see com.mumfrey.webprefs.interfaces.IWebPreferences#isReadOnly()
  62 + */
  63 + @Override
  64 + public final boolean isReadOnly()
  65 + {
  66 + return this.isReadOnly;
  67 + }
  68 +
  69 + /* (non-Javadoc)
  70 + * @see com.mumfrey.webprefs.interfaces.IWebPreferences
  71 + * #request(java.lang.String)
  72 + */
  73 + @Override
  74 + public void request(String key)
  75 + {
  76 + WebPreferences.validateKey(key);
  77 + }
  78 +
  79 + /* (non-Javadoc)
  80 + * @see com.mumfrey.webprefs.interfaces.IWebPreferences
  81 + * #request(java.lang.String[])
  82 + */
  83 + @Override
  84 + public void request(String... keys)
  85 + {
  86 + if (keys == null || keys.length < 1) return;
  87 + if (keys.length == 1) this.request(keys[0]);
  88 +
  89 + for (String key : keys)
  90 + {
  91 + this.request(key);
  92 + }
  93 + }
  94 +
  95 + /* (non-Javadoc)
  96 + * @see com.mumfrey.webprefs.interfaces.IWebPreferences
  97 + * #request(java.util.Set)
  98 + */
  99 + @Override
  100 + public void request(Set<String> keys)
  101 + {
  102 + if (keys == null || keys.size() < 1) return;
  103 +
  104 + for (String key : keys)
  105 + {
  106 + this.request(key);
  107 + }
  108 + }
  109 +
  110 + /* (non-Javadoc)
  111 + * @see com.mumfrey.webprefs.interfaces.IWebPreferences#commit(boolean)
  112 + */
  113 + @Override
  114 + public void commit(boolean force)
  115 + {
  116 + }
  117 +
  118 + /* (non-Javadoc)
  119 + * @see com.mumfrey.webprefs.interfaces.IWebPreferences
  120 + * #set(java.lang.String, java.lang.String)
  121 + */
  122 + @Override
  123 + public void set(String key, String value)
  124 + {
  125 + WebPreferences.validateKey(key);
  126 + }
  127 +
  128 + /* (non-Javadoc)
  129 + * @see com.mumfrey.webprefs.interfaces.IWebPreferences
  130 + * #remove(java.lang.String)
  131 + */
  132 + @Override
  133 + public void remove(String key)
  134 + {
  135 + this.set(key, "");
  136 + }
  137 +}
... ...
src/client/java/com/mumfrey/webprefs/DummyOfflineWebPreferences.java 0 → 100644
  1 +package com.mumfrey.webprefs;
  2 +
  3 +import java.util.UUID;
  4 +
  5 +/**
  6 + * No-op webpreferences set which is returned for nonlocal players when the
  7 + * client is running in offline mode.
  8 + */
  9 +public class DummyOfflineWebPreferences extends AbstractWebPreferences
  10 +{
  11 + DummyOfflineWebPreferences(UUID uuid, boolean isPrivate, boolean isReadOnly)
  12 + {
  13 + this(uuid.toString(), isPrivate, isReadOnly);
  14 + }
  15 +
  16 + public DummyOfflineWebPreferences(String uuid, boolean isPrivate, boolean isReadOnly)
  17 + {
  18 + super(uuid, isPrivate, isReadOnly);
  19 + }
  20 +
  21 + /* (non-Javadoc)
  22 + * @see com.mumfrey.webprefs.interfaces.IWebPreferences#poll()
  23 + */
  24 + @Override
  25 + public void poll()
  26 + {
  27 + }
  28 +
  29 + /* (non-Javadoc)
  30 + * @see com.mumfrey.webprefs.interfaces.IWebPreferences
  31 + * #has(java.lang.String)
  32 + */
  33 + @Override
  34 + public boolean has(String key)
  35 + {
  36 + WebPreferences.validateKey(key);
  37 + return false;
  38 + }
  39 +
  40 + /* (non-Javadoc)
  41 + * @see com.mumfrey.webprefs.interfaces.IWebPreferences
  42 + * #get(java.lang.String)
  43 + */
  44 + @Override
  45 + public String get(String key)
  46 + {
  47 + WebPreferences.validateKey(key);
  48 + return "";
  49 + }
  50 +
  51 + /* (non-Javadoc)
  52 + * @see com.mumfrey.webprefs.interfaces.IWebPreferences
  53 + * #get(java.lang.String, java.lang.String)
  54 + */
  55 + @Override
  56 + public String get(String key, String defaultValue)
  57 + {
  58 + WebPreferences.validateKey(key);
  59 + return defaultValue;
  60 + }
  61 +}
0 62 \ No newline at end of file
... ...
src/client/java/com/mumfrey/webprefs/OfflineWebPreferences.java 0 → 100644
  1 +package com.mumfrey.webprefs;
  2 +
  3 +import java.io.File;
  4 +import java.io.FileReader;
  5 +import java.io.FileWriter;
  6 +import java.io.IOException;
  7 +import java.util.HashMap;
  8 +import java.util.Map;
  9 +import java.util.UUID;
  10 +
  11 +import com.google.gson.Gson;
  12 +import com.google.gson.GsonBuilder;
  13 +import com.mumfrey.liteloader.core.LiteLoader;
  14 +import com.mumfrey.webprefs.exceptions.ReadOnlyPreferencesException;
  15 +
  16 +/**
  17 + * Surrogate preferences returned for a local offline player, allows offline
  18 + * players to use the webpreferences system with preferences just being stored
  19 + * locally.
  20 + */
  21 +class OfflineWebPreferences extends DummyOfflineWebPreferences
  22 +{
  23 + /**
  24 + * Gson instance for serialisation/deserialisation to local JSON file
  25 + */
  26 + private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
  27 +
  28 + /**
  29 + * Maximum commit-to-disk rate in ticks
  30 + */
  31 + private static final int COMMIT_RATE = 20 * 3;
  32 +
  33 + /**
  34 + * JSON file
  35 + */
  36 + private final File store;
  37 +
  38 + /**
  39 + * Local KV store
  40 + */
  41 + final Map<String, String> prefs;
  42 +
  43 + /**
  44 + * Tick number for write throttling
  45 + */
  46 + private int tickNumber;
  47 +
  48 + /**
  49 + * Flag indicating serialisation to disk is required
  50 + */
  51 + private boolean isDirty;
  52 +
  53 + OfflineWebPreferences(UUID uuid, boolean isPrivate, boolean isReadOnly)
  54 + {
  55 + this(uuid.toString(), isPrivate, isReadOnly);
  56 + }
  57 +
  58 + OfflineWebPreferences(String uuid, boolean isPrivate, boolean isReadOnly)
  59 + {
  60 + super(uuid, isPrivate, isReadOnly);
  61 +
  62 + this.store = new File(LiteLoader.getCommonConfigFolder(), String.format("%s.%sprefs.json", uuid, isPrivate ? "private" : ""));
  63 + this.prefs = this.loadValues();
  64 + }
  65 +
  66 + @SuppressWarnings("unchecked")
  67 + private Map<String, String> loadValues()
  68 + {
  69 + if (this.store.isFile())
  70 + {
  71 + FileReader reader = null;
  72 +
  73 + try
  74 + {
  75 + reader = new FileReader(this.store);
  76 + return OfflineWebPreferences.gson.fromJson(reader, Map.class);
  77 + }
  78 + catch (IOException ex) {}
  79 + finally
  80 + {
  81 + try
  82 + {
  83 + if (reader != null) reader.close();
  84 + }
  85 + catch (IOException ex) {}
  86 + }
  87 + }
  88 +
  89 + return new HashMap<String, String>();
  90 + }
  91 +
  92 + private void saveValues()
  93 + {
  94 + FileWriter writer = null;
  95 +
  96 + try
  97 + {
  98 + writer = new FileWriter(this.store);
  99 + OfflineWebPreferences.gson.toJson(this.prefs, writer);
  100 + }
  101 + catch (IOException ex) {}
  102 + finally
  103 + {
  104 + try
  105 + {
  106 + if (writer != null) writer.close();
  107 + }
  108 + catch (IOException ex) {}
  109 + }
  110 + }
  111 +
  112 + @Override
  113 + void onTick()
  114 + {
  115 + if (this.tickNumber++ > OfflineWebPreferences.COMMIT_RATE && this.isDirty)
  116 + {
  117 + this.isDirty = false;
  118 + this.tickNumber = 0;
  119 + this.saveValues();
  120 + }
  121 + }
  122 +
  123 + @Override
  124 + public void request(String key)
  125 + {
  126 + WebPreferences.validateKey(key);
  127 + if (!this.prefs.containsKey(key))
  128 + {
  129 + this.prefs.put(key, "");
  130 + }
  131 + }
  132 +
  133 + /* (non-Javadoc)
  134 + * @see com.mumfrey.webprefs.interfaces.IWebPreferences#commit(boolean)
  135 + */
  136 + @Override
  137 + public void commit(boolean force)
  138 + {
  139 + this.isDirty = true;
  140 + }
  141 +
  142 + /* (non-Javadoc)
  143 + * @see com.mumfrey.webprefs.interfaces.IWebPreferences
  144 + * #has(java.lang.String)
  145 + */
  146 + @Override
  147 + public boolean has(String key)
  148 + {
  149 + WebPreferences.validateKey(key);
  150 + return this.prefs.containsKey(key);
  151 + }
  152 +
  153 + /* (non-Javadoc)
  154 + * @see com.mumfrey.webprefs.interfaces.IWebPreferences
  155 + * #get(java.lang.String)
  156 + */
  157 + @Override
  158 + public String get(String key)
  159 + {
  160 + return this.get(key, "");
  161 + }
  162 +
  163 + /* (non-Javadoc)
  164 + * @see com.mumfrey.webprefs.interfaces.IWebPreferences
  165 + * #get(java.lang.String, java.lang.String)
  166 + */
  167 + @Override
  168 + public String get(String key, String defaultValue)
  169 + {
  170 + WebPreferences.validateKey(key);
  171 + String value = this.prefs.get(key);
  172 + return value != null ? value : defaultValue;
  173 + }
  174 +
  175 + /* (non-Javadoc)
  176 + * @see com.mumfrey.webprefs.interfaces.IWebPreferences
  177 + * #set(java.lang.String, java.lang.String)
  178 + */
  179 + @Override
  180 + public void set(String key, String value)
  181 + {
  182 + if (this.isReadOnly())
  183 + {
  184 + throw new ReadOnlyPreferencesException("Preference collection for " + this.uuid + " is read-only");
  185 + }
  186 +
  187 + WebPreferences.validateKV(key, value);
  188 +
  189 + this.prefs.put(key, value);
  190 + this.isDirty = true;
  191 + }
  192 +}
... ...
src/client/java/com/mumfrey/webprefs/WebPreferences.java
... ... @@ -4,6 +4,7 @@ import java.util.HashMap;
4 4 import java.util.HashSet;
5 5 import java.util.Map;
6 6 import java.util.Set;
  7 +import java.util.UUID;
7 8 import java.util.concurrent.ConcurrentHashMap;
8 9 import java.util.regex.Pattern;
9 10  
... ... @@ -12,11 +13,10 @@ import com.mumfrey.webprefs.exceptions.InvalidKeyException;
12 13 import com.mumfrey.webprefs.exceptions.InvalidValueException;
13 14 import com.mumfrey.webprefs.exceptions.ReadOnlyPreferencesException;
14 15 import com.mumfrey.webprefs.framework.RequestFailureReason;
15   -import com.mumfrey.webprefs.interfaces.IWebPreferences;
16 16 import com.mumfrey.webprefs.interfaces.IWebPreferencesClient;
17 17 import com.mumfrey.webprefs.interfaces.IWebPreferencesProvider;
18 18  
19   -class WebPreferences implements IWebPreferences
  19 +class WebPreferences extends AbstractWebPreferences
20 20 {
21 21 /**
22 22 * The update frequency to use when operating normally, this is the
... ... @@ -104,18 +104,6 @@ class WebPreferences implements IWebPreferences
104 104 private final IWebPreferencesClient client;
105 105  
106 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 107 * Current key/value pairs
120 108 */
121 109 protected final Map<String, String> prefs = new ConcurrentHashMap<String, String>();
... ... @@ -149,61 +137,30 @@ class WebPreferences implements IWebPreferences
149 137 private volatile int updateCheckTimer = 1;
150 138  
151 139 protected int requestTimeoutTimer = 0;
  140 +
  141 + WebPreferences(IWebPreferencesProvider provider, UUID uuid, boolean isPrivate, boolean isReadOnly)
  142 + {
  143 + this(provider, uuid.toString(), isPrivate, isReadOnly);
  144 + }
152 145  
153 146 WebPreferences(IWebPreferencesProvider provider, String uuid, boolean isPrivate, boolean isReadOnly)
154 147 {
  148 + super(uuid, isPrivate, isReadOnly);
155 149 this.provider = provider;
156   - this.uuid = uuid;
157   - this.isPrivate = isPrivate;
158   - this.isReadOnly = isReadOnly;
159 150 this.client = new Client();
160 151 }
161 152  
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 153 @Override
184   - public boolean isReadOnly()
185   - {
186   - return this.isReadOnly;
187   - }
188   -
189 154 void onTick()
190 155 {
191   - if (this.updateCheckTimer > 0)
  156 + if (this.updateCheckTimer > 0 && --this.updateCheckTimer < 1)
192 157 {
193   - this.updateCheckTimer--;
194   - if (this.updateCheckTimer < 1)
195   - {
196   - this.update();
197   - }
  158 + this.update();
198 159 }
199 160  
200   - if (this.requestTimeoutTimer > 0)
  161 + if (this.requestTimeoutTimer > 0 && --this.requestTimeoutTimer < 1)
201 162 {
202   - this.requestTimeoutTimer--;
203   - if (this.requestTimeoutTimer < 1)
204   - {
205   - this.handleTimeout();
206   - }
  163 + this.handleTimeout();
207 164 }
208 165 }
209 166  
... ... @@ -449,16 +406,6 @@ class WebPreferences implements IWebPreferences
449 406 }
450 407 }
451 408  
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 409 /**
463 410 * Add a key to the current request set, the key will be requested from the
464 411 * server on the next {@link #update()}
... ... @@ -563,7 +510,7 @@ class WebPreferences implements IWebPreferences
563 510 /**
564 511 * @param key
565 512 */
566   - protected static final void validateKey(String key)
  513 + protected static void validateKey(String key)
567 514 {
568 515 if (key == null || !WebPreferences.keyPattern.matcher(key).matches())
569 516 {
... ... @@ -575,7 +522,7 @@ class WebPreferences implements IWebPreferences
575 522 * @param key
576 523 * @param value
577 524 */
578   - protected static final void validateKV(String key, String value)
  525 + protected static void validateKV(String key, String value)
579 526 {
580 527 WebPreferences.validateKey(key);
581 528  
... ...
src/client/java/com/mumfrey/webprefs/WebPreferencesManager.java
... ... @@ -130,7 +130,7 @@ public final class WebPreferencesManager
130 130 /**
131 131 * All preference sets, for iteration purposes
132 132 */
133   - private final List<WebPreferences> allPreferences = new LinkedList<WebPreferences>();
  133 + private final List<AbstractWebPreferences> allPreferences = new LinkedList<AbstractWebPreferences>();
134 134  
135 135 /**
136 136 * All public preference sets, mapped by UUID
... ... @@ -152,7 +152,7 @@ public final class WebPreferencesManager
152 152 {
153 153 this.provider.onTick();
154 154  
155   - for (WebPreferences prefs : this.allPreferences)
  155 + for (AbstractWebPreferences prefs : this.allPreferences)
156 156 {
157 157 try
158 158 {
... ... @@ -164,7 +164,7 @@ public final class WebPreferencesManager
164 164  
165 165 void onJoinGame()
166 166 {
167   - for (WebPreferences prefs : this.allPreferences)
  167 + for (AbstractWebPreferences prefs : this.allPreferences)
168 168 {
169 169 try
170 170 {
... ... @@ -176,7 +176,9 @@ public final class WebPreferencesManager
176 176  
177 177  
178 178 /**
179   - * Get a public or private preferences collection for the local player
  179 + * Get a public or private preferences collection for the local player. If
  180 + * the game is running in offline mode, a local preference collection is
  181 + * returned instead.
180 182 *
181 183 * @param privatePrefs true to fetch the player's private preferences, false
182 184 * to fetch public preferences
... ... @@ -184,11 +186,21 @@ public final class WebPreferencesManager
184 186 */
185 187 public IWebPreferences getLocalPreferences(boolean privatePrefs)
186 188 {
187   - return this.getPreferences(this.session.getPlayerID(), privatePrefs);
  189 + try
  190 + {
  191 + return this.getPreferences(this.session.getPlayerID(), privatePrefs);
  192 + }
  193 + catch (InvalidUUIDException ex)
  194 + {
  195 + UUID offlineUUID = EntityPlayer.getOfflineUUID(this.session.getUsername());
  196 + return this.getOfflinePreferences(offlineUUID, privatePrefs, false, false);
  197 + }
188 198 }
189 199  
190 200 /**
191   - * Get a public preferences collection for the specified player.
  201 + * Get a public preferences collection for the specified player. If the game
  202 + * is running in offline mode, a dummy preference collection supporting no
  203 + * operations is returned instead.
192 204 *
193 205 * @param player Player to fetch preferences for
194 206 * @param privatePrefs True to fetch the player's private preferences, false
... ... @@ -198,7 +210,16 @@ public final class WebPreferencesManager
198 210 */
199 211 public IWebPreferences getPreferences(EntityPlayer player)
200 212 {
201   - return this.getPreferences(player, false);
  213 + try
  214 + {
  215 + return this.getPreferences(player, false);
  216 + }
  217 + catch (InvalidUUIDException ex)
  218 + {
  219 + String playerName = player.getName();
  220 + UUID offlineUUID = EntityPlayer.getOfflineUUID(playerName);
  221 + return this.getOfflinePreferences(offlineUUID, false, false, !playerName.equals(this.session.getUsername()));
  222 + }
202 223 }
203 224  
204 225 /**
... ... @@ -279,7 +300,6 @@ public final class WebPreferencesManager
279 300 uuid = this.sanitiseUUID(uuid);
280 301  
281 302 Map<String, IWebPreferences> preferences = privatePrefs ? this.preferencesPrivate : this.preferencesPublic;
282   -
283 303 IWebPreferences prefs = preferences.get(uuid);
284 304  
285 305 if (prefs == null)
... ... @@ -293,6 +313,24 @@ public final class WebPreferencesManager
293 313 return prefs;
294 314 }
295 315  
  316 + private IWebPreferences getOfflinePreferences(UUID uuid, boolean privatePrefs, boolean readOnly, boolean dummy)
  317 + {
  318 + Map<String, IWebPreferences> preferences = privatePrefs ? this.preferencesPrivate : this.preferencesPublic;
  319 + IWebPreferences prefs = preferences.get(uuid);
  320 +
  321 + if (prefs == null)
  322 + {
  323 + AbstractWebPreferences newPrefs = dummy
  324 + ? new DummyOfflineWebPreferences(uuid, privatePrefs, readOnly)
  325 + : new OfflineWebPreferences(uuid, privatePrefs, readOnly);
  326 + this.allPreferences.add(newPrefs);
  327 + preferences.put(uuid.toString(), newPrefs);
  328 + prefs = newPrefs;
  329 + }
  330 +
  331 + return prefs;
  332 + }
  333 +
296 334 private String sanitiseUUID(String uuid)
297 335 {
298 336 if (uuid == null)
... ...