Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | package org.mozilla.gecko.sync; |
michael@0 | 6 | |
michael@0 | 7 | import java.net.URI; |
michael@0 | 8 | import java.net.URISyntaxException; |
michael@0 | 9 | import java.util.Collection; |
michael@0 | 10 | import java.util.HashMap; |
michael@0 | 11 | import java.util.HashSet; |
michael@0 | 12 | import java.util.Map; |
michael@0 | 13 | import java.util.Map.Entry; |
michael@0 | 14 | import java.util.Set; |
michael@0 | 15 | |
michael@0 | 16 | import org.mozilla.gecko.background.common.log.Logger; |
michael@0 | 17 | import org.mozilla.gecko.sync.crypto.KeyBundle; |
michael@0 | 18 | import org.mozilla.gecko.sync.crypto.PersistedCrypto5Keys; |
michael@0 | 19 | import org.mozilla.gecko.sync.net.AuthHeaderProvider; |
michael@0 | 20 | import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage; |
michael@0 | 21 | |
michael@0 | 22 | import android.content.SharedPreferences; |
michael@0 | 23 | import android.content.SharedPreferences.Editor; |
michael@0 | 24 | |
michael@0 | 25 | public class SyncConfiguration { |
michael@0 | 26 | |
michael@0 | 27 | public class EditorBranch implements Editor { |
michael@0 | 28 | |
michael@0 | 29 | private String prefix; |
michael@0 | 30 | private Editor editor; |
michael@0 | 31 | |
michael@0 | 32 | public EditorBranch(SyncConfiguration config, String prefix) { |
michael@0 | 33 | if (!prefix.endsWith(".")) { |
michael@0 | 34 | throw new IllegalArgumentException("No trailing period in prefix."); |
michael@0 | 35 | } |
michael@0 | 36 | this.prefix = prefix; |
michael@0 | 37 | this.editor = config.getEditor(); |
michael@0 | 38 | } |
michael@0 | 39 | |
michael@0 | 40 | public void apply() { |
michael@0 | 41 | // Android <=r8 SharedPreferences.Editor does not contain apply() for overriding. |
michael@0 | 42 | this.editor.commit(); |
michael@0 | 43 | } |
michael@0 | 44 | |
michael@0 | 45 | @Override |
michael@0 | 46 | public Editor clear() { |
michael@0 | 47 | this.editor = this.editor.clear(); |
michael@0 | 48 | return this; |
michael@0 | 49 | } |
michael@0 | 50 | |
michael@0 | 51 | @Override |
michael@0 | 52 | public boolean commit() { |
michael@0 | 53 | return this.editor.commit(); |
michael@0 | 54 | } |
michael@0 | 55 | |
michael@0 | 56 | @Override |
michael@0 | 57 | public Editor putBoolean(String key, boolean value) { |
michael@0 | 58 | this.editor = this.editor.putBoolean(prefix + key, value); |
michael@0 | 59 | return this; |
michael@0 | 60 | } |
michael@0 | 61 | |
michael@0 | 62 | @Override |
michael@0 | 63 | public Editor putFloat(String key, float value) { |
michael@0 | 64 | this.editor = this.editor.putFloat(prefix + key, value); |
michael@0 | 65 | return this; |
michael@0 | 66 | } |
michael@0 | 67 | |
michael@0 | 68 | @Override |
michael@0 | 69 | public Editor putInt(String key, int value) { |
michael@0 | 70 | this.editor = this.editor.putInt(prefix + key, value); |
michael@0 | 71 | return this; |
michael@0 | 72 | } |
michael@0 | 73 | |
michael@0 | 74 | @Override |
michael@0 | 75 | public Editor putLong(String key, long value) { |
michael@0 | 76 | this.editor = this.editor.putLong(prefix + key, value); |
michael@0 | 77 | return this; |
michael@0 | 78 | } |
michael@0 | 79 | |
michael@0 | 80 | @Override |
michael@0 | 81 | public Editor putString(String key, String value) { |
michael@0 | 82 | this.editor = this.editor.putString(prefix + key, value); |
michael@0 | 83 | return this; |
michael@0 | 84 | } |
michael@0 | 85 | |
michael@0 | 86 | // Not marking as Override, because Android <= 10 doesn't have |
michael@0 | 87 | // putStringSet. Neither can we implement it. |
michael@0 | 88 | public Editor putStringSet(String key, Set<String> value) { |
michael@0 | 89 | throw new RuntimeException("putStringSet not available."); |
michael@0 | 90 | } |
michael@0 | 91 | |
michael@0 | 92 | @Override |
michael@0 | 93 | public Editor remove(String key) { |
michael@0 | 94 | this.editor = this.editor.remove(prefix + key); |
michael@0 | 95 | return this; |
michael@0 | 96 | } |
michael@0 | 97 | |
michael@0 | 98 | } |
michael@0 | 99 | |
michael@0 | 100 | /** |
michael@0 | 101 | * A wrapper around a portion of the SharedPreferences space. |
michael@0 | 102 | * |
michael@0 | 103 | * @author rnewman |
michael@0 | 104 | * |
michael@0 | 105 | */ |
michael@0 | 106 | public class ConfigurationBranch implements SharedPreferences { |
michael@0 | 107 | |
michael@0 | 108 | private SyncConfiguration config; |
michael@0 | 109 | private String prefix; // Including trailing period. |
michael@0 | 110 | |
michael@0 | 111 | public ConfigurationBranch(SyncConfiguration syncConfiguration, |
michael@0 | 112 | String prefix) { |
michael@0 | 113 | if (!prefix.endsWith(".")) { |
michael@0 | 114 | throw new IllegalArgumentException("No trailing period in prefix."); |
michael@0 | 115 | } |
michael@0 | 116 | this.config = syncConfiguration; |
michael@0 | 117 | this.prefix = prefix; |
michael@0 | 118 | } |
michael@0 | 119 | |
michael@0 | 120 | @Override |
michael@0 | 121 | public boolean contains(String key) { |
michael@0 | 122 | return config.getPrefs().contains(prefix + key); |
michael@0 | 123 | } |
michael@0 | 124 | |
michael@0 | 125 | @Override |
michael@0 | 126 | public Editor edit() { |
michael@0 | 127 | return new EditorBranch(config, prefix); |
michael@0 | 128 | } |
michael@0 | 129 | |
michael@0 | 130 | @Override |
michael@0 | 131 | public Map<String, ?> getAll() { |
michael@0 | 132 | // Not implemented. TODO |
michael@0 | 133 | return null; |
michael@0 | 134 | } |
michael@0 | 135 | |
michael@0 | 136 | @Override |
michael@0 | 137 | public boolean getBoolean(String key, boolean defValue) { |
michael@0 | 138 | return config.getPrefs().getBoolean(prefix + key, defValue); |
michael@0 | 139 | } |
michael@0 | 140 | |
michael@0 | 141 | @Override |
michael@0 | 142 | public float getFloat(String key, float defValue) { |
michael@0 | 143 | return config.getPrefs().getFloat(prefix + key, defValue); |
michael@0 | 144 | } |
michael@0 | 145 | |
michael@0 | 146 | @Override |
michael@0 | 147 | public int getInt(String key, int defValue) { |
michael@0 | 148 | return config.getPrefs().getInt(prefix + key, defValue); |
michael@0 | 149 | } |
michael@0 | 150 | |
michael@0 | 151 | @Override |
michael@0 | 152 | public long getLong(String key, long defValue) { |
michael@0 | 153 | return config.getPrefs().getLong(prefix + key, defValue); |
michael@0 | 154 | } |
michael@0 | 155 | |
michael@0 | 156 | @Override |
michael@0 | 157 | public String getString(String key, String defValue) { |
michael@0 | 158 | return config.getPrefs().getString(prefix + key, defValue); |
michael@0 | 159 | } |
michael@0 | 160 | |
michael@0 | 161 | // Not marking as Override, because Android <= 10 doesn't have |
michael@0 | 162 | // getStringSet. Neither can we implement it. |
michael@0 | 163 | public Set<String> getStringSet(String key, Set<String> defValue) { |
michael@0 | 164 | throw new RuntimeException("getStringSet not available."); |
michael@0 | 165 | } |
michael@0 | 166 | |
michael@0 | 167 | @Override |
michael@0 | 168 | public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { |
michael@0 | 169 | config.getPrefs().registerOnSharedPreferenceChangeListener(listener); |
michael@0 | 170 | } |
michael@0 | 171 | |
michael@0 | 172 | @Override |
michael@0 | 173 | public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { |
michael@0 | 174 | config.getPrefs().unregisterOnSharedPreferenceChangeListener(listener); |
michael@0 | 175 | } |
michael@0 | 176 | } |
michael@0 | 177 | |
michael@0 | 178 | private static final String LOG_TAG = "SyncConfiguration"; |
michael@0 | 179 | |
michael@0 | 180 | // These must be set in GlobalSession's constructor. |
michael@0 | 181 | public URI clusterURL; |
michael@0 | 182 | public KeyBundle syncKeyBundle; |
michael@0 | 183 | |
michael@0 | 184 | public CollectionKeys collectionKeys; |
michael@0 | 185 | public InfoCollections infoCollections; |
michael@0 | 186 | public MetaGlobal metaGlobal; |
michael@0 | 187 | public String syncID; |
michael@0 | 188 | |
michael@0 | 189 | protected final String username; |
michael@0 | 190 | |
michael@0 | 191 | /** |
michael@0 | 192 | * Persisted collection of enabledEngineNames. |
michael@0 | 193 | * <p> |
michael@0 | 194 | * Can contain engines Android Sync is not currently aware of, such as "prefs" |
michael@0 | 195 | * or "addons". |
michael@0 | 196 | * <p> |
michael@0 | 197 | * Copied from latest downloaded meta/global record and used to generate a |
michael@0 | 198 | * fresh meta/global record for upload. |
michael@0 | 199 | */ |
michael@0 | 200 | public Set<String> enabledEngineNames; |
michael@0 | 201 | public Set<String> declinedEngineNames = new HashSet<String>(); |
michael@0 | 202 | |
michael@0 | 203 | /** |
michael@0 | 204 | * Names of stages to sync <it>this sync</it>, or <code>null</code> to sync |
michael@0 | 205 | * all known stages. |
michael@0 | 206 | * <p> |
michael@0 | 207 | * Generated <it>each sync</it> from extras bundle passed to |
michael@0 | 208 | * <code>SyncAdapter.onPerformSync</code> and not persisted. |
michael@0 | 209 | * <p> |
michael@0 | 210 | * Not synchronized! Set this exactly once per global session and don't modify |
michael@0 | 211 | * it -- especially not from multiple threads. |
michael@0 | 212 | */ |
michael@0 | 213 | public Collection<String> stagesToSync; |
michael@0 | 214 | |
michael@0 | 215 | /** |
michael@0 | 216 | * Engines whose sync state has been modified by the user through |
michael@0 | 217 | * SelectEnginesActivity, where each key-value pair is an engine name and |
michael@0 | 218 | * its sync state. |
michael@0 | 219 | * |
michael@0 | 220 | * This differs from <code>enabledEngineNames</code> in that |
michael@0 | 221 | * <code>enabledEngineNames</code> reflects the downloaded meta/global, |
michael@0 | 222 | * whereas <code>userSelectedEngines</code> stores the differences in engines to |
michael@0 | 223 | * sync that the user has selected. |
michael@0 | 224 | * |
michael@0 | 225 | * Each engine stage will check for engine changes at the beginning of the |
michael@0 | 226 | * stage. |
michael@0 | 227 | * |
michael@0 | 228 | * If no engine sync state changes have been made by the user, userSelectedEngines |
michael@0 | 229 | * will be null, and Sync will proceed normally. |
michael@0 | 230 | * |
michael@0 | 231 | * If the user has made changes to engine syncing state, each engine will sync |
michael@0 | 232 | * according to the sync state specified in userSelectedEngines and propagate that |
michael@0 | 233 | * state to meta/global, to be uploaded. |
michael@0 | 234 | */ |
michael@0 | 235 | public Map<String, Boolean> userSelectedEngines; |
michael@0 | 236 | public long userSelectedEnginesTimestamp; |
michael@0 | 237 | |
michael@0 | 238 | public SharedPreferences prefs; |
michael@0 | 239 | |
michael@0 | 240 | protected final AuthHeaderProvider authHeaderProvider; |
michael@0 | 241 | |
michael@0 | 242 | public static final String PREF_PREFS_VERSION = "prefs.version"; |
michael@0 | 243 | public static final long CURRENT_PREFS_VERSION = 1; |
michael@0 | 244 | |
michael@0 | 245 | public static final String CLIENTS_COLLECTION_TIMESTAMP = "serverClientsTimestamp"; // When the collection was touched. |
michael@0 | 246 | public static final String CLIENT_RECORD_TIMESTAMP = "serverClientRecordTimestamp"; // When our record was touched. |
michael@0 | 247 | |
michael@0 | 248 | public static final String PREF_CLUSTER_URL = "clusterURL"; |
michael@0 | 249 | public static final String PREF_SYNC_ID = "syncID"; |
michael@0 | 250 | |
michael@0 | 251 | public static final String PREF_ENABLED_ENGINE_NAMES = "enabledEngineNames"; |
michael@0 | 252 | public static final String PREF_DECLINED_ENGINE_NAMES = "declinedEngineNames"; |
michael@0 | 253 | public static final String PREF_USER_SELECTED_ENGINES_TO_SYNC = "userSelectedEngines"; |
michael@0 | 254 | public static final String PREF_USER_SELECTED_ENGINES_TO_SYNC_TIMESTAMP = "userSelectedEnginesTimestamp"; |
michael@0 | 255 | |
michael@0 | 256 | public static final String PREF_CLUSTER_URL_IS_STALE = "clusterurlisstale"; |
michael@0 | 257 | |
michael@0 | 258 | public static final String PREF_ACCOUNT_GUID = "account.guid"; |
michael@0 | 259 | public static final String PREF_CLIENT_NAME = "account.clientName"; |
michael@0 | 260 | public static final String PREF_NUM_CLIENTS = "account.numClients"; |
michael@0 | 261 | |
michael@0 | 262 | private static final String API_VERSION = "1.5"; |
michael@0 | 263 | |
michael@0 | 264 | public SyncConfiguration(String username, AuthHeaderProvider authHeaderProvider, SharedPreferences prefs) { |
michael@0 | 265 | this.username = username; |
michael@0 | 266 | this.authHeaderProvider = authHeaderProvider; |
michael@0 | 267 | this.prefs = prefs; |
michael@0 | 268 | this.loadFromPrefs(prefs); |
michael@0 | 269 | } |
michael@0 | 270 | |
michael@0 | 271 | public SyncConfiguration(String username, AuthHeaderProvider authHeaderProvider, SharedPreferences prefs, KeyBundle syncKeyBundle) { |
michael@0 | 272 | this(username, authHeaderProvider, prefs); |
michael@0 | 273 | this.syncKeyBundle = syncKeyBundle; |
michael@0 | 274 | } |
michael@0 | 275 | |
michael@0 | 276 | public String getAPIVersion() { |
michael@0 | 277 | return API_VERSION; |
michael@0 | 278 | } |
michael@0 | 279 | |
michael@0 | 280 | public SharedPreferences getPrefs() { |
michael@0 | 281 | return this.prefs; |
michael@0 | 282 | } |
michael@0 | 283 | |
michael@0 | 284 | /** |
michael@0 | 285 | * Valid engines supported by Android Sync. |
michael@0 | 286 | * |
michael@0 | 287 | * @return Set<String> of valid engine names that Android Sync implements. |
michael@0 | 288 | */ |
michael@0 | 289 | public static Set<String> validEngineNames() { |
michael@0 | 290 | Set<String> engineNames = new HashSet<String>(); |
michael@0 | 291 | for (Stage stage : Stage.getNamedStages()) { |
michael@0 | 292 | engineNames.add(stage.getRepositoryName()); |
michael@0 | 293 | } |
michael@0 | 294 | return engineNames; |
michael@0 | 295 | } |
michael@0 | 296 | |
michael@0 | 297 | /** |
michael@0 | 298 | * Return a convenient accessor for part of prefs. |
michael@0 | 299 | * @return |
michael@0 | 300 | * A ConfigurationBranch object representing this |
michael@0 | 301 | * section of the preferences space. |
michael@0 | 302 | */ |
michael@0 | 303 | public ConfigurationBranch getBranch(String prefix) { |
michael@0 | 304 | return new ConfigurationBranch(this, prefix); |
michael@0 | 305 | } |
michael@0 | 306 | |
michael@0 | 307 | /** |
michael@0 | 308 | * Gets the engine names that are enabled, declined, or other (depending on pref) in meta/global. |
michael@0 | 309 | * |
michael@0 | 310 | * @param prefs |
michael@0 | 311 | * SharedPreferences that the engines are associated with. |
michael@0 | 312 | * @param pref |
michael@0 | 313 | * The preference name to use. E.g, PREF_ENABLED_ENGINE_NAMES. |
michael@0 | 314 | * @return Set<String> of the enabled engine names if they have been stored, |
michael@0 | 315 | * or null otherwise. |
michael@0 | 316 | */ |
michael@0 | 317 | protected static Set<String> getEngineNamesFromPref(SharedPreferences prefs, String pref) { |
michael@0 | 318 | final String json = prefs.getString(pref, null); |
michael@0 | 319 | if (json == null) { |
michael@0 | 320 | return null; |
michael@0 | 321 | } |
michael@0 | 322 | try { |
michael@0 | 323 | final ExtendedJSONObject o = ExtendedJSONObject.parseJSONObject(json); |
michael@0 | 324 | return new HashSet<String>(o.keySet()); |
michael@0 | 325 | } catch (Exception e) { |
michael@0 | 326 | return null; |
michael@0 | 327 | } |
michael@0 | 328 | } |
michael@0 | 329 | |
michael@0 | 330 | /** |
michael@0 | 331 | * Returns the set of engine names that the user has enabled. If none |
michael@0 | 332 | * have been stored in prefs, <code>null</code> is returned. |
michael@0 | 333 | */ |
michael@0 | 334 | public static Set<String> getEnabledEngineNames(SharedPreferences prefs) { |
michael@0 | 335 | return getEngineNamesFromPref(prefs, PREF_ENABLED_ENGINE_NAMES); |
michael@0 | 336 | } |
michael@0 | 337 | |
michael@0 | 338 | /** |
michael@0 | 339 | * Returns the set of engine names that the user has declined. |
michael@0 | 340 | */ |
michael@0 | 341 | public static Set<String> getDeclinedEngineNames(SharedPreferences prefs) { |
michael@0 | 342 | final Set<String> names = getEngineNamesFromPref(prefs, PREF_DECLINED_ENGINE_NAMES); |
michael@0 | 343 | if (names == null) { |
michael@0 | 344 | return new HashSet<String>(); |
michael@0 | 345 | } |
michael@0 | 346 | return names; |
michael@0 | 347 | } |
michael@0 | 348 | |
michael@0 | 349 | /** |
michael@0 | 350 | * Gets the engines whose sync states have been changed by the user through the |
michael@0 | 351 | * SelectEnginesActivity. |
michael@0 | 352 | * |
michael@0 | 353 | * @param prefs |
michael@0 | 354 | * SharedPreferences of account that the engines are associated with. |
michael@0 | 355 | * @return Map<String, Boolean> of changed engines. Key is the lower-cased |
michael@0 | 356 | * engine name, Value is the new sync state. |
michael@0 | 357 | */ |
michael@0 | 358 | public static Map<String, Boolean> getUserSelectedEngines(SharedPreferences prefs) { |
michael@0 | 359 | String json = prefs.getString(PREF_USER_SELECTED_ENGINES_TO_SYNC, null); |
michael@0 | 360 | if (json == null) { |
michael@0 | 361 | return null; |
michael@0 | 362 | } |
michael@0 | 363 | try { |
michael@0 | 364 | ExtendedJSONObject o = ExtendedJSONObject.parseJSONObject(json); |
michael@0 | 365 | Map<String, Boolean> map = new HashMap<String, Boolean>(); |
michael@0 | 366 | for (Entry<String, Object> e : o.entrySet()) { |
michael@0 | 367 | String key = e.getKey(); |
michael@0 | 368 | Boolean value = (Boolean) e.getValue(); |
michael@0 | 369 | map.put(key, value); |
michael@0 | 370 | // Forms depends on history. Add forms if history is selected. |
michael@0 | 371 | if ("history".equals(key)) { |
michael@0 | 372 | map.put("forms", value); |
michael@0 | 373 | } |
michael@0 | 374 | } |
michael@0 | 375 | // Sanity check: remove forms if history does not exist. |
michael@0 | 376 | if (!map.containsKey("history")) { |
michael@0 | 377 | map.remove("forms"); |
michael@0 | 378 | } |
michael@0 | 379 | return map; |
michael@0 | 380 | } catch (Exception e) { |
michael@0 | 381 | return null; |
michael@0 | 382 | } |
michael@0 | 383 | } |
michael@0 | 384 | |
michael@0 | 385 | /** |
michael@0 | 386 | * Store a Map of engines and their sync states to prefs. |
michael@0 | 387 | * |
michael@0 | 388 | * Any engine that's disabled in the input is also recorded |
michael@0 | 389 | * as a declined engine, overwriting the stored values. |
michael@0 | 390 | * |
michael@0 | 391 | * @param prefs |
michael@0 | 392 | * SharedPreferences that the engines are associated with. |
michael@0 | 393 | * @param selectedEngines |
michael@0 | 394 | * Map<String, Boolean> of engine name to sync state |
michael@0 | 395 | */ |
michael@0 | 396 | public static void storeSelectedEnginesToPrefs(SharedPreferences prefs, Map<String, Boolean> selectedEngines) { |
michael@0 | 397 | ExtendedJSONObject jObj = new ExtendedJSONObject(); |
michael@0 | 398 | HashSet<String> declined = new HashSet<String>(); |
michael@0 | 399 | for (Entry<String, Boolean> e : selectedEngines.entrySet()) { |
michael@0 | 400 | final Boolean enabled = e.getValue(); |
michael@0 | 401 | final String engine = e.getKey(); |
michael@0 | 402 | jObj.put(engine, enabled); |
michael@0 | 403 | if (!enabled) { |
michael@0 | 404 | declined.add(engine); |
michael@0 | 405 | } |
michael@0 | 406 | } |
michael@0 | 407 | |
michael@0 | 408 | // Our history checkbox drives form history, too. |
michael@0 | 409 | // We don't need to do this for enablement: that's done at retrieval time. |
michael@0 | 410 | if (selectedEngines.containsKey("history") && !selectedEngines.get("history").booleanValue()) { |
michael@0 | 411 | declined.add("forms"); |
michael@0 | 412 | } |
michael@0 | 413 | |
michael@0 | 414 | String json = jObj.toJSONString(); |
michael@0 | 415 | long currentTime = System.currentTimeMillis(); |
michael@0 | 416 | Editor edit = prefs.edit(); |
michael@0 | 417 | edit.putString(PREF_USER_SELECTED_ENGINES_TO_SYNC, json); |
michael@0 | 418 | edit.putString(PREF_DECLINED_ENGINE_NAMES, setToJSONObjectString(declined)); |
michael@0 | 419 | edit.putLong(PREF_USER_SELECTED_ENGINES_TO_SYNC_TIMESTAMP, currentTime); |
michael@0 | 420 | Logger.error(LOG_TAG, "Storing user-selected engines at [" + currentTime + "]."); |
michael@0 | 421 | edit.commit(); |
michael@0 | 422 | } |
michael@0 | 423 | |
michael@0 | 424 | public void loadFromPrefs(SharedPreferences prefs) { |
michael@0 | 425 | if (prefs.contains(PREF_CLUSTER_URL)) { |
michael@0 | 426 | String u = prefs.getString(PREF_CLUSTER_URL, null); |
michael@0 | 427 | try { |
michael@0 | 428 | clusterURL = new URI(u); |
michael@0 | 429 | Logger.trace(LOG_TAG, "Set clusterURL from bundle: " + u); |
michael@0 | 430 | } catch (URISyntaxException e) { |
michael@0 | 431 | Logger.warn(LOG_TAG, "Ignoring bundle clusterURL (" + u + "): invalid URI.", e); |
michael@0 | 432 | } |
michael@0 | 433 | } |
michael@0 | 434 | if (prefs.contains(PREF_SYNC_ID)) { |
michael@0 | 435 | syncID = prefs.getString(PREF_SYNC_ID, null); |
michael@0 | 436 | Logger.trace(LOG_TAG, "Set syncID from bundle: " + syncID); |
michael@0 | 437 | } |
michael@0 | 438 | enabledEngineNames = getEnabledEngineNames(prefs); |
michael@0 | 439 | declinedEngineNames = getDeclinedEngineNames(prefs); |
michael@0 | 440 | userSelectedEngines = getUserSelectedEngines(prefs); |
michael@0 | 441 | userSelectedEnginesTimestamp = prefs.getLong(PREF_USER_SELECTED_ENGINES_TO_SYNC_TIMESTAMP, 0); |
michael@0 | 442 | // We don't set crypto/keys here because we need the syncKeyBundle to decrypt the JSON |
michael@0 | 443 | // and we won't have it on construction. |
michael@0 | 444 | // TODO: MetaGlobal, password, infoCollections. |
michael@0 | 445 | } |
michael@0 | 446 | |
michael@0 | 447 | public void persistToPrefs() { |
michael@0 | 448 | this.persistToPrefs(this.getPrefs()); |
michael@0 | 449 | } |
michael@0 | 450 | |
michael@0 | 451 | private static String setToJSONObjectString(Set<String> set) { |
michael@0 | 452 | ExtendedJSONObject o = new ExtendedJSONObject(); |
michael@0 | 453 | for (String name : set) { |
michael@0 | 454 | o.put(name, 0); |
michael@0 | 455 | } |
michael@0 | 456 | return o.toJSONString(); |
michael@0 | 457 | } |
michael@0 | 458 | |
michael@0 | 459 | public void persistToPrefs(SharedPreferences prefs) { |
michael@0 | 460 | Editor edit = prefs.edit(); |
michael@0 | 461 | if (clusterURL == null) { |
michael@0 | 462 | edit.remove(PREF_CLUSTER_URL); |
michael@0 | 463 | } else { |
michael@0 | 464 | edit.putString(PREF_CLUSTER_URL, clusterURL.toASCIIString()); |
michael@0 | 465 | } |
michael@0 | 466 | if (syncID != null) { |
michael@0 | 467 | edit.putString(PREF_SYNC_ID, syncID); |
michael@0 | 468 | } |
michael@0 | 469 | if (enabledEngineNames == null) { |
michael@0 | 470 | edit.remove(PREF_ENABLED_ENGINE_NAMES); |
michael@0 | 471 | } else { |
michael@0 | 472 | edit.putString(PREF_ENABLED_ENGINE_NAMES, setToJSONObjectString(enabledEngineNames)); |
michael@0 | 473 | } |
michael@0 | 474 | if (declinedEngineNames.isEmpty()) { |
michael@0 | 475 | edit.remove(PREF_DECLINED_ENGINE_NAMES); |
michael@0 | 476 | } else { |
michael@0 | 477 | edit.putString(PREF_DECLINED_ENGINE_NAMES, setToJSONObjectString(declinedEngineNames)); |
michael@0 | 478 | } |
michael@0 | 479 | if (userSelectedEngines == null) { |
michael@0 | 480 | edit.remove(PREF_USER_SELECTED_ENGINES_TO_SYNC); |
michael@0 | 481 | edit.remove(PREF_USER_SELECTED_ENGINES_TO_SYNC_TIMESTAMP); |
michael@0 | 482 | } |
michael@0 | 483 | // Don't bother saving userSelectedEngines - these should only be changed by |
michael@0 | 484 | // SelectEnginesActivity. |
michael@0 | 485 | edit.commit(); |
michael@0 | 486 | // TODO: keys. |
michael@0 | 487 | } |
michael@0 | 488 | |
michael@0 | 489 | public AuthHeaderProvider getAuthHeaderProvider() { |
michael@0 | 490 | return authHeaderProvider; |
michael@0 | 491 | } |
michael@0 | 492 | |
michael@0 | 493 | public CollectionKeys getCollectionKeys() { |
michael@0 | 494 | return collectionKeys; |
michael@0 | 495 | } |
michael@0 | 496 | |
michael@0 | 497 | public void setCollectionKeys(CollectionKeys k) { |
michael@0 | 498 | collectionKeys = k; |
michael@0 | 499 | } |
michael@0 | 500 | |
michael@0 | 501 | /** |
michael@0 | 502 | * Return path to storage endpoint without trailing slash. |
michael@0 | 503 | * |
michael@0 | 504 | * @return storage endpoint without trailing slash. |
michael@0 | 505 | */ |
michael@0 | 506 | public String storageURL() { |
michael@0 | 507 | return clusterURL + "/storage"; |
michael@0 | 508 | } |
michael@0 | 509 | |
michael@0 | 510 | protected String infoBaseURL() { |
michael@0 | 511 | return clusterURL + "/info/"; |
michael@0 | 512 | } |
michael@0 | 513 | |
michael@0 | 514 | public String infoCollectionsURL() { |
michael@0 | 515 | return infoBaseURL() + "collections"; |
michael@0 | 516 | } |
michael@0 | 517 | |
michael@0 | 518 | public String infoCollectionCountsURL() { |
michael@0 | 519 | return infoBaseURL() + "collection_counts"; |
michael@0 | 520 | } |
michael@0 | 521 | |
michael@0 | 522 | public String metaURL() { |
michael@0 | 523 | return storageURL() + "/meta/global"; |
michael@0 | 524 | } |
michael@0 | 525 | |
michael@0 | 526 | public URI collectionURI(String collection) throws URISyntaxException { |
michael@0 | 527 | return new URI(storageURL() + "/" + collection); |
michael@0 | 528 | } |
michael@0 | 529 | |
michael@0 | 530 | public URI collectionURI(String collection, boolean full) throws URISyntaxException { |
michael@0 | 531 | // Do it this way to make it easier to add more params later. |
michael@0 | 532 | // It's pretty ugly, I'll grant. |
michael@0 | 533 | boolean anyParams = full; |
michael@0 | 534 | String uriParams = ""; |
michael@0 | 535 | if (anyParams) { |
michael@0 | 536 | StringBuilder params = new StringBuilder("?"); |
michael@0 | 537 | if (full) { |
michael@0 | 538 | params.append("full=1"); |
michael@0 | 539 | } |
michael@0 | 540 | uriParams = params.toString(); |
michael@0 | 541 | } |
michael@0 | 542 | String uri = storageURL() + "/" + collection + uriParams; |
michael@0 | 543 | return new URI(uri); |
michael@0 | 544 | } |
michael@0 | 545 | |
michael@0 | 546 | public URI wboURI(String collection, String id) throws URISyntaxException { |
michael@0 | 547 | return new URI(storageURL() + "/" + collection + "/" + id); |
michael@0 | 548 | } |
michael@0 | 549 | |
michael@0 | 550 | public URI keysURI() throws URISyntaxException { |
michael@0 | 551 | return wboURI("crypto", "keys"); |
michael@0 | 552 | } |
michael@0 | 553 | |
michael@0 | 554 | public URI getClusterURL() { |
michael@0 | 555 | return clusterURL; |
michael@0 | 556 | } |
michael@0 | 557 | |
michael@0 | 558 | public String getClusterURLString() { |
michael@0 | 559 | if (clusterURL == null) { |
michael@0 | 560 | return null; |
michael@0 | 561 | } |
michael@0 | 562 | return clusterURL.toASCIIString(); |
michael@0 | 563 | } |
michael@0 | 564 | |
michael@0 | 565 | public void setClusterURL(URI u) { |
michael@0 | 566 | this.clusterURL = u; |
michael@0 | 567 | } |
michael@0 | 568 | |
michael@0 | 569 | /** |
michael@0 | 570 | * Used for direct management of related prefs. |
michael@0 | 571 | */ |
michael@0 | 572 | public Editor getEditor() { |
michael@0 | 573 | return this.getPrefs().edit(); |
michael@0 | 574 | } |
michael@0 | 575 | |
michael@0 | 576 | /** |
michael@0 | 577 | * We persist two different clients timestamps: our own record's, |
michael@0 | 578 | * and the timestamp for the collection. |
michael@0 | 579 | */ |
michael@0 | 580 | public void persistServerClientRecordTimestamp(long timestamp) { |
michael@0 | 581 | getEditor().putLong(SyncConfiguration.CLIENT_RECORD_TIMESTAMP, timestamp).commit(); |
michael@0 | 582 | } |
michael@0 | 583 | |
michael@0 | 584 | public long getPersistedServerClientRecordTimestamp() { |
michael@0 | 585 | return getPrefs().getLong(SyncConfiguration.CLIENT_RECORD_TIMESTAMP, 0); |
michael@0 | 586 | } |
michael@0 | 587 | |
michael@0 | 588 | public void persistServerClientsTimestamp(long timestamp) { |
michael@0 | 589 | getEditor().putLong(SyncConfiguration.CLIENTS_COLLECTION_TIMESTAMP, timestamp).commit(); |
michael@0 | 590 | } |
michael@0 | 591 | |
michael@0 | 592 | public long getPersistedServerClientsTimestamp() { |
michael@0 | 593 | return getPrefs().getLong(SyncConfiguration.CLIENTS_COLLECTION_TIMESTAMP, 0); |
michael@0 | 594 | } |
michael@0 | 595 | |
michael@0 | 596 | public void purgeCryptoKeys() { |
michael@0 | 597 | if (collectionKeys != null) { |
michael@0 | 598 | collectionKeys.clear(); |
michael@0 | 599 | } |
michael@0 | 600 | persistedCryptoKeys().purge(); |
michael@0 | 601 | } |
michael@0 | 602 | |
michael@0 | 603 | public void purgeMetaGlobal() { |
michael@0 | 604 | metaGlobal = null; |
michael@0 | 605 | persistedMetaGlobal().purge(); |
michael@0 | 606 | } |
michael@0 | 607 | |
michael@0 | 608 | public PersistedCrypto5Keys persistedCryptoKeys() { |
michael@0 | 609 | return new PersistedCrypto5Keys(getPrefs(), syncKeyBundle); |
michael@0 | 610 | } |
michael@0 | 611 | |
michael@0 | 612 | public PersistedMetaGlobal persistedMetaGlobal() { |
michael@0 | 613 | return new PersistedMetaGlobal(getPrefs()); |
michael@0 | 614 | } |
michael@0 | 615 | } |