summaryrefslogtreecommitdiffstats
path: root/mobile/android/services/src/main/java/org/mozilla/gecko/sync
diff options
context:
space:
mode:
authorAscrod <32915892+Ascrod@users.noreply.github.com>2019-04-18 20:35:10 -0400
committerAscrod <32915892+Ascrod@users.noreply.github.com>2019-04-18 20:35:10 -0400
commitaf7e140d4ed8f5bc9a69da2f0338ad3cb1319dec (patch)
tree4aac6c4383fb9e279fccb13c65a4e44595fd4cf6 /mobile/android/services/src/main/java/org/mozilla/gecko/sync
parent40fc72376411587e7bf9985fb9545eca1c9aaa8e (diff)
parent51722cd4fecb5c8c79a302f2771cad71535df5ea (diff)
downloadUXP-af7e140d4ed8f5bc9a69da2f0338ad3cb1319dec.tar
UXP-af7e140d4ed8f5bc9a69da2f0338ad3cb1319dec.tar.gz
UXP-af7e140d4ed8f5bc9a69da2f0338ad3cb1319dec.tar.lz
UXP-af7e140d4ed8f5bc9a69da2f0338ad3cb1319dec.tar.xz
UXP-af7e140d4ed8f5bc9a69da2f0338ad3cb1319dec.zip
Merge branch 'master' into default-pref
Diffstat (limited to 'mobile/android/services/src/main/java/org/mozilla/gecko/sync')
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/AlreadySyncingException.java22
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/BackoffHandler.java34
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/BadRequiredFieldJSONException.java5
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/CollectionKeys.java199
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/CommandProcessor.java261
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/CommandRunner.java22
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/CredentialException.java56
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/CryptoRecord.java255
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/DelayedWorkTracker.java69
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/EngineSettings.java31
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/ExtendedJSONObject.java426
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/GlobalSession.java1167
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/HTTPFailureException.java47
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/InfoCollections.java103
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/InfoConfiguration.java93
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/InfoCounts.java67
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/JSONRecordFetcher.java145
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/KeyBundleProvider.java11
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobal.java372
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobalException.java45
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobalMissingEnginesException.java9
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobalNotSetException.java9
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/NoCollectionKeysSetException.java16
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/NodeAuthenticationException.java16
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/NonArrayJSONException.java17
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/NonObjectJSONException.java17
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/NullClusterURLException.java16
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/PersistedMetaGlobal.java86
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/PrefsBackoffHandler.java59
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/README.txt1
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/Server11PreviousPostFailedException.java12
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/Server11RecordPostFailedException.java12
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/SharedPreferencesClientsDataDelegate.java121
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/Sync11Configuration.java84
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncConfiguration.java480
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncConfigurationException.java16
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncConstants.java20
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncException.java34
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/SynchronizerConfiguration.java68
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/ThreadPool.java15
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/UnexpectedJSONException.java25
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/UnknownSynchronizerConfigurationVersionException.java16
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/Utils.java575
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/CryptoException.java19
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/CryptoInfo.java232
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/HKDF.java128
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/HMACVerificationException.java12
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/KeyBundle.java135
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/MissingCryptoInputException.java9
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/NoKeyBundleException.java9
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/PBKDF2.java78
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/PersistedCrypto5Keys.java103
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/ClientsDataDelegate.java28
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/FreshStartDelegate.java10
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/GlobalSessionCallback.java49
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/JSONRecordFetchDelegate.java19
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/KeyUploadDelegate.java21
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/MetaGlobalDelegate.java15
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/WipeServerDelegate.java10
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/Crypto5MiddlewareRepository.java76
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/Crypto5MiddlewareRepositorySession.java172
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/MiddlewareRepository.java22
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/MiddlewareRepositorySession.java185
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/AbstractBearerTokenAuthHeaderProvider.java34
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/AuthHeaderProvider.java30
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BaseResource.java565
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BaseResourceDelegate.java44
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BasicAuthHeaderProvider.java51
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BearerAuthHeaderProvider.java22
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BrowserIDAuthHeaderProvider.java23
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/ConnectionMonitorThread.java44
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/GzipNonChunkedCompressingEntity.java92
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HMACAuthHeaderProvider.java257
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HandleProgressException.java15
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HawkAuthHeaderProvider.java403
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HttpResponseObserver.java20
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/MozResponse.java225
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/Resource.java20
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/ResourceDelegate.java55
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SRPConstants.java174
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncResponse.java157
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageCollectionRequest.java145
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageCollectionRequestDelegate.java9
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRecordRequest.java95
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRequest.java204
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRequestDelegate.java38
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRequestIncrementalDelegate.java9
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageResponse.java85
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/TLSSocketFactory.java62
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/WBOCollectionRequestDelegate.java35
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/WBORequestDelegate.java14
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/BookmarkNeedsReparentingException.java17
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/BookmarksRepository.java16
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/ConstrainedServer11Repository.java51
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/FetchFailedException.java11
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/HashSetStoreTracker.java61
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/HistoryRepository.java16
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/IdentityRecordFactory.java15
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InactiveSessionException.java17
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InvalidBookmarkTypeException.java17
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InvalidRequestException.java16
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InvalidSessionTransitionException.java17
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/MultipleRecordsForGuidException.java16
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NoContentProviderException.java25
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NoGuidForIdException.java16
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NoStoreDelegateException.java11
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NullCursorException.java17
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/ParentNotFoundException.java17
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/ProfileDatabaseException.java17
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RecordFactory.java13
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RecordFilter.java11
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Repository.java18
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RepositorySession.java384
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RepositorySessionBundle.java55
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Server11Repository.java144
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Server11RepositorySession.java104
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/StoreFailedException.java11
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/StoreTracker.java82
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/StoreTrackingRepositorySession.java102
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java326
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserBookmarksRepository.java25
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java1107
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserHistoryDataAccessor.java188
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserHistoryRepository.java25
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserHistoryRepositorySession.java208
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserRepository.java74
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java232
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserRepositorySession.java792
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/BookmarksDeletionManager.java239
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/BookmarksInsertionManager.java298
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/BrowserContractHelpers.java154
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/CachedSQLiteOpenHelper.java62
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/ClientsDatabase.java252
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/ClientsDatabaseAccessor.java178
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/FennecTabsRepository.java383
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/FormHistoryRepositorySession.java723
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/PasswordsRepositorySession.java725
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/RepoUtils.java290
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/VisitsHelper.java130
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferrableRepositorySessionCreationDelegate.java41
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionBeginDelegate.java46
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionFetchRecordsDelegate.java56
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionFinishDelegate.java51
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionStoreDelegate.java57
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionBeginDelegate.java23
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionCleanDelegate.java12
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionCreationDelegate.java15
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionFetchRecordsDelegate.java27
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionFinishDelegate.java16
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionGuidsSinceDelegate.java10
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionStoreDelegate.java23
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionWipeDelegate.java13
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/BookmarkRecord.java488
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/BookmarkRecordFactory.java25
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/ClientRecord.java231
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/ClientRecordFactory.java17
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/FormHistoryRecord.java139
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/HistoryRecord.java217
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/HistoryRecordFactory.java25
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/PasswordRecord.java205
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/PasswordRecordFactory.java19
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/Record.java308
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/RecordParseException.java14
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/TabsRecord.java153
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/TabsRecordFactory.java17
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/VersionConstants.java14
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/downloaders/BatchingDownloader.java310
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/downloaders/BatchingDownloaderDelegate.java91
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/BatchMeta.java165
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/BatchingUploader.java344
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/BufferSizeTracker.java103
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/MayUploadProvider.java9
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/Payload.java66
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/PayloadUploadDelegate.java185
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/RecordUploadRunnable.java176
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/Constants.java29
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/InvalidSyncKeyException.java9
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/activities/ActivityUtils.java34
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/activities/WebURLFinder.java161
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AbstractNonRepositorySyncStage.java26
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AbstractSessionManagingSyncStage.java43
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AndroidBrowserBookmarksServerSyncStage.java80
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AndroidBrowserHistoryServerSyncStage.java74
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/CheckPreconditionsStage.java13
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/CompletedStage.java16
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/EnsureCrypto5KeysStage.java192
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FennecTabsServerSyncStage.java40
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FetchInfoCollectionsStage.java44
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FetchInfoConfigurationStage.java59
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FetchMetaGlobalStage.java79
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FormHistoryServerSyncStage.java76
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/GlobalSyncStage.java93
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/NoSuchStageException.java13
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/PasswordsServerSyncStage.java38
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/SafeConstrainedServer11Repository.java110
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/ServerSyncStage.java627
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/SyncClientsEngineStage.java691
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/UploadMetaGlobalStage.java18
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/ConcurrentRecordConsumer.java122
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordConsumer.java26
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordsChannel.java292
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordsChannelDelegate.java13
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordsConsumerDelegate.java23
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SerialRecordConsumer.java131
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/ServerLocalSynchronizer.java18
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/ServerLocalSynchronizerSession.java78
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SessionNotBegunException.java19
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/Synchronizer.java105
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SynchronizerDelegate.java10
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SynchronizerSession.java425
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SynchronizerSessionDelegate.java13
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/UnbundleError.java19
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/UnexpectedSessionException.java26
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/sync/telemetry/TelemetryContract.java56
214 files changed, 0 insertions, 24773 deletions
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/AlreadySyncingException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/AlreadySyncingException.java
deleted file mode 100644
index 75eb5ad37..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/AlreadySyncingException.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
-
-import android.content.SyncResult;
-
-public class AlreadySyncingException extends SyncException {
- Stage inState;
- public AlreadySyncingException(Stage currentState) {
- inState = currentState;
- }
-
- private static final long serialVersionUID = -5647548462539009893L;
-
- @Override
- public void updateStats(GlobalSession globalSession, SyncResult syncResult) {
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/BackoffHandler.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/BackoffHandler.java
deleted file mode 100644
index abb880621..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/BackoffHandler.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-
-public interface BackoffHandler {
- public long getEarliestNextRequest();
-
- /**
- * Provide a timestamp in millis before which we shouldn't sync again.
- * Overrides any existing value.
- *
- * @param next
- * a timestamp in milliseconds.
- */
- public void setEarliestNextRequest(long next);
-
- /**
- * Provide a timestamp in millis before which we shouldn't sync again. Only
- * change our persisted value if it's later than the existing time.
- *
- * @param next
- * a timestamp in milliseconds.
- */
- public void extendEarliestNextRequest(long next);
-
- /**
- * Return the number of milliseconds until we're allowed to sync again,
- * or 0 if now is fine.
- */
- public long delayMilliseconds();
-} \ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/BadRequiredFieldJSONException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/BadRequiredFieldJSONException.java
deleted file mode 100644
index 3db93652d..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/BadRequiredFieldJSONException.java
+++ /dev/null
@@ -1,5 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CollectionKeys.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CollectionKeys.java
deleted file mode 100644
index 1fd363bcb..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CollectionKeys.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map.Entry;
-import java.util.Set;
-
-import org.json.simple.JSONArray;
-import org.mozilla.apache.commons.codec.binary.Base64;
-import org.mozilla.gecko.sync.crypto.CryptoException;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-
-public class CollectionKeys {
- private KeyBundle defaultKeyBundle = null;
- private final HashMap<String, KeyBundle> collectionKeyBundles = new HashMap<String, KeyBundle>();
-
- /**
- * Randomly generate a basic CollectionKeys object.
- * @throws CryptoException
- */
- public static CollectionKeys generateCollectionKeys() throws CryptoException {
- CollectionKeys ck = new CollectionKeys();
- ck.clear();
- ck.defaultKeyBundle = KeyBundle.withRandomKeys();
- // TODO: eventually we would like to keep per-collection keys, just generate
- // new ones as appropriate.
- return ck;
- }
-
- public KeyBundle defaultKeyBundle() throws NoCollectionKeysSetException {
- if (this.defaultKeyBundle == null) {
- throw new NoCollectionKeysSetException();
- }
- return this.defaultKeyBundle;
- }
-
- public boolean keyBundleForCollectionIsNotDefault(String collection) {
- return collectionKeyBundles.containsKey(collection);
- }
-
- public KeyBundle keyBundleForCollection(String collection)
- throws NoCollectionKeysSetException {
- if (this.defaultKeyBundle == null) {
- throw new NoCollectionKeysSetException();
- }
- if (keyBundleForCollectionIsNotDefault(collection)) {
- return collectionKeyBundles.get(collection);
- }
- return this.defaultKeyBundle;
- }
-
- /**
- * Take a pair of values in a JSON array, handing them off to KeyBundle to
- * produce a usable keypair.
- */
- private static KeyBundle arrayToKeyBundle(JSONArray array) throws UnsupportedEncodingException {
- String encKeyStr = (String) array.get(0);
- String hmacKeyStr = (String) array.get(1);
- return KeyBundle.fromBase64EncodedKeys(encKeyStr, hmacKeyStr);
- }
-
- @SuppressWarnings("unchecked")
- private static JSONArray keyBundleToArray(KeyBundle bundle) {
- // Generate JSON.
- JSONArray keysArray = new JSONArray();
- keysArray.add(new String(Base64.encodeBase64(bundle.getEncryptionKey())));
- keysArray.add(new String(Base64.encodeBase64(bundle.getHMACKey())));
- return keysArray;
- }
-
- private ExtendedJSONObject asRecordContents() throws NoCollectionKeysSetException {
- ExtendedJSONObject json = new ExtendedJSONObject();
- json.put("id", "keys");
- json.put("collection", "crypto");
- json.put("default", keyBundleToArray(this.defaultKeyBundle()));
- ExtendedJSONObject colls = new ExtendedJSONObject();
- for (Entry<String, KeyBundle> collKey : collectionKeyBundles.entrySet()) {
- colls.put(collKey.getKey(), keyBundleToArray(collKey.getValue()));
- }
- json.put("collections", colls);
- return json;
- }
-
- public CryptoRecord asCryptoRecord() throws NoCollectionKeysSetException {
- ExtendedJSONObject payload = this.asRecordContents();
- CryptoRecord record = new CryptoRecord(payload);
- record.collection = "crypto";
- record.guid = "keys";
- record.deleted = false;
- return record;
- }
-
- /**
- * Set my key bundle and collection keys with the given key bundle and data
- * (possibly decrypted) from the given record.
- *
- * @param keys
- * A "crypto/keys" <code>CryptoRecord</code>, encrypted with
- * <code>syncKeyBundle</code> if <code>syncKeyBundle</code> is non-null.
- * @param syncKeyBundle
- * If non-null, the sync key bundle to decrypt <code>keys</code> with.
- */
- public void setKeyPairsFromWBO(CryptoRecord keys, KeyBundle syncKeyBundle)
- throws CryptoException, IOException, NonObjectJSONException {
- if (keys == null) {
- throw new IllegalArgumentException("cannot set key pairs from null record");
- }
- if (syncKeyBundle != null) {
- keys.keyBundle = syncKeyBundle;
- keys.decrypt();
- }
- ExtendedJSONObject cleartext = keys.payload;
- KeyBundle defaultKey = arrayToKeyBundle((JSONArray) cleartext.get("default"));
-
- ExtendedJSONObject collections = cleartext.getObject("collections");
- HashMap<String, KeyBundle> collectionKeys = new HashMap<String, KeyBundle>();
- for (Entry<String, Object> pair : collections.entrySet()) {
- KeyBundle bundle = arrayToKeyBundle((JSONArray) pair.getValue());
- collectionKeys.put(pair.getKey(), bundle);
- }
-
- this.collectionKeyBundles.clear();
- this.collectionKeyBundles.putAll(collectionKeys);
- this.defaultKeyBundle = defaultKey;
- }
-
- public void setKeyBundleForCollection(String collection, KeyBundle keys) {
- this.collectionKeyBundles.put(collection, keys);
- }
-
- public void setDefaultKeyBundle(KeyBundle keys) {
- this.defaultKeyBundle = keys;
- }
-
- public void clear() {
- this.defaultKeyBundle = null;
- this.collectionKeyBundles.clear();
- }
-
- /**
- * Return set of collections where key is either missing from one collection
- * or not the same in both collections.
- * <p>
- * Does not check for different default keys.
- */
- public static Set<String> differences(CollectionKeys a, CollectionKeys b) {
- Set<String> differences = new HashSet<String>();
- Set<String> collections = new HashSet<String>(a.collectionKeyBundles.keySet());
- collections.addAll(b.collectionKeyBundles.keySet());
-
- // Iterate through one collection, collecting missing and differences.
- for (String collection : collections) {
- KeyBundle keyA;
- KeyBundle keyB;
- try {
- keyA = a.keyBundleForCollection(collection); // Will return default key as appropriate.
- keyB = b.keyBundleForCollection(collection); // Will return default key as appropriate.
- } catch (NoCollectionKeysSetException e) {
- differences.add(collection);
- continue;
- }
- // keyA and keyB are not null at this point.
- if (!keyA.equals(keyB)) {
- differences.add(collection);
- }
- }
-
- return differences;
- }
-
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof CollectionKeys)) {
- return false;
- }
- CollectionKeys other = (CollectionKeys) o;
- try {
- // It would be nice to use map equality here, but there can be map entries
- // where the key is the default key that should compare equal to a missing
- // map entry. Therefore, we always compute the set of differences.
- return defaultKeyBundle().equals(other.defaultKeyBundle()) &&
- CollectionKeys.differences(this, other).isEmpty();
- } catch (NoCollectionKeysSetException e) {
- // If either default key bundle is not set, we'll say the bundles are not equal.
- return false;
- }
- }
-
- @Override
- public int hashCode() {
- return super.hashCode();
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CommandProcessor.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CommandProcessor.java
deleted file mode 100644
index 371603de5..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CommandProcessor.java
+++ /dev/null
@@ -1,261 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import org.json.simple.JSONArray;
-import org.json.simple.JSONObject;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
-import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * Process commands received from Sync clients.
- * <p>
- * We need a command processor at two different times:
- * <ol>
- * <li>We execute commands during the "clients" engine stage of a Sync. Each
- * command takes a <code>GlobalSession</code> instance as a parameter.</li>
- * <li>We queue commands to be executed or propagated to other Sync clients
- * during an activity completely unrelated to a sync</li>
- * </ol>
- * To provide a processor for both these time frames, we maintain a static
- * long-lived singleton.
- */
-public class CommandProcessor {
- private static final String LOG_TAG = "Command";
- private static final AtomicInteger currentId = new AtomicInteger();
- protected ConcurrentHashMap<String, CommandRunner> commands = new ConcurrentHashMap<String, CommandRunner>();
-
- private final static CommandProcessor processor = new CommandProcessor();
-
- /**
- * Get the global singleton command processor.
- *
- * @return the singleton processor.
- */
- public static CommandProcessor getProcessor() {
- return processor;
- }
-
- public static class Command {
- public final String commandType;
- public final JSONArray args;
- private List<String> argsList;
-
- public Command(String commandType, JSONArray args) {
- this.commandType = commandType;
- this.args = args;
- }
-
- /**
- * Get list of arguments as strings. Individual arguments may be null.
- *
- * @return list of strings.
- */
- public synchronized List<String> getArgsList() {
- if (argsList == null) {
- ArrayList<String> argsList = new ArrayList<String>(args.size());
-
- for (int i = 0; i < args.size(); i++) {
- final Object arg = args.get(i);
- if (arg == null) {
- argsList.add(null);
- continue;
- }
- argsList.add(arg.toString());
- }
- this.argsList = argsList;
- }
- return this.argsList;
- }
-
- @SuppressWarnings("unchecked")
- public JSONObject asJSONObject() {
- JSONObject out = new JSONObject();
- out.put("command", this.commandType);
- out.put("args", this.args);
- return out;
- }
- }
-
- /**
- * Register a command.
- * <p>
- * Any existing registration is overwritten.
- *
- * @param commandType
- * the name of the command, i.e., "displayURI".
- * @param command
- * the <code>CommandRunner</code> instance that should handle the
- * command.
- */
- public void registerCommand(String commandType, CommandRunner command) {
- commands.put(commandType, command);
- }
-
- /**
- * Process a command in the context of the given global session.
- *
- * @param session
- * the <code>GlobalSession</code> instance currently executing.
- * @param unparsedCommand
- * command as a <code>ExtendedJSONObject</code> instance.
- */
- public void processCommand(final GlobalSession session, ExtendedJSONObject unparsedCommand) {
- Command command = parseCommand(unparsedCommand);
- if (command == null) {
- Logger.debug(LOG_TAG, "Invalid command: " + unparsedCommand + " will not be processed.");
- return;
- }
-
- CommandRunner executableCommand = commands.get(command.commandType);
- if (executableCommand == null) {
- Logger.debug(LOG_TAG, "Command \"" + command.commandType + "\" not registered and will not be processed.");
- return;
- }
-
- executableCommand.executeCommand(session, command.getArgsList());
- }
-
- /**
- * Parse a JSON command into a ParsedCommand object for easier handling.
- *
- * @param unparsedCommand - command as ExtendedJSONObject
- * @return - null if command is invalid, else return ParsedCommand with
- * no null attributes.
- */
- protected static Command parseCommand(ExtendedJSONObject unparsedCommand) {
- String type = (String) unparsedCommand.get("command");
- if (type == null) {
- return null;
- }
-
- try {
- JSONArray unparsedArgs = unparsedCommand.getArray("args");
- if (unparsedArgs == null) {
- return null;
- }
-
- return new Command(type, unparsedArgs);
- } catch (NonArrayJSONException e) {
- Logger.debug(LOG_TAG, "Unable to parse args array. Invalid command");
- return null;
- }
- }
-
- @SuppressWarnings("unchecked")
- public void sendURIToClientForDisplay(String uri, String clientID, String title, String sender, Context context) {
- Logger.info(LOG_TAG, "Sending URI to client " + clientID + ".");
- if (Logger.LOG_PERSONAL_INFORMATION) {
- Logger.pii(LOG_TAG, "URI is " + uri + "; title is '" + title + "'.");
- }
-
- final JSONArray args = new JSONArray();
- args.add(uri);
- args.add(sender);
- args.add(title);
-
- final Command displayURICommand = new Command("displayURI", args);
- this.sendCommand(clientID, displayURICommand, context);
- }
-
- /**
- * Validates and sends a command to a client or all clients.
- *
- * Calling this does not actually sync the command data to the server. If the
- * client already has the command/args pair, it won't receive a duplicate
- * command.
- *
- * @param clientID
- * Client ID to send command to. If null, send to all remote
- * clients.
- * @param command
- * Command to invoke on remote clients
- */
- public void sendCommand(String clientID, Command command, Context context) {
- Logger.debug(LOG_TAG, "In sendCommand.");
-
- CommandRunner commandData = commands.get(command.commandType);
-
- // Don't send commands that we don't know about.
- if (commandData == null) {
- Logger.error(LOG_TAG, "Unknown command to send: " + command);
- return;
- }
-
- // Don't send a command with the wrong number of arguments.
- if (!commandData.argumentsAreValid(command.getArgsList())) {
- Logger.error(LOG_TAG, "Expected " + commandData.argCount + " args for '" +
- command + "', but got " + command.args);
- return;
- }
-
- if (clientID != null) {
- this.sendCommandToClient(clientID, command, context);
- return;
- }
-
- ClientsDatabaseAccessor db = new ClientsDatabaseAccessor(context);
- try {
- Map<String, ClientRecord> clientMap = db.fetchAllClients();
- for (ClientRecord client : clientMap.values()) {
- this.sendCommandToClient(client.guid, command, context);
- }
- } catch (NullCursorException e) {
- Logger.error(LOG_TAG, "NullCursorException when fetching all GUIDs");
- } finally {
- db.close();
- }
- }
-
- protected void sendCommandToClient(String clientID, Command command, Context context) {
- Logger.info(LOG_TAG, "Sending " + command.commandType + " to " + clientID);
-
- ClientsDatabaseAccessor db = new ClientsDatabaseAccessor(context);
- try {
- db.store(clientID, command);
- } catch (NullCursorException e) {
- Logger.error(LOG_TAG, "NullCursorException: Unable to send command.");
- } finally {
- db.close();
- }
- }
-
- public static void displayURI(final List<String> args, final Context context) {
- // We trust the client sender that these exist.
- final String uri = args.get(0);
- final String clientId = args.get(1);
- Logger.pii(LOG_TAG, "Received a URI for display: " + uri + " from " + clientId);
-
- if (uri == null) {
- Logger.pii(LOG_TAG, "URI is null – ignoring");
- return;
- }
-
- String title = null;
- if (args.size() == 3) {
- title = args.get(2);
- }
-
- final Intent sendTabNotificationIntent = new Intent();
- sendTabNotificationIntent.setClassName(context, BrowserContract.TAB_RECEIVED_SERVICE_CLASS_NAME);
- sendTabNotificationIntent.setData(Uri.parse(uri));
- sendTabNotificationIntent.putExtra(Intent.EXTRA_TITLE, title);
- sendTabNotificationIntent.putExtra(BrowserContract.EXTRA_CLIENT_GUID, clientId);
- final ComponentName componentName = context.startService(sendTabNotificationIntent);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CommandRunner.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CommandRunner.java
deleted file mode 100644
index c7a0f1762..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CommandRunner.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import java.util.List;
-
-public abstract class CommandRunner {
- public final int argCount;
-
- public CommandRunner(int argCount) {
- this.argCount = argCount;
- }
-
- public abstract void executeCommand(GlobalSession session, List<String> args);
-
- public boolean argumentsAreValid(List<String> args) {
- return args != null &&
- args.size() == argCount;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CredentialException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CredentialException.java
deleted file mode 100644
index f9004e14c..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CredentialException.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import android.content.SyncResult;
-
-/**
- * There was a problem with the Sync account's credentials: bad username,
- * missing password, malformed sync key, etc.
- */
-public abstract class CredentialException extends SyncException {
- private static final long serialVersionUID = 833010553314100538L;
-
- public CredentialException() {
- super();
- }
-
- public CredentialException(final Throwable e) {
- super(e);
- }
-
- @Override
- public void updateStats(GlobalSession globalSession, SyncResult syncResult) {
- syncResult.stats.numAuthExceptions += 1;
- }
-
- /**
- * No credentials at all.
- */
- public static class MissingAllCredentialsException extends CredentialException {
- private static final long serialVersionUID = 3763937096217604611L;
-
- public MissingAllCredentialsException() {
- super();
- }
-
- public MissingAllCredentialsException(final Throwable e) {
- super(e);
- }
- }
-
- /**
- * Some credential is missing.
- */
- public static class MissingCredentialException extends CredentialException {
- private static final long serialVersionUID = -7543031216547596248L;
-
- public final String missingCredential;
-
- public MissingCredentialException(final String missingCredential) {
- this.missingCredential = missingCredential;
- }
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CryptoRecord.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CryptoRecord.java
deleted file mode 100644
index 65563d344..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CryptoRecord.java
+++ /dev/null
@@ -1,255 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-
-import org.json.simple.JSONObject;
-import org.mozilla.apache.commons.codec.binary.Base64;
-import org.mozilla.gecko.sync.crypto.CryptoException;
-import org.mozilla.gecko.sync.crypto.CryptoInfo;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.mozilla.gecko.sync.crypto.MissingCryptoInputException;
-import org.mozilla.gecko.sync.crypto.NoKeyBundleException;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-import org.mozilla.gecko.sync.repositories.domain.RecordParseException;
-
-/**
- * A Sync crypto record has:
- *
- * <ul>
- * <li>a collection of fields which are not encrypted (id and collection);</il>
- * <li>a set of metadata fields (index, modified, ttl);</il>
- * <li>a payload, which is encrypted and decrypted on request.</il>
- * </ul>
- *
- * The payload flips between being a blob of JSON with hmac/IV/ciphertext
- * attributes and the cleartext itself.
- *
- * Until there's some benefit to the abstraction, we're simply going to call
- * this <code>CryptoRecord</code>.
- *
- * <code>CryptoRecord</code> uses <code>CryptoInfo</code> to do the actual
- * encryption and decryption.
- */
-public class CryptoRecord extends Record {
-
- // JSON related constants.
- private static final String KEY_ID = "id";
- private static final String KEY_COLLECTION = "collection";
- private static final String KEY_PAYLOAD = "payload";
- private static final String KEY_MODIFIED = "modified";
- private static final String KEY_SORTINDEX = "sortindex";
- private static final String KEY_TTL = "ttl";
- private static final String KEY_CIPHERTEXT = "ciphertext";
- private static final String KEY_HMAC = "hmac";
- private static final String KEY_IV = "IV";
-
- /**
- * Helper method for doing actual decryption.
- *
- * Input: JSONObject containing a valid payload (cipherText, IV, HMAC),
- * KeyBundle with keys for decryption. Output: byte[] clearText
- * @throws CryptoException
- * @throws UnsupportedEncodingException
- */
- private static byte[] decryptPayload(ExtendedJSONObject payload, KeyBundle keybundle) throws CryptoException, UnsupportedEncodingException {
- byte[] ciphertext = Base64.decodeBase64(((String) payload.get(KEY_CIPHERTEXT)).getBytes("UTF-8"));
- byte[] iv = Base64.decodeBase64(((String) payload.get(KEY_IV)).getBytes("UTF-8"));
- byte[] hmac = Utils.hex2Byte((String) payload.get(KEY_HMAC));
-
- return CryptoInfo.decrypt(ciphertext, iv, hmac, keybundle).getMessage();
- }
-
- // The encrypted JSON body object.
- // The decrypted JSON body object. Fields are copied from `body`.
-
- public ExtendedJSONObject payload;
- public KeyBundle keyBundle;
-
- /**
- * Don't forget to set cleartext or body!
- */
- public CryptoRecord() {
- super(null, null, 0, false);
- }
-
- public CryptoRecord(ExtendedJSONObject payload) {
- super(null, null, 0, false);
- if (payload == null) {
- throw new IllegalArgumentException(
- "No payload provided to CryptoRecord constructor.");
- }
- this.payload = payload;
- }
-
- public CryptoRecord(String jsonString) throws IOException, NonObjectJSONException {
-
- this(new ExtendedJSONObject(jsonString));
- }
-
- /**
- * Create a new CryptoRecord with the same metadata as an existing record.
- *
- * @param source
- */
- public CryptoRecord(Record source) {
- super(source.guid, source.collection, source.lastModified, source.deleted);
- this.ttl = source.ttl;
- }
-
- @Override
- public Record copyWithIDs(String guid, long androidID) {
- CryptoRecord out = new CryptoRecord(this);
- out.guid = guid;
- out.androidID = androidID;
- out.sortIndex = this.sortIndex;
- out.ttl = this.ttl;
- out.payload = (this.payload == null) ? null : new ExtendedJSONObject(this.payload.object);
- out.keyBundle = this.keyBundle; // TODO: copy me?
- return out;
- }
-
- /**
- * Take a whole record as JSON -- i.e., something like
- *
- * {"payload": "{...}", "id":"foobarbaz"}
- *
- * and turn it into a CryptoRecord object.
- *
- * @param jsonRecord
- * @return
- * A CryptoRecord that encapsulates the provided record.
- *
- * @throws NonObjectJSONException
- * @throws IOException
- */
- public static CryptoRecord fromJSONRecord(String jsonRecord)
- throws NonObjectJSONException, IOException, RecordParseException {
- byte[] bytes = jsonRecord.getBytes("UTF-8");
- ExtendedJSONObject object = ExtendedJSONObject.parseUTF8AsJSONObject(bytes);
-
- return CryptoRecord.fromJSONRecord(object);
- }
-
- // TODO: defensive programming.
- public static CryptoRecord fromJSONRecord(ExtendedJSONObject jsonRecord)
- throws IOException, NonObjectJSONException, RecordParseException {
- String id = (String) jsonRecord.get(KEY_ID);
- String collection = (String) jsonRecord.get(KEY_COLLECTION);
- String jsonEncodedPayload = (String) jsonRecord.get(KEY_PAYLOAD);
-
- ExtendedJSONObject payload = new ExtendedJSONObject(jsonEncodedPayload);
-
- CryptoRecord record = new CryptoRecord(payload);
- record.guid = id;
- record.collection = collection;
- if (jsonRecord.containsKey(KEY_MODIFIED)) {
- Long timestamp = jsonRecord.getTimestamp(KEY_MODIFIED);
- if (timestamp == null) {
- throw new RecordParseException("timestamp could not be parsed");
- }
- record.lastModified = timestamp;
- }
- if (jsonRecord.containsKey(KEY_SORTINDEX)) {
- // getLong tries to cast to Long, and might return null. We catch all
- // exceptions, just to be safe.
- try {
- record.sortIndex = jsonRecord.getLong(KEY_SORTINDEX);
- } catch (Exception e) {
- throw new RecordParseException("timestamp could not be parsed");
- }
- }
- if (jsonRecord.containsKey(KEY_TTL)) {
- // TTLs are never returned by the sync server, so should never be true if
- // the record was fetched.
- try {
- record.ttl = jsonRecord.getLong(KEY_TTL);
- } catch (Exception e) {
- throw new RecordParseException("TTL could not be parsed");
- }
- }
- // TODO: deleted?
- return record;
- }
-
- public void setKeyBundle(KeyBundle bundle) {
- this.keyBundle = bundle;
- }
-
- public CryptoRecord decrypt() throws CryptoException, IOException, NonObjectJSONException {
- if (keyBundle == null) {
- throw new NoKeyBundleException();
- }
-
- // Check that payload contains all pieces for crypto.
- if (!payload.containsKey(KEY_CIPHERTEXT) ||
- !payload.containsKey(KEY_IV) ||
- !payload.containsKey(KEY_HMAC)) {
- throw new MissingCryptoInputException();
- }
-
- // There's no difference between handling the crypto/keys object and
- // anything else; we just get this.keyBundle from a different source.
- byte[] cleartext = decryptPayload(payload, keyBundle);
- payload = ExtendedJSONObject.parseUTF8AsJSONObject(cleartext);
- return this;
- }
-
- public CryptoRecord encrypt() throws CryptoException, UnsupportedEncodingException {
- if (this.keyBundle == null) {
- throw new NoKeyBundleException();
- }
- String cleartext = payload.toJSONString();
- byte[] cleartextBytes = cleartext.getBytes("UTF-8");
- CryptoInfo info = CryptoInfo.encrypt(cleartextBytes, keyBundle);
- String message = new String(Base64.encodeBase64(info.getMessage()));
- String iv = new String(Base64.encodeBase64(info.getIV()));
- String hmac = Utils.byte2Hex(info.getHMAC());
- ExtendedJSONObject ciphertext = new ExtendedJSONObject();
- ciphertext.put(KEY_CIPHERTEXT, message);
- ciphertext.put(KEY_HMAC, hmac);
- ciphertext.put(KEY_IV, iv);
- this.payload = ciphertext;
- return this;
- }
-
- @Override
- public void initFromEnvelope(CryptoRecord payload) {
- throw new IllegalStateException("Can't do this with a CryptoRecord.");
- }
-
- @Override
- public CryptoRecord getEnvelope() {
- throw new IllegalStateException("Can't do this with a CryptoRecord.");
- }
-
- @Override
- protected void populatePayload(ExtendedJSONObject payload) {
- throw new IllegalStateException("Can't do this with a CryptoRecord.");
- }
-
- @Override
- protected void initFromPayload(ExtendedJSONObject payload) {
- throw new IllegalStateException("Can't do this with a CryptoRecord.");
- }
-
- // TODO: this only works with encrypted object, and has other limitations.
- public JSONObject toJSONObject() {
- ExtendedJSONObject o = new ExtendedJSONObject();
- o.put(KEY_PAYLOAD, payload.toJSONString());
- o.put(KEY_ID, this.guid);
- if (this.ttl > 0) {
- o.put(KEY_TTL, this.ttl);
- }
- return o.object;
- }
-
- @Override
- public String toJSONString() {
- return toJSONObject().toJSONString();
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/DelayedWorkTracker.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/DelayedWorkTracker.java
deleted file mode 100644
index ddcb5411c..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/DelayedWorkTracker.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import org.mozilla.gecko.background.common.log.Logger;
-
-/**
- * A little class to allow us to maintain a count of extant
- * things (in our case, callbacks that need to fire), and
- * some work that we want done when that count hits 0.
- *
- * @author rnewman
- *
- */
-public class DelayedWorkTracker {
- private static final String LOG_TAG = "DelayedWorkTracker";
- protected Runnable workItem = null;
- protected int outstandingCount = 0;
-
- public int incrementOutstanding() {
- Logger.trace(LOG_TAG, "Incrementing outstanding.");
- synchronized(this) {
- return ++outstandingCount;
- }
- }
- public int decrementOutstanding() {
- Logger.trace(LOG_TAG, "Decrementing outstanding.");
- Runnable job = null;
- int count;
- synchronized(this) {
- if ((count = --outstandingCount) == 0 &&
- workItem != null) {
- job = workItem;
- workItem = null;
- } else {
- return count;
- }
- }
- job.run();
- // In case it's changed.
- return getOutstandingOperations();
- }
- public int getOutstandingOperations() {
- synchronized(this) {
- return outstandingCount;
- }
- }
- public void delayWorkItem(Runnable item) {
- Logger.trace(LOG_TAG, "delayWorkItem.");
- boolean runnableNow = false;
- synchronized(this) {
- Logger.trace(LOG_TAG, "outstandingCount: " + outstandingCount);
- if (outstandingCount == 0) {
- runnableNow = true;
- } else {
- if (workItem != null) {
- throw new IllegalStateException("Work item already set!");
- }
- workItem = item;
- }
- }
- if (runnableNow) {
- Logger.trace(LOG_TAG, "Running item now.");
- item.run();
- }
- }
-} \ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/EngineSettings.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/EngineSettings.java
deleted file mode 100644
index 035816088..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/EngineSettings.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-public class EngineSettings {
- public final String syncID;
- public final int version;
-
- public EngineSettings(final String syncID, final int version) {
- this.syncID = syncID;
- this.version = version;
- }
-
- public EngineSettings(ExtendedJSONObject object) {
- try {
- this.syncID = object.getString("syncID");
- this.version = object.getIntegerSafely("version");
- } catch (Exception e ) {
- throw new IllegalArgumentException(e);
- }
- }
-
- public ExtendedJSONObject toJSONObject() {
- ExtendedJSONObject json = new ExtendedJSONObject();
- json.put("syncID", syncID);
- json.put("version", version);
- return json;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/ExtendedJSONObject.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/ExtendedJSONObject.java
deleted file mode 100644
index f5fac0009..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/ExtendedJSONObject.java
+++ /dev/null
@@ -1,426 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import org.json.simple.JSONArray;
-import org.json.simple.JSONObject;
-import org.json.simple.parser.JSONParser;
-import org.json.simple.parser.ParseException;
-import org.mozilla.apache.commons.codec.binary.Base64;
-import org.mozilla.gecko.sync.UnexpectedJSONException.BadRequiredFieldJSONException;
-
-import java.io.IOException;
-import java.io.Reader;
-import java.io.StringReader;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-
-/**
- * Extend JSONObject to do little things, like, y'know, accessing members.
- *
- * @author rnewman
- *
- */
-public class ExtendedJSONObject {
-
- public JSONObject object;
-
- /**
- * Return a <code>JSONParser</code> instance for immediate use.
- * <p>
- * <code>JSONParser</code> is not thread-safe, so we return a new instance
- * each call. This is extremely inefficient in execution time and especially
- * memory use -- each instance allocates a 16kb temporary buffer -- and we
- * hope to improve matters eventually.
- */
- protected static JSONParser getJSONParser() {
- return new JSONParser();
- }
-
- /**
- * Parse a JSON encoded string.
- *
- * @param in <code>Reader</code> over a JSON-encoded input to parse; not
- * necessarily a JSON object.
- * @return a regular Java <code>Object</code>.
- * @throws ParseException
- * @throws IOException
- */
- protected static Object parseRaw(Reader in) throws ParseException, IOException {
- try {
- return getJSONParser().parse(in);
- } catch (Error e) {
- // Don't be stupid, org.json.simple. Bug 1042929.
- throw new ParseException(ParseException.ERROR_UNEXPECTED_EXCEPTION, e);
- }
- }
-
- /**
- * Parse a JSON encoded string.
- * <p>
- * You should prefer the streaming interface {@link #parseRaw(Reader)}.
- *
- * @param input JSON-encoded input string to parse; not necessarily a JSON object.
- * @return a regular Java <code>Object</code>.
- * @throws ParseException
- */
- protected static Object parseRaw(String input) throws ParseException {
- try {
- return getJSONParser().parse(input);
- } catch (Error e) {
- // Don't be stupid, org.json.simple. Bug 1042929.
- throw new ParseException(ParseException.ERROR_UNEXPECTED_EXCEPTION, e);
- }
- }
-
- /**
- * Helper method to get a JSON array from a stream.
- *
- * @param in <code>Reader</code> over a JSON-encoded array to parse.
- * @throws ParseException
- * @throws IOException
- * @throws NonArrayJSONException if the object is valid JSON, but not an array.
- */
- public static JSONArray parseJSONArray(Reader in)
- throws IOException, ParseException, NonArrayJSONException {
- Object o = parseRaw(in);
-
- if (o == null) {
- return null;
- }
-
- if (o instanceof JSONArray) {
- return (JSONArray) o;
- }
-
- throw new NonArrayJSONException("value must be a JSON array");
- }
-
- /**
- * Helper method to get a JSON array from a string.
- * <p>
- * You should prefer the stream interface {@link #parseJSONArray(Reader)}.
- *
- * @param jsonString input.
- * @throws IOException
- * @throws NonArrayJSONException if the object is invalid JSON or not an array.
- */
- public static JSONArray parseJSONArray(String jsonString)
- throws IOException, NonArrayJSONException {
- Object o = null;
- try {
- o = parseRaw(jsonString);
- } catch (ParseException e) {
- throw new NonArrayJSONException(e);
- }
-
- if (o == null) {
- return null;
- }
-
- if (o instanceof JSONArray) {
- return (JSONArray) o;
- }
-
- throw new NonArrayJSONException("value must be a JSON array");
- }
-
- /**
- * Helper method to get a JSON object from a UTF-8 byte array.
- *
- * @param in UTF-8 bytes.
- * @throws NonObjectJSONException if the object is not valid JSON or not an object.
- * @throws IOException
- */
- public static ExtendedJSONObject parseUTF8AsJSONObject(byte[] in)
- throws NonObjectJSONException, IOException {
- return new ExtendedJSONObject(new String(in, "UTF-8"));
- }
-
- public ExtendedJSONObject() {
- this.object = new JSONObject();
- }
-
- public ExtendedJSONObject(JSONObject o) {
- this.object = o;
- }
-
- public ExtendedJSONObject(Reader in) throws IOException, NonObjectJSONException {
- if (in == null) {
- this.object = new JSONObject();
- return;
- }
-
- Object obj = null;
- try {
- obj = parseRaw(in);
- } catch (ParseException e) {
- throw new NonObjectJSONException(e);
- }
-
- if (obj instanceof JSONObject) {
- this.object = ((JSONObject) obj);
- } else {
- throw new NonObjectJSONException("value must be a JSON object");
- }
- }
-
- public ExtendedJSONObject(String jsonString) throws IOException, NonObjectJSONException {
- this(jsonString == null ? null : new StringReader(jsonString));
- }
-
- @Override
- public ExtendedJSONObject clone() {
- return new ExtendedJSONObject((JSONObject) this.object.clone());
- }
-
- // Passthrough methods.
- public Object get(String key) {
- return this.object.get(key);
- }
-
- public long getLong(String key, long def) {
- if (!object.containsKey(key)) {
- return def;
- }
-
- Long val = getLong(key);
- if (val == null) {
- return def;
- }
- return val.longValue();
- }
-
- public Long getLong(String key) {
- return (Long) this.get(key);
- }
-
- public String getString(String key) {
- return (String) this.get(key);
- }
-
- public Boolean getBoolean(String key) {
- return (Boolean) this.get(key);
- }
-
- /**
- * Return an Integer if the value for this key is an Integer, Long, or String
- * that can be parsed as a base 10 Integer.
- * Passes through null.
- *
- * @throws NumberFormatException
- */
- public Integer getIntegerSafely(String key) throws NumberFormatException {
- Object val = this.object.get(key);
- if (val == null) {
- return null;
- }
- if (val instanceof Integer) {
- return (Integer) val;
- }
- if (val instanceof Long) {
- return ((Long) val).intValue();
- }
- if (val instanceof String) {
- return Integer.parseInt((String) val, 10);
- }
- throw new NumberFormatException("Expecting Integer, got " + val.getClass());
- }
-
- /**
- * Return a server timestamp value as milliseconds since epoch.
- *
- * @param key
- * @return A Long, or null if the value is non-numeric or doesn't exist.
- */
- public Long getTimestamp(String key) {
- Object val = this.object.get(key);
-
- // This is absurd.
- if (val instanceof Double) {
- double millis = ((Double) val) * 1000;
- return Double.valueOf(millis).longValue();
- }
- if (val instanceof Float) {
- double millis = ((Float) val).doubleValue() * 1000;
- return Double.valueOf(millis).longValue();
- }
- if (val instanceof Number) {
- // Must be an integral number.
- return ((Number) val).longValue() * 1000;
- }
-
- return null;
- }
-
- public boolean containsKey(String key) {
- return this.object.containsKey(key);
- }
-
- public String toJSONString() {
- return this.object.toJSONString();
- }
-
- @Override
- public String toString() {
- return this.object.toString();
- }
-
- protected void putRaw(String key, Object value) {
- @SuppressWarnings("unchecked")
- Map<Object, Object> map = this.object;
- map.put(key, value);
- }
-
- public void put(String key, String value) {
- this.putRaw(key, value);
- }
-
- public void put(String key, boolean value) {
- this.putRaw(key, value);
- }
-
- public void put(String key, long value) {
- this.putRaw(key, value);
- }
-
- public void put(String key, int value) {
- this.putRaw(key, value);
- }
-
- public void put(String key, ExtendedJSONObject value) {
- this.putRaw(key, value);
- }
-
- public void put(String key, JSONArray value) {
- this.putRaw(key, value);
- }
-
- @SuppressWarnings("unchecked")
- public void putArray(String key, List<String> value) {
- // Frustratingly inefficient, but there you have it.
- final JSONArray jsonArray = new JSONArray();
- jsonArray.addAll(value);
- this.putRaw(key, jsonArray);
- }
-
- /**
- * Remove key-value pair from JSONObject.
- *
- * @param key
- * to be removed.
- * @return true if key exists and was removed, false otherwise.
- */
- public boolean remove(String key) {
- Object res = this.object.remove(key);
- return (res != null);
- }
-
- public ExtendedJSONObject getObject(String key) throws NonObjectJSONException {
- Object o = this.object.get(key);
- if (o == null) {
- return null;
- }
- if (o instanceof ExtendedJSONObject) {
- return (ExtendedJSONObject) o;
- }
- if (o instanceof JSONObject) {
- return new ExtendedJSONObject((JSONObject) o);
- }
- throw new NonObjectJSONException("value must be a JSON object for key: " + key);
- }
-
- @SuppressWarnings("unchecked")
- public Set<Entry<String, Object>> entrySet() {
- return this.object.entrySet();
- }
-
- @SuppressWarnings("unchecked")
- public Set<String> keySet() {
- return this.object.keySet();
- }
-
- public org.json.simple.JSONArray getArray(String key) throws NonArrayJSONException {
- Object o = this.object.get(key);
- if (o == null) {
- return null;
- }
- if (o instanceof JSONArray) {
- return (JSONArray) o;
- }
- throw new NonArrayJSONException("key must be a JSON array: " + key);
- }
-
- public int size() {
- return this.object.size();
- }
-
- @Override
- public int hashCode() {
- if (this.object == null) {
- return getClass().hashCode();
- }
- return this.object.hashCode() ^ getClass().hashCode();
- }
-
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof ExtendedJSONObject)) {
- return false;
- }
- if (o == this) {
- return true;
- }
- ExtendedJSONObject other = (ExtendedJSONObject) o;
- if (this.object == null) {
- return other.object == null;
- }
- return this.object.equals(other.object);
- }
-
- /**
- * Throw if keys are missing or values have wrong types.
- *
- * @param requiredFields list of required keys.
- * @param requiredFieldClass class that values must be coercable to; may be null, which means don't check.
- * @throws UnexpectedJSONException
- */
- public void throwIfFieldsMissingOrMisTyped(String[] requiredFields, Class<?> requiredFieldClass) throws BadRequiredFieldJSONException {
- // Defensive as possible: verify object has expected key(s) with string value.
- for (String k : requiredFields) {
- Object value = get(k);
- if (value == null) {
- throw new BadRequiredFieldJSONException("Expected key not present in result: " + k);
- }
- if (requiredFieldClass != null && !(requiredFieldClass.isInstance(value))) {
- throw new BadRequiredFieldJSONException("Value for key not an instance of " + requiredFieldClass + ": " + k);
- }
- }
- }
-
- /**
- * Return a base64-encoded string value as a byte array.
- */
- public byte[] getByteArrayBase64(String key) {
- String s = (String) this.object.get(key);
- if (s == null) {
- return null;
- }
- return Base64.decodeBase64(s);
- }
-
- /**
- * Return a hex-encoded string value as a byte array.
- */
- public byte[] getByteArrayHex(String key) {
- String s = (String) this.object.get(key);
- if (s == null) {
- return null;
- }
- return Utils.hex2Byte(s);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/GlobalSession.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/GlobalSession.java
deleted file mode 100644
index e28bbe4cc..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/GlobalSession.java
+++ /dev/null
@@ -1,1167 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import android.content.Context;
-
-import org.json.simple.JSONArray;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.crypto.CryptoException;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
-import org.mozilla.gecko.sync.delegates.FreshStartDelegate;
-import org.mozilla.gecko.sync.delegates.GlobalSessionCallback;
-import org.mozilla.gecko.sync.delegates.JSONRecordFetchDelegate;
-import org.mozilla.gecko.sync.delegates.KeyUploadDelegate;
-import org.mozilla.gecko.sync.delegates.MetaGlobalDelegate;
-import org.mozilla.gecko.sync.delegates.WipeServerDelegate;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.net.BaseResource;
-import org.mozilla.gecko.sync.net.HttpResponseObserver;
-import org.mozilla.gecko.sync.net.SyncResponse;
-import org.mozilla.gecko.sync.net.SyncStorageRecordRequest;
-import org.mozilla.gecko.sync.net.SyncStorageRequest;
-import org.mozilla.gecko.sync.net.SyncStorageRequestDelegate;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-import org.mozilla.gecko.sync.stage.AndroidBrowserBookmarksServerSyncStage;
-import org.mozilla.gecko.sync.stage.AndroidBrowserHistoryServerSyncStage;
-import org.mozilla.gecko.sync.stage.CheckPreconditionsStage;
-import org.mozilla.gecko.sync.stage.CompletedStage;
-import org.mozilla.gecko.sync.stage.EnsureCrypto5KeysStage;
-import org.mozilla.gecko.sync.stage.FennecTabsServerSyncStage;
-import org.mozilla.gecko.sync.stage.FetchInfoCollectionsStage;
-import org.mozilla.gecko.sync.stage.FetchInfoConfigurationStage;
-import org.mozilla.gecko.sync.stage.FetchMetaGlobalStage;
-import org.mozilla.gecko.sync.stage.FormHistoryServerSyncStage;
-import org.mozilla.gecko.sync.stage.GlobalSyncStage;
-import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
-import org.mozilla.gecko.sync.stage.NoSuchStageException;
-import org.mozilla.gecko.sync.stage.PasswordsServerSyncStage;
-import org.mozilla.gecko.sync.stage.SyncClientsEngineStage;
-import org.mozilla.gecko.sync.stage.UploadMetaGlobalStage;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumMap;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicLong;
-
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
-
-public class GlobalSession implements HttpResponseObserver {
- private static final String LOG_TAG = "GlobalSession";
-
- public static final long STORAGE_VERSION = 5;
-
- public SyncConfiguration config = null;
-
- protected Map<Stage, GlobalSyncStage> stages;
- public Stage currentState = Stage.idle;
-
- public final GlobalSessionCallback callback;
- protected final Context context;
- protected final ClientsDataDelegate clientsDelegate;
-
- /**
- * Map from engine name to new settings for an updated meta/global record.
- * Engines to remove will have <code>null</code> EngineSettings.
- */
- public final Map<String, EngineSettings> enginesToUpdate = new HashMap<String, EngineSettings>();
-
- /*
- * Key accessors.
- */
- public KeyBundle keyBundleForCollection(String collection) throws NoCollectionKeysSetException {
- return config.getCollectionKeys().keyBundleForCollection(collection);
- }
-
- /*
- * Config passthrough for convenience.
- */
- public AuthHeaderProvider getAuthHeaderProvider() {
- return config.getAuthHeaderProvider();
- }
-
- public URI wboURI(String collection, String id) throws URISyntaxException {
- return config.wboURI(collection, id);
- }
-
- public GlobalSession(SyncConfiguration config,
- GlobalSessionCallback callback,
- Context context,
- ClientsDataDelegate clientsDelegate)
- throws SyncConfigurationException, IllegalArgumentException, IOException, NonObjectJSONException {
-
- if (callback == null) {
- throw new IllegalArgumentException("Must provide a callback to GlobalSession constructor.");
- }
-
- this.callback = callback;
- this.context = context;
- this.clientsDelegate = clientsDelegate;
-
- this.config = config;
- registerCommands();
- prepareStages();
-
- if (config.stagesToSync == null) {
- Logger.info(LOG_TAG, "No stages to sync specified; defaulting to all valid engine names.");
- config.stagesToSync = Collections.unmodifiableCollection(SyncConfiguration.validEngineNames());
- }
-
- // TODO: data-driven plan for the sync, referring to prepareStages.
- }
-
- /**
- * Register commands this global session knows how to process.
- * <p>
- * Re-registering a command overwrites any existing registration.
- */
- protected static void registerCommands() {
- final CommandProcessor processor = CommandProcessor.getProcessor();
-
- processor.registerCommand("resetEngine", new CommandRunner(1) {
- @Override
- public void executeCommand(final GlobalSession session, List<String> args) {
- HashSet<String> names = new HashSet<String>();
- names.add(args.get(0));
- session.resetStagesByName(names);
- }
- });
-
- processor.registerCommand("resetAll", new CommandRunner(0) {
- @Override
- public void executeCommand(final GlobalSession session, List<String> args) {
- session.resetAllStages();
- }
- });
-
- processor.registerCommand("wipeEngine", new CommandRunner(1) {
- @Override
- public void executeCommand(final GlobalSession session, List<String> args) {
- HashSet<String> names = new HashSet<String>();
- names.add(args.get(0));
- session.wipeStagesByName(names);
- }
- });
-
- processor.registerCommand("wipeAll", new CommandRunner(0) {
- @Override
- public void executeCommand(final GlobalSession session, List<String> args) {
- session.wipeAllStages();
- }
- });
-
- processor.registerCommand("displayURI", new CommandRunner(3) {
- @Override
- public void executeCommand(final GlobalSession session, List<String> args) {
- CommandProcessor.displayURI(args, session.getContext());
- }
- });
- }
-
- protected void prepareStages() {
- Map<Stage, GlobalSyncStage> stages = new EnumMap<Stage, GlobalSyncStage>(Stage.class);
-
- stages.put(Stage.checkPreconditions, new CheckPreconditionsStage());
- stages.put(Stage.fetchInfoCollections, new FetchInfoCollectionsStage());
- stages.put(Stage.fetchMetaGlobal, new FetchMetaGlobalStage());
- stages.put(Stage.fetchInfoConfiguration, new FetchInfoConfigurationStage(
- config.infoConfigurationURL(), getAuthHeaderProvider()));
- stages.put(Stage.ensureKeysStage, new EnsureCrypto5KeysStage());
-
- stages.put(Stage.syncClientsEngine, new SyncClientsEngineStage());
-
- stages.put(Stage.syncTabs, new FennecTabsServerSyncStage());
- stages.put(Stage.syncPasswords, new PasswordsServerSyncStage());
- stages.put(Stage.syncBookmarks, new AndroidBrowserBookmarksServerSyncStage());
- stages.put(Stage.syncHistory, new AndroidBrowserHistoryServerSyncStage());
- stages.put(Stage.syncFormHistory, new FormHistoryServerSyncStage());
-
- stages.put(Stage.uploadMetaGlobal, new UploadMetaGlobalStage());
- stages.put(Stage.completed, new CompletedStage());
-
- this.stages = Collections.unmodifiableMap(stages);
- }
-
- public GlobalSyncStage getSyncStageByName(String name) throws NoSuchStageException {
- return getSyncStageByName(Stage.byName(name));
- }
-
- public GlobalSyncStage getSyncStageByName(Stage next) throws NoSuchStageException {
- GlobalSyncStage stage = stages.get(next);
- if (stage == null) {
- throw new NoSuchStageException(next);
- }
- return stage;
- }
-
- public Collection<GlobalSyncStage> getSyncStagesByEnum(Collection<Stage> enums) {
- ArrayList<GlobalSyncStage> out = new ArrayList<GlobalSyncStage>();
- for (Stage name : enums) {
- try {
- GlobalSyncStage stage = this.getSyncStageByName(name);
- out.add(stage);
- } catch (NoSuchStageException e) {
- Logger.warn(LOG_TAG, "Unable to find stage with name " + name);
- }
- }
- return out;
- }
-
- public Collection<GlobalSyncStage> getSyncStagesByName(Collection<String> names) {
- ArrayList<GlobalSyncStage> out = new ArrayList<GlobalSyncStage>();
- for (String name : names) {
- try {
- GlobalSyncStage stage = this.getSyncStageByName(name);
- out.add(stage);
- } catch (NoSuchStageException e) {
- Logger.warn(LOG_TAG, "Unable to find stage with name " + name);
- }
- }
- return out;
- }
-
- /**
- * Advance and loop around the stages of a sync.
- * @param current
- * @return
- * The next stage to execute.
- */
- public static Stage nextStage(Stage current) {
- int index = current.ordinal() + 1;
- int max = Stage.completed.ordinal() + 1;
- return Stage.values()[index % max];
- }
-
- /**
- * Move to the next stage in the syncing process.
- */
- public void advance() {
- // If we have a backoff, request a backoff and don't advance to next stage.
- long existingBackoff = largestBackoffObserved.get();
- if (existingBackoff > 0) {
- this.abort(null, "Aborting sync because of backoff of " + existingBackoff + " milliseconds.");
- return;
- }
-
- this.callback.handleStageCompleted(this.currentState, this);
- Stage next = nextStage(this.currentState);
- GlobalSyncStage nextStage;
- try {
- nextStage = this.getSyncStageByName(next);
- } catch (NoSuchStageException e) {
- this.abort(e, "No such stage " + next);
- return;
- }
- this.currentState = next;
- Logger.info(LOG_TAG, "Running next stage " + next + " (" + nextStage + ")...");
- try {
- nextStage.execute(this);
- } catch (Exception ex) {
- Logger.warn(LOG_TAG, "Caught exception " + ex + " running stage " + next);
- this.abort(ex, "Uncaught exception in stage.");
- return;
- }
- }
-
- public Context getContext() {
- return this.context;
- }
-
- /**
- * Begin a sync.
- * <p>
- * The caller is responsible for:
- * <ul>
- * <li>Verifying that any backoffs/minimum next sync requests are respected.</li>
- * <li>Ensuring that the device is online.</li>
- * <li>Ensuring that dependencies are ready.</li>
- * </ul>
- *
- * @throws AlreadySyncingException
- */
- public void start() throws AlreadySyncingException {
- if (this.currentState != GlobalSyncStage.Stage.idle) {
- throw new AlreadySyncingException(this.currentState);
- }
- installAsHttpResponseObserver(); // Uninstalled by completeSync or abort.
- this.advance();
- }
-
- /**
- * Stop this sync and start again.
- * @throws AlreadySyncingException
- */
- protected void restart() throws AlreadySyncingException {
- this.currentState = GlobalSyncStage.Stage.idle;
- if (callback.shouldBackOffStorage()) {
- this.callback.handleAborted(this, "Told to back off.");
- return;
- }
- this.start();
- }
-
- /**
- * We're finished (aborted or succeeded): release resources.
- */
- protected void cleanUp() {
- uninstallAsHttpResponseObserver();
- this.stages = null;
- }
-
- public void completeSync() {
- cleanUp();
- this.currentState = GlobalSyncStage.Stage.idle;
- this.callback.handleSuccess(this);
- }
-
- /**
- * Record that an updated meta/global record should be uploaded with the given
- * settings for the given engine.
- *
- * @param engineName engine to update.
- * @param engineSettings new syncID and version.
- */
- public void recordForMetaGlobalUpdate(String engineName, EngineSettings engineSettings) {
- enginesToUpdate.put(engineName, engineSettings);
- }
-
- /**
- * Record that an updated meta/global record should be uploaded without the
- * given engine name.
- *
- * @param engineName
- * engine to remove.
- */
- public void removeEngineFromMetaGlobal(String engineName) {
- enginesToUpdate.put(engineName, null);
- }
-
- public boolean hasUpdatedMetaGlobal() {
- if (enginesToUpdate.isEmpty()) {
- Logger.info(LOG_TAG, "Not uploading updated meta/global record since there are no engines requesting upload.");
- return false;
- }
-
- if (Logger.shouldLogVerbose(LOG_TAG)) {
- Logger.trace(LOG_TAG, "Uploading updated meta/global record since there are engine changes to meta/global.");
- Logger.trace(LOG_TAG, "Engines requesting update [" + Utils.toCommaSeparatedString(enginesToUpdate.keySet()) + "]");
- }
-
- return true;
- }
-
- public void updateMetaGlobalInPlace() {
- config.metaGlobal.declined = this.declinedEngineNames();
- ExtendedJSONObject engines = config.metaGlobal.getEngines();
- for (Entry<String, EngineSettings> pair : enginesToUpdate.entrySet()) {
- if (pair.getValue() == null) {
- engines.remove(pair.getKey());
- } else {
- engines.put(pair.getKey(), pair.getValue().toJSONObject());
- }
- }
-
- enginesToUpdate.clear();
- }
-
- /**
- * Synchronously upload an updated meta/global.
- * <p>
- * All problems are logged and ignored.
- */
- public void uploadUpdatedMetaGlobal() {
- updateMetaGlobalInPlace();
-
- Logger.debug(LOG_TAG, "Uploading updated meta/global record.");
- final Object monitor = new Object();
-
- Runnable doUpload = new Runnable() {
- @Override
- public void run() {
- config.metaGlobal.upload(new MetaGlobalDelegate() {
- @Override
- public void handleSuccess(MetaGlobal global, SyncStorageResponse response) {
- Logger.info(LOG_TAG, "Successfully uploaded updated meta/global record.");
- // Engine changes are stored as diffs, so update enabled engines in config to match uploaded meta/global.
- config.enabledEngineNames = config.metaGlobal.getEnabledEngineNames();
- // Clear userSelectedEngines because they are updated in config and meta/global.
- config.userSelectedEngines = null;
-
- synchronized (monitor) {
- monitor.notify();
- }
- }
-
- @Override
- public void handleMissing(MetaGlobal global, SyncStorageResponse response) {
- Logger.warn(LOG_TAG, "Got 404 missing uploading updated meta/global record; shouldn't happen. Ignoring.");
- synchronized (monitor) {
- monitor.notify();
- }
- }
-
- @Override
- public void handleFailure(SyncStorageResponse response) {
- Logger.warn(LOG_TAG, "Failed to upload updated meta/global record; ignoring.");
- synchronized (monitor) {
- monitor.notify();
- }
- }
-
- @Override
- public void handleError(Exception e) {
- Logger.warn(LOG_TAG, "Got exception trying to upload updated meta/global record; ignoring.", e);
- synchronized (monitor) {
- monitor.notify();
- }
- }
- });
- }
- };
-
- final Thread upload = new Thread(doUpload);
- synchronized (monitor) {
- try {
- upload.start();
- monitor.wait();
- Logger.debug(LOG_TAG, "Uploaded updated meta/global record.");
- } catch (InterruptedException e) {
- Logger.error(LOG_TAG, "Uploading updated meta/global interrupted; continuing.");
- }
- }
- }
-
-
- public void abort(Exception e, String reason) {
- Logger.warn(LOG_TAG, "Aborting sync: " + reason, e);
- cleanUp();
- long existingBackoff = largestBackoffObserved.get();
- if (existingBackoff > 0) {
- callback.requestBackoff(existingBackoff);
- }
- if (!(e instanceof HTTPFailureException)) {
- // e is null, or we aborted for a non-HTTP reason; okay to upload new meta/global record.
- if (this.hasUpdatedMetaGlobal()) {
- this.uploadUpdatedMetaGlobal(); // Only logs errors; does not call abort.
- }
- }
- this.callback.handleError(this, e);
- }
-
- public void handleHTTPError(SyncStorageResponse response, String reason) {
- // TODO: handling of 50x (backoff), 401 (node reassignment or auth error).
- // Fall back to aborting.
- Logger.warn(LOG_TAG, "Aborting sync due to HTTP " + response.getStatusCode());
- this.interpretHTTPFailure(response.httpResponse());
- this.abort(new HTTPFailureException(response), reason);
- }
-
- /**
- * Perform appropriate backoff etc. extraction.
- */
- public void interpretHTTPFailure(HttpResponse response) {
- // TODO: handle permanent rejection.
- long responseBackoff = (new SyncResponse(response)).totalBackoffInMilliseconds();
- if (responseBackoff > 0) {
- callback.requestBackoff(responseBackoff);
- }
-
- if (response.getStatusLine() != null) {
- final int statusCode = response.getStatusLine().getStatusCode();
- switch(statusCode) {
-
- case 400:
- SyncStorageResponse storageResponse = new SyncStorageResponse(response);
- this.interpretHTTPBadRequestBody(storageResponse);
- break;
-
- case 401:
- /*
- * Alert our callback we have a 401 on a cluster URL. This GlobalSession
- * will fail, but the next one will fetch a new cluster URL and will
- * distinguish between "node reassignment" and "user password changed".
- */
- callback.informUnauthorizedResponse(this, config.getClusterURL());
- break;
- }
- }
- }
-
- protected void interpretHTTPBadRequestBody(final SyncStorageResponse storageResponse) {
- try {
- final String body = storageResponse.body();
- if (body == null) {
- return;
- }
- if (SyncStorageResponse.RESPONSE_CLIENT_UPGRADE_REQUIRED.equals(body)) {
- callback.informUpgradeRequiredResponse(this);
- return;
- }
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Exception parsing HTTP 400 body.", e);
- }
- }
-
- public void fetchInfoCollections(JSONRecordFetchDelegate callback) throws URISyntaxException {
- final JSONRecordFetcher fetcher = new JSONRecordFetcher(config.infoCollectionsURL(), getAuthHeaderProvider());
- fetcher.fetch(callback);
- }
-
- /**
- * Upload new crypto/keys.
- *
- * @param keys
- * new keys.
- * @param keyUploadDelegate
- * a delegate.
- */
- public void uploadKeys(final CollectionKeys keys,
- final KeyUploadDelegate keyUploadDelegate) {
- SyncStorageRecordRequest request;
- try {
- request = new SyncStorageRecordRequest(this.config.keysURI());
- } catch (URISyntaxException e) {
- keyUploadDelegate.onKeyUploadFailed(e);
- return;
- }
-
- request.delegate = new SyncStorageRequestDelegate() {
-
- @Override
- public String ifUnmodifiedSince() {
- return null;
- }
-
- @Override
- public void handleRequestSuccess(SyncStorageResponse response) {
- Logger.debug(LOG_TAG, "Keys uploaded.");
- BaseResource.consumeEntity(response); // We don't need the response at all.
- keyUploadDelegate.onKeysUploaded();
- }
-
- @Override
- public void handleRequestFailure(SyncStorageResponse response) {
- Logger.debug(LOG_TAG, "Failed to upload keys.");
- GlobalSession.this.interpretHTTPFailure(response.httpResponse());
- BaseResource.consumeEntity(response); // The exception thrown should not need the body of the response.
- keyUploadDelegate.onKeyUploadFailed(new HTTPFailureException(response));
- }
-
- @Override
- public void handleRequestError(Exception ex) {
- Logger.warn(LOG_TAG, "Got exception trying to upload keys", ex);
- keyUploadDelegate.onKeyUploadFailed(ex);
- }
-
- @Override
- public AuthHeaderProvider getAuthHeaderProvider() {
- return GlobalSession.this.getAuthHeaderProvider();
- }
- };
-
- // Convert keys to an encrypted crypto record.
- CryptoRecord keysRecord;
- try {
- keysRecord = keys.asCryptoRecord();
- keysRecord.setKeyBundle(config.syncKeyBundle);
- keysRecord.encrypt();
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Got exception trying creating crypto record from keys", e);
- keyUploadDelegate.onKeyUploadFailed(e);
- return;
- }
-
- request.put(keysRecord);
- }
-
- /*
- * meta/global callbacks.
- */
- public void processMetaGlobal(MetaGlobal global) {
- config.metaGlobal = global;
-
- Long storageVersion = global.getStorageVersion();
- if (storageVersion == null) {
- Logger.warn(LOG_TAG, "Malformed remote meta/global: could not retrieve remote storage version.");
- freshStart();
- return;
- }
- if (storageVersion < STORAGE_VERSION) {
- Logger.warn(LOG_TAG, "Outdated server: reported " +
- "remote storage version " + storageVersion + " < " +
- "local storage version " + STORAGE_VERSION);
- freshStart();
- return;
- }
- if (storageVersion > STORAGE_VERSION) {
- Logger.warn(LOG_TAG, "Outdated client: reported " +
- "remote storage version " + storageVersion + " > " +
- "local storage version " + STORAGE_VERSION);
- requiresUpgrade();
- return;
- }
- String remoteSyncID = global.getSyncID();
- if (remoteSyncID == null) {
- Logger.warn(LOG_TAG, "Malformed remote meta/global: could not retrieve remote syncID.");
- freshStart();
- return;
- }
- String localSyncID = config.syncID;
- if (!remoteSyncID.equals(localSyncID)) {
- Logger.warn(LOG_TAG, "Remote syncID different from local syncID: resetting client and assuming remote syncID.");
- resetAllStages();
- config.purgeCryptoKeys();
- config.syncID = remoteSyncID;
- }
- // Compare lastModified timestamps for remote/local engine selection times.
- Logger.debug(LOG_TAG, "Comparing local engine selection timestamp [" + config.userSelectedEnginesTimestamp + "] to server meta/global timestamp [" + config.persistedMetaGlobal().lastModified() + "].");
- if (config.userSelectedEnginesTimestamp < config.persistedMetaGlobal().lastModified()) {
- // Remote has later meta/global timestamp. Don't upload engine changes.
- config.userSelectedEngines = null;
- }
- // Persist enabled engine names.
- config.enabledEngineNames = global.getEnabledEngineNames();
- if (config.enabledEngineNames == null) {
- Logger.warn(LOG_TAG, "meta/global reported no enabled engine names!");
- } else {
- if (Logger.shouldLogVerbose(LOG_TAG)) {
- Logger.trace(LOG_TAG, "Persisting enabled engine names '" +
- Utils.toCommaSeparatedString(config.enabledEngineNames) + "' from meta/global.");
- }
- }
-
- // Persist declined.
- // Our declined engines at any point are:
- // Whatever they were remotely, plus whatever they were locally, less any
- // engines that were just enabled locally or remotely.
- // If remote just 'won', our recently enabled list just got cleared.
- final HashSet<String> allDeclined = new HashSet<String>();
-
- final Set<String> newRemoteDeclined = global.getDeclinedEngineNames();
- final Set<String> oldLocalDeclined = config.declinedEngineNames;
-
- allDeclined.addAll(newRemoteDeclined);
- allDeclined.addAll(oldLocalDeclined);
-
- if (config.userSelectedEngines != null) {
- for (Entry<String, Boolean> selection : config.userSelectedEngines.entrySet()) {
- if (selection.getValue()) {
- allDeclined.remove(selection.getKey());
- }
- }
- }
-
- config.declinedEngineNames = allDeclined;
- if (config.declinedEngineNames.isEmpty()) {
- Logger.debug(LOG_TAG, "meta/global reported no declined engine names, and we have none declined locally.");
- } else {
- if (Logger.shouldLogVerbose(LOG_TAG)) {
- Logger.trace(LOG_TAG, "Persisting declined engine names '" +
- Utils.toCommaSeparatedString(config.declinedEngineNames) + "' from meta/global.");
- }
- }
-
- config.persistToPrefs();
- advance();
- }
-
- public void processMissingMetaGlobal(MetaGlobal global) {
- freshStart();
- }
-
- /**
- * Do a fresh start then quietly finish the sync, starting another.
- */
- public void freshStart() {
- final GlobalSession globalSession = this;
- freshStart(this, new FreshStartDelegate() {
-
- @Override
- public void onFreshStartFailed(Exception e) {
- globalSession.abort(e, "Fresh start failed.");
- }
-
- @Override
- public void onFreshStart() {
- try {
- Logger.warn(LOG_TAG, "Fresh start succeeded; restarting global session.");
- globalSession.config.persistToPrefs();
- globalSession.restart();
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Got exception when restarting sync after freshStart.", e);
- globalSession.abort(e, "Got exception after freshStart.");
- }
- }
- });
- }
-
- /**
- * Clean the server, aborting the current sync.
- * <p>
- * <ol>
- * <li>Wipe the server storage.</li>
- * <li>Reset all stages and purge cached state: (meta/global and crypto/keys records).</li>
- * <li>Upload fresh meta/global record.</li>
- * <li>Upload fresh crypto/keys record.</li>
- * <li>Restart the sync entirely in order to re-download meta/global and crypto/keys record.</li>
- * </ol>
- * @param session the current session.
- * @param freshStartDelegate delegate to notify on fresh start or failure.
- */
- protected static void freshStart(final GlobalSession session, final FreshStartDelegate freshStartDelegate) {
- Logger.debug(LOG_TAG, "Fresh starting.");
-
- final MetaGlobal mg = session.generateNewMetaGlobal();
-
- session.wipeServer(session.getAuthHeaderProvider(), new WipeServerDelegate() {
-
- @Override
- public void onWiped(long timestamp) {
- Logger.debug(LOG_TAG, "Successfully wiped server. Resetting all stages and purging cached meta/global and crypto/keys records.");
-
- session.resetAllStages();
- session.config.purgeMetaGlobal();
- session.config.purgeCryptoKeys();
- session.config.persistToPrefs();
-
- Logger.info(LOG_TAG, "Uploading new meta/global with sync ID " + mg.syncID + ".");
-
- // It would be good to set the X-If-Unmodified-Since header to `timestamp`
- // for this PUT to ensure at least some level of transactionality.
- // Unfortunately, the servers don't support it after a wipe right now
- // (bug 693893), so we're going to defer this until bug 692700.
- mg.upload(new MetaGlobalDelegate() {
- @Override
- public void handleSuccess(MetaGlobal uploadedGlobal, SyncStorageResponse uploadResponse) {
- Logger.info(LOG_TAG, "Uploaded new meta/global with sync ID " + uploadedGlobal.syncID + ".");
-
- // Generate new keys.
- CollectionKeys keys = null;
- try {
- keys = session.generateNewCryptoKeys();
- } catch (CryptoException e) {
- Logger.warn(LOG_TAG, "Got exception generating new keys; failing fresh start.", e);
- freshStartDelegate.onFreshStartFailed(e);
- }
- if (keys == null) {
- Logger.warn(LOG_TAG, "Got null keys from generateNewKeys; failing fresh start.");
- freshStartDelegate.onFreshStartFailed(null);
- }
-
- // Upload new keys.
- Logger.info(LOG_TAG, "Uploading new crypto/keys.");
- session.uploadKeys(keys, new KeyUploadDelegate() {
- @Override
- public void onKeysUploaded() {
- Logger.info(LOG_TAG, "Uploaded new crypto/keys.");
- freshStartDelegate.onFreshStart();
- }
-
- @Override
- public void onKeyUploadFailed(Exception e) {
- Logger.warn(LOG_TAG, "Got exception uploading new keys.", e);
- freshStartDelegate.onFreshStartFailed(e);
- }
- });
- }
-
- @Override
- public void handleMissing(MetaGlobal global, SyncStorageResponse response) {
- // Shouldn't happen on upload.
- Logger.warn(LOG_TAG, "Got 'missing' response uploading new meta/global.");
- freshStartDelegate.onFreshStartFailed(new Exception("meta/global missing while uploading."));
- }
-
- @Override
- public void handleFailure(SyncStorageResponse response) {
- Logger.warn(LOG_TAG, "Got failure " + response.getStatusCode() + " uploading new meta/global.");
- session.interpretHTTPFailure(response.httpResponse());
- freshStartDelegate.onFreshStartFailed(new HTTPFailureException(response));
- }
-
- @Override
- public void handleError(Exception e) {
- Logger.warn(LOG_TAG, "Got error uploading new meta/global.", e);
- freshStartDelegate.onFreshStartFailed(e);
- }
- });
- }
-
- @Override
- public void onWipeFailed(Exception e) {
- Logger.warn(LOG_TAG, "Wipe failed.");
- freshStartDelegate.onFreshStartFailed(e);
- }
- });
- }
-
- // Note that we do not yet implement wipeRemote: it's only necessary for
- // first sync options.
- // -- reset local stages, wipe server for each stage *except* clients
- // (stages only, not whole server!), send wipeEngine commands to each client.
- //
- // Similarly for startOver (because we don't receive that notification).
- // -- remove client data from server, reset local stages, clear keys, reset
- // backoff, clear all prefs, discard credentials.
- //
- // Change passphrase: wipe entire server, reset client to force upload, sync.
- //
- // When an engine is disabled: wipe its collections on the server, reupload
- // meta/global.
- //
- // On syncing each stage: if server has engine version 0 or old, wipe server,
- // reset client to prompt reupload.
- // If sync ID mismatch: take that syncID and reset client.
-
- protected void wipeServer(final AuthHeaderProvider authHeaderProvider, final WipeServerDelegate wipeDelegate) {
- SyncStorageRequest request;
- final GlobalSession self = this;
-
- try {
- request = new SyncStorageRequest(config.storageURL());
- } catch (URISyntaxException ex) {
- Logger.warn(LOG_TAG, "Invalid URI in wipeServer.");
- wipeDelegate.onWipeFailed(ex);
- return;
- }
-
- request.delegate = new SyncStorageRequestDelegate() {
-
- @Override
- public String ifUnmodifiedSince() {
- return null;
- }
-
- @Override
- public void handleRequestSuccess(SyncStorageResponse response) {
- BaseResource.consumeEntity(response);
- wipeDelegate.onWiped(response.normalizedWeaveTimestamp());
- }
-
- @Override
- public void handleRequestFailure(SyncStorageResponse response) {
- Logger.warn(LOG_TAG, "Got request failure " + response.getStatusCode() + " in wipeServer.");
- // Process HTTP failures here to pick up backoffs, etc.
- self.interpretHTTPFailure(response.httpResponse());
- BaseResource.consumeEntity(response); // The exception thrown should not need the body of the response.
- wipeDelegate.onWipeFailed(new HTTPFailureException(response));
- }
-
- @Override
- public void handleRequestError(Exception ex) {
- Logger.warn(LOG_TAG, "Got exception in wipeServer.", ex);
- wipeDelegate.onWipeFailed(ex);
- }
-
- @Override
- public AuthHeaderProvider getAuthHeaderProvider() {
- return GlobalSession.this.getAuthHeaderProvider();
- }
- };
- request.delete();
- }
-
- public void wipeAllStages() {
- Logger.info(LOG_TAG, "Wiping all stages.");
- // Includes "clients".
- this.wipeStagesByEnum(Stage.getNamedStages());
- }
-
- public void wipeStages(Collection<GlobalSyncStage> stages) {
- for (GlobalSyncStage stage : stages) {
- try {
- Logger.info(LOG_TAG, "Wiping " + stage);
- stage.wipeLocal(this);
- } catch (Exception e) {
- Logger.error(LOG_TAG, "Ignoring wipe failure for stage " + stage, e);
- }
- }
- }
-
- public void wipeStagesByEnum(Collection<Stage> stages) {
- wipeStages(this.getSyncStagesByEnum(stages));
- }
-
- public void wipeStagesByName(Collection<String> names) {
- wipeStages(this.getSyncStagesByName(names));
- }
-
- public void resetAllStages() {
- Logger.info(LOG_TAG, "Resetting all stages.");
- // Includes "clients".
- this.resetStagesByEnum(Stage.getNamedStages());
- }
-
- public void resetStages(Collection<GlobalSyncStage> stages) {
- for (GlobalSyncStage stage : stages) {
- try {
- Logger.info(LOG_TAG, "Resetting " + stage);
- stage.resetLocal(this);
- } catch (Exception e) {
- Logger.error(LOG_TAG, "Ignoring reset failure for stage " + stage, e);
- }
- }
- }
-
- public void resetStagesByEnum(Collection<Stage> stages) {
- resetStages(this.getSyncStagesByEnum(stages));
- }
-
- public void resetStagesByName(Collection<String> names) {
- resetStages(this.getSyncStagesByName(names));
- }
-
- /**
- * Engines to explicitly mark as declined in a fresh meta/global record.
- * <p>
- * Returns an empty array if the user hasn't elected to customize data types,
- * or an array of engines that the user un-checked during customization.
- * <p>
- * Engines that Android Sync doesn't recognize are <b>not</b> included in
- * the returned array.
- *
- * @return a new JSONArray of engine names.
- */
- @SuppressWarnings("unchecked")
- protected JSONArray declinedEngineNames() {
- final JSONArray declined = new JSONArray();
- for (String engine : config.declinedEngineNames) {
- declined.add(engine);
- };
-
- return declined;
- }
-
- /**
- * Engines to include in a fresh meta/global record.
- * <p>
- * Returns either the persisted engine names (perhaps we have been node
- * re-assigned and are initializing a clean server: we want to upload the
- * persisted engine names so that we don't accidentally disable engines that
- * Android Sync doesn't recognize), or the set of engines names that Android
- * Sync implements.
- *
- * @return set of engine names.
- */
- protected Set<String> enabledEngineNames() {
- if (config.enabledEngineNames != null) {
- return config.enabledEngineNames;
- }
-
- // These are the default set of engine names.
- Set<String> validEngineNames = SyncConfiguration.validEngineNames();
-
- // If the user hasn't set any selected engines, that's okay -- default to
- // everything.
- if (config.userSelectedEngines == null) {
- return validEngineNames;
- }
-
- // userSelectedEngines has keys that are engine names, and boolean values
- // corresponding to whether the user asked for the engine to sync or not. If
- // an engine is not present, that means the user didn't change its sync
- // setting. Since we default to everything on, that means the user didn't
- // turn it off; therefore, it's included in the set of engines to sync.
- Set<String> validAndSelectedEngineNames = new HashSet<String>();
- for (String engineName : validEngineNames) {
- if (config.userSelectedEngines.containsKey(engineName) &&
- !config.userSelectedEngines.get(engineName)) {
- continue;
- }
- validAndSelectedEngineNames.add(engineName);
- }
- return validAndSelectedEngineNames;
- }
-
- /**
- * Generate fresh crypto/keys collection.
- * @return crypto/keys collection.
- * @throws CryptoException
- */
- @SuppressWarnings("static-method")
- public CollectionKeys generateNewCryptoKeys() throws CryptoException {
- return CollectionKeys.generateCollectionKeys();
- }
-
- /**
- * Generate a fresh meta/global record.
- * @return meta/global record.
- */
- public MetaGlobal generateNewMetaGlobal() {
- final String newSyncID = Utils.generateGuid();
- final String metaURL = this.config.metaURL();
-
- ExtendedJSONObject engines = new ExtendedJSONObject();
- for (String engineName : enabledEngineNames()) {
- EngineSettings engineSettings = null;
- try {
- GlobalSyncStage globalStage = this.getSyncStageByName(engineName);
- Integer version = globalStage.getStorageVersion();
- if (version == null) {
- continue; // Don't want this stage to be included in meta/global.
- }
- engineSettings = new EngineSettings(Utils.generateGuid(), version);
- } catch (NoSuchStageException e) {
- // No trouble; Android Sync might not recognize this engine yet.
- // By default, version 0. Other clients will see the 0 version and reset/wipe accordingly.
- engineSettings = new EngineSettings(Utils.generateGuid(), 0);
- }
- engines.put(engineName, engineSettings.toJSONObject());
- }
-
- MetaGlobal metaGlobal = new MetaGlobal(metaURL, this.getAuthHeaderProvider());
- metaGlobal.setSyncID(newSyncID);
- metaGlobal.setStorageVersion(STORAGE_VERSION);
- metaGlobal.setEngines(engines);
-
- // We assume that the config's declined engines have been updated
- // according to the user's selections.
- metaGlobal.setDeclinedEngineNames(this.declinedEngineNames());
-
- return metaGlobal;
- }
-
- /**
- * Suggest that your Sync client needs to be upgraded to work
- * with this server.
- */
- public void requiresUpgrade() {
- Logger.info(LOG_TAG, "Client outdated storage version; requires update.");
- // TODO: notify UI.
- this.abort(null, "Requires upgrade");
- }
-
- /**
- * If meta/global is missing or malformed, throws a MetaGlobalException.
- * Otherwise, returns true if there is an entry for this engine in the
- * meta/global "engines" object.
- * <p>
- * This is a global/permanent setting, not a local/temporary setting. For the
- * latter, see {@link GlobalSession#isEngineLocallyEnabled(String)}.
- *
- * @param engineName the name to check (e.g., "bookmarks").
- * @param engineSettings
- * if non-null, verify that the server engine settings are congruent
- * with this, throwing the appropriate MetaGlobalException if not.
- * @return
- * true if the engine with the provided name is present in the
- * meta/global "engines" object, and verification passed.
- *
- * @throws MetaGlobalException
- */
- public boolean isEngineRemotelyEnabled(String engineName, EngineSettings engineSettings) throws MetaGlobalException {
- if (this.config.metaGlobal == null) {
- throw new MetaGlobalNotSetException();
- }
-
- // This should not occur.
- if (this.config.enabledEngineNames == null) {
- Logger.error(LOG_TAG, "No enabled engines in config. Giving up.");
- throw new MetaGlobalMissingEnginesException();
- }
-
- if (!(this.config.enabledEngineNames.contains(engineName))) {
- Logger.debug(LOG_TAG, "Engine " + engineName + " not enabled: no meta/global entry.");
- return false;
- }
-
- // If we have a meta/global, check that it's safe for us to sync.
- // (If we don't, we'll create one later, which is why we return `true` above.)
- if (engineSettings != null) {
- // Throws if there's a problem.
- this.config.metaGlobal.verifyEngineSettings(engineName, engineSettings);
- }
-
- return true;
- }
-
-
- /**
- * Return true if the named stage should be synced this session.
- * <p>
- * This is a local/temporary setting, in contrast to the meta/global record,
- * which is a global/permanent setting. For the latter, see
- * {@link GlobalSession#isEngineRemotelyEnabled(String, EngineSettings)}.
- *
- * @param stageName
- * to query.
- * @return true if named stage is enabled for this sync.
- */
- public boolean isEngineLocallyEnabled(String stageName) {
- if (config.stagesToSync == null) {
- return true;
- }
- return config.stagesToSync.contains(stageName);
- }
-
- public ClientsDataDelegate getClientsDelegate() {
- return this.clientsDelegate;
- }
-
- /**
- * The longest backoff observed to date; -1 means no backoff observed.
- */
- protected final AtomicLong largestBackoffObserved = new AtomicLong(-1);
-
- /**
- * Reset any observed backoff and start observing HTTP responses for backoff
- * requests.
- */
- protected void installAsHttpResponseObserver() {
- Logger.debug(LOG_TAG, "Adding " + this + " as a BaseResource HttpResponseObserver.");
- BaseResource.addHttpResponseObserver(this);
- largestBackoffObserved.set(-1);
- }
-
- /**
- * Stop observing HttpResponses for backoff requests.
- */
- protected void uninstallAsHttpResponseObserver() {
- Logger.debug(LOG_TAG, "Removing " + this + " as a BaseResource HttpResponseObserver.");
- BaseResource.removeHttpResponseObserver(this);
- }
-
- /**
- * Observe all HTTP response for backoff requests on all status codes, not just errors.
- */
- @Override
- public void observeHttpResponse(HttpUriRequest request, HttpResponse response) {
- // Ignore non-Sync storage requests.
- final URI clusterURL = config.getClusterURL();
- if (clusterURL != null && !clusterURL.getHost().equals(request.getURI().getHost())) {
- // It's possible to see requests without a clusterURL (in particular,
- // during testing); allow some extra backoffs in this case.
- return;
- }
-
- long responseBackoff = (new SyncResponse(response)).totalBackoffInMilliseconds(); // TODO: don't allocate object?
- if (responseBackoff <= 0) {
- return;
- }
-
- Logger.debug(LOG_TAG, "Observed " + responseBackoff + " millisecond backoff request.");
- while (true) {
- long existingBackoff = largestBackoffObserved.get();
- if (existingBackoff >= responseBackoff) {
- return;
- }
- if (largestBackoffObserved.compareAndSet(existingBackoff, responseBackoff)) {
- return;
- }
- }
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/HTTPFailureException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/HTTPFailureException.java
deleted file mode 100644
index 69bba8841..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/HTTPFailureException.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-
-import android.content.SyncResult;
-
-public class HTTPFailureException extends SyncException {
- private static final long serialVersionUID = -5415864029780770619L;
- public SyncStorageResponse response;
-
- public HTTPFailureException(SyncStorageResponse response) {
- this.response = response;
- }
-
- @Override
- public String toString() {
- String errorMessage;
- try {
- errorMessage = this.response.getErrorMessage();
- } catch (Exception e) {
- // Oh well.
- errorMessage = "[unknown error message]";
- }
- return "<HTTPFailureException " + this.response.getStatusCode() +
- " :: (" + errorMessage + ")>";
- }
-
- @Override
- public void updateStats(GlobalSession globalSession, SyncResult syncResult) {
- switch (response.getStatusCode()) {
- case 401:
- // Node reassignment 401s get handled internally.
- syncResult.stats.numAuthExceptions++;
- return;
- case 500:
- case 501:
- case 503:
- // TODO: backoff.
- syncResult.stats.numIoExceptions++;
- return;
- }
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/InfoCollections.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/InfoCollections.java
deleted file mode 100644
index 374fa5cf5..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/InfoCollections.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-
-import org.mozilla.gecko.background.common.log.Logger;
-
-/**
- * Fetches the timestamp information in <code>info/collections</code> on the
- * Sync server. Provides access to those timestamps, along with logic to check
- * for whether a collection requires an update.
- */
-public class InfoCollections {
- private static final String LOG_TAG = "InfoCollections";
-
- /**
- * Fields fetched from the server, or <code>null</code> if not yet fetched.
- * <p>
- * Rather than storing decimal/double timestamps, as provided by the server,
- * we convert immediately to milliseconds since epoch.
- */
- final Map<String, Long> timestamps;
-
- public InfoCollections() {
- this(new ExtendedJSONObject());
- }
-
- public InfoCollections(final ExtendedJSONObject record) {
- Logger.debug(LOG_TAG, "info/collections is " + record.toJSONString());
- HashMap<String, Long> map = new HashMap<String, Long>();
-
- for (Entry<String, Object> entry : record.entrySet()) {
- final String key = entry.getKey();
- final Object value = entry.getValue();
-
- // These objects are most likely going to be Doubles. Regardless, we
- // want to get them in a more sane time format.
- if (value instanceof Double) {
- map.put(key, Utils.decimalSecondsToMilliseconds((Double) value));
- continue;
- }
- if (value instanceof Long) {
- map.put(key, Utils.decimalSecondsToMilliseconds((Long) value));
- continue;
- }
- if (value instanceof Integer) {
- map.put(key, Utils.decimalSecondsToMilliseconds((Integer) value));
- continue;
- }
- Logger.warn(LOG_TAG, "Skipping info/collections entry for " + key);
- }
-
- this.timestamps = Collections.unmodifiableMap(map);
- }
-
- /**
- * Return the timestamp for the given collection, or null if the timestamps
- * have not been fetched or the given collection does not have a timestamp.
- *
- * @param collection
- * The collection to inspect.
- * @return the timestamp in milliseconds since epoch.
- */
- public Long getTimestamp(String collection) {
- if (timestamps == null) {
- return null;
- }
- return timestamps.get(collection);
- }
-
- /**
- * Test if a given collection needs to be updated.
- *
- * @param collection
- * The collection to test.
- * @param lastModified
- * Timestamp when local record was last modified.
- */
- public boolean updateNeeded(String collection, long lastModified) {
- Logger.trace(LOG_TAG, "Testing " + collection + " for updateNeeded. Local last modified is " + lastModified + ".");
-
- // No local record of modification time? Need an update.
- if (lastModified <= 0) {
- return true;
- }
-
- // No meta/global on the server? We need an update. The server fetch will fail and
- // then we will upload a fresh meta/global.
- Long serverLastModified = getTimestamp(collection);
- if (serverLastModified == null) {
- return true;
- }
-
- // Otherwise, we need an update if our modification time is stale.
- return serverLastModified > lastModified;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/InfoConfiguration.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/InfoConfiguration.java
deleted file mode 100644
index eb2428433..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/InfoConfiguration.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import android.util.Log;
-
-import org.mozilla.gecko.background.common.log.Logger;
-
-/**
- * Wraps and provides access to configuration data returned from info/configuration.
- * Docs: https://docs.services.mozilla.com/storage/apis-1.5.html#general-info
- *
- * - <bold>max_request_bytes</bold>: the maximum size in bytes of the overall
- * HTTP request body that will be accepted by the server.
- *
- * - <bold>max_post_records</bold>: the maximum number of records that can be
- * uploaded to a collection in a single POST request.
- *
- * - <bold>max_post_bytes</bold>: the maximum combined size in bytes of the
- * record payloads that can be uploaded to a collection in a single
- * POST request.
- *
- * - <bold>max_total_records</bold>: the maximum number of records that can be
- * uploaded to a collection as part of a batched upload.
- *
- * - <bold>max_total_bytes</bold>: the maximum combined size in bytes of the
- * record payloads that can be uploaded to a collection as part of
- * a batched upload.
- */
-public class InfoConfiguration {
- private static final String LOG_TAG = "InfoConfiguration";
-
- public static final String MAX_REQUEST_BYTES = "max_request_bytes";
- public static final String MAX_POST_RECORDS = "max_post_records";
- public static final String MAX_POST_BYTES = "max_post_bytes";
- public static final String MAX_TOTAL_RECORDS = "max_total_records";
- public static final String MAX_TOTAL_BYTES = "max_total_bytes";
-
- private static final long DEFAULT_MAX_REQUEST_BYTES = 1048576;
- private static final long DEFAULT_MAX_POST_RECORDS = 100;
- private static final long DEFAULT_MAX_POST_BYTES = 1048576;
- private static final long DEFAULT_MAX_TOTAL_RECORDS = 10000;
- private static final long DEFAULT_MAX_TOTAL_BYTES = 104857600;
-
- // While int's upper range is (2^31-1), which in bytes is equivalent to 2.147 GB, let's be optimistic
- // about the future and use long here, so that this code works if the server decides its clients are
- // all on fiber and have congress-library sized bookmark collections.
- // Record counts are long for the sake of simplicity.
- public final long maxRequestBytes;
- public final long maxPostRecords;
- public final long maxPostBytes;
- public final long maxTotalRecords;
- public final long maxTotalBytes;
-
- public InfoConfiguration() {
- Logger.debug(LOG_TAG, "info/configuration is unavailable, using defaults");
-
- maxRequestBytes = DEFAULT_MAX_REQUEST_BYTES;
- maxPostRecords = DEFAULT_MAX_POST_RECORDS;
- maxPostBytes = DEFAULT_MAX_POST_BYTES;
- maxTotalRecords = DEFAULT_MAX_TOTAL_RECORDS;
- maxTotalBytes = DEFAULT_MAX_TOTAL_BYTES;
- }
-
- public InfoConfiguration(final ExtendedJSONObject record) {
- Logger.debug(LOG_TAG, "info/configuration is " + record.toJSONString());
-
- maxRequestBytes = getValueFromRecord(record, MAX_REQUEST_BYTES, DEFAULT_MAX_REQUEST_BYTES);
- maxPostRecords = getValueFromRecord(record, MAX_POST_RECORDS, DEFAULT_MAX_POST_RECORDS);
- maxPostBytes = getValueFromRecord(record, MAX_POST_BYTES, DEFAULT_MAX_POST_BYTES);
- maxTotalRecords = getValueFromRecord(record, MAX_TOTAL_RECORDS, DEFAULT_MAX_TOTAL_RECORDS);
- maxTotalBytes = getValueFromRecord(record, MAX_TOTAL_BYTES, DEFAULT_MAX_TOTAL_BYTES);
- }
-
- private static Long getValueFromRecord(ExtendedJSONObject record, String key, long defaultValue) {
- if (!record.containsKey(key)) {
- return defaultValue;
- }
-
- try {
- Long val = record.getLong(key);
- if (val == null) {
- return defaultValue;
- }
- return val;
- } catch (NumberFormatException e) {
- Log.w(LOG_TAG, "Could not parse key " + key + " from record: " + record, e);
- return defaultValue;
- }
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/InfoCounts.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/InfoCounts.java
deleted file mode 100644
index 832e97d10..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/InfoCounts.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-
-import org.mozilla.gecko.background.common.log.Logger;
-
-public class InfoCounts {
- static final String LOG_TAG = "InfoCounts";
-
- /**
- * Counts fetched from the server, or <code>null</code> if not yet fetched.
- */
- private Map<String, Integer> counts = null;
-
- @SuppressWarnings("unchecked")
- public InfoCounts(final ExtendedJSONObject record) {
- Logger.debug(LOG_TAG, "info/collection_counts is " + record.toJSONString());
- HashMap<String, Integer> map = new HashMap<String, Integer>();
-
- Set<Entry<String, Object>> entrySet = record.object.entrySet();
-
- String key;
- Object value;
-
- for (Entry<String, Object> entry : entrySet) {
- key = entry.getKey();
- value = entry.getValue();
-
- if (value instanceof Integer) {
- map.put(key, (Integer) value);
- continue;
- }
-
- if (value instanceof Long) {
- map.put(key, ((Long) value).intValue());
- continue;
- }
-
- Logger.warn(LOG_TAG, "Skipping info/collection_counts entry for " + key);
- }
-
- this.counts = Collections.unmodifiableMap(map);
- }
-
- /**
- * Return the server count for the given collection, or null if the counts
- * have not been fetched or the given collection does not have a count.
- *
- * @param collection
- * The collection to inspect.
- * @return the number of elements in the named collection.
- */
- public Integer getCount(String collection) {
- if (counts == null) {
- return null;
- }
- return counts.get(collection);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/JSONRecordFetcher.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/JSONRecordFetcher.java
deleted file mode 100644
index 982b5b026..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/JSONRecordFetcher.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.delegates.JSONRecordFetchDelegate;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.net.SyncStorageRecordRequest;
-import org.mozilla.gecko.sync.net.SyncStorageRequestDelegate;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-
-/**
- * An object which fetches a chunk of JSON from a URI, using certain credentials,
- * and informs its delegate of the result.
- */
-public class JSONRecordFetcher {
- private static final long DEFAULT_AWAIT_TIMEOUT_MSEC = 2 * 60 * 1000; // Two minutes.
- private static final String LOG_TAG = "JSONRecordFetcher";
-
- protected final AuthHeaderProvider authHeaderProvider;
- protected final String uri;
- protected JSONRecordFetchDelegate delegate;
-
- public JSONRecordFetcher(final String uri, final AuthHeaderProvider authHeaderProvider) {
- if (uri == null) {
- throw new IllegalArgumentException("uri must not be null");
- }
- this.uri = uri;
- this.authHeaderProvider = authHeaderProvider;
- }
-
- protected String getURI() {
- return this.uri;
- }
-
- private class JSONFetchHandler implements SyncStorageRequestDelegate {
-
- // SyncStorageRequestDelegate methods for fetching.
- @Override
- public AuthHeaderProvider getAuthHeaderProvider() {
- return authHeaderProvider;
- }
-
- @Override
- public String ifUnmodifiedSince() {
- return null;
- }
-
- @Override
- public void handleRequestSuccess(SyncStorageResponse response) {
- if (response.wasSuccessful()) {
- try {
- delegate.handleSuccess(response.jsonObjectBody());
- } catch (Exception e) {
- handleRequestError(e);
- }
- return;
- }
- handleRequestFailure(response);
- }
-
- @Override
- public void handleRequestFailure(SyncStorageResponse response) {
- delegate.handleFailure(response);
- }
-
- @Override
- public void handleRequestError(Exception ex) {
- delegate.handleError(ex);
- }
- }
-
- public void fetch(final JSONRecordFetchDelegate delegate) {
- this.delegate = delegate;
- try {
- final SyncStorageRecordRequest r = new SyncStorageRecordRequest(this.getURI());
- r.delegate = new JSONFetchHandler();
- r.get();
- } catch (Exception e) {
- delegate.handleError(e);
- }
- }
-
- private class LatchedJSONRecordFetchDelegate implements JSONRecordFetchDelegate {
- public ExtendedJSONObject body = null;
- public Exception exception = null;
- private final CountDownLatch latch;
-
- public LatchedJSONRecordFetchDelegate(CountDownLatch latch) {
- this.latch = latch;
- }
-
- @Override
- public void handleFailure(SyncStorageResponse response) {
- this.exception = new HTTPFailureException(response);
- latch.countDown();
- }
-
- @Override
- public void handleError(Exception e) {
- this.exception = e;
- latch.countDown();
- }
-
- @Override
- public void handleSuccess(ExtendedJSONObject body) {
- this.body = body;
- latch.countDown();
- }
- }
-
- /**
- * Fetch the info record, blocking until it returns.
- * @return the info record.
- */
- public ExtendedJSONObject fetchBlocking() throws HTTPFailureException, Exception {
- CountDownLatch latch = new CountDownLatch(1);
- LatchedJSONRecordFetchDelegate delegate = new LatchedJSONRecordFetchDelegate(latch);
- this.delegate = delegate;
- this.fetch(delegate);
-
- // Sanity wait: the resource itself will time out and throw after two
- // minutes, so we just want to avoid coding errors causing us to block
- // endlessly.
- if (!latch.await(DEFAULT_AWAIT_TIMEOUT_MSEC, TimeUnit.MILLISECONDS)) {
- Logger.warn(LOG_TAG, "Interrupted fetching info record.");
- throw new InterruptedException("info fetch timed out.");
- }
-
- if (delegate.body != null) {
- return delegate.body;
- }
-
- if (delegate.exception != null) {
- throw delegate.exception;
- }
-
- throw new Exception("Unknown error.");
- }
-} \ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/KeyBundleProvider.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/KeyBundleProvider.java
deleted file mode 100644
index 4a2be2a9b..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/KeyBundleProvider.java
+++ /dev/null
@@ -1,11 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-
-public interface KeyBundleProvider {
- public abstract KeyBundle keyBundle();
-} \ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobal.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobal.java
deleted file mode 100644
index a90c0fee8..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobal.java
+++ /dev/null
@@ -1,372 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import java.io.IOException;
-import java.net.URISyntaxException;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-import org.json.simple.JSONArray;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.MetaGlobalException.MetaGlobalMalformedSyncIDException;
-import org.mozilla.gecko.sync.MetaGlobalException.MetaGlobalMalformedVersionException;
-import org.mozilla.gecko.sync.delegates.MetaGlobalDelegate;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.net.SyncStorageRecordRequest;
-import org.mozilla.gecko.sync.net.SyncStorageRequestDelegate;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-
-public class MetaGlobal implements SyncStorageRequestDelegate {
- private static final String LOG_TAG = "MetaGlobal";
- protected String metaURL;
-
- // Fields.
- protected ExtendedJSONObject engines;
- protected JSONArray declined;
- protected Long storageVersion;
- protected String syncID;
-
- // Lookup tables.
- protected Map<String, String> syncIDs;
- protected Map<String, Integer> versions;
- protected Map<String, MetaGlobalException> exceptions;
-
- // Temporary location to store our callback.
- private MetaGlobalDelegate callback;
-
- // A little hack so we can use the same delegate implementation for upload and download.
- private boolean isUploading;
- protected final AuthHeaderProvider authHeaderProvider;
-
- public MetaGlobal(String metaURL, AuthHeaderProvider authHeaderProvider) {
- this.metaURL = metaURL;
- this.authHeaderProvider = authHeaderProvider;
- }
-
- public void fetch(MetaGlobalDelegate delegate) {
- this.callback = delegate;
- try {
- this.isUploading = false;
- SyncStorageRecordRequest r = new SyncStorageRecordRequest(this.metaURL);
- r.delegate = this;
- r.get();
- } catch (URISyntaxException e) {
- this.callback.handleError(e);
- }
- }
-
- public void upload(MetaGlobalDelegate callback) {
- try {
- this.isUploading = true;
- SyncStorageRecordRequest r = new SyncStorageRecordRequest(this.metaURL);
-
- r.delegate = this;
- this.callback = callback;
- r.put(this.asCryptoRecord());
- } catch (Exception e) {
- callback.handleError(e);
- }
- }
-
- protected ExtendedJSONObject asRecordContents() {
- ExtendedJSONObject json = new ExtendedJSONObject();
- json.put("storageVersion", storageVersion);
- json.put("engines", engines);
- json.put("syncID", syncID);
- json.put("declined", declined);
- return json;
- }
-
- /**
- * Return a copy ready for upload.
- * @return an unencrypted <code>CryptoRecord</code>.
- */
- public CryptoRecord asCryptoRecord() {
- ExtendedJSONObject payload = this.asRecordContents();
- CryptoRecord record = new CryptoRecord(payload);
- record.collection = "meta";
- record.guid = "global";
- record.deleted = false;
- return record;
- }
-
- public void setFromRecord(CryptoRecord record) throws IllegalStateException, IOException, NonObjectJSONException, NonArrayJSONException {
- if (record == null) {
- throw new IllegalArgumentException("Cannot set meta/global from null record");
- }
- Logger.debug(LOG_TAG, "meta/global is " + record.payload.toJSONString());
- this.storageVersion = (Long) record.payload.get("storageVersion");
- this.syncID = (String) record.payload.get("syncID");
-
- setEngines(record.payload.getObject("engines"));
-
- // Accepts null -- declined can be missing.
- setDeclinedEngineNames(record.payload.getArray("declined"));
- }
-
- public Long getStorageVersion() {
- return this.storageVersion;
- }
-
- public void setStorageVersion(Long version) {
- this.storageVersion = version;
- }
-
- public ExtendedJSONObject getEngines() {
- return engines;
- }
-
- @SuppressWarnings("unchecked")
- public void declineEngine(String engine) {
- if (this.declined == null) {
- JSONArray replacement = new JSONArray();
- replacement.add(engine);
- setDeclinedEngineNames(replacement);
- return;
- }
-
- this.declined.add(engine);
- }
-
- @SuppressWarnings("unchecked")
- public void declineEngineNames(Collection<String> additional) {
- if (this.declined == null) {
- JSONArray replacement = new JSONArray();
- replacement.addAll(additional);
- setDeclinedEngineNames(replacement);
- return;
- }
-
- for (String engine : additional) {
- if (!this.declined.contains(engine)) {
- this.declined.add(engine);
- }
- }
- }
-
- public void setDeclinedEngineNames(JSONArray declined) {
- if (declined == null) {
- this.declined = new JSONArray();
- return;
- }
- this.declined = declined;
- }
-
- /**
- * Return the set of engines that we support (given as an argument)
- * but the user hasn't explicitly declined on another device.
- *
- * Can return the input if the user hasn't declined any engines.
- */
- public Set<String> getNonDeclinedEngineNames(Set<String> supported) {
- if (this.declined == null ||
- this.declined.isEmpty()) {
- return supported;
- }
-
- final Set<String> result = new HashSet<String>(supported);
- result.removeAll(this.declined);
- return result;
- }
-
- public void setEngines(ExtendedJSONObject engines) {
- if (engines == null) {
- engines = new ExtendedJSONObject();
- }
- this.engines = engines;
- final int count = engines.size();
- versions = new HashMap<String, Integer>(count);
- syncIDs = new HashMap<String, String>(count);
- exceptions = new HashMap<String, MetaGlobalException>(count);
- for (String engineName : engines.keySet()) {
- try {
- ExtendedJSONObject engineEntry = engines.getObject(engineName);
- recordEngineState(engineName, engineEntry);
- } catch (NonObjectJSONException e) {
- Logger.error(LOG_TAG, "Engine field for " + engineName + " in meta/global is not an object.");
- recordEngineState(engineName, new ExtendedJSONObject()); // Doesn't have a version or syncID, for example, so will be server wiped.
- }
- }
- }
-
- /**
- * Take a JSON object corresponding to the 'engines' field for the provided engine name,
- * updating {@link #syncIDs} and {@link #versions} accordingly.
- *
- * If the record is malformed, an entry is added to {@link #exceptions}, to be rethrown
- * during validation.
- */
- protected void recordEngineState(String engineName, ExtendedJSONObject engineEntry) {
- if (engineEntry == null) {
- throw new IllegalArgumentException("engineEntry cannot be null.");
- }
-
- // Record syncID first, so that engines with bad versions are recorded.
- try {
- String syncID = engineEntry.getString("syncID");
- if (syncID == null) {
- Logger.warn(LOG_TAG, "No syncID for " + engineName + ". Recording exception.");
- exceptions.put(engineName, new MetaGlobalMalformedSyncIDException());
- }
- syncIDs.put(engineName, syncID);
- } catch (ClassCastException e) {
- // Malformed syncID on the server. Wipe the server.
- Logger.warn(LOG_TAG, "Malformed syncID " + engineEntry.get("syncID") +
- " for " + engineName + ". Recording exception.");
- exceptions.put(engineName, new MetaGlobalMalformedSyncIDException());
- }
-
- try {
- Integer version = engineEntry.getIntegerSafely("version");
- Logger.trace(LOG_TAG, "Engine " + engineName + " has server version " + version);
- if (version == null ||
- version == 0) {
- // Invalid version. Wipe the server.
- Logger.warn(LOG_TAG, "Malformed version " + version +
- " for " + engineName + ". Recording exception.");
- exceptions.put(engineName, new MetaGlobalMalformedVersionException());
- return;
- }
- versions.put(engineName, version);
- } catch (NumberFormatException e) {
- // Invalid version. Wipe the server.
- Logger.warn(LOG_TAG, "Malformed version " + engineEntry.get("version") +
- " for " + engineName + ". Recording exception.");
- exceptions.put(engineName, new MetaGlobalMalformedVersionException());
- return;
- }
- }
-
- /**
- * Get enabled engine names.
- *
- * @return a collection of engine names or <code>null</code> if meta/global
- * was malformed.
- */
- public Set<String> getEnabledEngineNames() {
- if (engines == null) {
- return null;
- }
- return new HashSet<String>(engines.keySet());
- }
-
- @SuppressWarnings("unchecked")
- public Set<String> getDeclinedEngineNames() {
- if (declined == null) {
- return null;
- }
- return new HashSet<String>(declined);
- }
-
- /**
- * Returns if the server settings and local settings match.
- * Throws a specific MetaGlobalException if that's not the case.
- */
- public void verifyEngineSettings(String engineName, EngineSettings engineSettings)
- throws MetaGlobalException {
-
- // We use syncIDs as our canary.
- if (syncIDs == null) {
- throw new IllegalStateException("No meta/global record yet processed.");
- }
-
- if (engineSettings == null) {
- throw new IllegalArgumentException("engineSettings cannot be null.");
- }
-
- // First, see if we had a parsing problem.
- final MetaGlobalException exception = exceptions.get(engineName);
- if (exception != null) {
- throw exception;
- }
-
- final String syncID = syncIDs.get(engineName);
- if (syncID == null) {
- // We have checked engineName against enabled engine names before this, so
- // we should either have a syncID or an exception for this engine already.
- throw new IllegalArgumentException("Unknown engine " + engineName);
- }
-
- // Since we don't have an exception, and we do have a syncID, we should have a version.
- final Integer version = versions.get(engineName);
- if (version > engineSettings.version) {
- // We're out of date.
- throw new MetaGlobalException.MetaGlobalStaleClientVersionException(version);
- }
-
- if (!syncID.equals(engineSettings.syncID)) {
- // Our syncID is wrong. Reset client and take the server syncID.
- throw new MetaGlobalException.MetaGlobalStaleClientSyncIDException(syncID);
- }
- }
-
- public String getSyncID() {
- return syncID;
- }
-
- public void setSyncID(String syncID) {
- this.syncID = syncID;
- }
-
- // SyncStorageRequestDelegate methods for fetching.
- public String credentials() {
- return null;
- }
-
- @Override
- public AuthHeaderProvider getAuthHeaderProvider() {
- return authHeaderProvider;
- }
-
- @Override
- public String ifUnmodifiedSince() {
- return null;
- }
-
- @Override
- public void handleRequestSuccess(SyncStorageResponse response) {
- if (this.isUploading) {
- this.handleUploadSuccess(response);
- } else {
- this.handleDownloadSuccess(response);
- }
- }
-
- private void handleUploadSuccess(SyncStorageResponse response) {
- this.callback.handleSuccess(this, response);
- }
-
- private void handleDownloadSuccess(SyncStorageResponse response) {
- if (response.wasSuccessful()) {
- try {
- CryptoRecord record = CryptoRecord.fromJSONRecord(response.jsonObjectBody());
- this.setFromRecord(record);
- this.callback.handleSuccess(this, response);
- } catch (Exception e) {
- this.callback.handleError(e);
- }
- return;
- }
- this.callback.handleFailure(response);
- }
-
- @Override
- public void handleRequestFailure(SyncStorageResponse response) {
- if (response.getStatusCode() == 404) {
- this.callback.handleMissing(this, response);
- return;
- }
- this.callback.handleFailure(response);
- }
-
- @Override
- public void handleRequestError(Exception e) {
- this.callback.handleError(e);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobalException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobalException.java
deleted file mode 100644
index bec531d11..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobalException.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-public class MetaGlobalException extends SyncException {
- private static final long serialVersionUID = -6182315615113508925L;
-
- public static class MetaGlobalMalformedSyncIDException extends MetaGlobalException {
- private static final long serialVersionUID = 1L;
- }
-
- public static class MetaGlobalMalformedVersionException extends MetaGlobalException {
- private static final long serialVersionUID = 1L;
- }
-
- public static class MetaGlobalOutdatedVersionException extends MetaGlobalException {
- private static final long serialVersionUID = 1L;
- }
-
- public static class MetaGlobalStaleClientVersionException extends MetaGlobalException {
- private static final long serialVersionUID = 1L;
- public final int serverVersion;
- public MetaGlobalStaleClientVersionException(final int version) {
- this.serverVersion = version;
- }
- }
-
- public static class MetaGlobalStaleClientSyncIDException extends MetaGlobalException {
- private static final long serialVersionUID = 1L;
- public final String serverSyncID;
- public MetaGlobalStaleClientSyncIDException(final String syncID) {
- this.serverSyncID = syncID;
- }
- }
-
- public static class MetaGlobalEngineStateChangedException extends MetaGlobalException {
- private static final long serialVersionUID = 1L;
- public final boolean isEnabled;
- public MetaGlobalEngineStateChangedException(boolean isEnabled) {
- this.isEnabled = isEnabled;
- }
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobalMissingEnginesException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobalMissingEnginesException.java
deleted file mode 100644
index 91bfd2f76..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobalMissingEnginesException.java
+++ /dev/null
@@ -1,9 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-public class MetaGlobalMissingEnginesException extends MetaGlobalException {
- private static final long serialVersionUID = -2662107402622277865L;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobalNotSetException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobalNotSetException.java
deleted file mode 100644
index ef059c71d..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/MetaGlobalNotSetException.java
+++ /dev/null
@@ -1,9 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-public class MetaGlobalNotSetException extends MetaGlobalException {
- private static final long serialVersionUID = 2959032409571832970L;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NoCollectionKeysSetException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NoCollectionKeysSetException.java
deleted file mode 100644
index 323e355b4..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NoCollectionKeysSetException.java
+++ /dev/null
@@ -1,16 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import android.content.SyncResult;
-
-public class NoCollectionKeysSetException extends SyncException {
- private static final long serialVersionUID = -6185128075412771120L;
-
- @Override
- public void updateStats(GlobalSession globalSession, SyncResult syncResult) {
- syncResult.stats.numAuthExceptions++;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NodeAuthenticationException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NodeAuthenticationException.java
deleted file mode 100644
index a5cd5f0eb..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NodeAuthenticationException.java
+++ /dev/null
@@ -1,16 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import android.content.SyncResult;
-
-public class NodeAuthenticationException extends SyncException {
- private static final long serialVersionUID = 8156745873212364352L;
-
- @Override
- public void updateStats(GlobalSession globalSession, SyncResult syncResult) {
- syncResult.stats.numAuthExceptions++;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NonArrayJSONException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NonArrayJSONException.java
deleted file mode 100644
index 554645b11..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NonArrayJSONException.java
+++ /dev/null
@@ -1,17 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-public class NonArrayJSONException extends UnexpectedJSONException {
- private static final long serialVersionUID = 5582918057432365749L;
-
- public NonArrayJSONException(String detailMessage) {
- super(detailMessage);
- }
-
- public NonArrayJSONException(Throwable throwable) {
- super(throwable);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NonObjectJSONException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NonObjectJSONException.java
deleted file mode 100644
index fd50d465e..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NonObjectJSONException.java
+++ /dev/null
@@ -1,17 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-public class NonObjectJSONException extends UnexpectedJSONException {
- private static final long serialVersionUID = 2214238763035650087L;
-
- public NonObjectJSONException(String detailMessage) {
- super(detailMessage);
- }
-
- public NonObjectJSONException(Throwable throwable) {
- super(throwable);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NullClusterURLException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NullClusterURLException.java
deleted file mode 100644
index c1d8833b6..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/NullClusterURLException.java
+++ /dev/null
@@ -1,16 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import android.content.SyncResult;
-
-public class NullClusterURLException extends SyncException {
- private static final long serialVersionUID = 4277845518548393161L;
-
- @Override
- public void updateStats(GlobalSession globalSession, SyncResult syncResult) {
- syncResult.stats.numAuthExceptions++;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/PersistedMetaGlobal.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/PersistedMetaGlobal.java
deleted file mode 100644
index d3467545c..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/PersistedMetaGlobal.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-
-import android.content.SharedPreferences;
-
-public class PersistedMetaGlobal {
- public static final String LOG_TAG = "PersistedMetaGlobal";
-
- public static final String META_GLOBAL_SERVER_RESPONSE_BODY = "metaGlobalServerResponseBody";
- public static final String META_GLOBAL_LAST_MODIFIED = "metaGlobalLastModified";
-
- protected SharedPreferences prefs;
-
- public PersistedMetaGlobal(SharedPreferences prefs) {
- this.prefs = prefs;
- }
-
- /**
- * Sets a <code>MetaGlobal</code> from persisted prefs.
- *
- * @param metaUrl
- * meta/global server URL
- * @param credentials
- * Sync credentials
- *
- * @return <MetaGlobal> set from previously fetched meta/global record from
- * server
- */
- public MetaGlobal metaGlobal(String metaUrl, AuthHeaderProvider authHeaderProvider) {
- String json = prefs.getString(META_GLOBAL_SERVER_RESPONSE_BODY, null);
- if (json == null) {
- return null;
- }
- MetaGlobal metaGlobal = null;
- try {
- CryptoRecord cryptoRecord = CryptoRecord.fromJSONRecord(json);
- MetaGlobal mg = new MetaGlobal(metaUrl, authHeaderProvider);
- mg.setFromRecord(cryptoRecord);
- metaGlobal = mg;
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Got exception decrypting persisted meta/global.", e);
- }
- return metaGlobal;
- }
-
- public void persistMetaGlobal(MetaGlobal metaGlobal) {
- if (metaGlobal == null) {
- Logger.debug(LOG_TAG, "Clearing persisted meta/global.");
- prefs.edit().remove(META_GLOBAL_SERVER_RESPONSE_BODY).commit();
- return;
- }
- try {
- CryptoRecord cryptoRecord = metaGlobal.asCryptoRecord();
- String json = cryptoRecord.toJSONString();
- Logger.debug(LOG_TAG, "Persisting meta/global.");
- prefs.edit().putString(META_GLOBAL_SERVER_RESPONSE_BODY, json).commit();
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Got exception encrypting while persisting meta/global.", e);
- }
- }
-
- public long lastModified() {
- return prefs.getLong(META_GLOBAL_LAST_MODIFIED, -1);
- }
-
- public void persistLastModified(long lastModified) {
- if (lastModified <= 0) {
- Logger.debug(LOG_TAG, "Clearing persisted meta/global last modified timestamp.");
- prefs.edit().remove(META_GLOBAL_LAST_MODIFIED).commit();
- return;
- }
- Logger.debug(LOG_TAG, "Persisting meta/global last modified timestamp " + lastModified + ".");
- prefs.edit().putLong(META_GLOBAL_LAST_MODIFIED, lastModified).commit();
- }
-
- public void purge() {
- persistLastModified(-1);
- persistMetaGlobal(null);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/PrefsBackoffHandler.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/PrefsBackoffHandler.java
deleted file mode 100644
index 63f6446da..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/PrefsBackoffHandler.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
-
-public class PrefsBackoffHandler implements BackoffHandler {
- public static final String PREF_EARLIEST_NEXT = "earliestnext";
-
- private final SharedPreferences prefs;
- private final String prefEarliest;
-
- public PrefsBackoffHandler(final SharedPreferences prefs, final String prefSuffix) {
- if (prefs == null) {
- throw new IllegalArgumentException("prefs must not be null.");
- }
- this.prefs = prefs;
- this.prefEarliest = PREF_EARLIEST_NEXT + "." + prefSuffix;
- }
-
- @Override
- public synchronized long getEarliestNextRequest() {
- return prefs.getLong(prefEarliest, 0);
- }
-
- @Override
- public synchronized void setEarliestNextRequest(final long next) {
- final Editor edit = prefs.edit();
- edit.putLong(prefEarliest, next);
- edit.commit();
- }
-
- @Override
- public synchronized void extendEarliestNextRequest(final long next) {
- if (prefs.getLong(prefEarliest, 0) >= next) {
- return;
- }
- final Editor edit = prefs.edit();
- edit.putLong(prefEarliest, next);
- edit.commit();
- }
-
- /**
- * Return the number of milliseconds until we're allowed to touch the server again,
- * or 0 if now is fine.
- */
- @Override
- public long delayMilliseconds() {
- long earliestNextRequest = getEarliestNextRequest();
- if (earliestNextRequest <= 0) {
- return 0;
- }
- long now = System.currentTimeMillis();
- return Math.max(0, earliestNextRequest - now);
- }
-} \ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/README.txt b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/README.txt
deleted file mode 100644
index cf4624ca4..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/README.txt
+++ /dev/null
@@ -1 +0,0 @@
-These files are managed in the android-sync repo. Do not modify directly, or your changes will be lost.
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Server11PreviousPostFailedException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Server11PreviousPostFailedException.java
deleted file mode 100644
index 4ea77f37c..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Server11PreviousPostFailedException.java
+++ /dev/null
@@ -1,12 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-/**
- * A previous POST failed, so we won't send any more records this session.
- */
-public class Server11PreviousPostFailedException extends SyncException {
- private static final long serialVersionUID = -3582490631414624310L;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Server11RecordPostFailedException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Server11RecordPostFailedException.java
deleted file mode 100644
index d654d3116..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Server11RecordPostFailedException.java
+++ /dev/null
@@ -1,12 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-/**
- * The server rejected a record in its "failure" array.
- */
-public class Server11RecordPostFailedException extends SyncException {
- private static final long serialVersionUID = -8517471217486190314L;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SharedPreferencesClientsDataDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SharedPreferencesClientsDataDelegate.java
deleted file mode 100644
index 4c1584d5a..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SharedPreferencesClientsDataDelegate.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import org.mozilla.gecko.background.fxa.FxAccountUtils;
-import org.mozilla.gecko.fxa.FirefoxAccounts;
-import org.mozilla.gecko.fxa.FxAccountDeviceRegistrator;
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
-import org.mozilla.gecko.util.HardwareUtils;
-
-import android.accounts.Account;
-import android.content.Context;
-import android.content.SharedPreferences;
-
-/**
- * A <code>ClientsDataDelegate</code> implementation that persists to a
- * <code>SharedPreferences</code> instance.
- */
-public class SharedPreferencesClientsDataDelegate implements ClientsDataDelegate {
- protected final SharedPreferences sharedPreferences;
- protected final Context context;
-
- public SharedPreferencesClientsDataDelegate(SharedPreferences sharedPreferences, Context context) {
- this.sharedPreferences = sharedPreferences;
- this.context = context;
-
- // It's safe to init this multiple times.
- HardwareUtils.init(context);
- }
-
- @Override
- public synchronized String getAccountGUID() {
- String accountGUID = sharedPreferences.getString(SyncConfiguration.PREF_ACCOUNT_GUID, null);
- if (accountGUID == null) {
- accountGUID = Utils.generateGuid();
- sharedPreferences.edit().putString(SyncConfiguration.PREF_ACCOUNT_GUID, accountGUID).commit();
- }
- return accountGUID;
- }
-
- private synchronized void saveClientNameToSharedPreferences(String clientName, long now) {
- sharedPreferences
- .edit()
- .putString(SyncConfiguration.PREF_CLIENT_NAME, clientName)
- .putLong(SyncConfiguration.PREF_CLIENT_DATA_TIMESTAMP, now)
- .apply();
- }
-
- /**
- * Set client name.
- *
- * @param clientName to change to.
- */
- @Override
- public synchronized void setClientName(String clientName, long now) {
- saveClientNameToSharedPreferences(clientName, now);
-
- // Update the FxA device registration
- final Account account = FirefoxAccounts.getFirefoxAccount(context);
- if (account != null) {
- final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
- fxAccount.resetDeviceRegistrationVersion();
- }
- }
-
- @Override
- public String getDefaultClientName() {
- return FxAccountUtils.defaultClientName(context);
- }
-
- @Override
- public synchronized String getClientName() {
- String clientName = sharedPreferences.getString(SyncConfiguration.PREF_CLIENT_NAME, null);
- if (clientName == null) {
- clientName = getDefaultClientName();
- long now = System.currentTimeMillis();
- saveClientNameToSharedPreferences(clientName, now); // Save locally only to avoid a recursion loop
- }
- return clientName;
- }
-
- @Override
- public synchronized void setClientsCount(int clientsCount) {
- sharedPreferences.edit().putLong(SyncConfiguration.PREF_NUM_CLIENTS, clientsCount).commit();
- }
-
- @Override
- public boolean isLocalGUID(String guid) {
- return getAccountGUID().equals(guid);
- }
-
- @Override
- public synchronized int getClientsCount() {
- return (int) sharedPreferences.getLong(SyncConfiguration.PREF_NUM_CLIENTS, 0);
- }
-
- @Override
- public long getLastModifiedTimestamp() {
- return sharedPreferences.getLong(SyncConfiguration.PREF_CLIENT_DATA_TIMESTAMP, 0);
- }
-
- @Override
- public String getFormFactor() {
- if (HardwareUtils.isLargeTablet()) {
- return "largetablet";
- }
-
- if (HardwareUtils.isSmallTablet()) {
- return "smalltablet";
- }
-
- if (HardwareUtils.isTelevision()) {
- return "tv";
- }
-
- return "phone";
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Sync11Configuration.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Sync11Configuration.java
deleted file mode 100644
index 4b2280895..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Sync11Configuration.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import java.net.URI;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
-
-/**
- * Override SyncConfiguration to restore the old behavior of clusterURL --
- * that is, a URL without the protocol version etc.
- *
- */
-public class Sync11Configuration extends SyncConfiguration {
- private static final String LOG_TAG = "Sync11Configuration";
- private static final String API_VERSION = "1.1";
-
- public Sync11Configuration(String username,
- AuthHeaderProvider authHeaderProvider,
- SharedPreferences prefs) {
- super(username, authHeaderProvider, prefs);
- }
-
- public Sync11Configuration(String username,
- AuthHeaderProvider authHeaderProvider,
- SharedPreferences prefs,
- KeyBundle keyBundle) {
- super(username, authHeaderProvider, prefs, keyBundle);
- }
-
- @Override
- public String getAPIVersion() {
- return API_VERSION;
- }
-
- @Override
- public String storageURL() {
- return clusterURL + API_VERSION + "/" + username + "/storage";
- }
-
- @Override
- protected String infoBaseURL() {
- return clusterURL + API_VERSION + "/" + username + "/info/";
- }
-
- protected void setAndPersistClusterURL(URI u, SharedPreferences prefs) {
- boolean shouldPersist = (prefs != null) && (clusterURL == null);
-
- Logger.trace(LOG_TAG, "Setting cluster URL to " + u.toASCIIString() +
- (shouldPersist ? ". Persisting." : ". Not persisting."));
- clusterURL = u;
- if (shouldPersist) {
- Editor edit = prefs.edit();
- edit.putString(PREF_CLUSTER_URL, clusterURL.toASCIIString());
- edit.commit();
- }
- }
-
- protected void setClusterURL(URI u, SharedPreferences prefs) {
- if (u == null) {
- Logger.warn(LOG_TAG, "Refusing to set cluster URL to null.");
- return;
- }
- URI uri = u.normalize();
- if (uri.toASCIIString().endsWith("/")) {
- setAndPersistClusterURL(u, prefs);
- return;
- }
- setAndPersistClusterURL(uri.resolve("/"), prefs);
- Logger.trace(LOG_TAG, "Set cluster URL to " + clusterURL.toASCIIString() + ", given input " + u.toASCIIString());
- }
-
- @Override
- public void setClusterURL(URI u) {
- setClusterURL(u, this.getPrefs());
- }
-} \ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncConfiguration.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncConfiguration.java
deleted file mode 100644
index 53edf5f84..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncConfiguration.java
+++ /dev/null
@@ -1,480 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-
-import org.mozilla.gecko.background.common.PrefsBranch;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.mozilla.gecko.sync.crypto.PersistedCrypto5Keys;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
-
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
-
-public class SyncConfiguration {
- private static final String LOG_TAG = "SyncConfiguration";
-
- // These must be set in GlobalSession's constructor.
- public URI clusterURL;
- public KeyBundle syncKeyBundle;
-
- public InfoConfiguration infoConfiguration;
-
- public CollectionKeys collectionKeys;
- public InfoCollections infoCollections;
- public MetaGlobal metaGlobal;
- public String syncID;
-
- protected final String username;
-
- /**
- * Persisted collection of enabledEngineNames.
- * <p>
- * Can contain engines Android Sync is not currently aware of, such as "prefs"
- * or "addons".
- * <p>
- * Copied from latest downloaded meta/global record and used to generate a
- * fresh meta/global record for upload.
- */
- public Set<String> enabledEngineNames;
- public Set<String> declinedEngineNames = new HashSet<String>();
-
- /**
- * Names of stages to sync <it>this sync</it>, or <code>null</code> to sync
- * all known stages.
- * <p>
- * Generated <it>each sync</it> from extras bundle passed to
- * <code>SyncAdapter.onPerformSync</code> and not persisted.
- * <p>
- * Not synchronized! Set this exactly once per global session and don't modify
- * it -- especially not from multiple threads.
- */
- public Collection<String> stagesToSync;
-
- /**
- * Engines whose sync state has been modified by the user through
- * SelectEnginesActivity, where each key-value pair is an engine name and
- * its sync state.
- *
- * This differs from <code>enabledEngineNames</code> in that
- * <code>enabledEngineNames</code> reflects the downloaded meta/global,
- * whereas <code>userSelectedEngines</code> stores the differences in engines to
- * sync that the user has selected.
- *
- * Each engine stage will check for engine changes at the beginning of the
- * stage.
- *
- * If no engine sync state changes have been made by the user, userSelectedEngines
- * will be null, and Sync will proceed normally.
- *
- * If the user has made changes to engine syncing state, each engine will sync
- * according to the sync state specified in userSelectedEngines and propagate that
- * state to meta/global, to be uploaded.
- */
- public Map<String, Boolean> userSelectedEngines;
- public long userSelectedEnginesTimestamp;
-
- public SharedPreferences prefs;
-
- protected final AuthHeaderProvider authHeaderProvider;
-
- public static final String PREF_PREFS_VERSION = "prefs.version";
- public static final long CURRENT_PREFS_VERSION = 1;
-
- public static final String CLIENTS_COLLECTION_TIMESTAMP = "serverClientsTimestamp"; // When the collection was touched.
- public static final String CLIENT_RECORD_TIMESTAMP = "serverClientRecordTimestamp"; // When our record was touched.
- public static final String MIGRATION_SENTINEL_CHECK_TIMESTAMP = "migrationSentinelCheckTimestamp"; // When we last looked in meta/fxa_credentials.
-
- public static final String PREF_CLUSTER_URL = "clusterURL";
- public static final String PREF_SYNC_ID = "syncID";
-
- public static final String PREF_ENABLED_ENGINE_NAMES = "enabledEngineNames";
- public static final String PREF_DECLINED_ENGINE_NAMES = "declinedEngineNames";
- public static final String PREF_USER_SELECTED_ENGINES_TO_SYNC = "userSelectedEngines";
- public static final String PREF_USER_SELECTED_ENGINES_TO_SYNC_TIMESTAMP = "userSelectedEnginesTimestamp";
-
- public static final String PREF_CLUSTER_URL_IS_STALE = "clusterurlisstale";
-
- public static final String PREF_ACCOUNT_GUID = "account.guid";
- public static final String PREF_CLIENT_NAME = "account.clientName";
- public static final String PREF_NUM_CLIENTS = "account.numClients";
- public static final String PREF_CLIENT_DATA_TIMESTAMP = "account.clientDataTimestamp";
-
- private static final String API_VERSION = "1.5";
-
- public SyncConfiguration(String username, AuthHeaderProvider authHeaderProvider, SharedPreferences prefs) {
- this.username = username;
- this.authHeaderProvider = authHeaderProvider;
- this.prefs = prefs;
- this.loadFromPrefs(prefs);
- }
-
- public SyncConfiguration(String username, AuthHeaderProvider authHeaderProvider, SharedPreferences prefs, KeyBundle syncKeyBundle) {
- this(username, authHeaderProvider, prefs);
- this.syncKeyBundle = syncKeyBundle;
- }
-
- public String getAPIVersion() {
- return API_VERSION;
- }
-
- public SharedPreferences getPrefs() {
- return this.prefs;
- }
-
- /**
- * Valid engines supported by Android Sync.
- *
- * @return Set<String> of valid engine names that Android Sync implements.
- */
- public static Set<String> validEngineNames() {
- Set<String> engineNames = new HashSet<String>();
- for (Stage stage : Stage.getNamedStages()) {
- engineNames.add(stage.getRepositoryName());
- }
- return engineNames;
- }
-
- /**
- * Return a convenient accessor for part of prefs.
- * @return
- * A PrefsBranch object representing this
- * section of the preferences space.
- */
- public PrefsBranch getBranch(String prefix) {
- return new PrefsBranch(this.getPrefs(), prefix);
- }
-
- /**
- * Gets the engine names that are enabled, declined, or other (depending on pref) in meta/global.
- *
- * @param prefs
- * SharedPreferences that the engines are associated with.
- * @param pref
- * The preference name to use. E.g, PREF_ENABLED_ENGINE_NAMES.
- * @return Set<String> of the enabled engine names if they have been stored,
- * or null otherwise.
- */
- protected static Set<String> getEngineNamesFromPref(SharedPreferences prefs, String pref) {
- final String json = prefs.getString(pref, null);
- if (json == null) {
- return null;
- }
- try {
- final ExtendedJSONObject o = new ExtendedJSONObject(json);
- return new HashSet<String>(o.keySet());
- } catch (Exception e) {
- return null;
- }
- }
-
- /**
- * Returns the set of engine names that the user has enabled. If none
- * have been stored in prefs, <code>null</code> is returned.
- */
- public static Set<String> getEnabledEngineNames(SharedPreferences prefs) {
- return getEngineNamesFromPref(prefs, PREF_ENABLED_ENGINE_NAMES);
- }
-
- /**
- * Returns the set of engine names that the user has declined.
- */
- public static Set<String> getDeclinedEngineNames(SharedPreferences prefs) {
- final Set<String> names = getEngineNamesFromPref(prefs, PREF_DECLINED_ENGINE_NAMES);
- if (names == null) {
- return new HashSet<String>();
- }
- return names;
- }
-
- /**
- * Gets the engines whose sync states have been changed by the user through the
- * SelectEnginesActivity.
- *
- * @param prefs
- * SharedPreferences of account that the engines are associated with.
- * @return Map<String, Boolean> of changed engines. Key is the lower-cased
- * engine name, Value is the new sync state.
- */
- public static Map<String, Boolean> getUserSelectedEngines(SharedPreferences prefs) {
- String json = prefs.getString(PREF_USER_SELECTED_ENGINES_TO_SYNC, null);
- if (json == null) {
- return null;
- }
- try {
- ExtendedJSONObject o = new ExtendedJSONObject(json);
- Map<String, Boolean> map = new HashMap<String, Boolean>();
- for (Entry<String, Object> e : o.entrySet()) {
- String key = e.getKey();
- Boolean value = (Boolean) e.getValue();
- map.put(key, value);
- // Forms depends on history. Add forms if history is selected.
- if ("history".equals(key)) {
- map.put("forms", value);
- }
- }
- // Sanity check: remove forms if history does not exist.
- if (!map.containsKey("history")) {
- map.remove("forms");
- }
- return map;
- } catch (Exception e) {
- return null;
- }
- }
-
- /**
- * Store a Map of engines and their sync states to prefs.
- *
- * Any engine that's disabled in the input is also recorded
- * as a declined engine, overwriting the stored values.
- *
- * @param prefs
- * SharedPreferences that the engines are associated with.
- * @param selectedEngines
- * Map<String, Boolean> of engine name to sync state
- */
- public static void storeSelectedEnginesToPrefs(SharedPreferences prefs, Map<String, Boolean> selectedEngines) {
- ExtendedJSONObject jObj = new ExtendedJSONObject();
- HashSet<String> declined = new HashSet<String>();
- for (Entry<String, Boolean> e : selectedEngines.entrySet()) {
- final Boolean enabled = e.getValue();
- final String engine = e.getKey();
- jObj.put(engine, enabled);
- if (!enabled) {
- declined.add(engine);
- }
- }
-
- // Our history checkbox drives form history, too.
- // We don't need to do this for enablement: that's done at retrieval time.
- if (selectedEngines.containsKey("history") && !selectedEngines.get("history")) {
- declined.add("forms");
- }
-
- String json = jObj.toJSONString();
- long currentTime = System.currentTimeMillis();
- Editor edit = prefs.edit();
- edit.putString(PREF_USER_SELECTED_ENGINES_TO_SYNC, json);
- edit.putString(PREF_DECLINED_ENGINE_NAMES, setToJSONObjectString(declined));
- edit.putLong(PREF_USER_SELECTED_ENGINES_TO_SYNC_TIMESTAMP, currentTime);
- Logger.error(LOG_TAG, "Storing user-selected engines at [" + currentTime + "].");
- edit.commit();
- }
-
- public void loadFromPrefs(SharedPreferences prefs) {
- if (prefs.contains(PREF_CLUSTER_URL)) {
- String u = prefs.getString(PREF_CLUSTER_URL, null);
- try {
- clusterURL = new URI(u);
- Logger.trace(LOG_TAG, "Set clusterURL from bundle: " + u);
- } catch (URISyntaxException e) {
- Logger.warn(LOG_TAG, "Ignoring bundle clusterURL (" + u + "): invalid URI.", e);
- }
- }
- if (prefs.contains(PREF_SYNC_ID)) {
- syncID = prefs.getString(PREF_SYNC_ID, null);
- Logger.trace(LOG_TAG, "Set syncID from bundle: " + syncID);
- }
- enabledEngineNames = getEnabledEngineNames(prefs);
- declinedEngineNames = getDeclinedEngineNames(prefs);
- userSelectedEngines = getUserSelectedEngines(prefs);
- userSelectedEnginesTimestamp = prefs.getLong(PREF_USER_SELECTED_ENGINES_TO_SYNC_TIMESTAMP, 0);
- // We don't set crypto/keys here because we need the syncKeyBundle to decrypt the JSON
- // and we won't have it on construction.
- // TODO: MetaGlobal, password, infoCollections.
- }
-
- public void persistToPrefs() {
- this.persistToPrefs(this.getPrefs());
- }
-
- private static String setToJSONObjectString(Set<String> set) {
- ExtendedJSONObject o = new ExtendedJSONObject();
- for (String name : set) {
- o.put(name, 0);
- }
- return o.toJSONString();
- }
-
- public void persistToPrefs(SharedPreferences prefs) {
- Editor edit = prefs.edit();
- if (clusterURL == null) {
- edit.remove(PREF_CLUSTER_URL);
- } else {
- edit.putString(PREF_CLUSTER_URL, clusterURL.toASCIIString());
- }
- if (syncID != null) {
- edit.putString(PREF_SYNC_ID, syncID);
- }
- if (enabledEngineNames == null) {
- edit.remove(PREF_ENABLED_ENGINE_NAMES);
- } else {
- edit.putString(PREF_ENABLED_ENGINE_NAMES, setToJSONObjectString(enabledEngineNames));
- }
- if (declinedEngineNames == null || declinedEngineNames.isEmpty()) {
- edit.remove(PREF_DECLINED_ENGINE_NAMES);
- } else {
- edit.putString(PREF_DECLINED_ENGINE_NAMES, setToJSONObjectString(declinedEngineNames));
- }
- if (userSelectedEngines == null) {
- edit.remove(PREF_USER_SELECTED_ENGINES_TO_SYNC);
- edit.remove(PREF_USER_SELECTED_ENGINES_TO_SYNC_TIMESTAMP);
- }
- // Don't bother saving userSelectedEngines - these should only be changed by
- // SelectEnginesActivity.
- edit.commit();
- // TODO: keys.
- }
-
- public AuthHeaderProvider getAuthHeaderProvider() {
- return authHeaderProvider;
- }
-
- public CollectionKeys getCollectionKeys() {
- return collectionKeys;
- }
-
- public void setCollectionKeys(CollectionKeys k) {
- collectionKeys = k;
- }
-
- /**
- * Return path to storage endpoint without trailing slash.
- *
- * @return storage endpoint without trailing slash.
- */
- public String storageURL() {
- return clusterURL + "/storage";
- }
-
- protected String infoBaseURL() {
- return clusterURL + "/info/";
- }
-
- public String infoCollectionsURL() {
- return infoBaseURL() + "collections";
- }
-
- public String infoConfigurationURL() {
- return infoBaseURL() + "configuration";
- }
-
- public String infoCollectionCountsURL() {
- return infoBaseURL() + "collection_counts";
- }
-
- public String metaURL() {
- return storageURL() + "/meta/global";
- }
-
- public URI collectionURI(String collection) throws URISyntaxException {
- return new URI(storageURL() + "/" + collection);
- }
-
- public URI collectionURI(String collection, boolean full) throws URISyntaxException {
- // Do it this way to make it easier to add more params later.
- // It's pretty ugly, I'll grant.
- boolean anyParams = full;
- String uriParams = "";
- if (anyParams) {
- StringBuilder params = new StringBuilder("?");
- if (full) {
- params.append("full=1");
- }
- uriParams = params.toString();
- }
- String uri = storageURL() + "/" + collection + uriParams;
- return new URI(uri);
- }
-
- public URI wboURI(String collection, String id) throws URISyntaxException {
- return new URI(storageURL() + "/" + collection + "/" + id);
- }
-
- public URI keysURI() throws URISyntaxException {
- return wboURI("crypto", "keys");
- }
-
- public URI getClusterURL() {
- return clusterURL;
- }
-
- public String getClusterURLString() {
- if (clusterURL == null) {
- return null;
- }
- return clusterURL.toASCIIString();
- }
-
- public void setClusterURL(URI u) {
- this.clusterURL = u;
- }
-
- /**
- * Used for direct management of related prefs.
- */
- public Editor getEditor() {
- return this.getPrefs().edit();
- }
-
- /**
- * We persist two different clients timestamps: our own record's,
- * and the timestamp for the collection.
- */
- public void persistServerClientRecordTimestamp(long timestamp) {
- getEditor().putLong(SyncConfiguration.CLIENT_RECORD_TIMESTAMP, timestamp).commit();
- }
-
- public long getPersistedServerClientRecordTimestamp() {
- return getPrefs().getLong(SyncConfiguration.CLIENT_RECORD_TIMESTAMP, 0L);
- }
-
- public void persistServerClientsTimestamp(long timestamp) {
- getEditor().putLong(SyncConfiguration.CLIENTS_COLLECTION_TIMESTAMP, timestamp).commit();
- }
-
- public long getPersistedServerClientsTimestamp() {
- return getPrefs().getLong(SyncConfiguration.CLIENTS_COLLECTION_TIMESTAMP, 0L);
- }
-
- public void persistLastMigrationSentinelCheckTimestamp(long timestamp) {
- getEditor().putLong(SyncConfiguration.MIGRATION_SENTINEL_CHECK_TIMESTAMP, timestamp).commit();
- }
-
- public long getLastMigrationSentinelCheckTimestamp() {
- return getPrefs().getLong(SyncConfiguration.MIGRATION_SENTINEL_CHECK_TIMESTAMP, 0L);
- }
-
- public void purgeCryptoKeys() {
- if (collectionKeys != null) {
- collectionKeys.clear();
- }
- persistedCryptoKeys().purge();
- }
-
- public void purgeMetaGlobal() {
- metaGlobal = null;
- persistedMetaGlobal().purge();
- }
-
- public PersistedCrypto5Keys persistedCryptoKeys() {
- return new PersistedCrypto5Keys(getPrefs(), syncKeyBundle);
- }
-
- public PersistedMetaGlobal persistedMetaGlobal() {
- return new PersistedMetaGlobal(getPrefs());
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncConfigurationException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncConfigurationException.java
deleted file mode 100644
index 02ba118c5..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncConfigurationException.java
+++ /dev/null
@@ -1,16 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import android.content.SyncResult;
-
-public class SyncConfigurationException extends SyncException {
- private static final long serialVersionUID = 1107080177269358381L;
-
- @Override
- public void updateStats(GlobalSession globalSession, SyncResult syncResult) {
- syncResult.stats.numAuthExceptions++;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncConstants.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncConstants.java
deleted file mode 100644
index 5dc7b289f..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncConstants.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import org.mozilla.gecko.AppConstants;
-
-public class SyncConstants {
- public static final String GLOBAL_LOG_TAG = "FxSync";
- public static final String SYNC_MAJOR_VERSION = "1";
- public static final String SYNC_MINOR_VERSION = "0";
- public static final String SYNC_VERSION_STRING = SYNC_MAJOR_VERSION + "." +
- AppConstants.MOZ_APP_VERSION + "." +
- SYNC_MINOR_VERSION;
-
- public static final String USER_AGENT = "Firefox AndroidSync " +
- SYNC_VERSION_STRING + " (" +
- AppConstants.MOZ_APP_UA_NAME + ")";
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncException.java
deleted file mode 100644
index ee0902568..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SyncException.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import android.content.SyncResult;
-
-public abstract class SyncException extends Exception {
- private static final long serialVersionUID = -6928990004393234738L;
-
- public SyncException() {
- super();
- }
-
- public SyncException(final Throwable e) {
- super(e);
- }
-
- /**
- * Update sync result statistics with information particular to this
- * exception.
- *
- * @param globalSession
- * current session, or null.
- * @param syncResult
- * Android sync result to update.
- */
- public void updateStats(GlobalSession globalSession, SyncResult syncResult) {
- // Assume storage error.
- // TODO: this logic is overly simplistic.
- syncResult.databaseError = true;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SynchronizerConfiguration.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SynchronizerConfiguration.java
deleted file mode 100644
index 2b08be9c4..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SynchronizerConfiguration.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import android.content.SharedPreferences.Editor;
-
-import org.mozilla.gecko.background.common.PrefsBranch;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
-
-import java.io.IOException;
-
-public class SynchronizerConfiguration {
- private static final String LOG_TAG = "SynczrConfiguration";
-
- public String syncID;
- public RepositorySessionBundle remoteBundle;
- public RepositorySessionBundle localBundle;
-
- public SynchronizerConfiguration(PrefsBranch config) throws NonObjectJSONException, IOException {
- this.load(config);
- }
-
- public SynchronizerConfiguration(String syncID, RepositorySessionBundle remoteBundle, RepositorySessionBundle localBundle) {
- this.syncID = syncID;
- this.remoteBundle = remoteBundle;
- this.localBundle = localBundle;
- }
-
- // This should get partly shuffled back into SyncConfiguration, I think.
- public void load(PrefsBranch config) throws NonObjectJSONException, IOException {
- if (config == null) {
- throw new IllegalArgumentException("config cannot be null.");
- }
- String remoteJSON = config.getString("remote", null);
- String localJSON = config.getString("local", null);
- RepositorySessionBundle rB = new RepositorySessionBundle(remoteJSON);
- RepositorySessionBundle lB = new RepositorySessionBundle(localJSON);
- if (remoteJSON == null) {
- rB.setTimestamp(0);
- }
- if (localJSON == null) {
- lB.setTimestamp(0);
- }
- syncID = config.getString("syncID", null);
- remoteBundle = rB;
- localBundle = lB;
- Logger.debug(LOG_TAG, "Loaded SynchronizerConfiguration. syncID: " + syncID + ", remoteBundle: " + remoteBundle + ", localBundle: " + localBundle);
- }
-
- public void persist(PrefsBranch config) {
- if (config == null) {
- throw new IllegalArgumentException("config cannot be null.");
- }
- String jsonRemote = remoteBundle.toJSONString();
- String jsonLocal = localBundle.toJSONString();
- Editor editor = config.edit();
- editor.putString("remote", jsonRemote);
- editor.putString("local", jsonLocal);
- editor.putString("syncID", syncID);
-
- // Synchronous.
- editor.commit();
- Logger.debug(LOG_TAG, "Persisted SynchronizerConfiguration. syncID: " + syncID + ", remoteBundle: " + remoteBundle + ", localBundle: " + localBundle);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/ThreadPool.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/ThreadPool.java
deleted file mode 100644
index 7f2029566..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/ThreadPool.java
+++ /dev/null
@@ -1,15 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-public class ThreadPool {
- public static ExecutorService executorService = Executors.newCachedThreadPool();
- public static void run(Runnable runnable) {
- executorService.submit(runnable);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/UnexpectedJSONException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/UnexpectedJSONException.java
deleted file mode 100644
index e5771452c..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/UnexpectedJSONException.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-public class UnexpectedJSONException extends Exception {
- private static final long serialVersionUID = 4797570033096443169L;
-
- public UnexpectedJSONException(String detailMessage) {
- super(detailMessage);
- }
-
- public UnexpectedJSONException(Throwable throwable) {
- super(throwable);
- }
-
- public static class BadRequiredFieldJSONException extends UnexpectedJSONException {
- private static final long serialVersionUID = -9207736984784497612L;
-
- public BadRequiredFieldJSONException(String string) {
- super(string);
- }
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/UnknownSynchronizerConfigurationVersionException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/UnknownSynchronizerConfigurationVersionException.java
deleted file mode 100644
index e2350095e..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/UnknownSynchronizerConfigurationVersionException.java
+++ /dev/null
@@ -1,16 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-public class UnknownSynchronizerConfigurationVersionException extends
- SyncConfigurationException {
- public int badVersion;
- private static final long serialVersionUID = -8497255862099517395L;
-
- public UnknownSynchronizerConfigurationVersionException(int version) {
- super();
- badVersion = version;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Utils.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Utils.java
deleted file mode 100644
index ef8859b4a..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Utils.java
+++ /dev/null
@@ -1,575 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync;
-
-import java.io.BufferedReader;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.UnsupportedEncodingException;
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.net.URLDecoder;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.text.DecimalFormat;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.concurrent.Executor;
-
-import org.json.simple.JSONArray;
-import org.mozilla.apache.commons.codec.binary.Base32;
-import org.mozilla.apache.commons.codec.binary.Base64;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.nativecode.NativeCrypto;
-import org.mozilla.gecko.sync.setup.Constants;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-
-public class Utils {
-
- private static final String LOG_TAG = "Utils";
-
- private static final SecureRandom sharedSecureRandom = new SecureRandom();
-
- // See <http://developer.android.com/reference/android/content/Context.html#getSharedPreferences%28java.lang.String,%20int%29>
- public static final int SHARED_PREFERENCES_MODE = 0;
-
- public static String generateGuid() {
- byte[] encodedBytes = Base64.encodeBase64(generateRandomBytes(9), false);
- return new String(encodedBytes).replace("+", "-").replace("/", "_");
- }
-
- /**
- * Helper to generate secure random bytes.
- *
- * @param length
- * Number of bytes to generate.
- */
- public static byte[] generateRandomBytes(int length) {
- byte[] bytes = new byte[length];
- sharedSecureRandom.nextBytes(bytes);
- return bytes;
- }
-
- /**
- * Helper to generate a random integer in a specified range.
- *
- * @param r
- * Generate an integer between 0 and r-1 inclusive.
- */
- public static BigInteger generateBigIntegerLessThan(BigInteger r) {
- int maxBytes = (int) Math.ceil(((double) r.bitLength()) / 8);
- BigInteger randInt = new BigInteger(generateRandomBytes(maxBytes));
- return randInt.mod(r);
- }
-
- /**
- * Helper to convert a byte array to a hex-encoded string
- */
- public static String byte2Hex(final byte[] b) {
- return byte2Hex(b, 2 * b.length);
- }
-
- public static String byte2Hex(final byte[] b, int hexLength) {
- final StringBuilder hs = new StringBuilder(Math.max(2*b.length, hexLength));
- String stmp;
-
- for (int n = 0; n < hexLength - 2*b.length; n++) {
- hs.append("0");
- }
-
- for (int n = 0; n < b.length; n++) {
- stmp = Integer.toHexString(b[n] & 0XFF);
-
- if (stmp.length() == 1) {
- hs.append("0");
- }
- hs.append(stmp);
- }
-
- return hs.toString();
- }
-
- public static byte[] concatAll(byte[] first, byte[]... rest) {
- int totalLength = first.length;
- for (byte[] array : rest) {
- totalLength += array.length;
- }
-
- byte[] result = new byte[totalLength];
- int offset = first.length;
-
- System.arraycopy(first, 0, result, 0, offset);
-
- for (byte[] array : rest) {
- System.arraycopy(array, 0, result, offset, array.length);
- offset += array.length;
- }
- return result;
- }
-
- /**
- * Utility for Base64 decoding. Should ensure that the correct
- * Apache Commons version is used.
- *
- * @param base64
- * An input string. Will be decoded as UTF-8.
- * @return
- * A byte array of decoded values.
- * @throws UnsupportedEncodingException
- * Should not occur.
- */
- public static byte[] decodeBase64(String base64) throws UnsupportedEncodingException {
- return Base64.decodeBase64(base64.getBytes("UTF-8"));
- }
-
- public static byte[] decodeFriendlyBase32(String base32) {
- Base32 converter = new Base32();
- final String translated = base32.replace('8', 'l').replace('9', 'o');
- return converter.decode(translated.toUpperCase(Locale.US));
- }
-
- public static byte[] hex2Byte(String str, int byteLength) {
- byte[] second = hex2Byte(str);
- if (second.length >= byteLength) {
- return second;
- }
- // New Java arrays are zeroed:
- // http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.12.5
- byte[] first = new byte[byteLength - second.length];
- return Utils.concatAll(first, second);
- }
-
- public static byte[] hex2Byte(String str) {
- if (str.length() % 2 == 1) {
- str = "0" + str;
- }
-
- byte[] bytes = new byte[str.length() / 2];
- for (int i = 0; i < bytes.length; i++) {
- bytes[i] = (byte) Integer.parseInt(str.substring(2 * i, 2 * i + 2), 16);
- }
- return bytes;
- }
-
- public static String millisecondsToDecimalSecondsString(long ms) {
- return millisecondsToDecimalSeconds(ms).toString();
- }
-
- // For dumping into JSON without quotes.
- public static BigDecimal millisecondsToDecimalSeconds(long ms) {
- return new BigDecimal(ms).movePointLeft(3);
- }
-
- // This lives until Bug 708956 lands, and we don't have to do it any more.
- public static long decimalSecondsToMilliseconds(String decimal) {
- try {
- return new BigDecimal(decimal).movePointRight(3).longValue();
- } catch (Exception e) {
- return -1;
- }
- }
-
- // Oh, Java.
- public static long decimalSecondsToMilliseconds(Double decimal) {
- // Truncates towards 0.
- return (long)(decimal * 1000);
- }
-
- public static long decimalSecondsToMilliseconds(Long decimal) {
- return decimal * 1000;
- }
-
- public static long decimalSecondsToMilliseconds(Integer decimal) {
- return (decimal * 1000);
- }
-
- public static byte[] sha256(byte[] in)
- throws NoSuchAlgorithmException {
- MessageDigest sha1 = MessageDigest.getInstance("SHA-256");
- return sha1.digest(in);
- }
-
- protected static byte[] sha1(final String utf8)
- throws NoSuchAlgorithmException, UnsupportedEncodingException {
- final byte[] bytes = utf8.getBytes("UTF-8");
- try {
- return NativeCrypto.sha1(bytes);
- } catch (final LinkageError e) {
- // This will throw UnsatisifiedLinkError (missing mozglue) the first time it is called, and
- // ClassNotDefFoundError, for the uninitialized NativeCrypto class, each subsequent time this
- // is called; LinkageError is their common ancestor.
- Logger.warn(LOG_TAG, "Got throwable stretching password using native sha1 implementation; " +
- "ignoring and using Java implementation.", e);
- final MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
- return sha1.digest(utf8.getBytes("UTF-8"));
- }
- }
-
- protected static String sha1Base32(final String utf8)
- throws NoSuchAlgorithmException, UnsupportedEncodingException {
- return new Base32().encodeAsString(sha1(utf8)).toLowerCase(Locale.US);
- }
-
- /**
- * If we encounter characters not allowed by the API (as found for
- * instance in an email address), hash the value.
- * @param account
- * An account string.
- * @return
- * An acceptable string.
- * @throws UnsupportedEncodingException
- * @throws NoSuchAlgorithmException
- */
- public static String usernameFromAccount(final String account) throws NoSuchAlgorithmException, UnsupportedEncodingException {
- if (account == null || account.equals("")) {
- throw new IllegalArgumentException("No account name provided.");
- }
- if (account.matches("^[A-Za-z0-9._-]+$")) {
- return account.toLowerCase(Locale.US);
- }
- return sha1Base32(account.toLowerCase(Locale.US));
- }
-
- public static SharedPreferences getSharedPreferences(final Context context, final String product, final String username, final String serverURL, final String profile, final long version)
- throws NoSuchAlgorithmException, UnsupportedEncodingException {
- String prefsPath = getPrefsPath(product, username, serverURL, profile, version);
- return context.getSharedPreferences(prefsPath, SHARED_PREFERENCES_MODE);
- }
-
- /**
- * Get shared preferences path for a Sync account.
- *
- * @param product the Firefox Sync product package name (like "org.mozilla.firefox").
- * @param username the Sync account name, optionally encoded with <code>Utils.usernameFromAccount</code>.
- * @param serverURL the Sync account server URL.
- * @param profile the Firefox profile name.
- * @param version the version of preferences to reference.
- * @return the path.
- * @throws NoSuchAlgorithmException
- * @throws UnsupportedEncodingException
- */
- public static String getPrefsPath(final String product, final String username, final String serverURL, final String profile, final long version)
- throws NoSuchAlgorithmException, UnsupportedEncodingException {
- final String encodedAccount = sha1Base32(serverURL + ":" + usernameFromAccount(username));
-
- if (version <= 0) {
- return "sync.prefs." + encodedAccount;
- } else {
- final String sanitizedProduct = product.replace('.', '!').replace(' ', '!');
- return "sync.prefs." + sanitizedProduct + "." + encodedAccount + "." + profile + "." + version;
- }
- }
-
- public static void addToIndexBucketMap(TreeMap<Long, ArrayList<String>> map, long index, String value) {
- ArrayList<String> bucket = map.get(index);
- if (bucket == null) {
- bucket = new ArrayList<String>();
- }
- bucket.add(value);
- map.put(index, bucket);
- }
-
- /**
- * Yes, an equality method that's null-safe.
- */
- private static boolean same(Object a, Object b) {
- if (a == b) {
- return true;
- }
- if (a == null || b == null) {
- return false; // If both null, case above applies.
- }
- return a.equals(b);
- }
-
- /**
- * Return true if the two arrays are both null, or are both arrays
- * containing the same elements in the same order.
- */
- public static boolean sameArrays(JSONArray a, JSONArray b) {
- if (a == b) {
- return true;
- }
- if (a == null || b == null) {
- return false;
- }
- final int size = a.size();
- if (size != b.size()) {
- return false;
- }
- for (int i = 0; i < size; ++i) {
- if (!same(a.get(i), b.get(i))) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Takes a URI, extracting URI components.
- * @param scheme the URI scheme on which to match.
- */
- @SuppressWarnings("deprecation")
- public static Map<String, String> extractURIComponents(String scheme, String uri) {
- if (uri.indexOf(scheme) != 0) {
- throw new IllegalArgumentException("URI scheme does not match: " + scheme);
- }
-
- // Do this the hard way to avoid taking a large dependency on
- // HttpClient or getting all regex-tastic.
- String components = uri.substring(scheme.length());
- HashMap<String, String> out = new HashMap<String, String>();
- String[] parts = components.split("&");
- for (int i = 0; i < parts.length; ++i) {
- String part = parts[i];
- if (part.length() == 0) {
- continue;
- }
- String[] pair = part.split("=", 2);
- switch (pair.length) {
- case 0:
- continue;
- case 1:
- out.put(URLDecoder.decode(pair[0]), null);
- break;
- case 2:
- out.put(URLDecoder.decode(pair[0]), URLDecoder.decode(pair[1]));
- break;
- }
- }
- return out;
- }
-
- // Because TextUtils.join is not stubbed.
- public static String toDelimitedString(String delimiter, Collection<? extends Object> items) {
- if (items == null || items.size() == 0) {
- return "";
- }
-
- StringBuilder sb = new StringBuilder();
- int i = 0;
- int c = items.size();
- for (Object object : items) {
- sb.append(object.toString());
- if (++i < c) {
- sb.append(delimiter);
- }
- }
- return sb.toString();
- }
-
- public static String toCommaSeparatedString(Collection<? extends Object> items) {
- return toDelimitedString(", ", items);
- }
-
- /**
- * Names of stages to sync: (ALL intersect SYNC) intersect (ALL minus SKIP).
- *
- * @param knownStageNames collection of known stage names (set ALL above).
- * @param toSync set SYNC above, or <code>null</code> to sync all known stages.
- * @param toSkip set SKIP above, or <code>null</code> to not skip any stages.
- * @return stage names.
- */
- public static Collection<String> getStagesToSync(final Collection<String> knownStageNames, Collection<String> toSync, Collection<String> toSkip) {
- if (toSkip == null) {
- toSkip = new HashSet<String>();
- } else {
- toSkip = new HashSet<String>(toSkip);
- }
-
- if (toSync == null) {
- toSync = new HashSet<String>(knownStageNames);
- } else {
- toSync = new HashSet<String>(toSync);
- }
- toSync.retainAll(knownStageNames);
- toSync.removeAll(toSkip);
- return toSync;
- }
-
- /**
- * Get names of stages to sync: (ALL intersect SYNC) intersect (ALL minus SKIP).
- *
- * @param knownStageNames collection of known stage names (set ALL above).
- * @param extras
- * a <code>Bundle</code> instance (possibly null) optionally containing keys
- * <code>EXTRAS_KEY_STAGES_TO_SYNC</code> (set SYNC above) and
- * <code>EXTRAS_KEY_STAGES_TO_SKIP</code> (set SKIP above).
- * @return stage names.
- */
- public static Collection<String> getStagesToSyncFromBundle(final Collection<String> knownStageNames, final Bundle extras) {
- if (extras == null) {
- return knownStageNames;
- }
- String toSyncString = extras.getString(Constants.EXTRAS_KEY_STAGES_TO_SYNC);
- String toSkipString = extras.getString(Constants.EXTRAS_KEY_STAGES_TO_SKIP);
- if (toSyncString == null && toSkipString == null) {
- return knownStageNames;
- }
-
- ArrayList<String> toSync = null;
- ArrayList<String> toSkip = null;
- if (toSyncString != null) {
- try {
- toSync = new ArrayList<String>(new ExtendedJSONObject(toSyncString).keySet());
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Got exception parsing stages to sync: '" + toSyncString + "'.", e);
- }
- }
- if (toSkipString != null) {
- try {
- toSkip = new ArrayList<String>(new ExtendedJSONObject(toSkipString).keySet());
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Got exception parsing stages to skip: '" + toSkipString + "'.", e);
- }
- }
-
- Logger.info(LOG_TAG, "Asked to sync '" + Utils.toCommaSeparatedString(toSync) +
- "' and to skip '" + Utils.toCommaSeparatedString(toSkip) + "'.");
- return getStagesToSync(knownStageNames, toSync, toSkip);
- }
-
- /**
- * Put names of stages to sync and to skip into sync extras bundle.
- *
- * @param bundle
- * a <code>Bundle</code> instance (possibly null).
- * @param stagesToSync
- * collection of stage names to sync: key
- * <code>EXTRAS_KEY_STAGES_TO_SYNC</code>; ignored if <code>null</code>.
- * @param stagesToSkip
- * collection of stage names to skip: key
- * <code>EXTRAS_KEY_STAGES_TO_SKIP</code>; ignored if <code>null</code>.
- */
- public static void putStageNamesToSync(final Bundle bundle, final String[] stagesToSync, final String[] stagesToSkip) {
- if (bundle == null) {
- return;
- }
-
- if (stagesToSync != null) {
- ExtendedJSONObject o = new ExtendedJSONObject();
- for (String stageName : stagesToSync) {
- o.put(stageName, 0);
- }
- bundle.putString(Constants.EXTRAS_KEY_STAGES_TO_SYNC, o.toJSONString());
- }
-
- if (stagesToSkip != null) {
- ExtendedJSONObject o = new ExtendedJSONObject();
- for (String stageName : stagesToSkip) {
- o.put(stageName, 0);
- }
- bundle.putString(Constants.EXTRAS_KEY_STAGES_TO_SKIP, o.toJSONString());
- }
- }
-
- /**
- * Read contents of file as a string.
- *
- * @param context Android context.
- * @param filename name of file to read; must not be null.
- * @return <code>String</code> instance.
- */
- public static String readFile(final Context context, final String filename) {
- if (filename == null) {
- throw new IllegalArgumentException("Passed null filename in readFile.");
- }
-
- FileInputStream fis = null;
- InputStreamReader isr = null;
- BufferedReader br = null;
-
- try {
- fis = context.openFileInput(filename);
- isr = new InputStreamReader(fis);
- br = new BufferedReader(isr);
- StringBuilder sb = new StringBuilder();
- String line;
- while ((line = br.readLine()) != null) {
- sb.append(line);
- }
- return sb.toString();
- } catch (Exception e) {
- return null;
- } finally {
- if (isr != null) {
- try {
- isr.close();
- } catch (IOException e) {
- // Ignore.
- }
- }
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException e) {
- // Ignore.
- }
- }
- }
- }
-
- /**
- * Format a duration as a string, like "0.56 seconds".
- *
- * @param startMillis start time in milliseconds.
- * @param endMillis end time in milliseconds.
- * @return formatted string.
- */
- public static String formatDuration(long startMillis, long endMillis) {
- final long duration = endMillis - startMillis;
- return new DecimalFormat("#0.00 seconds").format(((double) duration) / 1000);
- }
-
- /**
- * This will take a string containing a UTF-8 representation of a UTF-8
- * byte array — e.g., "pïgéons1" — and return UTF-8 (e.g., "pïgéons1").
- *
- * This is the format produced by desktop Firefox when exchanging credentials
- * containing non-ASCII characters.
- */
- public static String decodeUTF8(final String in) throws UnsupportedEncodingException {
- final int length = in.length();
- final byte[] asciiBytes = new byte[length];
- for (int i = 0; i < length; ++i) {
- asciiBytes[i] = (byte) in.codePointAt(i);
- }
- return new String(asciiBytes, "UTF-8");
- }
-
- /**
- * Replace "foo@bar.com" with "XXX@XXX.XXX".
- */
- public static String obfuscateEmail(final String in) {
- return in.replaceAll("[^@\\.]", "X");
- }
-
- public static void throwIfNull(Object... objects) {
- for (Object object : objects) {
- if (object == null) {
- throw new IllegalArgumentException("object must not be null");
- }
- }
- }
-
- public static Executor newSynchronousExecutor() {
- return new Executor() {
- @Override
- public void execute(Runnable runnable) {
- runnable.run();
- }
- };
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/CryptoException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/CryptoException.java
deleted file mode 100644
index a8d0483c9..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/CryptoException.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.crypto;
-
-import java.security.GeneralSecurityException;
-
-public class CryptoException extends Exception {
- public GeneralSecurityException cause;
- public CryptoException(GeneralSecurityException e) {
- this();
- this.cause = e;
- }
- public CryptoException() {
-
- }
- private static final long serialVersionUID = -5219310989960126830L;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/CryptoInfo.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/CryptoInfo.java
deleted file mode 100644
index 355571c6a..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/CryptoInfo.java
+++ /dev/null
@@ -1,232 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.crypto;
-
-import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.util.Arrays;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.Mac;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-
-import org.mozilla.apache.commons.codec.binary.Base64;
-
-/*
- * All info in these objects should be decoded (i.e. not BaseXX encoded).
- */
-public class CryptoInfo {
- private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
- private static final String KEY_ALGORITHM_SPEC = "AES";
-
- private byte[] message;
- private byte[] iv;
- private byte[] hmac;
- private KeyBundle keys;
-
- /**
- * Return a CryptoInfo with given plaintext encrypted using given keys.
- */
- public static CryptoInfo encrypt(byte[] plaintextBytes, KeyBundle keys) throws CryptoException {
- CryptoInfo info = new CryptoInfo(plaintextBytes, keys);
- info.encrypt();
- return info;
- }
-
- /**
- * Return a CryptoInfo with given plaintext encrypted using given keys and initial vector.
- */
- public static CryptoInfo encrypt(byte[] plaintextBytes, byte[] iv, KeyBundle keys) throws CryptoException {
- CryptoInfo info = new CryptoInfo(plaintextBytes, iv, null, keys);
- info.encrypt();
- return info;
- }
-
- /**
- * Return a CryptoInfo with given ciphertext decrypted using given keys and initial vector, verifying that given HMAC validates.
- */
- public static CryptoInfo decrypt(byte[] ciphertext, byte[] iv, byte[] hmac, KeyBundle keys) throws CryptoException {
- CryptoInfo info = new CryptoInfo(ciphertext, iv, hmac, keys);
- info.decrypt();
- return info;
- }
-
- /*
- * Constructor typically used when encrypting.
- */
- public CryptoInfo(byte[] message, KeyBundle keys) {
- this.setMessage(message);
- this.setKeys(keys);
- }
-
- /*
- * Constructor typically used when decrypting.
- */
- public CryptoInfo(byte[] message, byte[] iv, byte[] hmac, KeyBundle keys) {
- this.setMessage(message);
- this.setIV(iv);
- this.setHMAC(hmac);
- this.setKeys(keys);
- }
-
- public byte[] getMessage() {
- return message;
- }
-
- public void setMessage(byte[] message) {
- this.message = message;
- }
-
- public byte[] getIV() {
- return iv;
- }
-
- public void setIV(byte[] iv) {
- this.iv = iv;
- }
-
- public byte[] getHMAC() {
- return hmac;
- }
-
- public void setHMAC(byte[] hmac) {
- this.hmac = hmac;
- }
-
- public KeyBundle getKeys() {
- return keys;
- }
-
- public void setKeys(KeyBundle keys) {
- this.keys = keys;
- }
-
- /*
- * Generate HMAC for given cipher text.
- */
- public static byte[] generatedHMACFor(byte[] message, KeyBundle keys) throws NoSuchAlgorithmException, InvalidKeyException {
- Mac hmacHasher = HKDF.makeHMACHasher(keys.getHMACKey());
- return hmacHasher.doFinal(Base64.encodeBase64(message));
- }
-
- /*
- * Return true if generated HMAC is the same as the specified HMAC.
- */
- public boolean generatedHMACIsHMAC() throws NoSuchAlgorithmException, InvalidKeyException {
- byte[] generatedHMAC = generatedHMACFor(getMessage(), getKeys());
- byte[] expectedHMAC = getHMAC();
- return Arrays.equals(generatedHMAC, expectedHMAC);
- }
-
- /**
- * Performs functionality common to both encryption and decryption.
- *
- * @param cipher
- * @param inputMessage non-BaseXX-encoded message
- * @return encrypted/decrypted message
- * @throws CryptoException
- */
- private static byte[] commonCrypto(Cipher cipher, byte[] inputMessage)
- throws CryptoException {
- byte[] outputMessage = null;
- try {
- outputMessage = cipher.doFinal(inputMessage);
- } catch (IllegalBlockSizeException | BadPaddingException e) {
- throw new CryptoException(e);
- }
- return outputMessage;
- }
-
- /**
- * Encrypt a CryptoInfo in-place.
- *
- * @throws CryptoException
- */
- public void encrypt() throws CryptoException {
-
- Cipher cipher = CryptoInfo.getCipher(TRANSFORMATION);
- try {
- byte[] encryptionKey = getKeys().getEncryptionKey();
- SecretKeySpec spec = new SecretKeySpec(encryptionKey, KEY_ALGORITHM_SPEC);
-
- // If no IV is provided, we allow the cipher to provide one.
- if (getIV() == null || getIV().length == 0) {
- cipher.init(Cipher.ENCRYPT_MODE, spec);
- } else {
- cipher.init(Cipher.ENCRYPT_MODE, spec, new IvParameterSpec(getIV()));
- }
- } catch (GeneralSecurityException ex) {
- throw new CryptoException(ex);
- }
-
- // Encrypt.
- byte[] encryptedBytes = commonCrypto(cipher, getMessage());
- byte[] iv = cipher.getIV();
-
- byte[] hmac;
- // Generate HMAC.
- try {
- hmac = generatedHMACFor(encryptedBytes, keys);
- } catch (NoSuchAlgorithmException | InvalidKeyException e) {
- throw new CryptoException(e);
- }
-
- // Update in place. keys is already set.
- this.setHMAC(hmac);
- this.setIV(iv);
- this.setMessage(encryptedBytes);
- }
-
- /**
- * Decrypt a CryptoInfo in-place.
- *
- * @throws CryptoException
- */
- public void decrypt() throws CryptoException {
-
- // Check HMAC.
- try {
- if (!generatedHMACIsHMAC()) {
- throw new HMACVerificationException();
- }
- } catch (NoSuchAlgorithmException | InvalidKeyException e) {
- throw new CryptoException(e);
- }
-
- Cipher cipher = CryptoInfo.getCipher(TRANSFORMATION);
- try {
- byte[] encryptionKey = getKeys().getEncryptionKey();
- SecretKeySpec spec = new SecretKeySpec(encryptionKey, KEY_ALGORITHM_SPEC);
- cipher.init(Cipher.DECRYPT_MODE, spec, new IvParameterSpec(getIV()));
- } catch (GeneralSecurityException ex) {
- throw new CryptoException(ex);
- }
- byte[] decryptedBytes = commonCrypto(cipher, getMessage());
- byte[] iv = cipher.getIV();
-
- // Update in place. keys is already set.
- this.setHMAC(null);
- this.setIV(iv);
- this.setMessage(decryptedBytes);
- }
-
- /**
- * Helper to get a Cipher object.
- *
- * @param transformation The type of Cipher to get.
- */
- private static Cipher getCipher(String transformation) throws CryptoException {
- try {
- return Cipher.getInstance(transformation);
- } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
- throw new CryptoException(e);
- }
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/HKDF.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/HKDF.java
deleted file mode 100644
index 16c0d8147..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/HKDF.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.crypto;
-
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.NoSuchAlgorithmException;
-
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-
-import org.mozilla.gecko.sync.Utils;
-
-/*
- * A standards-compliant implementation of RFC 5869
- * for HMAC-based Key Derivation Function.
- * HMAC uses HMAC SHA256 standard.
- */
-public class HKDF {
- public static String HMAC_ALGORITHM = "hmacSHA256";
-
- /**
- * Used for conversion in cases in which you *know* the encoding exists.
- */
- public static final byte[] bytes(String in) {
- try {
- return in.getBytes("UTF-8");
- } catch (java.io.UnsupportedEncodingException e) {
- return null;
- }
- }
-
- public static final int BLOCKSIZE = 256 / 8;
- public static final byte[] HMAC_INPUT = bytes("Sync-AES_256_CBC-HMAC256");
-
- /*
- * Step 1 of RFC 5869
- * Get sha256HMAC Bytes
- * Input: salt (message), IKM (input keyring material)
- * Output: PRK (pseudorandom key)
- */
- public static byte[] hkdfExtract(byte[] salt, byte[] IKM) throws NoSuchAlgorithmException, InvalidKeyException {
- return digestBytes(IKM, makeHMACHasher(salt));
- }
-
- /*
- * Step 2 of RFC 5869.
- * Input: PRK from step 1, info, length.
- * Output: OKM (output keyring material).
- */
- public static byte[] hkdfExpand(byte[] prk, byte[] info, int len) throws NoSuchAlgorithmException, InvalidKeyException {
- Mac hmacHasher = makeHMACHasher(prk);
-
- byte[] T = {};
- byte[] Tn = {};
-
- int iterations = (int) Math.ceil(((double)len) / (BLOCKSIZE));
- for (int i = 0; i < iterations; i++) {
- Tn = digestBytes(Utils.concatAll(Tn, info, Utils.hex2Byte(Integer.toHexString(i + 1))),
- hmacHasher);
- T = Utils.concatAll(T, Tn);
- }
-
- byte[] result = new byte[len];
- System.arraycopy(T, 0, result, 0, len);
- return result;
- }
-
- /*
- * Make HMAC key
- * Input: key (salt)
- * Output: Key HMAC-Key
- */
- public static Key makeHMACKey(byte[] key) {
- if (key.length == 0) {
- key = new byte[BLOCKSIZE];
- }
- return new SecretKeySpec(key, HMAC_ALGORITHM);
- }
-
- /*
- * Make an HMAC hasher
- * Input: Key hmacKey
- * Ouput: An HMAC Hasher
- */
- public static Mac makeHMACHasher(byte[] key) throws NoSuchAlgorithmException, InvalidKeyException {
- Mac hmacHasher = null;
- hmacHasher = Mac.getInstance(HMAC_ALGORITHM);
-
- // If Mac.getInstance doesn't throw NoSuchAlgorithmException, hmacHasher is
- // non-null.
- assert(hmacHasher != null);
-
- hmacHasher.init(makeHMACKey(key));
- return hmacHasher;
- }
-
- /*
- * Hash bytes with given hasher
- * Input: message to hash, HMAC hasher
- * Output: hashed byte[].
- */
- public static byte[] digestBytes(byte[] message, Mac hasher) {
- hasher.update(message);
- byte[] ret = hasher.doFinal();
- hasher.reset();
- return ret;
- }
-
- public static byte[] derive(byte[] skm, byte[] xts, byte[] ctxInfo, int dkLen) throws InvalidKeyException, NoSuchAlgorithmException {
- return hkdfExpand(hkdfExtract(xts, skm), ctxInfo, dkLen);
- }
-
- public static void deriveMany(byte[] skm, byte[] xts, byte[] ctxInfo, byte[]... keys) throws InvalidKeyException, NoSuchAlgorithmException {
- int length = 0;
- for (byte[] key : keys) {
- length += key.length;
- }
- byte[] derived = hkdfExpand(hkdfExtract(xts, skm), ctxInfo, length);
- int offset = 0;
- for (byte[] key : keys) {
- System.arraycopy(derived, offset, key, 0, key.length);
- offset += key.length;
- }
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/HMACVerificationException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/HMACVerificationException.java
deleted file mode 100644
index f33babd52..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/HMACVerificationException.java
+++ /dev/null
@@ -1,12 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.crypto;
-
-public class HMACVerificationException extends CryptoException {
- private static final long serialVersionUID = 1235311303567074897L;
- public HMACVerificationException() {
- }
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/KeyBundle.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/KeyBundle.java
deleted file mode 100644
index 2063b1e32..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/KeyBundle.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.crypto;
-
-import java.io.UnsupportedEncodingException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.util.Arrays;
-
-import javax.crypto.KeyGenerator;
-import javax.crypto.Mac;
-
-import org.mozilla.apache.commons.codec.binary.Base64;
-import org.mozilla.gecko.sync.Utils;
-
-public class KeyBundle {
- private static final String KEY_ALGORITHM_SPEC = "AES";
- private static final int KEY_SIZE = 256;
-
- private byte[] encryptionKey;
- private byte[] hmacKey;
-
- // These are the same for every sync key bundle.
- private static final byte[] EMPTY_BYTES = {};
- private static final byte[] ENCR_INPUT_BYTES = {1};
- private static final byte[] HMAC_INPUT_BYTES = {2};
-
- /*
- * Mozilla's use of HKDF for getting keys from the Sync Key string.
- *
- * We do exactly 2 HKDF iterations and make the first iteration the
- * encryption key and the second iteration the HMAC key.
- *
- */
- public KeyBundle(String username, String base32SyncKey) throws CryptoException {
- if (base32SyncKey == null) {
- throw new IllegalArgumentException("No sync key provided.");
- }
- if (username == null || username.equals("")) {
- throw new IllegalArgumentException("No username provided.");
- }
- // Hash appropriately.
- try {
- username = Utils.usernameFromAccount(username);
- } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
- throw new IllegalArgumentException("Invalid username.");
- }
-
- byte[] syncKey = Utils.decodeFriendlyBase32(base32SyncKey);
- byte[] user = username.getBytes();
-
- Mac hmacHasher;
- try {
- hmacHasher = HKDF.makeHMACHasher(syncKey);
- } catch (NoSuchAlgorithmException | InvalidKeyException e) {
- throw new CryptoException(e);
- }
- assert(hmacHasher != null); // If makeHMACHasher doesn't throw, then hmacHasher is non-null.
-
- byte[] encrBytes = Utils.concatAll(EMPTY_BYTES, HKDF.HMAC_INPUT, user, ENCR_INPUT_BYTES);
- byte[] encrKey = HKDF.digestBytes(encrBytes, hmacHasher);
- byte[] hmacBytes = Utils.concatAll(encrKey, HKDF.HMAC_INPUT, user, HMAC_INPUT_BYTES);
-
- this.hmacKey = HKDF.digestBytes(hmacBytes, hmacHasher);
- this.encryptionKey = encrKey;
- }
-
- public KeyBundle(byte[] encryptionKey, byte[] hmacKey) {
- this.setEncryptionKey(encryptionKey);
- this.setHMACKey(hmacKey);
- }
-
- /**
- * Make a KeyBundle with the specified base64-encoded keys.
- *
- * @return A KeyBundle with the specified keys.
- */
- public static KeyBundle fromBase64EncodedKeys(String base64EncryptionKey, String base64HmacKey) throws UnsupportedEncodingException {
- return new KeyBundle(Base64.decodeBase64(base64EncryptionKey.getBytes("UTF-8")),
- Base64.decodeBase64(base64HmacKey.getBytes("UTF-8")));
- }
-
- /**
- * Make a KeyBundle with two random 256 bit keys (encryption and HMAC).
- *
- * @return A KeyBundle with random keys.
- */
- public static KeyBundle withRandomKeys() throws CryptoException {
- KeyGenerator keygen;
- try {
- keygen = KeyGenerator.getInstance(KEY_ALGORITHM_SPEC);
- } catch (NoSuchAlgorithmException e) {
- throw new CryptoException(e);
- }
-
- keygen.init(KEY_SIZE);
- byte[] encryptionKey = keygen.generateKey().getEncoded();
- byte[] hmacKey = keygen.generateKey().getEncoded();
-
- return new KeyBundle(encryptionKey, hmacKey);
- }
-
- public byte[] getEncryptionKey() {
- return encryptionKey;
- }
-
- public void setEncryptionKey(byte[] encryptionKey) {
- this.encryptionKey = encryptionKey;
- }
-
- public byte[] getHMACKey() {
- return hmacKey;
- }
-
- public void setHMACKey(byte[] hmacKey) {
- this.hmacKey = hmacKey;
- }
-
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof KeyBundle)) {
- return false;
- }
- KeyBundle other = (KeyBundle) o;
- return Arrays.equals(other.encryptionKey, this.encryptionKey) &&
- Arrays.equals(other.hmacKey, this.hmacKey);
- }
-
- @Override
- public int hashCode() {
- throw new UnsupportedOperationException("No hashCode for KeyBundle.");
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/MissingCryptoInputException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/MissingCryptoInputException.java
deleted file mode 100644
index 8add1cf11..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/MissingCryptoInputException.java
+++ /dev/null
@@ -1,9 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.crypto;
-
-public class MissingCryptoInputException extends CryptoException {
- private static final long serialVersionUID = 5334412407012972445L;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/NoKeyBundleException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/NoKeyBundleException.java
deleted file mode 100644
index 00e0f8b18..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/NoKeyBundleException.java
+++ /dev/null
@@ -1,9 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.crypto;
-
-public class NoKeyBundleException extends CryptoException {
- private static final long serialVersionUID = -6627154503154040915L;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/PBKDF2.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/PBKDF2.java
deleted file mode 100644
index 636b2105c..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/PBKDF2.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.crypto;
-
-import java.security.GeneralSecurityException;
-import java.util.Arrays;
-
-import javax.crypto.Mac;
-import javax.crypto.ShortBufferException;
-import javax.crypto.spec.SecretKeySpec;
-
-public class PBKDF2 {
- public static byte[] pbkdf2SHA256(byte[] password, byte[] salt, int c, int dkLen)
- throws GeneralSecurityException {
- final String algorithm = "HmacSHA256";
- SecretKeySpec keyspec = new SecretKeySpec(password, algorithm);
- Mac prf = Mac.getInstance(algorithm);
- prf.init(keyspec);
-
- int hLen = prf.getMacLength();
-
- byte U_r[] = new byte[hLen];
- byte U_i[] = new byte[salt.length + 4];
- byte scratch[] = new byte[hLen];
-
- int l = Math.max(dkLen, hLen);
- int r = dkLen - (l - 1) * hLen;
- byte T[] = new byte[l * hLen];
- int ti_offset = 0;
- for (int i = 1; i <= l; i++) {
- Arrays.fill(U_r, (byte) 0);
- F(T, ti_offset, prf, salt, c, i, U_r, U_i, scratch);
- ti_offset += hLen;
- }
-
- if (r < hLen) {
- // Incomplete last block.
- byte DK[] = new byte[dkLen];
- System.arraycopy(T, 0, DK, 0, dkLen);
- return DK;
- }
-
- return T;
- }
-
- private static void F(byte[] dest, int offset, Mac prf, byte[] S, int c, int blockIndex, byte U_r[], byte U_i[], byte[] scratch)
- throws ShortBufferException, IllegalStateException {
- final int hLen = prf.getMacLength();
-
- // U0 = S || INT (i);
- System.arraycopy(S, 0, U_i, 0, S.length);
- INT(U_i, S.length, blockIndex);
-
- for (int i = 0; i < c; i++) {
- prf.update(U_i);
- prf.doFinal(scratch, 0);
- U_i = scratch;
- xor(U_r, U_i);
- }
-
- System.arraycopy(U_r, 0, dest, offset, hLen);
- }
-
- private static void xor(byte[] dest, byte[] src) {
- for (int i = 0; i < dest.length; i++) {
- dest[i] ^= src[i];
- }
- }
-
- private static void INT(byte[] dest, int offset, int i) {
- dest[offset + 0] = (byte) (i / (256 * 256 * 256));
- dest[offset + 1] = (byte) (i / (256 * 256));
- dest[offset + 2] = (byte) (i / (256));
- dest[offset + 3] = (byte) (i);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/PersistedCrypto5Keys.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/PersistedCrypto5Keys.java
deleted file mode 100644
index 4dba4f258..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/crypto/PersistedCrypto5Keys.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.crypto;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.CollectionKeys;
-import org.mozilla.gecko.sync.CryptoRecord;
-
-import android.content.SharedPreferences;
-
-public class PersistedCrypto5Keys {
- public static final String LOG_TAG = "PersistedC5Keys";
-
- public static final String CRYPTO5_KEYS_SERVER_RESPONSE_BODY = "crypto5KeysServerResponseBody";
- public static final String CRYPTO5_KEYS_LAST_MODIFIED = "crypto5KeysLastModified";
-
- protected SharedPreferences prefs;
- protected KeyBundle syncKeyBundle;
-
- public PersistedCrypto5Keys(SharedPreferences prefs, KeyBundle syncKeyBundle) {
- if (syncKeyBundle == null) {
- throw new IllegalArgumentException("Null syncKeyBundle passed in to PersistedCrypto5Keys constructor.");
- }
- this.prefs = prefs;
- this.syncKeyBundle = syncKeyBundle;
- }
-
- /**
- * Get persisted crypto/keys.
- * <p>
- * crypto/keys is fetched from an encrypted JSON-encoded <code>CryptoRecord</code>.
- *
- * @return A <code>CollectionKeys</code> instance or <code>null</code> if none
- * is currently persisted.
- */
- public CollectionKeys keys() {
- String keysJSON = prefs.getString(CRYPTO5_KEYS_SERVER_RESPONSE_BODY, null);
- if (keysJSON == null) {
- return null;
- }
- try {
- CryptoRecord cryptoRecord = CryptoRecord.fromJSONRecord(keysJSON);
- CollectionKeys keys = new CollectionKeys();
- keys.setKeyPairsFromWBO(cryptoRecord, syncKeyBundle);
- return keys;
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Got exception decrypting persisted crypto/keys.", e);
- return null;
- }
- }
-
- /**
- * Persist crypto/keys.
- * <p>
- * crypto/keys is stored as an encrypted JSON-encoded <code>CryptoRecord</code>.
- *
- * @param keys
- * The <code>CollectionKeys</code> object to persist, which should
- * have the same default key bundle as the sync key bundle.
- */
- public void persistKeys(CollectionKeys keys) {
- if (keys == null) {
- Logger.debug(LOG_TAG, "Clearing persisted crypto/keys.");
- prefs.edit().remove(CRYPTO5_KEYS_SERVER_RESPONSE_BODY).commit();
- return;
- }
- try {
- CryptoRecord cryptoRecord = keys.asCryptoRecord();
- cryptoRecord.keyBundle = syncKeyBundle;
- cryptoRecord.encrypt();
- String keysJSON = cryptoRecord.toJSONString();
- Logger.debug(LOG_TAG, "Persisting crypto/keys.");
- prefs.edit().putString(CRYPTO5_KEYS_SERVER_RESPONSE_BODY, keysJSON).commit();
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Got exception encrypting while persisting crypto/keys.", e);
- }
- }
-
- public boolean persistedKeysExist() {
- return lastModified() > 0;
- }
-
- public long lastModified() {
- return prefs.getLong(CRYPTO5_KEYS_LAST_MODIFIED, -1);
- }
-
- public void persistLastModified(long lastModified) {
- if (lastModified <= 0) {
- Logger.debug(LOG_TAG, "Clearing persisted crypto/keys last modified timestamp.");
- prefs.edit().remove(CRYPTO5_KEYS_LAST_MODIFIED).commit();
- return;
- }
- Logger.debug(LOG_TAG, "Persisting crypto/keys last modified timestamp " + lastModified + ".");
- prefs.edit().putLong(CRYPTO5_KEYS_LAST_MODIFIED, lastModified).commit();
- }
-
- public void purge() {
- persistLastModified(-1);
- persistKeys(null);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/ClientsDataDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/ClientsDataDelegate.java
deleted file mode 100644
index 07e9179f0..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/ClientsDataDelegate.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.delegates;
-
-public interface ClientsDataDelegate {
- public String getAccountGUID();
- public String getDefaultClientName();
- public void setClientName(String clientName, long now);
- public String getClientName();
- public void setClientsCount(int clientsCount);
- public int getClientsCount();
- public boolean isLocalGUID(String guid);
- public String getFormFactor();
-
- /**
- * The last time the client's data was modified in a way that should be
- * reflected remotely.
- * <p>
- * Changing the client's name should be reflected remotely, while changing the
- * clients count should not (since that data is only used to inform local
- * policy.)
- *
- * @return timestamp in milliseconds.
- */
- public long getLastModifiedTimestamp();
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/FreshStartDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/FreshStartDelegate.java
deleted file mode 100644
index 2e5347061..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/FreshStartDelegate.java
+++ /dev/null
@@ -1,10 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.delegates;
-
-public interface FreshStartDelegate {
- void onFreshStart();
- void onFreshStartFailed(Exception e);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/GlobalSessionCallback.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/GlobalSessionCallback.java
deleted file mode 100644
index 9829f5b34..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/GlobalSessionCallback.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.delegates;
-
-import java.net.URI;
-
-import org.mozilla.gecko.sync.GlobalSession;
-import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
-
-public interface GlobalSessionCallback {
- /**
- * Request that no further syncs occur within the next `backoff` milliseconds.
- * @param backoff a duration in milliseconds.
- */
- void requestBackoff(long backoff);
-
- /**
- * Called on a 401 HTTP response.
- */
- void informUnauthorizedResponse(GlobalSession globalSession, URI oldClusterURL);
-
-
- /**
- * Called when an HTTP failure indicates that a software upgrade is required.
- */
- void informUpgradeRequiredResponse(GlobalSession session);
-
- /**
- * Called when a migration sentinel has been found and processed successfully.
- * <p>
- * This account should stop syncing immediately, and arrange to delete itself.
- */
- void informMigrated(GlobalSession session);
-
- void handleAborted(GlobalSession globalSession, String reason);
- void handleError(GlobalSession globalSession, Exception ex);
- void handleSuccess(GlobalSession globalSession);
- void handleStageCompleted(Stage currentState, GlobalSession globalSession);
-
- /**
- * Called when a {@link GlobalSession} wants to know if it should continue
- * to make storage requests.
- *
- * @return false if the session should make no further requests.
- */
- boolean shouldBackOffStorage();
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/JSONRecordFetchDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/JSONRecordFetchDelegate.java
deleted file mode 100644
index 90b73a33a..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/JSONRecordFetchDelegate.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.delegates;
-
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-
-/**
- * A fairly generic delegate to handle fetches of single JSON object blobs, as
- * provided by <code>info/configuration</code>, <code>info/collections</code>
- * and <code>info/collection_counts</code>.
- */
-public interface JSONRecordFetchDelegate {
- public void handleSuccess(ExtendedJSONObject body);
- public void handleFailure(SyncStorageResponse response);
- public void handleError(Exception e);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/KeyUploadDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/KeyUploadDelegate.java
deleted file mode 100644
index 0cd5ec732..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/KeyUploadDelegate.java
+++ /dev/null
@@ -1,21 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.delegates;
-
-public interface KeyUploadDelegate {
- /**
- * Called when keys have been successfully uploaded to the server.
- * <p>
- * The uploaded keys are intentionally not exposed. It is possible for two
- * clients to simultaneously upload keys and for each client to conclude that
- * its keys are current (since the server returned 200 on upload). To shorten
- * the window wherein two such clients can race, all clients should upload and
- * then immediately re-download the fetched keys.
- * <p>
- * See Bug 692700, Bug 693893.
- */
- void onKeysUploaded();
- void onKeyUploadFailed(Exception e);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/MetaGlobalDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/MetaGlobalDelegate.java
deleted file mode 100644
index 13854cb5a..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/MetaGlobalDelegate.java
+++ /dev/null
@@ -1,15 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.delegates;
-
-import org.mozilla.gecko.sync.MetaGlobal;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-
-public interface MetaGlobalDelegate {
- public void handleSuccess(MetaGlobal global, SyncStorageResponse response);
- public void handleMissing(MetaGlobal global, SyncStorageResponse response);
- public void handleFailure(SyncStorageResponse response);
- public void handleError(Exception e);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/WipeServerDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/WipeServerDelegate.java
deleted file mode 100644
index ef3565812..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/delegates/WipeServerDelegate.java
+++ /dev/null
@@ -1,10 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.delegates;
-
-public interface WipeServerDelegate {
- public void onWiped(long timestamp);
- public void onWipeFailed(Exception e);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/Crypto5MiddlewareRepository.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/Crypto5MiddlewareRepository.java
deleted file mode 100644
index 79319aff5..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/Crypto5MiddlewareRepository.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.middleware;
-
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.mozilla.gecko.sync.repositories.IdentityRecordFactory;
-import org.mozilla.gecko.sync.repositories.RecordFactory;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCleanDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-
-import android.content.Context;
-
-/**
- * Wrap an existing repository in middleware that encrypts and decrypts records
- * passing through.
- *
- * @author rnewman
- *
- */
-public class Crypto5MiddlewareRepository extends MiddlewareRepository {
-
- public RecordFactory recordFactory = new IdentityRecordFactory();
-
- public class Crypto5MiddlewareRepositorySessionCreationDelegate extends MiddlewareRepository.SessionCreationDelegate {
- private final Crypto5MiddlewareRepository repository;
- private final RepositorySessionCreationDelegate outerDelegate;
-
- public Crypto5MiddlewareRepositorySessionCreationDelegate(Crypto5MiddlewareRepository repository, RepositorySessionCreationDelegate outerDelegate) {
- this.repository = repository;
- this.outerDelegate = outerDelegate;
- }
-
- @Override
- public void onSessionCreateFailed(Exception ex) {
- this.outerDelegate.onSessionCreateFailed(ex);
- }
-
- @Override
- public void onSessionCreated(RepositorySession session) {
- // Do some work, then report success with the wrapping session.
- Crypto5MiddlewareRepositorySession cryptoSession;
- try {
- // Synchronous, baby.
- cryptoSession = new Crypto5MiddlewareRepositorySession(session, this.repository, recordFactory);
- } catch (Exception ex) {
- this.outerDelegate.onSessionCreateFailed(ex);
- return;
- }
- this.outerDelegate.onSessionCreated(cryptoSession);
- }
- }
-
- public KeyBundle keyBundle;
- private final Repository inner;
-
- public Crypto5MiddlewareRepository(Repository inner, KeyBundle keys) {
- super();
- this.inner = inner;
- this.keyBundle = keys;
- }
- @Override
- public void createSession(RepositorySessionCreationDelegate delegate, Context context) {
- Crypto5MiddlewareRepositorySessionCreationDelegate delegateWrapper = new Crypto5MiddlewareRepositorySessionCreationDelegate(this, delegate);
- inner.createSession(delegateWrapper, context);
- }
-
- @Override
- public void clean(boolean success, RepositorySessionCleanDelegate delegate,
- Context context) {
- this.inner.clean(success, delegate, context);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/Crypto5MiddlewareRepositorySession.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/Crypto5MiddlewareRepositorySession.java
deleted file mode 100644
index 46de7a236..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/Crypto5MiddlewareRepositorySession.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.middleware;
-
-import java.io.UnsupportedEncodingException;
-import java.util.concurrent.ExecutorService;
-
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.crypto.CryptoException;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.mozilla.gecko.sync.repositories.InactiveSessionException;
-import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
-import org.mozilla.gecko.sync.repositories.RecordFactory;
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-/**
- * It's a RepositorySession that accepts Records as input, producing CryptoRecords
- * for submission to a remote service.
- * Takes a RecordFactory as a parameter. This is in charge of taking decrypted CryptoRecords
- * as input and producing some expected kind of Record as output for local use.
- *
- *
-
-
-
- +------------------------------------+
- | Server11RepositorySession |
- +-------------------------+----------+
- ^ |
- | |
- Encrypted CryptoRecords
- | |
- | v
- +---------+--------------------------+
- | Crypto5MiddlewareRepositorySession |
- +------------------------------------+
- ^ |
- | | Decrypted CryptoRecords
- | |
- | +---------------+
- | | RecordFactory |
- | +--+------------+
- | |
- Local Record instances
- | |
- | v
- +---------+--------------------------+
- | Local RepositorySession instance |
- +------------------------------------+
-
-
- * @author rnewman
- *
- */
-public class Crypto5MiddlewareRepositorySession extends MiddlewareRepositorySession {
- private final KeyBundle keyBundle;
- private final RecordFactory recordFactory;
-
- public Crypto5MiddlewareRepositorySession(RepositorySession session, Crypto5MiddlewareRepository repository, RecordFactory recordFactory) {
- super(session, repository);
- this.keyBundle = repository.keyBundle;
- this.recordFactory = recordFactory;
- }
-
- public class DecryptingTransformingFetchDelegate implements RepositorySessionFetchRecordsDelegate {
- private final RepositorySessionFetchRecordsDelegate next;
- private final KeyBundle keyBundle;
- private final RecordFactory recordFactory;
-
- DecryptingTransformingFetchDelegate(RepositorySessionFetchRecordsDelegate next, KeyBundle bundle, RecordFactory recordFactory) {
- this.next = next;
- this.keyBundle = bundle;
- this.recordFactory = recordFactory;
- }
-
- @Override
- public void onFetchFailed(Exception ex, Record record) {
- next.onFetchFailed(ex, record);
- }
-
- @Override
- public void onFetchedRecord(Record record) {
- CryptoRecord r;
- try {
- r = (CryptoRecord) record;
- } catch (ClassCastException e) {
- next.onFetchFailed(e, record);
- return;
- }
- r.keyBundle = keyBundle;
- try {
- r.decrypt();
- } catch (Exception e) {
- next.onFetchFailed(e, r);
- return;
- }
- Record transformed;
- try {
- transformed = this.recordFactory.createRecord(r);
- } catch (Exception e) {
- next.onFetchFailed(e, r);
- return;
- }
- next.onFetchedRecord(transformed);
- }
-
- @Override
- public void onFetchCompleted(final long fetchEnd) {
- next.onFetchCompleted(fetchEnd);
- }
-
- @Override
- public RepositorySessionFetchRecordsDelegate deferredFetchDelegate(ExecutorService executor) {
- // Synchronously perform *our* work, passing through appropriately.
- RepositorySessionFetchRecordsDelegate deferredNext = next.deferredFetchDelegate(executor);
- return new DecryptingTransformingFetchDelegate(deferredNext, keyBundle, recordFactory);
- }
- }
-
- private DecryptingTransformingFetchDelegate makeUnwrappingDelegate(RepositorySessionFetchRecordsDelegate inner) {
- if (inner == null) {
- throw new IllegalArgumentException("Inner delegate cannot be null!");
- }
- return new DecryptingTransformingFetchDelegate(inner, this.keyBundle, this.recordFactory);
- }
-
- @Override
- public void fetchSince(long timestamp,
- RepositorySessionFetchRecordsDelegate delegate) {
- inner.fetchSince(timestamp, makeUnwrappingDelegate(delegate));
- }
-
- @Override
- public void fetch(String[] guids,
- RepositorySessionFetchRecordsDelegate delegate) throws InactiveSessionException {
- inner.fetch(guids, makeUnwrappingDelegate(delegate));
- }
-
- @Override
- public void fetchAll(RepositorySessionFetchRecordsDelegate delegate) {
- inner.fetchAll(makeUnwrappingDelegate(delegate));
- }
-
- @Override
- public void setStoreDelegate(RepositorySessionStoreDelegate delegate) {
- // TODO: it remains to be seen how this will work.
- inner.setStoreDelegate(delegate);
- this.delegate = delegate; // So we can handle errors without involving inner.
- }
-
- @Override
- public void store(Record record) throws NoStoreDelegateException {
- if (delegate == null) {
- throw new NoStoreDelegateException();
- }
- CryptoRecord rec = record.getEnvelope();
- rec.keyBundle = this.keyBundle;
- try {
- rec.encrypt();
- } catch (UnsupportedEncodingException | CryptoException e) {
- delegate.onRecordStoreFailed(e, record.guid);
- return;
- }
- // Allow the inner session to do delegate handling.
- inner.store(rec);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/MiddlewareRepository.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/MiddlewareRepository.java
deleted file mode 100644
index d807aa5c0..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/MiddlewareRepository.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.middleware;
-
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-
-public abstract class MiddlewareRepository extends Repository {
-
- public abstract class SessionCreationDelegate implements
- RepositorySessionCreationDelegate {
-
- // We call through to our inner repository, so we don't need our own
- // deferral scheme.
- @Override
- public RepositorySessionCreationDelegate deferredCreationDelegate() {
- return this;
- }
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/MiddlewareRepositorySession.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/MiddlewareRepositorySession.java
deleted file mode 100644
index e14ef5226..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/middleware/MiddlewareRepositorySession.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.middleware;
-
-import java.util.concurrent.ExecutorService;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.repositories.InactiveSessionException;
-import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionGuidsSinceDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
-
-public abstract class MiddlewareRepositorySession extends RepositorySession {
- private static final String LOG_TAG = "MiddlewareSession";
- protected final RepositorySession inner;
-
- public MiddlewareRepositorySession(RepositorySession innerSession, MiddlewareRepository repository) {
- super(repository);
- this.inner = innerSession;
- }
-
- @Override
- public void wipe(RepositorySessionWipeDelegate delegate) {
- inner.wipe(delegate);
- }
-
- public class MiddlewareRepositorySessionBeginDelegate implements RepositorySessionBeginDelegate {
-
- private final MiddlewareRepositorySession outerSession;
- private final RepositorySessionBeginDelegate next;
-
- public MiddlewareRepositorySessionBeginDelegate(MiddlewareRepositorySession outerSession, RepositorySessionBeginDelegate next) {
- this.outerSession = outerSession;
- this.next = next;
- }
-
- @Override
- public void onBeginFailed(Exception ex) {
- next.onBeginFailed(ex);
- }
-
- @Override
- public void onBeginSucceeded(RepositorySession session) {
- next.onBeginSucceeded(outerSession);
- }
-
- @Override
- public RepositorySessionBeginDelegate deferredBeginDelegate(ExecutorService executor) {
- final RepositorySessionBeginDelegate deferred = next.deferredBeginDelegate(executor);
- return new RepositorySessionBeginDelegate() {
- @Override
- public void onBeginSucceeded(RepositorySession session) {
- if (inner != session) {
- Logger.warn(LOG_TAG, "Got onBeginSucceeded for session " + session + ", not our inner session!");
- }
- deferred.onBeginSucceeded(outerSession);
- }
-
- @Override
- public void onBeginFailed(Exception ex) {
- deferred.onBeginFailed(ex);
- }
-
- @Override
- public RepositorySessionBeginDelegate deferredBeginDelegate(ExecutorService executor) {
- return this;
- }
- };
- }
- }
-
- @Override
- public void begin(RepositorySessionBeginDelegate delegate) throws InvalidSessionTransitionException {
- inner.begin(new MiddlewareRepositorySessionBeginDelegate(this, delegate));
- }
-
- public class MiddlewareRepositorySessionFinishDelegate implements RepositorySessionFinishDelegate {
- private final MiddlewareRepositorySession outerSession;
- private final RepositorySessionFinishDelegate next;
-
- public MiddlewareRepositorySessionFinishDelegate(MiddlewareRepositorySession outerSession, RepositorySessionFinishDelegate next) {
- this.outerSession = outerSession;
- this.next = next;
- }
-
- @Override
- public void onFinishFailed(Exception ex) {
- next.onFinishFailed(ex);
- }
-
- @Override
- public void onFinishSucceeded(RepositorySession session, RepositorySessionBundle bundle) {
- next.onFinishSucceeded(outerSession, bundle);
- }
-
- @Override
- public RepositorySessionFinishDelegate deferredFinishDelegate(ExecutorService executor) {
- return this;
- }
- }
-
- @Override
- public void finish(RepositorySessionFinishDelegate delegate) throws InactiveSessionException {
- inner.finish(new MiddlewareRepositorySessionFinishDelegate(this, delegate));
- }
-
-
- @Override
- public synchronized void ensureActive() throws InactiveSessionException {
- inner.ensureActive();
- }
-
- @Override
- public synchronized boolean isActive() {
- return inner.isActive();
- }
-
- @Override
- public synchronized SessionStatus getStatus() {
- return inner.getStatus();
- }
-
- @Override
- public synchronized void setStatus(SessionStatus status) {
- inner.setStatus(status);
- }
-
- @Override
- public synchronized void transitionFrom(SessionStatus from, SessionStatus to)
- throws InvalidSessionTransitionException {
- inner.transitionFrom(from, to);
- }
-
- @Override
- public void abort() {
- inner.abort();
- }
-
- @Override
- public void abort(RepositorySessionFinishDelegate delegate) {
- inner.abort(new MiddlewareRepositorySessionFinishDelegate(this, delegate));
- }
-
- @Override
- public void guidsSince(long timestamp, RepositorySessionGuidsSinceDelegate delegate) {
- // TODO: need to do anything here?
- inner.guidsSince(timestamp, delegate);
- }
-
- @Override
- public void storeDone() {
- inner.storeDone();
- }
-
- @Override
- public void storeDone(long storeEnd) {
- inner.storeDone(storeEnd);
- }
-
- @Override
- public boolean shouldSkip() {
- return inner.shouldSkip();
- }
-
- @Override
- public boolean dataAvailable() {
- return inner.dataAvailable();
- }
-
- @Override
- public void unbundle(RepositorySessionBundle bundle) {
- inner.unbundle(bundle);
- }
-
- @Override
- public long getLastSyncTimestamp() {
- return inner.getLastSyncTimestamp();
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/AbstractBearerTokenAuthHeaderProvider.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/AbstractBearerTokenAuthHeaderProvider.java
deleted file mode 100644
index e3b4f25b1..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/AbstractBearerTokenAuthHeaderProvider.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-import ch.boye.httpclientandroidlib.Header;
-import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
-import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
-import ch.boye.httpclientandroidlib.message.BasicHeader;
-import ch.boye.httpclientandroidlib.protocol.BasicHttpContext;
-
-/**
- * An <code>AuthHeaderProvider</code> that returns an Authorization header for
- * bearer tokens, adding a simple prefix.
- */
-public abstract class AbstractBearerTokenAuthHeaderProvider implements AuthHeaderProvider {
- protected final String header;
-
- public AbstractBearerTokenAuthHeaderProvider(String token) {
- if (token == null) {
- throw new IllegalArgumentException("token must not be null.");
- }
-
- this.header = getPrefix() + " " + token;
- }
-
- protected abstract String getPrefix();
-
- @Override
- public Header getAuthHeader(HttpRequestBase request, BasicHttpContext context, DefaultHttpClient client) {
- return new BasicHeader("Authorization", header);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/AuthHeaderProvider.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/AuthHeaderProvider.java
deleted file mode 100644
index 7be6fef3d..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/AuthHeaderProvider.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-import java.security.GeneralSecurityException;
-
-import ch.boye.httpclientandroidlib.Header;
-import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
-import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
-import ch.boye.httpclientandroidlib.protocol.BasicHttpContext;
-
-/**
- * An <code>AuthHeaderProvider</code> generates HTTP Authorization headers for
- * HTTP requests.
- */
-public interface AuthHeaderProvider {
- /**
- * Generate an HTTP Authorization header.
- *
- * @param request HTTP request.
- * @param context HTTP context.
- * @param client HTTP client.
- * @return HTTP Authorization header.
- * @throws GeneralSecurityException usually wrapping a more specific exception.
- */
- Header getAuthHeader(HttpRequestBase request, BasicHttpContext context, DefaultHttpClient client)
- throws GeneralSecurityException;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BaseResource.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BaseResource.java
deleted file mode 100644
index 60bbc86bb..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BaseResource.java
+++ /dev/null
@@ -1,565 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.lang.ref.WeakReference;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.security.GeneralSecurityException;
-import java.security.KeyManagementException;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-import javax.net.ssl.SSLContext;
-
-import org.json.simple.JSONArray;
-import org.json.simple.JSONObject;
-import org.mozilla.gecko.background.common.GlobalConstants;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-
-import ch.boye.httpclientandroidlib.Header;
-import ch.boye.httpclientandroidlib.HttpEntity;
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.HttpVersion;
-import ch.boye.httpclientandroidlib.client.AuthCache;
-import ch.boye.httpclientandroidlib.client.ClientProtocolException;
-import ch.boye.httpclientandroidlib.client.entity.GzipCompressingEntity;
-import ch.boye.httpclientandroidlib.client.methods.HttpDelete;
-import ch.boye.httpclientandroidlib.client.methods.HttpGet;
-import ch.boye.httpclientandroidlib.client.methods.HttpPatch;
-import ch.boye.httpclientandroidlib.client.methods.HttpPost;
-import ch.boye.httpclientandroidlib.client.methods.HttpPut;
-import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
-import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
-import ch.boye.httpclientandroidlib.client.protocol.ClientContext;
-import ch.boye.httpclientandroidlib.conn.ClientConnectionManager;
-import ch.boye.httpclientandroidlib.conn.scheme.PlainSocketFactory;
-import ch.boye.httpclientandroidlib.conn.scheme.Scheme;
-import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry;
-import ch.boye.httpclientandroidlib.conn.ssl.SSLSocketFactory;
-import ch.boye.httpclientandroidlib.entity.StringEntity;
-import ch.boye.httpclientandroidlib.impl.client.BasicAuthCache;
-import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
-import ch.boye.httpclientandroidlib.impl.conn.tsccm.ThreadSafeClientConnManager;
-import ch.boye.httpclientandroidlib.params.HttpConnectionParams;
-import ch.boye.httpclientandroidlib.params.HttpParams;
-import ch.boye.httpclientandroidlib.params.HttpProtocolParams;
-import ch.boye.httpclientandroidlib.protocol.BasicHttpContext;
-import ch.boye.httpclientandroidlib.protocol.HttpContext;
-import ch.boye.httpclientandroidlib.util.EntityUtils;
-
-/**
- * Provide simple HTTP access to a Sync server or similar.
- * Implements Basic Auth by asking its delegate for credentials.
- * Communicates with a ResourceDelegate to asynchronously return responses and errors.
- * Exposes simple get/post/put/delete methods.
- */
-@SuppressWarnings("deprecation")
-public class BaseResource implements Resource {
- private static final String ANDROID_LOOPBACK_IP = "10.0.2.2";
-
- private static final int MAX_TOTAL_CONNECTIONS = 20;
- private static final int MAX_CONNECTIONS_PER_ROUTE = 10;
-
- private boolean retryOnFailedRequest = true;
-
- public static boolean rewriteLocalhost = true;
-
- private static final String LOG_TAG = "BaseResource";
-
- protected final URI uri;
- protected BasicHttpContext context;
- protected DefaultHttpClient client;
- public ResourceDelegate delegate;
- protected HttpRequestBase request;
- public final String charset = "utf-8";
-
- private boolean shouldGzipCompress = false;
- // A hint whether uploaded payloads are chunked. Default true to use GzipCompressingEntity, which is built-in functionality.
- private boolean shouldChunkUploadsHint = true;
-
- /**
- * We have very few writes (observers tend to be installed around sync
- * sessions) and many iterations (every HTTP request iterates observers), so
- * CopyOnWriteArrayList is a reasonable choice.
- */
- protected static final CopyOnWriteArrayList<WeakReference<HttpResponseObserver>>
- httpResponseObservers = new CopyOnWriteArrayList<>();
-
- public BaseResource(String uri) throws URISyntaxException {
- this(uri, rewriteLocalhost);
- }
-
- public BaseResource(URI uri) {
- this(uri, rewriteLocalhost);
- }
-
- public BaseResource(String uri, boolean rewrite) throws URISyntaxException {
- this(new URI(uri), rewrite);
- }
-
- public BaseResource(URI uri, boolean rewrite) {
- if (uri == null) {
- throw new IllegalArgumentException("uri must not be null");
- }
- if (rewrite && "localhost".equals(uri.getHost())) {
- // Rewrite localhost URIs to refer to the special Android emulator loopback passthrough interface.
- Logger.debug(LOG_TAG, "Rewriting " + uri + " to point to " + ANDROID_LOOPBACK_IP + ".");
- try {
- this.uri = new URI(uri.getScheme(), uri.getUserInfo(), ANDROID_LOOPBACK_IP, uri.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment());
- } catch (URISyntaxException e) {
- Logger.error(LOG_TAG, "Got error rewriting URI for Android emulator.", e);
- throw new IllegalArgumentException("Invalid URI", e);
- }
- } else {
- this.uri = uri;
- }
- }
-
- public static void addHttpResponseObserver(HttpResponseObserver newHttpResponseObserver) {
- if (newHttpResponseObserver == null) {
- return;
- }
- httpResponseObservers.add(new WeakReference<HttpResponseObserver>(newHttpResponseObserver));
- }
-
- public static boolean isHttpResponseObserver(HttpResponseObserver httpResponseObserver) {
- for (WeakReference<HttpResponseObserver> weakReference : httpResponseObservers) {
- HttpResponseObserver innerHttpResponseObserver = weakReference.get();
- if (innerHttpResponseObserver == httpResponseObserver) {
- return true;
- }
- }
- return false;
- }
-
- public static boolean removeHttpResponseObserver(HttpResponseObserver httpResponseObserver) {
- for (WeakReference<HttpResponseObserver> weakReference : httpResponseObservers) {
- HttpResponseObserver innerHttpResponseObserver = weakReference.get();
- if (innerHttpResponseObserver == httpResponseObserver) {
- // It's safe to mutate the observers while iterating.
- httpResponseObservers.remove(weakReference);
- return true;
- }
- }
- return false;
- }
-
- @Override
- public URI getURI() {
- return this.uri;
- }
-
- @Override
- public String getURIString() {
- return this.uri.toString();
- }
-
- @Override
- public String getHostname() {
- return this.getURI().getHost();
- }
-
- /**
- * Causes the Resource to compress the uploaded entity payload in requests with payloads (e.g. post, put)
- * @param shouldCompress true if the entity should be compressed, false otherwise
- */
- public void setShouldCompressUploadedEntity(final boolean shouldCompress) {
- shouldGzipCompress = shouldCompress;
- }
-
- /**
- * Causes the Resource to chunk the uploaded entity payload in requests with payloads (e.g. post, put).
- * Note: this flag is only a hint - chunking is not guaranteed.
- *
- * Chunking is currently supported with gzip compression.
- *
- * @param shouldChunk true if the transfer should be chunked, false otherwise
- */
- public void setShouldChunkUploadsHint(final boolean shouldChunk) {
- shouldChunkUploadsHint = shouldChunk;
- }
-
- private HttpEntity getMaybeCompressedEntity(final HttpEntity entity) {
- if (!shouldGzipCompress) {
- return entity;
- }
-
- return shouldChunkUploadsHint ? new GzipCompressingEntity(entity) : new GzipNonChunkedCompressingEntity(entity);
- }
-
- /**
- * This shuts up HttpClient, which will otherwise debug log about there
- * being no auth cache in the context.
- */
- private static void addAuthCacheToContext(HttpUriRequest request, HttpContext context) {
- AuthCache authCache = new BasicAuthCache(); // Not thread safe.
- context.setAttribute(ClientContext.AUTH_CACHE, authCache);
- }
-
- /**
- * Invoke this after delegate and request have been set.
- * @throws NoSuchAlgorithmException
- * @throws KeyManagementException
- */
- protected void prepareClient() throws KeyManagementException, NoSuchAlgorithmException, GeneralSecurityException {
- context = new BasicHttpContext();
-
- // We could reuse these client instances, except that we mess around
- // with their parameters… so we'd need a pool of some kind.
- client = new DefaultHttpClient(getConnectionManager());
-
- // TODO: Eventually we should use Apache HttpAsyncClient. It's not out of alpha yet.
- // Until then, we synchronously make the request, then invoke our delegate's callback.
- AuthHeaderProvider authHeaderProvider = delegate.getAuthHeaderProvider();
- if (authHeaderProvider != null) {
- Header authHeader = authHeaderProvider.getAuthHeader(request, context, client);
- if (authHeader != null) {
- request.addHeader(authHeader);
- Logger.debug(LOG_TAG, "Added auth header.");
- }
- }
-
- addAuthCacheToContext(request, context);
-
- HttpParams params = client.getParams();
- HttpConnectionParams.setConnectionTimeout(params, delegate.connectionTimeout());
- HttpConnectionParams.setSoTimeout(params, delegate.socketTimeout());
- HttpConnectionParams.setStaleCheckingEnabled(params, false);
- HttpProtocolParams.setContentCharset(params, charset);
- HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
- final String userAgent = delegate.getUserAgent();
- if (userAgent != null) {
- HttpProtocolParams.setUserAgent(params, userAgent);
- }
- delegate.addHeaders(request, client);
- }
-
- private static final Object connManagerMonitor = new Object();
- private static ClientConnectionManager connManager;
-
- // Call within a synchronized block on connManagerMonitor.
- private static ClientConnectionManager enableTLSConnectionManager() throws KeyManagementException, NoSuchAlgorithmException {
- SSLContext sslContext = SSLContext.getInstance("TLS");
- sslContext.init(null, null, new SecureRandom());
-
- Logger.debug(LOG_TAG, "Using protocols and cipher suites for Android API " + android.os.Build.VERSION.SDK_INT);
- SSLSocketFactory sf = new SSLSocketFactory(sslContext, GlobalConstants.DEFAULT_PROTOCOLS, GlobalConstants.DEFAULT_CIPHER_SUITES, null);
- SchemeRegistry schemeRegistry = new SchemeRegistry();
- schemeRegistry.register(new Scheme("https", 443, sf));
- schemeRegistry.register(new Scheme("http", 80, new PlainSocketFactory()));
- ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(schemeRegistry);
-
- cm.setMaxTotal(MAX_TOTAL_CONNECTIONS);
- cm.setDefaultMaxPerRoute(MAX_CONNECTIONS_PER_ROUTE);
- connManager = cm;
- return cm;
- }
-
- public static ClientConnectionManager getConnectionManager() throws KeyManagementException, NoSuchAlgorithmException
- {
- // TODO: shutdown.
- synchronized (connManagerMonitor) {
- if (connManager != null) {
- return connManager;
- }
- return enableTLSConnectionManager();
- }
- }
-
- /**
- * Do some cleanup, so we don't need the stale connection check.
- */
- public static void closeExpiredConnections() {
- ClientConnectionManager connectionManager;
- synchronized (connManagerMonitor) {
- connectionManager = connManager;
- }
- if (connectionManager == null) {
- return;
- }
- Logger.trace(LOG_TAG, "Closing expired connections.");
- connectionManager.closeExpiredConnections();
- }
-
- public static void shutdownConnectionManager() {
- ClientConnectionManager connectionManager;
- synchronized (connManagerMonitor) {
- connectionManager = connManager;
- connManager = null;
- }
- if (connectionManager == null) {
- return;
- }
- Logger.debug(LOG_TAG, "Shutting down connection manager.");
- connectionManager.shutdown();
- }
-
- private void execute() {
- HttpResponse response;
- try {
- response = client.execute(request, context);
- Logger.debug(LOG_TAG, "Response: " + response.getStatusLine().toString());
- } catch (ClientProtocolException e) {
- delegate.handleHttpProtocolException(e);
- return;
- } catch (IOException e) {
- Logger.debug(LOG_TAG, "I/O exception returned from execute.");
- if (!retryOnFailedRequest) {
- delegate.handleHttpIOException(e);
- } else {
- retryRequest();
- }
- return;
- } catch (Exception e) {
- // Bug 740731: Don't let an exception fall through. Wrapping isn't
- // optimal, but often the exception is treated as an Exception anyway.
- if (!retryOnFailedRequest) {
- // Bug 769671: IOException(Throwable cause) was added only in API level 9.
- final IOException ex = new IOException();
- ex.initCause(e);
- delegate.handleHttpIOException(ex);
- } else {
- retryRequest();
- }
- return;
- }
-
- // Don't retry if the observer or delegate throws!
- for (WeakReference<HttpResponseObserver> weakReference : httpResponseObservers) {
- HttpResponseObserver observer = weakReference.get();
- if (observer != null) {
- observer.observeHttpResponse(request, response);
- }
- }
- delegate.handleHttpResponse(response);
- }
-
- private void retryRequest() {
- // Only retry once.
- retryOnFailedRequest = false;
- Logger.debug(LOG_TAG, "Retrying request...");
- this.execute();
- }
-
- private void go(HttpRequestBase request) {
- if (delegate == null) {
- throw new IllegalArgumentException("No delegate provided.");
- }
- this.request = request;
- try {
- this.prepareClient();
- } catch (KeyManagementException e) {
- Logger.error(LOG_TAG, "Couldn't prepare client.", e);
- delegate.handleTransportException(e);
- return;
- } catch (GeneralSecurityException e) {
- Logger.error(LOG_TAG, "Couldn't prepare client.", e);
- delegate.handleTransportException(e);
- return;
- } catch (Exception e) {
- // Bug 740731: Don't let an exception fall through. Wrapping isn't
- // optimal, but often the exception is treated as an Exception anyway.
- delegate.handleTransportException(new GeneralSecurityException(e));
- return;
- }
- this.execute();
- }
-
- @Override
- public void get() {
- Logger.debug(LOG_TAG, "HTTP GET " + this.uri.toASCIIString());
- this.go(new HttpGet(this.uri));
- }
-
- /**
- * Perform an HTTP GET as with {@link BaseResource#get()}, returning only
- * after callbacks have been invoked.
- */
- public void getBlocking() {
- // Until we use the asynchronous Apache HttpClient, we can simply call
- // through.
- this.get();
- }
-
- @Override
- public void delete() {
- Logger.debug(LOG_TAG, "HTTP DELETE " + this.uri.toASCIIString());
- this.go(new HttpDelete(this.uri));
- }
-
- @Override
- public void post(HttpEntity body) {
- Logger.debug(LOG_TAG, "HTTP POST " + this.uri.toASCIIString());
- body = getMaybeCompressedEntity(body);
- HttpPost request = new HttpPost(this.uri);
- request.setEntity(body);
- this.go(request);
- }
-
- @Override
- public void patch(HttpEntity body) {
- Logger.debug(LOG_TAG, "HTTP PATCH " + this.uri.toASCIIString());
- body = getMaybeCompressedEntity(body);
- HttpPatch request = new HttpPatch(this.uri);
- request.setEntity(body);
- this.go(request);
- }
-
- @Override
- public void put(HttpEntity body) {
- Logger.debug(LOG_TAG, "HTTP PUT " + this.uri.toASCIIString());
- body = getMaybeCompressedEntity(body);
- HttpPut request = new HttpPut(this.uri);
- request.setEntity(body);
- this.go(request);
- }
-
- protected static StringEntity stringEntityWithContentTypeApplicationJSON(String s) {
- StringEntity e = new StringEntity(s, "UTF-8");
- e.setContentType("application/json");
- return e;
- }
-
- /**
- * Helper for turning a JSON object into a payload.
- * @throws UnsupportedEncodingException
- */
- protected static StringEntity jsonEntity(JSONObject body) {
- return stringEntityWithContentTypeApplicationJSON(body.toJSONString());
- }
-
- /**
- * Helper for turning an extended JSON object into a payload.
- * @throws UnsupportedEncodingException
- */
- protected static StringEntity jsonEntity(ExtendedJSONObject body) {
- return stringEntityWithContentTypeApplicationJSON(body.toJSONString());
- }
-
- /**
- * Helper for turning a JSON array into a payload.
- * @throws UnsupportedEncodingException
- */
- protected static HttpEntity jsonEntity(JSONArray toPOST) throws UnsupportedEncodingException {
- return stringEntityWithContentTypeApplicationJSON(toPOST.toJSONString());
- }
-
- /**
- * Best-effort attempt to ensure that the entity has been fully consumed and
- * that the underlying stream has been closed.
- *
- * This releases the connection back to the connection pool.
- *
- * @param entity The HttpEntity to be consumed.
- */
- public static void consumeEntity(HttpEntity entity) {
- try {
- EntityUtils.consume(entity);
- } catch (IOException e) {
- // Doesn't matter.
- }
- }
-
- /**
- * Best-effort attempt to ensure that the entity corresponding to the given
- * HTTP response has been fully consumed and that the underlying stream has
- * been closed.
- *
- * This releases the connection back to the connection pool.
- *
- * @param response
- * The HttpResponse to be consumed.
- */
- public static void consumeEntity(HttpResponse response) {
- if (response == null) {
- return;
- }
- try {
- EntityUtils.consume(response.getEntity());
- } catch (IOException e) {
- }
- }
-
- /**
- * Best-effort attempt to ensure that the entity corresponding to the given
- * Sync storage response has been fully consumed and that the underlying
- * stream has been closed.
- *
- * This releases the connection back to the connection pool.
- *
- * @param response
- * The SyncStorageResponse to be consumed.
- */
- public static void consumeEntity(SyncStorageResponse response) {
- if (response.httpResponse() == null) {
- return;
- }
- consumeEntity(response.httpResponse());
- }
-
- /**
- * Best-effort attempt to ensure that the reader has been fully consumed, so
- * that the underlying stream will be closed.
- *
- * This should allow the connection to be released back to the connection pool.
- *
- * @param reader The BufferedReader to be consumed.
- */
- public static void consumeReader(BufferedReader reader) {
- try {
- reader.close();
- } catch (IOException e) {
- // Do nothing.
- }
- }
-
- public void post(JSONArray jsonArray) throws UnsupportedEncodingException {
- post(jsonEntity(jsonArray));
- }
-
- public void put(JSONObject jsonObject) throws UnsupportedEncodingException {
- put(jsonEntity(jsonObject));
- }
-
- public void put(ExtendedJSONObject o) {
- put(jsonEntity(o));
- }
-
- public void post(ExtendedJSONObject o) {
- post(jsonEntity(o));
- }
-
- /**
- * Perform an HTTP POST as with {@link BaseResource#post(ExtendedJSONObject)}, returning only
- * after callbacks have been invoked.
- */
- public void postBlocking(final ExtendedJSONObject o) {
- // Until we use the asynchronous Apache HttpClient, we can simply call
- // through.
- post(jsonEntity(o));
- }
-
- public void post(JSONObject jsonObject) throws UnsupportedEncodingException {
- post(jsonEntity(jsonObject));
- }
-
- public void patch(JSONArray jsonArray) throws UnsupportedEncodingException {
- patch(jsonEntity(jsonArray));
- }
-
- public void patch(ExtendedJSONObject o) {
- patch(jsonEntity(o));
- }
-
- public void patch(JSONObject jsonObject) throws UnsupportedEncodingException {
- patch(jsonEntity(jsonObject));
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BaseResourceDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BaseResourceDelegate.java
deleted file mode 100644
index 84ae7a3d5..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BaseResourceDelegate.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
-import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
-
-/**
- * Shared abstract class for resource delegate that use the same timeouts
- * and no credentials.
- *
- * @author rnewman
- *
- */
-public abstract class BaseResourceDelegate implements ResourceDelegate {
- public static int connectionTimeoutInMillis = 1000 * 30; // Wait 30s for a connection to open.
- public static int socketTimeoutInMillis = 1000 * 2 * 60; // Wait 2 minutes for data.
-
- protected Resource resource;
- public BaseResourceDelegate(Resource resource) {
- this.resource = resource;
- }
-
- @Override
- public int connectionTimeout() {
- return connectionTimeoutInMillis;
- }
-
- @Override
- public int socketTimeout() {
- return socketTimeoutInMillis;
- }
-
- @Override
- public AuthHeaderProvider getAuthHeaderProvider() {
- return null;
- }
-
- @Override
- public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BasicAuthHeaderProvider.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BasicAuthHeaderProvider.java
deleted file mode 100644
index d8a371ddc..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BasicAuthHeaderProvider.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-import ch.boye.httpclientandroidlib.Header;
-import ch.boye.httpclientandroidlib.auth.Credentials;
-import ch.boye.httpclientandroidlib.auth.UsernamePasswordCredentials;
-import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
-import ch.boye.httpclientandroidlib.impl.auth.BasicScheme;
-import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
-import ch.boye.httpclientandroidlib.protocol.BasicHttpContext;
-
-/**
- * An <code>AuthHeaderProvider</code> that returns an HTTP Basic auth header.
- */
-public class BasicAuthHeaderProvider implements AuthHeaderProvider {
- protected final String credentials;
-
- /**
- * Constructor.
- *
- * @param credentials string in form "user:pass".
- */
- public BasicAuthHeaderProvider(String credentials) {
- this.credentials = credentials;
- }
-
- /**
- * Constructor.
- *
- * @param user username.
- * @param pass password.
- */
- public BasicAuthHeaderProvider(String user, String pass) {
- this(user + ":" + pass);
- }
-
- /**
- * Return a Header object representing an Authentication header for HTTP
- * Basic.
- */
- @Override
- public Header getAuthHeader(HttpRequestBase request, BasicHttpContext context, DefaultHttpClient client) {
- Credentials creds = new UsernamePasswordCredentials(credentials);
-
- // This must be UTF-8 to generate the same Basic Auth headers as desktop for non-ASCII passwords.
- return BasicScheme.authenticate(creds, "UTF-8", false);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BearerAuthHeaderProvider.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BearerAuthHeaderProvider.java
deleted file mode 100644
index d142d50d9..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BearerAuthHeaderProvider.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-/**
- * An <code>AuthHeaderProvider</code> that returns an Authorization header for
- * Bearer tokens in the format expected by a Mozilla Firefox Accounts Profile Server.
- * <p>
- * See <a href="https://github.com/mozilla/fxa-profile-server/blob/master/docs/API.md">https://github.com/mozilla/fxa-profile-server/blob/master/docs/API.md</a>.
- */
-public class BearerAuthHeaderProvider extends AbstractBearerTokenAuthHeaderProvider {
- public BearerAuthHeaderProvider(String token) {
- super(token);
- }
-
- @Override
- protected String getPrefix() {
- return "Bearer";
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BrowserIDAuthHeaderProvider.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BrowserIDAuthHeaderProvider.java
deleted file mode 100644
index 5004673b3..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/BrowserIDAuthHeaderProvider.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-/**
- * An <code>AuthHeaderProvider</code> that returns an Authorization header for
- * BrowserID assertions in the format expected by a Mozilla Services Token
- * Server.
- * <p>
- * See <a href="http://docs.services.mozilla.com/token/apis.html">http://docs.services.mozilla.com/token/apis.html</a>.
- */
-public class BrowserIDAuthHeaderProvider extends AbstractBearerTokenAuthHeaderProvider {
- public BrowserIDAuthHeaderProvider(String assertion) {
- super(assertion);
- }
-
- @Override
- protected String getPrefix() {
- return "BrowserID";
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/ConnectionMonitorThread.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/ConnectionMonitorThread.java
deleted file mode 100644
index 1a2011771..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/ConnectionMonitorThread.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-import org.mozilla.gecko.background.common.log.Logger;
-
-/**
- * Every <code>REAP_INTERVAL</code> milliseconds, wake up
- * and expire any connections that need cleaning up.
- *
- * When we're told to shut down, take the connection manager
- * with us.
- */
-public class ConnectionMonitorThread extends Thread {
- private static final long REAP_INTERVAL = 5000; // 5 seconds.
- private static final String LOG_TAG = "ConnectionMonitorThread";
-
- private volatile boolean stopping;
-
- @Override
- public void run() {
- try {
- while (!stopping) {
- synchronized (this) {
- wait(REAP_INTERVAL);
- BaseResource.closeExpiredConnections();
- }
- }
- } catch (InterruptedException e) {
- Logger.trace(LOG_TAG, "Interrupted.");
- }
- BaseResource.shutdownConnectionManager();
- }
-
- public void shutdown() {
- Logger.debug(LOG_TAG, "ConnectionMonitorThread told to shut down.");
- stopping = true;
- synchronized (this) {
- notifyAll();
- }
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/GzipNonChunkedCompressingEntity.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/GzipNonChunkedCompressingEntity.java
deleted file mode 100644
index 1e238c022..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/GzipNonChunkedCompressingEntity.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-import ch.boye.httpclientandroidlib.HttpEntity;
-import ch.boye.httpclientandroidlib.client.entity.GzipCompressingEntity;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * Wrapping entity that compresses content when {@link #writeTo writing}.
- *
- * This differs from {@link GzipCompressingEntity} in that it does not chunk
- * the sent data, therefore replacing the "Transfer-Encoding" HTTP header with
- * the "Content-Length" header required by some servers.
- *
- * However, to measure the content length, the gzipped content will be temporarily
- * stored in memory so be careful what content you send!
- */
-public class GzipNonChunkedCompressingEntity extends GzipCompressingEntity {
- final int MAX_BUFFER_SIZE_BYTES = 10 * 1000 * 1000; // 10 MB.
-
- private byte[] gzippedContent;
-
- public GzipNonChunkedCompressingEntity(final HttpEntity entity) {
- super(entity);
- }
-
- /**
- * @return content length for gzipped content or -1 if there is an error
- */
- @Override
- public long getContentLength() {
- try {
- initBuffer();
- } catch (final IOException e) {
- // GzipCompressingEntity always returns -1 in which case a 'Content-Length' header is omitted.
- // Presumably, without it the request will fail (either client-side or server-side).
- return -1;
- }
- return gzippedContent.length;
- }
-
- @Override
- public boolean isChunked() {
- // "Content-Length" & chunked encoding are mutually exclusive:
- // https://en.wikipedia.org/wiki/Chunked_transfer_encoding
- return false;
- }
-
- @Override
- public InputStream getContent() throws IOException {
- initBuffer();
- return new ByteArrayInputStream(gzippedContent);
- }
-
- @Override
- public void writeTo(final OutputStream outstream) throws IOException {
- initBuffer();
- outstream.write(gzippedContent);
- }
-
- private void initBuffer() throws IOException {
- if (gzippedContent != null) {
- return;
- }
-
- final long unzippedContentLength = wrappedEntity.getContentLength();
- if (unzippedContentLength > MAX_BUFFER_SIZE_BYTES) {
- throw new IOException(
- "Wrapped entity content length, " + unzippedContentLength + " bytes, exceeds max: " + MAX_BUFFER_SIZE_BYTES);
- }
-
- // The buffer size needed by the gzipped content should be smaller than this,
- // but it's more efficient just to allocate one larger buffer than allocate
- // twice if the gzipped content is too large for the default buffer.
- final ByteArrayOutputStream s = new ByteArrayOutputStream((int) unzippedContentLength);
- try {
- super.writeTo(s);
- } finally {
- s.close();
- }
-
- gzippedContent = s.toByteArray();
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HMACAuthHeaderProvider.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HMACAuthHeaderProvider.java
deleted file mode 100644
index 5314d345b..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HMACAuthHeaderProvider.java
+++ /dev/null
@@ -1,257 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-
-import org.mozilla.apache.commons.codec.binary.Base64;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.Utils;
-
-import ch.boye.httpclientandroidlib.Header;
-import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
-import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
-import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
-import ch.boye.httpclientandroidlib.message.BasicHeader;
-import ch.boye.httpclientandroidlib.protocol.BasicHttpContext;
-
-/**
- * An <code>AuthHeaderProvider</code> that returns an Authorization header for
- * HMAC-SHA1-signed requests in the format expected by Mozilla Services
- * identity-attached services and specified by the MAC Authentication spec, available at
- * <a href="https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac">https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac</a>.
- * <p>
- * See <a href="https://wiki.mozilla.org/Services/Sagrada/ServiceClientFlow#Access">https://wiki.mozilla.org/Services/Sagrada/ServiceClientFlow#Access</a>.
- */
-public class HMACAuthHeaderProvider implements AuthHeaderProvider {
- public static final String LOG_TAG = "HMACAuthHeaderProvider";
-
- public static final int NONCE_LENGTH_IN_BYTES = 8;
-
- public static final String HMAC_SHA1_ALGORITHM = "hmacSHA1";
-
- public final String identifier;
- public final String key;
-
- public HMACAuthHeaderProvider(String identifier, String key) {
- // Validate identifier string. From the MAC Authentication spec:
- // id = "id" "=" string-value
- // string-value = ( <"> plain-string <"> ) / plain-string
- // plain-string = 1*( %x20-21 / %x23-5B / %x5D-7E )
- // We add quotes around the id string, so input identifier must be a plain-string.
- if (identifier == null) {
- throw new IllegalArgumentException("identifier must not be null.");
- }
- if (!isPlainString(identifier)) {
- throw new IllegalArgumentException("identifier must be a plain-string.");
- }
-
- if (key == null) {
- throw new IllegalArgumentException("key must not be null.");
- }
-
- this.identifier = identifier;
- this.key = key;
- }
-
- @Override
- public Header getAuthHeader(HttpRequestBase request, BasicHttpContext context, DefaultHttpClient client) throws GeneralSecurityException {
- long timestamp = System.currentTimeMillis() / 1000;
- String nonce = Base64.encodeBase64String(Utils.generateRandomBytes(NONCE_LENGTH_IN_BYTES));
- String extra = "";
-
- try {
- return getAuthHeader(request, context, client, timestamp, nonce, extra);
- } catch (InvalidKeyException | NoSuchAlgorithmException | UnsupportedEncodingException e) {
- // We lie a little and make every exception a GeneralSecurityException.
- throw new GeneralSecurityException(e);
- }
- }
-
- /**
- * Test if input is a <code>plain-string</code>.
- * <p>
- * A plain-string is defined by the MAC Authentication spec as
- * <code>plain-string = 1*( %x20-21 / %x23-5B / %x5D-7E )</code>.
- *
- * @param input
- * as a String of "US-ASCII" bytes.
- * @return true if input is a <code>plain-string</code>; false otherwise.
- * @throws UnsupportedEncodingException
- */
- protected static boolean isPlainString(String input) {
- if (input == null || input.length() == 0) {
- return false;
- }
-
- byte[] bytes;
- try {
- bytes = input.getBytes("US-ASCII");
- } catch (UnsupportedEncodingException e) {
- // Should never happen.
- Logger.warn(LOG_TAG, "Got exception in isPlainString; returning false.", e);
- return false;
- }
-
- for (byte b : bytes) {
- if ((0x20 <= b && b <= 0x21) || (0x23 <= b && b <= 0x5B) || (0x5D <= b && b <= 0x7E)) {
- continue;
- }
- return false;
- }
-
- return true;
- }
-
- /**
- * Helper function that generates an HTTP Authorization header given
- * additional MAC Authentication specific data.
- *
- * @throws UnsupportedEncodingException
- * @throws NoSuchAlgorithmException
- * @throws InvalidKeyException
- */
- protected Header getAuthHeader(HttpRequestBase request, BasicHttpContext context, DefaultHttpClient client,
- long timestamp, String nonce, String extra)
- throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException {
- // Validate timestamp. From the MAC Authentication spec:
- // timestamp = 1*DIGIT
- // This is equivalent to timestamp >= 0.
- if (timestamp < 0) {
- throw new IllegalArgumentException("timestamp must contain only [0-9].");
- }
-
- // Validate nonce string. From the MAC Authentication spec:
- // nonce = "nonce" "=" string-value
- // string-value = ( <"> plain-string <"> ) / plain-string
- // plain-string = 1*( %x20-21 / %x23-5B / %x5D-7E )
- // We add quotes around the nonce string, so input nonce must be a plain-string.
- if (nonce == null) {
- throw new IllegalArgumentException("nonce must not be null.");
- }
- if (nonce.length() == 0) {
- throw new IllegalArgumentException("nonce must not be empty.");
- }
- if (!isPlainString(nonce)) {
- throw new IllegalArgumentException("nonce must be a plain-string.");
- }
-
- // Validate extra string. From the MAC Authentication spec:
- // ext = "ext" "=" string-value
- // string-value = ( <"> plain-string <"> ) / plain-string
- // plain-string = 1*( %x20-21 / %x23-5B / %x5D-7E )
- // We add quotes around the extra string, so input extra must be a plain-string.
- // We break the spec by allowing ext to be an empty string, i.e. to match 0*(...).
- if (extra == null) {
- throw new IllegalArgumentException("extra must not be null.");
- }
- if (extra.length() > 0 && !isPlainString(extra)) {
- throw new IllegalArgumentException("extra must be a plain-string.");
- }
-
- String requestString = getRequestString(request, timestamp, nonce, extra);
- String macString = getSignature(requestString, this.key);
-
- String h = "MAC id=\"" + this.identifier + "\", " +
- "ts=\"" + timestamp + "\", " +
- "nonce=\"" + nonce + "\", " +
- "mac=\"" + macString + "\"";
-
- if (extra != null) {
- h += ", ext=\"" + extra + "\"";
- }
-
- Header header = new BasicHeader("Authorization", h);
-
- return header;
- }
-
- protected static byte[] sha1(byte[] message, byte[] key)
- throws NoSuchAlgorithmException, InvalidKeyException {
-
- SecretKeySpec keySpec = new SecretKeySpec(key, HMAC_SHA1_ALGORITHM);
-
- Mac hasher = Mac.getInstance(HMAC_SHA1_ALGORITHM);
- hasher.init(keySpec);
- hasher.update(message);
-
- byte[] hmac = hasher.doFinal();
-
- return hmac;
- }
-
- /**
- * Sign an HMAC request string.
- *
- * @param requestString to sign.
- * @param key as <code>String</code>.
- * @return signature as base-64 encoded string.
- * @throws InvalidKeyException
- * @throws NoSuchAlgorithmException
- * @throws UnsupportedEncodingException
- */
- protected static String getSignature(String requestString, String key)
- throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException {
- String macString = Base64.encodeBase64String(sha1(requestString.getBytes("UTF-8"), key.getBytes("UTF-8")));
-
- return macString;
- }
-
- /**
- * Generate an HMAC request string.
- * <p>
- * This method trusts its inputs to be valid as per the MAC Authentication spec.
- *
- * @param request HTTP request.
- * @param timestamp to use.
- * @param nonce to use.
- * @param extra to use.
- * @return request string.
- */
- protected static String getRequestString(HttpUriRequest request, long timestamp, String nonce, String extra) {
- String method = request.getMethod().toUpperCase();
-
- URI uri = request.getURI();
- String host = uri.getHost();
-
- String path = uri.getRawPath();
- if (uri.getRawQuery() != null) {
- path += "?";
- path += uri.getRawQuery();
- }
- if (uri.getRawFragment() != null) {
- path += "#";
- path += uri.getRawFragment();
- }
-
- int port = uri.getPort();
- String scheme = uri.getScheme();
- if (port != -1) {
- } else if ("http".equalsIgnoreCase(scheme)) {
- port = 80;
- } else if ("https".equalsIgnoreCase(scheme)) {
- port = 443;
- } else {
- throw new IllegalArgumentException("Unsupported URI scheme: " + scheme + ".");
- }
-
- String requestString = timestamp + "\n" +
- nonce + "\n" +
- method + "\n" +
- path + "\n" +
- host + "\n" +
- port + "\n" +
- extra + "\n";
-
- return requestString;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HandleProgressException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HandleProgressException.java
deleted file mode 100644
index 27ec74b66..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HandleProgressException.java
+++ /dev/null
@@ -1,15 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class HandleProgressException extends SyncException {
- private static final long serialVersionUID = -4444933937013161059L;
-
- public HandleProgressException(Exception ex) {
- super(ex);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HawkAuthHeaderProvider.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HawkAuthHeaderProvider.java
deleted file mode 100644
index 2bdd5604a..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HawkAuthHeaderProvider.java
+++ /dev/null
@@ -1,403 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Locale;
-
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-
-import org.mozilla.apache.commons.codec.binary.Base64;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.Utils;
-
-import ch.boye.httpclientandroidlib.Header;
-import ch.boye.httpclientandroidlib.HttpEntity;
-import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest;
-import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
-import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
-import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
-import ch.boye.httpclientandroidlib.message.BasicHeader;
-import ch.boye.httpclientandroidlib.protocol.BasicHttpContext;
-
-/**
- * An <code>AuthHeaderProvider</code> that returns an Authorization header for
- * Hawk: <a href="https://github.com/hueniverse/hawk">https://github.com/hueniverse/hawk</a>.
- *
- * Hawk is an HTTP authentication scheme using a message authentication code
- * (MAC) algorithm to provide partial HTTP request cryptographic verification.
- * Hawk is the successor to the HMAC authentication scheme.
- */
-public class HawkAuthHeaderProvider implements AuthHeaderProvider {
- public static final String LOG_TAG = HawkAuthHeaderProvider.class.getSimpleName();
-
- public static final int HAWK_HEADER_VERSION = 1;
-
- protected static final int NONCE_LENGTH_IN_BYTES = 8;
- protected static final String HMAC_SHA256_ALGORITHM = "hmacSHA256";
-
- protected final String id;
- protected final byte[] key;
- protected final boolean includePayloadHash;
- protected final long skewSeconds;
-
- /**
- * Create a Hawk Authorization header provider.
- * <p>
- * Hawk specifies no mechanism by which a client receives an
- * identifier-and-key pair from the server.
- * <p>
- * Hawk requests can include a payload verification hash with requests that
- * enclose an entity (PATCH, POST, and PUT requests). <b>You should default
- * to including the payload verification hash<b> unless you have a good reason
- * not to -- the server can always ignore payload verification hashes provided
- * by the client.
- *
- * @param id
- * to name requests with.
- * @param key
- * to sign request with.
- *
- * @param includePayloadHash
- * true if payload verification hash should be included in signed
- * request header. See <a href="https://github.com/hueniverse/hawk#payload-validation">https://github.com/hueniverse/hawk#payload-validation</a>.
- *
- * @param skewSeconds
- * a number of seconds by which to skew the current time when
- * computing a header.
- */
- public HawkAuthHeaderProvider(String id, byte[] key, boolean includePayloadHash, long skewSeconds) {
- if (id == null) {
- throw new IllegalArgumentException("id must not be null");
- }
- if (key == null) {
- throw new IllegalArgumentException("key must not be null");
- }
- this.id = id;
- this.key = key;
- this.includePayloadHash = includePayloadHash;
- this.skewSeconds = skewSeconds;
- }
-
- /**
- * @return the current time in milliseconds.
- */
- @SuppressWarnings("static-method")
- protected long now() {
- return System.currentTimeMillis();
- }
-
- /**
- * @return the current time in seconds, adjusted for skew. This should
- * approximate the server's timestamp.
- */
- protected long getTimestampSeconds() {
- return (now() / 1000) + skewSeconds;
- }
-
- @Override
- public Header getAuthHeader(HttpRequestBase request, BasicHttpContext context, DefaultHttpClient client) throws GeneralSecurityException {
- long timestamp = getTimestampSeconds();
- String nonce = Base64.encodeBase64String(Utils.generateRandomBytes(NONCE_LENGTH_IN_BYTES));
- String extra = "";
-
- try {
- return getAuthHeader(request, context, client, timestamp, nonce, extra, this.includePayloadHash);
- } catch (Exception e) {
- // We lie a little and make every exception a GeneralSecurityException.
- throw new GeneralSecurityException(e);
- }
- }
-
- /**
- * Helper function that generates an HTTP Authorization: Hawk header given
- * additional Hawk specific data.
- *
- * @throws NoSuchAlgorithmException
- * @throws InvalidKeyException
- * @throws IOException
- */
- protected Header getAuthHeader(HttpRequestBase request, BasicHttpContext context, DefaultHttpClient client,
- long timestamp, String nonce, String extra, boolean includePayloadHash)
- throws InvalidKeyException, NoSuchAlgorithmException, IOException {
- if (timestamp < 0) {
- throw new IllegalArgumentException("timestamp must contain only [0-9].");
- }
-
- if (nonce == null) {
- throw new IllegalArgumentException("nonce must not be null.");
- }
- if (nonce.length() == 0) {
- throw new IllegalArgumentException("nonce must not be empty.");
- }
-
- String payloadHash = null;
- if (includePayloadHash) {
- payloadHash = getPayloadHashString(request);
- } else {
- Logger.debug(LOG_TAG, "Configured to not include payload hash for this request.");
- }
-
- String app = null;
- String dlg = null;
- String requestString = getRequestString(request, "header", timestamp, nonce, payloadHash, extra, app, dlg);
- String macString = getSignature(requestString.getBytes("UTF-8"), this.key);
-
- StringBuilder sb = new StringBuilder();
- sb.append("Hawk id=\"");
- sb.append(this.id);
- sb.append("\", ");
- sb.append("ts=\"");
- sb.append(timestamp);
- sb.append("\", ");
- sb.append("nonce=\"");
- sb.append(nonce);
- sb.append("\", ");
- if (payloadHash != null) {
- sb.append("hash=\"");
- sb.append(payloadHash);
- sb.append("\", ");
- }
- if (extra != null && extra.length() > 0) {
- sb.append("ext=\"");
- sb.append(escapeExtraHeaderAttribute(extra));
- sb.append("\", ");
- }
- sb.append("mac=\"");
- sb.append(macString);
- sb.append("\"");
-
- return new BasicHeader("Authorization", sb.toString());
- }
-
- /**
- * Get the payload verification hash for the given request, if possible.
- * <p>
- * Returns null if the request does not enclose an entity (is not an HTTP
- * PATCH, POST, or PUT). Throws if the payload verification hash cannot be
- * computed.
- *
- * @param request
- * to compute hash for.
- * @return verification hash, or null if the request does not enclose an entity.
- * @throws IllegalArgumentException if the request does not enclose a valid non-null entity.
- * @throws UnsupportedEncodingException
- * @throws NoSuchAlgorithmException
- * @throws IOException
- */
- protected static String getPayloadHashString(HttpRequestBase request)
- throws UnsupportedEncodingException, NoSuchAlgorithmException, IOException, IllegalArgumentException {
- final boolean shouldComputePayloadHash = request instanceof HttpEntityEnclosingRequest;
- if (!shouldComputePayloadHash) {
- Logger.debug(LOG_TAG, "Not computing payload verification hash for non-enclosing request.");
- return null;
- }
- if (!(request instanceof HttpEntityEnclosingRequest)) {
- throw new IllegalArgumentException("Cannot compute payload verification hash for enclosing request without an entity");
- }
- final HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
- if (entity == null) {
- throw new IllegalArgumentException("Cannot compute payload verification hash for enclosing request with a null entity");
- }
- return Base64.encodeBase64String(getPayloadHash(entity));
- }
-
- /**
- * Escape the user-provided extra string for the ext="" header attribute.
- * <p>
- * Hawk escapes the header ext="" attribute differently than it does the extra
- * line in the normalized request string.
- * <p>
- * See <a href="https://github.com/hueniverse/hawk/blob/871cc597973110900467bd3dfb84a3c892f678fb/lib/browser.js#L385">https://github.com/hueniverse/hawk/blob/871cc597973110900467bd3dfb84a3c892f678fb/lib/browser.js#L385</a>.
- *
- * @param extra to escape.
- * @return extra escaped for the ext="" header attribute.
- */
- protected static String escapeExtraHeaderAttribute(String extra) {
- return extra.replaceAll("\\\\", "\\\\").replaceAll("\"", "\\\"");
- }
-
- /**
- * Escape the user-provided extra string for inserting into the normalized
- * request string.
- * <p>
- * Hawk escapes the header ext="" attribute differently than it does the extra
- * line in the normalized request string.
- * <p>
- * See <a href="https://github.com/hueniverse/hawk/blob/871cc597973110900467bd3dfb84a3c892f678fb/lib/crypto.js#L67">https://github.com/hueniverse/hawk/blob/871cc597973110900467bd3dfb84a3c892f678fb/lib/crypto.js#L67</a>.
- *
- * @param extra to escape.
- * @return extra escaped for the normalized request string.
- */
- protected static String escapeExtraString(String extra) {
- return extra.replaceAll("\\\\", "\\\\").replaceAll("\n", "\\n");
- }
-
- /**
- * Return the content type with no parameters (pieces following ;).
- *
- * @param contentTypeHeader to interrogate.
- * @return base content type.
- */
- protected static String getBaseContentType(Header contentTypeHeader) {
- if (contentTypeHeader == null) {
- throw new IllegalArgumentException("contentTypeHeader must not be null.");
- }
- String contentType = contentTypeHeader.getValue();
- if (contentType == null) {
- throw new IllegalArgumentException("contentTypeHeader value must not be null.");
- }
- int index = contentType.indexOf(";");
- if (index < 0) {
- return contentType.trim();
- }
- return contentType.substring(0, index).trim();
- }
-
- /**
- * Generate the SHA-256 hash of a normalized Hawk payload generated from an
- * HTTP entity.
- * <p>
- * <b>Warning:</b> the entity <b>must</b> be repeatable. If it is not, this
- * code throws an <code>IllegalArgumentException</code>.
- * <p>
- * This is under-specified; the code here was reverse engineered from the code
- * at
- * <a href="https://github.com/hueniverse/hawk/blob/871cc597973110900467bd3dfb84a3c892f678fb/lib/crypto.js#L81">https://github.com/hueniverse/hawk/blob/871cc597973110900467bd3dfb84a3c892f678fb/lib/crypto.js#L81</a>.
- * @param entity to normalize and hash.
- * @return hash.
- * @throws IllegalArgumentException if entity is not repeatable.
- */
- protected static byte[] getPayloadHash(HttpEntity entity) throws UnsupportedEncodingException, IOException, NoSuchAlgorithmException {
- if (!entity.isRepeatable()) {
- throw new IllegalArgumentException("entity must be repeatable");
- }
- final MessageDigest digest = MessageDigest.getInstance("SHA-256");
- digest.update(("hawk." + HAWK_HEADER_VERSION + ".payload\n").getBytes("UTF-8"));
- digest.update(getBaseContentType(entity.getContentType()).getBytes("UTF-8"));
- digest.update("\n".getBytes("UTF-8"));
- InputStream stream = entity.getContent();
- try {
- int numRead;
- byte[] buffer = new byte[4096];
- while (-1 != (numRead = stream.read(buffer))) {
- if (numRead > 0) {
- digest.update(buffer, 0, numRead);
- }
- }
- digest.update("\n".getBytes("UTF-8")); // Trailing newline is specified by Hawk.
- return digest.digest();
- } finally {
- stream.close();
- }
- }
-
- /**
- * Generate a normalized Hawk request string. This is under-specified; the
- * code here was reverse engineered from the code at
- * <a href="https://github.com/hueniverse/hawk/blob/871cc597973110900467bd3dfb84a3c892f678fb/lib/crypto.js#L55">https://github.com/hueniverse/hawk/blob/871cc597973110900467bd3dfb84a3c892f678fb/lib/crypto.js#L55</a>.
- * <p>
- * This method trusts its inputs to be valid.
- */
- protected static String getRequestString(HttpUriRequest request, String type, long timestamp, String nonce, String hash, String extra, String app, String dlg) {
- String method = request.getMethod().toUpperCase(Locale.US);
-
- URI uri = request.getURI();
- String host = uri.getHost();
-
- String path = uri.getRawPath();
- if (uri.getRawQuery() != null) {
- path += "?";
- path += uri.getRawQuery();
- }
- if (uri.getRawFragment() != null) {
- path += "#";
- path += uri.getRawFragment();
- }
-
- int port = uri.getPort();
- String scheme = uri.getScheme();
- if (port != -1) {
- } else if ("http".equalsIgnoreCase(scheme)) {
- port = 80;
- } else if ("https".equalsIgnoreCase(scheme)) {
- port = 443;
- } else {
- throw new IllegalArgumentException("Unsupported URI scheme: " + scheme + ".");
- }
-
- StringBuilder sb = new StringBuilder();
- sb.append("hawk.");
- sb.append(HAWK_HEADER_VERSION);
- sb.append('.');
- sb.append(type);
- sb.append('\n');
- sb.append(timestamp);
- sb.append('\n');
- sb.append(nonce);
- sb.append('\n');
- sb.append(method);
- sb.append('\n');
- sb.append(path);
- sb.append('\n');
- sb.append(host);
- sb.append('\n');
- sb.append(port);
- sb.append('\n');
- if (hash != null) {
- sb.append(hash);
- }
- sb.append("\n");
- if (extra != null && extra.length() > 0) {
- sb.append(escapeExtraString(extra));
- }
- sb.append("\n");
- if (app != null) {
- sb.append(app);
- sb.append("\n");
- if (dlg != null) {
- sb.append(dlg);
- }
- sb.append("\n");
- }
-
- return sb.toString();
- }
-
- protected static byte[] hmacSha256(byte[] message, byte[] key)
- throws NoSuchAlgorithmException, InvalidKeyException {
-
- SecretKeySpec keySpec = new SecretKeySpec(key, HMAC_SHA256_ALGORITHM);
-
- Mac hasher = Mac.getInstance(HMAC_SHA256_ALGORITHM);
- hasher.init(keySpec);
- hasher.update(message);
-
- return hasher.doFinal();
- }
-
- /**
- * Sign a Hawk request string.
- *
- * @param requestString to sign.
- * @param key as <code>String</code>.
- * @return signature as base-64 encoded string.
- * @throws InvalidKeyException
- * @throws NoSuchAlgorithmException
- * @throws UnsupportedEncodingException
- */
- protected static String getSignature(byte[] requestString, byte[] key)
- throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException {
- return Base64.encodeBase64String(hmacSha256(requestString, key));
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HttpResponseObserver.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HttpResponseObserver.java
deleted file mode 100644
index 24b37a0e6..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/HttpResponseObserver.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
-
-public interface HttpResponseObserver {
- /**
- * Observe an HTTP response.
- * @param request
- * The <code>HttpUriRequest<code> that elicited the response.
- *
- * @param response
- * The <code>HttpResponse</code> to observe.
- */
- public void observeHttpResponse(HttpUriRequest request, HttpResponse response);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/MozResponse.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/MozResponse.java
deleted file mode 100644
index 3f76f929f..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/MozResponse.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.util.Scanner;
-
-import org.json.simple.JSONArray;
-import org.json.simple.parser.JSONParser;
-import org.json.simple.parser.ParseException;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.NonArrayJSONException;
-import org.mozilla.gecko.sync.NonObjectJSONException;
-
-import ch.boye.httpclientandroidlib.Header;
-import ch.boye.httpclientandroidlib.HttpEntity;
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.HttpStatus;
-import ch.boye.httpclientandroidlib.impl.cookie.DateParseException;
-import ch.boye.httpclientandroidlib.impl.cookie.DateUtils;
-
-public class MozResponse {
- private static final String LOG_TAG = "MozResponse";
-
- private static final String HEADER_RETRY_AFTER = "retry-after";
-
- protected HttpResponse response;
- private String body = null;
-
- public HttpResponse httpResponse() {
- return this.response;
- }
-
- public int getStatusCode() {
- return this.response.getStatusLine().getStatusCode();
- }
-
- public boolean wasSuccessful() {
- return this.getStatusCode() == 200;
- }
-
- public boolean isInvalidAuthentication() {
- return this.getStatusCode() == HttpStatus.SC_UNAUTHORIZED;
- }
-
- /**
- * Fetch the content type of the HTTP response body.
- *
- * @return a <code>Header</code> instance, or <code>null</code> if there was
- * no body or no valid Content-Type.
- */
- public Header getContentType() {
- HttpEntity entity = this.response.getEntity();
- if (entity == null) {
- return null;
- }
- return entity.getContentType();
- }
-
- private static boolean missingHeader(String value) {
- return value == null ||
- value.trim().length() == 0;
- }
-
- public String body() throws IllegalStateException, IOException {
- if (body != null) {
- return body;
- }
- final HttpEntity entity = this.response.getEntity();
- if (entity == null) {
- body = null;
- return null;
- }
-
- InputStreamReader is = new InputStreamReader(entity.getContent());
- // Oh, Java, you are so evil.
- body = new Scanner(is).useDelimiter("\\A").next();
- return body;
- }
-
- /**
- * Return the body as a <b>non-null</b> <code>ExtendedJSONObject</code>.
- *
- * @return A non-null <code>ExtendedJSONObject</code>.
- *
- * @throws IllegalStateException
- * @throws IOException
- * @throws NonObjectJSONException
- */
- public ExtendedJSONObject jsonObjectBody() throws IllegalStateException, IOException, NonObjectJSONException {
- if (body != null) {
- // Do it from the cached String.
- return new ExtendedJSONObject(body);
- }
-
- HttpEntity entity = this.response.getEntity();
- if (entity == null) {
- throw new IOException("no entity");
- }
-
- InputStream content = entity.getContent();
- try {
- Reader in = new BufferedReader(new InputStreamReader(content, "UTF-8"));
- return new ExtendedJSONObject(in);
- } finally {
- content.close();
- }
- }
-
- public JSONArray jsonArrayBody() throws NonArrayJSONException, IOException {
- final JSONParser parser = new JSONParser();
- try {
- if (body != null) {
- // Do it from the cached String.
- return (JSONArray) parser.parse(body);
- }
-
- final HttpEntity entity = this.response.getEntity();
- if (entity == null) {
- throw new IOException("no entity");
- }
-
- final InputStream content = entity.getContent();
- final Reader in = new BufferedReader(new InputStreamReader(content, "UTF-8"));
- try {
- return (JSONArray) parser.parse(in);
- } finally {
- in.close();
- }
- } catch (ClassCastException | ParseException e) {
- NonArrayJSONException exception = new NonArrayJSONException("value must be a json array");
- exception.initCause(e);
- throw exception;
- }
- }
-
- protected boolean hasHeader(String h) {
- return this.response.containsHeader(h);
- }
-
- public MozResponse(HttpResponse res) {
- response = res;
- }
-
- protected String getNonMissingHeader(String h) {
- if (!this.hasHeader(h)) {
- return null;
- }
-
- final Header header = this.response.getFirstHeader(h);
- final String value = header.getValue();
- if (missingHeader(value)) {
- Logger.warn(LOG_TAG, h + " header present but empty.");
- return null;
- }
- return value;
- }
-
- protected long getLongHeader(String h) throws NumberFormatException {
- final String value = getNonMissingHeader(h);
- if (value == null) {
- return -1L;
- }
- return Long.parseLong(value, 10);
- }
-
- protected int getIntegerHeader(String h) throws NumberFormatException {
- final String value = getNonMissingHeader(h);
- if (value == null) {
- return -1;
- }
- return Integer.parseInt(value, 10);
- }
-
- /**
- * @return A number of seconds, or -1 if the 'Retry-After' header was not present.
- */
- public int retryAfterInSeconds() throws NumberFormatException {
- final String retryAfter = getNonMissingHeader(HEADER_RETRY_AFTER);
- if (retryAfter == null) {
- return -1;
- }
-
- try {
- return Integer.parseInt(retryAfter, 10);
- } catch (NumberFormatException e) {
- // Fall through to try date format.
- }
-
- try {
- final long then = DateUtils.parseDate(retryAfter).getTime();
- final long now = System.currentTimeMillis();
- return (int)((then - now) / 1000); // Convert milliseconds to seconds.
- } catch (DateParseException e) {
- Logger.warn(LOG_TAG, "Retry-After header neither integer nor date: " + retryAfter);
- return -1;
- }
- }
-
- /**
- * @return A number of seconds, or -1 if the 'Backoff' header was not
- * present.
- */
- public int backoffInSeconds() throws NumberFormatException {
- return this.getIntegerHeader("backoff");
- }
-
- public void logResponseBody(final String logTag) {
- if (!Logger.LOG_PERSONAL_INFORMATION) {
- return;
- }
- try {
- Logger.pii(logTag, "Response body: " + body());
- } catch (Throwable e) {
- Logger.debug(logTag, "No response body.");
- }
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/Resource.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/Resource.java
deleted file mode 100644
index ab7b98aff..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/Resource.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-import java.net.URI;
-
-import ch.boye.httpclientandroidlib.HttpEntity;
-
-public interface Resource {
- public abstract URI getURI();
- public abstract String getURIString();
- public abstract String getHostname();
- public abstract void get();
- public abstract void delete();
- public abstract void post(HttpEntity body);
- public abstract void patch(HttpEntity body);
- public abstract void put(HttpEntity body);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/ResourceDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/ResourceDelegate.java
deleted file mode 100644
index 0dea9432b..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/ResourceDelegate.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-import java.io.IOException;
-import java.security.GeneralSecurityException;
-
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.client.ClientProtocolException;
-import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
-import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
-
-/**
- * ResourceDelegate implementers must ensure that HTTP responses
- * are fully consumed to ensure that connections are returned to
- * the pool:
- *
- * EntityUtils.consume(entity);
- * @author rnewman
- *
- */
-public interface ResourceDelegate {
- // Request augmentation.
- AuthHeaderProvider getAuthHeaderProvider();
- void addHeaders(HttpRequestBase request, DefaultHttpClient client);
-
- /**
- * The value of the User-Agent header to include with the request.
- *
- * @return User-Agent header value; null means do not set User-Agent header.
- */
- public String getUserAgent();
-
- // Response handling.
-
- /**
- * Override this to handle an HttpResponse.
- *
- * ResourceDelegate implementers <b>must</b> ensure that HTTP responses are
- * fully consumed to ensure that connections are returned to the pool, for
- * example by calling <code>EntityUtils.consume(response.getEntity())</code>.
- */
- void handleHttpResponse(HttpResponse response);
- void handleHttpProtocolException(ClientProtocolException e);
- void handleHttpIOException(IOException e);
-
- // During preparation.
- void handleTransportException(GeneralSecurityException e);
-
- // Connection parameters.
- int connectionTimeout();
- int socketTimeout();
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SRPConstants.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SRPConstants.java
deleted file mode 100644
index 5dfe660ef..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SRPConstants.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-import java.math.BigInteger;
-
-/**
- * SRP Group Parameters from
- * <a href="http://tools.ietf.org/html/rfc5054#appendix-A">Appendix A of RFC 5054</a>.
- *
- * The 1024-, 1536-, and 2048-bit groups are taken from software
- * developed by Tom Wu and Eugene Jhong for the Stanford SRP
- * distribution, and subsequently proven to be prime. The larger primes
- * are taken from [MODP], but generators have been calculated that are
- * primitive roots of N, unlike the generators in [MODP].
- *
- * The 1024-bit and 1536-bit groups <b>MUST</b> be supported.
- */
-public class SRPConstants {
- public static class Parameters {
- public final BigInteger N;
- public final BigInteger g;
- public final int bitLength;
- public final int byteLength;
- public final int hexLength;
-
- protected Parameters(String N, long g) {
- if (N == null) {
- throw new IllegalArgumentException("N must not be null");
- }
- this.N = new BigInteger(N.replaceAll(" ", ""), 16); // Hex.
- this.g = BigInteger.valueOf(g);
- this.hexLength = this.N.toString(16).length();
- this.byteLength = hexLength / 2;
- this.bitLength = this.byteLength * 8;
- }
- }
-
- public static final Parameters _1024 = new Parameters("" +
- "EEAF0AB9 ADB38DD6 9C33F80A FA8FC5E8 60726187 75FF3C0B 9EA2314C" +
- "9C256576 D674DF74 96EA81D3 383B4813 D692C6E0 E0D5D8E2 50B98BE4" +
- "8E495C1D 6089DAD1 5DC7D7B4 6154D6B6 CE8EF4AD 69B15D49 82559B29" +
- "7BCF1885 C529F566 660E57EC 68EDBC3C 05726CC0 2FD4CBF4 976EAA9A" +
- "FD5138FE 8376435B 9FC61D2F C0EB06E3", 2L);
-
- public static final Parameters _1536 = new Parameters("" +
- "9DEF3CAF B939277A B1F12A86 17A47BBB DBA51DF4 99AC4C80 BEEEA961" +
- "4B19CC4D 5F4F5F55 6E27CBDE 51C6A94B E4607A29 1558903B A0D0F843" +
- "80B655BB 9A22E8DC DF028A7C EC67F0D0 8134B1C8 B9798914 9B609E0B" +
- "E3BAB63D 47548381 DBC5B1FC 764E3F4B 53DD9DA1 158BFD3E 2B9C8CF5" +
- "6EDF0195 39349627 DB2FD53D 24B7C486 65772E43 7D6C7F8C E442734A" +
- "F7CCB7AE 837C264A E3A9BEB8 7F8A2FE9 B8B5292E 5A021FFF 5E91479E" +
- "8CE7A28C 2442C6F3 15180F93 499A234D CF76E3FE D135F9BB", 2L);
-
- public static final Parameters _2048 = new Parameters("" +
- "AC6BDB41 324A9A9B F166DE5E 1389582F AF72B665 1987EE07 FC319294" +
- "3DB56050 A37329CB B4A099ED 8193E075 7767A13D D52312AB 4B03310D" +
- "CD7F48A9 DA04FD50 E8083969 EDB767B0 CF609517 9A163AB3 661A05FB" +
- "D5FAAAE8 2918A996 2F0B93B8 55F97993 EC975EEA A80D740A DBF4FF74" +
- "7359D041 D5C33EA7 1D281E44 6B14773B CA97B43A 23FB8016 76BD207A" +
- "436C6481 F1D2B907 8717461A 5B9D32E6 88F87748 544523B5 24B0D57D" +
- "5EA77A27 75D2ECFA 032CFBDB F52FB378 61602790 04E57AE6 AF874E73" +
- "03CE5329 9CCC041C 7BC308D8 2A5698F3 A8D0C382 71AE35F8 E9DBFBB6" +
- "94B5C803 D89F7AE4 35DE236D 525F5475 9B65E372 FCD68EF2 0FA7111F" +
- "9E4AFF73", 2L);
-
- public static final Parameters _3072 = new Parameters("" +
- "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08" +
- "8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B" +
- "302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9" +
- "A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6" +
- "49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8" +
- "FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" +
- "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C" +
- "180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718" +
- "3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D" +
- "04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D" +
- "B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226" +
- "1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" +
- "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC" +
- "E0FD108E 4B82D120 A93AD2CA FFFFFFFF FFFFFFFF", 5L);
-
- public static final Parameters _4096 = new Parameters("" +
- "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08" +
- "8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B" +
- "302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9" +
- "A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6" +
- "49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8" +
- "FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" +
- "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C" +
- "180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718" +
- "3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D" +
- "04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D" +
- "B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226" +
- "1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" +
- "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC" +
- "E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 88719A10 BDBA5B26" +
- "99C32718 6AF4E23C 1A946834 B6150BDA 2583E9CA 2AD44CE8 DBBBC2DB" +
- "04DE8EF9 2E8EFC14 1FBECAA6 287C5947 4E6BC05D 99B2964F A090C3A2" +
- "233BA186 515BE7ED 1F612970 CEE2D7AF B81BDD76 2170481C D0069127" +
- "D5B05AA9 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34063199" +
- "FFFFFFFF FFFFFFFF", 5L);
-
- public static final Parameters _6144 = new Parameters("" +
- "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08" +
- "8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B" +
- "302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9" +
- "A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6" +
- "49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8" +
- "FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" +
- "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C" +
- "180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718" +
- "3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D" +
- "04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D" +
- "B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226" +
- "1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" +
- "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC" +
- "E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 88719A10 BDBA5B26" +
- "99C32718 6AF4E23C 1A946834 B6150BDA 2583E9CA 2AD44CE8 DBBBC2DB" +
- "04DE8EF9 2E8EFC14 1FBECAA6 287C5947 4E6BC05D 99B2964F A090C3A2" +
- "233BA186 515BE7ED 1F612970 CEE2D7AF B81BDD76 2170481C D0069127" +
- "D5B05AA9 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492" +
- "36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD F8FF9406" +
- "AD9E530E E5DB382F 413001AE B06A53ED 9027D831 179727B0 865A8918" +
- "DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B DB7F1447 E6CC254B 33205151" +
- "2BD7AF42 6FB8F401 378CD2BF 5983CA01 C64B92EC F032EA15 D1721D03" +
- "F482D7CE 6E74FEF6 D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F" +
- "BEC7E8F3 23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA" +
- "CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328 06A1D58B" +
- "B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C DA56C9EC 2EF29632" +
- "387FE8D7 6E3C0468 043E8F66 3F4860EE 12BF2D5B 0B7474D6 E694F91E" +
- "6DCC4024 FFFFFFFF FFFFFFFF", 5L);
-
- public static final Parameters _8192 = new Parameters("" +
- "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08" +
- "8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B" +
- "302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9" +
- "A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6" +
- "49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8" +
- "FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" +
- "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C" +
- "180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718" +
- "3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D" +
- "04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D" +
- "B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226" +
- "1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" +
- "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC" +
- "E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 88719A10 BDBA5B26" +
- "99C32718 6AF4E23C 1A946834 B6150BDA 2583E9CA 2AD44CE8 DBBBC2DB" +
- "04DE8EF9 2E8EFC14 1FBECAA6 287C5947 4E6BC05D 99B2964F A090C3A2" +
- "233BA186 515BE7ED 1F612970 CEE2D7AF B81BDD76 2170481C D0069127" +
- "D5B05AA9 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492" +
- "36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD F8FF9406" +
- "AD9E530E E5DB382F 413001AE B06A53ED 9027D831 179727B0 865A8918" +
- "DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B DB7F1447 E6CC254B 33205151" +
- "2BD7AF42 6FB8F401 378CD2BF 5983CA01 C64B92EC F032EA15 D1721D03" +
- "F482D7CE 6E74FEF6 D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F" +
- "BEC7E8F3 23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA" +
- "CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328 06A1D58B" +
- "B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C DA56C9EC 2EF29632" +
- "387FE8D7 6E3C0468 043E8F66 3F4860EE 12BF2D5B 0B7474D6 E694F91E" +
- "6DBE1159 74A3926F 12FEE5E4 38777CB6 A932DF8C D8BEC4D0 73B931BA" +
- "3BC832B6 8D9DD300 741FA7BF 8AFC47ED 2576F693 6BA42466 3AAB639C" +
- "5AE4F568 3423B474 2BF1C978 238F16CB E39D652D E3FDB8BE FC848AD9" +
- "22222E04 A4037C07 13EB57A8 1A23F0C7 3473FC64 6CEA306B 4BCBC886" +
- "2F8385DD FA9D4B7F A2C087E8 79683303 ED5BDD3A 062B3CF5 B3A278A6" +
- "6D2A13F8 3F44F82D DF310EE0 74AB6A36 4597E899 A0255DC1 64F31CC5" +
- "0846851D F9AB4819 5DED7EA1 B1D510BD 7EE74D73 FAF36BC3 1ECFA268" +
- "359046F4 EB879F92 4009438B 481C6CD7 889A002E D5EE382B C9190DA6" +
- "FC026E47 9558E447 5677E9AA 9E3050E2 765694DF C81F56E8 80B96E71" +
- "60C980DD 98EDD3DF FFFFFFFF FFFFFFFF", 19L);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncResponse.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncResponse.java
deleted file mode 100644
index 177d7aaba..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncResponse.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-import android.support.annotation.Nullable;
-
-import org.mozilla.gecko.sync.Utils;
-
-import ch.boye.httpclientandroidlib.HttpResponse;
-
-public class SyncResponse extends MozResponse {
- public static final String X_WEAVE_BACKOFF = "x-weave-backoff";
- public static final String X_BACKOFF = "x-backoff";
- public static final String X_LAST_MODIFIED = "x-last-modified";
- public static final String X_WEAVE_TIMESTAMP = "x-weave-timestamp";
- public static final String X_WEAVE_RECORDS = "x-weave-records";
- public static final String X_WEAVE_QUOTA_REMAINING = "x-weave-quota-remaining";
- public static final String X_WEAVE_ALERT = "x-weave-alert";
- public static final String X_WEAVE_NEXT_OFFSET = "x-weave-next-offset";
-
- public SyncResponse(HttpResponse res) {
- super(res);
- }
-
- /**
- * @return A number of seconds, or -1 if the 'X-Weave-Backoff' header was not
- * present.
- */
- public int weaveBackoffInSeconds() throws NumberFormatException {
- return this.getIntegerHeader(X_WEAVE_BACKOFF);
- }
-
- /**
- * @return A number of seconds, or -1 if the 'X-Backoff' header was not
- * present.
- */
- public int xBackoffInSeconds() throws NumberFormatException {
- return this.getIntegerHeader(X_BACKOFF);
- }
-
- /**
- * Extract a number of seconds, or -1 if none of the specified headers were present.
- *
- * @param includeRetryAfter
- * if <code>true</code>, the Retry-After header is excluded. This is
- * useful for processing non-error responses where a Retry-After
- * header would be unexpected.
- * @return the maximum of the three possible backoff headers, in seconds.
- */
- public int totalBackoffInSeconds(boolean includeRetryAfter) {
- int retryAfterInSeconds = -1;
- if (includeRetryAfter) {
- try {
- retryAfterInSeconds = retryAfterInSeconds();
- } catch (NumberFormatException e) {
- }
- }
-
- int weaveBackoffInSeconds = -1;
- try {
- weaveBackoffInSeconds = weaveBackoffInSeconds();
- } catch (NumberFormatException e) {
- }
-
- int backoffInSeconds = -1;
- try {
- backoffInSeconds = xBackoffInSeconds();
- } catch (NumberFormatException e) {
- }
-
- int totalBackoff = Math.max(retryAfterInSeconds, Math.max(backoffInSeconds, weaveBackoffInSeconds));
- if (totalBackoff < 0) {
- return -1;
- } else {
- return totalBackoff;
- }
- }
-
- /**
- * @return A number of milliseconds, or -1 if neither the 'Retry-After',
- * 'X-Backoff', or 'X-Weave-Backoff' header were present.
- */
- public long totalBackoffInMilliseconds() {
- long totalBackoff = totalBackoffInSeconds(true);
- if (totalBackoff < 0) {
- return -1;
- } else {
- return 1000 * totalBackoff;
- }
- }
-
- public long normalizedWeaveTimestamp() {
- return normalizedTimestampForHeader(X_WEAVE_TIMESTAMP);
- }
-
- /**
- * Timestamps returned from a Sync server are decimal numbers of seconds,
- * e.g., 1323393518.04.
- *
- * We want milliseconds since epoch.
- *
- * @return milliseconds since the epoch, as a long, or -1 if the header
- * was missing or invalid.
- */
- public long normalizedTimestampForHeader(String header) {
- if (!this.hasHeader(header)) {
- return -1;
- }
-
- return Utils.decimalSecondsToMilliseconds(
- this.response.getFirstHeader(header).getValue()
- );
- }
-
- public int weaveRecords() throws NumberFormatException {
- return this.getIntegerHeader(X_WEAVE_RECORDS);
- }
-
- public int weaveQuotaRemaining() throws NumberFormatException {
- return this.getIntegerHeader(X_WEAVE_QUOTA_REMAINING);
- }
-
- public String weaveAlert() {
- return this.getNonMissingHeader(X_WEAVE_ALERT);
- }
-
- /**
- * This header may be sent back with multi-record responses where the request included a limit parameter.
- * Its presence indicates that the number of available records exceeded the given limit.
- * The value from this header can be passed back in the offset parameter to retrieve additional records.
- * The value of this header will always be a string of characters from the urlsafe-base64 alphabet.
- * The specific contents of the string are an implementation detail of the server,
- * so clients should treat it as an opaque token.
- *
- * @return the offset header
- */
- public String weaveOffset() {
- return this.getNonMissingHeader(X_WEAVE_NEXT_OFFSET);
- }
-
- /**
- * This header gives the last-modified time of the target resource as seen during processing of the request,
- * and will be included in all success responses (200, 201, 204).
- * When given in response to a write request, this will be equal to the server’s current time and
- * to the new last-modified time of any BSOs created or changed by the request.
- * It is similar to the standard HTTP Last-Modified header,
- * but the value is a decimal timestamp rather than a HTTP-format date.
- *
- * @return the last modified header
- */
- @Nullable
- public String lastModified() {
- return this.getNonMissingHeader(X_LAST_MODIFIED);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageCollectionRequest.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageCollectionRequest.java
deleted file mode 100644
index 3ae672f21..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageCollectionRequest.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.net.URI;
-
-import org.mozilla.gecko.background.common.log.Logger;
-
-import ch.boye.httpclientandroidlib.Header;
-import ch.boye.httpclientandroidlib.HttpEntity;
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
-import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
-
-/**
- * A request class that handles line-by-line responses. Eventually this will
- * handle real stream processing; for now, just parse the returned body
- * line-by-line.
- *
- * @author rnewman
- *
- */
-public class SyncStorageCollectionRequest extends SyncStorageRequest {
- private static final String LOG_TAG = "CollectionRequest";
-
- public SyncStorageCollectionRequest(URI uri) {
- super(uri);
- }
-
- protected volatile boolean aborting = false;
-
- /**
- * Instruct the request that it should process no more records,
- * and decline to notify any more delegate callbacks.
- */
- public void abort() {
- aborting = true;
- try {
- this.resource.request.abort();
- } catch (Exception e) {
- // Just in case.
- Logger.warn(LOG_TAG, "Got exception in abort: " + e);
- }
- }
-
- @Override
- protected BaseResourceDelegate makeResourceDelegate(SyncStorageRequest request) {
- return new SyncCollectionResourceDelegate((SyncStorageCollectionRequest) request);
- }
-
- // TODO: this is awful.
- public class SyncCollectionResourceDelegate extends
- SyncStorageResourceDelegate {
-
- private static final String CONTENT_TYPE_INCREMENTAL = "application/newlines";
- private static final int FETCH_BUFFER_SIZE = 16 * 1024; // 16K chars.
-
- SyncCollectionResourceDelegate(SyncStorageCollectionRequest request) {
- super(request);
- }
-
- @Override
- public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
- super.addHeaders(request, client);
- request.setHeader("Accept", CONTENT_TYPE_INCREMENTAL);
- // Caller is responsible for setting full=1.
- }
-
- @Override
- public void handleHttpResponse(HttpResponse response) {
- if (aborting) {
- return;
- }
-
- if (response.getStatusLine().getStatusCode() != 200) {
- super.handleHttpResponse(response);
- return;
- }
-
- HttpEntity entity = response.getEntity();
- Header contentType = entity.getContentType();
- if (!contentType.getValue().startsWith(CONTENT_TYPE_INCREMENTAL)) {
- // Not incremental!
- super.handleHttpResponse(response);
- return;
- }
-
- // TODO: at this point we can access X-Weave-Timestamp, compare
- // that to our local timestamp, and compute an estimate of clock
- // skew. We can provide this to the incremental delegate, which
- // will allow it to seamlessly correct timestamps on the records
- // it processes. Bug 721887.
-
- // Line-by-line processing, then invoke success.
- SyncStorageCollectionRequestDelegate delegate = (SyncStorageCollectionRequestDelegate) this.request.delegate;
- InputStream content = null;
- BufferedReader br = null;
- try {
- content = entity.getContent();
- br = new BufferedReader(new InputStreamReader(content), FETCH_BUFFER_SIZE);
- String line;
-
- // This relies on connection timeouts at the HTTP layer.
- while (!aborting &&
- null != (line = br.readLine())) {
- try {
- delegate.handleRequestProgress(line);
- } catch (Exception ex) {
- delegate.handleRequestError(new HandleProgressException(ex));
- BaseResource.consumeEntity(entity);
- return;
- }
- }
- if (aborting) {
- // So we don't hit the success case below.
- return;
- }
- } catch (IOException ex) {
- if (!aborting) {
- delegate.handleRequestError(ex);
- }
- BaseResource.consumeEntity(entity);
- return;
- } finally {
- // Attempt to close the stream and reader.
- if (br != null) {
- try {
- br.close();
- } catch (IOException e) {
- // We don't care if this fails.
- }
- }
- }
- // We're done processing the entity. Don't let fetching the body succeed!
- BaseResource.consumeEntity(entity);
- delegate.handleRequestSuccess(new SyncStorageResponse(response));
- }
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageCollectionRequestDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageCollectionRequestDelegate.java
deleted file mode 100644
index ddf52007b..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageCollectionRequestDelegate.java
+++ /dev/null
@@ -1,9 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-public abstract class SyncStorageCollectionRequestDelegate implements
- SyncStorageRequestIncrementalDelegate, SyncStorageRequestDelegate {
-} \ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRecordRequest.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRecordRequest.java
deleted file mode 100644
index c18c4fe15..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRecordRequest.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-import org.json.simple.JSONArray;
-import org.json.simple.JSONObject;
-import org.mozilla.gecko.sync.CryptoRecord;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URISyntaxException;
-
-/**
- * Resource class that implements expected headers and processing for Sync.
- * Accepts a simplified delegate.
- *
- * Includes:
- * * Basic Auth headers (via Resource)
- * * Error responses:
- * * 401
- * * 503
- * * Headers:
- * * Retry-After
- * * X-Weave-Backoff
- * * X-Backoff
- * * X-Weave-Records?
- * * ...
- * * Timeouts
- * * Network errors
- * * application/newlines
- * * JSON parsing
- * * Content-Type and Content-Length validation.
- */
-public class SyncStorageRecordRequest extends SyncStorageRequest {
-
- public class SyncStorageRecordResourceDelegate extends SyncStorageResourceDelegate {
- SyncStorageRecordResourceDelegate(SyncStorageRequest request) {
- super(request);
- }
- }
-
- public SyncStorageRecordRequest(URI uri) {
- super(uri);
- }
-
- public SyncStorageRecordRequest(String url) throws URISyntaxException {
- this(new URI(url));
- }
-
- @Override
- protected BaseResourceDelegate makeResourceDelegate(SyncStorageRequest request) {
- return new SyncStorageRecordResourceDelegate(request);
- }
-
- @SuppressWarnings("unchecked")
- public void post(JSONObject body) {
- // Let's do this the trivial way for now.
- // Note that POSTs should be an array, so we wrap here.
- final JSONArray toPOST = new JSONArray();
- toPOST.add(body);
- try {
- this.resource.post(toPOST);
- } catch (UnsupportedEncodingException e) {
- this.delegate.handleRequestError(e);
- }
- }
-
- public void post(JSONArray body) {
- // Let's do this the trivial way for now.
- try {
- this.resource.post(body);
- } catch (UnsupportedEncodingException e) {
- this.delegate.handleRequestError(e);
- }
- }
-
- public void put(JSONObject body) {
- // Let's do this the trivial way for now.
- try {
- this.resource.put(body);
- } catch (UnsupportedEncodingException e) {
- this.delegate.handleRequestError(e);
- }
- }
-
- public void post(CryptoRecord record) {
- this.post(record.toJSONObject());
- }
-
- public void put(CryptoRecord record) {
- this.put(record.toJSONObject());
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRequest.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRequest.java
deleted file mode 100644
index 3ede9cded..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRequest.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.security.GeneralSecurityException;
-import java.util.HashMap;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.SyncConstants;
-
-import ch.boye.httpclientandroidlib.HttpEntity;
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.client.ClientProtocolException;
-import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
-import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
-
-public class SyncStorageRequest implements Resource {
- public static HashMap<String, String> SERVER_ERROR_MESSAGES;
- static {
- HashMap<String, String> errors = new HashMap<String, String>();
-
- // Sync protocol errors.
- errors.put("1", "Illegal method/protocol");
- errors.put("2", "Incorrect/missing CAPTCHA");
- errors.put("3", "Invalid/missing username");
- errors.put("4", "Attempt to overwrite data that can't be overwritten (such as creating a user ID that already exists)");
- errors.put("5", "User ID does not match account in path");
- errors.put("6", "JSON parse failure");
- errors.put("7", "Missing password field");
- errors.put("8", "Invalid Weave Basic Object");
- errors.put("9", "Requested password not strong enough");
- errors.put("10", "Invalid/missing password reset code");
- errors.put("11", "Unsupported function");
- errors.put("12", "No email address on file");
- errors.put("13", "Invalid collection");
- errors.put("14", "User over quota");
- errors.put("15", "The email does not match the username");
- errors.put("16", "Client upgrade required");
- errors.put("255", "An unexpected server error occurred: pool is empty.");
-
- // Infrastructure-generated errors.
- errors.put("\"server issue: getVS failed\"", "server issue: getVS failed");
- errors.put("\"server issue: prefix not set\"", "server issue: prefix not set");
- errors.put("\"server issue: host header not received from client\"", "server issue: host header not received from client");
- errors.put("\"server issue: database lookup failed\"", "server issue: database lookup failed");
- errors.put("\"server issue: database is not healthy\"", "server issue: database is not healthy");
- errors.put("\"server issue: database not in pool\"", "server issue: database not in pool");
- errors.put("\"server issue: database marked as down\"", "server issue: database marked as down");
- SERVER_ERROR_MESSAGES = errors;
- }
- public static String getServerErrorMessage(String body) {
- if (SERVER_ERROR_MESSAGES.containsKey(body)) {
- return SERVER_ERROR_MESSAGES.get(body);
- }
- return body;
- }
-
- /**
- * @param uri
- * @throws URISyntaxException
- */
- public SyncStorageRequest(String uri) throws URISyntaxException {
- this(new URI(uri));
- }
-
- /**
- * @param uri
- */
- public SyncStorageRequest(URI uri) {
- this.resource = new BaseResource(uri);
- this.resourceDelegate = this.makeResourceDelegate(this);
- this.resource.delegate = this.resourceDelegate;
- }
-
- @Override
- public URI getURI() {
- return this.resource.getURI();
- }
-
- @Override
- public String getURIString() {
- return this.resource.getURIString();
- }
-
- @Override
- public String getHostname() {
- return this.resource.getHostname();
- }
-
- /**
- * A ResourceDelegate that mediates between Resource-level notifications and the SyncStorageRequest.
- */
- public class SyncStorageResourceDelegate extends BaseResourceDelegate {
- private static final String LOG_TAG = "SSResourceDelegate";
- protected SyncStorageRequest request;
-
- SyncStorageResourceDelegate(SyncStorageRequest request) {
- super(request);
- this.request = request;
- }
-
- @Override
- public AuthHeaderProvider getAuthHeaderProvider() {
- return request.delegate.getAuthHeaderProvider();
- }
-
- @Override
- public String getUserAgent() {
- return SyncConstants.USER_AGENT;
- }
-
- @Override
- public void handleHttpResponse(HttpResponse response) {
- Logger.debug(LOG_TAG, "SyncStorageResourceDelegate handling response: " + response.getStatusLine() + ".");
- SyncStorageRequestDelegate d = this.request.delegate;
- SyncStorageResponse res = new SyncStorageResponse(response);
- // It is the responsibility of the delegate handlers to completely consume the response.
- // In context of a Sync storage response, success is either a 200 OK or 202 Accepted.
- // 202 is returned during uploads of data in a batching mode, indicating that more is expected.
- if (res.getStatusCode() == 200 || res.getStatusCode() == 202) {
- d.handleRequestSuccess(res);
- } else {
- Logger.warn(LOG_TAG, "HTTP request failed.");
- try {
- Logger.warn(LOG_TAG, "HTTP response body: " + res.getErrorMessage());
- } catch (Exception e) {
- Logger.error(LOG_TAG, "Can't fetch HTTP response body.", e);
- }
- d.handleRequestFailure(res);
- }
- }
-
- @Override
- public void handleHttpProtocolException(ClientProtocolException e) {
- this.request.delegate.handleRequestError(e);
- }
-
- @Override
- public void handleHttpIOException(IOException e) {
- this.request.delegate.handleRequestError(e);
- }
-
- @Override
- public void handleTransportException(GeneralSecurityException e) {
- this.request.delegate.handleRequestError(e);
- }
-
- @Override
- public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
- // Clients can use their delegate interface to specify X-If-Unmodified-Since.
- String ifUnmodifiedSince = this.request.delegate.ifUnmodifiedSince();
- if (ifUnmodifiedSince != null) {
- Logger.debug(LOG_TAG, "Making request with X-If-Unmodified-Since = " + ifUnmodifiedSince);
- request.setHeader("x-if-unmodified-since", ifUnmodifiedSince);
- }
- if (request.getMethod().equalsIgnoreCase("DELETE")) {
- request.addHeader("x-confirm-delete", "1");
- }
- }
- }
-
- protected BaseResourceDelegate resourceDelegate;
- public SyncStorageRequestDelegate delegate;
- protected BaseResource resource;
-
- public SyncStorageRequest() {
- super();
- }
-
- // Default implementation. Override this.
- protected BaseResourceDelegate makeResourceDelegate(SyncStorageRequest request) {
- return new SyncStorageResourceDelegate(request);
- }
-
- @Override
- public void get() {
- this.resource.get();
- }
-
- @Override
- public void delete() {
- this.resource.delete();
- }
-
- @Override
- public void post(HttpEntity body) {
- this.resource.post(body);
- }
-
- @Override
- public void patch(HttpEntity body) {
- this.resource.patch(body);
- }
-
- @Override
- public void put(HttpEntity body) {
- this.resource.put(body);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRequestDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRequestDelegate.java
deleted file mode 100644
index 29f42cc28..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRequestDelegate.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-public interface SyncStorageRequestDelegate {
- public AuthHeaderProvider getAuthHeaderProvider();
-
- String ifUnmodifiedSince();
-
- // TODO: at this point we can access X-Weave-Timestamp, compare
- // that to our local timestamp, and compute an estimate of clock
- // skew. Bug 721887.
-
- /**
- * Override this to handle a successful SyncStorageRequest.
- *
- * SyncStorageResourceDelegate implementers <b>must</b> ensure that the HTTP
- * responses underlying SyncStorageResponses are fully consumed to ensure that
- * connections are returned to the pool, for example by calling
- * <code>BaseResource.consumeEntity(response)</code>.
- */
- void handleRequestSuccess(SyncStorageResponse response);
-
- /**
- * Override this to handle a failed SyncStorageRequest.
- *
- *
- * SyncStorageResourceDelegate implementers <b>must</b> ensure that the HTTP
- * responses underlying SyncStorageResponses are fully consumed to ensure that
- * connections are returned to the pool, for example by calling
- * <code>BaseResource.consumeEntity(response)</code>.
- */
- void handleRequestFailure(SyncStorageResponse response);
-
- void handleRequestError(Exception ex);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRequestIncrementalDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRequestIncrementalDelegate.java
deleted file mode 100644
index aa5d735bf..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageRequestIncrementalDelegate.java
+++ /dev/null
@@ -1,9 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-public interface SyncStorageRequestIncrementalDelegate {
- void handleRequestProgress(String progress); // For line-by-line.
-} \ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageResponse.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageResponse.java
deleted file mode 100644
index 644df314c..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageResponse.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-import java.io.IOException;
-import java.util.HashMap;
-
-import org.mozilla.gecko.background.common.log.Logger;
-
-import ch.boye.httpclientandroidlib.HttpResponse;
-
-public class SyncStorageResponse extends SyncResponse {
- private static final String LOG_TAG = "SyncStorageResponse";
-
- // Responses that are actionable get constant status codes.
- public static final String RESPONSE_CLIENT_UPGRADE_REQUIRED = "16";
-
- public static HashMap<String, String> SERVER_ERROR_MESSAGES;
- static {
- HashMap<String, String> errors = new HashMap<String, String>();
-
- // Sync protocol errors.
- errors.put("1", "Illegal method/protocol");
- errors.put("2", "Incorrect/missing CAPTCHA");
- errors.put("3", "Invalid/missing username");
- errors.put("4", "Attempt to overwrite data that can't be overwritten (such as creating a user ID that already exists)");
- errors.put("5", "User ID does not match account in path");
- errors.put("6", "JSON parse failure");
- errors.put("7", "Missing password field");
- errors.put("8", "Invalid Weave Basic Object");
- errors.put("9", "Requested password not strong enough");
- errors.put("10", "Invalid/missing password reset code");
- errors.put("11", "Unsupported function");
- errors.put("12", "No email address on file");
- errors.put("13", "Invalid collection");
- errors.put("14", "User over quota");
- errors.put("15", "The email does not match the username");
- errors.put(RESPONSE_CLIENT_UPGRADE_REQUIRED, "Client upgrade required");
- errors.put("255", "An unexpected server error occurred: pool is empty.");
-
- // Infrastructure-generated errors.
- errors.put("\"server issue: getVS failed\"", "server issue: getVS failed");
- errors.put("\"server issue: prefix not set\"", "server issue: prefix not set");
- errors.put("\"server issue: host header not received from client\"", "server issue: host header not received from client");
- errors.put("\"server issue: database lookup failed\"", "server issue: database lookup failed");
- errors.put("\"server issue: database is not healthy\"", "server issue: database is not healthy");
- errors.put("\"server issue: database not in pool\"", "server issue: database not in pool");
- errors.put("\"server issue: database marked as down\"", "server issue: database marked as down");
- SERVER_ERROR_MESSAGES = errors;
- }
- public static String getServerErrorMessage(String body) {
- Logger.debug(LOG_TAG, "Looking up message for body \"" + body + "\"");
- if (SERVER_ERROR_MESSAGES.containsKey(body)) {
- return SERVER_ERROR_MESSAGES.get(body);
- }
- return body;
- }
-
-
- public SyncStorageResponse(HttpResponse res) {
- super(res);
- }
-
- public String getErrorMessage() throws IllegalStateException, IOException {
- return SyncStorageResponse.getServerErrorMessage(this.body().trim());
- }
-
- /**
- * This header gives the last-modified time of the target resource as seen during processing of
- * the request, and will be included in all success responses (200, 201, 204).
- * When given in response to a write request, this will be equal to the server’s current time and
- * to the new last-modified time of any BSOs created or changed by the request.
- */
- public String getLastModified() {
- if (!response.containsHeader(X_LAST_MODIFIED)) {
- return null;
- }
- return response.getFirstHeader(X_LAST_MODIFIED).getValue();
- }
-
- // TODO: Content-Type and Content-Length validation.
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/TLSSocketFactory.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/TLSSocketFactory.java
deleted file mode 100644
index dd68c0515..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/TLSSocketFactory.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-import java.io.IOException;
-import java.net.Socket;
-
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocket;
-
-import org.mozilla.gecko.background.common.GlobalConstants;
-import org.mozilla.gecko.background.common.log.Logger;
-
-import ch.boye.httpclientandroidlib.conn.ssl.SSLSocketFactory;
-import ch.boye.httpclientandroidlib.params.HttpParams;
-
-public class TLSSocketFactory extends SSLSocketFactory {
- private static final String LOG_TAG = "TLSSocketFactory";
-
- // Guarded by `this`.
- private static String[] cipherSuites = GlobalConstants.DEFAULT_CIPHER_SUITES;
-
- public TLSSocketFactory(SSLContext sslContext) {
- super(sslContext);
- }
-
- /**
- * Attempt to specify the cipher suites to use for a connection. If
- * setting fails (as it will on Android 2.2, because the wrong names
- * are in use to specify ciphers), attempt to set the defaults.
- *
- * We store the list of cipher suites in `cipherSuites`, which
- * avoids this fallback handling having to be executed more than once.
- *
- * This method is synchronized to ensure correct use of that member.
- *
- * See Bug 717691 for more details.
- *
- * @param socket
- * The SSLSocket on which to operate.
- */
- public static synchronized void setEnabledCipherSuites(SSLSocket socket) {
- try {
- socket.setEnabledCipherSuites(cipherSuites);
- } catch (IllegalArgumentException e) {
- cipherSuites = socket.getSupportedCipherSuites();
- Logger.warn(LOG_TAG, "Setting enabled cipher suites failed: " + e.getMessage());
- Logger.warn(LOG_TAG, "Using " + cipherSuites.length + " supported suites.");
- socket.setEnabledCipherSuites(cipherSuites);
- }
- }
-
- @Override
- public Socket createSocket(HttpParams params) throws IOException {
- SSLSocket socket = (SSLSocket) super.createSocket(params);
- socket.setEnabledProtocols(GlobalConstants.DEFAULT_PROTOCOLS);
- setEnabledCipherSuites(socket);
- return socket;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/WBOCollectionRequestDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/WBOCollectionRequestDelegate.java
deleted file mode 100644
index 2e26f041b..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/WBOCollectionRequestDelegate.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.KeyBundleProvider;
-
-/**
- * Subclass this to handle collection fetches.
- * @author rnewman
- *
- */
-public abstract class WBOCollectionRequestDelegate
-extends SyncStorageCollectionRequestDelegate
-implements KeyBundleProvider {
-
- @Override
- public abstract KeyBundle keyBundle();
- public abstract void handleWBO(CryptoRecord record);
-
- @Override
- public void handleRequestProgress(String progress) {
- try {
- CryptoRecord record = CryptoRecord.fromJSONRecord(progress);
- record.keyBundle = this.keyBundle();
- this.handleWBO(record);
- } catch (Exception e) {
- this.handleRequestError(e);
- // TODO: abort?! Allow exception to propagate to fail?
- }
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/WBORequestDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/WBORequestDelegate.java
deleted file mode 100644
index 8a09e0c7f..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/WBORequestDelegate.java
+++ /dev/null
@@ -1,14 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.net;
-
-import org.mozilla.gecko.sync.KeyBundleProvider;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-
-public abstract class WBORequestDelegate
-implements SyncStorageRequestDelegate, KeyBundleProvider {
- @Override
- public abstract KeyBundle keyBundle();
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/BookmarkNeedsReparentingException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/BookmarkNeedsReparentingException.java
deleted file mode 100644
index 5fe3dc9fa..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/BookmarkNeedsReparentingException.java
+++ /dev/null
@@ -1,17 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class BookmarkNeedsReparentingException extends SyncException {
-
- private static final long serialVersionUID = -7018336108709392800L;
-
- public BookmarkNeedsReparentingException(Exception ex) {
- super(ex);
- }
-
-} \ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/BookmarksRepository.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/BookmarksRepository.java
deleted file mode 100644
index 289fc48ec..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/BookmarksRepository.java
+++ /dev/null
@@ -1,16 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-/**
- * Shared interface for repositories that consume and produce
- * bookmark records.
- *
- * @author rnewman
- *
- */
-public interface BookmarksRepository {
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/ConstrainedServer11Repository.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/ConstrainedServer11Repository.java
deleted file mode 100644
index a6dc3f6b8..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/ConstrainedServer11Repository.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-import java.net.URISyntaxException;
-
-import org.mozilla.gecko.sync.InfoCollections;
-import org.mozilla.gecko.sync.InfoConfiguration;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-
-/**
- * A kind of Server11Repository that supports explicit setting of total fetch limit, per-batch fetch limit, and a sort order.
- *
- * @author rnewman
- *
- */
-public class ConstrainedServer11Repository extends Server11Repository {
-
- private final String sort;
- private final long batchLimit;
- private final long totalLimit;
-
- public ConstrainedServer11Repository(String collection, String storageURL,
- AuthHeaderProvider authHeaderProvider,
- InfoCollections infoCollections,
- InfoConfiguration infoConfiguration,
- long batchLimit, long totalLimit, String sort)
- throws URISyntaxException {
- super(collection, storageURL, authHeaderProvider, infoCollections, infoConfiguration);
- this.batchLimit = batchLimit;
- this.totalLimit = totalLimit;
- this.sort = sort;
- }
-
- @Override
- public String getDefaultSort() {
- return sort;
- }
-
- @Override
- public long getDefaultBatchLimit() {
- return batchLimit;
- }
-
- @Override
- public long getDefaultTotalLimit() {
- return totalLimit;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/FetchFailedException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/FetchFailedException.java
deleted file mode 100644
index 8b29a37ba..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/FetchFailedException.java
+++ /dev/null
@@ -1,11 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class FetchFailedException extends SyncException {
- private static final long serialVersionUID = -7533105300182522946L;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/HashSetStoreTracker.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/HashSetStoreTracker.java
deleted file mode 100644
index 3b6facc31..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/HashSetStoreTracker.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-import java.util.HashSet;
-import java.util.Iterator;
-
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-public class HashSetStoreTracker implements StoreTracker {
-
- // Guarded by `this`.
- // Used to store GUIDs that were not locally modified but
- // have been modified by a call to `store`, and thus
- // should not be returned by a subsequent fetch.
- private final HashSet<String> guids;
-
- public HashSetStoreTracker() {
- guids = new HashSet<String>();
- }
-
- @Override
- public String toString() {
- return "#<Tracker: " + guids.size() + " guids tracked.>";
- }
-
- @Override
- public synchronized boolean trackRecordForExclusion(String guid) {
- return (guid != null) && guids.add(guid);
- }
-
- @Override
- public synchronized boolean isTrackedForExclusion(String guid) {
- return (guid != null) && guids.contains(guid);
- }
-
- @Override
- public synchronized boolean untrackStoredForExclusion(String guid) {
- return (guid != null) && guids.remove(guid);
- }
-
- @Override
- public RecordFilter getFilter() {
- if (guids.size() == 0) {
- return null;
- }
- return new RecordFilter() {
- @Override
- public boolean excludeRecord(Record r) {
- return isTrackedForExclusion(r.guid);
- }
- };
- }
-
- @Override
- public Iterator<String> recordsTrackedForExclusion() {
- return this.guids.iterator();
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/HistoryRepository.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/HistoryRepository.java
deleted file mode 100644
index eddc32102..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/HistoryRepository.java
+++ /dev/null
@@ -1,16 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-/**
- * Shared interface for repositories that consume and produce
- * history records.
- *
- * @author rnewman
- *
- */
-public interface HistoryRepository {
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/IdentityRecordFactory.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/IdentityRecordFactory.java
deleted file mode 100644
index acedc66e2..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/IdentityRecordFactory.java
+++ /dev/null
@@ -1,15 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-public class IdentityRecordFactory extends RecordFactory {
-
- @Override
- public Record createRecord(Record record) {
- return record;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InactiveSessionException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InactiveSessionException.java
deleted file mode 100644
index 185f0d724..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InactiveSessionException.java
+++ /dev/null
@@ -1,17 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class InactiveSessionException extends SyncException {
-
- private static final long serialVersionUID = 537241160815940991L;
-
- public InactiveSessionException(Exception ex) {
- super(ex);
- }
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InvalidBookmarkTypeException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InvalidBookmarkTypeException.java
deleted file mode 100644
index 3597276a4..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InvalidBookmarkTypeException.java
+++ /dev/null
@@ -1,17 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class InvalidBookmarkTypeException extends SyncException {
-
- private static final long serialVersionUID = -6098516814844387449L;
-
- public InvalidBookmarkTypeException(Exception e) {
- super(e);
- }
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InvalidRequestException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InvalidRequestException.java
deleted file mode 100644
index 3f761e540..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InvalidRequestException.java
+++ /dev/null
@@ -1,16 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class InvalidRequestException extends SyncException {
-
- private static final long serialVersionUID = 4502951350743608243L;
-
- public InvalidRequestException(Exception ex) {
- super(ex);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InvalidSessionTransitionException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InvalidSessionTransitionException.java
deleted file mode 100644
index 0963892c9..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/InvalidSessionTransitionException.java
+++ /dev/null
@@ -1,17 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class InvalidSessionTransitionException extends SyncException {
-
- private static final long serialVersionUID = 4157729859314427281L;
-
- public InvalidSessionTransitionException(Exception ex) {
- super(ex);
- }
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/MultipleRecordsForGuidException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/MultipleRecordsForGuidException.java
deleted file mode 100644
index 58cca4a49..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/MultipleRecordsForGuidException.java
+++ /dev/null
@@ -1,16 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class MultipleRecordsForGuidException extends SyncException {
-
- private static final long serialVersionUID = 7426987323485324741L;
-
- public MultipleRecordsForGuidException(Exception ex) {
- super(ex);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NoContentProviderException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NoContentProviderException.java
deleted file mode 100644
index 85d119a5d..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NoContentProviderException.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-import android.net.Uri;
-
-/**
- * Raised when a Content Provider cannot be retrieved.
- *
- * @author rnewman
- *
- */
-public class NoContentProviderException extends SyncException {
- private static final long serialVersionUID = 1L;
-
- public final Uri requestedProvider;
- public NoContentProviderException(Uri requested) {
- super();
- this.requestedProvider = requested;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NoGuidForIdException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NoGuidForIdException.java
deleted file mode 100644
index 3681deffd..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NoGuidForIdException.java
+++ /dev/null
@@ -1,16 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class NoGuidForIdException extends SyncException {
-
- private static final long serialVersionUID = -675614284405829041L;
-
- public NoGuidForIdException(Exception ex) {
- super(ex);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NoStoreDelegateException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NoStoreDelegateException.java
deleted file mode 100644
index 5747039aa..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NoStoreDelegateException.java
+++ /dev/null
@@ -1,11 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class NoStoreDelegateException extends SyncException {
- private static final long serialVersionUID = 6631689468978422074L;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NullCursorException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NullCursorException.java
deleted file mode 100644
index 4d9057992..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/NullCursorException.java
+++ /dev/null
@@ -1,17 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class NullCursorException extends SyncException {
-
- private static final long serialVersionUID = 3146506225701104661L;
-
- public NullCursorException(Exception e) {
- super(e);
- }
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/ParentNotFoundException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/ParentNotFoundException.java
deleted file mode 100644
index 991fd7426..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/ParentNotFoundException.java
+++ /dev/null
@@ -1,17 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class ParentNotFoundException extends SyncException {
-
- private static final long serialVersionUID = -2687003621705922982L;
-
- public ParentNotFoundException(Exception ex) {
- super(ex);
- }
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/ProfileDatabaseException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/ProfileDatabaseException.java
deleted file mode 100644
index 0f8075133..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/ProfileDatabaseException.java
+++ /dev/null
@@ -1,17 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class ProfileDatabaseException extends SyncException {
-
- private static final long serialVersionUID = -4916908502042261602L;
-
- public ProfileDatabaseException(Exception ex) {
- super(ex);
- }
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RecordFactory.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RecordFactory.java
deleted file mode 100644
index 6a8d81a77..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RecordFactory.java
+++ /dev/null
@@ -1,13 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-// Take a record retrieved from some middleware, producing
-// some concrete record type for application to some local repository.
-public abstract class RecordFactory {
- public abstract Record createRecord(Record record);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RecordFilter.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RecordFilter.java
deleted file mode 100644
index 733448ded..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RecordFilter.java
+++ /dev/null
@@ -1,11 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-public interface RecordFilter {
- public boolean excludeRecord(Record r);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Repository.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Repository.java
deleted file mode 100644
index 3dd3fd2c4..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Repository.java
+++ /dev/null
@@ -1,18 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCleanDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-
-import android.content.Context;
-
-public abstract class Repository {
- public abstract void createSession(RepositorySessionCreationDelegate delegate, Context context);
-
- public void clean(boolean success, RepositorySessionCleanDelegate delegate, Context context) {
- delegate.onCleaned(this);
- }
-} \ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RepositorySession.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RepositorySession.java
deleted file mode 100644
index 84fca1379..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RepositorySession.java
+++ /dev/null
@@ -1,384 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionGuidsSinceDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-/**
- * A <code>RepositorySession</code> is created and used thusly:
- *
- *<ul>
- * <li>Construct, with a reference to its parent {@link Repository}, by calling
- * {@link Repository#createSession(RepositorySessionCreationDelegate, android.content.Context)}.</li>
- * <li>Populate with saved information by calling {@link #unbundle(RepositorySessionBundle)}.</li>
- * <li>Begin a sync by calling {@link #begin(RepositorySessionBeginDelegate)}. <code>begin()</code>
- * is an appropriate place to initialize expensive resources.</li>
- * <li>Perform operations such as {@link #fetchSince(long, RepositorySessionFetchRecordsDelegate)} and
- * {@link #store(Record)}.</li>
- * <li>Finish by calling {@link #finish(RepositorySessionFinishDelegate)}, retrieving and storing
- * the current bundle.</li>
- *</ul>
- *
- * If <code>finish()</code> is not called, {@link #abort()} must be called. These calls must
- * <em>always</em> be paired with <code>begin()</code>.
- *
- */
-public abstract class RepositorySession {
-
- public enum SessionStatus {
- UNSTARTED,
- ACTIVE,
- ABORTED,
- DONE
- }
-
- private static final String LOG_TAG = "RepositorySession";
-
- protected static void trace(String message) {
- Logger.trace(LOG_TAG, message);
- }
-
- private SessionStatus status = SessionStatus.UNSTARTED;
- protected Repository repository;
- protected RepositorySessionStoreDelegate delegate;
-
- /**
- * A queue of Runnables which call out into delegates.
- */
- protected ExecutorService delegateQueue = Executors.newSingleThreadExecutor();
-
- /**
- * A queue of Runnables which effect storing.
- * This includes actual store work, and also the consequences of storeDone.
- * This provides strict ordering.
- */
- protected ExecutorService storeWorkQueue = Executors.newSingleThreadExecutor();
-
- // The time that the last sync on this collection completed, in milliseconds since epoch.
- private long lastSyncTimestamp = 0;
-
- public long getLastSyncTimestamp() {
- return lastSyncTimestamp;
- }
-
- public static long now() {
- return System.currentTimeMillis();
- }
-
- public RepositorySession(Repository repository) {
- this.repository = repository;
- }
-
- public abstract void guidsSince(long timestamp, RepositorySessionGuidsSinceDelegate delegate);
- public abstract void fetchSince(long timestamp, RepositorySessionFetchRecordsDelegate delegate);
- public abstract void fetch(String[] guids, RepositorySessionFetchRecordsDelegate delegate) throws InactiveSessionException;
- public abstract void fetchAll(RepositorySessionFetchRecordsDelegate delegate);
-
- /**
- * Override this if you wish to short-circuit a sync when you know --
- * e.g., by inspecting the database or info/collections -- that no new
- * data are available.
- *
- * @return true if a sync should proceed.
- */
- public boolean dataAvailable() {
- return true;
- }
-
- /**
- * @return true if we cannot safely sync from this <code>RepositorySession</code>.
- */
- public boolean shouldSkip() {
- return false;
- }
-
- /*
- * Store operations proceed thusly:
- *
- * * Set a delegate
- * * Store an arbitrary number of records. At any time the delegate can be
- * notified of an error.
- * * Call storeDone to notify the session that no more items are forthcoming.
- * * The store delegate will be notified of error or completion.
- *
- * This arrangement of calls allows for batching at the session level.
- *
- * Store success calls are not guaranteed.
- */
- public void setStoreDelegate(RepositorySessionStoreDelegate delegate) {
- Logger.debug(LOG_TAG, "Setting store delegate to " + delegate);
- this.delegate = delegate;
- }
- public abstract void store(Record record) throws NoStoreDelegateException;
-
- public void storeDone() {
- // Our default behavior will be to assume that the Runnable is
- // executed as soon as all the stores synchronously finish, so
- // our end timestamp can just be… now.
- storeDone(now());
- }
-
- public void storeDone(final long end) {
- Logger.debug(LOG_TAG, "Scheduling onStoreCompleted for after storing is done: " + end);
- Runnable command = new Runnable() {
- @Override
- public void run() {
- delegate.onStoreCompleted(end);
- }
- };
- storeWorkQueue.execute(command);
- }
-
- public abstract void wipe(RepositorySessionWipeDelegate delegate);
-
- /**
- * Synchronously perform the shared work of beginning. Throws on failure.
- * @throws InvalidSessionTransitionException
- *
- */
- protected void sharedBegin() throws InvalidSessionTransitionException {
- Logger.debug(LOG_TAG, "Shared begin.");
- if (delegateQueue.isShutdown()) {
- throw new InvalidSessionTransitionException(null);
- }
- if (storeWorkQueue.isShutdown()) {
- throw new InvalidSessionTransitionException(null);
- }
- this.transitionFrom(SessionStatus.UNSTARTED, SessionStatus.ACTIVE);
- }
-
- /**
- * Start the session. This is an appropriate place to initialize
- * data access components such as database handles.
- *
- * @param delegate
- * @throws InvalidSessionTransitionException
- */
- public void begin(RepositorySessionBeginDelegate delegate) throws InvalidSessionTransitionException {
- sharedBegin();
- delegate.deferredBeginDelegate(delegateQueue).onBeginSucceeded(this);
- }
-
- public void unbundle(RepositorySessionBundle bundle) {
- this.lastSyncTimestamp = bundle == null ? 0 : bundle.getTimestamp();
- }
-
- /**
- * Override this in your subclasses to return values to save between sessions.
- * Note that RepositorySession automatically bumps the timestamp to the time
- * the last sync began. If unbundled but not begun, this will be the same as the
- * value in the input bundle.
- *
- * The Synchronizer most likely wants to bump the bundle timestamp to be a value
- * return from a fetch call.
- */
- protected RepositorySessionBundle getBundle() {
- // Why don't we just persist the old bundle?
- long timestamp = getLastSyncTimestamp();
- RepositorySessionBundle bundle = new RepositorySessionBundle(timestamp);
- Logger.debug(LOG_TAG, "Setting bundle timestamp to " + timestamp + ".");
-
- return bundle;
- }
-
- /**
- * Just like finish(), but doesn't do any work that should only be performed
- * at the end of a successful sync, and can be called any time.
- */
- public void abort(RepositorySessionFinishDelegate delegate) {
- this.abort();
- delegate.deferredFinishDelegate(delegateQueue).onFinishSucceeded(this, this.getBundle());
- }
-
- /**
- * Abnormally terminate the repository session, freeing or closing
- * any resources that were opened during the lifetime of the session.
- */
- public void abort() {
- // TODO: do something here.
- this.setStatus(SessionStatus.ABORTED);
- try {
- storeWorkQueue.shutdownNow();
- } catch (Exception e) {
- Logger.error(LOG_TAG, "Caught exception shutting down store work queue.", e);
- }
- try {
- delegateQueue.shutdown();
- } catch (Exception e) {
- Logger.error(LOG_TAG, "Caught exception shutting down delegate queue.", e);
- }
- }
-
- /**
- * End the repository session, freeing or closing any resources
- * that were opened during the lifetime of the session.
- *
- * @param delegate notified of success or failure.
- * @throws InactiveSessionException
- */
- public void finish(final RepositorySessionFinishDelegate delegate) throws InactiveSessionException {
- try {
- this.transitionFrom(SessionStatus.ACTIVE, SessionStatus.DONE);
- delegate.deferredFinishDelegate(delegateQueue).onFinishSucceeded(this, this.getBundle());
- } catch (InvalidSessionTransitionException e) {
- Logger.error(LOG_TAG, "Tried to finish() an unstarted or already finished session");
- throw new InactiveSessionException(e);
- }
-
- Logger.trace(LOG_TAG, "Shutting down work queues.");
- storeWorkQueue.shutdown();
- delegateQueue.shutdown();
- }
-
- /**
- * Run the provided command if we're active and our delegate queue
- * is not shut down.
- */
- protected synchronized void executeDelegateCommand(Runnable command)
- throws InactiveSessionException {
- if (!isActive() || delegateQueue.isShutdown()) {
- throw new InactiveSessionException(null);
- }
- delegateQueue.execute(command);
- }
-
- public synchronized void ensureActive() throws InactiveSessionException {
- if (!isActive()) {
- throw new InactiveSessionException(null);
- }
- }
-
- public synchronized boolean isActive() {
- return status == SessionStatus.ACTIVE;
- }
-
- public synchronized SessionStatus getStatus() {
- return status;
- }
-
- public synchronized void setStatus(SessionStatus status) {
- this.status = status;
- }
-
- public synchronized void transitionFrom(SessionStatus from, SessionStatus to) throws InvalidSessionTransitionException {
- if (from == null || this.status == from) {
- Logger.trace(LOG_TAG, "Successfully transitioning from " + this.status + " to " + to);
-
- this.status = to;
- return;
- }
- Logger.warn(LOG_TAG, "Wanted to transition from " + from + " but in state " + this.status);
- throw new InvalidSessionTransitionException(null);
- }
-
- /**
- * Produce a record that is some combination of the remote and local records
- * provided.
- *
- * The returned record must be produced without mutating either remoteRecord
- * or localRecord. It is acceptable to return either remoteRecord or localRecord
- * if no modifications are to be propagated.
- *
- * The returned record *should* have the local androidID and the remote GUID,
- * and some optional merge of data from the two records.
- *
- * This method can be called with records that are identical, or differ in
- * any regard.
- *
- * This method will not be called if:
- *
- * * either record is marked as deleted, or
- * * there is no local mapping for a new remote record.
- *
- * Otherwise, it will be called precisely once.
- *
- * Side-effects (e.g., for transactional storage) can be hooked in here.
- *
- * @param remoteRecord
- * The record retrieved from upstream, already adjusted for clock skew.
- * @param localRecord
- * The record retrieved from local storage.
- * @param lastRemoteRetrieval
- * The timestamp of the last retrieved set of remote records, adjusted for
- * clock skew.
- * @param lastLocalRetrieval
- * The timestamp of the last retrieved set of local records.
- * @return
- * A Record instance to apply, or null to apply nothing.
- */
- protected Record reconcileRecords(final Record remoteRecord,
- final Record localRecord,
- final long lastRemoteRetrieval,
- final long lastLocalRetrieval) {
- Logger.debug(LOG_TAG, "Reconciling remote " + remoteRecord.guid + " against local " + localRecord.guid);
-
- if (localRecord.equalPayloads(remoteRecord)) {
- if (remoteRecord.lastModified > localRecord.lastModified) {
- Logger.debug(LOG_TAG, "Records are equal. No record application needed.");
- return null;
- }
-
- // Local wins.
- return null;
- }
-
- // TODO: Decide what to do based on:
- // * Which of the two records is modified;
- // * Whether they are equal or congruent;
- // * The modified times of each record (interpreted through the lens of clock skew);
- // * ...
- boolean localIsMoreRecent = localRecord.lastModified > remoteRecord.lastModified;
- Logger.debug(LOG_TAG, "Local record is more recent? " + localIsMoreRecent);
- Record donor = localIsMoreRecent ? localRecord : remoteRecord;
-
- // Modify the local record to match the remote record's GUID and values.
- // Preserve the local Android ID, and merge data where possible.
- // It sure would be nice if copyWithIDs didn't give a shit about androidID, mm?
- Record out = donor.copyWithIDs(remoteRecord.guid, localRecord.androidID);
-
- // We don't want to upload the record if the remote record was
- // applied without changes.
- // This logic will become more complicated as reconciling becomes smarter.
- if (!localIsMoreRecent) {
- trackGUID(out.guid);
- }
- return out;
- }
-
- /**
- * Depending on the RepositorySession implementation, track
- * that a record — most likely a brand-new record that has been
- * applied unmodified — should be tracked so as to not be uploaded
- * redundantly.
- *
- * The default implementations do nothing.
- */
- protected void trackGUID(String guid) {
- }
-
- protected synchronized void untrackGUIDs(Collection<String> guids) {
- }
-
- protected void untrackGUID(String guid) {
- }
-
- // Ah, Java. You wretched creature.
- public Iterator<String> getTrackedRecordIDs() {
- return new ArrayList<String>().iterator();
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RepositorySessionBundle.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RepositorySessionBundle.java
deleted file mode 100644
index 7908ec797..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/RepositorySessionBundle.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.NonObjectJSONException;
-
-import java.io.IOException;
-
-public class RepositorySessionBundle {
- public static final String LOG_TAG = RepositorySessionBundle.class.getSimpleName();
-
- protected static final String JSON_KEY_TIMESTAMP = "timestamp";
-
- protected final ExtendedJSONObject object;
-
- public RepositorySessionBundle(String jsonString) throws IOException, NonObjectJSONException {
-
- object = new ExtendedJSONObject(jsonString);
- }
-
- public RepositorySessionBundle(long lastSyncTimestamp) {
- object = new ExtendedJSONObject();
- this.setTimestamp(lastSyncTimestamp);
- }
-
- public long getTimestamp() {
- if (object.containsKey(JSON_KEY_TIMESTAMP)) {
- return object.getLong(JSON_KEY_TIMESTAMP);
- }
-
- return -1;
- }
-
- public void setTimestamp(long timestamp) {
- Logger.debug(LOG_TAG, "Setting timestamp to " + timestamp + ".");
- object.put(JSON_KEY_TIMESTAMP, timestamp);
- }
-
- public void bumpTimestamp(long timestamp) {
- long existing = this.getTimestamp();
- if (timestamp > existing) {
- this.setTimestamp(timestamp);
- } else {
- Logger.debug(LOG_TAG, "Timestamp " + timestamp + " not greater than " + existing + "; not bumping.");
- }
- }
-
- public String toJSONString() {
- return object.toJSONString();
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Server11Repository.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Server11Repository.java
deleted file mode 100644
index 4404fda25..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Server11Repository.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-
-import org.mozilla.gecko.sync.InfoCollections;
-import org.mozilla.gecko.sync.InfoConfiguration;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-
-import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-/**
- * A Server11Repository implements fetching and storing against the Sync 1.1 API.
- * It doesn't do crypto: that's the job of the middleware.
- *
- * @author rnewman
- */
-public class Server11Repository extends Repository {
- protected String collection;
- protected URI collectionURI;
- protected final AuthHeaderProvider authHeaderProvider;
- protected final InfoCollections infoCollections;
-
- private final InfoConfiguration infoConfiguration;
-
- /**
- * Construct a new repository that fetches and stores against the Sync 1.1. API.
- *
- * @param collection name.
- * @param storageURL full URL to storage endpoint.
- * @param authHeaderProvider to use in requests; may be null.
- * @param infoCollections instance; must not be null.
- * @throws URISyntaxException
- */
- public Server11Repository(@NonNull String collection, @NonNull String storageURL, AuthHeaderProvider authHeaderProvider, @NonNull InfoCollections infoCollections, @NonNull InfoConfiguration infoConfiguration) throws URISyntaxException {
- if (collection == null) {
- throw new IllegalArgumentException("collection must not be null");
- }
- if (storageURL == null) {
- throw new IllegalArgumentException("storageURL must not be null");
- }
- if (infoCollections == null) {
- throw new IllegalArgumentException("infoCollections must not be null");
- }
- this.collection = collection;
- this.collectionURI = new URI(storageURL + (storageURL.endsWith("/") ? collection : "/" + collection));
- this.authHeaderProvider = authHeaderProvider;
- this.infoCollections = infoCollections;
- this.infoConfiguration = infoConfiguration;
- }
-
- @Override
- public void createSession(RepositorySessionCreationDelegate delegate,
- Context context) {
- delegate.onSessionCreated(new Server11RepositorySession(this));
- }
-
- public URI collectionURI() {
- return this.collectionURI;
- }
-
- public URI collectionURI(boolean full, long newer, long limit, String sort, String ids, String offset) throws URISyntaxException {
- ArrayList<String> params = new ArrayList<String>();
- if (full) {
- params.add("full=1");
- }
- if (newer >= 0) {
- // Translate local millisecond timestamps into server decimal seconds.
- String newerString = Utils.millisecondsToDecimalSecondsString(newer);
- params.add("newer=" + newerString);
- }
- if (limit > 0) {
- params.add("limit=" + limit);
- }
- if (sort != null) {
- params.add("sort=" + sort); // We trust these values.
- }
- if (ids != null) {
- params.add("ids=" + ids); // We trust these values.
- }
- if (offset != null) {
- // Offset comes straight out of HTTP headers and it is the responsibility of the caller to URI-escape it.
- params.add("offset=" + offset);
- }
- if (params.size() == 0) {
- return this.collectionURI;
- }
-
- StringBuilder out = new StringBuilder();
- char indicator = '?';
- for (String param : params) {
- out.append(indicator);
- indicator = '&';
- out.append(param);
- }
- String uri = this.collectionURI + out.toString();
- return new URI(uri);
- }
-
- public URI wboURI(String id) throws URISyntaxException {
- return new URI(this.collectionURI + "/" + id);
- }
-
- // Override these.
- @SuppressWarnings("static-method")
- public long getDefaultBatchLimit() {
- return -1;
- }
-
- @SuppressWarnings("static-method")
- public String getDefaultSort() {
- return null;
- }
-
- public long getDefaultTotalLimit() {
- return -1;
- }
-
- public AuthHeaderProvider getAuthHeaderProvider() {
- return authHeaderProvider;
- }
-
- public boolean updateNeeded(long lastSyncTimestamp) {
- return infoCollections.updateNeeded(collection, lastSyncTimestamp);
- }
-
- @Nullable
- public Long getCollectionLastModified() {
- return infoCollections.getTimestamp(collection);
- }
-
- public InfoConfiguration getInfoConfiguration() {
- return infoConfiguration;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Server11RepositorySession.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Server11RepositorySession.java
deleted file mode 100644
index 20c735a6b..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/Server11RepositorySession.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionGuidsSinceDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-import org.mozilla.gecko.sync.repositories.downloaders.BatchingDownloader;
-import org.mozilla.gecko.sync.repositories.uploaders.BatchingUploader;
-
-public class Server11RepositorySession extends RepositorySession {
- public static final String LOG_TAG = "Server11Session";
-
- Server11Repository serverRepository;
- private BatchingUploader uploader;
- private final BatchingDownloader downloader;
-
- public Server11RepositorySession(Repository repository) {
- super(repository);
- serverRepository = (Server11Repository) repository;
- this.downloader = new BatchingDownloader(serverRepository, this);
- }
-
- public Server11Repository getServerRepository() {
- return serverRepository;
- }
-
- @Override
- public void setStoreDelegate(RepositorySessionStoreDelegate delegate) {
- this.delegate = delegate;
-
- // Now that we have the delegate, we can initialize our uploader.
- this.uploader = new BatchingUploader(this, storeWorkQueue, delegate);
- }
-
- @Override
- public void guidsSince(long timestamp,
- RepositorySessionGuidsSinceDelegate delegate) {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void fetchSince(long timestamp,
- RepositorySessionFetchRecordsDelegate delegate) {
- this.downloader.fetchSince(timestamp, delegate);
- }
-
- @Override
- public void fetchAll(RepositorySessionFetchRecordsDelegate delegate) {
- this.fetchSince(-1, delegate);
- }
-
- @Override
- public void fetch(String[] guids,
- RepositorySessionFetchRecordsDelegate delegate) {
- this.downloader.fetch(guids, delegate);
- }
-
- @Override
- public void wipe(RepositorySessionWipeDelegate delegate) {
- if (!isActive()) {
- delegate.onWipeFailed(new InactiveSessionException(null));
- return;
- }
- // TODO: implement wipe.
- }
-
- @Override
- public void store(Record record) throws NoStoreDelegateException {
- if (delegate == null) {
- throw new NoStoreDelegateException();
- }
-
- // If delegate was set, this shouldn't happen.
- if (uploader == null) {
- throw new IllegalStateException("Uploader haven't been initialized");
- }
-
- uploader.process(record);
- }
-
- @Override
- public void storeDone() {
- Logger.debug(LOG_TAG, "storeDone().");
-
- // If delegate was set, this shouldn't happen.
- if (uploader == null) {
- throw new IllegalStateException("Uploader haven't been initialized");
- }
-
- uploader.noMoreRecordsToUpload();
- }
-
- @Override
- public boolean dataAvailable() {
- return serverRepository.updateNeeded(getLastSyncTimestamp());
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/StoreFailedException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/StoreFailedException.java
deleted file mode 100644
index fcb09e32e..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/StoreFailedException.java
+++ /dev/null
@@ -1,11 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-import org.mozilla.gecko.sync.SyncException;
-
-public class StoreFailedException extends SyncException {
- private static final long serialVersionUID = 6080340122855859752L;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/StoreTracker.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/StoreTracker.java
deleted file mode 100644
index b6a3071a9..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/StoreTracker.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-import java.util.Iterator;
-
-/**
- * Our hacky version of transactional semantics. The goal is to prevent
- * the following situation:
- *
- * * AAA is not modified locally.
- * * A modified AAA is downloaded during the storing phase. Its local
- * timestamp is advanced.
- * * The direction of syncing changes, and AAA is now uploaded to the server.
- *
- * The following situation should still be supported:
- *
- * * AAA is not modified locally.
- * * A modified AAA is downloaded and merged with the local AAA.
- * * The merged AAA is uploaded to the server.
- *
- * As should:
- *
- * * AAA is modified locally.
- * * A modified AAA is downloaded, and discarded or merged.
- * * The current version of AAA is uploaded to the server.
- *
- * We achieve this by tracking GUIDs during the storing phase. If we
- * apply a record such that the local copy is substantially the same
- * as the record we just downloaded, we add it to a list of records
- * to avoid uploading. The definition of "substantially the same"
- * depends on the particular repository. The only consideration is "do we
- * want to upload this record in this sync?".
- *
- * Note that items are removed from this list when a fetch that
- * considers them for upload completes successfully. The entire list
- * is discarded when the session is completed.
- *
- * This interface exposes methods to:
- *
- * * During a store, recording that a record has been stored, and should
- * thus not be returned in subsequent fetches;
- * * During a fetch, checking whether a record should be returned.
- *
- * In the future this might also grow self-persistence.
- *
- * See also RepositorySession.trackRecord.
- *
- * @author rnewman
- *
- */
-public interface StoreTracker {
-
- /**
- * @param guid
- * The GUID of the item to track.
- * @return
- * Whether the GUID was a newly tracked value.
- */
- public boolean trackRecordForExclusion(String guid);
-
- /**
- * @param guid
- * The GUID of the item to check.
- * @return
- * true if the item is already tracked.
- */
- public boolean isTrackedForExclusion(String guid);
-
- /**
- *
- * @param guid
- * @return true if the specified GUID was removed from the tracked set.
- */
- public boolean untrackStoredForExclusion(String guid);
-
- public RecordFilter getFilter();
-
- public Iterator<String> recordsTrackedForExclusion();
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/StoreTrackingRepositorySession.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/StoreTrackingRepositorySession.java
deleted file mode 100644
index 1a5c1e96a..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/StoreTrackingRepositorySession.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories;
-
-import java.util.Collection;
-import java.util.Iterator;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-public abstract class StoreTrackingRepositorySession extends RepositorySession {
- private static final String LOG_TAG = "StoreTrackSession";
- protected StoreTracker storeTracker;
-
- protected static StoreTracker createStoreTracker() {
- return new HashSetStoreTracker();
- }
-
- public StoreTrackingRepositorySession(Repository repository) {
- super(repository);
- }
-
- @Override
- public void begin(RepositorySessionBeginDelegate delegate) throws InvalidSessionTransitionException {
- RepositorySessionBeginDelegate deferredDelegate = delegate.deferredBeginDelegate(delegateQueue);
- try {
- super.sharedBegin();
- } catch (InvalidSessionTransitionException e) {
- deferredDelegate.onBeginFailed(e);
- return;
- }
- // Or do this in your own subclass.
- storeTracker = createStoreTracker();
- deferredDelegate.onBeginSucceeded(this);
- }
-
- @Override
- protected synchronized void trackGUID(String guid) {
- if (this.storeTracker == null) {
- throw new IllegalStateException("Store tracker not yet initialized!");
- }
- this.storeTracker.trackRecordForExclusion(guid);
- }
-
- @Override
- protected synchronized void untrackGUID(String guid) {
- if (this.storeTracker == null) {
- throw new IllegalStateException("Store tracker not yet initialized!");
- }
- this.storeTracker.untrackStoredForExclusion(guid);
- }
-
- @Override
- protected synchronized void untrackGUIDs(Collection<String> guids) {
- if (this.storeTracker == null) {
- throw new IllegalStateException("Store tracker not yet initialized!");
- }
- if (guids == null) {
- return;
- }
- for (String guid : guids) {
- this.storeTracker.untrackStoredForExclusion(guid);
- }
- }
-
- protected void trackRecord(Record record) {
-
- Logger.debug(LOG_TAG, "Tracking record " + record.guid +
- " (" + record.lastModified + ") to avoid re-upload.");
- // Future: we care about the timestamp…
- trackGUID(record.guid);
- }
-
- protected void untrackRecord(Record record) {
- Logger.debug(LOG_TAG, "Un-tracking record " + record.guid + ".");
- untrackGUID(record.guid);
- }
-
- @Override
- public Iterator<String> getTrackedRecordIDs() {
- if (this.storeTracker == null) {
- throw new IllegalStateException("Store tracker not yet initialized!");
- }
- return this.storeTracker.recordsTrackedForExclusion();
- }
-
- @Override
- public void abort(RepositorySessionFinishDelegate delegate) {
- this.storeTracker = null;
- super.abort(delegate);
- }
-
- @Override
- public void finish(RepositorySessionFinishDelegate delegate) throws InactiveSessionException {
- super.finish(delegate);
- this.storeTracker = null;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java
deleted file mode 100644
index fd3c35da0..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.android;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.json.simple.JSONArray;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-
-public class AndroidBrowserBookmarksDataAccessor extends AndroidBrowserRepositoryDataAccessor {
-
- private static final String LOG_TAG = "BookmarksDataAccessor";
-
- /*
- * Fragments of SQL to make our lives easier.
- */
- private static final String BOOKMARK_IS_FOLDER = BrowserContract.Bookmarks.TYPE + " = " +
- BrowserContract.Bookmarks.TYPE_FOLDER;
-
- // SQL fragment to retrieve GUIDs whose ID mappings should be tracked by this session.
- // Exclude folders we don't want to sync.
- private static final String GUID_SHOULD_TRACK = BrowserContract.SyncColumns.GUID + " NOT IN ('" +
- BrowserContract.Bookmarks.TAGS_FOLDER_GUID + "', '" +
- BrowserContract.Bookmarks.PLACES_FOLDER_GUID + "', '" +
- BrowserContract.Bookmarks.PINNED_FOLDER_GUID + "')";
-
- private static final String EXCLUDE_SPECIAL_GUIDS_WHERE_CLAUSE;
- static {
- if (AndroidBrowserBookmarksRepositorySession.SPECIAL_GUIDS.length > 0) {
- StringBuilder b = new StringBuilder(BrowserContract.SyncColumns.GUID + " NOT IN (");
-
- int remaining = AndroidBrowserBookmarksRepositorySession.SPECIAL_GUIDS.length - 1;
- for (String specialGuid : AndroidBrowserBookmarksRepositorySession.SPECIAL_GUIDS) {
- b.append('"');
- b.append(specialGuid);
- b.append('"');
- if (remaining-- > 0) {
- b.append(", ");
- }
- }
- b.append(')');
- EXCLUDE_SPECIAL_GUIDS_WHERE_CLAUSE = b.toString();
- } else {
- EXCLUDE_SPECIAL_GUIDS_WHERE_CLAUSE = null; // null is a valid WHERE clause.
- }
- }
-
- public static final String TYPE_FOLDER = "folder";
- public static final String TYPE_BOOKMARK = "bookmark";
-
- private final RepoUtils.QueryHelper queryHelper;
-
- public AndroidBrowserBookmarksDataAccessor(Context context) {
- super(context);
- this.queryHelper = new RepoUtils.QueryHelper(context, getUri(), LOG_TAG);
- }
-
- @Override
- protected Uri getUri() {
- return BrowserContractHelpers.BOOKMARKS_CONTENT_URI;
- }
-
- protected static Uri getPositionsUri() {
- return BrowserContractHelpers.BOOKMARKS_POSITIONS_CONTENT_URI;
- }
-
- @Override
- public void wipe() {
- Uri uri = getUri();
- Logger.info(LOG_TAG, "wiping (except for special guids): " + uri);
- context.getContentResolver().delete(uri, EXCLUDE_SPECIAL_GUIDS_WHERE_CLAUSE, null);
- }
-
- private final String[] GUID_AND_ID = new String[] { BrowserContract.Bookmarks.GUID,
- BrowserContract.Bookmarks._ID };
-
- protected Cursor getGuidsIDsForFolders() throws NullCursorException {
- // Exclude items that we don't want to sync (pinned items, reading list,
- // tags, the places root), in case they've ended up in the DB.
- String where = BOOKMARK_IS_FOLDER + " AND " + GUID_SHOULD_TRACK;
- return queryHelper.safeQuery(".getGuidsIDsForFolders", GUID_AND_ID, where, null, null);
- }
-
- /**
- * Issue a request to the Content Provider to update the positions of the
- * records named by the provided GUIDs to the index of their GUID in the
- * provided array.
- *
- * @param childArray
- * A sequence of GUID strings.
- */
- public int updatePositions(ArrayList<String> childArray) {
- final int size = childArray.size();
- if (size == 0) {
- return 0;
- }
-
- Logger.debug(LOG_TAG, "Updating positions for " + size + " items.");
- String[] args = childArray.toArray(new String[size]);
- return context.getContentResolver().update(getPositionsUri(), new ContentValues(), null, args);
- }
-
- public int bumpModifiedByGUID(Collection<String> ids, long modified) {
- final int size = ids.size();
- if (size == 0) {
- return 0;
- }
-
- Logger.debug(LOG_TAG, "Bumping modified for " + size + " items to " + modified);
- String where = RepoUtils.computeSQLInClause(size, BrowserContract.Bookmarks.GUID);
- String[] selectionArgs = ids.toArray(new String[size]);
- ContentValues values = new ContentValues();
- values.put(BrowserContract.Bookmarks.DATE_MODIFIED, modified);
-
- return context.getContentResolver().update(getUri(), values, where, selectionArgs);
- }
-
- /**
- * Bump the modified time of a record by ID.
- */
- public int bumpModified(long id, long modified) {
- Logger.debug(LOG_TAG, "Bumping modified for " + id + " to " + modified);
- String where = BrowserContract.Bookmarks._ID + " = ?";
- String[] selectionArgs = new String[] { String.valueOf(id) };
- ContentValues values = new ContentValues();
- values.put(BrowserContract.Bookmarks.DATE_MODIFIED, modified);
-
- return context.getContentResolver().update(getUri(), values, where, selectionArgs);
- }
-
- protected void updateParentAndPosition(String guid, long newParentId, long position) {
- ContentValues cv = new ContentValues();
- cv.put(BrowserContract.Bookmarks.PARENT, newParentId);
- if (position >= 0) {
- cv.put(BrowserContract.Bookmarks.POSITION, position);
- }
- updateByGuid(guid, cv);
- }
-
- protected Map<String, Long> idsForGUIDs(String[] guids) throws NullCursorException {
- final String where = RepoUtils.computeSQLInClause(guids.length, BrowserContract.Bookmarks.GUID);
- Cursor c = queryHelper.safeQuery(".idsForGUIDs", GUID_AND_ID, where, guids, null);
- try {
- HashMap<String, Long> out = new HashMap<String, Long>();
- if (!c.moveToFirst()) {
- return out;
- }
- final int guidIndex = c.getColumnIndexOrThrow(BrowserContract.Bookmarks.GUID);
- final int idIndex = c.getColumnIndexOrThrow(BrowserContract.Bookmarks._ID);
- while (!c.isAfterLast()) {
- out.put(c.getString(guidIndex), c.getLong(idIndex));
- c.moveToNext();
- }
- return out;
- } finally {
- c.close();
- }
- }
-
- /**
- * Move the children of each source folder to the destination folder.
- * Bump the modified time of each child.
- * The caller should bump the modified time of the destination if desired.
- *
- * @param fromIDs the Android IDs of the source folders.
- * @param to the Android ID of the destination folder.
- * @return the number of updated rows.
- */
- protected int moveChildren(String[] fromIDs, long to) {
- long now = System.currentTimeMillis();
- long pos = -1;
-
- ContentValues cv = new ContentValues();
- cv.put(BrowserContract.Bookmarks.PARENT, to);
- cv.put(BrowserContract.Bookmarks.DATE_MODIFIED, now);
- cv.put(BrowserContract.Bookmarks.POSITION, pos);
-
- final String where = RepoUtils.computeSQLInClause(fromIDs.length, BrowserContract.Bookmarks.PARENT);
- return context.getContentResolver().update(getUri(), cv, where, fromIDs);
- }
-
- /*
- * Verify that all special GUIDs are present and that they aren't marked as deleted.
- * Insert them if they aren't there.
- */
- public void checkAndBuildSpecialGuids() throws NullCursorException {
- final String[] specialGUIDs = AndroidBrowserBookmarksRepositorySession.SPECIAL_GUIDS;
- Cursor cur = fetch(specialGUIDs);
- long placesRoot = 0;
-
- // Map from GUID to whether deleted. Non-presence implies just that.
- HashMap<String, Boolean> statuses = new HashMap<String, Boolean>(specialGUIDs.length);
- try {
- if (cur.moveToFirst()) {
- while (!cur.isAfterLast()) {
- String guid = RepoUtils.getStringFromCursor(cur, BrowserContract.SyncColumns.GUID);
- if ("places".equals(guid)) {
- placesRoot = RepoUtils.getLongFromCursor(cur, BrowserContract.CommonColumns._ID);
- }
- // Make sure none of these folders are marked as deleted.
- boolean deleted = RepoUtils.getLongFromCursor(cur, BrowserContract.SyncColumns.IS_DELETED) == 1;
- statuses.put(guid, deleted);
- cur.moveToNext();
- }
- }
- } finally {
- cur.close();
- }
-
- // Insert or undelete them if missing.
- for (String guid : specialGUIDs) {
- if (statuses.containsKey(guid)) {
- if (statuses.get(guid)) {
- // Undelete.
- Logger.info(LOG_TAG, "Undeleting special GUID " + guid);
- ContentValues cv = new ContentValues();
- cv.put(BrowserContract.SyncColumns.IS_DELETED, 0);
- updateByGuid(guid, cv);
- }
- } else {
- // Insert.
- if (guid.equals("places")) {
- // This is awkward.
- Logger.info(LOG_TAG, "No places root. Inserting one.");
- placesRoot = insertSpecialFolder("places", 0);
- } else if (guid.equals("mobile")) {
- Logger.info(LOG_TAG, "No mobile folder. Inserting one under the places root.");
- insertSpecialFolder("mobile", placesRoot);
- } else {
- // unfiled, menu, toolbar.
- Logger.info(LOG_TAG, "No " + guid + " root. Inserting one under places (" + placesRoot + ").");
- insertSpecialFolder(guid, placesRoot);
- }
- }
- }
- }
-
- private long insertSpecialFolder(String guid, long parentId) {
- BookmarkRecord record = new BookmarkRecord(guid);
- record.title = AndroidBrowserBookmarksRepositorySession.SPECIAL_GUIDS_MAP.get(guid);
- record.type = "folder";
- record.androidParentID = parentId;
- return ContentUris.parseId(insert(record));
- }
-
- @Override
- protected ContentValues getContentValues(Record record) {
- BookmarkRecord rec = (BookmarkRecord) record;
-
- if (rec.deleted) {
- ContentValues cv = new ContentValues();
- cv.put(BrowserContract.SyncColumns.GUID, rec.guid);
- cv.put(BrowserContract.Bookmarks.IS_DELETED, 1);
- return cv;
- }
-
- final int recordType = BrowserContractHelpers.typeCodeForString(rec.type);
- if (recordType == -1) {
- throw new IllegalStateException("Unexpected record type " + rec.type);
- }
-
- ContentValues cv = new ContentValues();
- cv.put(BrowserContract.SyncColumns.GUID, rec.guid);
- cv.put(BrowserContract.Bookmarks.TYPE, recordType);
- cv.put(BrowserContract.Bookmarks.TITLE, rec.title);
- cv.put(BrowserContract.Bookmarks.URL, rec.bookmarkURI);
- cv.put(BrowserContract.Bookmarks.DESCRIPTION, rec.description);
- if (rec.tags == null) {
- rec.tags = new JSONArray();
- }
- cv.put(BrowserContract.Bookmarks.TAGS, rec.tags.toJSONString());
- cv.put(BrowserContract.Bookmarks.KEYWORD, rec.keyword);
- cv.put(BrowserContract.Bookmarks.PARENT, rec.androidParentID);
- cv.put(BrowserContract.Bookmarks.POSITION, rec.androidPosition);
-
- // Note that we don't set the modified timestamp: we allow the
- // content provider to do that for us.
- return cv;
- }
-
- /**
- * Returns a cursor over non-deleted records that list the given androidID as a parent.
- */
- public Cursor getChildren(long androidID) throws NullCursorException {
- return getChildren(androidID, false);
- }
-
- /**
- * Returns a cursor with any records that list the given androidID as a parent.
- * Excludes 'places', and optionally any deleted records.
- */
- public Cursor getChildren(long androidID, boolean includeDeleted) throws NullCursorException {
- final String where = BrowserContract.Bookmarks.PARENT + " = ? AND " +
- BrowserContract.SyncColumns.GUID + " <> ? " +
- (!includeDeleted ? ("AND " + BrowserContract.SyncColumns.IS_DELETED + " = 0") : "");
-
- final String[] args = new String[] { String.valueOf(androidID), "places" };
-
- // Order by position, falling back on creation date and ID.
- final String order = BrowserContract.Bookmarks.POSITION + ", " +
- BrowserContract.SyncColumns.DATE_CREATED + ", " +
- BrowserContract.Bookmarks._ID;
- return queryHelper.safeQuery(".getChildren", getAllColumns(), where, args, order);
- }
-
-
- @Override
- protected String[] getAllColumns() {
- return BrowserContractHelpers.BookmarkColumns;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserBookmarksRepository.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserBookmarksRepository.java
deleted file mode 100644
index 38520fd7a..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserBookmarksRepository.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.android;
-
-import org.mozilla.gecko.sync.repositories.BookmarksRepository;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-
-import android.content.Context;
-
-public class AndroidBrowserBookmarksRepository extends AndroidBrowserRepository implements BookmarksRepository {
-
- @Override
- protected void sessionCreator(RepositorySessionCreationDelegate delegate, Context context) {
- AndroidBrowserBookmarksRepositorySession session = new AndroidBrowserBookmarksRepositorySession(AndroidBrowserBookmarksRepository.this, context);
- final RepositorySessionCreationDelegate deferredCreationDelegate = delegate.deferredCreationDelegate();
- deferredCreationDelegate.onSessionCreated(session);
- }
-
- @Override
- protected AndroidBrowserRepositoryDataAccessor getDataAccessor(Context context) {
- return new AndroidBrowserBookmarksDataAccessor(context);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java
deleted file mode 100644
index fb79901a1..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java
+++ /dev/null
@@ -1,1107 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.android;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.TreeMap;
-
-import org.json.simple.JSONArray;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.repositories.InactiveSessionException;
-import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
-import org.mozilla.gecko.sync.repositories.NoGuidForIdException;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.ParentNotFoundException;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
-import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-import android.content.ContentUris;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-
-public class AndroidBrowserBookmarksRepositorySession extends AndroidBrowserRepositorySession
- implements BookmarksInsertionManager.BookmarkInserter {
-
- public static final int DEFAULT_DELETION_FLUSH_THRESHOLD = 50;
- public static final int DEFAULT_INSERTION_FLUSH_THRESHOLD = 50;
-
- // TODO: synchronization for these.
- private final HashMap<String, Long> parentGuidToIDMap = new HashMap<String, Long>();
- private final HashMap<Long, String> parentIDToGuidMap = new HashMap<Long, String>();
-
- /**
- * Some notes on reparenting/reordering.
- *
- * Fennec stores new items with a high-negative position, because it doesn't care.
- * On the other hand, it also doesn't give us any help managing positions.
- *
- * We can process records and folders in any order, though we'll usually see folders
- * first because their sortindex is larger.
- *
- * We can also see folders that refer to children we haven't seen, and children we
- * won't see (perhaps due to a TTL, perhaps due to a limit on our fetch).
- *
- * And of course folders can refer to local children (including ones that might
- * be reconciled into oblivion!), or local children in other folders. And the local
- * version of a folder -- which might be a reconciling target, or might not -- can
- * have local additions or removals. (That causes complications with on-the-fly
- * reordering: we don't know in advance which records will even exist by the end
- * of the sync.)
- *
- * We opt to leave records in a reasonable state as we go, applying reordering/
- * reparenting operations whenever possible. A final sequence is applied after all
- * incoming records have been handled.
- *
- * As such, we need to track a bunch of stuff as we go:
- *
- * • For each downloaded folder, the array of children. These will be server GUIDs,
- * but not necessarily identical to the remote list: if we download a record and
- * it's been locally moved, it must be removed from this child array.
- *
- * This mapping can be discarded when final reordering has occurred, either on
- * store completion or when every child has been seen within this session.
- *
- * • A list of orphans: records whose parent folder does not yet exist. This can be
- * trimmed as orphans are reparented.
- *
- * • Mappings from folder GUIDs to folder IDs, so that we can parent items without
- * having to look in the DB. Of course, this must be kept up-to-date as we
- * reconcile.
- *
- * Reordering also needs to occur during fetch. That is, a folder might have been
- * created locally, or modified locally without any remote changes. An order must
- * be generated for the folder's children array, and it must be persisted into the
- * database to act as a starting point for future changes. But of course we don't
- * want to incur a database write if the children already have a satisfactory order.
- *
- * Do we also need a list of "adopters", parents that are still waiting for children?
- * As items get picked out of the orphans list, we can do on-the-fly ordering, until
- * we're left with lonely records at the end.
- *
- * As we modify local folders, perhaps by moving children out of their purview, we
- * must bump their modification time so as to cause them to be uploaded on the next
- * stage of syncing. The same applies to simple reordering.
- */
-
- // TODO: can we guarantee serial access to these?
- private final HashMap<String, ArrayList<String>> missingParentToChildren = new HashMap<String, ArrayList<String>>();
- private final HashMap<String, JSONArray> parentToChildArray = new HashMap<String, JSONArray>();
- private int needsReparenting = 0;
-
- private final AndroidBrowserBookmarksDataAccessor dataAccessor;
-
- protected BookmarksDeletionManager deletionManager;
- protected BookmarksInsertionManager insertionManager;
-
- /**
- * An array of known-special GUIDs.
- */
- public static final String[] SPECIAL_GUIDS = new String[] {
- // Mobile and desktop places roots have to come first.
- "places",
- "mobile",
- "toolbar",
- "menu",
- "unfiled"
- };
-
- /**
- * = A note about folder mapping =
- *
- * Note that _none_ of Places's folders actually have a special GUID. They're all
- * randomly generated. Special folders are indicated by membership in the
- * moz_bookmarks_roots table, and by having the parent `1`.
- *
- * Additionally, the mobile root is annotated. In Firefox Sync, PlacesUtils is
- * used to find the IDs of these special folders.
- *
- * We need to consume records with these various GUIDs, producing a local
- * representation which we are able to stably map upstream.
- *
- * Android Sync skips over the contents of some special GUIDs -- `places`, `tags`,
- * etc. -- when finding IDs.
- * Some of these special GUIDs are part of desktop structure (places, tags). Some
- * are part of Fennec's custom data (readinglist, pinned).
- *
- * We don't want to upload or apply these records.
- *
- * That is:
- *
- * * We should not upload a `places`,`tags`, `readinglist`, or `pinned` record.
- * * We can stably _store_ menu/toolbar/unfiled/mobile as special GUIDs, and set
- * their parent ID as appropriate on upload.
- *
- * Fortunately, Fennec stores our representation of the data, not Places: that is,
- * there's a "places" root, containing "mobile", "menu", "toolbar", etc.
- *
- * These are guaranteed to exist when the database is created.
- *
- * = Places folders =
- *
- * guid root_name folder_id parent
- * ---------- ---------- ---------- ----------
- * ? places 1 0
- * ? menu 2 1
- * ? toolbar 3 1
- * ? tags 4 1
- * ? unfiled 5 1
- *
- * ? mobile* 474 1
- *
- *
- * = Fennec folders =
- *
- * guid folder_id parent
- * ---------- ---------- ----------
- * places 0 0
- * mobile 1 0
- * menu 2 0
- * etc.
- *
- */
- public static final Map<String, String> SPECIAL_GUID_PARENTS;
- static {
- HashMap<String, String> m = new HashMap<String, String>();
- m.put("places", null);
- m.put("menu", "places");
- m.put("toolbar", "places");
- m.put("tags", "places");
- m.put("unfiled", "places");
- m.put("mobile", "places");
- SPECIAL_GUID_PARENTS = Collections.unmodifiableMap(m);
- }
-
-
- /**
- * A map of guids to their localized name strings.
- */
- // Oh, if only we could make this final and initialize it in the static initializer.
- public static Map<String, String> SPECIAL_GUIDS_MAP;
-
- /**
- * Return true if the provided record GUID should be skipped
- * in child lists or fetch results.
- *
- * @param recordGUID the GUID of the record to check.
- * @return true if the record should be skipped.
- */
- public static boolean forbiddenGUID(final String recordGUID) {
- return recordGUID == null ||
- BrowserContract.Bookmarks.PINNED_FOLDER_GUID.equals(recordGUID) ||
- BrowserContract.Bookmarks.PLACES_FOLDER_GUID.equals(recordGUID) ||
- BrowserContract.Bookmarks.TAGS_FOLDER_GUID.equals(recordGUID);
- }
-
- /**
- * Return true if the provided parent GUID's children should
- * be skipped in child lists or fetch results.
- * This differs from {@link #forbiddenGUID(String)} in that we're skipping
- * part of the hierarchy.
- *
- * @param parentGUID the GUID of parent of the record to check.
- * @return true if the record should be skipped.
- */
- public static boolean forbiddenParent(final String parentGUID) {
- return parentGUID == null ||
- BrowserContract.Bookmarks.PINNED_FOLDER_GUID.equals(parentGUID);
- }
-
- public AndroidBrowserBookmarksRepositorySession(Repository repository, Context context) {
- super(repository);
-
- if (SPECIAL_GUIDS_MAP == null) {
- HashMap<String, String> m = new HashMap<String, String>();
-
- // Note that we always use the literal name "mobile" for the Mobile Bookmarks
- // folder, regardless of its actual name in the database or the Fennec UI.
- // This is to match desktop (working around Bug 747699) and to avoid a similar
- // issue locally. See Bug 748898.
- m.put("mobile", "mobile");
-
- // Other folders use their contextualized names, and we simply rely on
- // these not changing, matching desktop, and such to avoid issues.
- m.put("menu", context.getString(R.string.bookmarks_folder_menu));
- m.put("places", context.getString(R.string.bookmarks_folder_places));
- m.put("toolbar", context.getString(R.string.bookmarks_folder_toolbar));
- m.put("unfiled", context.getString(R.string.bookmarks_folder_unfiled));
-
- SPECIAL_GUIDS_MAP = Collections.unmodifiableMap(m);
- }
-
- dbHelper = new AndroidBrowserBookmarksDataAccessor(context);
- dataAccessor = (AndroidBrowserBookmarksDataAccessor) dbHelper;
- }
-
- private static int getTypeFromCursor(Cursor cur) {
- return RepoUtils.getIntFromCursor(cur, BrowserContract.Bookmarks.TYPE);
- }
-
- private static boolean rowIsFolder(Cursor cur) {
- return getTypeFromCursor(cur) == BrowserContract.Bookmarks.TYPE_FOLDER;
- }
-
- private String getGUIDForID(long androidID) {
- String guid = parentIDToGuidMap.get(androidID);
- trace(" " + androidID + " => " + guid);
- return guid;
- }
-
- private long getIDForGUID(String guid) {
- Long id = parentGuidToIDMap.get(guid);
- if (id == null) {
- Logger.warn(LOG_TAG, "Couldn't find local ID for GUID " + guid);
- return -1;
- }
- return id;
- }
-
- private String getGUID(Cursor cur) {
- return RepoUtils.getStringFromCursor(cur, "guid");
- }
-
- private long getParentID(Cursor cur) {
- return RepoUtils.getLongFromCursor(cur, BrowserContract.Bookmarks.PARENT);
- }
-
- // More efficient for bulk operations.
- private long getPosition(Cursor cur, int positionIndex) {
- return cur.getLong(positionIndex);
- }
- private long getPosition(Cursor cur) {
- return RepoUtils.getLongFromCursor(cur, BrowserContract.Bookmarks.POSITION);
- }
-
- private String getParentName(String parentGUID) throws ParentNotFoundException, NullCursorException {
- if (parentGUID == null) {
- return "";
- }
- if (SPECIAL_GUIDS_MAP.containsKey(parentGUID)) {
- return SPECIAL_GUIDS_MAP.get(parentGUID);
- }
-
- // Get parent name from database.
- String parentName = "";
- Cursor name = dataAccessor.fetch(new String[] { parentGUID });
- try {
- name.moveToFirst();
- if (!name.isAfterLast()) {
- parentName = RepoUtils.getStringFromCursor(name, BrowserContract.Bookmarks.TITLE);
- }
- else {
- Logger.error(LOG_TAG, "Couldn't find record with guid '" + parentGUID + "' when looking for parent name.");
- throw new ParentNotFoundException(null);
- }
- } finally {
- name.close();
- }
- return parentName;
- }
-
- /**
- * Retrieve the child array for a record, repositioning and updating the database as necessary.
- *
- * @param folderID
- * The database ID of the folder.
- * @param persist
- * True if generated positions should be written to the database. The modified
- * time of the parent folder is only bumped if this is true.
- * @param childArray
- * A new, empty JSONArray which will be populated with an array of GUIDs.
- * @return
- * True if the resulting array is "clean" (i.e., reflects the content of the database).
- * @throws NullCursorException
- */
- @SuppressWarnings("unchecked")
- private boolean getChildrenArray(long folderID, boolean persist, JSONArray childArray) throws NullCursorException {
- trace("Calling getChildren for androidID " + folderID);
- Cursor children = dataAccessor.getChildren(folderID);
- try {
- if (!children.moveToFirst()) {
- trace("No children: empty cursor.");
- return true;
- }
- final int positionIndex = children.getColumnIndex(BrowserContract.Bookmarks.POSITION);
- final int count = children.getCount();
- Logger.debug(LOG_TAG, "Expecting " + count + " children.");
-
- // Sorted by requested position.
- TreeMap<Long, ArrayList<String>> guids = new TreeMap<Long, ArrayList<String>>();
-
- while (!children.isAfterLast()) {
- final String childGuid = getGUID(children);
- final long childPosition = getPosition(children, positionIndex);
- trace(" Child GUID: " + childGuid);
- trace(" Child position: " + childPosition);
- Utils.addToIndexBucketMap(guids, Math.abs(childPosition), childGuid);
- children.moveToNext();
- }
-
- // This will suffice for taking a jumble of records and indices and
- // producing a sorted sequence that preserves some kind of order --
- // from the abs of the position, falling back on cursor order (that
- // is, creation time and ID).
- // Note that this code is not intended to merge values from two sources!
- boolean changed = false;
- int i = 0;
- for (Entry<Long, ArrayList<String>> entry : guids.entrySet()) {
- long pos = entry.getKey();
- int atPos = entry.getValue().size();
-
- // If every element has a different index, and the indices are
- // in strict natural order, then changed will be false.
- if (atPos > 1 || pos != i) {
- changed = true;
- }
-
- ++i;
-
- for (String guid : entry.getValue()) {
- if (!forbiddenGUID(guid)) {
- childArray.add(guid);
- }
- }
- }
-
- if (Logger.shouldLogVerbose(LOG_TAG)) {
- // Don't JSON-encode unless we're logging.
- Logger.trace(LOG_TAG, "Output child array: " + childArray.toJSONString());
- }
-
- if (!changed) {
- Logger.debug(LOG_TAG, "Nothing moved! Database reflects child array.");
- return true;
- }
-
- if (!persist) {
- Logger.debug(LOG_TAG, "Returned array does not match database, and not persisting.");
- return false;
- }
-
- Logger.debug(LOG_TAG, "Generating child array required moving records. Updating DB.");
- final long time = now();
- if (0 < dataAccessor.updatePositions(childArray)) {
- Logger.debug(LOG_TAG, "Bumping parent time to " + time + ".");
- dataAccessor.bumpModified(folderID, time);
- }
- return true;
- } finally {
- children.close();
- }
- }
-
- protected static boolean isDeleted(Cursor cur) {
- return RepoUtils.getLongFromCursor(cur, BrowserContract.SyncColumns.IS_DELETED) != 0;
- }
-
- @Override
- protected Record retrieveDuringStore(Cursor cur) throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
- // During storing of a retrieved record, we never care about the children
- // array that's already present in the database -- we don't use it for
- // reconciling. Skip all that effort for now.
- return retrieveRecord(cur, false);
- }
-
- @Override
- protected Record retrieveDuringFetch(Cursor cur) throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
- return retrieveRecord(cur, true);
- }
-
- /**
- * Build a record from a cursor, with a flag to dictate whether the
- * children array should be computed and written back into the database.
- */
- protected BookmarkRecord retrieveRecord(Cursor cur, boolean computeAndPersistChildren) throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
- String recordGUID = getGUID(cur);
- Logger.trace(LOG_TAG, "Record from mirror cursor: " + recordGUID);
-
- if (forbiddenGUID(recordGUID)) {
- Logger.debug(LOG_TAG, "Ignoring " + recordGUID + " record in recordFromMirrorCursor.");
- return null;
- }
-
- // Short-cut for deleted items.
- if (isDeleted(cur)) {
- return AndroidBrowserBookmarksRepositorySession.bookmarkFromMirrorCursor(cur, null, null, null);
- }
-
- long androidParentID = getParentID(cur);
-
- // Ensure special folders stay in the right place.
- String androidParentGUID = SPECIAL_GUID_PARENTS.get(recordGUID);
- if (androidParentGUID == null) {
- androidParentGUID = getGUIDForID(androidParentID);
- }
-
- boolean needsReparenting = false;
-
- if (androidParentGUID == null) {
- Logger.debug(LOG_TAG, "No parent GUID for record " + recordGUID + " with parent " + androidParentID);
- // If the parent has been stored and somehow has a null GUID, throw an error.
- if (parentIDToGuidMap.containsKey(androidParentID)) {
- Logger.error(LOG_TAG, "Have the parent android ID for the record but the parent's GUID wasn't found.");
- throw new NoGuidForIdException(null);
- }
-
- // We have a parent ID but it's wrong. If the record is deleted,
- // we'll just say that it was in the Unsorted Bookmarks folder.
- // If not, we'll move it into Mobile Bookmarks.
- needsReparenting = true;
- }
-
- // If record is a folder, and we want to see children at this time, then build out the children array.
- final JSONArray childArray;
- if (computeAndPersistChildren) {
- childArray = getChildrenArrayForRecordCursor(cur, recordGUID, true);
- } else {
- childArray = null;
- }
- String parentName = getParentName(androidParentGUID);
- BookmarkRecord bookmark = AndroidBrowserBookmarksRepositorySession.bookmarkFromMirrorCursor(cur, androidParentGUID, parentName, childArray);
-
- if (bookmark == null) {
- Logger.warn(LOG_TAG, "Unable to extract bookmark from cursor. Record GUID " + recordGUID +
- ", parent " + androidParentGUID + "/" + androidParentID);
- return null;
- }
-
- if (needsReparenting) {
- Logger.warn(LOG_TAG, "Bookmark record " + recordGUID + " has a bad parent pointer. Reparenting now.");
-
- String destination = bookmark.deleted ? "unfiled" : "mobile";
- bookmark.androidParentID = getIDForGUID(destination);
- bookmark.androidPosition = getPosition(cur);
- bookmark.parentID = destination;
- bookmark.parentName = getParentName(destination);
- if (!bookmark.deleted) {
- // Actually move it.
- // TODO: compute position. Persist.
- relocateBookmark(bookmark);
- }
- }
-
- return bookmark;
- }
-
- /**
- * Ensure that the local database row for the provided bookmark
- * reflects this record's parent information.
- *
- * @param bookmark
- */
- private void relocateBookmark(BookmarkRecord bookmark) {
- dataAccessor.updateParentAndPosition(bookmark.guid, bookmark.androidParentID, bookmark.androidPosition);
- }
-
- protected JSONArray getChildrenArrayForRecordCursor(Cursor cur, String recordGUID, boolean persist) throws NullCursorException {
- boolean isFolder = rowIsFolder(cur);
- if (!isFolder) {
- return null;
- }
-
- long androidID = parentGuidToIDMap.get(recordGUID);
- JSONArray childArray = new JSONArray();
- getChildrenArray(androidID, persist, childArray);
-
- Logger.debug(LOG_TAG, "Fetched " + childArray.size() + " children for " + recordGUID);
- return childArray;
- }
-
- @Override
- public boolean shouldIgnore(Record record) {
- if (!(record instanceof BookmarkRecord)) {
- return true;
- }
- if (record.deleted) {
- return false;
- }
-
- BookmarkRecord bmk = (BookmarkRecord) record;
-
- if (forbiddenGUID(bmk.guid)) {
- Logger.debug(LOG_TAG, "Ignoring forbidden record with guid: " + bmk.guid);
- return true;
- }
-
- if (forbiddenParent(bmk.parentID)) {
- Logger.debug(LOG_TAG, "Ignoring child " + bmk.guid + " of forbidden parent folder " + bmk.parentID);
- return true;
- }
-
- if (BrowserContractHelpers.isSupportedType(bmk.type)) {
- return false;
- }
-
- Logger.debug(LOG_TAG, "Ignoring record with guid: " + bmk.guid + " and type: " + bmk.type);
- return true;
- }
-
- @Override
- public void begin(RepositorySessionBeginDelegate delegate) throws InvalidSessionTransitionException {
- // Check for the existence of special folders
- // and insert them if they don't exist.
- Cursor cur;
- try {
- Logger.debug(LOG_TAG, "Check and build special GUIDs.");
- dataAccessor.checkAndBuildSpecialGuids();
- cur = dataAccessor.getGuidsIDsForFolders();
- Logger.debug(LOG_TAG, "Got GUIDs for folders.");
- } catch (android.database.sqlite.SQLiteConstraintException e) {
- Logger.error(LOG_TAG, "Got sqlite constraint exception working with Fennec bookmark DB.", e);
- delegate.onBeginFailed(e);
- return;
- } catch (Exception e) {
- delegate.onBeginFailed(e);
- return;
- }
-
- // To deal with parent mapping of bookmarks we have to do some
- // hairy stuff. Here's the setup for it.
-
- Logger.debug(LOG_TAG, "Preparing folder ID mappings.");
-
- // Fake our root.
- Logger.debug(LOG_TAG, "Tracking places root as ID 0.");
- parentIDToGuidMap.put(0L, "places");
- parentGuidToIDMap.put("places", 0L);
- try {
- cur.moveToFirst();
- while (!cur.isAfterLast()) {
- String guid = getGUID(cur);
- long id = RepoUtils.getLongFromCursor(cur, BrowserContract.Bookmarks._ID);
- parentGuidToIDMap.put(guid, id);
- parentIDToGuidMap.put(id, guid);
- Logger.debug(LOG_TAG, "GUID " + guid + " maps to " + id);
- cur.moveToNext();
- }
- } finally {
- cur.close();
- }
- deletionManager = new BookmarksDeletionManager(dataAccessor, DEFAULT_DELETION_FLUSH_THRESHOLD);
-
- // We just crawled the database enumerating all folders; we'll start the
- // insertion manager with exactly these folders as the known parents (the
- // collection is copied) in the manager constructor.
- insertionManager = new BookmarksInsertionManager(DEFAULT_INSERTION_FLUSH_THRESHOLD, parentGuidToIDMap.keySet(), this);
-
- Logger.debug(LOG_TAG, "Done with initial setup of bookmarks session.");
- super.begin(delegate);
- }
-
- /**
- * Implement method of BookmarksInsertionManager.BookmarkInserter.
- */
- @Override
- public boolean insertFolder(BookmarkRecord record) {
- // A folder that is *not* deleted needs its androidID updated, so that
- // updateBookkeeping can re-parent, etc.
- Record toStore = prepareRecord(record);
- try {
- Uri recordURI = dbHelper.insert(toStore);
- if (recordURI == null) {
- delegate.onRecordStoreFailed(new RuntimeException("Got null URI inserting folder with guid " + toStore.guid + "."), record.guid);
- return false;
- }
- toStore.androidID = ContentUris.parseId(recordURI);
- Logger.debug(LOG_TAG, "Inserted folder with guid " + toStore.guid + " as androidID " + toStore.androidID);
-
- updateBookkeeping(toStore);
- } catch (Exception e) {
- delegate.onRecordStoreFailed(e, record.guid);
- return false;
- }
- trackRecord(toStore);
- delegate.onRecordStoreSucceeded(record.guid);
- return true;
- }
-
- /**
- * Implement method of BookmarksInsertionManager.BookmarkInserter.
- */
- @Override
- public void bulkInsertNonFolders(Collection<BookmarkRecord> records) {
- // All of these records are *not* deleted and *not* folders, so we don't
- // need to update androidID at all!
- // TODO: persist records that fail to insert for later retry.
- ArrayList<Record> toStores = new ArrayList<Record>(records.size());
- for (Record record : records) {
- toStores.add(prepareRecord(record));
- }
-
- try {
- int stored = dataAccessor.bulkInsert(toStores);
- if (stored != toStores.size()) {
- // Something failed; most pessimistic action is to declare that all insertions failed.
- // TODO: perform the bulkInsert in a transaction and rollback unless all insertions succeed?
- for (Record failed : toStores) {
- delegate.onRecordStoreFailed(new RuntimeException("Possibly failed to bulkInsert non-folder with guid " + failed.guid + "."), failed.guid);
- }
- return;
- }
- } catch (NullCursorException e) {
- for (Record failed : toStores) {
- delegate.onRecordStoreFailed(e, failed.guid);
- }
- return;
- }
-
- // Success For All!
- for (Record succeeded : toStores) {
- try {
- updateBookkeeping(succeeded);
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Got exception updating bookkeeping of non-folder with guid " + succeeded.guid + ".", e);
- }
- trackRecord(succeeded);
- delegate.onRecordStoreSucceeded(succeeded.guid);
- }
- }
-
- @Override
- public void finish(RepositorySessionFinishDelegate delegate) throws InactiveSessionException {
- // Allow these to be GCed.
- deletionManager = null;
- insertionManager = null;
-
- // Override finish to do this check; make sure all records
- // needing re-parenting have been re-parented.
- if (needsReparenting != 0) {
- Logger.error(LOG_TAG, "Finish called but " + needsReparenting +
- " bookmark(s) have been placed in unsorted bookmarks and not been reparented.");
-
- // TODO: handling of failed reparenting.
- // E.g., delegate.onFinishFailed(new BookmarkNeedsReparentingException(null));
- }
- super.finish(delegate);
- };
-
- @Override
- public void setStoreDelegate(RepositorySessionStoreDelegate delegate) {
- super.setStoreDelegate(delegate);
-
- if (deletionManager != null) {
- deletionManager.setDelegate(delegate);
- }
- }
-
- @Override
- protected Record reconcileRecords(Record remoteRecord, Record localRecord,
- long lastRemoteRetrieval,
- long lastLocalRetrieval) {
-
- BookmarkRecord reconciled = (BookmarkRecord) super.reconcileRecords(remoteRecord, localRecord,
- lastRemoteRetrieval,
- lastLocalRetrieval);
-
- // For now we *always* use the remote record's children array as a starting point.
- // We won't write it into the database yet; we'll record it and process as we go.
- reconciled.children = ((BookmarkRecord) remoteRecord).children;
-
- // *Always* track folders, though: if we decide we need to reposition items, we'll
- // untrack later.
- if (reconciled.isFolder()) {
- trackRecord(reconciled);
- }
- return reconciled;
- }
-
- /**
- * Rename mobile folders to "mobile", both in and out. The other half of
- * this logic lives in {@link #computeParentFields(BookmarkRecord, String, String)}, where
- * the parent name of a record is set from {@link #SPECIAL_GUIDS_MAP} rather than
- * from source data.
- *
- * Apply this approach generally for symmetry.
- */
- @Override
- protected void fixupRecord(Record record) {
- final BookmarkRecord r = (BookmarkRecord) record;
- final String parentName = SPECIAL_GUIDS_MAP.get(r.parentID);
- if (parentName == null) {
- return;
- }
- if (Logger.shouldLogVerbose(LOG_TAG)) {
- Logger.trace(LOG_TAG, "Replacing parent name \"" + r.parentName + "\" with \"" + parentName + "\".");
- }
- r.parentName = parentName;
- }
-
- @Override
- protected Record prepareRecord(Record record) {
- if (record.deleted) {
- Logger.debug(LOG_TAG, "No need to prepare deleted record " + record.guid);
- return record;
- }
-
- BookmarkRecord bmk = (BookmarkRecord) record;
-
- if (!isSpecialRecord(record)) {
- // We never want to reparent special records.
- handleParenting(bmk);
- }
-
- if (Logger.LOG_PERSONAL_INFORMATION) {
- if (bmk.isFolder()) {
- Logger.pii(LOG_TAG, "Inserting folder " + bmk.guid + ", " + bmk.title +
- " with parent " + bmk.androidParentID +
- " (" + bmk.parentID + ", " + bmk.parentName +
- ", " + bmk.androidPosition + ")");
- } else {
- Logger.pii(LOG_TAG, "Inserting bookmark " + bmk.guid + ", " + bmk.title + ", " +
- bmk.bookmarkURI + " with parent " + bmk.androidParentID +
- " (" + bmk.parentID + ", " + bmk.parentName +
- ", " + bmk.androidPosition + ")");
- }
- } else {
- if (bmk.isFolder()) {
- Logger.debug(LOG_TAG, "Inserting folder " + bmk.guid + ", parent " +
- bmk.androidParentID +
- " (" + bmk.parentID + ", " + bmk.androidPosition + ")");
- } else {
- Logger.debug(LOG_TAG, "Inserting bookmark " + bmk.guid + " with parent " +
- bmk.androidParentID +
- " (" + bmk.parentID + ", " + ", " + bmk.androidPosition + ")");
- }
- }
- return bmk;
- }
-
- /**
- * If the provided record doesn't have correct parent information,
- * update appropriate bookkeeping to improve the situation.
- *
- * @param bmk
- */
- private void handleParenting(BookmarkRecord bmk) {
- if (parentGuidToIDMap.containsKey(bmk.parentID)) {
- bmk.androidParentID = parentGuidToIDMap.get(bmk.parentID);
-
- // Might as well set a basic position from the downloaded children array.
- JSONArray children = parentToChildArray.get(bmk.parentID);
- if (children != null) {
- int index = children.indexOf(bmk.guid);
- if (index >= 0) {
- bmk.androidPosition = index;
- }
- }
- }
- else {
- bmk.androidParentID = parentGuidToIDMap.get("unfiled");
- ArrayList<String> children;
- if (missingParentToChildren.containsKey(bmk.parentID)) {
- children = missingParentToChildren.get(bmk.parentID);
- } else {
- children = new ArrayList<String>();
- }
- children.add(bmk.guid);
- needsReparenting++;
- missingParentToChildren.put(bmk.parentID, children);
- }
- }
-
- private boolean isSpecialRecord(Record record) {
- return SPECIAL_GUID_PARENTS.containsKey(record.guid);
- }
-
- @Override
- protected void updateBookkeeping(Record record) throws NoGuidForIdException,
- NullCursorException,
- ParentNotFoundException {
- super.updateBookkeeping(record);
- BookmarkRecord bmk = (BookmarkRecord) record;
-
- // If record is folder, update maps and re-parent children if necessary.
- if (!bmk.isFolder()) {
- Logger.debug(LOG_TAG, "Not a folder. No bookkeeping.");
- return;
- }
-
- Logger.debug(LOG_TAG, "Updating bookkeeping for folder " + record.guid);
-
- // Mappings between ID and GUID.
- // TODO: update our persisted children arrays!
- // TODO: if our Android ID just changed, replace parents for all of our children.
- parentGuidToIDMap.put(bmk.guid, bmk.androidID);
- parentIDToGuidMap.put(bmk.androidID, bmk.guid);
-
- JSONArray childArray = bmk.children;
-
- if (Logger.shouldLogVerbose(LOG_TAG)) {
- Logger.trace(LOG_TAG, bmk.guid + " has children " + childArray.toJSONString());
- }
- parentToChildArray.put(bmk.guid, childArray);
-
- // Re-parent.
- if (missingParentToChildren.containsKey(bmk.guid)) {
- for (String child : missingParentToChildren.get(bmk.guid)) {
- // This might return -1; that's OK, the bookmark will
- // be properly repositioned later.
- long position = childArray.indexOf(child);
- dataAccessor.updateParentAndPosition(child, bmk.androidID, position);
- needsReparenting--;
- }
- missingParentToChildren.remove(bmk.guid);
- }
- }
-
- @Override
- protected void insert(Record record) throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
- try {
- insertionManager.enqueueRecord((BookmarkRecord) record);
- } catch (Exception e) {
- throw new NullCursorException(e);
- }
- }
-
- @Override
- protected void storeRecordDeletion(final Record record, final Record existingRecord) {
- if (SPECIAL_GUIDS_MAP.containsKey(record.guid)) {
- Logger.debug(LOG_TAG, "Told to delete record " + record.guid + ". Ignoring.");
- return;
- }
- final BookmarkRecord bookmarkRecord = (BookmarkRecord) record;
- final BookmarkRecord existingBookmark = (BookmarkRecord) existingRecord;
- final boolean isFolder = existingBookmark.isFolder();
- final String parentGUID = existingBookmark.parentID;
- deletionManager.deleteRecord(bookmarkRecord.guid, isFolder, parentGUID);
- }
-
- protected void flushQueues() {
- long now = now();
- Logger.debug(LOG_TAG, "Applying remaining insertions.");
- try {
- insertionManager.finishUp();
- Logger.debug(LOG_TAG, "Done applying remaining insertions.");
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Unable to apply remaining insertions.", e);
- }
-
- Logger.debug(LOG_TAG, "Applying deletions.");
- try {
- untrackGUIDs(deletionManager.flushAll(getIDForGUID("unfiled"), now));
- Logger.debug(LOG_TAG, "Done applying deletions.");
- } catch (Exception e) {
- Logger.error(LOG_TAG, "Unable to apply deletions.", e);
- }
- }
-
- @SuppressWarnings("unchecked")
- private void finishUp() {
- try {
- flushQueues();
- Logger.debug(LOG_TAG, "Have " + parentToChildArray.size() + " folders whose children might need repositioning.");
- for (Entry<String, JSONArray> entry : parentToChildArray.entrySet()) {
- String guid = entry.getKey();
- JSONArray onServer = entry.getValue();
- try {
- final long folderID = getIDForGUID(guid);
- final JSONArray inDB = new JSONArray();
- final boolean clean = getChildrenArray(folderID, false, inDB);
- final boolean sameArrays = Utils.sameArrays(onServer, inDB);
-
- // If the local children and the remote children are already
- // the same, then we don't need to bump the modified time of the
- // parent: we wouldn't upload a different record, so avoid the cycle.
- if (!sameArrays) {
- int added = 0;
- for (Object o : inDB) {
- if (!onServer.contains(o)) {
- onServer.add(o);
- added++;
- }
- }
- Logger.debug(LOG_TAG, "Added " + added + " items locally.");
- Logger.debug(LOG_TAG, "Untracking and bumping " + guid + "(" + folderID + ")");
- dataAccessor.bumpModified(folderID, now());
- untrackGUID(guid);
- }
-
- // If the arrays are different, or they're the same but not flushed to disk,
- // write them out now.
- if (!sameArrays || !clean) {
- dataAccessor.updatePositions(new ArrayList<String>(onServer));
- }
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Error repositioning children for " + guid, e);
- }
- }
- } finally {
- super.storeDone();
- }
- }
-
- /**
- * Hook into the deletion manager on wipe.
- */
- class BookmarkWipeRunnable extends WipeRunnable {
- public BookmarkWipeRunnable(RepositorySessionWipeDelegate delegate) {
- super(delegate);
- }
-
- @Override
- public void run() {
- try {
- // Clear our queued deletions.
- deletionManager.clear();
- insertionManager.clear();
- super.run();
- } catch (Exception ex) {
- delegate.onWipeFailed(ex);
- return;
- }
- }
- }
-
- @Override
- protected WipeRunnable getWipeRunnable(RepositorySessionWipeDelegate delegate) {
- return new BookmarkWipeRunnable(delegate);
- }
-
- @Override
- public void storeDone() {
- Runnable command = new Runnable() {
- @Override
- public void run() {
- finishUp();
- }
- };
- storeWorkQueue.execute(command);
- }
-
- @Override
- protected String buildRecordString(Record record) {
- BookmarkRecord bmk = (BookmarkRecord) record;
- String parent = bmk.parentName + "/";
- if (bmk.isBookmark()) {
- return "b" + parent + bmk.bookmarkURI + ":" + bmk.title;
- }
- if (bmk.isFolder()) {
- return "f" + parent + bmk.title;
- }
- if (bmk.isSeparator()) {
- return "s" + parent + bmk.androidPosition;
- }
- if (bmk.isQuery()) {
- return "q" + parent + bmk.bookmarkURI;
- }
- return null;
- }
-
- public static BookmarkRecord computeParentFields(BookmarkRecord rec, String suggestedParentGUID, String suggestedParentName) {
- final String guid = rec.guid;
- if (guid == null) {
- // Oh dear.
- Logger.error(LOG_TAG, "No guid in computeParentFields!");
- return null;
- }
-
- String realParent = SPECIAL_GUID_PARENTS.get(guid);
- if (realParent == null) {
- // No magic parent. Use whatever the caller suggests.
- realParent = suggestedParentGUID;
- } else {
- Logger.debug(LOG_TAG, "Ignoring suggested parent ID " + suggestedParentGUID +
- " for " + guid + "; using " + realParent);
- }
-
- if (realParent == null) {
- // Oh dear.
- Logger.error(LOG_TAG, "No parent for record " + guid);
- return null;
- }
-
- // Always set the parent name for special folders back to default.
- String parentName = SPECIAL_GUIDS_MAP.get(realParent);
- if (parentName == null) {
- parentName = suggestedParentName;
- }
-
- rec.parentID = realParent;
- rec.parentName = parentName;
- return rec;
- }
-
- private static BookmarkRecord logBookmark(BookmarkRecord rec) {
- try {
- Logger.debug(LOG_TAG, "Returning " + (rec.deleted ? "deleted " : "") +
- "bookmark record " + rec.guid + " (" + rec.androidID +
- ", parent " + rec.parentID + ")");
- if (!rec.deleted && Logger.LOG_PERSONAL_INFORMATION) {
- Logger.pii(LOG_TAG, "> Parent name: " + rec.parentName);
- Logger.pii(LOG_TAG, "> Title: " + rec.title);
- Logger.pii(LOG_TAG, "> Type: " + rec.type);
- Logger.pii(LOG_TAG, "> URI: " + rec.bookmarkURI);
- Logger.pii(LOG_TAG, "> Position: " + rec.androidPosition);
- if (rec.isFolder()) {
- Logger.pii(LOG_TAG, "FOLDER: Children are " +
- (rec.children == null ?
- "null" :
- rec.children.toJSONString()));
- }
- }
- } catch (Exception e) {
- Logger.debug(LOG_TAG, "Exception logging bookmark record " + rec, e);
- }
- return rec;
- }
-
- // Create a BookmarkRecord object from a cursor on a row containing a Fennec bookmark.
- public static BookmarkRecord bookmarkFromMirrorCursor(Cursor cur, String parentGUID, String parentName, JSONArray children) {
- final String collection = "bookmarks";
- final String guid = RepoUtils.getStringFromCursor(cur, BrowserContract.SyncColumns.GUID);
- final long lastModified = RepoUtils.getLongFromCursor(cur, BrowserContract.SyncColumns.DATE_MODIFIED);
- final boolean deleted = isDeleted(cur);
- BookmarkRecord rec = new BookmarkRecord(guid, collection, lastModified, deleted);
-
- // No point in populating it.
- if (deleted) {
- return logBookmark(rec);
- }
-
- int rowType = getTypeFromCursor(cur);
- String typeString = BrowserContractHelpers.typeStringForCode(rowType);
-
- if (typeString == null) {
- Logger.warn(LOG_TAG, "Unsupported type code " + rowType);
- return null;
- }
-
- Logger.trace(LOG_TAG, "Record " + guid + " has type " + typeString);
-
- rec.type = typeString;
- rec.title = RepoUtils.getStringFromCursor(cur, BrowserContract.Bookmarks.TITLE);
- rec.bookmarkURI = RepoUtils.getStringFromCursor(cur, BrowserContract.Bookmarks.URL);
- rec.description = RepoUtils.getStringFromCursor(cur, BrowserContract.Bookmarks.DESCRIPTION);
- rec.tags = RepoUtils.getJSONArrayFromCursor(cur, BrowserContract.Bookmarks.TAGS);
- rec.keyword = RepoUtils.getStringFromCursor(cur, BrowserContract.Bookmarks.KEYWORD);
-
- rec.androidID = RepoUtils.getLongFromCursor(cur, BrowserContract.Bookmarks._ID);
- rec.androidPosition = RepoUtils.getLongFromCursor(cur, BrowserContract.Bookmarks.POSITION);
- rec.children = children;
-
- // Need to restore the parentId since it isn't stored in content provider.
- // We also take this opportunity to fix up parents for special folders,
- // allowing us to map between the hierarchies used by Fennec and Places.
- BookmarkRecord withParentFields = computeParentFields(rec, parentGUID, parentName);
- if (withParentFields == null) {
- // Oh dear. Something went wrong.
- return null;
- }
- return logBookmark(withParentFields);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserHistoryDataAccessor.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserHistoryDataAccessor.java
deleted file mode 100644
index c09d64708..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserHistoryDataAccessor.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.android;
-
-import java.util.ArrayList;
-
-import org.json.simple.JSONArray;
-import org.json.simple.JSONObject;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.domain.HistoryRecord;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.net.Uri;
-
-public class AndroidBrowserHistoryDataAccessor extends
- AndroidBrowserRepositoryDataAccessor {
-
- public AndroidBrowserHistoryDataAccessor(Context context) {
- super(context);
- }
-
- @Override
- protected Uri getUri() {
- return BrowserContractHelpers.HISTORY_CONTENT_URI;
- }
-
- @Override
- protected ContentValues getContentValues(Record record) {
- ContentValues cv = new ContentValues();
- HistoryRecord rec = (HistoryRecord) record;
- cv.put(BrowserContract.History.GUID, rec.guid);
- cv.put(BrowserContract.History.TITLE, rec.title);
- cv.put(BrowserContract.History.URL, rec.histURI);
- if (rec.visits != null) {
- JSONArray visits = rec.visits;
- long mostRecent = getLastVisited(visits);
-
- // Fennec stores history timestamps in milliseconds, and visit timestamps in microseconds.
- // The rest of Sync works in microseconds. This is the conversion point for records coming form Sync.
- cv.put(BrowserContract.History.DATE_LAST_VISITED, mostRecent / 1000);
- cv.put(BrowserContract.History.REMOTE_DATE_LAST_VISITED, mostRecent / 1000);
- cv.put(BrowserContract.History.VISITS, Long.toString(visits.size()));
- }
- return cv;
- }
-
- @Override
- protected String[] getAllColumns() {
- return BrowserContractHelpers.HistoryColumns;
- }
-
- @Override
- public Uri insert(Record record) {
- HistoryRecord rec = (HistoryRecord) record;
-
- Logger.debug(LOG_TAG, "Storing record " + record.guid);
- Uri newRecordUri = super.insert(record);
-
- Logger.debug(LOG_TAG, "Storing visits for " + record.guid);
- context.getContentResolver().bulkInsert(
- BrowserContract.Visits.CONTENT_URI,
- VisitsHelper.getVisitsContentValues(rec.guid, rec.visits)
- );
-
- return newRecordUri;
- }
-
- /**
- * Given oldGUID, first updates corresponding history record with new values (super operation),
- * and then inserts visits from the new record.
- * Existing visits from the old record are updated on database level to point to new GUID if necessary.
- *
- * @param oldGUID GUID of old <code>HistoryRecord</code>
- * @param newRecord new <code>HistoryRecord</code> to replace old one with, and insert visits from
- */
- @Override
- public void update(String oldGUID, Record newRecord) {
- // First, update existing history records with new values. This might involve changing history GUID,
- // and thanks to ON UPDATE CASCADE clause on Visits.HISTORY_GUID foreign key, visits will be "ported over"
- // to the new GUID.
- super.update(oldGUID, newRecord);
-
- // Now we need to insert any visits from the new record
- HistoryRecord rec = (HistoryRecord) newRecord;
- String newGUID = newRecord.guid;
- Logger.debug(LOG_TAG, "Storing visits for " + newGUID + ", replacing " + oldGUID);
-
- context.getContentResolver().bulkInsert(
- BrowserContract.Visits.CONTENT_URI,
- VisitsHelper.getVisitsContentValues(newGUID, rec.visits)
- );
- }
-
- /**
- * Insert records.
- * <p>
- * This inserts all the records (using <code>ContentProvider.bulkInsert</code>),
- * then inserts all the visit information (also using <code>ContentProvider.bulkInsert</code>).
- *
- * @param records
- * the records to insert.
- * @return
- * the number of records actually inserted.
- * @throws NullCursorException
- */
- public int bulkInsert(ArrayList<HistoryRecord> records) throws NullCursorException {
- if (records.isEmpty()) {
- Logger.debug(LOG_TAG, "No records to insert, returning.");
- }
-
- int size = records.size();
- ContentValues[] cvs = new ContentValues[size];
- int index = 0;
- for (Record record : records) {
- if (record.guid == null) {
- throw new IllegalArgumentException("Record with null GUID passed in to bulkInsert.");
- }
- cvs[index] = getContentValues(record);
- index += 1;
- }
-
- // First update the history records.
- int inserted = context.getContentResolver().bulkInsert(getUri(), cvs);
- if (inserted == size) {
- Logger.debug(LOG_TAG, "Inserted " + inserted + " records, as expected.");
- } else {
- Logger.debug(LOG_TAG, "Inserted " +
- inserted + " records but expected " +
- size + " records; continuing to update visits.");
- }
-
- final ContentValues remoteVisitAggregateValues = new ContentValues();
- final Uri historyIncrementRemoteAggregateUri = getUri().buildUpon()
- .appendQueryParameter(BrowserContract.PARAM_INCREMENT_REMOTE_AGGREGATES, "true")
- .build();
- for (Record record : records) {
- HistoryRecord rec = (HistoryRecord) record;
- if (rec.visits != null && rec.visits.size() != 0) {
- int remoteVisitsInserted = context.getContentResolver().bulkInsert(
- BrowserContract.Visits.CONTENT_URI,
- VisitsHelper.getVisitsContentValues(rec.guid, rec.visits)
- );
-
- // If we just inserted any visits, update remote visit aggregate values.
- // While inserting visits, we might not insert all of rec.visits - if we already have a local
- // visit record with matching (guid,date), we will skip that visit.
- // Remote visits aggregate value will be incremented by number of visits inserted.
- // Note that we don't need to set REMOTE_DATE_LAST_VISITED, because it already gets set above.
- if (remoteVisitsInserted > 0) {
- // Note that REMOTE_VISITS must be set before calling cr.update(...) with a URI
- // that has PARAM_INCREMENT_REMOTE_AGGREGATES=true.
- remoteVisitAggregateValues.put(BrowserContract.History.REMOTE_VISITS, remoteVisitsInserted);
- context.getContentResolver().update(
- historyIncrementRemoteAggregateUri,
- remoteVisitAggregateValues,
- BrowserContract.History.GUID + " = ?", new String[] {rec.guid}
- );
- }
- }
- }
-
- return inserted;
- }
-
- /**
- * Helper method used to find largest <code>VisitsHelper.SYNC_DATE_KEY</code> value in a provided JSONArray.
- *
- * @param visits Array of objects which will be searched.
- * @return largest value of <code>VisitsHelper.SYNC_DATE_KEY</code>.
- */
- private long getLastVisited(JSONArray visits) {
- long mostRecent = 0;
- for (int i = 0; i < visits.size(); i++) {
- final JSONObject visit = (JSONObject) visits.get(i);
- long visitDate = (Long) visit.get(VisitsHelper.SYNC_DATE_KEY);
- if (visitDate > mostRecent) {
- mostRecent = visitDate;
- }
- }
- return mostRecent;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserHistoryRepository.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserHistoryRepository.java
deleted file mode 100644
index bd2b5d31f..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserHistoryRepository.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.android;
-
-import org.mozilla.gecko.sync.repositories.HistoryRepository;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-
-import android.content.Context;
-
-public class AndroidBrowserHistoryRepository extends AndroidBrowserRepository implements HistoryRepository {
-
- @Override
- protected void sessionCreator(RepositorySessionCreationDelegate delegate, Context context) {
- AndroidBrowserHistoryRepositorySession session = new AndroidBrowserHistoryRepositorySession(AndroidBrowserHistoryRepository.this, context);
- delegate.onSessionCreated(session);
- }
-
- @Override
- protected AndroidBrowserRepositoryDataAccessor getDataAccessor(Context context) {
- return new AndroidBrowserHistoryDataAccessor(context);
- }
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserHistoryRepositorySession.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserHistoryRepositorySession.java
deleted file mode 100644
index 7c462abc3..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserHistoryRepositorySession.java
+++ /dev/null
@@ -1,208 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.android;
-
-import java.util.ArrayList;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
-import org.mozilla.gecko.sync.repositories.NoGuidForIdException;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.ParentNotFoundException;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
-import org.mozilla.gecko.sync.repositories.domain.HistoryRecord;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-import android.content.ContentProviderClient;
-import android.content.Context;
-import android.database.Cursor;
-import android.os.RemoteException;
-
-public class AndroidBrowserHistoryRepositorySession extends AndroidBrowserRepositorySession {
- public static final String LOG_TAG = "ABHistoryRepoSess";
-
- /**
- * The number of records to queue for insertion before writing to databases.
- */
- public static final int INSERT_RECORD_THRESHOLD = 50;
- public static final int RECENT_VISITS_LIMIT = 20;
-
- public AndroidBrowserHistoryRepositorySession(Repository repository, Context context) {
- super(repository);
- dbHelper = new AndroidBrowserHistoryDataAccessor(context);
- }
-
- @Override
- public void begin(RepositorySessionBeginDelegate delegate) throws InvalidSessionTransitionException {
- // HACK: Fennec creates history records without a GUID. Mercilessly drop
- // them on the floor. See Bug 739514.
- try {
- dbHelper.delete(BrowserContract.History.GUID + " IS NULL", null);
- } catch (Exception e) {
- // Ignore.
- }
- super.begin(delegate);
- }
-
- @Override
- protected Record retrieveDuringStore(Cursor cur) {
- return RepoUtils.historyFromMirrorCursor(cur);
- }
-
- @Override
- protected Record retrieveDuringFetch(Cursor cur) {
- return RepoUtils.historyFromMirrorCursor(cur);
- }
-
- @Override
- protected String buildRecordString(Record record) {
- HistoryRecord hist = (HistoryRecord) record;
- return hist.histURI;
- }
-
- @Override
- public boolean shouldIgnore(Record record) {
- if (super.shouldIgnore(record)) {
- return true;
- }
- if (!(record instanceof HistoryRecord)) {
- return true;
- }
- HistoryRecord r = (HistoryRecord) record;
- return !RepoUtils.isValidHistoryURI(r.histURI);
- }
-
- @Override
- protected Record transformRecord(Record record) throws NullCursorException {
- return addVisitsToRecord(record);
- }
-
- private Record addVisitsToRecord(Record record) throws NullCursorException {
- Logger.debug(LOG_TAG, "Adding visits for GUID " + record.guid);
-
- // Sync is an object store, so what we attach here will replace what's already present on the Sync servers.
- // We upload just a recent subset of visits for each history record for space and bandwidth reasons.
- // We chose 20 to be conservative. See Bug 1164660 for details.
- ContentProviderClient visitsClient = dbHelper.context.getContentResolver().acquireContentProviderClient(BrowserContractHelpers.VISITS_CONTENT_URI);
- if (visitsClient == null) {
- throw new IllegalStateException("Could not obtain a ContentProviderClient for Visits URI");
- }
-
- try {
- ((HistoryRecord) record).visits = VisitsHelper.getRecentHistoryVisitsForGUID(
- visitsClient, record.guid, RECENT_VISITS_LIMIT);
- } catch (RemoteException e) {
- throw new IllegalStateException("Error while obtaining visits for a record", e);
- } finally {
- visitsClient.release();
- }
-
- return record;
- }
-
- @Override
- protected Record prepareRecord(Record record) {
- return record;
- }
-
- protected final Object recordsBufferMonitor = new Object();
- protected ArrayList<HistoryRecord> recordsBuffer = new ArrayList<HistoryRecord>();
-
- /**
- * Queue record for insertion, possibly flushing the queue.
- * <p>
- * Must be called on <code>storeWorkQueue</code> thread! But this is only
- * called from <code>store</code>, which is called on the queue thread.
- *
- * @param record
- * A <code>Record</code> with a GUID that is not present locally.
- */
- @Override
- protected void insert(Record record) throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
- enqueueNewRecord((HistoryRecord) prepareRecord(record));
- }
-
- /**
- * Batch incoming records until some reasonable threshold is hit or storeDone
- * is received.
- * <p>
- * Must be called on <code>storeWorkQueue</code> thread!
- *
- * @param record A <code>Record</code> with a GUID that is not present locally.
- * @throws NullCursorException
- */
- protected void enqueueNewRecord(HistoryRecord record) throws NullCursorException {
- synchronized (recordsBufferMonitor) {
- if (recordsBuffer.size() >= INSERT_RECORD_THRESHOLD) {
- flushNewRecords();
- }
- Logger.debug(LOG_TAG, "Enqueuing new record with GUID " + record.guid);
- recordsBuffer.add(record);
- }
- }
-
- /**
- * Flush queue of incoming records to database.
- * <p>
- * Must be called on <code>storeWorkQueue</code> thread!
- * <p>
- * Must be locked by recordsBufferMonitor!
- * @throws NullCursorException
- */
- protected void flushNewRecords() throws NullCursorException {
- if (recordsBuffer.size() < 1) {
- Logger.debug(LOG_TAG, "No records to flush, returning.");
- return;
- }
-
- final ArrayList<HistoryRecord> outgoing = recordsBuffer;
- recordsBuffer = new ArrayList<HistoryRecord>();
- Logger.debug(LOG_TAG, "Flushing " + outgoing.size() + " records to database.");
- // TODO: move bulkInsert to AndroidBrowserDataAccessor?
- int inserted = ((AndroidBrowserHistoryDataAccessor) dbHelper).bulkInsert(outgoing);
- if (inserted != outgoing.size()) {
- // Something failed; most pessimistic action is to declare that all insertions failed.
- // TODO: perform the bulkInsert in a transaction and rollback unless all insertions succeed?
- for (HistoryRecord failed : outgoing) {
- delegate.onRecordStoreFailed(new RuntimeException("Failed to insert history item with guid " + failed.guid + "."), failed.guid);
- }
- return;
- }
-
- // All good, everybody succeeded.
- for (HistoryRecord succeeded : outgoing) {
- try {
- // Does not use androidID -- just GUID -> String map.
- updateBookkeeping(succeeded);
- } catch (NoGuidForIdException | ParentNotFoundException e) {
- // Should not happen.
- throw new NullCursorException(e);
- } catch (NullCursorException e) {
- throw e;
- }
- trackRecord(succeeded);
- delegate.onRecordStoreSucceeded(succeeded.guid); // At this point, we are really inserted.
- }
- }
-
- @Override
- public void storeDone() {
- storeWorkQueue.execute(new Runnable() {
- @Override
- public void run() {
- synchronized (recordsBufferMonitor) {
- try {
- flushNewRecords();
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Error flushing records to database.", e);
- }
- }
- storeDone(System.currentTimeMillis());
- }
- });
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserRepository.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserRepository.java
deleted file mode 100644
index 6c5c661ee..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserRepository.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.android;
-
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCleanDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-
-import android.content.Context;
-
-public abstract class AndroidBrowserRepository extends Repository {
-
- @Override
- public void createSession(RepositorySessionCreationDelegate delegate, Context context) {
- new CreateSessionThread(delegate, context).start();
- }
-
- @Override
- public void clean(boolean success, RepositorySessionCleanDelegate delegate, Context context) {
- // Only clean deleted records if success
- if (success) {
- new CleanThread(delegate, context).start();
- }
- }
-
- class CleanThread extends Thread {
- private final RepositorySessionCleanDelegate delegate;
- private final Context context;
-
- public CleanThread(RepositorySessionCleanDelegate delegate, Context context) {
- if (context == null) {
- throw new IllegalArgumentException("context is null");
- }
- this.delegate = delegate;
- this.context = context;
- }
-
- @Override
- public void run() {
- try {
- getDataAccessor(context).purgeDeleted();
- } catch (Exception e) {
- delegate.onCleanFailed(AndroidBrowserRepository.this, e);
- return;
- }
- delegate.onCleaned(AndroidBrowserRepository.this);
- }
- }
-
- protected abstract AndroidBrowserRepositoryDataAccessor getDataAccessor(Context context);
- protected abstract void sessionCreator(RepositorySessionCreationDelegate delegate, Context context);
-
- class CreateSessionThread extends Thread {
- private final RepositorySessionCreationDelegate delegate;
- private final Context context;
-
- public CreateSessionThread(RepositorySessionCreationDelegate delegate, Context context) {
- if (context == null) {
- throw new IllegalArgumentException("context is null.");
- }
- this.delegate = delegate;
- this.context = context;
- }
-
- @Override
- public void run() {
- sessionCreator(delegate, context);
- }
- }
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java
deleted file mode 100644
index 138d63d4c..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java
+++ /dev/null
@@ -1,232 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.android;
-
-import java.util.List;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.db.CursorDumper;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-
-public abstract class AndroidBrowserRepositoryDataAccessor {
-
- private static final String[] GUID_COLUMNS = new String[] { BrowserContract.SyncColumns.GUID };
- protected Context context;
- protected static String LOG_TAG = "BrowserDataAccessor";
- protected final RepoUtils.QueryHelper queryHelper;
-
- public AndroidBrowserRepositoryDataAccessor(Context context) {
- this.context = context;
- this.queryHelper = new RepoUtils.QueryHelper(context, getUri(), LOG_TAG);
- }
-
- protected abstract String[] getAllColumns();
-
- /**
- * Produce a <code>ContentValues</code> instance that represents the provided <code>Record</code>.
- *
- * @param record The <code>Record</code> to be converted.
- * @return The <code>ContentValues</code> corresponding to <code>record</code>.
- */
- protected abstract ContentValues getContentValues(Record record);
-
- protected abstract Uri getUri();
-
- /**
- * Dump all the records in raw format.
- */
- public void dumpDB() {
- Cursor cur = null;
- try {
- cur = queryHelper.safeQuery(".dumpDB", null, null, null, null);
- CursorDumper.dumpCursor(cur);
- } catch (NullCursorException e) {
- } finally {
- if (cur != null) {
- cur.close();
- }
- }
- }
-
- public String dateModifiedWhere(long timestamp) {
- return BrowserContract.SyncColumns.DATE_MODIFIED + " >= " + Long.toString(timestamp);
- }
-
- public void delete(String where, String[] args) {
- Uri uri = getUri();
- context.getContentResolver().delete(uri, where, args);
- }
-
- public void wipe() {
- Logger.debug(LOG_TAG, "Wiping.");
- delete(null, null);
- }
-
- public void purgeDeleted() throws NullCursorException {
- String where = BrowserContract.SyncColumns.IS_DELETED + "= 1";
- Uri uri = getUri();
- Logger.info(LOG_TAG, "Purging deleted from: " + uri);
- context.getContentResolver().delete(uri, where, null);
- }
-
- /**
- * Remove matching records from the database entirely, i.e., do not set a
- * deleted flag, delete entirely.
- *
- * @param guid
- * The GUID of the record to be deleted.
- * @return The number of records deleted.
- */
- public int purgeGuid(String guid) {
- String where = BrowserContract.SyncColumns.GUID + " = ?";
- String[] args = new String[] { guid };
-
- int deleted = context.getContentResolver().delete(getUri(), where, args);
- if (deleted != 1) {
- Logger.warn(LOG_TAG, "Unexpectedly deleted " + deleted + " records for guid " + guid);
- }
- return deleted;
- }
-
- public void update(String guid, Record newRecord) {
- String where = BrowserContract.SyncColumns.GUID + " = ?";
- String[] args = new String[] { guid };
- ContentValues cv = getContentValues(newRecord);
- int updated = context.getContentResolver().update(getUri(), cv, where, args);
- if (updated != 1) {
- Logger.warn(LOG_TAG, "Unexpectedly updated " + updated + " rows for guid " + guid);
- }
- }
-
- public Uri insert(Record record) {
- ContentValues cv = getContentValues(record);
- return context.getContentResolver().insert(getUri(), cv);
- }
-
- /**
- * Fetch all records.
- * <p>
- * The caller is responsible for closing the cursor.
- *
- * @return A cursor. You </b>must</b> close this when you're done with it.
- * @throws NullCursorException
- */
- public Cursor fetchAll() throws NullCursorException {
- return queryHelper.safeQuery(".fetchAll", getAllColumns(), null, null, null);
- }
-
- /**
- * Fetch GUIDs for records modified since the provided timestamp.
- * <p>
- * The caller is responsible for closing the cursor.
- *
- * @param timestamp A timestamp in milliseconds.
- * @return A cursor. You <b>must</b> close this when you're done with it.
- * @throws NullCursorException
- */
- public Cursor getGUIDsSince(long timestamp) throws NullCursorException {
- return queryHelper.safeQuery(".getGUIDsSince",
- GUID_COLUMNS,
- dateModifiedWhere(timestamp),
- null, null);
- }
-
- /**
- * Fetch records modified since the provided timestamp.
- * <p>
- * The caller is responsible for closing the cursor.
- *
- * @param timestamp A timestamp in milliseconds.
- * @return A cursor. You <b>must</b> close this when you're done with it.
- * @throws NullCursorException
- */
- public Cursor fetchSince(long timestamp) throws NullCursorException {
- return queryHelper.safeQuery(".fetchSince",
- getAllColumns(),
- dateModifiedWhere(timestamp),
- null, null);
- }
-
- /**
- * Fetch records for the provided GUIDs.
- * <p>
- * The caller is responsible for closing the cursor.
- *
- * @param guids The GUIDs of the records to fetch.
- * @return A cursor. You <b>must</b> close this when you're done with it.
- * @throws NullCursorException
- */
- public Cursor fetch(String guids[]) throws NullCursorException {
- String where = RepoUtils.computeSQLInClause(guids.length, "guid");
- return queryHelper.safeQuery(".fetch", getAllColumns(), where, guids, null);
- }
-
- public void updateByGuid(String guid, ContentValues cv) {
- String where = BrowserContract.SyncColumns.GUID + " = ?";
- String[] args = new String[] { guid };
-
- int updated = context.getContentResolver().update(getUri(), cv, where, args);
- if (updated == 1) {
- return;
- }
- Logger.warn(LOG_TAG, "Unexpectedly updated " + updated + " rows for guid " + guid);
- }
-
- /**
- * Insert records.
- * <p>
- * This inserts all the records (using <code>ContentProvider.bulkInsert</code>),
- * but does <b>not</b> update the <code>androidID</code> of each record.
- *
- * @param records
- * the records to insert.
- * @return
- * the number of records actually inserted.
- * @throws NullCursorException
- */
- public int bulkInsert(List<Record> records) throws NullCursorException {
- if (records.isEmpty()) {
- Logger.debug(LOG_TAG, "No records to insert, returning.");
- }
-
- int size = records.size();
- ContentValues[] cvs = new ContentValues[size];
- int index = 0;
- for (Record record : records) {
- try {
- cvs[index] = getContentValues(record);
- index += 1;
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Got exception in getContentValues for record with guid " + record.guid, e);
- }
- }
-
- if (index != size) {
- // bulkInsert treats null ContentValues as blank rows, which we don't want
- // to insert into the database.
- // We expect exceptions in getContentValues to be exceedingly rare, so we
- // re-allocate in the (rare) error case and maintain a fast path for the
- // success case.
- size = index;
- }
-
- int inserted = context.getContentResolver().bulkInsert(getUri(), cvs);
- if (inserted == size) {
- Logger.debug(LOG_TAG, "Inserted " + inserted + " records, as expected.");
- } else {
- Logger.debug(LOG_TAG, "Inserted " +
- inserted + " records but expected " +
- size + " records.");
- }
- return inserted;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserRepositorySession.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserRepositorySession.java
deleted file mode 100644
index 4f0da0bcc..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/AndroidBrowserRepositorySession.java
+++ /dev/null
@@ -1,792 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.android;
-
-import java.util.ArrayList;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.repositories.InactiveSessionException;
-import org.mozilla.gecko.sync.repositories.InvalidRequestException;
-import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
-import org.mozilla.gecko.sync.repositories.MultipleRecordsForGuidException;
-import org.mozilla.gecko.sync.repositories.NoGuidForIdException;
-import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.ParentNotFoundException;
-import org.mozilla.gecko.sync.repositories.ProfileDatabaseException;
-import org.mozilla.gecko.sync.repositories.RecordFilter;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.StoreTrackingRepositorySession;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionGuidsSinceDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-import android.content.ContentUris;
-import android.database.Cursor;
-import android.net.Uri;
-import android.util.SparseArray;
-
-/**
- * You'll notice that all delegate calls *either*:
- *
- * - request a deferred delegate with the appropriate work queue, then
- * make the appropriate call, or
- * - create a Runnable which makes the appropriate call, and pushes it
- * directly into the appropriate work queue.
- *
- * This is to ensure that all delegate callbacks happen off the current
- * thread. This provides lock safety (we don't enter another method that
- * might try to take a lock already taken in our caller), and ensures
- * that operations take place off the main thread.
- *
- * Don't do both -- the two approaches are equivalent -- and certainly
- * don't do neither unless you know what you're doing!
- *
- * Similarly, all store calls go through the appropriate store queue. This
- * ensures that store() and storeDone() consequences occur before-after.
- *
- * @author rnewman
- *
- */
-public abstract class AndroidBrowserRepositorySession extends StoreTrackingRepositorySession {
- public static final String LOG_TAG = "BrowserRepoSession";
-
- protected AndroidBrowserRepositoryDataAccessor dbHelper;
-
- /**
- * In order to reconcile the "same record" with two *different* GUIDs (for
- * example, the same bookmark created by two different clients), we maintain a
- * mapping for each local record from a "record string" to
- * "local record GUID".
- * <p>
- * The "record string" above is a "record identifying unique key" produced by
- * <code>buildRecordString</code>.
- * <p>
- * Since we hash each "record string", this map may produce a false positive.
- * In this case, we search the database for a matching record explicitly using
- * <code>findByRecordString</code>.
- */
- protected SparseArray<String> recordToGuid;
-
- public AndroidBrowserRepositorySession(Repository repository) {
- super(repository);
- }
-
- /**
- * Retrieve a record from a cursor. Act as if we don't know the final contents of
- * the record: for example, a folder's child array might change.
- *
- * Return null if this record should not be processed.
- *
- * @throws NoGuidForIdException
- * @throws NullCursorException
- * @throws ParentNotFoundException
- */
- protected abstract Record retrieveDuringStore(Cursor cur) throws NoGuidForIdException, NullCursorException, ParentNotFoundException;
-
- /**
- * Retrieve a record from a cursor. Ensure that the contents of the database are
- * updated to match the record that we're constructing: for example, the children
- * of a folder might be repositioned as we generate the folder's record.
- *
- * @throws NoGuidForIdException
- * @throws NullCursorException
- * @throws ParentNotFoundException
- */
- protected abstract Record retrieveDuringFetch(Cursor cur) throws NoGuidForIdException, NullCursorException, ParentNotFoundException;
-
- /**
- * Override this to allow records to be skipped during insertion.
- *
- * For example, a session subclass might skip records of an unsupported type.
- */
- @SuppressWarnings("static-method")
- public boolean shouldIgnore(Record record) {
- return false;
- }
-
- /**
- * Perform any necessary transformation of a record prior to searching by
- * any field other than GUID.
- *
- * Example: translating remote folder names into local names.
- */
- @SuppressWarnings("static-method")
- protected void fixupRecord(Record record) {
- return;
- }
-
- /**
- * Override in subclass to implement record extension.
- *
- * Populate any fields of the record that are expensive to calculate,
- * prior to reconciling.
- *
- * Example: computing children arrays.
- *
- * Return null if this record should not be processed.
- *
- * @param record
- * The record to transform. Can be null.
- * @return The transformed record. Can be null.
- * @throws NullCursorException
- */
- @SuppressWarnings("static-method")
- protected Record transformRecord(Record record) throws NullCursorException {
- return record;
- }
-
- @Override
- public void begin(RepositorySessionBeginDelegate delegate) throws InvalidSessionTransitionException {
- RepositorySessionBeginDelegate deferredDelegate = delegate.deferredBeginDelegate(delegateQueue);
- super.sharedBegin();
-
- try {
- // We do this check here even though it results in one extra call to the DB
- // because if we didn't, we have to do a check on every other call since there
- // is no way of knowing which call would be hit first.
- checkDatabase();
- } catch (ProfileDatabaseException e) {
- Logger.error(LOG_TAG, "ProfileDatabaseException from begin. Fennec must be launched once until this error is fixed");
- deferredDelegate.onBeginFailed(e);
- return;
- } catch (Exception e) {
- deferredDelegate.onBeginFailed(e);
- return;
- }
- storeTracker = createStoreTracker();
- deferredDelegate.onBeginSucceeded(this);
- }
-
- @Override
- public void finish(RepositorySessionFinishDelegate delegate) throws InactiveSessionException {
- dbHelper = null;
- recordToGuid = null;
- super.finish(delegate);
- }
-
- /**
- * Produce a "record string" (record identifying unique key).
- *
- * @param record
- * the <code>Record</code> to identify.
- * @return a <code>String</code> instance.
- */
- protected abstract String buildRecordString(Record record);
-
- protected void checkDatabase() throws ProfileDatabaseException, NullCursorException {
- Logger.debug(LOG_TAG, "BEGIN: checking database.");
- try {
- dbHelper.fetch(new String[] { "none" }).close();
- Logger.debug(LOG_TAG, "END: checking database.");
- } catch (NullPointerException e) {
- throw new ProfileDatabaseException(e);
- }
- }
-
- @Override
- public void guidsSince(long timestamp, RepositorySessionGuidsSinceDelegate delegate) {
- GuidsSinceRunnable command = new GuidsSinceRunnable(timestamp, delegate);
- delegateQueue.execute(command);
- }
-
- class GuidsSinceRunnable implements Runnable {
-
- private final RepositorySessionGuidsSinceDelegate delegate;
- private final long timestamp;
-
- public GuidsSinceRunnable(long timestamp,
- RepositorySessionGuidsSinceDelegate delegate) {
- this.timestamp = timestamp;
- this.delegate = delegate;
- }
-
- @Override
- public void run() {
- if (!isActive()) {
- delegate.onGuidsSinceFailed(new InactiveSessionException(null));
- return;
- }
-
- Cursor cur;
- try {
- cur = dbHelper.getGUIDsSince(timestamp);
- } catch (Exception e) {
- delegate.onGuidsSinceFailed(e);
- return;
- }
-
- ArrayList<String> guids;
- try {
- if (!cur.moveToFirst()) {
- delegate.onGuidsSinceSucceeded(new String[] {});
- return;
- }
- guids = new ArrayList<String>();
- while (!cur.isAfterLast()) {
- guids.add(RepoUtils.getStringFromCursor(cur, "guid"));
- cur.moveToNext();
- }
- } finally {
- Logger.debug(LOG_TAG, "Closing cursor after guidsSince.");
- cur.close();
- }
-
- String guidsArray[] = new String[guids.size()];
- guids.toArray(guidsArray);
- delegate.onGuidsSinceSucceeded(guidsArray);
- }
- }
-
- @Override
- public void fetch(String[] guids,
- RepositorySessionFetchRecordsDelegate delegate) throws InactiveSessionException {
- FetchRunnable command = new FetchRunnable(guids, now(), null, delegate);
- executeDelegateCommand(command);
- }
-
- abstract class FetchingRunnable implements Runnable {
- protected final RepositorySessionFetchRecordsDelegate delegate;
-
- public FetchingRunnable(RepositorySessionFetchRecordsDelegate delegate) {
- this.delegate = delegate;
- }
-
- protected void fetchFromCursor(Cursor cursor, RecordFilter filter, long end) {
- Logger.debug(LOG_TAG, "Fetch from cursor:");
- try {
- try {
- if (!cursor.moveToFirst()) {
- delegate.onFetchCompleted(end);
- return;
- }
- while (!cursor.isAfterLast()) {
- Record r = retrieveDuringFetch(cursor);
- if (r != null) {
- if (filter == null || !filter.excludeRecord(r)) {
- Logger.trace(LOG_TAG, "Processing record " + r.guid);
- delegate.onFetchedRecord(transformRecord(r));
- } else {
- Logger.debug(LOG_TAG, "Skipping filtered record " + r.guid);
- }
- }
- cursor.moveToNext();
- }
- delegate.onFetchCompleted(end);
- } catch (NoGuidForIdException e) {
- Logger.warn(LOG_TAG, "No GUID for ID.", e);
- delegate.onFetchFailed(e, null);
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Exception in fetchFromCursor.", e);
- delegate.onFetchFailed(e, null);
- return;
- }
- } finally {
- Logger.trace(LOG_TAG, "Closing cursor after fetch.");
- cursor.close();
- }
- }
- }
-
- public class FetchRunnable extends FetchingRunnable {
- private final String[] guids;
- private final long end;
- private final RecordFilter filter;
-
- public FetchRunnable(String[] guids,
- long end,
- RecordFilter filter,
- RepositorySessionFetchRecordsDelegate delegate) {
- super(delegate);
- this.guids = guids;
- this.end = end;
- this.filter = filter;
- }
-
- @Override
- public void run() {
- if (!isActive()) {
- delegate.onFetchFailed(new InactiveSessionException(null), null);
- return;
- }
-
- if (guids == null || guids.length < 1) {
- Logger.error(LOG_TAG, "No guids sent to fetch");
- delegate.onFetchFailed(new InvalidRequestException(null), null);
- return;
- }
-
- try {
- Cursor cursor = dbHelper.fetch(guids);
- this.fetchFromCursor(cursor, filter, end);
- } catch (NullCursorException e) {
- delegate.onFetchFailed(e, null);
- }
- }
- }
-
- @Override
- public void fetchSince(long timestamp,
- RepositorySessionFetchRecordsDelegate delegate) {
- if (this.storeTracker == null) {
- throw new IllegalStateException("Store tracker not yet initialized!");
- }
-
- Logger.debug(LOG_TAG, "Running fetchSince(" + timestamp + ").");
- FetchSinceRunnable command = new FetchSinceRunnable(timestamp, now(), this.storeTracker.getFilter(), delegate);
- delegateQueue.execute(command);
- }
-
- class FetchSinceRunnable extends FetchingRunnable {
- private final long since;
- private final long end;
- private final RecordFilter filter;
-
- public FetchSinceRunnable(long since,
- long end,
- RecordFilter filter,
- RepositorySessionFetchRecordsDelegate delegate) {
- super(delegate);
- this.since = since;
- this.end = end;
- this.filter = filter;
- }
-
- @Override
- public void run() {
- if (!isActive()) {
- delegate.onFetchFailed(new InactiveSessionException(null), null);
- return;
- }
-
- try {
- Cursor cursor = dbHelper.fetchSince(since);
- this.fetchFromCursor(cursor, filter, end);
- } catch (NullCursorException e) {
- delegate.onFetchFailed(e, null);
- return;
- }
- }
- }
-
- @Override
- public void fetchAll(RepositorySessionFetchRecordsDelegate delegate) {
- this.fetchSince(0, delegate);
- }
-
- protected int storeCount = 0;
-
- @Override
- public void store(final Record record) throws NoStoreDelegateException {
- if (delegate == null) {
- throw new NoStoreDelegateException();
- }
- if (record == null) {
- Logger.error(LOG_TAG, "Record sent to store was null");
- throw new IllegalArgumentException("Null record passed to AndroidBrowserRepositorySession.store().");
- }
-
- storeCount += 1;
- Logger.debug(LOG_TAG, "Storing record with GUID " + record.guid + " (stored " + storeCount + " records this session).");
-
- // Store Runnables *must* complete synchronously. It's OK, they
- // run on a background thread.
- Runnable command = new Runnable() {
-
- @Override
- public void run() {
- if (!isActive()) {
- Logger.warn(LOG_TAG, "AndroidBrowserRepositorySession is inactive. Store failing.");
- delegate.onRecordStoreFailed(new InactiveSessionException(null), record.guid);
- return;
- }
-
- // Check that the record is a valid type.
- // Fennec only supports bookmarks and folders. All other types of records,
- // including livemarks and queries, are simply ignored.
- // See Bug 708149. This might be resolved by Fennec changing its database
- // schema, or by Sync storing non-applied records in its own private database.
- if (shouldIgnore(record)) {
- Logger.debug(LOG_TAG, "Ignoring record " + record.guid);
-
- // Don't throw: we don't want to abort the entire sync when we get a livemark!
- // delegate.onRecordStoreFailed(new InvalidBookmarkTypeException(null));
- return;
- }
-
-
- // TODO: lift these into the session.
- // Temporary: this matches prior syncing semantics, in which only
- // the relationship between the local and remote record is considered.
- // In the future we'll track these two timestamps and use them to
- // determine which records have changed, and thus process incoming
- // records more efficiently.
- long lastLocalRetrieval = 0; // lastSyncTimestamp?
- long lastRemoteRetrieval = 0; // TODO: adjust for clock skew.
- boolean remotelyModified = record.lastModified > lastRemoteRetrieval;
-
- Record existingRecord;
- try {
- // GUID matching only: deleted records don't have a payload with which to search.
- existingRecord = retrieveByGUIDDuringStore(record.guid);
- if (record.deleted) {
- if (existingRecord == null) {
- // We're done. Don't bother with a callback. That can change later
- // if we want it to.
- trace("Incoming record " + record.guid + " is deleted, and no local version. Bye!");
- return;
- }
-
- if (existingRecord.deleted) {
- trace("Local record already deleted. Bye!");
- return;
- }
-
- // Which one wins?
- if (!remotelyModified) {
- trace("Ignoring deleted record from the past.");
- return;
- }
-
- boolean locallyModified = existingRecord.lastModified > lastLocalRetrieval;
- if (!locallyModified) {
- trace("Remote modified, local not. Deleting.");
- storeRecordDeletion(record, existingRecord);
- return;
- }
-
- trace("Both local and remote records have been modified.");
- if (record.lastModified > existingRecord.lastModified) {
- trace("Remote is newer, and deleted. Deleting local.");
- storeRecordDeletion(record, existingRecord);
- return;
- }
-
- trace("Remote is older, local is not deleted. Ignoring.");
- return;
- }
- // End deletion logic.
-
- // Now we're processing a non-deleted incoming record.
- // Apply any changes we need in order to correctly find existing records.
- fixupRecord(record);
-
- if (existingRecord == null) {
- trace("Looking up match for record " + record.guid);
- existingRecord = findExistingRecord(record);
- }
-
- if (existingRecord == null) {
- // The record is new.
- trace("No match. Inserting.");
- insert(record);
- return;
- }
-
- // We found a local dupe.
- trace("Incoming record " + record.guid + " dupes to local record " + existingRecord.guid);
-
- // Populate more expensive fields prior to reconciling.
- existingRecord = transformRecord(existingRecord);
- Record toStore = reconcileRecords(record, existingRecord, lastRemoteRetrieval, lastLocalRetrieval);
-
- if (toStore == null) {
- Logger.debug(LOG_TAG, "Reconciling returned null. Not inserting a record.");
- return;
- }
-
- // TODO: pass in timestamps?
-
- // This section of code will only run if the incoming record is not
- // marked as deleted, so we never want to just drop ours from the database:
- // we need to upload it later.
- // Allowing deleted items to propagate through `replace` allows normal
- // logging and side-effects to occur, and is no more expensive than simply
- // bumping the modified time.
- Logger.debug(LOG_TAG, "Replacing existing " + existingRecord.guid +
- (toStore.deleted ? " with deleted record " : " with record ") +
- toStore.guid);
- Record replaced = replace(toStore, existingRecord);
-
- // Note that we don't track records here; deciding that is the job
- // of reconcileRecords.
- Logger.debug(LOG_TAG, "Calling delegate callback with guid " + replaced.guid +
- "(" + replaced.androidID + ")");
- delegate.onRecordStoreSucceeded(replaced.guid);
- return;
-
- } catch (MultipleRecordsForGuidException e) {
- Logger.error(LOG_TAG, "Multiple records returned for given guid: " + record.guid);
- delegate.onRecordStoreFailed(e, record.guid);
- return;
- } catch (NoGuidForIdException e) {
- Logger.error(LOG_TAG, "Store failed for " + record.guid, e);
- delegate.onRecordStoreFailed(e, record.guid);
- return;
- } catch (Exception e) {
- Logger.error(LOG_TAG, "Store failed for " + record.guid, e);
- delegate.onRecordStoreFailed(e, record.guid);
- return;
- }
- }
- };
- storeWorkQueue.execute(command);
- }
-
- /**
- * Process a request for deletion of a record.
- * Neither argument will ever be null.
- *
- * @param record the incoming record. This will be mostly blank, given that it's a deletion.
- * @param existingRecord the existing record. Use this to decide how to process the deletion.
- */
- protected void storeRecordDeletion(final Record record, final Record existingRecord) {
- // TODO: we ought to mark the record as deleted rather than purging it,
- // in order to support syncing to multiple destinations. Bug 722607.
- dbHelper.purgeGuid(record.guid);
- delegate.onRecordStoreSucceeded(record.guid);
- }
-
- protected void insert(Record record) throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
- Record toStore = prepareRecord(record);
- Uri recordURI = dbHelper.insert(toStore);
- if (recordURI == null) {
- throw new NullCursorException(new RuntimeException("Got null URI inserting record with guid " + record.guid));
- }
- toStore.androidID = ContentUris.parseId(recordURI);
-
- updateBookkeeping(toStore);
- trackRecord(toStore);
- delegate.onRecordStoreSucceeded(toStore.guid);
-
- Logger.debug(LOG_TAG, "Inserted record with guid " + toStore.guid + " as androidID " + toStore.androidID);
- }
-
- protected Record replace(Record newRecord, Record existingRecord) throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
- Record toStore = prepareRecord(newRecord);
-
- // newRecord should already have suitable androidID and guid.
- dbHelper.update(existingRecord.guid, toStore);
- updateBookkeeping(toStore);
- Logger.debug(LOG_TAG, "replace() returning record " + toStore.guid);
- return toStore;
- }
-
- /**
- * Retrieve a record from the store by GUID, without writing unnecessarily to the
- * database.
- *
- * @throws NoGuidForIdException
- * @throws NullCursorException
- * @throws ParentNotFoundException
- * @throws MultipleRecordsForGuidException
- */
- protected Record retrieveByGUIDDuringStore(String guid) throws
- NoGuidForIdException,
- NullCursorException,
- ParentNotFoundException,
- MultipleRecordsForGuidException {
- Cursor cursor = dbHelper.fetch(new String[] { guid });
- try {
- if (!cursor.moveToFirst()) {
- return null;
- }
-
- Record r = retrieveDuringStore(cursor);
-
- cursor.moveToNext();
- if (cursor.isAfterLast()) {
- // Got one record!
- return r; // Not transformed.
- }
-
- // More than one. Oh dear.
- throw (new MultipleRecordsForGuidException(null));
- } finally {
- cursor.close();
- }
- }
-
- /**
- * Attempt to find an equivalent record through some means other than GUID.
- *
- * @param record
- * The record for which to search.
- * @return
- * An equivalent Record object, or null if none is found.
- *
- * @throws MultipleRecordsForGuidException
- * @throws NoGuidForIdException
- * @throws NullCursorException
- * @throws ParentNotFoundException
- */
- protected Record findExistingRecord(Record record) throws MultipleRecordsForGuidException,
- NoGuidForIdException, NullCursorException, ParentNotFoundException {
-
- Logger.debug(LOG_TAG, "Finding existing record for incoming record with GUID " + record.guid);
- String recordString = buildRecordString(record);
- if (recordString == null) {
- Logger.debug(LOG_TAG, "No record string for incoming record " + record.guid);
- return null;
- }
-
- if (Logger.LOG_PERSONAL_INFORMATION) {
- Logger.pii(LOG_TAG, "Searching with record string " + recordString);
- } else {
- Logger.debug(LOG_TAG, "Searching with record string.");
- }
- String guid = getGuidForString(recordString);
- if (guid == null) {
- Logger.debug(LOG_TAG, "Failed to find existing record for " + record.guid);
- return null;
- }
-
- // Our map contained a match, but it could be a false positive. Since
- // computed record string is supposed to be a unique key, we can easily
- // verify our positive.
- Logger.debug(LOG_TAG, "Found one. Checking stored record.");
- Record stored = retrieveByGUIDDuringStore(guid);
- String storedRecordString = buildRecordString(record);
- if (recordString.equals(storedRecordString)) {
- Logger.debug(LOG_TAG, "Existing record matches incoming record. Returning existing record.");
- return stored;
- }
-
- // Oh no, we got a false positive! (This should be *very* rare --
- // essentially, we got a hash collision.) Search the DB for this record
- // explicitly by hand.
- Logger.debug(LOG_TAG, "Existing record does not match incoming record. Trying to find record by record string.");
- return findByRecordString(recordString);
- }
-
- protected String getGuidForString(String recordString) throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
- if (recordToGuid == null) {
- createRecordToGuidMap();
- }
- return recordToGuid.get(recordString.hashCode());
- }
-
- protected void createRecordToGuidMap() throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
- Logger.info(LOG_TAG, "BEGIN: creating record -> GUID map.");
- recordToGuid = new SparseArray<String>();
-
- // TODO: we should be able to do this entire thing with string concatenations within SQL.
- // Also consider whether it's better to fetch and process every record in the DB into
- // memory, or run a query per record to do the same thing.
- Cursor cur = dbHelper.fetchAll();
- try {
- if (!cur.moveToFirst()) {
- return;
- }
- while (!cur.isAfterLast()) {
- Record record = retrieveDuringStore(cur);
- if (record != null) {
- final String recordString = buildRecordString(record);
- if (recordString != null) {
- recordToGuid.put(recordString.hashCode(), record.guid);
- }
- }
- cur.moveToNext();
- }
- } finally {
- cur.close();
- }
- Logger.info(LOG_TAG, "END: creating record -> GUID map.");
- }
-
- /**
- * Search the local database for a record with the same "record string".
- * <p>
- * We expect to do this only in the unlikely event of a hash
- * collision, so we iterate the database completely. Since we want
- * to include information about the parents of bookmarks, it is
- * difficult to do better purely using the
- * <code>ContentProvider</code> interface.
- *
- * @param recordString
- * the "record string" to search for; must be n
- * @return a <code>Record</code> with the same "record string", or
- * <code>null</code> if none is present.
- * @throws ParentNotFoundException
- * @throws NullCursorException
- * @throws NoGuidForIdException
- */
- protected Record findByRecordString(String recordString) throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
- Cursor cur = dbHelper.fetchAll();
- try {
- if (!cur.moveToFirst()) {
- return null;
- }
- while (!cur.isAfterLast()) {
- Record record = retrieveDuringStore(cur);
- if (record != null) {
- final String storedRecordString = buildRecordString(record);
- if (recordString.equals(storedRecordString)) {
- return record;
- }
- }
- cur.moveToNext();
- }
- return null;
- } finally {
- cur.close();
- }
- }
-
- public void putRecordToGuidMap(String recordString, String guid) throws NoGuidForIdException, NullCursorException, ParentNotFoundException {
- if (recordString == null) {
- return;
- }
-
- if (recordToGuid == null) {
- createRecordToGuidMap();
- }
- recordToGuid.put(recordString.hashCode(), guid);
- }
-
- protected abstract Record prepareRecord(Record record);
-
- protected void updateBookkeeping(Record record) throws NoGuidForIdException,
- NullCursorException,
- ParentNotFoundException {
- putRecordToGuidMap(buildRecordString(record), record.guid);
- }
-
- protected WipeRunnable getWipeRunnable(RepositorySessionWipeDelegate delegate) {
- return new WipeRunnable(delegate);
- }
-
- @Override
- public void wipe(RepositorySessionWipeDelegate delegate) {
- Runnable command = getWipeRunnable(delegate);
- storeWorkQueue.execute(command);
- }
-
- class WipeRunnable implements Runnable {
- protected RepositorySessionWipeDelegate delegate;
-
- public WipeRunnable(RepositorySessionWipeDelegate delegate) {
- this.delegate = delegate;
- }
-
- @Override
- public void run() {
- if (!isActive()) {
- delegate.onWipeFailed(new InactiveSessionException(null));
- return;
- }
- dbHelper.wipe();
- delegate.onWipeSucceeded();
- }
- }
-
- // For testing purposes.
- public AndroidBrowserRepositoryDataAccessor getDBHelper() {
- return dbHelper;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/BookmarksDeletionManager.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/BookmarksDeletionManager.java
deleted file mode 100644
index d8d8756f7..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/BookmarksDeletionManager.java
+++ /dev/null
@@ -1,239 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.android;
-
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
-
-/**
- * Queue up deletions. Process them at the end.
- *
- * Algorithm:
- *
- * * Collect GUIDs as we go. For convenience we partition these into
- * folders and non-folders.
- *
- * * Non-folders can be deleted in batches as we go.
- *
- * * At the end of the sync:
- * * Delete all that aren't folders.
- * * Move the remaining children of any that are folders to an "Orphans" folder.
- * - We do this even for children that are _marked_ as deleted -- we still want
- * to upload them, and their parent is irrelevant.
- * * Delete all the folders.
- *
- * * Any outstanding records -- the ones we moved to "Orphans" -- are true orphans.
- * These should be reuploaded (because their parent has changed), as should their
- * new parent (because its children array has changed).
- * We achieve the former by moving them without tracking (but we don't make any
- * special effort here -- warning! Lurking bug!).
- * We achieve the latter by bumping its mtime. The caller should take care of untracking it.
- *
- * Note that we make no particular effort to handle repositioning or reparenting:
- * batching deletes at the end should be handled seamlessly by existing code,
- * because the deleted records could have arrived in a batch at the end regardless.
- *
- * Note that this class is not thread safe. This should be fine: call it only
- * from within a store runnable.
- *
- */
-public class BookmarksDeletionManager {
- private static final String LOG_TAG = "BookmarkDelete";
-
- private final AndroidBrowserBookmarksDataAccessor dataAccessor;
- private RepositorySessionStoreDelegate delegate;
-
- private final int flushThreshold;
-
- private final HashSet<String> folders = new HashSet<String>();
- private final HashSet<String> nonFolders = new HashSet<String>();
- private int nonFolderCount = 0;
-
- // Records that we need to touch once we've deleted the non-folders.
- private HashSet<String> nonFolderParents = new HashSet<String>();
- private HashSet<String> folderParents = new HashSet<String>();
-
- /**
- * Create an instance to be used for tracking deletions in a bookmarks
- * repository session.
- *
- * @param dataAccessor
- * Used to effect database changes.
- *
- * @param flushThreshold
- * When this many non-folder records have been stored for deletion,
- * an incremental flush occurs.
- */
- public BookmarksDeletionManager(AndroidBrowserBookmarksDataAccessor dataAccessor, int flushThreshold) {
- this.dataAccessor = dataAccessor;
- this.flushThreshold = flushThreshold;
- }
-
- /**
- * Set the delegate to use for callbacks.
- * If not invoked, no callbacks will be submitted.
- *
- * @param delegate a delegate, which should already be a delayed delegate.
- */
- public void setDelegate(RepositorySessionStoreDelegate delegate) {
- this.delegate = delegate;
- }
-
- public void deleteRecord(String guid, boolean isFolder, String parentGUID) {
- if (guid == null) {
- Logger.warn(LOG_TAG, "Cannot queue deletion of record with no GUID.");
- return;
- }
- Logger.debug(LOG_TAG, "Queuing deletion of " + guid);
-
- if (isFolder) {
- folders.add(guid);
- if (!folders.contains(parentGUID)) {
- // We're not going to delete its parent; will need to bump it.
- folderParents.add(parentGUID);
- }
-
- nonFolderParents.remove(guid);
- folderParents.remove(guid);
- return;
- }
-
- if (!folders.contains(parentGUID)) {
- // We're not going to delete its parent; will need to bump it.
- nonFolderParents.add(parentGUID);
- }
-
- if (nonFolders.add(guid)) {
- if (++nonFolderCount >= flushThreshold) {
- deleteNonFolders();
- }
- }
- }
-
- /**
- * Flush deletions that can be easily taken care of right now.
- */
- public void incrementalFlush() {
- // Yes, this means we only bump when we finish, not during an incremental flush.
- deleteNonFolders();
- }
-
- /**
- * Apply all pending deletions and reset state for the next batch of stores.
- *
- * @param orphanDestination the ID of the folder to which orphaned children
- * should be moved.
- *
- * @throws NullCursorException
- * @return a set of IDs to untrack. Will not be null.
- */
- public Set<String> flushAll(long orphanDestination, long now) throws NullCursorException {
- Logger.debug(LOG_TAG, "Doing complete flush of deleted items. Moving orphans to " + orphanDestination);
- deleteNonFolders();
-
- // Find out which parents *won't* be deleted, and thus need to have their
- // modified times bumped.
- nonFolderParents.removeAll(folders);
-
- Logger.debug(LOG_TAG, "Bumping modified times for " + nonFolderParents.size() +
- " parents of deleted non-folders.");
- dataAccessor.bumpModifiedByGUID(nonFolderParents, now);
-
- if (folders.size() > 0) {
- final String[] folderGUIDs = folders.toArray(new String[folders.size()]);
- final String[] folderIDs = getIDs(folderGUIDs); // Throws if any don't exist.
- int moved = dataAccessor.moveChildren(folderIDs, orphanDestination);
- if (moved > 0) {
- dataAccessor.bumpModified(orphanDestination, now);
- }
-
- // We've deleted or moved anything that might be under these folders.
- // Just delete them.
- final String folderWhere = RepoUtils.computeSQLInClause(folders.size(), BrowserContract.Bookmarks.GUID);
- dataAccessor.delete(folderWhere, folderGUIDs);
- invokeCallbacks(delegate, folderGUIDs);
-
- folderParents.removeAll(folders);
- Logger.debug(LOG_TAG, "Bumping modified times for " + folderParents.size() +
- " parents of deleted folders.");
- dataAccessor.bumpModifiedByGUID(folderParents, now);
-
- // Clean up.
- folders.clear();
- }
-
- HashSet<String> ret = nonFolderParents;
- ret.addAll(folderParents);
-
- nonFolderParents = new HashSet<String>();
- folderParents = new HashSet<String>();
- return ret;
- }
-
- private String[] getIDs(String[] guids) throws NullCursorException {
- // Convert GUIDs to numeric IDs.
- String[] ids = new String[guids.length];
- Map<String, Long> guidsToIDs = dataAccessor.idsForGUIDs(guids);
- for (int i = 0; i < guids.length; ++i) {
- String guid = guids[i];
- Long id = guidsToIDs.get(guid);
- if (id == null) {
- throw new IllegalArgumentException("Can't get ID for unknown record " + guid);
- }
- ids[i] = id.toString();
- }
- return ids;
- }
-
- /**
- * Flush non-folder deletions. This can be called at any time.
- */
- private void deleteNonFolders() {
- if (nonFolderCount == 0) {
- Logger.debug(LOG_TAG, "No non-folders to delete.");
- return;
- }
-
- Logger.debug(LOG_TAG, "Applying deletion of " + nonFolderCount + " non-folders.");
- final String[] nonFolderGUIDs = nonFolders.toArray(new String[nonFolderCount]);
- final String nonFolderWhere = RepoUtils.computeSQLInClause(nonFolderCount, BrowserContract.Bookmarks.GUID);
- dataAccessor.delete(nonFolderWhere, nonFolderGUIDs);
-
- invokeCallbacks(delegate, nonFolderGUIDs);
-
- // Discard these.
- // Note that we maintain folderParents and nonFolderParents; we need them later.
- nonFolders.clear();
- nonFolderCount = 0;
- }
-
- private void invokeCallbacks(RepositorySessionStoreDelegate delegate,
- String[] nonFolderGUIDs) {
- if (delegate == null) {
- return;
- }
- Logger.trace(LOG_TAG, "Invoking store callback for " + nonFolderGUIDs.length + " GUIDs.");
- for (String guid : nonFolderGUIDs) {
- delegate.onRecordStoreSucceeded(guid);
- }
- }
-
- /**
- * Clear state in case of redundancy (e.g., wipe).
- */
- public void clear() {
- nonFolders.clear();
- nonFolderCount = 0;
- folders.clear();
- nonFolderParents.clear();
- folderParents.clear();
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/BookmarksInsertionManager.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/BookmarksInsertionManager.java
deleted file mode 100644
index 98670d39b..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/BookmarksInsertionManager.java
+++ /dev/null
@@ -1,298 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.android;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
-import java.util.Map;
-import java.util.Set;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
-
-/**
- * Queue up insertions:
- * <ul>
- * <li>Folder inserts where the parent is known. Do these immediately, because
- * they allow other records to be inserted. Requires bookkeeping updates. On
- * insert, flush the next set.</li>
- * <li>Regular inserts where the parent is known. These can happen whenever.
- * Batch for speed.</li>
- * <li>Records where the parent is not known. These can be flushed out when the
- * parent is known, or entered as orphans. This can be a queue earlier in the
- * process, so they don't get assigned to Unsorted. Feed into the main batch
- * when the parent arrives.</li>
- * </ul>
- * <p>
- * Deletions are always done at the end so that orphaning is minimized, and
- * that's why we are batching folders and non-folders separately.
- * <p>
- * Updates are always applied as they arrive.
- * <p>
- * Note that this class is not thread safe. This should be fine: call it only
- * from within a store runnable.
- */
-public class BookmarksInsertionManager {
- public static final String LOG_TAG = "BookmarkInsert";
- public static boolean DEBUG = false;
-
- protected final int flushThreshold;
- protected final BookmarkInserter inserter;
-
- /**
- * Folders that have been successfully inserted.
- */
- private final Set<String> insertedFolders = new HashSet<String>();
-
- /**
- * Non-folders waiting for bulk insertion.
- * <p>
- * We write in insertion order to keep things easy to debug.
- */
- private final Set<BookmarkRecord> nonFoldersToWrite = new LinkedHashSet<BookmarkRecord>();
-
- /**
- * Map from parent folder GUID to child records (folders and non-folders)
- * waiting to be enqueued after parent folder is inserted.
- */
- private final Map<String, Set<BookmarkRecord>> recordsWaitingForParent = new HashMap<String, Set<BookmarkRecord>>();
-
- /**
- * Create an instance to be used for tracking insertions in a bookmarks
- * repository session.
- *
- * @param flushThreshold
- * When this many non-folder records have been stored for insertion,
- * an incremental flush occurs.
- * @param insertedFolders
- * The GUIDs of all the folders already inserted into the database.
- * @param inserter
- * The <code>BookmarkInsert</code> to use.
- */
- public BookmarksInsertionManager(int flushThreshold, Collection<String> insertedFolders, BookmarkInserter inserter) {
- this.flushThreshold = flushThreshold;
- this.insertedFolders.addAll(insertedFolders);
- this.inserter = inserter;
- }
-
- protected void addRecordWithUnwrittenParent(BookmarkRecord record) {
- Set<BookmarkRecord> destination = recordsWaitingForParent.get(record.parentID);
- if (destination == null) {
- destination = new LinkedHashSet<BookmarkRecord>();
- recordsWaitingForParent.put(record.parentID, destination);
- }
- destination.add(record);
- }
-
- /**
- * If <code>record</code> is a folder, insert it immediately; if it is a
- * non-folder, enqueue it. Then do the same for any records waiting for this record.
- *
- * @param record
- * the <code>BookmarkRecord</code> to enqueue.
- */
- protected void recursivelyEnqueueRecordAndChildren(BookmarkRecord record) {
- if (record.isFolder()) {
- if (!inserter.insertFolder(record)) {
- Logger.warn(LOG_TAG, "Folder with known parent with guid " + record.parentID + " failed to insert!");
- return;
- }
- Logger.debug(LOG_TAG, "Folder with known parent with guid " + record.parentID + " inserted; adding to inserted folders.");
- insertedFolders.add(record.guid);
- } else {
- Logger.debug(LOG_TAG, "Non-folder has known parent with guid " + record.parentID + "; adding to insertion queue.");
- nonFoldersToWrite.add(record);
- }
-
- // Now process record's children.
- Set<BookmarkRecord> waiting = recordsWaitingForParent.remove(record.guid);
- if (waiting == null) {
- return;
- }
- for (BookmarkRecord waiter : waiting) {
- recursivelyEnqueueRecordAndChildren(waiter);
- }
- }
-
- /**
- * Enqueue a folder.
- *
- * @param record
- * the folder to enqueue.
- */
- protected void enqueueFolder(BookmarkRecord record) {
- Logger.debug(LOG_TAG, "Inserting folder with guid " + record.guid);
-
- if (!insertedFolders.contains(record.parentID)) {
- Logger.debug(LOG_TAG, "Folder has unknown parent with guid " + record.parentID + "; keeping until we see the parent.");
- addRecordWithUnwrittenParent(record);
- return;
- }
-
- // Parent is known; add as much of the tree as this roots.
- recursivelyEnqueueRecordAndChildren(record);
- flushNonFoldersIfNecessary();
- }
-
- /**
- * Enqueue a non-folder.
- *
- * @param record
- * the non-folder to enqueue.
- */
- protected void enqueueNonFolder(BookmarkRecord record) {
- Logger.debug(LOG_TAG, "Inserting non-folder with guid " + record.guid);
-
- if (!insertedFolders.contains(record.parentID)) {
- Logger.debug(LOG_TAG, "Non-folder has unknown parent with guid " + record.parentID + "; keeping until we see the parent.");
- addRecordWithUnwrittenParent(record);
- return;
- }
-
- // Parent is known; add to insertion queue and maybe write.
- Logger.debug(LOG_TAG, "Non-folder has known parent with guid " + record.parentID + "; adding to insertion queue.");
- nonFoldersToWrite.add(record);
- flushNonFoldersIfNecessary();
- }
-
- /**
- * Enqueue a bookmark record for eventual insertion.
- *
- * @param record
- * the <code>BookmarkRecord</code> to enqueue.
- */
- public void enqueueRecord(BookmarkRecord record) {
- if (record.isFolder()) {
- enqueueFolder(record);
- } else {
- enqueueNonFolder(record);
- }
- if (DEBUG) {
- dumpState();
- }
- }
-
- /**
- * Flush non-folders; empties the insertion queue entirely.
- */
- protected void flushNonFolders() {
- inserter.bulkInsertNonFolders(nonFoldersToWrite); // All errors are handled in bulkInsertNonFolders.
- nonFoldersToWrite.clear();
- }
-
- /**
- * Flush non-folder insertions if there are many of them; empties the
- * insertion queue entirely.
- */
- protected void flushNonFoldersIfNecessary() {
- int num = nonFoldersToWrite.size();
- if (num < flushThreshold) {
- Logger.debug(LOG_TAG, "Incremental flush called with " + num + " < " + flushThreshold + " non-folders; not flushing.");
- return;
- }
- Logger.debug(LOG_TAG, "Incremental flush called with " + num + " non-folders; flushing.");
- flushNonFolders();
- }
-
- /**
- * Insert all remaining folders followed by all remaining non-folders,
- * regardless of whether parent records have been successfully inserted.
- */
- public void finishUp() {
- // Iterate through all waiting records, writing the folders and collecting
- // the non-folders for bulk insertion.
- int numFolders = 0;
- int numNonFolders = 0;
- for (Set<BookmarkRecord> records : recordsWaitingForParent.values()) {
- for (BookmarkRecord record : records) {
- if (!record.isFolder()) {
- numNonFolders += 1;
- nonFoldersToWrite.add(record);
- continue;
- }
-
- numFolders += 1;
- if (!inserter.insertFolder(record)) {
- Logger.warn(LOG_TAG, "Folder with known parent with guid " + record.parentID + " failed to insert!");
- continue;
- }
-
- Logger.debug(LOG_TAG, "Folder with known parent with guid " + record.parentID + " inserted; adding to inserted folders.");
- insertedFolders.add(record.guid);
- }
- }
- recordsWaitingForParent.clear();
- flushNonFolders();
-
- Logger.debug(LOG_TAG, "finishUp inserted " +
- numFolders + " folders without known parents and " +
- numNonFolders + " non-folders without known parents.");
- if (DEBUG) {
- dumpState();
- }
- }
-
- public void clear() {
- this.insertedFolders.clear();
- this.nonFoldersToWrite.clear();
- this.recordsWaitingForParent.clear();
- }
-
- // For debugging.
- public boolean isClear() {
- return nonFoldersToWrite.isEmpty() && recordsWaitingForParent.isEmpty();
- }
-
- // For debugging.
- public void dumpState() {
- ArrayList<String> readies = new ArrayList<String>();
- for (BookmarkRecord record : nonFoldersToWrite) {
- readies.add(record.guid);
- }
- String ready = Utils.toCommaSeparatedString(new ArrayList<String>(readies));
-
- ArrayList<String> waits = new ArrayList<String>();
- for (Set<BookmarkRecord> recs : recordsWaitingForParent.values()) {
- for (BookmarkRecord rec : recs) {
- waits.add(rec.guid);
- }
- }
- String waiting = Utils.toCommaSeparatedString(waits);
- String known = Utils.toCommaSeparatedString(insertedFolders);
-
- Logger.debug(LOG_TAG, "Q=(" + ready + "), W = (" + waiting + "), P=(" + known + ")");
- }
-
- public interface BookmarkInserter {
- /**
- * Insert a single folder.
- * <p>
- * All exceptions should be caught and all delegate callbacks invoked here.
- *
- * @param record
- * the record to insert.
- * @return
- * <code>true</code> if the folder was inserted; <code>false</code> otherwise.
- */
- public boolean insertFolder(BookmarkRecord record);
-
- /**
- * Insert many non-folders. Each non-folder's parent was already present in
- * the database before this <code>BookmarkInsertionsManager</code> was
- * created, or had <code>insertFolder</code> called with it as argument (and
- * possibly was not inserted).
- * <p>
- * All exceptions should be caught and all delegate callbacks invoked here.
- *
- * @param records
- * the records to insert.
- */
- public void bulkInsertNonFolders(Collection<BookmarkRecord> records);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/BrowserContractHelpers.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/BrowserContractHelpers.java
deleted file mode 100644
index e83aea087..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/BrowserContractHelpers.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.android;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.sync.setup.Constants;
-
-import android.net.Uri;
-
-public class BrowserContractHelpers extends BrowserContract {
-
- protected static Uri withSyncAndDeletedAndProfile(Uri u) {
- return u.buildUpon()
- .appendQueryParameter(PARAM_PROFILE, Constants.DEFAULT_PROFILE)
- .appendQueryParameter(PARAM_IS_SYNC, "true")
- .appendQueryParameter(PARAM_SHOW_DELETED, "true")
- .build();
- }
- protected static Uri withSyncAndProfile(Uri u) {
- return u.buildUpon()
- .appendQueryParameter(PARAM_PROFILE, Constants.DEFAULT_PROFILE)
- .appendQueryParameter(PARAM_IS_SYNC, "true")
- .build();
- }
-
- public static final Uri BOOKMARKS_CONTENT_URI = withSyncAndDeletedAndProfile(Bookmarks.CONTENT_URI);
- public static final Uri BOOKMARKS_PARENTS_CONTENT_URI = withSyncAndDeletedAndProfile(Bookmarks.PARENTS_CONTENT_URI);
- public static final Uri BOOKMARKS_POSITIONS_CONTENT_URI = withSyncAndDeletedAndProfile(Bookmarks.POSITIONS_CONTENT_URI);
- public static final Uri HISTORY_CONTENT_URI = withSyncAndDeletedAndProfile(History.CONTENT_URI);
- public static final Uri VISITS_CONTENT_URI = withSyncAndDeletedAndProfile(Visits.CONTENT_URI);
- public static final Uri SCHEMA_CONTENT_URI = withSyncAndDeletedAndProfile(Schema.CONTENT_URI);
- public static final Uri PASSWORDS_CONTENT_URI = withSyncAndDeletedAndProfile(Passwords.CONTENT_URI);
- public static final Uri DELETED_PASSWORDS_CONTENT_URI = withSyncAndDeletedAndProfile(DeletedPasswords.CONTENT_URI);
- public static final Uri FORM_HISTORY_CONTENT_URI = withSyncAndProfile(FormHistory.CONTENT_URI);
- public static final Uri DELETED_FORM_HISTORY_CONTENT_URI = withSyncAndProfile(DeletedFormHistory.CONTENT_URI);
- public static final Uri TABS_CONTENT_URI = withSyncAndProfile(Tabs.CONTENT_URI);
- public static final Uri CLIENTS_CONTENT_URI = withSyncAndProfile(Clients.CONTENT_URI);
- public static final Uri LOGINS_CONTENT_URI = withSyncAndProfile(Logins.CONTENT_URI);
-
- public static final String[] PasswordColumns = new String[] {
- Passwords.ID,
- Passwords.HOSTNAME,
- Passwords.HTTP_REALM,
- Passwords.FORM_SUBMIT_URL,
- Passwords.USERNAME_FIELD,
- Passwords.PASSWORD_FIELD,
- Passwords.ENCRYPTED_USERNAME,
- Passwords.ENCRYPTED_PASSWORD,
- Passwords.ENC_TYPE,
- Passwords.TIME_CREATED,
- Passwords.TIME_LAST_USED,
- Passwords.TIME_PASSWORD_CHANGED,
- Passwords.TIMES_USED,
- Passwords.GUID
- };
-
- public static final String[] HistoryColumns = new String[] {
- CommonColumns._ID,
- SyncColumns.GUID,
- SyncColumns.DATE_CREATED,
- SyncColumns.DATE_MODIFIED,
- SyncColumns.IS_DELETED,
- History.TITLE,
- History.URL,
- History.DATE_LAST_VISITED,
- History.VISITS
- };
-
- public static final String[] BookmarkColumns = new String[] {
- CommonColumns._ID,
- SyncColumns.GUID,
- SyncColumns.DATE_CREATED,
- SyncColumns.DATE_MODIFIED,
- SyncColumns.IS_DELETED,
- Bookmarks.TITLE,
- Bookmarks.URL,
- Bookmarks.TYPE,
- Bookmarks.PARENT,
- Bookmarks.POSITION,
- Bookmarks.TAGS,
- Bookmarks.DESCRIPTION,
- Bookmarks.KEYWORD
- };
-
- public static final String[] FormHistoryColumns = new String[] {
- FormHistory.ID,
- FormHistory.GUID,
- FormHistory.FIELD_NAME,
- FormHistory.VALUE,
- FormHistory.TIMES_USED,
- FormHistory.FIRST_USED,
- FormHistory.LAST_USED
- };
-
- public static final String[] DeletedColumns = new String[] {
- BrowserContract.DeletedColumns.ID,
- BrowserContract.DeletedColumns.GUID,
- BrowserContract.DeletedColumns.TIME_DELETED
- };
-
- // Mapping from Sync types to Fennec types.
- public static final String[] BOOKMARK_TYPE_CODE_TO_STRING = {
- // Observe omissions: "microsummary", "item".
- "folder", "bookmark", "separator", "livemark", "query"
- };
- private static final int MAX_BOOKMARK_TYPE_CODE = BOOKMARK_TYPE_CODE_TO_STRING.length - 1;
- public static final Map<String, Integer> BOOKMARK_TYPE_STRING_TO_CODE;
- static {
- HashMap<String, Integer> t = new HashMap<String, Integer>();
- t.put("folder", Bookmarks.TYPE_FOLDER);
- t.put("bookmark", Bookmarks.TYPE_BOOKMARK);
- t.put("separator", Bookmarks.TYPE_SEPARATOR);
- t.put("livemark", Bookmarks.TYPE_LIVEMARK);
- t.put("query", Bookmarks.TYPE_QUERY);
- BOOKMARK_TYPE_STRING_TO_CODE = Collections.unmodifiableMap(t);
- }
-
- /**
- * Convert a database bookmark type code into the Sync string equivalent.
- *
- * @param code one of the <code>Bookmarks.TYPE_*</code> enumerations.
- * @return the string equivalent, or null if not found.
- */
- public static String typeStringForCode(int code) {
- if (0 <= code && code <= MAX_BOOKMARK_TYPE_CODE) {
- return BOOKMARK_TYPE_CODE_TO_STRING[code];
- }
- return null;
- }
-
- /**
- * Convert a Sync type string into a Fennec type code.
- *
- * @param type a type string, such as "livemark".
- * @return the type code, or -1 if not found.
- */
- public static int typeCodeForString(String type) {
- Integer found = BOOKMARK_TYPE_STRING_TO_CODE.get(type);
- if (found == null) {
- return -1;
- }
- return found;
- }
-
- public static boolean isSupportedType(String type) {
- return BOOKMARK_TYPE_STRING_TO_CODE.containsKey(type);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/CachedSQLiteOpenHelper.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/CachedSQLiteOpenHelper.java
deleted file mode 100644
index 5c17f9b85..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/CachedSQLiteOpenHelper.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.android;
-
-import android.content.Context;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteDatabase.CursorFactory;
-import android.database.sqlite.SQLiteOpenHelper;
-
-public abstract class CachedSQLiteOpenHelper extends SQLiteOpenHelper {
-
- public CachedSQLiteOpenHelper(Context context, String name, CursorFactory factory,
- int version) {
- super(context, name, factory, version);
- }
-
- // Cache these so we don't have to track them across cursors. Call `close`
- // when you're done.
- private SQLiteDatabase readableDatabase;
- private SQLiteDatabase writableDatabase;
-
- synchronized protected SQLiteDatabase getCachedReadableDatabase() {
- if (readableDatabase == null) {
- if (writableDatabase == null) {
- readableDatabase = this.getReadableDatabase();
- return readableDatabase;
- } else {
- return writableDatabase;
- }
- } else {
- return readableDatabase;
- }
- }
-
- synchronized protected SQLiteDatabase getCachedWritableDatabase() {
- if (writableDatabase == null) {
- writableDatabase = this.getWritableDatabase();
- }
- return writableDatabase;
- }
-
- @Override
- synchronized public void close() {
- if (readableDatabase != null) {
- readableDatabase.close();
- readableDatabase = null;
- }
- if (writableDatabase != null) {
- writableDatabase.close();
- writableDatabase = null;
- }
- super.close();
- }
-
- // Used for testing.
- public boolean isClosed() {
- return readableDatabase == null &&
- writableDatabase == null;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/ClientsDatabase.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/ClientsDatabase.java
deleted file mode 100644
index 4962a20c6..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/ClientsDatabase.java
+++ /dev/null
@@ -1,252 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.android;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-
-public class ClientsDatabase extends CachedSQLiteOpenHelper {
-
- public static final String LOG_TAG = "ClientsDatabase";
-
- // Database Specifications.
- protected static final String DB_NAME = "clients_database";
- protected static final int SCHEMA_VERSION = 3;
-
- // Clients Table.
- public static final String TBL_CLIENTS = "clients";
- public static final String COL_ACCOUNT_GUID = "guid";
- public static final String COL_PROFILE = "profile";
- public static final String COL_NAME = "name";
- public static final String COL_TYPE = "device_type";
-
- // Optional fields.
- public static final String COL_FORMFACTOR = "formfactor";
- public static final String COL_OS = "os";
- public static final String COL_APPLICATION = "application";
- public static final String COL_APP_PACKAGE = "appPackage";
- public static final String COL_DEVICE = "device";
-
- public static final String[] TBL_CLIENTS_COLUMNS = new String[] { COL_ACCOUNT_GUID, COL_PROFILE, COL_NAME, COL_TYPE,
- COL_FORMFACTOR, COL_OS, COL_APPLICATION, COL_APP_PACKAGE, COL_DEVICE };
- public static final String TBL_CLIENTS_KEY = COL_ACCOUNT_GUID + " = ? AND " +
- COL_PROFILE + " = ?";
-
- // Commands Table.
- public static final String TBL_COMMANDS = "commands";
- public static final String COL_COMMAND = "command";
- public static final String COL_ARGS = "args";
-
- public static final String[] TBL_COMMANDS_COLUMNS = new String[] { COL_ACCOUNT_GUID, COL_COMMAND, COL_ARGS };
- public static final String TBL_COMMANDS_KEY = COL_ACCOUNT_GUID + " = ? AND " +
- COL_COMMAND + " = ? AND " +
- COL_ARGS + " = ?";
- public static final String TBL_COMMANDS_GUID_QUERY = COL_ACCOUNT_GUID + " = ? ";
-
- private final RepoUtils.QueryHelper queryHelper;
-
- public ClientsDatabase(Context context) {
- super(context, DB_NAME, null, SCHEMA_VERSION);
- this.queryHelper = new RepoUtils.QueryHelper(context, null, LOG_TAG);
- Logger.debug(LOG_TAG, "ClientsDatabase instantiated.");
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- Logger.debug(LOG_TAG, "ClientsDatabase.onCreate().");
- createClientsTable(db);
- createCommandsTable(db);
- }
-
- public static void createClientsTable(SQLiteDatabase db) {
- Logger.debug(LOG_TAG, "ClientsDatabase.createClientsTable().");
- String createClientsTableSql = "CREATE TABLE " + TBL_CLIENTS + " ("
- + COL_ACCOUNT_GUID + " TEXT, "
- + COL_PROFILE + " TEXT, "
- + COL_NAME + " TEXT, "
- + COL_TYPE + " TEXT, "
- + COL_FORMFACTOR + " TEXT, "
- + COL_OS + " TEXT, "
- + COL_APPLICATION + " TEXT, "
- + COL_APP_PACKAGE + " TEXT, "
- + COL_DEVICE + " TEXT, "
- + "PRIMARY KEY (" + COL_ACCOUNT_GUID + ", " + COL_PROFILE + "))";
- db.execSQL(createClientsTableSql);
- }
-
- public static void createCommandsTable(SQLiteDatabase db) {
- Logger.debug(LOG_TAG, "ClientsDatabase.createCommandsTable().");
- String createCommandsTableSql = "CREATE TABLE " + TBL_COMMANDS + " ("
- + COL_ACCOUNT_GUID + " TEXT, "
- + COL_COMMAND + " TEXT, "
- + COL_ARGS + " TEXT, "
- + "PRIMARY KEY (" + COL_ACCOUNT_GUID + ", " + COL_COMMAND + ", " + COL_ARGS + "), "
- + "FOREIGN KEY (" + COL_ACCOUNT_GUID + ") REFERENCES " + TBL_CLIENTS + " (" + COL_ACCOUNT_GUID + "))";
- db.execSQL(createCommandsTableSql);
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- Logger.debug(LOG_TAG, "ClientsDatabase.onUpgrade(" + oldVersion + ", " + newVersion + ").");
- if (oldVersion < 2) {
- // For now we'll just drop and recreate the tables.
- db.execSQL("DROP TABLE IF EXISTS " + TBL_CLIENTS);
- db.execSQL("DROP TABLE IF EXISTS " + TBL_COMMANDS);
- onCreate(db);
- return;
- }
-
- if (newVersion >= 3) {
- // Add the optional columns to clients.
- db.execSQL("ALTER TABLE " + TBL_CLIENTS + " ADD COLUMN " + COL_FORMFACTOR + " TEXT");
- db.execSQL("ALTER TABLE " + TBL_CLIENTS + " ADD COLUMN " + COL_OS + " TEXT");
- db.execSQL("ALTER TABLE " + TBL_CLIENTS + " ADD COLUMN " + COL_APPLICATION + " TEXT");
- db.execSQL("ALTER TABLE " + TBL_CLIENTS + " ADD COLUMN " + COL_APP_PACKAGE + " TEXT");
- db.execSQL("ALTER TABLE " + TBL_CLIENTS + " ADD COLUMN " + COL_DEVICE + " TEXT");
- }
- }
-
- public void wipeDB() {
- SQLiteDatabase db = this.getCachedWritableDatabase();
- onUpgrade(db, 0, SCHEMA_VERSION);
- }
-
- public void wipeClientsTable() {
- SQLiteDatabase db = this.getCachedWritableDatabase();
- db.execSQL("DELETE FROM " + TBL_CLIENTS);
- }
-
- public void wipeCommandsTable() {
- SQLiteDatabase db = this.getCachedWritableDatabase();
- db.execSQL("DELETE FROM " + TBL_COMMANDS);
- }
-
- // If a record with given GUID exists, we'll update it,
- // otherwise we'll insert it.
- public void store(String profileId, ClientRecord record) {
- SQLiteDatabase db = this.getCachedWritableDatabase();
-
- ContentValues cv = new ContentValues();
- cv.put(COL_ACCOUNT_GUID, record.guid);
- cv.put(COL_PROFILE, profileId);
- cv.put(COL_NAME, record.name);
- cv.put(COL_TYPE, record.type);
-
- if (record.formfactor != null) {
- cv.put(COL_FORMFACTOR, record.formfactor);
- }
-
- if (record.os != null) {
- cv.put(COL_OS, record.os);
- }
-
- if (record.application != null) {
- cv.put(COL_APPLICATION, record.application);
- }
-
- if (record.appPackage != null) {
- cv.put(COL_APP_PACKAGE, record.appPackage);
- }
-
- if (record.device != null) {
- cv.put(COL_DEVICE, record.device);
- }
-
- String[] args = new String[] { record.guid, profileId };
- int rowsUpdated = db.update(TBL_CLIENTS, cv, TBL_CLIENTS_KEY, args);
-
- if (rowsUpdated >= 1) {
- Logger.debug(LOG_TAG, "Replaced client record for row with accountGUID " + record.guid);
- } else {
- long rowId = db.insert(TBL_CLIENTS, null, cv);
- Logger.debug(LOG_TAG, "Inserted client record into row: " + rowId);
- }
- }
-
- /**
- * Store a command in the commands database if it doesn't already exist.
- *
- * @param accountGUID
- * @param command - The command type
- * @param args - A JSON string of args
- * @throws NullCursorException
- */
- public void store(String accountGUID, String command, String args) throws NullCursorException {
- if (Logger.LOG_PERSONAL_INFORMATION) {
- Logger.pii(LOG_TAG, "Storing command " + command + " with args " + args);
- } else {
- Logger.trace(LOG_TAG, "Storing command " + command + ".");
- }
- SQLiteDatabase db = this.getCachedWritableDatabase();
-
- ContentValues cv = new ContentValues();
- cv.put(COL_ACCOUNT_GUID, accountGUID);
- cv.put(COL_COMMAND, command);
- if (args == null) {
- cv.put(COL_ARGS, "[]");
- } else {
- cv.put(COL_ARGS, args);
- }
-
- Cursor cur = this.fetchSpecificCommand(accountGUID, command, args);
- try {
- if (cur.moveToFirst()) {
- Logger.debug(LOG_TAG, "Command already exists in database.");
- return;
- }
- } finally {
- cur.close();
- }
-
- long rowId = db.insert(TBL_COMMANDS, null, cv);
- Logger.debug(LOG_TAG, "Inserted command into row: " + rowId);
- }
-
- public Cursor fetchClientsCursor(String accountGUID, String profileId) throws NullCursorException {
- String[] args = new String[] { accountGUID, profileId };
- SQLiteDatabase db = this.getCachedReadableDatabase();
-
- return queryHelper.safeQuery(db, ".fetchClientsCursor", TBL_CLIENTS, TBL_CLIENTS_COLUMNS, TBL_CLIENTS_KEY, args);
- }
-
- public Cursor fetchSpecificCommand(String accountGUID, String command, String commandArgs) throws NullCursorException {
- String[] args = new String[] { accountGUID, command, commandArgs };
- SQLiteDatabase db = this.getCachedReadableDatabase();
-
- return queryHelper.safeQuery(db, ".fetchSpecificCommand", TBL_COMMANDS, TBL_COMMANDS_COLUMNS, TBL_COMMANDS_KEY, args);
- }
-
- public Cursor fetchCommandsForClient(String accountGUID) throws NullCursorException {
- String[] args = new String[] { accountGUID };
- SQLiteDatabase db = this.getCachedReadableDatabase();
-
- return queryHelper.safeQuery(db, ".fetchCommandsForClient", TBL_COMMANDS, TBL_COMMANDS_COLUMNS, TBL_COMMANDS_GUID_QUERY, args);
- }
-
- public Cursor fetchAllClients() throws NullCursorException {
- SQLiteDatabase db = this.getCachedReadableDatabase();
-
- return queryHelper.safeQuery(db, ".fetchAllClients", TBL_CLIENTS, TBL_CLIENTS_COLUMNS, null, null);
- }
-
- public Cursor fetchAllCommands() throws NullCursorException {
- SQLiteDatabase db = this.getCachedReadableDatabase();
-
- return queryHelper.safeQuery(db, ".fetchAllCommands", TBL_COMMANDS, TBL_COMMANDS_COLUMNS, null, null);
- }
-
- public void deleteClient(String accountGUID, String profileId) {
- String[] args = new String[] { accountGUID, profileId };
-
- SQLiteDatabase db = this.getCachedWritableDatabase();
- db.delete(TBL_CLIENTS, TBL_CLIENTS_KEY, args);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/ClientsDatabaseAccessor.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/ClientsDatabaseAccessor.java
deleted file mode 100644
index 4af84ceaf..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/ClientsDatabaseAccessor.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.android;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.json.simple.JSONArray;
-
-import org.mozilla.gecko.sync.CommandProcessor.Command;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
-import org.mozilla.gecko.sync.setup.Constants;
-
-import android.content.Context;
-import android.database.Cursor;
-
-public class ClientsDatabaseAccessor {
-
- public static final String LOG_TAG = "ClientsDatabaseAccessor";
-
- private ClientsDatabase db;
-
- // Need this so we can properly stub out the class for testing.
- public ClientsDatabaseAccessor() {}
-
- public ClientsDatabaseAccessor(Context context) {
- db = new ClientsDatabase(context);
- }
-
- public void store(ClientRecord record) {
- db.store(getProfileId(), record);
- }
-
- public void store(Collection<ClientRecord> records) {
- for (ClientRecord record : records) {
- this.store(record);
- }
- }
-
- public void store(String accountGUID, Command command) throws NullCursorException {
- db.store(accountGUID, command.commandType, command.args.toJSONString());
- }
-
- public ClientRecord fetchClient(String accountGUID) throws NullCursorException {
- final Cursor cur = db.fetchClientsCursor(accountGUID, getProfileId());
- try {
- if (!cur.moveToFirst()) {
- return null;
- }
- return recordFromCursor(cur);
- } finally {
- cur.close();
- }
- }
-
- public Map<String, ClientRecord> fetchAllClients() throws NullCursorException {
- final HashMap<String, ClientRecord> map = new HashMap<String, ClientRecord>();
- final Cursor cur = db.fetchAllClients();
- try {
- if (!cur.moveToFirst()) {
- return Collections.unmodifiableMap(map);
- }
-
- while (!cur.isAfterLast()) {
- ClientRecord clientRecord = recordFromCursor(cur);
- map.put(clientRecord.guid, clientRecord);
- cur.moveToNext();
- }
- return Collections.unmodifiableMap(map);
- } finally {
- cur.close();
- }
- }
-
- public List<Command> fetchAllCommands() throws NullCursorException {
- final List<Command> commands = new ArrayList<Command>();
- final Cursor cur = db.fetchAllCommands();
- try {
- if (!cur.moveToFirst()) {
- return Collections.unmodifiableList(commands);
- }
-
- while (!cur.isAfterLast()) {
- Command command = commandFromCursor(cur);
- commands.add(command);
- cur.moveToNext();
- }
- return Collections.unmodifiableList(commands);
- } finally {
- cur.close();
- }
- }
-
- public List<Command> fetchCommandsForClient(String accountGUID) throws NullCursorException {
- final List<Command> commands = new ArrayList<Command>();
- final Cursor cur = db.fetchCommandsForClient(accountGUID);
- try {
- if (!cur.moveToFirst()) {
- return Collections.unmodifiableList(commands);
- }
-
- while(!cur.isAfterLast()) {
- Command command = commandFromCursor(cur);
- commands.add(command);
- cur.moveToNext();
- }
- return Collections.unmodifiableList(commands);
- } finally {
- cur.close();
- }
- }
-
- protected static ClientRecord recordFromCursor(Cursor cur) {
- final String accountGUID = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_ACCOUNT_GUID);
- final String clientName = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_NAME);
- final String clientType = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_TYPE);
-
- final ClientRecord record = new ClientRecord(accountGUID);
- record.name = clientName;
- record.type = clientType;
-
- // Optional fields. These will either be null or strings.
- record.formfactor = RepoUtils.optStringFromCursor(cur, ClientsDatabase.COL_FORMFACTOR);
- record.os = RepoUtils.optStringFromCursor(cur, ClientsDatabase.COL_OS);
- record.device = RepoUtils.optStringFromCursor(cur, ClientsDatabase.COL_DEVICE);
- record.appPackage = RepoUtils.optStringFromCursor(cur, ClientsDatabase.COL_APP_PACKAGE);
- record.application = RepoUtils.optStringFromCursor(cur, ClientsDatabase.COL_APPLICATION);
-
- return record;
- }
-
- protected static Command commandFromCursor(Cursor cur) {
- String commandType = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_COMMAND);
- JSONArray commandArgs = RepoUtils.getJSONArrayFromCursor(cur, ClientsDatabase.COL_ARGS);
- return new Command(commandType, commandArgs);
- }
-
- public int clientsCount() {
- try {
- final Cursor cur = db.fetchAllClients();
- try {
- return cur.getCount();
- } finally {
- cur.close();
- }
- } catch (NullCursorException e) {
- return 0;
- }
-
- }
-
- private String getProfileId() {
- return Constants.DEFAULT_PROFILE;
- }
-
- public void wipeDB() {
- db.wipeDB();
- }
-
- public void wipeClientsTable() {
- db.wipeClientsTable();
- }
-
- public void wipeCommandsTable() {
- db.wipeCommandsTable();
- }
-
- public void close() {
- db.close();
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/FennecTabsRepository.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/FennecTabsRepository.java
deleted file mode 100644
index 720d856eb..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/FennecTabsRepository.java
+++ /dev/null
@@ -1,383 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.android;
-
-import java.util.ArrayList;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.db.Tab;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserContract.Clients;
-import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
-import org.mozilla.gecko.sync.repositories.InactiveSessionException;
-import org.mozilla.gecko.sync.repositories.NoContentProviderException;
-import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionGuidsSinceDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
-import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-import org.mozilla.gecko.sync.repositories.domain.TabsRecord;
-
-import android.content.ContentProviderClient;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.RemoteException;
-
-public class FennecTabsRepository extends Repository {
- private static final String LOG_TAG = "FennecTabsRepository";
-
- protected final ClientsDataDelegate clientsDataDelegate;
-
- public FennecTabsRepository(ClientsDataDelegate clientsDataDelegate) {
- this.clientsDataDelegate = clientsDataDelegate;
- }
-
- /**
- * Note that -- unlike most repositories -- this will only fetch Fennec's tabs,
- * and only store tabs from other clients.
- *
- * It will never retrieve tabs from other clients, or store tabs for Fennec,
- * unless you use {@link #fetch(String[], RepositorySessionFetchRecordsDelegate)}
- * and specify an explicit GUID.
- */
- public class FennecTabsRepositorySession extends RepositorySession {
- protected static final String LOG_TAG = "FennecTabsSession";
-
- private final ContentProviderClient tabsProvider;
- private final ContentProviderClient clientsProvider;
-
- protected final RepoUtils.QueryHelper tabsHelper;
-
- protected final ClientsDatabaseAccessor clientsDatabase;
-
- protected ContentProviderClient getContentProvider(final Context context, final Uri uri) throws NoContentProviderException {
- ContentProviderClient client = context.getContentResolver().acquireContentProviderClient(uri);
- if (client == null) {
- throw new NoContentProviderException(uri);
- }
- return client;
- }
-
- protected void releaseProviders() {
- try {
- clientsProvider.release();
- } catch (Exception e) {}
- try {
- tabsProvider.release();
- } catch (Exception e) {}
- clientsDatabase.close();
- }
-
- public FennecTabsRepositorySession(Repository repository, Context context) throws NoContentProviderException {
- super(repository);
- clientsProvider = getContentProvider(context, BrowserContractHelpers.CLIENTS_CONTENT_URI);
- try {
- tabsProvider = getContentProvider(context, BrowserContractHelpers.TABS_CONTENT_URI);
- } catch (NoContentProviderException e) {
- clientsProvider.release();
- throw e;
- } catch (Exception e) {
- clientsProvider.release();
- // Oh, Java.
- throw new RuntimeException(e);
- }
-
- tabsHelper = new RepoUtils.QueryHelper(context, BrowserContractHelpers.TABS_CONTENT_URI, LOG_TAG);
- clientsDatabase = new ClientsDatabaseAccessor(context);
- }
-
- @Override
- public void abort() {
- releaseProviders();
- super.abort();
- }
-
- @Override
- public void finish(final RepositorySessionFinishDelegate delegate) throws InactiveSessionException {
- releaseProviders();
- super.finish(delegate);
- }
-
- // Default parameters for local data: local client has null GUID. Override
- // these to test against non-live data.
- protected String localClientSelection() {
- return BrowserContract.Tabs.CLIENT_GUID + " IS NULL";
- }
-
- protected String[] localClientSelectionArgs() {
- return null;
- }
-
- @Override
- public void guidsSince(final long timestamp,
- final RepositorySessionGuidsSinceDelegate delegate) {
- // Bug 783692: Now that Bug 730039 has landed, we could implement this,
- // but it's not a priority since it's not used (yet).
- Logger.warn(LOG_TAG, "Not returning anything from guidsSince.");
- delegateQueue.execute(new Runnable() {
- @Override
- public void run() {
- delegate.onGuidsSinceSucceeded(new String[] {});
- }
- });
- }
-
- @Override
- public void fetchSince(final long timestamp,
- final RepositorySessionFetchRecordsDelegate delegate) {
- if (tabsProvider == null) {
- throw new IllegalArgumentException("tabsProvider was null.");
- }
- if (tabsHelper == null) {
- throw new IllegalArgumentException("tabsHelper was null.");
- }
-
- final String positionAscending = BrowserContract.Tabs.POSITION + " ASC";
-
- final String localClientSelection = localClientSelection();
- final String[] localClientSelectionArgs = localClientSelectionArgs();
-
- final Runnable command = new Runnable() {
- @Override
- public void run() {
- // We fetch all local tabs (since the record must contain them all)
- // but only process the record if the timestamp is sufficiently
- // recent, or if the client data has been modified.
- try {
- final Cursor cursor = tabsHelper.safeQuery(tabsProvider, ".fetchSince()", null,
- localClientSelection, localClientSelectionArgs, positionAscending);
- try {
- final String localClientGuid = clientsDataDelegate.getAccountGUID();
- final String localClientName = clientsDataDelegate.getClientName();
- final TabsRecord tabsRecord = FennecTabsRepository.tabsRecordFromCursor(cursor, localClientGuid, localClientName);
-
- if (tabsRecord.lastModified >= timestamp ||
- clientsDataDelegate.getLastModifiedTimestamp() >= timestamp) {
- delegate.onFetchedRecord(tabsRecord);
- }
- } finally {
- cursor.close();
- }
- } catch (Exception e) {
- delegate.onFetchFailed(e, null);
- return;
- }
- delegate.onFetchCompleted(now());
- }
- };
-
- delegateQueue.execute(command);
- }
-
- @Override
- public void fetch(final String[] guids,
- final RepositorySessionFetchRecordsDelegate delegate) {
- // Bug 783692: Now that Bug 730039 has landed, we could implement this,
- // but it's not a priority since it's not used (yet).
- Logger.warn(LOG_TAG, "Not returning anything from fetch");
- delegateQueue.execute(new Runnable() {
- @Override
- public void run() {
- delegate.onFetchCompleted(now());
- }
- });
- }
-
- @Override
- public void fetchAll(final RepositorySessionFetchRecordsDelegate delegate) {
- fetchSince(0, delegate);
- }
-
- private static final String TABS_CLIENT_GUID_IS = BrowserContract.Tabs.CLIENT_GUID + " = ?";
- private static final String CLIENT_GUID_IS = BrowserContract.Clients.GUID + " = ?";
-
- @Override
- public void store(final Record record) throws NoStoreDelegateException {
- if (delegate == null) {
- Logger.warn(LOG_TAG, "No store delegate.");
- throw new NoStoreDelegateException();
- }
- if (record == null) {
- Logger.error(LOG_TAG, "Record sent to store was null");
- throw new IllegalArgumentException("Null record passed to FennecTabsRepositorySession.store().");
- }
- if (!(record instanceof TabsRecord)) {
- Logger.error(LOG_TAG, "Can't store anything but a TabsRecord");
- throw new IllegalArgumentException("Non-TabsRecord passed to FennecTabsRepositorySession.store().");
- }
- final TabsRecord tabsRecord = (TabsRecord) record;
-
- Runnable command = new Runnable() {
- @Override
- public void run() {
- Logger.debug(LOG_TAG, "Storing tabs for client " + tabsRecord.guid);
- if (!isActive()) {
- delegate.onRecordStoreFailed(new InactiveSessionException(null), record.guid);
- return;
- }
- if (tabsRecord.guid == null) {
- delegate.onRecordStoreFailed(new RuntimeException("Can't store record with null GUID."), record.guid);
- return;
- }
-
- try {
- // This is nice and easy: we *always* store.
- final String[] selectionArgs = new String[] { tabsRecord.guid };
- if (tabsRecord.deleted) {
- try {
- Logger.debug(LOG_TAG, "Clearing entry for client " + tabsRecord.guid);
- clientsProvider.delete(BrowserContractHelpers.CLIENTS_CONTENT_URI,
- CLIENT_GUID_IS,
- selectionArgs);
- delegate.onRecordStoreSucceeded(record.guid);
- } catch (Exception e) {
- delegate.onRecordStoreFailed(e, record.guid);
- }
- return;
- }
-
- // If it exists, update the client record; otherwise insert.
- final ContentValues clientsCV = tabsRecord.getClientsContentValues();
-
- final ClientRecord clientRecord = clientsDatabase.fetchClient(tabsRecord.guid);
- if (null != clientRecord) {
- // Null is an acceptable device type.
- clientsCV.put(Clients.DEVICE_TYPE, clientRecord.type);
- }
-
- Logger.debug(LOG_TAG, "Updating clients provider.");
- final int updated = clientsProvider.update(BrowserContractHelpers.CLIENTS_CONTENT_URI,
- clientsCV,
- CLIENT_GUID_IS,
- selectionArgs);
- if (0 == updated) {
- clientsProvider.insert(BrowserContractHelpers.CLIENTS_CONTENT_URI, clientsCV);
- }
-
- // Now insert tabs.
- final ContentValues[] tabsArray = tabsRecord.getTabsContentValues();
- Logger.debug(LOG_TAG, "Inserting " + tabsArray.length + " tabs for client " + tabsRecord.guid);
-
- tabsProvider.delete(BrowserContractHelpers.TABS_CONTENT_URI, TABS_CLIENT_GUID_IS, selectionArgs);
- final int inserted = tabsProvider.bulkInsert(BrowserContractHelpers.TABS_CONTENT_URI, tabsArray);
- Logger.trace(LOG_TAG, "Inserted: " + inserted);
-
- delegate.onRecordStoreSucceeded(record.guid);
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Error storing tabs.", e);
- delegate.onRecordStoreFailed(e, record.guid);
- }
- }
- };
-
- storeWorkQueue.execute(command);
- }
-
- @Override
- public void wipe(RepositorySessionWipeDelegate delegate) {
- try {
- tabsProvider.delete(BrowserContractHelpers.TABS_CONTENT_URI, null, null);
- clientsProvider.delete(BrowserContractHelpers.CLIENTS_CONTENT_URI, null, null);
- } catch (RemoteException e) {
- Logger.warn(LOG_TAG, "Got RemoteException in wipe.", e);
- delegate.onWipeFailed(e);
- return;
- }
- delegate.onWipeSucceeded();
- }
- }
-
- @Override
- public void createSession(RepositorySessionCreationDelegate delegate,
- Context context) {
- try {
- final FennecTabsRepositorySession session = new FennecTabsRepositorySession(this, context);
- delegate.onSessionCreated(session);
- } catch (Exception e) {
- delegate.onSessionCreateFailed(e);
- }
- }
-
- /**
- * Extract a <code>TabsRecord</code> from a cursor.
- * <p>
- * Caller is responsible for creating and closing cursor. Each row of the
- * cursor should be an individual tab record.
- * <p>
- * The extracted tabs record has the given client GUID and client name.
- *
- * @param cursor
- * to inspect.
- * @param clientGuid
- * returned tabs record will have this client GUID.
- * @param clientName
- * returned tabs record will have this client name.
- * @return <code>TabsRecord</code> instance.
- */
- public static TabsRecord tabsRecordFromCursor(final Cursor cursor, final String clientGuid, final String clientName) {
- final String collection = "tabs";
- final TabsRecord record = new TabsRecord(clientGuid, collection, 0, false);
- record.tabs = new ArrayList<Tab>();
- record.clientName = clientName;
-
- record.androidID = -1;
- record.deleted = false;
-
- record.lastModified = 0;
-
- int position = cursor.getPosition();
- try {
- cursor.moveToFirst();
- while (!cursor.isAfterLast()) {
- final Tab tab = Tab.fromCursor(cursor);
- record.tabs.add(tab);
-
- if (tab.lastUsed > record.lastModified) {
- record.lastModified = tab.lastUsed;
- }
-
- cursor.moveToNext();
- }
- } finally {
- cursor.moveToPosition(position);
- }
-
- return record;
- }
-
- /**
- * Deletes all non-local clients and their associated remote tabs.
- */
- public static void deleteNonLocalClientsAndTabs(Context context) {
- final String nonLocalClientSelection = BrowserContract.Clients.GUID + " IS NOT NULL";
-
- ContentProviderClient clientsProvider = context.getContentResolver()
- .acquireContentProviderClient(BrowserContractHelpers.CLIENTS_CONTENT_URI);
- if (clientsProvider == null) {
- Logger.warn(LOG_TAG, "Unable to create clientsProvider!");
- return;
- }
-
- try {
- Logger.info(LOG_TAG, "Clearing all non-local clients and their associated remote tabs for default profile.");
- clientsProvider.delete(BrowserContractHelpers.CLIENTS_CONTENT_URI, nonLocalClientSelection, null);
- } catch (RemoteException e) {
- Logger.warn(LOG_TAG, "Error while deleting", e);
- } finally {
- try {
- clientsProvider.release();
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Got exception releasing clientsProvider!", e);
- }
- }
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/FormHistoryRepositorySession.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/FormHistoryRepositorySession.java
deleted file mode 100644
index 9beafa712..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/FormHistoryRepositorySession.java
+++ /dev/null
@@ -1,723 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.android;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.Callable;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserContract.DeletedFormHistory;
-import org.mozilla.gecko.db.BrowserContract.FormHistory;
-import org.mozilla.gecko.sync.repositories.InactiveSessionException;
-import org.mozilla.gecko.sync.repositories.NoContentProviderException;
-import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.RecordFilter;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.StoreTrackingRepositorySession;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionGuidsSinceDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
-import org.mozilla.gecko.sync.repositories.domain.FormHistoryRecord;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-import android.content.ContentProviderClient;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.RemoteException;
-
-public class FormHistoryRepositorySession extends
- StoreTrackingRepositorySession {
- public static final String LOG_TAG = "FormHistoryRepoSess";
-
- /**
- * Number of records to insert in one batch.
- */
- public static final int INSERT_ITEM_THRESHOLD = 200;
-
- private static final Uri FORM_HISTORY_CONTENT_URI = BrowserContractHelpers.FORM_HISTORY_CONTENT_URI;
- private static final Uri DELETED_FORM_HISTORY_CONTENT_URI = BrowserContractHelpers.DELETED_FORM_HISTORY_CONTENT_URI;
-
- public static class FormHistoryRepository extends Repository {
-
- @Override
- public void createSession(RepositorySessionCreationDelegate delegate,
- Context context) {
- try {
- final FormHistoryRepositorySession session = new FormHistoryRepositorySession(this, context);
- delegate.onSessionCreated(session);
- } catch (Exception e) {
- delegate.onSessionCreateFailed(e);
- }
- }
- }
-
- protected final ContentProviderClient formsProvider;
- protected final RepoUtils.QueryHelper regularHelper;
- protected final RepoUtils.QueryHelper deletedHelper;
-
- /**
- * Acquire the content provider client.
- * <p>
- * The caller is responsible for releasing the client.
- *
- * @param context The application context.
- * @return The <code>ContentProviderClient</code>.
- * @throws NoContentProviderException
- */
- public static ContentProviderClient acquireContentProvider(final Context context)
- throws NoContentProviderException {
- Uri uri = BrowserContract.FORM_HISTORY_AUTHORITY_URI;
- ContentProviderClient client = context.getContentResolver().acquireContentProviderClient(uri);
- if (client == null) {
- throw new NoContentProviderException(uri);
- }
- return client;
- }
-
- protected void releaseProviders() {
- try {
- if (formsProvider != null) {
- formsProvider.release();
- }
- } catch (Exception e) {
- }
- }
-
- // Only used for testing.
- public ContentProviderClient getFormsProvider() {
- return formsProvider;
- }
-
- public FormHistoryRepositorySession(Repository repository, Context context)
- throws NoContentProviderException {
- super(repository);
- formsProvider = acquireContentProvider(context);
- regularHelper = new RepoUtils.QueryHelper(context, BrowserContractHelpers.FORM_HISTORY_CONTENT_URI, LOG_TAG);
- deletedHelper = new RepoUtils.QueryHelper(context, BrowserContractHelpers.DELETED_FORM_HISTORY_CONTENT_URI, LOG_TAG);
- }
-
- @Override
- public void abort() {
- releaseProviders();
- super.abort();
- }
-
- @Override
- public void finish(final RepositorySessionFinishDelegate delegate)
- throws InactiveSessionException {
- releaseProviders();
- super.finish(delegate);
- }
-
- protected static final String[] GUID_COLUMNS = new String[] { FormHistory.GUID };
-
- @Override
- public void guidsSince(final long timestamp, final RepositorySessionGuidsSinceDelegate delegate) {
- Runnable command = new Runnable() {
- @Override
- public void run() {
- if (!isActive()) {
- delegate.onGuidsSinceFailed(new InactiveSessionException(null));
- return;
- }
-
- ArrayList<String> guids = new ArrayList<String>();
-
- final long sharedEnd = now();
- Cursor cur = null;
- try {
- cur = regularHelper.safeQuery(formsProvider, "", GUID_COLUMNS, regularBetween(timestamp, sharedEnd), null, null);
- cur.moveToFirst();
- while (!cur.isAfterLast()) {
- guids.add(cur.getString(0));
- cur.moveToNext();
- }
- } catch (RemoteException | NullCursorException e) {
- delegate.onGuidsSinceFailed(e);
- return;
- } finally {
- if (cur != null) {
- cur.close();
- }
- }
-
- try {
- cur = deletedHelper.safeQuery(formsProvider, "", GUID_COLUMNS, deletedBetween(timestamp, sharedEnd), null, null);
- cur.moveToFirst();
- while (!cur.isAfterLast()) {
- guids.add(cur.getString(0));
- cur.moveToNext();
- }
- } catch (RemoteException | NullCursorException e) {
- delegate.onGuidsSinceFailed(e);
- return;
- } finally {
- if (cur != null) {
- cur.close();
- }
- }
-
- String guidsArray[] = guids.toArray(new String[guids.size()]);
- delegate.onGuidsSinceSucceeded(guidsArray);
- }
- };
- delegateQueue.execute(command);
- }
-
- protected static FormHistoryRecord retrieveDuringFetch(final Cursor cursor) {
- // A simple and efficient way to distinguish two tables.
- if (cursor.getColumnCount() == BrowserContractHelpers.FormHistoryColumns.length) {
- return formHistoryRecordFromCursor(cursor);
- } else {
- return deletedFormHistoryRecordFromCursor(cursor);
- }
- }
-
- protected static FormHistoryRecord formHistoryRecordFromCursor(final Cursor cursor) {
- String guid = RepoUtils.getStringFromCursor(cursor, FormHistory.GUID);
- String collection = "forms";
- FormHistoryRecord record = new FormHistoryRecord(guid, collection, 0, false);
-
- record.fieldName = RepoUtils.getStringFromCursor(cursor, FormHistory.FIELD_NAME);
- record.fieldValue = RepoUtils.getStringFromCursor(cursor, FormHistory.VALUE);
- record.androidID = RepoUtils.getLongFromCursor(cursor, FormHistory.ID);
- record.lastModified = RepoUtils.getLongFromCursor(cursor, FormHistory.FIRST_USED) / 1000; // Convert microseconds to milliseconds.
- record.deleted = false;
-
- record.log(LOG_TAG);
- return record;
- }
-
- protected static FormHistoryRecord deletedFormHistoryRecordFromCursor(final Cursor cursor) {
- String guid = RepoUtils.getStringFromCursor(cursor, DeletedFormHistory.GUID);
- String collection = "forms";
- FormHistoryRecord record = new FormHistoryRecord(guid, collection, 0, false);
-
- record.guid = RepoUtils.getStringFromCursor(cursor, DeletedFormHistory.GUID);
- record.androidID = RepoUtils.getLongFromCursor(cursor, DeletedFormHistory.ID);
- record.lastModified = RepoUtils.getLongFromCursor(cursor, DeletedFormHistory.TIME_DELETED);
- record.deleted = true;
-
- record.log(LOG_TAG);
- return record;
- }
-
- protected static void fetchFromCursor(final Cursor cursor, final RecordFilter filter, final RepositorySessionFetchRecordsDelegate delegate)
- throws NullCursorException {
- Logger.debug(LOG_TAG, "Fetch from cursor");
- if (cursor == null) {
- throw new NullCursorException(null);
- }
- try {
- if (!cursor.moveToFirst()) {
- return;
- }
- while (!cursor.isAfterLast()) {
- Record r = retrieveDuringFetch(cursor);
- if (r != null) {
- if (filter == null || !filter.excludeRecord(r)) {
- Logger.trace(LOG_TAG, "Processing record " + r.guid);
- delegate.onFetchedRecord(r);
- } else {
- Logger.debug(LOG_TAG, "Skipping filtered record " + r.guid);
- }
- }
- cursor.moveToNext();
- }
- } finally {
- Logger.trace(LOG_TAG, "Closing cursor after fetch.");
- cursor.close();
- }
- }
-
- protected void fetchHelper(final RepositorySessionFetchRecordsDelegate delegate, final long end, final List<Callable<Cursor>> cursorCallables) {
- if (this.storeTracker == null) {
- throw new IllegalStateException("Store tracker not yet initialized!");
- }
-
- final RecordFilter filter = this.storeTracker.getFilter();
-
- Runnable command = new Runnable() {
- @Override
- public void run() {
- if (!isActive()) {
- delegate.onFetchFailed(new InactiveSessionException(null), null);
- return;
- }
-
- for (Callable<Cursor> cursorCallable : cursorCallables) {
- Cursor cursor = null;
- try {
- cursor = cursorCallable.call();
- fetchFromCursor(cursor, filter, delegate); // Closes cursor.
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Exception during fetchHelper", e);
- delegate.onFetchFailed(e, null);
- return;
- }
- }
-
- delegate.onFetchCompleted(end);
- }
- };
-
- delegateQueue.execute(command);
- }
-
- protected static String regularBetween(long start, long end) {
- return FormHistory.FIRST_USED + " >= " + Long.toString(1000 * start) + " AND " +
- FormHistory.FIRST_USED + " <= " + Long.toString(1000 * end); // Microseconds.
- }
-
- protected static String deletedBetween(long start, long end) {
- return DeletedFormHistory.TIME_DELETED + " >= " + Long.toString(start) + " AND " +
- DeletedFormHistory.TIME_DELETED + " <= " + Long.toString(end); // Milliseconds.
- }
-
- @Override
- public void fetchSince(final long timestamp, final RepositorySessionFetchRecordsDelegate delegate) {
- Logger.trace(LOG_TAG, "Running fetchSince(" + timestamp + ").");
-
- /*
- * We need to be careful about the timestamp we complete the fetch with. If
- * the first cursor Callable takes a year, then the second could return
- * records long after the first was kicked off. To protect against this, we
- * set an end point and bound our search.
- */
- final long sharedEnd = now();
-
- Callable<Cursor> regularCallable = new Callable<Cursor>() {
- @Override
- public Cursor call() throws Exception {
- return regularHelper.safeQuery(formsProvider, ".fetchSince(regular)", null, regularBetween(timestamp, sharedEnd), null, null);
- }
- };
-
- Callable<Cursor> deletedCallable = new Callable<Cursor>() {
- @Override
- public Cursor call() throws Exception {
- return deletedHelper.safeQuery(formsProvider, ".fetchSince(deleted)", null, deletedBetween(timestamp, sharedEnd), null, null);
- }
- };
-
- @SuppressWarnings("unchecked")
- List<Callable<Cursor>> callableCursors = Arrays.asList(regularCallable, deletedCallable);
-
- fetchHelper(delegate, sharedEnd, callableCursors);
- }
-
- @Override
- public void fetchAll(RepositorySessionFetchRecordsDelegate delegate) {
- Logger.trace(LOG_TAG, "Running fetchAll.");
- fetchSince(0, delegate);
- }
-
- @Override
- public void fetch(final String[] guids, final RepositorySessionFetchRecordsDelegate delegate) {
- Logger.trace(LOG_TAG, "Running fetch.");
-
- final long sharedEnd = now();
- final String where = RepoUtils.computeSQLInClause(guids.length, FormHistory.GUID);
-
- Callable<Cursor> regularCallable = new Callable<Cursor>() {
- @Override
- public Cursor call() throws Exception {
- String regularWhere = where + " AND " + FormHistory.FIRST_USED + " <= " + Long.toString(1000 * sharedEnd); // Microseconds.
- return regularHelper.safeQuery(formsProvider, ".fetch(regular)", null, regularWhere, guids, null);
- }
- };
-
- Callable<Cursor> deletedCallable = new Callable<Cursor>() {
- @Override
- public Cursor call() throws Exception {
- String deletedWhere = where + " AND " + DeletedFormHistory.TIME_DELETED + " <= " + Long.toString(sharedEnd); // Milliseconds.
- return deletedHelper.safeQuery(formsProvider, ".fetch(deleted)", null, deletedWhere, guids, null);
- }
- };
-
- @SuppressWarnings("unchecked")
- List<Callable<Cursor>> callableCursors = Arrays.asList(regularCallable, deletedCallable);
-
- fetchHelper(delegate, sharedEnd, callableCursors);
- }
-
- protected static final String GUID_IS = FormHistory.GUID + " = ?";
-
- protected Record findExistingRecordByGuid(String guid)
- throws RemoteException, NullCursorException {
- Cursor cursor = null;
- try {
- cursor = regularHelper.safeQuery(formsProvider, ".findExistingRecordByGuid(regular)",
- null, GUID_IS, new String[] { guid }, null);
- if (cursor.moveToFirst()) {
- return formHistoryRecordFromCursor(cursor);
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
-
- try {
- cursor = deletedHelper.safeQuery(formsProvider, ".findExistingRecordByGuid(deleted)",
- null, GUID_IS, new String[] { guid }, null);
- if (cursor.moveToFirst()) {
- return deletedFormHistoryRecordFromCursor(cursor);
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
-
- return null;
- }
-
- protected Record findExistingRecordByPayload(Record rawRecord)
- throws RemoteException, NullCursorException {
- if (!rawRecord.deleted) {
- FormHistoryRecord record = (FormHistoryRecord) rawRecord;
- Cursor cursor = null;
- try {
- String where = FormHistory.FIELD_NAME + " = ? AND " + FormHistory.VALUE + " = ?";
- cursor = regularHelper.safeQuery(formsProvider, ".findExistingRecordByPayload",
- null, where, new String[] { record.fieldName, record.fieldValue }, null);
- if (cursor.moveToFirst()) {
- return formHistoryRecordFromCursor(cursor);
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
-
- return null;
- }
-
- /**
- * Called when a record with locally known GUID has been reported deleted by
- * the server.
- * <p>
- * We purge the record's GUID from the regular and deleted tables.
- *
- * @param existingRecord
- * The local <code>Record</code> to replace.
- * @throws RemoteException
- */
- protected void deleteExistingRecord(Record existingRecord) throws RemoteException {
- if (existingRecord.deleted) {
- formsProvider.delete(DELETED_FORM_HISTORY_CONTENT_URI, GUID_IS, new String[] { existingRecord.guid });
- return;
- }
- formsProvider.delete(FORM_HISTORY_CONTENT_URI, GUID_IS, new String[] { existingRecord.guid });
- }
-
- protected static ContentValues contentValuesForRegularRecord(Record rawRecord) {
- if (rawRecord.deleted) {
- throw new IllegalArgumentException("Deleted record passed to insertNewRegularRecord.");
- }
-
- FormHistoryRecord record = (FormHistoryRecord) rawRecord;
- ContentValues cv = new ContentValues();
- cv.put(FormHistory.GUID, record.guid);
- cv.put(FormHistory.FIELD_NAME, record.fieldName);
- cv.put(FormHistory.VALUE, record.fieldValue);
- cv.put(FormHistory.FIRST_USED, 1000 * record.lastModified); // Microseconds.
- return cv;
- }
-
- protected final Object recordsBufferMonitor = new Object();
- protected ArrayList<ContentValues> recordsBuffer = new ArrayList<ContentValues>();
-
- protected void enqueueRegularRecord(Record record) {
- synchronized (recordsBufferMonitor) {
- if (recordsBuffer.size() >= INSERT_ITEM_THRESHOLD) {
- // Insert the existing contents, then enqueue.
- try {
- flushInsertQueue();
- } catch (Exception e) {
- delegate.onRecordStoreFailed(e, record.guid);
- return;
- }
- }
- // Store the ContentValues, rather than the record.
- recordsBuffer.add(contentValuesForRegularRecord(record));
- }
- }
-
- // Should always be called from storeWorkQueue.
- protected void flushInsertQueue() throws RemoteException {
- synchronized (recordsBufferMonitor) {
- if (recordsBuffer.size() > 0) {
- final ContentValues[] outgoing = recordsBuffer.toArray(new ContentValues[recordsBuffer.size()]);
- recordsBuffer = new ArrayList<ContentValues>();
-
- if (outgoing == null || outgoing.length == 0) {
- Logger.debug(LOG_TAG, "No form history items to insert; returning immediately.");
- return;
- }
-
- long before = System.currentTimeMillis();
- formsProvider.bulkInsert(FORM_HISTORY_CONTENT_URI, outgoing);
- long after = System.currentTimeMillis();
- Logger.debug(LOG_TAG, "Inserted " + outgoing.length + " form history items in (" + (after - before) + " milliseconds).");
- }
- }
- }
-
- @Override
- public void storeDone() {
- Runnable command = new Runnable() {
- @Override
- public void run() {
- Logger.debug(LOG_TAG, "Checking for residual form history items to insert.");
- try {
- synchronized (recordsBufferMonitor) {
- flushInsertQueue();
- }
- storeDone(now());
- } catch (Exception e) {
- // XXX TODO
- delegate.onRecordStoreFailed(e, null);
- }
- }
- };
- storeWorkQueue.execute(command);
- }
-
- /**
- * Called when a regular record with locally unknown GUID has been fetched
- * from the server.
- * <p>
- * Since the record is regular, we insert it into the regular table.
- *
- * @param record The regular <code>Record</code> from the server.
- * @throws RemoteException
- */
- protected void insertNewRegularRecord(Record record)
- throws RemoteException {
- enqueueRegularRecord(record);
- }
-
- /**
- * Called when a regular record with has been fetched from the server and
- * should replace an existing record.
- * <p>
- * We delete the existing record entirely, and then insert the new record into
- * the regular table.
- *
- * @param toStore
- * The regular <code>Record</code> from the server.
- * @param existingRecord
- * The local <code>Record</code> to replace.
- * @throws RemoteException
- */
- protected void replaceExistingRecordWithRegularRecord(Record toStore, Record existingRecord)
- throws RemoteException {
- if (existingRecord.deleted) {
- // Need two database operations -- purge from deleted table, insert into regular table.
- deleteExistingRecord(existingRecord);
- insertNewRegularRecord(toStore);
- return;
- }
-
- final ContentValues cv = contentValuesForRegularRecord(toStore);
- int updated = formsProvider.update(FORM_HISTORY_CONTENT_URI, cv, GUID_IS, new String[] { existingRecord.guid });
- if (updated != 1) {
- Logger.warn(LOG_TAG, "Expected to update 1 record with guid " + existingRecord.guid + " but updated " + updated + " records.");
- }
- }
-
- @Override
- public void store(Record rawRecord) throws NoStoreDelegateException {
- if (delegate == null) {
- Logger.warn(LOG_TAG, "No store delegate.");
- throw new NoStoreDelegateException();
- }
- if (rawRecord == null) {
- Logger.error(LOG_TAG, "Record sent to store was null");
- throw new IllegalArgumentException("Null record passed to FormHistoryRepositorySession.store().");
- }
- if (!(rawRecord instanceof FormHistoryRecord)) {
- Logger.error(LOG_TAG, "Can't store anything but a FormHistoryRecord");
- throw new IllegalArgumentException("Non-FormHistoryRecord passed to FormHistoryRepositorySession.store().");
- }
- final FormHistoryRecord record = (FormHistoryRecord) rawRecord;
-
- Runnable command = new Runnable() {
- @Override
- public void run() {
- if (!isActive()) {
- Logger.warn(LOG_TAG, "FormHistoryRepositorySession is inactive. Store failing.");
- delegate.onRecordStoreFailed(new InactiveSessionException(null), record.guid);
- return;
- }
-
- // TODO: lift these into the session.
- // Temporary: this matches prior syncing semantics, in which only
- // the relationship between the local and remote record is considered.
- // In the future we'll track these two timestamps and use them to
- // determine which records have changed, and thus process incoming
- // records more efficiently.
- long lastLocalRetrieval = 0; // lastSyncTimestamp?
- long lastRemoteRetrieval = 0; // TODO: adjust for clock skew.
- boolean remotelyModified = record.lastModified > lastRemoteRetrieval;
-
- Record existingRecord;
- try {
- // GUID matching only: deleted records don't have a payload with which to search.
- existingRecord = findExistingRecordByGuid(record.guid);
- if (record.deleted) {
- if (existingRecord == null) {
- // We're done. Don't bother with a callback. That can change later
- // if we want it to.
- Logger.trace(LOG_TAG, "Incoming record " + record.guid + " is deleted, and no local version. Bye!");
- return;
- }
-
- if (existingRecord.deleted) {
- Logger.trace(LOG_TAG, "Local record already deleted. Purging local.");
- deleteExistingRecord(existingRecord);
- return;
- }
-
- // Which one wins?
- if (!remotelyModified) {
- Logger.trace(LOG_TAG, "Ignoring deleted record from the past.");
- return;
- }
-
- boolean locallyModified = existingRecord.lastModified > lastLocalRetrieval;
- if (!locallyModified) {
- Logger.trace(LOG_TAG, "Remote modified, local not. Deleting.");
- deleteExistingRecord(existingRecord);
- trackRecord(record);
- delegate.onRecordStoreSucceeded(record.guid);
- return;
- }
-
- Logger.trace(LOG_TAG, "Both local and remote records have been modified.");
- if (record.lastModified > existingRecord.lastModified) {
- Logger.trace(LOG_TAG, "Remote is newer, and deleted. Purging local.");
- deleteExistingRecord(existingRecord);
- trackRecord(record);
- delegate.onRecordStoreSucceeded(record.guid);
- return;
- }
-
- Logger.trace(LOG_TAG, "Remote is older, local is not deleted. Ignoring.");
- if (!locallyModified) {
- Logger.warn(LOG_TAG, "Inconsistency: old remote record is deleted, but local record not modified!");
- // Ensure that this is tracked for upload.
- }
- return;
- }
- // End deletion logic.
-
- // Now we're processing a non-deleted incoming record.
- if (existingRecord == null) {
- Logger.trace(LOG_TAG, "Looking up match for record " + record.guid);
- existingRecord = findExistingRecordByPayload(record);
- }
-
- if (existingRecord == null) {
- // The record is new.
- Logger.trace(LOG_TAG, "No match. Inserting.");
- insertNewRegularRecord(record);
- trackRecord(record);
- delegate.onRecordStoreSucceeded(record.guid);
- return;
- }
-
- // We found a local duplicate.
- Logger.trace(LOG_TAG, "Incoming record " + record.guid + " dupes to local record " + existingRecord.guid);
-
- if (!RepoUtils.stringsEqual(record.guid, existingRecord.guid)) {
- // We found a local record that does NOT have the same GUID -- keep the server's version.
- Logger.trace(LOG_TAG, "Remote guid different from local guid. Storing to keep remote guid.");
- replaceExistingRecordWithRegularRecord(record, existingRecord);
- trackRecord(record);
- delegate.onRecordStoreSucceeded(record.guid);
- return;
- }
-
- // We found a local record that does have the same GUID -- check modification times.
- boolean locallyModified = existingRecord.lastModified > lastLocalRetrieval;
- if (!locallyModified) {
- Logger.trace(LOG_TAG, "Remote modified, local not. Storing.");
- replaceExistingRecordWithRegularRecord(record, existingRecord);
- trackRecord(record);
- delegate.onRecordStoreSucceeded(record.guid);
- return;
- }
-
- Logger.trace(LOG_TAG, "Both local and remote records have been modified.");
- if (record.lastModified > existingRecord.lastModified) {
- Logger.trace(LOG_TAG, "Remote is newer, and not deleted. Storing.");
- replaceExistingRecordWithRegularRecord(record, existingRecord);
- trackRecord(record);
- delegate.onRecordStoreSucceeded(record.guid);
- return;
- }
-
- Logger.trace(LOG_TAG, "Remote is older, local is not deleted. Ignoring.");
- if (!locallyModified) {
- Logger.warn(LOG_TAG, "Inconsistency: old remote record is not deleted, but local record not modified!");
- }
- return;
- } catch (Exception e) {
- Logger.error(LOG_TAG, "Store failed for " + record.guid, e);
- delegate.onRecordStoreFailed(e, record.guid);
- return;
- }
- }
- };
-
- storeWorkQueue.execute(command);
- }
-
- /**
- * Purge all data from the underlying databases.
- */
- public static void purgeDatabases(ContentProviderClient formsProvider)
- throws RemoteException {
- formsProvider.delete(FORM_HISTORY_CONTENT_URI, null, null);
- formsProvider.delete(DELETED_FORM_HISTORY_CONTENT_URI, null, null);
- }
-
- @Override
- public void wipe(final RepositorySessionWipeDelegate delegate) {
- Runnable command = new Runnable() {
- @Override
- public void run() {
- if (!isActive()) {
- delegate.onWipeFailed(new InactiveSessionException(null));
- return;
- }
-
- try {
- Logger.debug(LOG_TAG, "Wiping form history and deleted form history...");
- purgeDatabases(formsProvider);
- Logger.debug(LOG_TAG, "Wiping form history and deleted form history... DONE");
- } catch (Exception e) {
- delegate.onWipeFailed(e);
- return;
- }
-
- delegate.onWipeSucceeded();
- }
- };
- storeWorkQueue.execute(command);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/PasswordsRepositorySession.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/PasswordsRepositorySession.java
deleted file mode 100644
index f7b7416df..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/PasswordsRepositorySession.java
+++ /dev/null
@@ -1,725 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.android;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserContract.DeletedColumns;
-import org.mozilla.gecko.db.BrowserContract.DeletedPasswords;
-import org.mozilla.gecko.db.BrowserContract.Passwords;
-import org.mozilla.gecko.sync.repositories.InactiveSessionException;
-import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.RecordFilter;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.StoreTrackingRepositorySession;
-import org.mozilla.gecko.sync.repositories.android.RepoUtils.QueryHelper;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionGuidsSinceDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
-import org.mozilla.gecko.sync.repositories.domain.PasswordRecord;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-import android.content.ContentProviderClient;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.RemoteException;
-
-public class PasswordsRepositorySession extends
- StoreTrackingRepositorySession {
-
- public static class PasswordsRepository extends Repository {
- @Override
- public void createSession(RepositorySessionCreationDelegate delegate,
- Context context) {
- PasswordsRepositorySession session = new PasswordsRepositorySession(PasswordsRepository.this, context);
- final RepositorySessionCreationDelegate deferredCreationDelegate = delegate.deferredCreationDelegate();
- deferredCreationDelegate.onSessionCreated(session);
- }
- }
-
- private static final String LOG_TAG = "PasswordsRepoSession";
- private static final String COLLECTION = "passwords";
-
- private final RepoUtils.QueryHelper passwordsHelper;
- private final RepoUtils.QueryHelper deletedPasswordsHelper;
- private final ContentProviderClient passwordsProvider;
-
- private final Context context;
-
- public PasswordsRepositorySession(Repository repository, Context context) {
- super(repository);
- this.context = context;
- this.passwordsHelper = new QueryHelper(context, BrowserContractHelpers.PASSWORDS_CONTENT_URI, LOG_TAG);
- this.deletedPasswordsHelper = new QueryHelper(context, BrowserContractHelpers.DELETED_PASSWORDS_CONTENT_URI, LOG_TAG);
- this.passwordsProvider = context.getContentResolver().acquireContentProviderClient(BrowserContract.PASSWORDS_AUTHORITY_URI);
- }
-
- private static final String[] GUID_COLS = new String[] { Passwords.GUID };
- private static final String[] DELETED_GUID_COLS = new String[] { DeletedColumns.GUID };
-
- private static final String WHERE_GUID_IS = Passwords.GUID + " = ?";
- private static final String WHERE_DELETED_GUID_IS = DeletedPasswords.GUID + " = ?";
-
- @Override
- public void guidsSince(final long timestamp, final RepositorySessionGuidsSinceDelegate delegate) {
- final Runnable guidsSinceRunnable = new Runnable() {
- @Override
- public void run() {
-
- if (!isActive()) {
- delegate.onGuidsSinceFailed(new InactiveSessionException(null));
- return;
- }
-
- // Checks succeeded, now get GUIDs.
- final List<String> guids = new ArrayList<String>();
- try {
- Logger.debug(LOG_TAG, "Fetching guidsSince from data table.");
- final Cursor data = passwordsHelper.safeQuery(passwordsProvider, ".getGUIDsSince", GUID_COLS, dateModifiedWhere(timestamp), null, null);
- try {
- if (data.moveToFirst()) {
- while (!data.isAfterLast()) {
- guids.add(RepoUtils.getStringFromCursor(data, Passwords.GUID));
- data.moveToNext();
- }
- }
- } finally {
- data.close();
- }
-
- // Fetch guids from deleted table.
- Logger.debug(LOG_TAG, "Fetching guidsSince from deleted table.");
- final Cursor deleted = deletedPasswordsHelper.safeQuery(passwordsProvider, ".getGUIDsSince", DELETED_GUID_COLS, dateModifiedWhereDeleted(timestamp), null, null);
- try {
- if (deleted.moveToFirst()) {
- while (!deleted.isAfterLast()) {
- guids.add(RepoUtils.getStringFromCursor(deleted, DeletedColumns.GUID));
- deleted.moveToNext();
- }
- }
- } finally {
- deleted.close();
- }
- } catch (Exception e) {
- Logger.error(LOG_TAG, "Exception in fetch.");
- delegate.onGuidsSinceFailed(e);
- return;
- }
- String[] guidStrings = new String[guids.size()];
- delegate.onGuidsSinceSucceeded(guids.toArray(guidStrings));
- }
- };
-
- delegateQueue.execute(guidsSinceRunnable);
- }
-
- @Override
- public void fetchSince(final long timestamp, final RepositorySessionFetchRecordsDelegate delegate) {
- final RecordFilter filter = this.storeTracker.getFilter();
- final Runnable fetchSinceRunnable = new Runnable() {
- @Override
- public void run() {
- if (!isActive()) {
- delegate.onFetchFailed(new InactiveSessionException(null), null);
- return;
- }
-
- final long end = now();
- try {
- // Fetch from data table.
- Cursor data = passwordsHelper.safeQuery(passwordsProvider, ".fetchSince",
- getAllColumns(),
- dateModifiedWhere(timestamp),
- null, null);
- if (!fetchAndCloseCursorDeleted(data, false, filter, delegate)) {
- return;
- }
-
- // Fetch from deleted table.
- Cursor deleted = deletedPasswordsHelper.safeQuery(passwordsProvider, ".fetchSince",
- getAllDeletedColumns(),
- dateModifiedWhereDeleted(timestamp),
- null, null);
- if (!fetchAndCloseCursorDeleted(deleted, true, filter, delegate)) {
- return;
- }
-
- // Success!
- try {
- delegate.onFetchCompleted(end);
- } catch (Exception e) {
- Logger.error(LOG_TAG, "Delegate fetch completed callback failed.", e);
- // Don't call failure callback.
- return;
- }
- } catch (Exception e) {
- Logger.error(LOG_TAG, "Exception in fetch.");
- delegate.onFetchFailed(e, null);
- }
- }
- };
-
- delegateQueue.execute(fetchSinceRunnable);
- }
-
- @Override
- public void fetch(final String[] guids, final RepositorySessionFetchRecordsDelegate delegate) {
- if (guids == null || guids.length < 1) {
- Logger.error(LOG_TAG, "No guids to be fetched.");
- final long end = now();
- delegateQueue.execute(new Runnable() {
- @Override
- public void run() {
- delegate.onFetchCompleted(end);
- }
- });
- return;
- }
-
- // Checks succeeded, now fetch.
- final RecordFilter filter = this.storeTracker.getFilter();
- final Runnable fetchRunnable = new Runnable() {
- @Override
- public void run() {
- if (!isActive()) {
- delegate.onFetchFailed(new InactiveSessionException(null), null);
- return;
- }
-
- final long end = now();
- final String where = RepoUtils.computeSQLInClause(guids.length, "guid");
- Logger.trace(LOG_TAG, "Fetch guids where: " + where);
-
- try {
- // Fetch records from data table.
- Cursor data = passwordsHelper.safeQuery(passwordsProvider, ".fetch",
- getAllColumns(),
- where, guids, null);
- if (!fetchAndCloseCursorDeleted(data, false, filter, delegate)) {
- return;
- }
-
- // Fetch records from deleted table.
- Cursor deleted = deletedPasswordsHelper.safeQuery(passwordsProvider, ".fetch",
- getAllDeletedColumns(),
- where, guids, null);
- if (!fetchAndCloseCursorDeleted(deleted, true, filter, delegate)) {
- return;
- }
-
- delegate.onFetchCompleted(end);
-
- } catch (Exception e) {
- Logger.error(LOG_TAG, "Exception in fetch.");
- delegate.onFetchFailed(e, null);
- }
- }
- };
-
- delegateQueue.execute(fetchRunnable);
- }
-
- @Override
- public void fetchAll(RepositorySessionFetchRecordsDelegate delegate) {
- fetchSince(0, delegate);
- }
-
- @Override
- public void store(final Record record) throws NoStoreDelegateException {
- if (delegate == null) {
- Logger.error(LOG_TAG, "No store delegate.");
- throw new NoStoreDelegateException();
- }
- if (record == null) {
- Logger.error(LOG_TAG, "Record sent to store was null.");
- throw new IllegalArgumentException("Null record passed to PasswordsRepositorySession.store().");
- }
- if (!(record instanceof PasswordRecord)) {
- Logger.error(LOG_TAG, "Can't store anything but a PasswordRecord.");
- throw new IllegalArgumentException("Non-PasswordRecord passed to PasswordsRepositorySession.store().");
- }
-
- final PasswordRecord remoteRecord = (PasswordRecord) record;
-
- final Runnable storeRunnable = new Runnable() {
- @Override
- public void run() {
- if (!isActive()) {
- Logger.warn(LOG_TAG, "RepositorySession is inactive. Store failing.");
- delegate.onRecordStoreFailed(new InactiveSessionException(null), record.guid);
- return;
- }
-
- final String guid = remoteRecord.guid;
- if (guid == null) {
- delegate.onRecordStoreFailed(new RuntimeException("Can't store record with null GUID."), record.guid);
- return;
- }
-
- PasswordRecord existingRecord;
- try {
- existingRecord = retrieveByGUID(guid);
- } catch (NullCursorException | RemoteException e) {
- // Indicates a serious problem.
- delegate.onRecordStoreFailed(e, record.guid);
- return;
- }
-
- long lastLocalRetrieval = 0; // lastSyncTimestamp?
- long lastRemoteRetrieval = 0; // TODO: adjust for clock skew.
- boolean remotelyModified = remoteRecord.lastModified > lastRemoteRetrieval;
-
- // Check deleted state first.
- if (remoteRecord.deleted) {
- if (existingRecord == null) {
- // Do nothing, record does not exist anyways.
- Logger.info(LOG_TAG, "Incoming record " + remoteRecord.guid + " is deleted, and no local version.");
- return;
- }
-
- if (existingRecord.deleted) {
- // Record is already tracked as deleted. Delete from local.
- storeRecordDeletion(existingRecord); // different from ABRepoSess.
- Logger.info(LOG_TAG, "Incoming record " + remoteRecord.guid + " and local are both deleted.");
- return;
- }
-
- // Which one wins?
- if (!remotelyModified) {
- trace("Ignoring deleted record from the past.");
- return;
- }
-
- boolean locallyModified = existingRecord.lastModified > lastLocalRetrieval;
- if (!locallyModified) {
- trace("Remote modified, local not. Deleting.");
- storeRecordDeletion(remoteRecord);
- return;
- }
-
- trace("Both local and remote records have been modified.");
- if (remoteRecord.lastModified > existingRecord.lastModified) {
- trace("Remote is newer, and deleted. Deleting local.");
- storeRecordDeletion(remoteRecord);
- return;
- }
-
- trace("Remote is older, local is not deleted. Ignoring.");
- if (!locallyModified) {
- Logger.warn(LOG_TAG, "Inconsistency: old remote record is deleted, but local record not modified!");
- // Ensure that this is tracked for upload.
- }
- return;
- }
- // End deletion logic.
-
- // Validate the incoming record.
- if (!remoteRecord.isValid()) {
- Logger.warn(LOG_TAG, "Incoming record is invalid. Reporting store failed.");
- delegate.onRecordStoreFailed(new RuntimeException("Can't store invalid password record."), record.guid);
- return;
- }
-
- // Now we're processing a non-deleted incoming record.
- if (existingRecord == null) {
- trace("Looking up match for record " + remoteRecord.guid);
- try {
- existingRecord = findExistingRecord(remoteRecord);
- } catch (RemoteException e) {
- Logger.error(LOG_TAG, "Remote exception in findExistingRecord.");
- delegate.onRecordStoreFailed(e, record.guid);
- } catch (NullCursorException e) {
- Logger.error(LOG_TAG, "Null cursor in findExistingRecord.");
- delegate.onRecordStoreFailed(e, record.guid);
- }
- }
-
- if (existingRecord == null) {
- // The record is new.
- trace("No match. Inserting.");
- Logger.debug(LOG_TAG, "Didn't find matching record. Inserting.");
- Record inserted = null;
- try {
- inserted = insert(remoteRecord);
- } catch (RemoteException e) {
- Logger.debug(LOG_TAG, "Record insert caused a RemoteException.");
- delegate.onRecordStoreFailed(e, record.guid);
- return;
- }
- trackRecord(inserted);
- delegate.onRecordStoreSucceeded(inserted.guid);
- return;
- }
-
- // We found a local dupe.
- trace("Incoming record " + remoteRecord.guid + " dupes to local record " + existingRecord.guid);
- Logger.debug(LOG_TAG, "remote " + remoteRecord.guid + " dupes to " + existingRecord.guid);
-
- if (existingRecord.deleted && existingRecord.lastModified > remoteRecord.lastModified) {
- Logger.debug(LOG_TAG, "Local deletion is newer, not storing remote record.");
- return;
- }
-
- Record toStore = reconcileRecords(remoteRecord, existingRecord, lastRemoteRetrieval, lastLocalRetrieval);
- if (toStore == null) {
- Logger.debug(LOG_TAG, "Reconciling returned null. Not inserting a record.");
- return;
- }
-
- // TODO: pass in timestamps?
- Logger.debug(LOG_TAG, "Replacing " + existingRecord.guid + " with record " + toStore.guid);
- Record replaced = null;
- try {
- replaced = replace(existingRecord, toStore);
- } catch (RemoteException e) {
- Logger.debug(LOG_TAG, "Record replace caused a RemoteException.");
- delegate.onRecordStoreFailed(e, record.guid);
- return;
- }
-
- // Note that we don't track records here; deciding that is the job
- // of reconcileRecords.
- Logger.debug(LOG_TAG, "Calling delegate callback with guid " + replaced.guid +
- "(" + replaced.androidID + ")");
- delegate.onRecordStoreSucceeded(record.guid);
- return;
- }
- };
- storeWorkQueue.execute(storeRunnable);
- }
-
- @Override
- public void wipe(final RepositorySessionWipeDelegate delegate) {
- Logger.info(LOG_TAG, "Wiping " + BrowserContractHelpers.PASSWORDS_CONTENT_URI + ", " + BrowserContractHelpers.DELETED_PASSWORDS_CONTENT_URI);
-
- Runnable wipeRunnable = new Runnable() {
- @Override
- public void run() {
- if (!isActive()) {
- delegate.onWipeFailed(new InactiveSessionException(null));
- return;
- }
-
- // Wipe both data and deleted.
- try {
- context.getContentResolver().delete(BrowserContractHelpers.PASSWORDS_CONTENT_URI, null, null);
- context.getContentResolver().delete(BrowserContractHelpers.DELETED_PASSWORDS_CONTENT_URI, null, null);
- } catch (Exception e) {
- delegate.onWipeFailed(e);
- return;
- }
- delegate.onWipeSucceeded();
- }
- };
- storeWorkQueue.execute(wipeRunnable);
- }
-
- @Override
- public void abort() {
- passwordsProvider.release();
- super.abort();
- }
-
- @Override
- public void finish(final RepositorySessionFinishDelegate delegate) throws InactiveSessionException {
- passwordsProvider.release();
- super.finish(delegate);
- }
-
- public void deleteGUID(String guid) throws RemoteException {
- final String[] args = new String[] { guid };
-
- int deleted = passwordsProvider.delete(BrowserContractHelpers.PASSWORDS_CONTENT_URI, WHERE_GUID_IS, args) +
- passwordsProvider.delete(BrowserContractHelpers.DELETED_PASSWORDS_CONTENT_URI, WHERE_DELETED_GUID_IS, args);
- if (deleted == 1) {
- return;
- }
- Logger.warn(LOG_TAG, "Unexpectedly deleted " + deleted + " rows for guid " + guid);
- }
-
- /**
- * Insert record and return the record with its updated androidId set.
- *
- * @param record the record to insert.
- * @return updated record.
- * @throws RemoteException
- */
- public PasswordRecord insert(PasswordRecord record) throws RemoteException {
- record.timePasswordChanged = now();
- // TODO: are these necessary for Fennec autocomplete?
- // record.timesUsed = 1;
- // record.timeLastUsed = now();
- ContentValues cv = getContentValues(record);
- Uri insertedUri = passwordsProvider.insert(BrowserContractHelpers.PASSWORDS_CONTENT_URI, cv);
- if (insertedUri == null) {
- throw new RemoteException(); // Not much to be done here, save throw.
- }
- record.androidID = ContentUris.parseId(insertedUri);
- return record;
- }
-
- public Record replace(Record origRecord, Record newRecord) throws RemoteException {
- PasswordRecord newPasswordRecord = (PasswordRecord) newRecord;
- PasswordRecord origPasswordRecord = (PasswordRecord) origRecord;
- propagateTimes(newPasswordRecord, origPasswordRecord);
- ContentValues cv = getContentValues(newPasswordRecord);
-
- final String[] args = new String[] { origRecord.guid };
-
- if (origRecord.deleted) {
- // Purge from deleted table.
- deleteGUID(origRecord.guid);
- insert(newPasswordRecord);
- } else {
- int updated = context.getContentResolver().update(BrowserContractHelpers.PASSWORDS_CONTENT_URI, cv, WHERE_GUID_IS, args);
- if (updated != 1) {
- Logger.warn(LOG_TAG, "Unexpectedly updated " + updated + " rows for guid " + origPasswordRecord.guid);
- }
- }
-
- return newRecord;
- }
-
- // When replacing a record, propagate the times.
- private static void propagateTimes(PasswordRecord toRecord, PasswordRecord fromRecord) {
- toRecord.timePasswordChanged = now();
- toRecord.timeCreated = fromRecord.timeCreated;
- toRecord.timeLastUsed = fromRecord.timeLastUsed;
- toRecord.timesUsed = fromRecord.timesUsed;
- }
-
- private static String[] getAllColumns() {
- return BrowserContractHelpers.PasswordColumns;
- }
-
- private static String[] getAllDeletedColumns() {
- return BrowserContractHelpers.DeletedColumns;
- }
-
- /**
- * Constructs the DB query string for entry age for deleted records.
- *
- * @param timestamp
- * @return String DB query string for dates to fetch.
- */
- private static String dateModifiedWhereDeleted(long timestamp) {
- return DeletedColumns.TIME_DELETED + " >= " + Long.toString(timestamp);
- }
-
- /**
- * Constructs the DB query string for entry age for (undeleted) records.
- *
- * @param timestamp
- * @return String DB query string for dates to fetch.
- */
- private static String dateModifiedWhere(long timestamp) {
- return Passwords.TIME_PASSWORD_CHANGED + " >= " + Long.toString(timestamp);
- }
-
-
- /**
- * Fetch from the cursor with the given parameters, invoking
- * delegate callbacks and closing the cursor.
- * Returns true on success, false if failure was signaled.
- *
- * @param cursor
- fetch* cursor.
- * @param deleted
- * true if using deleted table, false when using data table.
- * @param delegate
- * FetchRecordsDelegate to process records.
- */
- private static boolean fetchAndCloseCursorDeleted(final Cursor cursor,
- final boolean deleted,
- final RecordFilter filter,
- final RepositorySessionFetchRecordsDelegate delegate) {
- if (cursor == null) {
- return true;
- }
-
- try {
- while (cursor.moveToNext()) {
- Record r = deleted ? deletedPasswordRecordFromCursor(cursor) : passwordRecordFromCursor(cursor);
- if (r != null) {
- if (filter == null || !filter.excludeRecord(r)) {
- Logger.debug(LOG_TAG, "Processing record " + r.guid);
- delegate.onFetchedRecord(r);
- } else {
- Logger.debug(LOG_TAG, "Skipping filtered record " + r.guid);
- }
- }
- }
- } catch (Exception e) {
- Logger.error(LOG_TAG, "Exception in fetch.");
- delegate.onFetchFailed(e, null);
- return false;
- } finally {
- cursor.close();
- }
-
- return true;
- }
-
- private PasswordRecord retrieveByGUID(String guid) throws NullCursorException, RemoteException {
- final String[] guidArg = new String[] { guid };
-
- // Check data table.
- final Cursor data = passwordsHelper.safeQuery(passwordsProvider, ".store", BrowserContractHelpers.PasswordColumns, WHERE_GUID_IS, guidArg, null);
- try {
- if (data.moveToFirst()) {
- return passwordRecordFromCursor(data);
- }
- } finally {
- data.close();
- }
-
- // Check deleted table.
- final Cursor deleted = deletedPasswordsHelper.safeQuery(passwordsProvider, ".retrieveByGuid", BrowserContractHelpers.DeletedColumns, WHERE_DELETED_GUID_IS, guidArg, null);
- try {
- if (deleted.moveToFirst()) {
- return deletedPasswordRecordFromCursor(deleted);
- }
- } finally {
- deleted.close();
- }
-
- return null;
- }
-
- private static final String WHERE_RECORD_DATA =
- Passwords.HOSTNAME + " = ? AND " +
- Passwords.HTTP_REALM + " = ? AND " +
- Passwords.FORM_SUBMIT_URL + " = ? AND " +
- Passwords.USERNAME_FIELD + " = ? AND " +
- Passwords.PASSWORD_FIELD + " = ?";
-
- private PasswordRecord findExistingRecord(PasswordRecord record) throws NullCursorException, RemoteException {
- PasswordRecord foundRecord = null;
- Cursor cursor = null;
- // Only check the data table.
- // We can't encrypt username directly for query, so run a more general query and then filter.
- final String[] whereArgs = new String[] {
- record.hostname,
- record.httpRealm,
- record.formSubmitURL,
- record.usernameField,
- record.passwordField
- };
-
- try {
- cursor = passwordsHelper.safeQuery(passwordsProvider, ".findRecord", getAllColumns(), WHERE_RECORD_DATA, whereArgs, null);
- while (cursor.moveToNext()) {
- foundRecord = passwordRecordFromCursor(cursor);
-
- // We don't directly query for username because the
- // username/password values are encrypted in the db.
- // We don't have the keys for encrypting our query,
- // so we run a more general query and then filter
- // the returned records for a matching username.
- Logger.pii(LOG_TAG, "Checking incoming [" + record.encryptedUsername + "] to [" + foundRecord.encryptedUsername + "]");
- if (record.encryptedUsername.equals(foundRecord.encryptedUsername)) {
- Logger.trace(LOG_TAG, "Found matching record: " + foundRecord.guid);
- return foundRecord;
- }
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- Logger.debug(LOG_TAG, "No matching records, returning null.");
- return null;
- }
-
- private void storeRecordDeletion(Record record) {
- try {
- deleteGUID(record.guid);
- } catch (RemoteException e) {
- Logger.error(LOG_TAG, "RemoteException in password delete.");
- delegate.onRecordStoreFailed(e, record.guid);
- return;
- }
- delegate.onRecordStoreSucceeded(record.guid);
- }
-
- /**
- * Make a PasswordRecord from a Cursor.
- * @param cur
- * Cursor from query.
- * @param deleted
- * true if creating a deleted Record, false if otherwise.
- * @return
- * PasswordRecord populated from Cursor.
- */
- private static PasswordRecord passwordRecordFromCursor(Cursor cur) {
- if (cur.isAfterLast()) {
- return null;
- }
- String guid = RepoUtils.getStringFromCursor(cur, BrowserContract.Passwords.GUID);
- long lastModified = RepoUtils.getLongFromCursor(cur, BrowserContract.Passwords.TIME_PASSWORD_CHANGED);
-
- PasswordRecord rec = new PasswordRecord(guid, COLLECTION, lastModified, false);
- rec.id = RepoUtils.getStringFromCursor(cur, BrowserContract.Passwords.ID);
- rec.hostname = RepoUtils.getStringFromCursor(cur, BrowserContract.Passwords.HOSTNAME);
- rec.httpRealm = RepoUtils.getStringFromCursor(cur, BrowserContract.Passwords.HTTP_REALM);
- rec.formSubmitURL = RepoUtils.getStringFromCursor(cur, BrowserContract.Passwords.FORM_SUBMIT_URL);
- rec.usernameField = RepoUtils.getStringFromCursor(cur, BrowserContract.Passwords.USERNAME_FIELD);
- rec.passwordField = RepoUtils.getStringFromCursor(cur, BrowserContract.Passwords.PASSWORD_FIELD);
- rec.encType = RepoUtils.getStringFromCursor(cur, BrowserContract.Passwords.ENC_TYPE);
-
- // TODO decryption of username/password here (Bug 711636)
- rec.encryptedUsername = RepoUtils.getStringFromCursor(cur, BrowserContract.Passwords.ENCRYPTED_USERNAME);
- rec.encryptedPassword = RepoUtils.getStringFromCursor(cur, BrowserContract.Passwords.ENCRYPTED_PASSWORD);
-
- rec.timeCreated = RepoUtils.getLongFromCursor(cur, BrowserContract.Passwords.TIME_CREATED);
- rec.timeLastUsed = RepoUtils.getLongFromCursor(cur, BrowserContract.Passwords.TIME_LAST_USED);
- rec.timePasswordChanged = RepoUtils.getLongFromCursor(cur, BrowserContract.Passwords.TIME_PASSWORD_CHANGED);
- rec.timesUsed = RepoUtils.getLongFromCursor(cur, BrowserContract.Passwords.TIMES_USED);
- return rec;
- }
-
- private static PasswordRecord deletedPasswordRecordFromCursor(Cursor cur) {
- if (cur.isAfterLast()) {
- return null;
- }
- String guid = RepoUtils.getStringFromCursor(cur, DeletedColumns.GUID);
- long lastModified = RepoUtils.getLongFromCursor(cur, DeletedColumns.TIME_DELETED);
- PasswordRecord rec = new PasswordRecord(guid, COLLECTION, lastModified, true);
- rec.androidID = RepoUtils.getLongFromCursor(cur, DeletedColumns.ID);
- return rec;
- }
-
- private static ContentValues getContentValues(Record record) {
- PasswordRecord rec = (PasswordRecord) record;
-
- ContentValues cv = new ContentValues();
- cv.put(BrowserContract.Passwords.GUID, rec.guid);
- cv.put(BrowserContract.Passwords.HOSTNAME, rec.hostname);
- cv.put(BrowserContract.Passwords.HTTP_REALM, rec.httpRealm);
- cv.put(BrowserContract.Passwords.FORM_SUBMIT_URL, rec.formSubmitURL);
- cv.put(BrowserContract.Passwords.USERNAME_FIELD, rec.usernameField);
- cv.put(BrowserContract.Passwords.PASSWORD_FIELD, rec.passwordField);
-
- // TODO Do encryption of username/password here. Bug 711636
- cv.put(BrowserContract.Passwords.ENC_TYPE, rec.encType);
- cv.put(BrowserContract.Passwords.ENCRYPTED_USERNAME, rec.encryptedUsername);
- cv.put(BrowserContract.Passwords.ENCRYPTED_PASSWORD, rec.encryptedPassword);
-
- cv.put(BrowserContract.Passwords.TIME_CREATED, rec.timeCreated);
- cv.put(BrowserContract.Passwords.TIME_LAST_USED, rec.timeLastUsed);
- cv.put(BrowserContract.Passwords.TIME_PASSWORD_CHANGED, rec.timePasswordChanged);
- cv.put(BrowserContract.Passwords.TIMES_USED, rec.timesUsed);
- return cv;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/RepoUtils.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/RepoUtils.java
deleted file mode 100644
index 9c29953f8..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/RepoUtils.java
+++ /dev/null
@@ -1,290 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.android;
-
-import android.content.ContentProviderClient;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.net.Uri;
-import android.os.RemoteException;
-
-import org.json.simple.JSONArray;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.NonArrayJSONException;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
-import org.mozilla.gecko.sync.repositories.domain.HistoryRecord;
-
-import java.io.IOException;
-
-public class RepoUtils {
-
- private static final String LOG_TAG = "RepoUtils";
-
- /**
- * A helper class for monotonous SQL querying. Does timing and logging,
- * offers a utility to throw on a null cursor.
- *
- * @author rnewman
- *
- */
- public static class QueryHelper {
- private final Context context;
- private final Uri uri;
- private final String tag;
-
- public QueryHelper(Context context, Uri uri, String tag) {
- this.context = context;
- this.uri = uri;
- this.tag = tag;
- }
-
- // For ContentProvider queries.
- public Cursor safeQuery(String label, String[] projection,
- String selection, String[] selectionArgs, String sortOrder) throws NullCursorException {
- long queryStart = android.os.SystemClock.uptimeMillis();
- Cursor c = context.getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);
- return checkAndLogCursor(label, queryStart, c);
- }
-
- public Cursor safeQuery(String[] projection, String selection, String[] selectionArgs, String sortOrder) throws NullCursorException {
- return this.safeQuery(null, projection, selection, selectionArgs, sortOrder);
- }
-
- // For ContentProviderClient queries.
- public Cursor safeQuery(ContentProviderClient client, String label, String[] projection,
- String selection, String[] selectionArgs, String sortOrder) throws NullCursorException, RemoteException {
- long queryStart = android.os.SystemClock.uptimeMillis();
- Cursor c = client.query(uri, projection, selection, selectionArgs, sortOrder);
- return checkAndLogCursor(label, queryStart, c);
- }
-
- // For SQLiteOpenHelper queries.
- public Cursor safeQuery(SQLiteDatabase db, String label, String table, String[] columns,
- String selection, String[] selectionArgs,
- String groupBy, String having, String orderBy, String limit) throws NullCursorException {
- long queryStart = android.os.SystemClock.uptimeMillis();
- Cursor c = db.query(table, columns, selection, selectionArgs, groupBy, having, orderBy, limit);
- return checkAndLogCursor(label, queryStart, c);
- }
-
- public Cursor safeQuery(SQLiteDatabase db, String label, String table, String[] columns,
- String selection, String[] selectionArgs) throws NullCursorException {
- return safeQuery(db, label, table, columns, selection, selectionArgs, null, null, null, null);
- }
-
- private Cursor checkAndLogCursor(String label, long queryStart, Cursor c) throws NullCursorException {
- long queryEnd = android.os.SystemClock.uptimeMillis();
- String logLabel = (label == null) ? tag : (tag + label);
- RepoUtils.queryTimeLogger(logLabel, queryStart, queryEnd);
- return checkNullCursor(logLabel, c);
- }
-
- public Cursor checkNullCursor(String logLabel, Cursor cursor) throws NullCursorException {
- if (cursor == null) {
- Logger.error(tag, "Got null cursor exception in " + logLabel);
- throw new NullCursorException(null);
- }
- return cursor;
- }
- }
-
- /**
- * This method exists because the behavior of <code>cur.getString()</code> is undefined
- * when the value in the database is <code>NULL</code>.
- * This method will return <code>null</code> in that case.
- */
- public static String optStringFromCursor(final Cursor cur, final String colId) {
- final int col = cur.getColumnIndex(colId);
- if (cur.isNull(col)) {
- return null;
- }
- return cur.getString(col);
- }
-
- /**
- * The behavior of this method when the value in the database is <code>NULL</code> is
- * determined by the implementation of the {@link Cursor}.
- */
- public static String getStringFromCursor(final Cursor cur, final String colId) {
- // TODO: getColumnIndexOrThrow?
- // TODO: don't look up columns by name!
- return cur.getString(cur.getColumnIndex(colId));
- }
-
- public static long getLongFromCursor(Cursor cur, String colId) {
- return cur.getLong(cur.getColumnIndex(colId));
- }
-
- public static int getIntFromCursor(Cursor cur, String colId) {
- return cur.getInt(cur.getColumnIndex(colId));
- }
-
- public static JSONArray getJSONArrayFromCursor(Cursor cur, String colId) {
- String jsonArrayAsString = getStringFromCursor(cur, colId);
- if (jsonArrayAsString == null) {
- return new JSONArray();
- }
- try {
- return ExtendedJSONObject.parseJSONArray(getStringFromCursor(cur, colId));
- } catch (NonArrayJSONException e) {
- Logger.error(LOG_TAG, "JSON parsing error for " + colId, e);
- return null;
- } catch (IOException e) {
- Logger.error(LOG_TAG, "JSON parsing error for " + colId, e);
- return null;
- }
- }
-
- /**
- * Return true if the provided URI is non-empty and acceptable to Fennec
- * (i.e., not an undesirable scheme).
- *
- * This code is pilfered from Fennec, which pilfered from Places.
- */
- public static boolean isValidHistoryURI(String uri) {
- if (uri == null || uri.length() == 0) {
- return false;
- }
-
- // First, check the most common cases (HTTP, HTTPS) to avoid most of the work.
- if (uri.startsWith("http:") || uri.startsWith("https:")) {
- return true;
- }
-
- String scheme = Uri.parse(uri).getScheme();
- if (scheme == null) {
- return false;
- }
-
- // Now check for all bad things.
- if (scheme.equals("about") ||
- scheme.equals("imap") ||
- scheme.equals("news") ||
- scheme.equals("mailbox") ||
- scheme.equals("moz-anno") ||
- scheme.equals("view-source") ||
- scheme.equals("chrome") ||
- scheme.equals("resource") ||
- scheme.equals("data") ||
- scheme.equals("wyciwyg") ||
- scheme.equals("javascript")) {
- return false;
- }
-
- return true;
- }
-
- /**
- * Create a HistoryRecord object from a cursor row.
- *
- * @return a HistoryRecord, or null if this row would produce
- * an invalid record (e.g., with a null URI or no visits).
- */
- public static HistoryRecord historyFromMirrorCursor(Cursor cur) {
- final String guid = getStringFromCursor(cur, BrowserContract.SyncColumns.GUID);
- if (guid == null) {
- Logger.debug(LOG_TAG, "Skipping history record with null GUID.");
- return null;
- }
-
- final String historyURI = getStringFromCursor(cur, BrowserContract.History.URL);
- if (!isValidHistoryURI(historyURI)) {
- Logger.debug(LOG_TAG, "Skipping history record " + guid + " with unwanted/invalid URI " + historyURI);
- return null;
- }
-
- final long visitCount = getLongFromCursor(cur, BrowserContract.History.VISITS);
- if (visitCount <= 0) {
- Logger.debug(LOG_TAG, "Skipping history record " + guid + " with <= 0 visit count.");
- return null;
- }
-
- final String collection = "history";
- final long lastModified = getLongFromCursor(cur, BrowserContract.SyncColumns.DATE_MODIFIED);
- final boolean deleted = getLongFromCursor(cur, BrowserContract.SyncColumns.IS_DELETED) == 1;
-
- final HistoryRecord rec = new HistoryRecord(guid, collection, lastModified, deleted);
-
- rec.androidID = getLongFromCursor(cur, BrowserContract.History._ID);
- rec.fennecDateVisited = getLongFromCursor(cur, BrowserContract.History.DATE_LAST_VISITED);
- rec.fennecVisitCount = visitCount;
- rec.histURI = historyURI;
- rec.title = getStringFromCursor(cur, BrowserContract.History.TITLE);
-
- return logHistory(rec);
- }
-
- private static HistoryRecord logHistory(HistoryRecord rec) {
- try {
- Logger.debug(LOG_TAG, "Returning history record " + rec.guid + " (" + rec.androidID + ")");
- Logger.debug(LOG_TAG, "> Visited: " + rec.fennecDateVisited);
- Logger.debug(LOG_TAG, "> Visits: " + rec.fennecVisitCount);
- if (Logger.LOG_PERSONAL_INFORMATION) {
- Logger.pii(LOG_TAG, "> Title: " + rec.title);
- Logger.pii(LOG_TAG, "> URI: " + rec.histURI);
- }
- } catch (Exception e) {
- Logger.debug(LOG_TAG, "Exception logging history record " + rec, e);
- }
- return rec;
- }
-
- public static void logClient(ClientRecord rec) {
- if (Logger.shouldLogVerbose(LOG_TAG)) {
- Logger.trace(LOG_TAG, "Returning client record " + rec.guid + " (" + rec.androidID + ")");
- Logger.trace(LOG_TAG, "Client Name: " + rec.name);
- Logger.trace(LOG_TAG, "Client Type: " + rec.type);
- Logger.trace(LOG_TAG, "Last Modified: " + rec.lastModified);
- Logger.trace(LOG_TAG, "Deleted: " + rec.deleted);
- }
- }
-
- public static void queryTimeLogger(String methodCallingQuery, long queryStart, long queryEnd) {
- long elapsedTime = queryEnd - queryStart;
- Logger.debug(LOG_TAG, "Query timer: " + methodCallingQuery + " took " + elapsedTime + "ms.");
- }
-
- public static boolean stringsEqual(String a, String b) {
- // Check for nulls
- if (a == b) return true;
- if (a == null && b != null) return false;
- if (a != null && b == null) return false;
-
- return a.equals(b);
- }
-
- public static String computeSQLLongInClause(long[] items, String field) {
- final StringBuilder builder = new StringBuilder(field);
- builder.append(" IN (");
- int i = 0;
- for (; i < items.length - 1; ++i) {
- builder.append(items[i]);
- builder.append(", ");
- }
- if (i < items.length) {
- builder.append(items[i]);
- }
- builder.append(")");
- return builder.toString();
- }
-
- public static String computeSQLInClause(int items, String field) {
- final StringBuilder builder = new StringBuilder(field);
- builder.append(" IN (");
- int i = 0;
- for (; i < items - 1; ++i) {
- builder.append("?, ");
- }
- if (i < items) {
- builder.append("?");
- }
- builder.append(")");
- return builder.toString();
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/VisitsHelper.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/VisitsHelper.java
deleted file mode 100644
index 9ba784759..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/android/VisitsHelper.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.android;
-
-import android.content.ContentProviderClient;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.RemoteException;
-import android.support.annotation.NonNull;
-
-import org.json.simple.JSONArray;
-import org.json.simple.JSONObject;
-import org.mozilla.gecko.db.BrowserContract.Visits;
-
-/**
- * This class is used by History Sync code (see <code>AndroidBrowserHistoryDataAccessor</code> and <code>AndroidBrowserHistoryRepositorySession</code>,
- * and provides utility functions for working with history visits. Primarily we're either inserting visits
- * into local database based on data received from Sync, or we're preparing local visits for upload into Sync.
- */
-public class VisitsHelper {
- public static final boolean DEFAULT_IS_LOCAL_VALUE = false;
- public static final String SYNC_TYPE_KEY = "type";
- public static final String SYNC_DATE_KEY = "date";
-
- /**
- * Returns a list of ContentValues of visits ready for insertion for a provided History GUID.
- * Visits must have data and type. See <code>getVisitContentValues</code>.
- *
- * @param guid History GUID to use when inserting visit records
- * @param visits <code>JSONArray</code> list of (date, type) tuples for visits
- * @return visits ready for insertion
- */
- public static ContentValues[] getVisitsContentValues(@NonNull String guid, @NonNull JSONArray visits) {
- final ContentValues[] visitsToStore = new ContentValues[visits.size()];
- final int visitCount = visits.size();
-
- if (visitCount == 0) {
- return visitsToStore;
- }
-
- for (int i = 0; i < visitCount; i++) {
- visitsToStore[i] = getVisitContentValues(
- guid, (JSONObject) visits.get(i), DEFAULT_IS_LOCAL_VALUE);
- }
- return visitsToStore;
- }
-
- /**
- * Maps up to <code>limit</code> visits for a given history GUID to an array of JSONObjects with "date" and "type" keys
- *
- * @param contentClient <code>ContentProviderClient</code> to use for querying Visits table
- * @param guid History GUID for which to return visits
- * @param limit Will return at most this number of visits
- * @return <code>JSONArray</code> of all visits found for given History GUID
- */
- public static JSONArray getRecentHistoryVisitsForGUID(@NonNull ContentProviderClient contentClient,
- @NonNull String guid, int limit) throws RemoteException {
- final JSONArray visits = new JSONArray();
-
- final Cursor cursor = contentClient.query(
- visitsUriWithLimit(limit),
- new String[] {Visits.VISIT_TYPE, Visits.DATE_VISITED},
- Visits.HISTORY_GUID + " = ?",
- new String[] {guid}, null);
- if (cursor == null) {
- return visits;
- }
- try {
- if (!cursor.moveToFirst()) {
- return visits;
- }
-
- final int dateVisitedCol = cursor.getColumnIndexOrThrow(Visits.DATE_VISITED);
- final int visitTypeCol = cursor.getColumnIndexOrThrow(Visits.VISIT_TYPE);
-
- while (!cursor.isAfterLast()) {
- insertTupleIntoVisitsUnchecked(visits,
- cursor.getLong(visitTypeCol),
- cursor.getLong(dateVisitedCol)
- );
- cursor.moveToNext();
- }
- } finally {
- cursor.close();
- }
-
- return visits;
- }
-
- /**
- * Constructs <code>ContentValues</code> object for a visit based on passed in parameters.
- *
- * @param visit <code>JSONObject</code> containing visit type and visit date keys for the visit
- * @param guid History GUID with with to associate this visit
- * @param isLocal Whether or not to mark this visit as local
- * @return <code>ContentValues</code> with all visit values necessary for database insertion
- * @throws IllegalArgumentException if visit object is missing date or type keys
- */
- public static ContentValues getVisitContentValues(@NonNull String guid, @NonNull JSONObject visit, boolean isLocal) {
- if (!visit.containsKey(SYNC_DATE_KEY) || !visit.containsKey(SYNC_TYPE_KEY)) {
- throw new IllegalArgumentException("Visit missing required keys");
- }
-
- final ContentValues cv = new ContentValues();
- cv.put(Visits.HISTORY_GUID, guid);
- cv.put(Visits.IS_LOCAL, isLocal ? Visits.VISIT_IS_LOCAL : Visits.VISIT_IS_REMOTE);
- cv.put(Visits.VISIT_TYPE, (Long) visit.get(SYNC_TYPE_KEY));
- cv.put(Visits.DATE_VISITED, (Long) visit.get(SYNC_DATE_KEY));
-
- return cv;
- }
-
- @SuppressWarnings("unchecked")
- private static void insertTupleIntoVisitsUnchecked(@NonNull final JSONArray visits, @NonNull Long type, @NonNull Long date) {
- final JSONObject visit = new JSONObject();
- visit.put(SYNC_TYPE_KEY, type);
- visit.put(SYNC_DATE_KEY, date);
- visits.add(visit);
- }
-
- private static Uri visitsUriWithLimit(int limit) {
- return BrowserContractHelpers.VISITS_CONTENT_URI
- .buildUpon()
- .appendQueryParameter("limit", Integer.toString(limit))
- .build();
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferrableRepositorySessionCreationDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferrableRepositorySessionCreationDelegate.java
deleted file mode 100644
index f292600e4..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferrableRepositorySessionCreationDelegate.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.delegates;
-
-import org.mozilla.gecko.sync.ThreadPool;
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-
-public abstract class DeferrableRepositorySessionCreationDelegate implements RepositorySessionCreationDelegate {
- @Override
- public RepositorySessionCreationDelegate deferredCreationDelegate() {
- final RepositorySessionCreationDelegate self = this;
- return new RepositorySessionCreationDelegate() {
-
- // TODO: rewrite to use ExecutorService.
- @Override
- public void onSessionCreated(final RepositorySession session) {
- ThreadPool.run(new Runnable() {
- @Override
- public void run() {
- self.onSessionCreated(session);
- }});
- }
-
- @Override
- public void onSessionCreateFailed(final Exception ex) {
- ThreadPool.run(new Runnable() {
- @Override
- public void run() {
- self.onSessionCreateFailed(ex);
- }});
- }
-
- @Override
- public RepositorySessionCreationDelegate deferredCreationDelegate() {
- return this;
- }
- };
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionBeginDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionBeginDelegate.java
deleted file mode 100644
index 1ccdcce19..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionBeginDelegate.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.delegates;
-
-import java.util.concurrent.ExecutorService;
-
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-
-public class DeferredRepositorySessionBeginDelegate implements RepositorySessionBeginDelegate {
- private final RepositorySessionBeginDelegate inner;
- private final ExecutorService executor;
- public DeferredRepositorySessionBeginDelegate(final RepositorySessionBeginDelegate inner, final ExecutorService executor) {
- this.inner = inner;
- this.executor = executor;
- }
-
- @Override
- public void onBeginSucceeded(final RepositorySession session) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- inner.onBeginSucceeded(session);
- }
- });
- }
-
- @Override
- public void onBeginFailed(final Exception ex) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- inner.onBeginFailed(ex);
- }
- });
- }
-
- @Override
- public RepositorySessionBeginDelegate deferredBeginDelegate(ExecutorService newExecutor) {
- if (newExecutor == executor) {
- return this;
- }
- throw new IllegalArgumentException("Can't re-defer this delegate.");
- }
-} \ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionFetchRecordsDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionFetchRecordsDelegate.java
deleted file mode 100644
index 1178d9b5b..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionFetchRecordsDelegate.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.delegates;
-
-import java.util.concurrent.ExecutorService;
-
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-public class DeferredRepositorySessionFetchRecordsDelegate implements RepositorySessionFetchRecordsDelegate {
- private final RepositorySessionFetchRecordsDelegate inner;
- private final ExecutorService executor;
- public DeferredRepositorySessionFetchRecordsDelegate(final RepositorySessionFetchRecordsDelegate inner, final ExecutorService executor) {
- this.inner = inner;
- this.executor = executor;
- }
-
- @Override
- public void onFetchedRecord(final Record record) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- inner.onFetchedRecord(record);
- }
- });
- }
-
- @Override
- public void onFetchFailed(final Exception ex, final Record record) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- inner.onFetchFailed(ex, record);
- }
- });
- }
-
- @Override
- public void onFetchCompleted(final long fetchEnd) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- inner.onFetchCompleted(fetchEnd);
- }
- });
- }
-
- @Override
- public RepositorySessionFetchRecordsDelegate deferredFetchDelegate(ExecutorService newExecutor) {
- if (newExecutor == executor) {
- return this;
- }
- throw new IllegalArgumentException("Can't re-defer this delegate.");
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionFinishDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionFinishDelegate.java
deleted file mode 100644
index dbe7e4327..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionFinishDelegate.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.delegates;
-
-import java.util.concurrent.ExecutorService;
-
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
-
-public class DeferredRepositorySessionFinishDelegate implements
- RepositorySessionFinishDelegate {
- protected final ExecutorService executor;
- protected final RepositorySessionFinishDelegate inner;
-
- public DeferredRepositorySessionFinishDelegate(RepositorySessionFinishDelegate inner,
- ExecutorService executor) {
- this.executor = executor;
- this.inner = inner;
- }
-
- @Override
- public void onFinishSucceeded(final RepositorySession session,
- final RepositorySessionBundle bundle) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- inner.onFinishSucceeded(session, bundle);
- }
- });
- }
-
- @Override
- public void onFinishFailed(final Exception ex) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- inner.onFinishFailed(ex);
- }
- });
- }
-
- @Override
- public RepositorySessionFinishDelegate deferredFinishDelegate(ExecutorService newExecutor) {
- if (newExecutor == executor) {
- return this;
- }
- throw new IllegalArgumentException("Can't re-defer this delegate.");
- }
-} \ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionStoreDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionStoreDelegate.java
deleted file mode 100644
index 2f659c733..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/DeferredRepositorySessionStoreDelegate.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.delegates;
-
-import java.util.concurrent.ExecutorService;
-
-public class DeferredRepositorySessionStoreDelegate implements
- RepositorySessionStoreDelegate {
- protected final RepositorySessionStoreDelegate inner;
- protected final ExecutorService executor;
-
- public DeferredRepositorySessionStoreDelegate(
- RepositorySessionStoreDelegate inner, ExecutorService executor) {
- this.inner = inner;
- this.executor = executor;
- }
-
- @Override
- public void onRecordStoreSucceeded(final String guid) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- inner.onRecordStoreSucceeded(guid);
- }
- });
- }
-
- @Override
- public void onRecordStoreFailed(final Exception ex, final String guid) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- inner.onRecordStoreFailed(ex, guid);
- }
- });
- }
-
- @Override
- public RepositorySessionStoreDelegate deferredStoreDelegate(ExecutorService newExecutor) {
- if (newExecutor == executor) {
- return this;
- }
- throw new IllegalArgumentException("Can't re-defer this delegate.");
- }
-
- @Override
- public void onStoreCompleted(final long storeEnd) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- inner.onStoreCompleted(storeEnd);
- }
- });
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionBeginDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionBeginDelegate.java
deleted file mode 100644
index f5853647f..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionBeginDelegate.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.delegates;
-
-import java.util.concurrent.ExecutorService;
-
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-
-/**
- * One of these two methods is guaranteed to be called after session.begin() is
- * invoked (possibly during the invocation). The callback will be invoked prior
- * to any other RepositorySession callbacks.
- *
- * @author rnewman
- *
- */
-public interface RepositorySessionBeginDelegate {
- public void onBeginFailed(Exception ex);
- public void onBeginSucceeded(RepositorySession session);
- public RepositorySessionBeginDelegate deferredBeginDelegate(ExecutorService executor);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionCleanDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionCleanDelegate.java
deleted file mode 100644
index 139c561a0..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionCleanDelegate.java
+++ /dev/null
@@ -1,12 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.delegates;
-
-import org.mozilla.gecko.sync.repositories.Repository;
-
-public interface RepositorySessionCleanDelegate {
- public void onCleaned(Repository repo);
- public void onCleanFailed(Repository repo, Exception ex);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionCreationDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionCreationDelegate.java
deleted file mode 100644
index 6ad4991c3..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionCreationDelegate.java
+++ /dev/null
@@ -1,15 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.delegates;
-
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-
-// Used to provide the sessionCallback and storeCallback
-// mechanism to repository instances.
-public interface RepositorySessionCreationDelegate {
- public void onSessionCreateFailed(Exception ex);
- public void onSessionCreated(RepositorySession session);
- public RepositorySessionCreationDelegate deferredCreationDelegate();
-} \ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionFetchRecordsDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionFetchRecordsDelegate.java
deleted file mode 100644
index 589a093dc..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionFetchRecordsDelegate.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.delegates;
-
-import java.util.concurrent.ExecutorService;
-
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-public interface RepositorySessionFetchRecordsDelegate {
- public void onFetchFailed(Exception ex, Record record);
- public void onFetchedRecord(Record record);
-
- /**
- * Called when all records in this fetch have been returned.
- *
- * @param fetchEnd
- * A millisecond-resolution timestamp indicating the *remote* timestamp
- * at the end of the range of records. Usually this is the timestamp at
- * which the request was received.
- * E.g., the (normalized) value of the X-Weave-Timestamp header.
- */
- public void onFetchCompleted(final long fetchEnd);
-
- public RepositorySessionFetchRecordsDelegate deferredFetchDelegate(ExecutorService executor);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionFinishDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionFinishDelegate.java
deleted file mode 100644
index 40296dd4f..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionFinishDelegate.java
+++ /dev/null
@@ -1,16 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.delegates;
-
-import java.util.concurrent.ExecutorService;
-
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
-
-public interface RepositorySessionFinishDelegate {
- public void onFinishFailed(Exception ex);
- public void onFinishSucceeded(RepositorySession session, RepositorySessionBundle bundle);
- public RepositorySessionFinishDelegate deferredFinishDelegate(ExecutorService executor);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionGuidsSinceDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionGuidsSinceDelegate.java
deleted file mode 100644
index 4f82768f1..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionGuidsSinceDelegate.java
+++ /dev/null
@@ -1,10 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.delegates;
-
-public interface RepositorySessionGuidsSinceDelegate {
- public void onGuidsSinceFailed(Exception ex);
- public void onGuidsSinceSucceeded(String[] guids);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionStoreDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionStoreDelegate.java
deleted file mode 100644
index 01e44c3ae..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionStoreDelegate.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.delegates;
-
-import java.util.concurrent.ExecutorService;
-
-/**
- * These methods *must* be invoked asynchronously. Use deferredStoreDelegate if you
- * need help doing this.
- *
- * @author rnewman
- *
- */
-public interface RepositorySessionStoreDelegate {
- public void onRecordStoreFailed(Exception ex, String recordGuid);
-
- // Called with a GUID when store has succeeded.
- public void onRecordStoreSucceeded(String guid);
- public void onStoreCompleted(long storeEnd);
- public RepositorySessionStoreDelegate deferredStoreDelegate(ExecutorService executor);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionWipeDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionWipeDelegate.java
deleted file mode 100644
index cc8830729..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/delegates/RepositorySessionWipeDelegate.java
+++ /dev/null
@@ -1,13 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.delegates;
-
-import java.util.concurrent.ExecutorService;
-
-public interface RepositorySessionWipeDelegate {
- public void onWipeFailed(Exception ex);
- public void onWipeSucceeded();
- public RepositorySessionWipeDelegate deferredWipeDelegate(ExecutorService executor);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/BookmarkRecord.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/BookmarkRecord.java
deleted file mode 100644
index 27b8e7151..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/BookmarkRecord.java
+++ /dev/null
@@ -1,488 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.domain;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.util.Map;
-
-import org.json.simple.JSONArray;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.NonArrayJSONException;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.repositories.android.RepoUtils;
-
-/**
- * Covers the fields used by all bookmark objects.
- * @author rnewman
- *
- */
-public class BookmarkRecord extends Record {
- public static final String PLACES_URI_PREFIX = "places:";
-
- private static final String LOG_TAG = "BookmarkRecord";
-
- public static final String COLLECTION_NAME = "bookmarks";
- public static final long BOOKMARKS_TTL = -1; // Never ttl bookmarks.
-
- public BookmarkRecord(String guid, String collection, long lastModified, boolean deleted) {
- super(guid, collection, lastModified, deleted);
- this.ttl = BOOKMARKS_TTL;
- }
- public BookmarkRecord(String guid, String collection, long lastModified) {
- this(guid, collection, lastModified, false);
- }
- public BookmarkRecord(String guid, String collection) {
- this(guid, collection, 0, false);
- }
- public BookmarkRecord(String guid) {
- this(guid, COLLECTION_NAME, 0, false);
- }
- public BookmarkRecord() {
- this(Utils.generateGuid(), COLLECTION_NAME, 0, false);
- }
-
- // Note: redundant accessors are evil. We're all grownups; let's just use
- // public fields.
- public String title;
- public String bookmarkURI;
- public String description;
- public String keyword;
- public String parentID;
- public String parentName;
- public long androidParentID;
- public String type;
- public long androidPosition;
-
- public JSONArray children;
- public JSONArray tags;
-
- @Override
- public String toString() {
- return "#<Bookmark " + guid + " (" + androidID + "), parent " +
- parentID + "/" + androidParentID + "/" + parentName + ">";
- }
-
- // Oh God, this is terribly thread-unsafe. These record objects should be immutable.
- @SuppressWarnings("unchecked")
- protected JSONArray copyChildren() {
- if (this.children == null) {
- return null;
- }
- JSONArray children = new JSONArray();
- children.addAll(this.children);
- return children;
- }
-
- @SuppressWarnings("unchecked")
- protected JSONArray copyTags() {
- if (this.tags == null) {
- return null;
- }
- JSONArray tags = new JSONArray();
- tags.addAll(this.tags);
- return tags;
- }
-
- @Override
- public Record copyWithIDs(String guid, long androidID) {
- BookmarkRecord out = new BookmarkRecord(guid, this.collection, this.lastModified, this.deleted);
- out.androidID = androidID;
- out.sortIndex = this.sortIndex;
- out.ttl = this.ttl;
-
- // Copy BookmarkRecord fields.
- out.title = this.title;
- out.bookmarkURI = this.bookmarkURI;
- out.description = this.description;
- out.keyword = this.keyword;
- out.parentID = this.parentID;
- out.parentName = this.parentName;
- out.androidParentID = this.androidParentID;
- out.type = this.type;
- out.androidPosition = this.androidPosition;
-
- out.children = this.copyChildren();
- out.tags = this.copyTags();
-
- return out;
- }
-
- public boolean isBookmark() {
- if (type == null) {
- return false;
- }
- return type.equals("bookmark");
- }
-
- public boolean isFolder() {
- if (type == null) {
- return false;
- }
- return type.equals("folder");
- }
-
- public boolean isLivemark() {
- if (type == null) {
- return false;
- }
- return type.equals("livemark");
- }
-
- public boolean isSeparator() {
- if (type == null) {
- return false;
- }
- return type.equals("separator");
- }
-
- public boolean isMicrosummary() {
- if (type == null) {
- return false;
- }
- return type.equals("microsummary");
- }
-
- public boolean isQuery() {
- if (type == null) {
- return false;
- }
- return type.equals("query");
- }
-
- /**
- * Return true if this record should have the Sync fields
- * of a bookmark, microsummary, or query.
- */
- private boolean isBookmarkIsh() {
- if (type == null) {
- return false;
- }
- return type.equals("bookmark") ||
- type.equals("microsummary") ||
- type.equals("query");
- }
-
- @Override
- protected void initFromPayload(ExtendedJSONObject payload) {
- this.type = payload.getString("type");
- this.title = payload.getString("title");
- this.description = payload.getString("description");
- this.parentID = payload.getString("parentid");
- this.parentName = payload.getString("parentName");
-
- if (isFolder()) {
- try {
- this.children = payload.getArray("children");
- } catch (NonArrayJSONException e) {
- Logger.error(LOG_TAG, "Got non-array children in bookmark record " + this.guid, e);
- // Let's see if we can recover later by using the parentid pointers.
- this.children = new JSONArray();
- }
- return;
- }
-
- final String bmkUri = payload.getString("bmkUri");
-
- // bookmark, microsummary, query.
- if (isBookmarkIsh()) {
- this.keyword = payload.getString("keyword");
- try {
- this.tags = payload.getArray("tags");
- } catch (NonArrayJSONException e) {
- Logger.warn(LOG_TAG, "Got non-array tags in bookmark record " + this.guid, e);
- this.tags = new JSONArray();
- }
- }
-
- if (isBookmark()) {
- this.bookmarkURI = bmkUri;
- return;
- }
-
- if (isLivemark()) {
- String siteUri = payload.getString("siteUri");
- String feedUri = payload.getString("feedUri");
- this.bookmarkURI = encodeUnsupportedTypeURI(bmkUri,
- "siteUri", siteUri,
- "feedUri", feedUri);
- return;
- }
- if (isQuery()) {
- String queryId = payload.getString("queryId");
- String folderName = payload.getString("folderName");
- this.bookmarkURI = encodeUnsupportedTypeURI(bmkUri,
- "queryId", queryId,
- "folderName", folderName);
- return;
- }
- if (isMicrosummary()) {
- String generatorUri = payload.getString("generatorUri");
- String staticTitle = payload.getString("staticTitle");
- this.bookmarkURI = encodeUnsupportedTypeURI(bmkUri,
- "generatorUri", generatorUri,
- "staticTitle", staticTitle);
- return;
- }
- if (isSeparator()) {
- Object p = payload.get("pos");
- if (p instanceof Long) {
- this.androidPosition = (Long) p;
- } else if (p instanceof String) {
- try {
- this.androidPosition = Long.parseLong((String) p, 10);
- } catch (NumberFormatException e) {
- return;
- }
- } else {
- Logger.warn(LOG_TAG, "Unsupported position value " + p);
- return;
- }
- String pos = String.valueOf(this.androidPosition);
- this.bookmarkURI = encodeUnsupportedTypeURI(null, "pos", pos, null, null);
- return;
- }
- }
-
- @Override
- protected void populatePayload(ExtendedJSONObject payload) {
- putPayload(payload, "type", this.type);
- putPayload(payload, "title", this.title);
- putPayload(payload, "description", this.description);
- putPayload(payload, "parentid", this.parentID);
- putPayload(payload, "parentName", this.parentName);
- putPayload(payload, "keyword", this.keyword);
-
- if (isFolder()) {
- payload.put("children", this.children);
- return;
- }
-
- // bookmark, microsummary, query.
- if (isBookmarkIsh()) {
- if (isBookmark()) {
- payload.put("bmkUri", bookmarkURI);
- }
-
- if (isQuery()) {
- Map<String, String> parts = Utils.extractURIComponents(PLACES_URI_PREFIX, this.bookmarkURI);
- putPayload(payload, "queryId", parts.get("queryId"), true);
- putPayload(payload, "folderName", parts.get("folderName"), true);
- putPayload(payload, "bmkUri", parts.get("uri"));
- return;
- }
-
- if (this.tags != null) {
- payload.put("tags", this.tags);
- }
-
- putPayload(payload, "keyword", this.keyword);
- return;
- }
-
- if (isLivemark()) {
- Map<String, String> parts = Utils.extractURIComponents(PLACES_URI_PREFIX, this.bookmarkURI);
- putPayload(payload, "siteUri", parts.get("siteUri"));
- putPayload(payload, "feedUri", parts.get("feedUri"));
- return;
- }
- if (isMicrosummary()) {
- Map<String, String> parts = Utils.extractURIComponents(PLACES_URI_PREFIX, this.bookmarkURI);
- putPayload(payload, "generatorUri", parts.get("generatorUri"));
- putPayload(payload, "staticTitle", parts.get("staticTitle"));
- return;
- }
- if (isSeparator()) {
- Map<String, String> parts = Utils.extractURIComponents(PLACES_URI_PREFIX, this.bookmarkURI);
- String pos = parts.get("pos");
- if (pos == null) {
- return;
- }
- try {
- payload.put("pos", Long.parseLong(pos, 10));
- } catch (NumberFormatException e) {
- return;
- }
- return;
- }
- }
-
- private void trace(String s) {
- Logger.trace(LOG_TAG, s);
- }
-
- @Override
- public boolean equalPayloads(Object o) {
- trace("Calling BookmarkRecord.equalPayloads.");
- if (!(o instanceof BookmarkRecord)) {
- return false;
- }
-
- BookmarkRecord other = (BookmarkRecord) o;
- if (!super.equalPayloads(other)) {
- return false;
- }
-
- if (!RepoUtils.stringsEqual(this.type, other.type)) {
- return false;
- }
-
- // Check children.
- if (isFolder() && (this.children != other.children)) {
- trace("BookmarkRecord.equals: this folder: " + this.title + ", " + this.guid);
- trace("BookmarkRecord.equals: other: " + other.title + ", " + other.guid);
- if (this.children == null &&
- other.children != null) {
- trace("Records differ: one children array is null.");
- return false;
- }
- if (this.children != null &&
- other.children == null) {
- trace("Records differ: one children array is null.");
- return false;
- }
- if (this.children.size() != other.children.size()) {
- trace("Records differ: children arrays differ in size (" +
- this.children.size() + " vs. " + other.children.size() + ").");
- return false;
- }
-
- for (int i = 0; i < this.children.size(); i++) {
- String child = (String) this.children.get(i);
- if (!other.children.contains(child)) {
- trace("Records differ: child " + child + " not found.");
- return false;
- }
- }
- }
-
- trace("Checking strings.");
- return RepoUtils.stringsEqual(this.title, other.title)
- && RepoUtils.stringsEqual(this.bookmarkURI, other.bookmarkURI)
- && RepoUtils.stringsEqual(this.parentID, other.parentID)
- && RepoUtils.stringsEqual(this.parentName, other.parentName)
- && RepoUtils.stringsEqual(this.description, other.description)
- && RepoUtils.stringsEqual(this.keyword, other.keyword)
- && jsonArrayStringsEqual(this.tags, other.tags);
- }
-
- // TODO: two records can be congruent if their child lists are different.
- @Override
- public boolean congruentWith(Object o) {
- return this.equalPayloads(o) &&
- super.congruentWith(o);
- }
-
- // Converts two JSONArrays to strings and checks if they are the same.
- // This is only useful for stuff like tags where we aren't actually
- // touching the data there (and therefore ordering won't change)
- private boolean jsonArrayStringsEqual(JSONArray a, JSONArray b) {
- // Check for nulls
- if (a == b) return true;
- if (a == null && b != null) return false;
- if (a != null && b == null) return false;
- return RepoUtils.stringsEqual(a.toJSONString(), b.toJSONString());
- }
-
- /**
- * URL-encode the provided string. If the input is null,
- * the empty string is returned.
- *
- * @param in the string to encode.
- * @return a URL-encoded version of the input.
- */
- protected static String encode(String in) {
- if (in == null) {
- return "";
- }
- try {
- return URLEncoder.encode(in, "UTF-8");
- } catch (UnsupportedEncodingException e) {
- // Will never occur.
- return null;
- }
- }
-
- /**
- * Take the provided URI and two parameters, constructing a URI like
- *
- * places:uri=$uri&p1=$p1&p2=$p2
- *
- * null values in either parameter or value result in the parameter being omitted.
- */
- protected static String encodeUnsupportedTypeURI(String originalURI, String p1, String v1, String p2, String v2) {
- StringBuilder b = new StringBuilder(PLACES_URI_PREFIX);
- boolean previous = false;
- if (originalURI != null) {
- b.append("uri=");
- b.append(encode(originalURI));
- previous = true;
- }
- if (p1 != null && v1 != null) {
- if (previous) {
- b.append("&");
- }
- b.append(p1);
- b.append("=");
- b.append(encode(v1));
- previous = true;
- }
- if (p2 != null && v2 != null) {
- if (previous) {
- b.append("&");
- }
- b.append(p2);
- b.append("=");
- b.append(encode(v2));
- previous = true;
- }
- return b.toString();
- }
-}
-
-
-/*
-// Bookmark:
-{cleartext:
- {id: "l7p2xqOTMMXw",
- type: "bookmark",
- title: "Your Flight Status",
- parentName: "mobile",
- bmkUri: "http: //www.flightstats.com/go/Mobile/flightStatusByFlightProcess.do;jsessionid=13A6C8DCC9592AF141A43349040262CE.web3: 8009?utm_medium=cpc&utm_campaign=co-op&utm_source=airlineInformationAndStatus&id=212492593",
- tags: [],
- keyword: null,
- description: null,
- loadInSidebar: false,
- parentid: "mobile"},
- data: {payload: {ciphertext: null},
- id: "l7p2xqOTMMXw",
- sortindex: 107},
- collection: "bookmarks"}
-
-// Folder:
-{cleartext:
- {id: "mobile",
- type: "folder",
- parentName: "",
- title: "mobile",
- description: null,
- children: ["1ROdlTuIoddD", "3Z_bMIHPSZQ8", "4mSDUuOo2iVB", "8aEdE9IIrJVr",
- "9DzPTmkkZRDb", "Qwwb99HtVKsD", "s8tM36aGPKbq", "JMTi61hOO3JV",
- "JQUDk0wSvYip", "LmVH-J1r3HLz", "NhgQlC5ykYGW", "OVanevUUaqO2",
- "OtQVX0PMiWQj", "_GP5cF595iie", "fkRssjXSZDL3", "k7K_NwIA1Ya0",
- "raox_QGzvqh1", "vXYL-xHjK06k", "QKHKUN6Dm-xv", "pmN2dYWT2MJ_",
- "EVeO_J1SQiwL", "7N-qkepS7bec", "NIGa3ha-HVOE", "2Phv1I25wbuH",
- "TTSIAH1fV0VE", "WOmZ8PfH39Da", "gDTXNg4m1AJZ", "ayI30OZslHbO",
- "zSEs4O3n6CzQ", "oWTDR0gO2aWf", "wWHUoFaInXi9", "F7QTuVJDpsTM",
- "FIboggegplk-", "G4HWrT5nfRYS", "MHA7y9bupDdv", "T_Ldzmj0Ttte",
- "U9eYu3SxsE_U", "bk463Kl9IO_m", "brUfrqJjFNSR", "ccpawfWsD-bY",
- "l7p2xqOTMMXw", "o-nSDKtXYln7"],
- parentid: "places"},
- data: {payload: {ciphertext: null},
- id: "mobile",
- sortindex: 1000000},
- collection: "bookmarks"}
-*/
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/BookmarkRecordFactory.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/BookmarkRecordFactory.java
deleted file mode 100644
index edf7b288c..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/BookmarkRecordFactory.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.domain;
-
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.repositories.RecordFactory;
-
-/**
- * Turns CryptoRecords into BookmarkRecords.
- *
- * @author rnewman
- *
- */
-public class BookmarkRecordFactory extends RecordFactory {
-
- @Override
- public Record createRecord(Record record) {
- BookmarkRecord r = new BookmarkRecord();
- r.initFromEnvelope((CryptoRecord) record);
- return r;
- }
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/ClientRecord.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/ClientRecord.java
deleted file mode 100644
index 0c513a4a0..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/ClientRecord.java
+++ /dev/null
@@ -1,231 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.domain;
-
-import org.json.simple.JSONArray;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.NonArrayJSONException;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.repositories.android.RepoUtils;
-
-public class ClientRecord extends Record {
- private static final String LOG_TAG = "ClientRecord";
-
- public static final String CLIENT_TYPE = "mobile";
- public static final String COLLECTION_NAME = "clients";
- public static final long CLIENTS_TTL = 21 * 24 * 60 * 60; // 21 days in seconds.
- public static final String DEFAULT_CLIENT_NAME = "Default Name";
-
- public static final String PROTOCOL_LEGACY_SYNC = "1.1";
- public static final String PROTOCOL_FXA_SYNC = "1.5";
-
- /**
- * Each of these fields is 'owned' by the client it represents. For example,
- * the "version" field is the Firefox version of that client; some time after
- * that client upgrades, it'll upload a new record with its new version.
- *
- * The only exception is for commands. When a command is sent to a client, the
- * sender will download its current record, append the command to the
- * "commands" array, and reupload the record. After processing, the recipient
- * will reupload its record with an empty commands array.
- *
- * Note that the version, then, will remain the version of the recipient, as
- * with the other descriptive fields.
- */
- public String name = ClientRecord.DEFAULT_CLIENT_NAME;
- public String type = ClientRecord.CLIENT_TYPE;
- public String version = null; // Free-form string, optional.
- public JSONArray commands;
- public JSONArray protocols;
-
- // Optional fields.
- // See <https://github.com/mozilla-services/docs/blob/master/source/sync/objectformats.rst#user-content-clients>
- // for full formats.
- // If a value isn't known, the field is omitted.
- public String formfactor; // "phone", "largetablet", "smalltablet", "desktop", "laptop", "tv".
- public String os; // One of "Android", "Darwin", "WINNT", "Linux", "iOS", "Firefox OS".
- public String application; // Display name, E.g., "Firefox Beta"
- public String appPackage; // E.g., "org.mozilla.firefox_beta"
- public String device; // E.g., "HTC One"
- public String fxaDeviceId; // E.g., "525b624eaaf1e40d21ec8997c3116ad8"
-
- public ClientRecord(String guid, String collection, long lastModified, boolean deleted) {
- super(guid, collection, lastModified, deleted);
- this.ttl = CLIENTS_TTL;
- }
-
- public ClientRecord(String guid, String collection, long lastModified) {
- this(guid, collection, lastModified, false);
- }
-
- public ClientRecord(String guid, String collection) {
- this(guid, collection, 0, false);
- }
-
- public ClientRecord(String guid) {
- this(guid, COLLECTION_NAME, 0, false);
- }
-
- public ClientRecord() {
- this(Utils.generateGuid(), COLLECTION_NAME, 0, false);
- }
-
- @Override
- protected void initFromPayload(ExtendedJSONObject payload) {
- this.name = (String) payload.get("name");
- this.type = (String) payload.get("type");
- try {
- this.version = (String) payload.get("version");
- } catch (Exception e) {
- // Oh well.
- }
-
- try {
- commands = payload.getArray("commands");
- } catch (NonArrayJSONException e) {
- Logger.debug(LOG_TAG, "Got non-array commands in client record " + guid, e);
- commands = null;
- }
-
- try {
- protocols = payload.getArray("protocols");
- } catch (NonArrayJSONException e) {
- Logger.debug(LOG_TAG, "Got non-array protocols in client record " + guid, e);
- protocols = null;
- }
-
- if (payload.containsKey("formfactor")) {
- this.formfactor = payload.getString("formfactor");
- }
-
- if (payload.containsKey("os")) {
- this.os = payload.getString("os");
- }
-
- if (payload.containsKey("application")) {
- this.application = payload.getString("application");
- }
-
- if (payload.containsKey("appPackage")) {
- this.appPackage = payload.getString("appPackage");
- }
-
- if (payload.containsKey("device")) {
- this.device = payload.getString("device");
- }
-
- if (payload.containsKey("fxaDeviceId")) {
- this.fxaDeviceId = payload.getString("fxaDeviceId");
- }
- }
-
- @Override
- protected void populatePayload(ExtendedJSONObject payload) {
- putPayload(payload, "id", this.guid);
- putPayload(payload, "name", this.name);
- putPayload(payload, "type", this.type);
- putPayload(payload, "version", this.version);
-
- if (this.commands != null) {
- payload.put("commands", this.commands);
- }
-
- if (this.protocols != null) {
- payload.put("protocols", this.protocols);
- }
-
- if (this.formfactor != null) {
- payload.put("formfactor", this.formfactor);
- }
-
- if (this.os != null) {
- payload.put("os", this.os);
- }
-
- if (this.application != null) {
- payload.put("application", this.application);
- }
-
- if (this.appPackage != null) {
- payload.put("appPackage", this.appPackage);
- }
-
- if (this.device != null) {
- payload.put("device", this.device);
- }
-
- if (this.fxaDeviceId != null) {
- payload.put("fxaDeviceId", this.fxaDeviceId);
- }
- }
-
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof ClientRecord) || !super.equals(o)) {
- return false;
- }
-
- return this.equalPayloads(o);
- }
-
- @Override
- public int hashCode() {
- return super.hashCode();
- }
-
- @Override
- public boolean equalPayloads(Object o) {
- if (!(o instanceof ClientRecord) || !super.equalPayloads(o)) {
- return false;
- }
-
- // Don't compare versions, protocols, or other optional fields, no matter how much we might want to.
- // They're not required by the spec.
- ClientRecord other = (ClientRecord) o;
- if (!RepoUtils.stringsEqual(other.name, this.name) ||
- !RepoUtils.stringsEqual(other.type, this.type)) {
- return false;
- }
- return true;
- }
-
- @Override
- public Record copyWithIDs(String guid, long androidID) {
- ClientRecord out = new ClientRecord(guid, this.collection, this.lastModified, this.deleted);
- out.androidID = androidID;
- out.sortIndex = this.sortIndex;
- out.ttl = this.ttl;
-
- out.name = this.name;
- out.type = this.type;
- out.version = this.version;
- out.protocols = this.protocols;
-
- out.formfactor = this.formfactor;
- out.os = this.os;
- out.application = this.application;
- out.appPackage = this.appPackage;
- out.device = this.device;
- out.fxaDeviceId = this.fxaDeviceId;
-
- return out;
- }
-
-/*
-Example record:
-
-{id:"relf31w7B4F1",
- name:"marina_mac",
- type:"mobile"
- commands:[{"args":["bookmarks"],"command":"wipeEngine"},
- {"args":["forms"],"command":"wipeEngine"},
- {"args":["history"],"command":"wipeEngine"},
- {"args":["passwords"],"command":"wipeEngine"},
- {"args":["prefs"],"command":"wipeEngine"},
- {"args":["tabs"],"command":"wipeEngine"},
- {"args":["addons"],"command":"wipeEngine"}]}
-*/
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/ClientRecordFactory.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/ClientRecordFactory.java
deleted file mode 100644
index 897d2859c..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/ClientRecordFactory.java
+++ /dev/null
@@ -1,17 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.domain;
-
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.repositories.RecordFactory;
-
-public class ClientRecordFactory extends RecordFactory {
- @Override
- public Record createRecord(Record record) {
- ClientRecord r = new ClientRecord();
- r.initFromEnvelope((CryptoRecord) record);
- return r;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/FormHistoryRecord.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/FormHistoryRecord.java
deleted file mode 100644
index e7ca70cb4..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/FormHistoryRecord.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.domain;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.repositories.android.RepoUtils;
-
-/**
- * A FormHistoryRecord represents a saved form element.
- *
- * I map a <code>fieldName</code> string to a <code>value</code> string.
- *
- * @see "<a href='http://dxr.mozilla.org/services-central/source/services-central/services/sync/modules/engines/forms.js'>http://dxr.mozilla.org/services-central/source/services-central/services/sync/modules/engines/forms.js</a>."
- */
-public class FormHistoryRecord extends Record {
- private static final String LOG_TAG = "FormHistoryRecord";
-
- public static final String COLLECTION_NAME = "forms";
- private static final String PAYLOAD_NAME = "name";
- private static final String PAYLOAD_VALUE = "value";
- public static final long FORMS_TTL = 3 * 365 * 24 * 60 * 60; // Three years in seconds.
-
- /**
- * The name of the saved form field.
- */
- public String fieldName;
-
- /**
- * The value of the saved form field.
- */
- public String fieldValue;
-
- public FormHistoryRecord(String guid, String collection, long lastModified, boolean deleted) {
- super(guid, collection, lastModified, deleted);
- this.ttl = FORMS_TTL;
- }
-
- public FormHistoryRecord(String guid, String collection, long lastModified) {
- this(guid, collection, lastModified, false);
- }
-
- public FormHistoryRecord(String guid, String collection) {
- this(guid, collection, 0, false);
- }
-
- public FormHistoryRecord(String guid) {
- this(guid, COLLECTION_NAME, 0, false);
- }
-
- public FormHistoryRecord() {
- this(Utils.generateGuid(), COLLECTION_NAME, 0, false);
- }
-
- @Override
- public Record copyWithIDs(String guid, long androidID) {
- FormHistoryRecord out = new FormHistoryRecord(guid, this.collection, this.lastModified, this.deleted);
- out.androidID = androidID;
- out.sortIndex = this.sortIndex;
-
- // Copy FormHistoryRecord fields.
- out.fieldName = this.fieldName;
- out.fieldValue = this.fieldValue;
-
- return out;
- }
-
- @Override
- public void populatePayload(ExtendedJSONObject payload) {
- putPayload(payload, PAYLOAD_NAME, this.fieldName);
- putPayload(payload, PAYLOAD_VALUE, this.fieldValue);
- }
-
- @Override
- public void initFromPayload(ExtendedJSONObject payload) {
- this.fieldName = payload.getString(PAYLOAD_NAME);
- this.fieldValue = payload.getString(PAYLOAD_VALUE);
- }
-
- /**
- * We consider two form history records to be congruent if they represent the
- * same form element regardless of times used.
- */
- @Override
- public boolean congruentWith(Object o) {
- if (!(o instanceof FormHistoryRecord)) {
- return false;
- }
- FormHistoryRecord other = (FormHistoryRecord) o;
- if (!super.congruentWith(other)) {
- return false;
- }
- return RepoUtils.stringsEqual(this.fieldName, other.fieldName) &&
- RepoUtils.stringsEqual(this.fieldValue, other.fieldValue);
- }
-
- @Override
- public boolean equalPayloads(Object o) {
- if (!(o instanceof FormHistoryRecord)) {
- Logger.debug(LOG_TAG, "Not a FormHistoryRecord: " + o.getClass());
- return false;
- }
- FormHistoryRecord other = (FormHistoryRecord) o;
- if (!super.equalPayloads(other)) {
- Logger.debug(LOG_TAG, "super.equalPayloads returned false.");
- return false;
- }
-
- if (this.deleted) {
- // FormHistoryRecords are equal if they are both deleted (which
- // they are, since super.equalPayloads is true) and have the
- // same GUID.
- if (other.deleted) {
- return RepoUtils.stringsEqual(this.guid, other.guid);
- }
- return false;
- }
-
- return RepoUtils.stringsEqual(this.fieldName, other.fieldName) &&
- RepoUtils.stringsEqual(this.fieldValue, other.fieldValue);
- }
-
- public FormHistoryRecord log(String logTag) {
- try {
- Logger.debug(logTag, "Returning form history record " + guid + " (" + androidID + ")");
- Logger.debug(logTag, "> Last modified: " + lastModified);
- if (Logger.LOG_PERSONAL_INFORMATION) {
- Logger.pii(logTag, "> Field name: " + fieldName);
- Logger.pii(logTag, "> Field value: " + fieldValue);
- }
- } catch (Exception e) {
- Logger.debug(logTag, "Exception logging form history record " + this, e);
- }
- return this;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/HistoryRecord.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/HistoryRecord.java
deleted file mode 100644
index 94eae13a7..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/HistoryRecord.java
+++ /dev/null
@@ -1,217 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.domain;
-
-import java.util.HashMap;
-
-import org.json.simple.JSONArray;
-import org.json.simple.JSONObject;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.NonArrayJSONException;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.repositories.android.RepoUtils;
-
-/**
- * Visits are in microsecond precision.
- *
- * @author rnewman
- *
- */
-public class HistoryRecord extends Record {
- private static final String LOG_TAG = "HistoryRecord";
-
- public static final String COLLECTION_NAME = "history";
- public static final long HISTORY_TTL = 60 * 24 * 60 * 60; // 60 days in seconds.
-
- public HistoryRecord(String guid, String collection, long lastModified, boolean deleted) {
- super(guid, collection, lastModified, deleted);
- this.ttl = HISTORY_TTL;
- }
- public HistoryRecord(String guid, String collection, long lastModified) {
- this(guid, collection, lastModified, false);
- }
- public HistoryRecord(String guid, String collection) {
- this(guid, collection, 0, false);
- }
- public HistoryRecord(String guid) {
- this(guid, COLLECTION_NAME, 0, false);
- }
- public HistoryRecord() {
- this(Utils.generateGuid(), COLLECTION_NAME, 0, false);
- }
-
- public String title;
- public String histURI;
- public JSONArray visits;
- public long fennecDateVisited;
- public long fennecVisitCount;
-
- @SuppressWarnings("unchecked")
- private JSONArray copyVisits() {
- if (this.visits == null) {
- return null;
- }
- JSONArray out = new JSONArray();
- out.addAll(this.visits);
- return out;
- }
-
- @Override
- public Record copyWithIDs(String guid, long androidID) {
- HistoryRecord out = new HistoryRecord(guid, this.collection, this.lastModified, this.deleted);
- out.androidID = androidID;
- out.sortIndex = this.sortIndex;
- out.ttl = this.ttl;
-
- // Copy HistoryRecord fields.
- out.title = this.title;
- out.histURI = this.histURI;
- out.fennecDateVisited = this.fennecDateVisited;
- out.fennecVisitCount = this.fennecVisitCount;
- out.visits = this.copyVisits();
-
- return out;
- }
-
- @Override
- protected void populatePayload(ExtendedJSONObject payload) {
- putPayload(payload, "id", this.guid);
- putPayload(payload, "title", this.title);
- putPayload(payload, "histUri", this.histURI); // TODO: encoding?
- payload.put("visits", this.visits);
- }
-
- @Override
- protected void initFromPayload(ExtendedJSONObject payload) {
- this.histURI = (String) payload.get("histUri");
- this.title = (String) payload.get("title");
- try {
- this.visits = payload.getArray("visits");
- } catch (NonArrayJSONException e) {
- Logger.error(LOG_TAG, "Got non-array visits in history record " + this.guid, e);
- this.visits = new JSONArray();
- }
- }
-
- /**
- * We consider two history records to be congruent if they represent the
- * same history record regardless of visits. Titles are allowed to differ,
- * but the URI must be the same.
- */
- @Override
- public boolean congruentWith(Object o) {
- if (!(o instanceof HistoryRecord)) {
- return false;
- }
- HistoryRecord other = (HistoryRecord) o;
- if (!super.congruentWith(other)) {
- return false;
- }
- return RepoUtils.stringsEqual(this.histURI, other.histURI);
- }
-
- @Override
- public boolean equalPayloads(Object o) {
- if (!(o instanceof HistoryRecord)) {
- Logger.debug(LOG_TAG, "Not a HistoryRecord: " + o.getClass());
- return false;
- }
- HistoryRecord other = (HistoryRecord) o;
- if (!super.equalPayloads(other)) {
- Logger.debug(LOG_TAG, "super.equalPayloads returned false.");
- return false;
- }
- return RepoUtils.stringsEqual(this.title, other.title) &&
- RepoUtils.stringsEqual(this.histURI, other.histURI) &&
- checkVisitsEquals(other);
- }
-
- @Override
- public boolean equalAndroidIDs(Record other) {
- return super.equalAndroidIDs(other) &&
- this.equalFennecVisits(other);
- }
-
- private boolean equalFennecVisits(Record other) {
- if (!(other instanceof HistoryRecord)) {
- return false;
- }
- HistoryRecord h = (HistoryRecord) other;
- return this.fennecDateVisited == h.fennecDateVisited &&
- this.fennecVisitCount == h.fennecVisitCount;
- }
-
- private boolean checkVisitsEquals(HistoryRecord other) {
- Logger.debug(LOG_TAG, "Checking visits.");
- if (Logger.LOG_PERSONAL_INFORMATION) {
- // Don't JSON-encode unless we're logging.
- Logger.pii(LOG_TAG, ">> Mine: " + ((this.visits == null) ? "null" : this.visits.toJSONString()));
- Logger.pii(LOG_TAG, ">> Theirs: " + ((other.visits == null) ? "null" : other.visits.toJSONString()));
- }
-
- // Handle nulls.
- if (this.visits == other.visits) {
- return true;
- }
-
- // Now they can't both be null.
- int aSize = this.visits == null ? 0 : this.visits.size();
- int bSize = other.visits == null ? 0 : other.visits.size();
-
- if (aSize != bSize) {
- return false;
- }
-
- // Now neither of them can be null.
-
- // TODO: do this by maintaining visits as a sorted array.
- HashMap<Long, Long> otherVisits = new HashMap<Long, Long>();
- for (int i = 0; i < bSize; i++) {
- JSONObject visit = (JSONObject) other.visits.get(i);
- otherVisits.put((Long) visit.get("date"), (Long) visit.get("type"));
- }
-
- for (int i = 0; i < aSize; i++) {
- JSONObject visit = (JSONObject) this.visits.get(i);
- if (!otherVisits.containsKey(visit.get("date"))) {
- return false;
- }
- Long otherDate = (Long) visit.get("date");
- Long otherType = otherVisits.get(otherDate);
- if (otherType == null) {
- return false;
- }
- if (!otherType.equals((Long) visit.get("type"))) {
- return false;
- }
- }
-
- return true;
- }
-
-//
-// Example record (note microsecond resolution):
-//
-// {id:"--DUvUomABNq",
-// histUri:"https://bugzilla.mozilla.org/show_bug.cgi?id=697634",
-// title:"697634 \u2013 xpcshell test failures on 10.7",
-// visits:[{date:1320087601465600, type:2},
-// {date:1320084970724990, type:1},
-// {date:1320084847035717, type:1},
-// {date:1319764134412287, type:1},
-// {date:1319757917982518, type:1},
-// {date:1319751664627351, type:1},
-// {date:1319681421072326, type:1},
-// {date:1319681306455594, type:1},
-// {date:1319678117125234, type:1},
-// {date:1319677508862901, type:1}]
-// }
-//
-//"type" is a transition type:
-//
-//https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsINavHistoryService#Transition_type_constants
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/HistoryRecordFactory.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/HistoryRecordFactory.java
deleted file mode 100644
index ac2c6a1dc..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/HistoryRecordFactory.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.domain;
-
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.repositories.RecordFactory;
-
-/**
- * Turns CryptoRecords into HistoryRecords.
- *
- * @author rnewman
- *
- */
-public class HistoryRecordFactory extends RecordFactory {
-
- @Override
- public Record createRecord(Record record) {
- HistoryRecord r = new HistoryRecord();
- r.initFromEnvelope((CryptoRecord) record);
- return r;
- }
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/PasswordRecord.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/PasswordRecord.java
deleted file mode 100644
index b2de60f3c..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/PasswordRecord.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.domain;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.repositories.android.RepoUtils;
-
-public class PasswordRecord extends Record {
- private static final String LOG_TAG = "PasswordRecord";
-
- public static final String COLLECTION_NAME = "passwords";
- public static long PASSWORDS_TTL = -1; // Never expire passwords.
-
- // Payload strings.
- public static final String PAYLOAD_HOSTNAME = "hostname";
- public static final String PAYLOAD_FORM_SUBMIT_URL = "formSubmitURL";
- public static final String PAYLOAD_HTTP_REALM = "httpRealm";
- public static final String PAYLOAD_USERNAME = "username";
- public static final String PAYLOAD_PASSWORD = "password";
- public static final String PAYLOAD_USERNAME_FIELD = "usernameField";
- public static final String PAYLOAD_PASSWORD_FIELD = "passwordField";
-
- public PasswordRecord(String guid, String collection, long lastModified, boolean deleted) {
- super(guid, collection, lastModified, deleted);
- this.ttl = PASSWORDS_TTL;
- }
- public PasswordRecord(String guid, String collection, long lastModified) {
- this(guid, collection, lastModified, false);
- }
- public PasswordRecord(String guid, String collection) {
- this(guid, collection, 0, false);
- }
- public PasswordRecord(String guid) {
- this(guid, COLLECTION_NAME, 0, false);
- }
- public PasswordRecord() {
- this(Utils.generateGuid(), COLLECTION_NAME, 0, false);
- }
-
- public String id;
- public String hostname;
- public String formSubmitURL;
- public String httpRealm;
- // TODO these are encrypted in the passwords content provider,
- // need to figure out what we need to do here.
- public String usernameField;
- public String passwordField;
- public String encryptedUsername;
- public String encryptedPassword;
- public String encType;
-
- public long timeCreated;
- public long timeLastUsed;
- public long timePasswordChanged;
- public long timesUsed;
-
-
- @Override
- public Record copyWithIDs(String guid, long androidID) {
- PasswordRecord out = new PasswordRecord(guid, this.collection, this.lastModified, this.deleted);
- out.androidID = androidID;
- out.sortIndex = this.sortIndex;
- out.ttl = this.ttl;
-
- // Copy PasswordRecord fields.
- out.id = this.id;
- out.hostname = this.hostname;
- out.formSubmitURL = this.formSubmitURL;
- out.httpRealm = this.httpRealm;
-
- out.usernameField = this.usernameField;
- out.passwordField = this.passwordField;
- out.encryptedUsername = this.encryptedUsername;
- out.encryptedPassword = this.encryptedPassword;
- out.encType = this.encType;
-
- out.timeCreated = this.timeCreated;
- out.timeLastUsed = this.timeLastUsed;
- out.timePasswordChanged = this.timePasswordChanged;
- out.timesUsed = this.timesUsed;
-
- return out;
- }
-
- @Override
- public void initFromPayload(ExtendedJSONObject payload) {
- this.hostname = payload.getString(PAYLOAD_HOSTNAME);
- this.formSubmitURL = payload.getString(PAYLOAD_FORM_SUBMIT_URL);
- this.httpRealm = payload.getString(PAYLOAD_HTTP_REALM);
- this.encryptedUsername = payload.getString(PAYLOAD_USERNAME);
- this.encryptedPassword = payload.getString(PAYLOAD_PASSWORD);
- this.usernameField = payload.getString(PAYLOAD_USERNAME_FIELD);
- this.passwordField = payload.getString(PAYLOAD_PASSWORD_FIELD);
- }
-
- @Override
- public void populatePayload(ExtendedJSONObject payload) {
- putPayload(payload, PAYLOAD_HOSTNAME, this.hostname);
- putPayload(payload, PAYLOAD_FORM_SUBMIT_URL, this.formSubmitURL);
- putPayload(payload, PAYLOAD_HTTP_REALM, this.httpRealm);
- putPayload(payload, PAYLOAD_USERNAME, this.encryptedUsername);
- putPayload(payload, PAYLOAD_PASSWORD, this.encryptedPassword);
- putPayload(payload, PAYLOAD_USERNAME_FIELD, this.usernameField);
- putPayload(payload, PAYLOAD_PASSWORD_FIELD, this.passwordField);
- }
-
- @Override
- public boolean congruentWith(Object o) {
- if (!(o instanceof PasswordRecord)) {
- return false;
- }
- PasswordRecord other = (PasswordRecord) o;
- if (!super.congruentWith(other)) {
- return false;
- }
- return RepoUtils.stringsEqual(this.hostname, other.hostname)
- && RepoUtils.stringsEqual(this.formSubmitURL, other.formSubmitURL)
- // Bug 738347 - SQLiteBridge does not check for nulls in ContentValues.
- // && RepoUtils.stringsEqual(this.httpRealm, other.httpRealm)
- // && RepoUtils.stringsEqual(this.encType, other.encType)
- && RepoUtils.stringsEqual(this.usernameField, other.usernameField)
- && RepoUtils.stringsEqual(this.passwordField, other.passwordField)
- && RepoUtils.stringsEqual(this.encryptedUsername, other.encryptedUsername)
- && RepoUtils.stringsEqual(this.encryptedPassword, other.encryptedPassword);
- }
-
- @Override
- public boolean equalPayloads(Object o) {
- if (!(o instanceof PasswordRecord)) {
- return false;
- }
-
- PasswordRecord other = (PasswordRecord) o;
- Logger.debug("PasswordRecord", "thisRecord:" + this.toString());
- Logger.debug("PasswordRecord", "otherRecord:" + o.toString());
-
- if (this.deleted) {
- if (other.deleted) {
- // Deleted records are equal if their guids match.
- return RepoUtils.stringsEqual(this.guid, other.guid);
- }
- // One record is deleted, the other is not. Not equal.
- return false;
- }
-
- if (!super.equalPayloads(other)) {
- Logger.debug(LOG_TAG, "super.equalPayloads returned false.");
- return false;
- }
-
- return RepoUtils.stringsEqual(this.hostname, other.hostname)
- && RepoUtils.stringsEqual(this.formSubmitURL, other.formSubmitURL)
- // Bug 738347 - SQLiteBridge does not check for nulls in ContentValues.
- // && RepoUtils.stringsEqual(this.httpRealm, other.httpRealm)
- // && RepoUtils.stringsEqual(this.encType, other.encType)
- && RepoUtils.stringsEqual(this.usernameField, other.usernameField)
- && RepoUtils.stringsEqual(this.passwordField, other.passwordField)
- && RepoUtils.stringsEqual(this.encryptedUsername, other.encryptedUsername)
- && RepoUtils.stringsEqual(this.encryptedPassword, other.encryptedPassword);
- // Desktop sync never sets timeCreated so this isn't relevant for sync records.
- }
-
- @Override
- public String toString() {
- return "PasswordRecord {"
- + "lastModified: " + this.lastModified + ", "
- + "hostname null?: " + (this.hostname == null) + ", "
- + "formSubmitURL null?: " + (this.formSubmitURL == null) + ", "
- + "httpRealm null?: " + (this.httpRealm == null) + ", "
- + "usernameField null?: " + (this.usernameField == null) + ", "
- + "passwordField null?: " + (this.passwordField == null) + ", "
- + "encryptedUsername null?: " + (this.encryptedUsername == null) + ", "
- + "encryptedPassword null?: " + (this.encryptedPassword == null) + ", "
- + "encType: " + this.encType + ", "
- + "timeCreated: " + this.timeCreated + ", "
- + "timeLastUsed: " + this.timeLastUsed + ", "
- + "timePasswordChanged: " + this.timePasswordChanged + ", "
- + "timesUsed: " + this.timesUsed;
- }
-
- /**
- * A PasswordRecord is considered valid if it abides by the database
- * constraints of the PasswordsProvider (moz_logins).
- *
- * See toolkit/components/passwordmgr/storage-mozStorage.js for the
- * definitions:
- *
- * http://hg.mozilla.org/mozilla-central/file/00955d61cc94/toolkit/components/passwordmgr/storage-mozStorage.js#l98
- */
- public boolean isValid() {
- if (this.deleted) {
- return true;
- }
-
- return this.hostname != null &&
- this.encryptedUsername != null &&
- this.encryptedPassword != null &&
- this.usernameField != null &&
- this.passwordField != null;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/PasswordRecordFactory.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/PasswordRecordFactory.java
deleted file mode 100644
index fc7ef916d..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/PasswordRecordFactory.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.domain;
-
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.repositories.RecordFactory;
-import org.mozilla.gecko.sync.repositories.domain.PasswordRecord;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-public class PasswordRecordFactory extends RecordFactory {
- @Override
- public Record createRecord(Record record) {
- PasswordRecord r = new PasswordRecord();
- r.initFromEnvelope((CryptoRecord) record);
- return r;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/Record.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/Record.java
deleted file mode 100644
index 145704c1c..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/Record.java
+++ /dev/null
@@ -1,308 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.domain;
-
-import java.io.UnsupportedEncodingException;
-
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-
-/**
- * Record is the abstract base class for all entries that Sync processes:
- * bookmarks, passwords, history, and such.
- *
- * A Record can be initialized from or serialized to a CryptoRecord for
- * submission to an encrypted store.
- *
- * Records should be considered to be conventionally immutable: modifications
- * should be completed before the new record object escapes its constructing
- * scope. Note that this is a critically important part of equality. As Rich
- * Hickey notes:
- *
- * … the only things you can really compare for equality are immutable things,
- * because if you compare two things for equality that are mutable, and ever
- * say true, and they're ever not the same thing, you are wrong. Or you will
- * become wrong at some point in the future.
- *
- * Records have a layered definition of equality. Two records can be said to be
- * "equal" if:
- *
- * * They have the same GUID and collection. Two crypto/keys records are in some
- * way "the same".
- * This is `equalIdentifiers`.
- *
- * * Their most significant fields are the same. That is to say, they share a
- * GUID, a collection, deletion, and domain-specific fields. Two copies of
- * crypto/keys, neither deleted, with the same encrypted data but different
- * modified times and sortIndex are in a stronger way "the same".
- * This is `equalPayloads`.
- *
- * * Their most significant fields are the same, and their local fields (e.g.,
- * the androidID to which we have decided that this record maps) are congruent.
- * A record with the same androidID, or one whose androidID has not been set,
- * can be considered "the same".
- * This concept can be extended by Record subclasses. The key point is that
- * reconciling should be applied to the contents of these records. For example,
- * two history records with the same URI and GUID, but different visit arrays,
- * can be said to be congruent.
- * This is `congruentWith`.
- *
- * * They are strictly identical. Every field that is persisted, including
- * lastModified and androidID, is equal.
- * This is `equals`.
- *
- * Different parts of the codebase have use for different layers of this
- * comparison hierarchy. For instance, lastModified times change every time a
- * record is stored; a store followed by a retrieval will return a Record that
- * shares its most significant fields with the input, but has a later
- * lastModified time and might not yet have values set for others. Reconciling
- * will thus ignore the modification time of a record.
- *
- * @author rnewman
- *
- */
-public abstract class Record {
-
- public String guid;
- public String collection;
- public long lastModified;
- public boolean deleted;
- public long androidID;
- /**
- * An integer indicating the relative importance of this item in the collection.
- * <p>
- * Default is 0.
- */
- public long sortIndex;
- /**
- * The number of seconds to keep this record. After that time this item will
- * no longer be returned in response to any request, and it may be pruned from
- * the database.
- * <p>
- * Negative values mean never forget this record.
- * <p>
- * Default is 1 year.
- */
- public long ttl;
-
- public Record(String guid, String collection, long lastModified, boolean deleted) {
- this.guid = guid;
- this.collection = collection;
- this.lastModified = lastModified;
- this.deleted = deleted;
- this.sortIndex = 0;
- this.ttl = 365 * 24 * 60 * 60; // Seconds.
- this.androidID = -1;
- }
-
- /**
- * Return true iff the input is a Record and has the same
- * collection and guid as this object.
- */
- public boolean equalIdentifiers(Object o) {
- if (!(o instanceof Record)) {
- return false;
- }
-
- Record other = (Record) o;
- if (this.guid == null) {
- if (other.guid != null) {
- return false;
- }
- } else {
- if (!this.guid.equals(other.guid)) {
- return false;
- }
- }
- if (this.collection == null) {
- if (other.collection != null) {
- return false;
- }
- } else {
- if (!this.collection.equals(other.collection)) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * @param o
- * The object to which this object should be compared.
- * @return
- * true iff the input is a Record which is substantially the
- * same as this object.
- */
- public boolean equalPayloads(Object o) {
- if (!this.equalIdentifiers(o)) {
- return false;
- }
- Record other = (Record) o;
- return this.deleted == other.deleted;
- }
-
- /**
- *
- *
- * @param o
- * The object to which this object should be compared.
- * @return
- * true iff the input is a Record which is substantially the
- * same as this object, considering the ability and desire to
- * reconcile the two objects if possible.
- */
- public boolean congruentWith(Object o) {
- if (!this.equalIdentifiers(o)) {
- return false;
- }
- Record other = (Record) o;
- return congruentAndroidIDs(other) &&
- (this.deleted == other.deleted);
- }
-
- public boolean congruentAndroidIDs(Record other) {
- // We treat -1 as "unset", and treat this as
- // congruent with any other value.
- if (this.androidID != -1 &&
- other.androidID != -1 &&
- this.androidID != other.androidID) {
- return false;
- }
- return true;
- }
-
- /**
- * Return true iff the input is both equal in terms of payload,
- * and also shares transient values such as timestamps.
- */
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof Record)) {
- return false;
- }
-
- Record other = (Record) o;
- return equalTimestamps(other) &&
- equalSortIndices(other) &&
- equalAndroidIDs(other) &&
- equalPayloads(o);
- }
-
- public boolean equalAndroidIDs(Record other) {
- return this.androidID == other.androidID;
- }
-
- public boolean equalSortIndices(Record other) {
- return this.sortIndex == other.sortIndex;
- }
-
- public boolean equalTimestamps(Object o) {
- if (!(o instanceof Record)) {
- return false;
- }
- return ((Record) o).lastModified == this.lastModified;
- }
-
- protected abstract void populatePayload(ExtendedJSONObject payload);
- protected abstract void initFromPayload(ExtendedJSONObject payload);
-
- public void initFromEnvelope(CryptoRecord envelope) {
- ExtendedJSONObject p = envelope.payload;
- this.guid = envelope.guid;
- checkGUIDs(p);
-
- this.collection = envelope.collection;
- this.lastModified = envelope.lastModified;
-
- final Object del = p.get("deleted");
- if (del instanceof Boolean) {
- this.deleted = (Boolean) del;
- } else {
- this.initFromPayload(p);
- }
-
- }
-
- public CryptoRecord getEnvelope() {
- CryptoRecord rec = new CryptoRecord(this);
- ExtendedJSONObject payload = new ExtendedJSONObject();
- payload.put("id", this.guid);
-
- if (this.deleted) {
- payload.put("deleted", true);
- } else {
- populatePayload(payload);
- }
- rec.payload = payload;
- return rec;
- }
-
- @SuppressWarnings("static-method")
- public String toJSONString() {
- throw new RuntimeException("Cannot JSONify non-CryptoRecord Records.");
- }
-
- public byte[] toJSONBytes() {
- try {
- return this.toJSONString().getBytes("UTF-8");
- } catch (UnsupportedEncodingException e) {
- // Can't happen.
- return null;
- }
- }
-
- /**
- * Utility for safely populating an output CryptoRecord.
- *
- * @param rec
- * @param key
- * @param value
- */
- @SuppressWarnings("static-method")
- protected void putPayload(CryptoRecord rec, String key, String value) {
- if (value == null) {
- return;
- }
- rec.payload.put(key, value);
- }
-
- protected void putPayload(ExtendedJSONObject payload, String key, String value) {
- this.putPayload(payload, key, value, false);
- }
-
- @SuppressWarnings("static-method")
- protected void putPayload(ExtendedJSONObject payload, String key, String value, boolean excludeEmpty) {
- if (value == null) {
- return;
- }
- if (excludeEmpty && value.equals("")) {
- return;
- }
- payload.put(key, value);
- }
-
- protected void checkGUIDs(ExtendedJSONObject payload) {
- String payloadGUID = (String) payload.get("id");
- if (this.guid == null ||
- payloadGUID == null) {
- String detailMessage = "Inconsistency: either envelope or payload GUID missing.";
- throw new IllegalStateException(detailMessage);
- }
- if (!this.guid.equals(payloadGUID)) {
- String detailMessage = "Inconsistency: record has envelope ID " + this.guid + ", payload ID " + payloadGUID;
- throw new IllegalStateException(detailMessage);
- }
- }
-
- /**
- * Oh for persistent data structures.
- *
- * @param guid
- * @param androidID
- * @return
- * An identical copy of this record with the provided two values.
- */
- public abstract Record copyWithIDs(String guid, long androidID);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/RecordParseException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/RecordParseException.java
deleted file mode 100644
index 0d8fe90b2..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/RecordParseException.java
+++ /dev/null
@@ -1,14 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.domain;
-
-
-public class RecordParseException extends Exception {
- private static final long serialVersionUID = -5145494854722254491L;
-
- public RecordParseException(String detailMessage) {
- super(detailMessage);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/TabsRecord.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/TabsRecord.java
deleted file mode 100644
index eb3a4f6d0..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/TabsRecord.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.domain;
-
-import java.util.ArrayList;
-
-import org.json.simple.JSONArray;
-import org.json.simple.JSONObject;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.db.Tab;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.NonArrayJSONException;
-import org.mozilla.gecko.sync.Utils;
-
-import android.content.ContentValues;
-
-/**
- * Represents a client's collection of tabs.
- *
- * @author rnewman
- *
- */
-public class TabsRecord extends Record {
- public static final String LOG_TAG = "TabsRecord";
-
- public static final String COLLECTION_NAME = "tabs";
- public static final long TABS_TTL = 7 * 24 * 60 * 60; // 7 days in seconds.
-
- public TabsRecord(String guid, String collection, long lastModified, boolean deleted) {
- super(guid, collection, lastModified, deleted);
- this.ttl = TABS_TTL;
- }
- public TabsRecord(String guid, String collection, long lastModified) {
- this(guid, collection, lastModified, false);
- }
- public TabsRecord(String guid, String collection) {
- this(guid, collection, 0, false);
- }
- public TabsRecord(String guid) {
- this(guid, COLLECTION_NAME, 0, false);
- }
- public TabsRecord() {
- this(Utils.generateGuid(), COLLECTION_NAME, 0, false);
- }
-
- public String clientName;
- public ArrayList<Tab> tabs;
-
- @Override
- public void initFromPayload(ExtendedJSONObject payload) {
- clientName = (String) payload.get("clientName");
- try {
- tabs = tabsFrom(payload.getArray("tabs"));
- } catch (NonArrayJSONException e) {
- // Oh well.
- tabs = new ArrayList<Tab>();
- }
- }
-
- @SuppressWarnings("unchecked")
- protected static JSONArray tabsToJSON(ArrayList<Tab> tabs) {
- JSONArray out = new JSONArray();
- for (Tab tab : tabs) {
- out.add(tabToJSONObject(tab));
- }
- return out;
- }
-
- protected static ArrayList<Tab> tabsFrom(JSONArray in) {
- ArrayList<Tab> tabs = new ArrayList<Tab>(in.size());
- for (Object o : in) {
- if (o instanceof JSONObject) {
- try {
- tabs.add(TabsRecord.tabFromJSONObject((JSONObject) o));
- } catch (NonArrayJSONException e) {
- Logger.warn(LOG_TAG, "urlHistory is not an array for this tab.", e);
- }
- }
- }
- return tabs;
- }
-
- @Override
- public void populatePayload(ExtendedJSONObject payload) {
- putPayload(payload, "id", this.guid);
- putPayload(payload, "clientName", this.clientName);
- payload.put("tabs", tabsToJSON(this.tabs));
- }
-
- @Override
- public Record copyWithIDs(String guid, long androidID) {
- TabsRecord out = new TabsRecord(guid, this.collection, this.lastModified, this.deleted);
- out.androidID = androidID;
- out.sortIndex = this.sortIndex;
- out.ttl = this.ttl;
-
- out.clientName = this.clientName;
- out.tabs = new ArrayList<Tab>(this.tabs);
-
- return out;
- }
-
- public ContentValues getClientsContentValues() {
- ContentValues cv = new ContentValues();
- cv.put(BrowserContract.Clients.GUID, this.guid);
- cv.put(BrowserContract.Clients.NAME, this.clientName);
- cv.put(BrowserContract.Clients.LAST_MODIFIED, this.lastModified);
- return cv;
- }
-
- public ContentValues[] getTabsContentValues() {
- int c = tabs.size();
- ContentValues[] out = new ContentValues[c];
- for (int i = 0; i < c; i++) {
- out[i] = tabs.get(i).toContentValues(this.guid, i);
- }
- return out;
- }
-
- public static Tab tabFromJSONObject(JSONObject o) throws NonArrayJSONException {
- ExtendedJSONObject obj = new ExtendedJSONObject(o);
- String title = obj.getString("title");
- String icon = obj.getString("icon");
- JSONArray history = obj.getArray("urlHistory");
-
- // Last used is inexplicably a string in seconds. Most of the time.
- long lastUsed = 0;
- Object lU = obj.get("lastUsed");
- if (lU instanceof Number) {
- lastUsed = ((Long) lU) * 1000L;
- } else if (lU instanceof String) {
- try {
- lastUsed = Long.parseLong((String) lU, 10) * 1000L;
- } catch (NumberFormatException e) {
- Logger.debug(TabsRecord.LOG_TAG, "Invalid number format in lastUsed: " + lU);
- }
- }
- return new Tab(title, icon, history, lastUsed);
- }
-
- @SuppressWarnings("unchecked")
- public static JSONObject tabToJSONObject(Tab tab) {
- JSONObject o = new JSONObject();
- o.put("title", tab.title);
- o.put("icon", tab.icon);
- o.put("urlHistory", tab.history);
- o.put("lastUsed", tab.lastUsed / 1000);
- return o;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/TabsRecordFactory.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/TabsRecordFactory.java
deleted file mode 100644
index 9504434d8..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/TabsRecordFactory.java
+++ /dev/null
@@ -1,17 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.domain;
-
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.repositories.RecordFactory;
-
-public class TabsRecordFactory extends RecordFactory {
- @Override
- public Record createRecord(Record record) {
- TabsRecord r = new TabsRecord();
- r.initFromEnvelope((CryptoRecord) record);
- return r;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/VersionConstants.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/VersionConstants.java
deleted file mode 100644
index 2d3d4fd32..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/domain/VersionConstants.java
+++ /dev/null
@@ -1,14 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.domain;
-
-public class VersionConstants {
- public static final int BOOKMARKS_ENGINE_VERSION = 2;
- public static final int CLIENTS_ENGINE_VERSION = 1;
- public static final int FORMS_ENGINE_VERSION = 1;
- public static final int HISTORY_ENGINE_VERSION = 1;
- public static final int PASSWORDS_ENGINE_VERSION = 1;
- public static final int TABS_ENGINE_VERSION = 1;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/downloaders/BatchingDownloader.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/downloaders/BatchingDownloader.java
deleted file mode 100644
index 5c3037e4d..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/downloaders/BatchingDownloader.java
+++ /dev/null
@@ -1,310 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.downloaders;
-
-import android.support.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.DelayedWorkTracker;
-import org.mozilla.gecko.sync.net.SyncResponse;
-import org.mozilla.gecko.sync.net.SyncStorageCollectionRequest;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-import org.mozilla.gecko.sync.repositories.Server11Repository;
-import org.mozilla.gecko.sync.repositories.Server11RepositorySession;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URLEncoder;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Batching Downloader, which implements batching protocol as supported by Sync 1.5.
- *
- * Downloader's batching behaviour is configured via two parameters, obtained from the repository:
- * - Per-batch limit, which specified how many records may be fetched in an individual GET request.
- * - Total limit, which controls number of batch GET requests we will make.
- *
- *
- * Batching is implemented via specifying a 'limit' GET parameter, and looking for an 'offset' token
- * in the response. If offset token is present, this indicates that there are more records than what
- * we've received so far, and we perform an additional fetch. Batching stops when either we hit a total
- * limit, or offset token is no longer present (indicating that we're done).
- *
- * For unlimited repositories (such as passwords), both of these value will be -1. Downloader will not
- * specify a limit parameter in this case, and the response will contain every record available and no
- * offset token, thus fully completing in one go.
- *
- * In between batches, we maintain a Last-Modified timestamp, based off the value return in the header
- * of the first response. Every response will have a Last-Modified header, indicating when the collection
- * was modified last. We pass along this header in our subsequent requests in a X-If-Unmodified-Since
- * header. Server will ensure that our collection did not change while we are batching, if it did it will
- * fail our fetch with a 412 (Consequent Modification) error. Additionally, we perform the same checks
- * locally.
- */
-public class BatchingDownloader {
- public static final String LOG_TAG = "BatchingDownloader";
-
- protected final Server11Repository repository;
- private final Server11RepositorySession repositorySession;
- private final DelayedWorkTracker workTracker = new DelayedWorkTracker();
- // Used to track outstanding requests, so that we can abort them as needed.
- @VisibleForTesting
- protected final Set<SyncStorageCollectionRequest> pending = Collections.synchronizedSet(new HashSet<SyncStorageCollectionRequest>());
- /* @GuardedBy("this") */ private String lastModified;
- /* @GuardedBy("this") */ private long numRecords = 0;
-
- public BatchingDownloader(final Server11Repository repository, final Server11RepositorySession repositorySession) {
- this.repository = repository;
- this.repositorySession = repositorySession;
- }
-
- @VisibleForTesting
- protected static String flattenIDs(String[] guids) {
- // Consider using Utils.toDelimitedString if and when the signature changes
- // to Collection<String> guids.
- if (guids.length == 0) {
- return "";
- }
- if (guids.length == 1) {
- return guids[0];
- }
- // Assuming 12-char GUIDs. There should be a -1 in there, but we accumulate one comma too many.
- StringBuilder b = new StringBuilder(guids.length * 12 + guids.length);
- for (String guid : guids) {
- b.append(guid);
- b.append(",");
- }
- return b.substring(0, b.length() - 1);
- }
-
- @VisibleForTesting
- protected void fetchWithParameters(long newer,
- long batchLimit,
- boolean full,
- String sort,
- String ids,
- SyncStorageCollectionRequest request,
- RepositorySessionFetchRecordsDelegate fetchRecordsDelegate)
- throws URISyntaxException, UnsupportedEncodingException {
- if (batchLimit > repository.getDefaultTotalLimit()) {
- throw new IllegalArgumentException("Batch limit should not be greater than total limit");
- }
-
- request.delegate = new BatchingDownloaderDelegate(this, fetchRecordsDelegate, request,
- newer, batchLimit, full, sort, ids);
- this.pending.add(request);
- request.get();
- }
-
- @VisibleForTesting
- @Nullable
- protected String encodeParam(String param) throws UnsupportedEncodingException {
- if (param != null) {
- return URLEncoder.encode(param, "UTF-8");
- }
- return null;
- }
-
- @VisibleForTesting
- protected SyncStorageCollectionRequest makeSyncStorageCollectionRequest(long newer,
- long batchLimit,
- boolean full,
- String sort,
- String ids,
- String offset)
- throws URISyntaxException, UnsupportedEncodingException {
- URI collectionURI = repository.collectionURI(full, newer, batchLimit, sort, ids, encodeParam(offset));
- Logger.debug(LOG_TAG, collectionURI.toString());
-
- return new SyncStorageCollectionRequest(collectionURI);
- }
-
- public void fetchSince(long timestamp, RepositorySessionFetchRecordsDelegate fetchRecordsDelegate) {
- this.fetchSince(timestamp, null, fetchRecordsDelegate);
- }
-
- private void fetchSince(long timestamp, String offset,
- RepositorySessionFetchRecordsDelegate fetchRecordsDelegate) {
- long batchLimit = repository.getDefaultBatchLimit();
- String sort = repository.getDefaultSort();
-
- try {
- SyncStorageCollectionRequest request = makeSyncStorageCollectionRequest(timestamp,
- batchLimit, true, sort, null, offset);
- this.fetchWithParameters(timestamp, batchLimit, true, sort, null, request, fetchRecordsDelegate);
- } catch (URISyntaxException | UnsupportedEncodingException e) {
- fetchRecordsDelegate.onFetchFailed(e, null);
- }
- }
-
- public void fetch(String[] guids, RepositorySessionFetchRecordsDelegate fetchRecordsDelegate) {
- String ids = flattenIDs(guids);
- String index = "index";
-
- try {
- SyncStorageCollectionRequest request = makeSyncStorageCollectionRequest(
- -1, -1, true, index, ids, null);
- this.fetchWithParameters(-1, -1, true, index, ids, request, fetchRecordsDelegate);
- } catch (URISyntaxException | UnsupportedEncodingException e) {
- fetchRecordsDelegate.onFetchFailed(e, null);
- }
- }
-
- public Server11Repository getServerRepository() {
- return this.repository;
- }
-
- public void onFetchCompleted(SyncStorageResponse response,
- final RepositorySessionFetchRecordsDelegate fetchRecordsDelegate,
- final SyncStorageCollectionRequest request, long newer,
- long limit, boolean full, String sort, String ids) {
- removeRequestFromPending(request);
-
- // When we process our first request, we get back a X-Last-Modified header indicating when collection was modified last.
- // We pass it to the server with every subsequent request (if we need to make more) as the X-If-Unmodified-Since header,
- // and server is supposed to ensure that this pre-condition is met, and fail our request with a 412 error code otherwise.
- // So, if all of this happens, these checks should never fail.
- // However, we also track this header in client side, and can defensively validate against it here as well.
- final String currentLastModifiedTimestamp = response.lastModified();
- Logger.debug(LOG_TAG, "Last modified timestamp " + currentLastModifiedTimestamp);
-
- // Sanity check. We also did a null check in delegate before passing it into here.
- if (currentLastModifiedTimestamp == null) {
- this.abort(fetchRecordsDelegate, "Last modified timestamp is missing");
- return;
- }
-
- final boolean lastModifiedChanged;
- synchronized (this) {
- if (this.lastModified == null) {
- // First time seeing last modified timestamp.
- this.lastModified = currentLastModifiedTimestamp;
- }
- lastModifiedChanged = !this.lastModified.equals(currentLastModifiedTimestamp);
- }
-
- if (lastModifiedChanged) {
- this.abort(fetchRecordsDelegate, "Last modified timestamp has changed unexpectedly");
- return;
- }
-
- final boolean hasNotReachedLimit;
- synchronized (this) {
- this.numRecords += response.weaveRecords();
- hasNotReachedLimit = this.numRecords < repository.getDefaultTotalLimit();
- }
-
- final String offset = response.weaveOffset();
- final SyncStorageCollectionRequest newRequest;
- try {
- newRequest = makeSyncStorageCollectionRequest(newer,
- limit, full, sort, ids, offset);
- } catch (final URISyntaxException | UnsupportedEncodingException e) {
- this.workTracker.delayWorkItem(new Runnable() {
- @Override
- public void run() {
- Logger.debug(LOG_TAG, "Delayed onFetchCompleted running.");
- fetchRecordsDelegate.onFetchFailed(e, null);
- }
- });
- return;
- }
-
- if (offset != null && hasNotReachedLimit) {
- try {
- this.fetchWithParameters(newer, limit, full, sort, ids, newRequest, fetchRecordsDelegate);
- } catch (final URISyntaxException | UnsupportedEncodingException e) {
- this.workTracker.delayWorkItem(new Runnable() {
- @Override
- public void run() {
- Logger.debug(LOG_TAG, "Delayed onFetchCompleted running.");
- fetchRecordsDelegate.onFetchFailed(e, null);
- }
- });
- }
- return;
- }
-
- final long normalizedTimestamp = response.normalizedTimestampForHeader(SyncResponse.X_LAST_MODIFIED);
- Logger.debug(LOG_TAG, "Fetch completed. Timestamp is " + normalizedTimestamp);
-
- this.workTracker.delayWorkItem(new Runnable() {
- @Override
- public void run() {
- Logger.debug(LOG_TAG, "Delayed onFetchCompleted running.");
- fetchRecordsDelegate.onFetchCompleted(normalizedTimestamp);
- }
- });
- }
-
- public void onFetchFailed(final Exception ex,
- final RepositorySessionFetchRecordsDelegate fetchRecordsDelegate,
- final SyncStorageCollectionRequest request) {
- removeRequestFromPending(request);
- this.workTracker.delayWorkItem(new Runnable() {
- @Override
- public void run() {
- Logger.debug(LOG_TAG, "Running onFetchFailed.");
- fetchRecordsDelegate.onFetchFailed(ex, null);
- }
- });
- }
-
- public void onFetchedRecord(CryptoRecord record,
- RepositorySessionFetchRecordsDelegate fetchRecordsDelegate) {
- this.workTracker.incrementOutstanding();
- try {
- fetchRecordsDelegate.onFetchedRecord(record);
- } catch (Exception ex) {
- Logger.warn(LOG_TAG, "Got exception calling onFetchedRecord with WBO.", ex);
- throw new RuntimeException(ex);
- } finally {
- this.workTracker.decrementOutstanding();
- }
- }
-
- private void removeRequestFromPending(SyncStorageCollectionRequest request) {
- if (request == null) {
- return;
- }
- this.pending.remove(request);
- }
-
- @VisibleForTesting
- protected void abortRequests() {
- this.repositorySession.abort();
- synchronized (this.pending) {
- for (SyncStorageCollectionRequest request : this.pending) {
- request.abort();
- }
- this.pending.clear();
- }
- }
-
- @Nullable
- protected synchronized String getLastModified() {
- return this.lastModified;
- }
-
- private void abort(final RepositorySessionFetchRecordsDelegate delegate, final String msg) {
- Logger.error(LOG_TAG, msg);
- this.abortRequests();
- this.workTracker.delayWorkItem(new Runnable() {
- @Override
- public void run() {
- Logger.debug(LOG_TAG, "Delayed onFetchCompleted running.");
- delegate.onFetchFailed(
- new IllegalStateException(msg),
- null);
- }
- });
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/downloaders/BatchingDownloaderDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/downloaders/BatchingDownloaderDelegate.java
deleted file mode 100644
index eb9f76d6b..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/downloaders/BatchingDownloaderDelegate.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.downloaders;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.HTTPFailureException;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.net.SyncStorageCollectionRequest;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-import org.mozilla.gecko.sync.net.WBOCollectionRequestDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
-
-/**
- * Delegate that gets passed into fetch methods to handle server response from fetch.
- */
-public class BatchingDownloaderDelegate extends WBOCollectionRequestDelegate {
- public static final String LOG_TAG = "BatchingDownloaderDelegate";
-
- private BatchingDownloader downloader;
- private RepositorySessionFetchRecordsDelegate fetchRecordsDelegate;
- public SyncStorageCollectionRequest request;
- // Used to pass back to BatchDownloader to start another fetch with these parameters if needed.
- private long newer;
- private long batchLimit;
- private boolean full;
- private String sort;
- private String ids;
-
- public BatchingDownloaderDelegate(final BatchingDownloader downloader,
- final RepositorySessionFetchRecordsDelegate fetchRecordsDelegate,
- final SyncStorageCollectionRequest request, long newer,
- long batchLimit, boolean full, String sort, String ids) {
- this.downloader = downloader;
- this.fetchRecordsDelegate = fetchRecordsDelegate;
- this.request = request;
- this.newer = newer;
- this.batchLimit = batchLimit;
- this.full = full;
- this.sort = sort;
- this.ids = ids;
- }
-
- @Override
- public AuthHeaderProvider getAuthHeaderProvider() {
- return this.downloader.getServerRepository().getAuthHeaderProvider();
- }
-
- @Override
- public String ifUnmodifiedSince() {
- return this.downloader.getLastModified();
- }
-
- @Override
- public void handleRequestSuccess(SyncStorageResponse response) {
- Logger.debug(LOG_TAG, "Fetch done.");
- if (response.lastModified() != null) {
- this.downloader.onFetchCompleted(response, this.fetchRecordsDelegate, this.request,
- this.newer, this.batchLimit, this.full, this.sort, this.ids);
- return;
- }
- this.downloader.onFetchFailed(
- new IllegalStateException("Missing last modified header from response"),
- this.fetchRecordsDelegate,
- this.request);
- }
-
- @Override
- public void handleRequestFailure(SyncStorageResponse response) {
- this.handleRequestError(new HTTPFailureException(response));
- }
-
- @Override
- public void handleRequestError(final Exception ex) {
- Logger.warn(LOG_TAG, "Got request error.", ex);
- this.downloader.onFetchFailed(ex, this.fetchRecordsDelegate, this.request);
- }
-
- @Override
- public void handleWBO(CryptoRecord record) {
- this.downloader.onFetchedRecord(record, this.fetchRecordsDelegate);
- }
-
- @Override
- public KeyBundle keyBundle() {
- return null;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/BatchMeta.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/BatchMeta.java
deleted file mode 100644
index 951588586..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/BatchMeta.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.uploaders;
-
-import android.support.annotation.CheckResult;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import org.mozilla.gecko.background.common.log.Logger;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.mozilla.gecko.sync.repositories.uploaders.BatchingUploader.TokenModifiedException;
-import org.mozilla.gecko.sync.repositories.uploaders.BatchingUploader.LastModifiedChangedUnexpectedly;
-import org.mozilla.gecko.sync.repositories.uploaders.BatchingUploader.LastModifiedDidNotChange;
-
-/**
- * Keeps track of token, Last-Modified value and GUIDs of succeeded records.
- */
-/* @ThreadSafe */
-public class BatchMeta extends BufferSizeTracker {
- private static final String LOG_TAG = "BatchMeta";
-
- // Will be set once first payload upload succeeds. We don't expect this to change until we
- // commit the batch, and which point it must change.
- /* @GuardedBy("this") */ private Long lastModified;
-
- // Will be set once first payload upload succeeds. We don't expect this to ever change until
- // a commit succeeds, at which point this gets set to null.
- /* @GuardedBy("this") */ private String token;
-
- /* @GuardedBy("accessLock") */ private boolean isUnlimited = false;
-
- // Accessed by synchronously running threads.
- /* @GuardedBy("accessLock") */ private final List<String> successRecordGuids = new ArrayList<>();
-
- /* @GuardedBy("accessLock") */ private boolean needsCommit = false;
-
- protected final Long collectionLastModified;
-
- public BatchMeta(@NonNull Object payloadLock, long maxBytes, long maxRecords, @Nullable Long collectionLastModified) {
- super(payloadLock, maxBytes, maxRecords);
- this.collectionLastModified = collectionLastModified;
- }
-
- protected void setIsUnlimited(boolean isUnlimited) {
- synchronized (accessLock) {
- this.isUnlimited = isUnlimited;
- }
- }
-
- @Override
- protected boolean canFit(long recordDeltaByteCount) {
- synchronized (accessLock) {
- return isUnlimited || super.canFit(recordDeltaByteCount);
- }
- }
-
- @Override
- @CheckResult
- protected boolean addAndEstimateIfFull(long recordDeltaByteCount) {
- synchronized (accessLock) {
- needsCommit = true;
- boolean isFull = super.addAndEstimateIfFull(recordDeltaByteCount);
- return !isUnlimited && isFull;
- }
- }
-
- protected boolean needToCommit() {
- synchronized (accessLock) {
- return needsCommit;
- }
- }
-
- protected synchronized String getToken() {
- return token;
- }
-
- protected synchronized void setToken(final String newToken, boolean isCommit) throws TokenModifiedException {
- // Set token once in a batching mode.
- // In a non-batching mode, this.token and newToken will be null, and this is a no-op.
- if (token == null) {
- token = newToken;
- return;
- }
-
- // Sanity checks.
- if (isCommit) {
- // We expect token to be null when commit payload succeeds.
- if (newToken != null) {
- throw new TokenModifiedException();
- } else {
- token = null;
- }
- return;
- }
-
- // We expect new token to always equal current token for non-commit payloads.
- if (!token.equals(newToken)) {
- throw new TokenModifiedException();
- }
- }
-
- protected synchronized Long getLastModified() {
- if (lastModified == null) {
- return collectionLastModified;
- }
- return lastModified;
- }
-
- protected synchronized void setLastModified(final Long newLastModified, final boolean expectedToChange) throws LastModifiedChangedUnexpectedly, LastModifiedDidNotChange {
- if (lastModified == null) {
- lastModified = newLastModified;
- return;
- }
-
- if (!expectedToChange && !lastModified.equals(newLastModified)) {
- Logger.debug(LOG_TAG, "Last-Modified timestamp changed when we didn't expect it");
- throw new LastModifiedChangedUnexpectedly();
-
- } else if (expectedToChange && lastModified.equals(newLastModified)) {
- Logger.debug(LOG_TAG, "Last-Modified timestamp did not change when we expected it to");
- throw new LastModifiedDidNotChange();
-
- } else {
- lastModified = newLastModified;
- }
- }
-
- protected ArrayList<String> getSuccessRecordGuids() {
- synchronized (accessLock) {
- return new ArrayList<>(this.successRecordGuids);
- }
- }
-
- protected void recordSucceeded(final String recordGuid) {
- // Sanity check.
- if (recordGuid == null) {
- throw new IllegalStateException();
- }
-
- synchronized (accessLock) {
- successRecordGuids.add(recordGuid);
- }
- }
-
- @Override
- protected boolean canFitRecordByteDelta(long byteDelta, long recordCount, long byteCount) {
- return isUnlimited || super.canFitRecordByteDelta(byteDelta, recordCount, byteCount);
- }
-
- @Override
- protected void reset() {
- synchronized (accessLock) {
- super.reset();
- token = null;
- lastModified = null;
- successRecordGuids.clear();
- needsCommit = false;
- }
- }
-} \ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/BatchingUploader.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/BatchingUploader.java
deleted file mode 100644
index 26efbd136..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/BatchingUploader.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.uploaders;
-
-import android.net.Uri;
-import android.support.annotation.VisibleForTesting;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.InfoConfiguration;
-import org.mozilla.gecko.sync.Server11RecordPostFailedException;
-import org.mozilla.gecko.sync.net.SyncResponse;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-import org.mozilla.gecko.sync.repositories.Server11RepositorySession;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-import java.util.ArrayList;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicLong;
-
-/**
- * Uploader which implements batching introduced in Sync 1.5.
- *
- * Batch vs payload terminology:
- * - batch is comprised of a series of payloads, which are all committed at the same time.
- * -- identified via a "batch token", which is returned after first payload for the batch has been uploaded.
- * - payload is a collection of records which are uploaded together. Associated with a batch.
- * -- last payload, identified via commit=true, commits the batch.
- *
- * Limits for how many records can fit into a payload and into a batch are defined in the passed-in
- * InfoConfiguration object.
- *
- * If we can't fit everything we'd like to upload into one batch (according to max-total-* limits),
- * then we commit that batch, and start a new one. There are no explicit limits on total number of
- * batches we might use, although at some point we'll start to run into storage limit errors from the API.
- *
- * Once we go past using one batch this uploader is no longer "atomic". Partial state is exposed
- * to other clients after our first batch is committed and before our last batch is committed.
- * However, our per-batch limits are high, X-I-U-S mechanics help protect downloading clients
- * (as long as they implement X-I-U-S) with 412 error codes in case of interleaving upload and download,
- * and most mobile clients will not be uploading large-enough amounts of data (especially structured
- * data, such as bookmarks).
- *
- * Last-Modified header returned with the first batch payload POST success is maintained for a batch,
- * to guard against concurrent-modification errors (different uploader commits before we're done).
- *
- * Non-batching mode notes:
- * We also support Sync servers which don't enable batching for uploads. In this case, we respect
- * payload limits for individual uploads, and every upload is considered a commit. Batching limits
- * do not apply, and batch token is irrelevant.
- * We do keep track of Last-Modified and send along X-I-U-S with our uploads, to protect against
- * concurrent modifications by other clients.
- */
-public class BatchingUploader {
- private static final String LOG_TAG = "BatchingUploader";
-
- private final Uri collectionUri;
-
- private volatile boolean recordUploadFailed = false;
-
- private final BatchMeta batchMeta;
- private final Payload payload;
-
- // Accessed by synchronously running threads, OK to not synchronize and just make it volatile.
- private volatile Boolean inBatchingMode;
-
- // Used to ensure we have thread-safe access to the following:
- // - byte and record counts in both Payload and BatchMeta objects
- // - buffers in the Payload object
- private final Object payloadLock = new Object();
-
- protected Executor workQueue;
- protected final RepositorySessionStoreDelegate sessionStoreDelegate;
- protected final Server11RepositorySession repositorySession;
-
- protected AtomicLong uploadTimestamp = new AtomicLong(0);
-
- protected static final int PER_RECORD_OVERHEAD_BYTE_COUNT = RecordUploadRunnable.RECORD_SEPARATOR.length;
- protected static final int PER_PAYLOAD_OVERHEAD_BYTE_COUNT = RecordUploadRunnable.RECORDS_END.length;
-
- // Sanity check. RECORD_SEPARATOR and RECORD_START are assumed to be of the same length.
- static {
- if (RecordUploadRunnable.RECORD_SEPARATOR.length != RecordUploadRunnable.RECORDS_START.length) {
- throw new IllegalStateException("Separator and start tokens must be of the same length");
- }
- }
-
- public BatchingUploader(final Server11RepositorySession repositorySession, final Executor workQueue, final RepositorySessionStoreDelegate sessionStoreDelegate) {
- this.repositorySession = repositorySession;
- this.workQueue = workQueue;
- this.sessionStoreDelegate = sessionStoreDelegate;
- this.collectionUri = Uri.parse(repositorySession.getServerRepository().collectionURI().toString());
-
- InfoConfiguration config = repositorySession.getServerRepository().getInfoConfiguration();
- this.batchMeta = new BatchMeta(
- payloadLock, config.maxTotalBytes, config.maxTotalRecords,
- repositorySession.getServerRepository().getCollectionLastModified()
- );
- this.payload = new Payload(payloadLock, config.maxPostBytes, config.maxPostRecords);
- }
-
- public void process(final Record record) {
- final String guid = record.guid;
- final byte[] recordBytes = record.toJSONBytes();
- final long recordDeltaByteCount = recordBytes.length + PER_RECORD_OVERHEAD_BYTE_COUNT;
-
- Logger.debug(LOG_TAG, "Processing a record with guid: " + guid);
-
- // We can't upload individual records which exceed our payload byte limit.
- if ((recordDeltaByteCount + PER_PAYLOAD_OVERHEAD_BYTE_COUNT) > payload.maxBytes) {
- sessionStoreDelegate.onRecordStoreFailed(new RecordTooLargeToUpload(), guid);
- return;
- }
-
- synchronized (payloadLock) {
- final boolean canFitRecordIntoBatch = batchMeta.canFit(recordDeltaByteCount);
- final boolean canFitRecordIntoPayload = payload.canFit(recordDeltaByteCount);
-
- // Record fits!
- if (canFitRecordIntoBatch && canFitRecordIntoPayload) {
- Logger.debug(LOG_TAG, "Record fits into the current batch and payload");
- addAndFlushIfNecessary(recordDeltaByteCount, recordBytes, guid);
-
- // Payload won't fit the record.
- } else if (canFitRecordIntoBatch) {
- Logger.debug(LOG_TAG, "Current payload won't fit incoming record, uploading payload.");
- flush(false, false);
-
- Logger.debug(LOG_TAG, "Recording the incoming record into a new payload");
-
- // Keep track of the overflow record.
- addAndFlushIfNecessary(recordDeltaByteCount, recordBytes, guid);
-
- // Batch won't fit the record.
- } else {
- Logger.debug(LOG_TAG, "Current batch won't fit incoming record, committing batch.");
- flush(true, false);
-
- Logger.debug(LOG_TAG, "Recording the incoming record into a new batch");
- batchMeta.reset();
-
- // Keep track of the overflow record.
- addAndFlushIfNecessary(recordDeltaByteCount, recordBytes, guid);
- }
- }
- }
-
- // Convenience function used from the process method; caller must hold a payloadLock.
- private void addAndFlushIfNecessary(long byteCount, byte[] recordBytes, String guid) {
- boolean isPayloadFull = payload.addAndEstimateIfFull(byteCount, recordBytes, guid);
- boolean isBatchFull = batchMeta.addAndEstimateIfFull(byteCount);
-
- // Preemptive commit batch or upload a payload if they're estimated to be full.
- if (isBatchFull) {
- flush(true, false);
- batchMeta.reset();
- } else if (isPayloadFull) {
- flush(false, false);
- }
- }
-
- public void noMoreRecordsToUpload() {
- Logger.debug(LOG_TAG, "Received 'no more records to upload' signal.");
-
- // Run this after the last payload succeeds, so that we know for sure if we're in a batching
- // mode and need to commit with a potentially empty payload.
- workQueue.execute(new Runnable() {
- @Override
- public void run() {
- commitIfNecessaryAfterLastPayload();
- }
- });
- }
-
- @VisibleForTesting
- protected void commitIfNecessaryAfterLastPayload() {
- // Must be called after last payload upload finishes.
- synchronized (payload) {
- // If we have any pending records in the Payload, flush them!
- if (!payload.isEmpty()) {
- flush(true, true);
-
- // If we have an empty payload but need to commit the batch in the batching mode, flush!
- } else if (batchMeta.needToCommit() && Boolean.TRUE.equals(inBatchingMode)) {
- flush(true, true);
-
- // Otherwise, we're done.
- } else {
- finished(uploadTimestamp);
- }
- }
- }
-
- /**
- * We've been told by our upload delegate that a payload succeeded.
- * Depending on the type of payload and batch mode status, inform our delegate of progress.
- *
- * @param response success response to our commit post
- * @param isCommit was this a commit upload?
- * @param isLastPayload was this a very last payload we'll upload?
- */
- public void payloadSucceeded(final SyncStorageResponse response, final boolean isCommit, final boolean isLastPayload) {
- // Sanity check.
- if (inBatchingMode == null) {
- throw new IllegalStateException("Can't process payload success until we know if we're in a batching mode");
- }
-
- // We consider records to have been committed if we're not in a batching mode or this was a commit.
- // If records have been committed, notify our store delegate.
- if (!inBatchingMode || isCommit) {
- for (String guid : batchMeta.getSuccessRecordGuids()) {
- sessionStoreDelegate.onRecordStoreSucceeded(guid);
- }
- }
-
- // If this was our very last commit, we're done storing records.
- // Get Last-Modified timestamp from the response, and pass it upstream.
- if (isLastPayload) {
- finished(response.normalizedTimestampForHeader(SyncResponse.X_LAST_MODIFIED));
- }
- }
-
- public void lastPayloadFailed() {
- finished(uploadTimestamp);
- }
-
- private void finished(long lastModifiedTimestamp) {
- bumpTimestampTo(uploadTimestamp, lastModifiedTimestamp);
- finished(uploadTimestamp);
- }
-
- private void finished(AtomicLong lastModifiedTimestamp) {
- repositorySession.storeDone(lastModifiedTimestamp.get());
- }
-
- public BatchMeta getCurrentBatch() {
- return batchMeta;
- }
-
- public void setInBatchingMode(boolean inBatchingMode) {
- this.inBatchingMode = inBatchingMode;
-
- // If we know for sure that we're not in a batching mode,
- // consider our batch to be of unlimited size.
- this.batchMeta.setIsUnlimited(!inBatchingMode);
- }
-
- public Boolean getInBatchingMode() {
- return inBatchingMode;
- }
-
- public void setLastModified(final Long lastModified, final boolean isCommit) throws BatchingUploaderException {
- // Sanity check.
- if (inBatchingMode == null) {
- throw new IllegalStateException("Can't process Last-Modified before we know we're in a batching mode.");
- }
-
- // In non-batching mode, every time we receive a Last-Modified timestamp, we expect it to change
- // since records are "committed" (become visible to other clients) on every payload.
- // In batching mode, we only expect Last-Modified to change when we commit a batch.
- batchMeta.setLastModified(lastModified, isCommit || !inBatchingMode);
- }
-
- public void recordSucceeded(final String recordGuid) {
- Logger.debug(LOG_TAG, "Record store succeeded: " + recordGuid);
- batchMeta.recordSucceeded(recordGuid);
- }
-
- public void recordFailed(final String recordGuid) {
- recordFailed(new Server11RecordPostFailedException(), recordGuid);
- }
-
- public void recordFailed(final Exception e, final String recordGuid) {
- Logger.debug(LOG_TAG, "Record store failed for guid " + recordGuid + " with exception: " + e.toString());
- recordUploadFailed = true;
- sessionStoreDelegate.onRecordStoreFailed(e, recordGuid);
- }
-
- public Server11RepositorySession getRepositorySession() {
- return repositorySession;
- }
-
- private static void bumpTimestampTo(final AtomicLong current, long newValue) {
- while (true) {
- long existing = current.get();
- if (existing > newValue) {
- return;
- }
- if (current.compareAndSet(existing, newValue)) {
- return;
- }
- }
- }
-
- private void flush(final boolean isCommit, final boolean isLastPayload) {
- final ArrayList<byte[]> outgoing;
- final ArrayList<String> outgoingGuids;
- final long byteCount;
-
- // Even though payload object itself is thread-safe, we want to ensure we get these altogether
- // as a "unit". Another approach would be to create a wrapper object for these values, but this works.
- synchronized (payloadLock) {
- outgoing = payload.getRecordsBuffer();
- outgoingGuids = payload.getRecordGuidsBuffer();
- byteCount = payload.getByteCount();
- }
-
- workQueue.execute(new RecordUploadRunnable(
- new BatchingAtomicUploaderMayUploadProvider(),
- collectionUri,
- batchMeta,
- new PayloadUploadDelegate(this, outgoingGuids, isCommit, isLastPayload),
- outgoing,
- byteCount,
- isCommit
- ));
-
- payload.reset();
- }
-
- private class BatchingAtomicUploaderMayUploadProvider implements MayUploadProvider {
- public boolean mayUpload() {
- return !recordUploadFailed;
- }
- }
-
- public static class BatchingUploaderException extends Exception {
- private static final long serialVersionUID = 1L;
- }
- public static class RecordTooLargeToUpload extends BatchingUploaderException {
- private static final long serialVersionUID = 1L;
- }
- public static class LastModifiedDidNotChange extends BatchingUploaderException {
- private static final long serialVersionUID = 1L;
- }
- public static class LastModifiedChangedUnexpectedly extends BatchingUploaderException {
- private static final long serialVersionUID = 1L;
- }
- public static class TokenModifiedException extends BatchingUploaderException {
- private static final long serialVersionUID = 1L;
- };
-} \ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/BufferSizeTracker.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/BufferSizeTracker.java
deleted file mode 100644
index 7f4c305f3..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/BufferSizeTracker.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.uploaders;
-
-import android.support.annotation.CallSuper;
-import android.support.annotation.CheckResult;
-
-/**
- * Implements functionality shared by BatchMeta and Payload objects, namely:
- * - keeping track of byte and record counts
- * - incrementing those counts when records are added
- * - checking if a record can fit
- */
-/* @ThreadSafe */
-public abstract class BufferSizeTracker {
- protected final Object accessLock;
-
- /* @GuardedBy("accessLock") */ private long byteCount = BatchingUploader.PER_PAYLOAD_OVERHEAD_BYTE_COUNT;
- /* @GuardedBy("accessLock") */ private long recordCount = 0;
- /* @GuardedBy("accessLock") */ protected Long smallestRecordByteCount;
-
- protected final long maxBytes;
- protected final long maxRecords;
-
- public BufferSizeTracker(Object accessLock, long maxBytes, long maxRecords) {
- this.accessLock = accessLock;
- this.maxBytes = maxBytes;
- this.maxRecords = maxRecords;
- }
-
- @CallSuper
- protected boolean canFit(long recordDeltaByteCount) {
- synchronized (accessLock) {
- return canFitRecordByteDelta(recordDeltaByteCount, recordCount, byteCount);
- }
- }
-
- protected boolean isEmpty() {
- synchronized (accessLock) {
- return recordCount == 0;
- }
- }
-
- /**
- * Adds a record and returns a boolean indicating whether batch is estimated to be full afterwards.
- */
- @CheckResult
- protected boolean addAndEstimateIfFull(long recordDeltaByteCount) {
- synchronized (accessLock) {
- // Sanity check. Calling this method when buffer won't fit the record is an error.
- if (!canFitRecordByteDelta(recordDeltaByteCount, recordCount, byteCount)) {
- throw new IllegalStateException("Buffer size exceeded");
- }
-
- byteCount += recordDeltaByteCount;
- recordCount += 1;
-
- if (smallestRecordByteCount == null || smallestRecordByteCount > recordDeltaByteCount) {
- smallestRecordByteCount = recordDeltaByteCount;
- }
-
- // See if we're full or nearly full after adding a record.
- // We're halving smallestRecordByteCount because we're erring
- // on the side of "can hopefully fit". We're trying to upload as soon as we know we
- // should, but we also need to be mindful of minimizing total number of uploads we make.
- return !canFitRecordByteDelta(smallestRecordByteCount / 2, recordCount, byteCount);
- }
- }
-
- protected long getByteCount() {
- synchronized (accessLock) {
- // Ensure we account for payload overhead twice when the batch is empty.
- // Payload overhead is either RECORDS_START ("[") or RECORDS_END ("]"),
- // and for an empty payload we need account for both ("[]").
- if (recordCount == 0) {
- return byteCount + BatchingUploader.PER_PAYLOAD_OVERHEAD_BYTE_COUNT;
- }
- return byteCount;
- }
- }
-
- protected long getRecordCount() {
- synchronized (accessLock) {
- return recordCount;
- }
- }
-
- @CallSuper
- protected void reset() {
- synchronized (accessLock) {
- byteCount = BatchingUploader.PER_PAYLOAD_OVERHEAD_BYTE_COUNT;
- recordCount = 0;
- }
- }
-
- @CallSuper
- protected boolean canFitRecordByteDelta(long byteDelta, long recordCount, long byteCount) {
- return recordCount < maxRecords
- && (byteCount + byteDelta) <= maxBytes;
- }
-} \ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/MayUploadProvider.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/MayUploadProvider.java
deleted file mode 100644
index a1994cf62..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/MayUploadProvider.java
+++ /dev/null
@@ -1,9 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.uploaders;
-
-public interface MayUploadProvider {
- boolean mayUpload();
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/Payload.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/Payload.java
deleted file mode 100644
index 1ed9b5798..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/Payload.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.uploaders;
-
-import android.support.annotation.CheckResult;
-
-import java.util.ArrayList;
-
-/**
- * Owns per-payload record byte and recordGuid buffers.
- */
-/* @ThreadSafe */
-public class Payload extends BufferSizeTracker {
- // Data of outbound records.
- /* @GuardedBy("accessLock") */ private final ArrayList<byte[]> recordsBuffer = new ArrayList<>();
-
- // GUIDs of outbound records. Used to fail entire payloads.
- /* @GuardedBy("accessLock") */ private final ArrayList<String> recordGuidsBuffer = new ArrayList<>();
-
- public Payload(Object payloadLock, long maxBytes, long maxRecords) {
- super(payloadLock, maxBytes, maxRecords);
- }
-
- @Override
- protected boolean addAndEstimateIfFull(long recordDelta) {
- throw new UnsupportedOperationException();
- }
-
- @CheckResult
- protected boolean addAndEstimateIfFull(long recordDelta, byte[] recordBytes, String guid) {
- synchronized (accessLock) {
- recordsBuffer.add(recordBytes);
- recordGuidsBuffer.add(guid);
- return super.addAndEstimateIfFull(recordDelta);
- }
- }
-
- @Override
- protected void reset() {
- synchronized (accessLock) {
- super.reset();
- recordsBuffer.clear();
- recordGuidsBuffer.clear();
- }
- }
-
- protected ArrayList<byte[]> getRecordsBuffer() {
- synchronized (accessLock) {
- return new ArrayList<>(recordsBuffer);
- }
- }
-
- protected ArrayList<String> getRecordGuidsBuffer() {
- synchronized (accessLock) {
- return new ArrayList<>(recordGuidsBuffer);
- }
- }
-
- protected boolean isEmpty() {
- synchronized (accessLock) {
- return recordsBuffer.isEmpty();
- }
- }
-} \ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/PayloadUploadDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/PayloadUploadDelegate.java
deleted file mode 100644
index e8bbb7df6..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/PayloadUploadDelegate.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.uploaders;
-
-import org.json.simple.JSONArray;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.HTTPFailureException;
-import org.mozilla.gecko.sync.NonArrayJSONException;
-import org.mozilla.gecko.sync.NonObjectJSONException;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.net.SyncResponse;
-import org.mozilla.gecko.sync.net.SyncStorageRequestDelegate;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-
-import java.util.ArrayList;
-
-public class PayloadUploadDelegate implements SyncStorageRequestDelegate {
- private static final String LOG_TAG = "PayloadUploadDelegate";
-
- private static final String KEY_BATCH = "batch";
-
- private final BatchingUploader uploader;
- private ArrayList<String> postedRecordGuids;
- private final boolean isCommit;
- private final boolean isLastPayload;
-
- public PayloadUploadDelegate(BatchingUploader uploader, ArrayList<String> postedRecordGuids, boolean isCommit, boolean isLastPayload) {
- this.uploader = uploader;
- this.postedRecordGuids = postedRecordGuids;
- this.isCommit = isCommit;
- this.isLastPayload = isLastPayload;
- }
-
- @Override
- public AuthHeaderProvider getAuthHeaderProvider() {
- return uploader.getRepositorySession().getServerRepository().getAuthHeaderProvider();
- }
-
- @Override
- public String ifUnmodifiedSince() {
- final Long lastModified = uploader.getCurrentBatch().getLastModified();
- if (lastModified == null) {
- return null;
- }
- return Utils.millisecondsToDecimalSecondsString(lastModified);
- }
-
- @Override
- public void handleRequestSuccess(final SyncStorageResponse response) {
- // First, do some sanity checking.
- if (response.getStatusCode() != 200 && response.getStatusCode() != 202) {
- handleRequestError(
- new IllegalStateException("handleRequestSuccess received a non-200/202 response: " + response.getStatusCode())
- );
- return;
- }
-
- // We always expect to see a Last-Modified header. It's returned with every success response.
- if (!response.httpResponse().containsHeader(SyncResponse.X_LAST_MODIFIED)) {
- handleRequestError(
- new IllegalStateException("Response did not have a Last-Modified header")
- );
- return;
- }
-
- // We expect to be able to parse the response as a JSON object.
- final ExtendedJSONObject body;
- try {
- body = response.jsonObjectBody(); // jsonObjectBody() throws or returns non-null.
- } catch (Exception e) {
- Logger.error(LOG_TAG, "Got exception parsing POST success body.", e);
- this.handleRequestError(e);
- return;
- }
-
- // If we got a 200, it could be either a non-batching result, or a batch commit.
- // - if we're in a batching mode, we expect this to be a commit.
- // If we got a 202, we expect there to be a token present in the response
- if (response.getStatusCode() == 200 && uploader.getCurrentBatch().getToken() != null) {
- if (uploader.getInBatchingMode() && !isCommit) {
- handleRequestError(
- new IllegalStateException("Got 200 OK in batching mode, but this was not a commit payload")
- );
- return;
- }
- } else if (response.getStatusCode() == 202) {
- if (!body.containsKey(KEY_BATCH)) {
- handleRequestError(
- new IllegalStateException("Batch response did not have a batch ID")
- );
- return;
- }
- }
-
- // With sanity checks out of the way, can now safely say if we're in a batching mode or not.
- // We only do this once per session.
- if (uploader.getInBatchingMode() == null) {
- uploader.setInBatchingMode(body.containsKey(KEY_BATCH));
- }
-
- // Tell current batch about the token we've received.
- // Throws if token changed after being set once, or if we got a non-null token after a commit.
- try {
- uploader.getCurrentBatch().setToken(body.getString(KEY_BATCH), isCommit);
- } catch (BatchingUploader.BatchingUploaderException e) {
- handleRequestError(e);
- return;
- }
-
- // Will throw if Last-Modified changed when it shouldn't have.
- try {
- uploader.setLastModified(
- response.normalizedTimestampForHeader(SyncResponse.X_LAST_MODIFIED),
- isCommit);
- } catch (BatchingUploader.BatchingUploaderException e) {
- handleRequestError(e);
- return;
- }
-
- // All looks good up to this point, let's process success and failed arrays.
- JSONArray success;
- try {
- success = body.getArray("success");
- } catch (NonArrayJSONException e) {
- handleRequestError(e);
- return;
- }
-
- if (success != null && !success.isEmpty()) {
- Logger.trace(LOG_TAG, "Successful records: " + success.toString());
- for (Object o : success) {
- try {
- uploader.recordSucceeded((String) o);
- } catch (ClassCastException e) {
- Logger.error(LOG_TAG, "Got exception parsing POST success guid.", e);
- // Not much to be done.
- }
- }
- }
- // GC
- success = null;
-
- ExtendedJSONObject failed;
- try {
- failed = body.getObject("failed");
- } catch (NonObjectJSONException e) {
- handleRequestError(e);
- return;
- }
-
- if (failed != null && !failed.object.isEmpty()) {
- Logger.debug(LOG_TAG, "Failed records: " + failed.object.toString());
- for (String guid : failed.keySet()) {
- uploader.recordFailed(guid);
- }
- }
- // GC
- failed = null;
-
- // And we're done! Let uploader finish up.
- uploader.payloadSucceeded(response, isCommit, isLastPayload);
- }
-
- @Override
- public void handleRequestFailure(final SyncStorageResponse response) {
- this.handleRequestError(new HTTPFailureException(response));
- }
-
- @Override
- public void handleRequestError(Exception e) {
- for (String guid : postedRecordGuids) {
- uploader.recordFailed(e, guid);
- }
- // GC
- postedRecordGuids = null;
-
- if (isLastPayload) {
- uploader.lastPayloadFailed();
- }
- }
-} \ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/RecordUploadRunnable.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/RecordUploadRunnable.java
deleted file mode 100644
index ce2955102..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/repositories/uploaders/RecordUploadRunnable.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.repositories.uploaders;
-
-import android.net.Uri;
-import android.support.annotation.VisibleForTesting;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.Server11PreviousPostFailedException;
-import org.mozilla.gecko.sync.net.SyncStorageRequest;
-import org.mozilla.gecko.sync.net.SyncStorageRequestDelegate;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-
-import ch.boye.httpclientandroidlib.entity.ContentProducer;
-import ch.boye.httpclientandroidlib.entity.EntityTemplate;
-
-/**
- * Responsible for creating and posting a <code>SyncStorageRequest</code> request object.
- */
-public class RecordUploadRunnable implements Runnable {
- public final String LOG_TAG = "RecordUploadRunnable";
-
- public final static byte[] RECORDS_START = { 91 }; // [ in UTF-8
- public final static byte[] RECORD_SEPARATOR = { 44 }; // , in UTF-8
- public final static byte[] RECORDS_END = { 93 }; // ] in UTF-8
-
- private static final String QUERY_PARAM_BATCH = "batch";
- private static final String QUERY_PARAM_TRUE = "true";
- private static final String QUERY_PARAM_BATCH_COMMIT = "commit";
-
- private final MayUploadProvider mayUploadProvider;
- private final SyncStorageRequestDelegate uploadDelegate;
-
- private final ArrayList<byte[]> outgoing;
- private final long byteCount;
-
- // Used to construct POST URI during run().
- @VisibleForTesting
- public final boolean isCommit;
- private final Uri collectionUri;
- private final BatchMeta batchMeta;
-
- public RecordUploadRunnable(MayUploadProvider mayUploadProvider,
- Uri collectionUri,
- BatchMeta batchMeta,
- SyncStorageRequestDelegate uploadDelegate,
- ArrayList<byte[]> outgoing,
- long byteCount,
- boolean isCommit) {
- this.mayUploadProvider = mayUploadProvider;
- this.uploadDelegate = uploadDelegate;
- this.outgoing = outgoing;
- this.byteCount = byteCount;
- this.batchMeta = batchMeta;
- this.collectionUri = collectionUri;
- this.isCommit = isCommit;
- }
-
- public static class ByteArraysContentProducer implements ContentProducer {
- ArrayList<byte[]> outgoing;
- public ByteArraysContentProducer(ArrayList<byte[]> arrays) {
- outgoing = arrays;
- }
-
- @Override
- public void writeTo(OutputStream outstream) throws IOException {
- int count = outgoing.size();
- outstream.write(RECORDS_START);
- if (count > 0) {
- outstream.write(outgoing.get(0));
- for (int i = 1; i < count; ++i) {
- outstream.write(RECORD_SEPARATOR);
- outstream.write(outgoing.get(i));
- }
- }
- outstream.write(RECORDS_END);
- }
-
- public static long outgoingBytesCount(ArrayList<byte[]> outgoing) {
- final long numberOfRecords = outgoing.size();
-
- // Account for start and end tokens.
- long count = RECORDS_START.length + RECORDS_END.length;
-
- // Account for all the records.
- for (int i = 0; i < numberOfRecords; i++) {
- count += outgoing.get(i).length;
- }
-
- // Account for a separator between the records.
- // There's one less separator than there are records.
- if (numberOfRecords > 1) {
- count += RECORD_SEPARATOR.length * (numberOfRecords - 1);
- }
-
- return count;
- }
- }
-
- public static class ByteArraysEntity extends EntityTemplate {
- private final long count;
- public ByteArraysEntity(ArrayList<byte[]> arrays, long totalBytes) {
- super(new ByteArraysContentProducer(arrays));
- this.count = totalBytes;
- this.setContentType("application/json");
- // charset is set in BaseResource.
-
- // Sanity check our byte counts.
- long realByteCount = ByteArraysContentProducer.outgoingBytesCount(arrays);
- if (realByteCount != totalBytes) {
- throw new IllegalStateException("Mismatched byte counts. Received " + totalBytes + " while real byte count is " + realByteCount);
- }
- }
-
- @Override
- public long getContentLength() {
- return count;
- }
-
- @Override
- public boolean isRepeatable() {
- return true;
- }
- }
-
- @Override
- public void run() {
- if (!mayUploadProvider.mayUpload()) {
- Logger.info(LOG_TAG, "Told not to proceed by the uploader. Cancelling upload, failing records.");
- uploadDelegate.handleRequestError(new Server11PreviousPostFailedException());
- return;
- }
-
- Logger.trace(LOG_TAG, "Running upload task. Outgoing records: " + outgoing.size());
-
- // We don't want the task queue to proceed until this request completes.
- // Fortunately, BaseResource is currently synchronous.
- // If that ever changes, you'll need to block here.
-
- final URI postURI = buildPostURI(isCommit, batchMeta, collectionUri);
- final SyncStorageRequest request = new SyncStorageRequest(postURI);
- request.delegate = uploadDelegate;
-
- ByteArraysEntity body = new ByteArraysEntity(outgoing, byteCount);
- request.post(body);
- }
-
- @VisibleForTesting
- public static URI buildPostURI(boolean isCommit, BatchMeta batchMeta, Uri collectionUri) {
- final Uri.Builder uriBuilder = collectionUri.buildUpon();
- final String batchToken = batchMeta.getToken();
-
- if (batchToken != null) {
- uriBuilder.appendQueryParameter(QUERY_PARAM_BATCH, batchToken);
- } else {
- uriBuilder.appendQueryParameter(QUERY_PARAM_BATCH, QUERY_PARAM_TRUE);
- }
-
- if (isCommit) {
- uriBuilder.appendQueryParameter(QUERY_PARAM_BATCH_COMMIT, QUERY_PARAM_TRUE);
- }
-
- try {
- return new URI(uriBuilder.build().toString());
- } catch (URISyntaxException e) {
- throw new IllegalStateException("Failed to construct a collection URI", e);
- }
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/Constants.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/Constants.java
deleted file mode 100644
index 66e6768b4..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/Constants.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.setup;
-
-public class Constants {
- public static final String DEFAULT_PROFILE = "default";
-
- /**
- * Key in sync extras bundle specifying stages to sync this sync session.
- * <p>
- * Corresponding value should be a String JSON-encoding an object, the keys of
- * which are the stage names to sync. For example:
- * <code>"{ \"stageToSync\": 0 }"</code>.
- */
- public static final String EXTRAS_KEY_STAGES_TO_SYNC = "sync";
-
- /**
- * Key in sync extras bundle specifying stages to skip this sync session.
- * <p>
- * Corresponding value should be a String JSON-encoding an object, the keys of
- * which are the stage names to skip. For example:
- * <code>"{ \"stageToSkip\": 0 }"</code>.
- */
- public static final String EXTRAS_KEY_STAGES_TO_SKIP = "skip";
-
- public static final String JSON_KEY_ACCOUNT = "account";
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/InvalidSyncKeyException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/InvalidSyncKeyException.java
deleted file mode 100644
index ac0fd58d0..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/InvalidSyncKeyException.java
+++ /dev/null
@@ -1,9 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.setup;
-
-public class InvalidSyncKeyException extends Exception {
- private static final long serialVersionUID = -6504925951580479894L;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/activities/ActivityUtils.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/activities/ActivityUtils.java
deleted file mode 100644
index 6542e1b00..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/activities/ActivityUtils.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.setup.activities;
-
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-
-import org.mozilla.gecko.background.common.GlobalConstants;
-import org.mozilla.gecko.db.BrowserContract;
-
-public class ActivityUtils {
- /**
- * Open a URL in Fennec, if one is provided; or just open Fennec.
- *
- * @param context Android context.
- * @param url to visit, or null to just open Fennec.
- */
- public static void openURLInFennec(final Context context, final String url) {
- Intent intent;
- if (url != null) {
- intent = new Intent(Intent.ACTION_VIEW);
- intent.setData(Uri.parse(url));
- } else {
- intent = new Intent(Intent.ACTION_MAIN);
- }
- intent.setClassName(GlobalConstants.BROWSER_INTENT_PACKAGE, GlobalConstants.BROWSER_INTENT_CLASS);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.putExtra(BrowserContract.SKIP_TAB_QUEUE_FLAG, true);
- context.startActivity(intent);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/activities/WebURLFinder.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/activities/WebURLFinder.java
deleted file mode 100644
index 8411d2a62..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/setup/activities/WebURLFinder.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.setup.activities;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class WebURLFinder {
- /**
- * These regular expressions are taken from Android's Patterns.java.
- * We brought them in to standardize URL matching across Android versions, instead of relying
- * on Android version-dependent built-ins that can vary across Android versions.
- * The original code can be found here:
- * http://androidxref.com/6.0.1_r10/xref/frameworks/base/core/java/android/util/Patterns.java
- *
- */
- public static final String GOOD_IRI_CHAR = "a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF";
- public static final String GOOD_GTLD_CHAR = "a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF";
- public static final String IRI = "[" + GOOD_IRI_CHAR + "]([" + GOOD_IRI_CHAR + "\\-]{0,61}[" + GOOD_IRI_CHAR + "]){0,1}";
- public static final String GTLD = "[" + GOOD_GTLD_CHAR + "]{2,63}";
- public static final String HOST_NAME = "(" + IRI + "\\.)+" + GTLD;
- public static final Pattern IP_ADDRESS = Pattern.compile("((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]"
- + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]"
- + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
- + "|[1-9][0-9]|[0-9]))");
- public static final Pattern DOMAIN_NAME = Pattern.compile("(" + HOST_NAME + "|" + IP_ADDRESS + ")");
- public static final Pattern WEB_URL = Pattern.compile("((?:(http|https|Http|Https|rtsp|Rtsp):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
- + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
- + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?"
- + "(?:" + DOMAIN_NAME + ")"
- + "(?:\\:\\d{1,5})?)"
- + "(\\/(?:(?:[" + GOOD_IRI_CHAR + "\\;\\/\\?\\:\\@\\&\\=\\#\\~"
- + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?"
- + "(?:\\b|$)");
-
- public final List<String> candidates;
-
- public WebURLFinder(String string) {
- if (string == null) {
- throw new IllegalArgumentException("string must not be null");
- }
-
- this.candidates = candidateWebURLs(string);
- }
-
- public WebURLFinder(List<String> strings) {
- if (strings == null) {
- throw new IllegalArgumentException("strings must not be null");
- }
-
- this.candidates = candidateWebURLs(strings);
- }
-
- /**
- * Check if string is a Web URL.
- * <p>
- * A Web URL is a URI that is not a <code>file:</code> or
- * <code>javascript:</code> scheme.
- *
- * @param string
- * to check.
- * @return <code>true</code> if <code>string</code> is a Web URL.
- */
- public static boolean isWebURL(String string) {
- try {
- new URI(string);
- } catch (Exception e) {
- return false;
- }
-
- if (android.webkit.URLUtil.isFileUrl(string) ||
- android.webkit.URLUtil.isJavaScriptUrl(string)) {
- return false;
- }
-
- return true;
- }
-
- /**
- * Return best Web URL.
- * <p>
- * "Best" means a Web URL with a scheme, and failing that, a Web URL without a
- * scheme.
- *
- * @return a Web URL or <code>null</code>.
- */
- public String bestWebURL() {
- String firstWebURLWithScheme = firstWebURLWithScheme();
- if (firstWebURLWithScheme != null) {
- return firstWebURLWithScheme;
- }
-
- return firstWebURLWithoutScheme();
- }
-
- protected static List<String> candidateWebURLs(Collection<String> strings) {
- List<String> candidates = new ArrayList<String>();
-
- for (String string : strings) {
- if (string == null) {
- continue;
- }
-
- candidates.addAll(candidateWebURLs(string));
- }
-
- return candidates;
- }
-
- protected static List<String> candidateWebURLs(String string) {
- Matcher matcher = WEB_URL.matcher(string);
- List<String> matches = new LinkedList<String>();
-
- while (matcher.find()) {
- // Remove URLs with bad schemes.
- if (!isWebURL(matcher.group())) {
- continue;
- }
-
- // Remove parts of email addresses.
- if (matcher.start() > 0 && (string.charAt(matcher.start() - 1) == '@')) {
- continue;
- }
-
- matches.add(matcher.group());
- }
-
- return matches;
- }
-
- protected String firstWebURLWithScheme() {
- for (String match : candidates) {
- try {
- if (new URI(match).getScheme() != null) {
- return match;
- }
- } catch (URISyntaxException e) {
- // Ignore: on to the next.
- continue;
- }
- }
-
- return null;
- }
-
- protected String firstWebURLWithoutScheme() {
- if (!candidates.isEmpty()) {
- return candidates.get(0);
- }
-
- return null;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AbstractNonRepositorySyncStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AbstractNonRepositorySyncStage.java
deleted file mode 100644
index c910216eb..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AbstractNonRepositorySyncStage.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.stage;
-
-
-/**
- * This is simply a stage that is not responsible for synchronizing repositories.
- */
-public abstract class AbstractNonRepositorySyncStage extends AbstractSessionManagingSyncStage {
- @Override
- protected void resetLocal() {
- // Do nothing.
- }
-
- @Override
- protected void wipeLocal() {
- // Do nothing.
- }
-
- @Override
- public Integer getStorageVersion() {
- return null; // Never include these engines in any meta/global records.
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AbstractSessionManagingSyncStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AbstractSessionManagingSyncStage.java
deleted file mode 100644
index 6592c3baa..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AbstractSessionManagingSyncStage.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.stage;
-
-import org.mozilla.gecko.sync.GlobalSession;
-
-/**
- * A global sync stage that manages a <code>GlobalSession</code> instance. This
- * class is intended to be temporary: it should disappear as work to make
- * data-driven syncs progresses.
- * <p>
- * This class is inherently <b>thread-unsafe</b>: if <code>session</code> is
- * mutated after being set, all sorts of bad things could occur. At the time of
- * writing, every <code>GlobalSyncStage</code> created is executed (wiped,
- * reset) with the same <code>GlobalSession</code> argument.
- */
-public abstract class AbstractSessionManagingSyncStage implements GlobalSyncStage {
- protected GlobalSession session;
-
- protected abstract void execute() throws NoSuchStageException;
- protected abstract void resetLocal();
- protected abstract void wipeLocal() throws Exception;
-
- @Override
- public void resetLocal(GlobalSession session) {
- this.session = session;
- resetLocal();
- }
-
- @Override
- public void wipeLocal(GlobalSession session) throws Exception {
- this.session = session;
- wipeLocal();
- }
-
- @Override
- public void execute(GlobalSession session) throws NoSuchStageException {
- this.session = session;
- execute();
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AndroidBrowserBookmarksServerSyncStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AndroidBrowserBookmarksServerSyncStage.java
deleted file mode 100644
index 10e209230..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AndroidBrowserBookmarksServerSyncStage.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.stage;
-
-import java.net.URISyntaxException;
-
-import org.mozilla.gecko.sync.JSONRecordFetcher;
-import org.mozilla.gecko.sync.MetaGlobalException;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.repositories.RecordFactory;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.android.AndroidBrowserBookmarksRepository;
-import org.mozilla.gecko.sync.repositories.domain.BookmarkRecordFactory;
-import org.mozilla.gecko.sync.repositories.domain.VersionConstants;
-
-public class AndroidBrowserBookmarksServerSyncStage extends ServerSyncStage {
- protected static final String LOG_TAG = "BookmarksStage";
-
- // Eventually this kind of sync stage will be data-driven,
- // and all this hard-coding can go away.
- private static final String BOOKMARKS_SORT = "index";
- // Sanity limit. Batch and total limit are the same for now, and will be adjusted
- // once buffer and high water mark are in place. See Bug 730142.
- private static final long BOOKMARKS_BATCH_LIMIT = 5000;
- private static final long BOOKMARKS_TOTAL_LIMIT = 5000;
-
- @Override
- protected String getCollection() {
- return "bookmarks";
- }
-
- @Override
- protected String getEngineName() {
- return "bookmarks";
- }
-
- @Override
- public Integer getStorageVersion() {
- return VersionConstants.BOOKMARKS_ENGINE_VERSION;
- }
-
- @Override
- protected Repository getRemoteRepository() throws URISyntaxException {
- // If this is a first sync, we need to check server counts to make sure that we aren't
- // going to screw up. SafeConstrainedServer11Repository does this. See Bug 814331.
- AuthHeaderProvider authHeaderProvider = session.getAuthHeaderProvider();
- final JSONRecordFetcher countsFetcher = new JSONRecordFetcher(session.config.infoCollectionCountsURL(), authHeaderProvider);
- String collection = getCollection();
- return new SafeConstrainedServer11Repository(
- collection,
- session.config.storageURL(),
- session.getAuthHeaderProvider(),
- session.config.infoCollections,
- session.config.infoConfiguration,
- BOOKMARKS_BATCH_LIMIT,
- BOOKMARKS_TOTAL_LIMIT,
- BOOKMARKS_SORT,
- countsFetcher);
- }
-
- @Override
- protected Repository getLocalRepository() {
- return new AndroidBrowserBookmarksRepository();
- }
-
- @Override
- protected RecordFactory getRecordFactory() {
- return new BookmarkRecordFactory();
- }
-
- @Override
- protected boolean isEnabled() throws MetaGlobalException {
- if (session == null || session.getContext() == null) {
- return false;
- }
- return super.isEnabled();
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AndroidBrowserHistoryServerSyncStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AndroidBrowserHistoryServerSyncStage.java
deleted file mode 100644
index 947a10898..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/AndroidBrowserHistoryServerSyncStage.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.stage;
-
-import java.net.URISyntaxException;
-
-import org.mozilla.gecko.sync.MetaGlobalException;
-import org.mozilla.gecko.sync.repositories.ConstrainedServer11Repository;
-import org.mozilla.gecko.sync.repositories.RecordFactory;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.android.AndroidBrowserHistoryRepository;
-import org.mozilla.gecko.sync.repositories.domain.HistoryRecordFactory;
-import org.mozilla.gecko.sync.repositories.domain.VersionConstants;
-
-public class AndroidBrowserHistoryServerSyncStage extends ServerSyncStage {
- protected static final String LOG_TAG = "HistoryStage";
-
- // Eventually this kind of sync stage will be data-driven,
- // and all this hard-coding can go away.
- private static final String HISTORY_SORT = "index";
- // Sanity limit. Batch and total limit are the same for now, and will be adjusted
- // once buffer and high water mark are in place. See Bug 730142.
- private static final long HISTORY_BATCH_LIMIT = 250;
- private static final long HISTORY_TOTAL_LIMIT = 250;
-
- @Override
- protected String getCollection() {
- return "history";
- }
-
- @Override
- protected String getEngineName() {
- return "history";
- }
-
- @Override
- public Integer getStorageVersion() {
- return VersionConstants.HISTORY_ENGINE_VERSION;
- }
-
- @Override
- protected Repository getLocalRepository() {
- return new AndroidBrowserHistoryRepository();
- }
-
- @Override
- protected Repository getRemoteRepository() throws URISyntaxException {
- String collection = getCollection();
- return new ConstrainedServer11Repository(
- collection,
- session.config.storageURL(),
- session.getAuthHeaderProvider(),
- session.config.infoCollections,
- session.config.infoConfiguration,
- HISTORY_BATCH_LIMIT,
- HISTORY_TOTAL_LIMIT,
- HISTORY_SORT);
- }
-
- @Override
- protected RecordFactory getRecordFactory() {
- return new HistoryRecordFactory();
- }
-
- @Override
- protected boolean isEnabled() throws MetaGlobalException {
- if (session == null || session.getContext() == null) {
- return false;
- }
- return super.isEnabled();
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/CheckPreconditionsStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/CheckPreconditionsStage.java
deleted file mode 100644
index b33f83ad1..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/CheckPreconditionsStage.java
+++ /dev/null
@@ -1,13 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.stage;
-
-
-public class CheckPreconditionsStage extends AbstractNonRepositorySyncStage {
- @Override
- public void execute() throws NoSuchStageException {
- session.advance();
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/CompletedStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/CompletedStage.java
deleted file mode 100644
index 7ec776324..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/CompletedStage.java
+++ /dev/null
@@ -1,16 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.stage;
-
-
-
-public class CompletedStage extends AbstractNonRepositorySyncStage {
- @Override
- public void execute() throws NoSuchStageException {
- // TODO: Update tracking timestamps, close connections, etc.
- // TODO: call clean() on each Repository in the sync constellation.
- session.completeSync();
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/EnsureCrypto5KeysStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/EnsureCrypto5KeysStage.java
deleted file mode 100644
index 5031cf770..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/EnsureCrypto5KeysStage.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.stage;
-
-import java.net.URISyntaxException;
-import java.util.HashSet;
-import java.util.Set;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.CollectionKeys;
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.InfoCollections;
-import org.mozilla.gecko.sync.NoCollectionKeysSetException;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.mozilla.gecko.sync.crypto.PersistedCrypto5Keys;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.net.SyncStorageRecordRequest;
-import org.mozilla.gecko.sync.net.SyncStorageRequestDelegate;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-
-public class EnsureCrypto5KeysStage
-extends AbstractNonRepositorySyncStage
-implements SyncStorageRequestDelegate {
-
- private static final String LOG_TAG = "EnsureC5KeysStage";
- private static final String CRYPTO_COLLECTION = "crypto";
- protected boolean retrying = false;
-
- @Override
- public void execute() throws NoSuchStageException {
- InfoCollections infoCollections = session.config.infoCollections;
- if (infoCollections == null) {
- session.abort(null, "No info/collections set in EnsureCrypto5KeysStage.");
- return;
- }
-
- PersistedCrypto5Keys pck = session.config.persistedCryptoKeys();
- long lastModified = pck.lastModified();
- if (retrying || !infoCollections.updateNeeded(CRYPTO_COLLECTION, lastModified)) {
- // Try to use our local collection keys for this session.
- Logger.debug(LOG_TAG, "Trying to use persisted collection keys for this session.");
- CollectionKeys keys = pck.keys();
- if (keys != null) {
- Logger.trace(LOG_TAG, "Using persisted collection keys for this session.");
- session.config.setCollectionKeys(keys);
- session.advance();
- return;
- }
- Logger.trace(LOG_TAG, "Failed to use persisted collection keys for this session.");
- }
-
- // We need an update: fetch fresh keys.
- Logger.debug(LOG_TAG, "Fetching fresh collection keys for this session.");
- try {
- SyncStorageRecordRequest request = new SyncStorageRecordRequest(session.wboURI(CRYPTO_COLLECTION, "keys"));
- request.delegate = this;
- request.get();
- } catch (URISyntaxException e) {
- session.abort(e, "Invalid URI.");
- }
- }
-
- @Override
- public AuthHeaderProvider getAuthHeaderProvider() {
- return session.getAuthHeaderProvider();
- }
-
- @Override
- public String ifUnmodifiedSince() {
- // TODO: last key time!
- return null;
- }
-
- protected void setAndPersist(PersistedCrypto5Keys pck, CollectionKeys keys, long timestamp) {
- session.config.setCollectionKeys(keys);
- pck.persistKeys(keys);
- pck.persistLastModified(timestamp);
- }
-
- /**
- * Return collections where either the individual key has changed, or if the
- * new default key is not the same as the old default key, where the
- * collection is using the default key.
- */
- protected Set<String> collectionsToUpdate(CollectionKeys oldKeys, CollectionKeys newKeys) {
- // These keys have explicitly changed; they definitely need updating.
- Set<String> changedKeys = new HashSet<String>(CollectionKeys.differences(oldKeys, newKeys));
-
- boolean defaultKeyChanged = true; // Most pessimistic is to assume default key has changed.
- KeyBundle newDefaultKeyBundle = null;
- try {
- KeyBundle oldDefaultKeyBundle = oldKeys.defaultKeyBundle();
- newDefaultKeyBundle = newKeys.defaultKeyBundle();
- defaultKeyChanged = !oldDefaultKeyBundle.equals(newDefaultKeyBundle);
- } catch (NoCollectionKeysSetException e) {
- Logger.warn(LOG_TAG, "NoCollectionKeysSetException in EnsureCrypto5KeysStage.", e);
- }
-
- if (newDefaultKeyBundle == null) {
- Logger.trace(LOG_TAG, "New default key not provided; returning changed individual keys.");
- return changedKeys;
- }
-
- if (!defaultKeyChanged) {
- Logger.trace(LOG_TAG, "New default key is the same as old default key; returning changed individual keys.");
- return changedKeys;
- }
-
- // New keys have a different default/sync key; check known collections against the default key.
- Logger.debug(LOG_TAG, "New default key is not the same as old default key.");
- for (Stage stage : Stage.getNamedStages()) {
- String name = stage.getRepositoryName();
- if (!newKeys.keyBundleForCollectionIsNotDefault(name)) {
- // Default key has changed, so this collection has changed.
- changedKeys.add(name);
- }
- }
-
- return changedKeys;
- }
-
- @Override
- public void handleRequestSuccess(SyncStorageResponse response) {
- // Take the timestamp from the response since it is later than the timestamp from info/collections.
- long responseTimestamp = response.normalizedWeaveTimestamp();
- CollectionKeys keys = new CollectionKeys();
- try {
- ExtendedJSONObject body = response.jsonObjectBody();
- if (Logger.LOG_PERSONAL_INFORMATION) {
- Logger.pii(LOG_TAG, "Fetched keys: " + body.toJSONString());
- }
- keys.setKeyPairsFromWBO(CryptoRecord.fromJSONRecord(body), session.config.syncKeyBundle);
- } catch (Exception e) {
- session.abort(e, "Invalid keys WBO.");
- return;
- }
-
- PersistedCrypto5Keys pck = session.config.persistedCryptoKeys();
- if (!pck.persistedKeysExist()) {
- // New keys, and no old keys! Persist keys and server timestamp.
- Logger.trace(LOG_TAG, "Setting fetched keys for this session; persisting fetched keys and last modified.");
- setAndPersist(pck, keys, responseTimestamp);
- session.advance();
- return;
- }
-
- // New keys, but we had old keys. Check for differences.
- CollectionKeys oldKeys = pck.keys();
- Set<String> changedCollections = collectionsToUpdate(oldKeys, keys);
- if (!changedCollections.isEmpty()) {
- // New keys, different from old keys.
- Logger.trace(LOG_TAG, "Fetched keys are not the same as persisted keys; " +
- "setting fetched keys for this session before resetting changed engines.");
- setAndPersist(pck, keys, responseTimestamp);
- session.resetStagesByName(changedCollections);
- session.abort(null, "crypto/keys changed on server.");
- return;
- }
-
- // New keys don't differ from old keys; persist timestamp and move on.
- Logger.trace(LOG_TAG, "Fetched keys are the same as persisted keys; persisting only last modified.");
- session.config.setCollectionKeys(oldKeys);
- pck.persistLastModified(response.normalizedWeaveTimestamp());
- session.advance();
- }
-
- @Override
- public void handleRequestFailure(SyncStorageResponse response) {
- if (retrying) {
- // Should happen very rarely -- this means we uploaded our crypto/keys
- // successfully, but failed to re-download.
- session.handleHTTPError(response, "Failure while re-downloading already uploaded keys.");
- return;
- }
-
- int statusCode = response.getStatusCode();
- if (statusCode == 404) {
- Logger.info(LOG_TAG, "Got 404 fetching keys. Fresh starting since keys are missing on server.");
- session.freshStart();
- return;
- }
- session.handleHTTPError(response, "Failure fetching keys: got response status code " + statusCode);
- }
-
- @Override
- public void handleRequestError(Exception ex) {
- session.abort(ex, "Failure fetching keys.");
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FennecTabsServerSyncStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FennecTabsServerSyncStage.java
deleted file mode 100644
index 40a474ef4..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FennecTabsServerSyncStage.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.stage;
-
-import org.mozilla.gecko.sync.repositories.RecordFactory;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
-import org.mozilla.gecko.sync.repositories.domain.TabsRecordFactory;
-import org.mozilla.gecko.sync.repositories.domain.VersionConstants;
-
-public class FennecTabsServerSyncStage extends ServerSyncStage {
- private static final String COLLECTION = "tabs";
-
- @Override
- protected String getCollection() {
- return COLLECTION;
- }
-
- @Override
- protected String getEngineName() {
- return COLLECTION;
- }
-
- @Override
- public Integer getStorageVersion() {
- return VersionConstants.TABS_ENGINE_VERSION;
- }
-
- @Override
- protected Repository getLocalRepository() {
- return new FennecTabsRepository(session.getClientsDelegate());
- }
-
- @Override
- protected RecordFactory getRecordFactory() {
- return new TabsRecordFactory();
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FetchInfoCollectionsStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FetchInfoCollectionsStage.java
deleted file mode 100644
index 088321d5b..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FetchInfoCollectionsStage.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.stage;
-
-import java.net.URISyntaxException;
-
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.InfoCollections;
-import org.mozilla.gecko.sync.delegates.JSONRecordFetchDelegate;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-
-public class FetchInfoCollectionsStage extends AbstractNonRepositorySyncStage {
- public class StageInfoCollectionsDelegate implements JSONRecordFetchDelegate {
-
- @Override
- public void handleSuccess(ExtendedJSONObject global) {
- session.config.infoCollections = new InfoCollections(global);
- session.advance();
- }
-
- @Override
- public void handleFailure(SyncStorageResponse response) {
- session.handleHTTPError(response, "Failure fetching info/collections.");
- }
-
- @Override
- public void handleError(Exception e) {
- session.abort(e, "Failure fetching info/collections.");
- }
-
- }
-
- @Override
- public void execute() throws NoSuchStageException {
- try {
- session.fetchInfoCollections(new StageInfoCollectionsDelegate());
- } catch (URISyntaxException e) {
- session.abort(e, "Invalid URI.");
- }
- }
-
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FetchInfoConfigurationStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FetchInfoConfigurationStage.java
deleted file mode 100644
index 7f53c2739..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FetchInfoConfigurationStage.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.stage;
-
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.InfoConfiguration;
-import org.mozilla.gecko.sync.JSONRecordFetcher;
-import org.mozilla.gecko.sync.delegates.JSONRecordFetchDelegate;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-
-/**
- * Fetches configuration data from info/configurations endpoint.
- */
-public class FetchInfoConfigurationStage extends AbstractNonRepositorySyncStage {
- private final String configurationURL;
- private final AuthHeaderProvider authHeaderProvider;
-
- public FetchInfoConfigurationStage(final String configurationURL, final AuthHeaderProvider authHeaderProvider) {
- super();
- this.configurationURL = configurationURL;
- this.authHeaderProvider = authHeaderProvider;
- }
-
- public class StageInfoConfigurationDelegate implements JSONRecordFetchDelegate {
- @Override
- public void handleSuccess(final ExtendedJSONObject result) {
- session.config.infoConfiguration = new InfoConfiguration(result);
- session.advance();
- }
-
- @Override
- public void handleFailure(final SyncStorageResponse response) {
- // Handle all non-404 failures upstream.
- if (response.getStatusCode() != 404) {
- session.handleHTTPError(response, "Failure fetching info/configuration");
- return;
- }
-
- // End-point might not be available (404) if server is running an older version.
- // We will use default config values in this case.
- session.config.infoConfiguration = new InfoConfiguration();
- session.advance();
- }
-
- @Override
- public void handleError(final Exception e) {
- session.abort(e, "Failure fetching info/configuration");
- }
- }
- @Override
- public void execute() {
- final StageInfoConfigurationDelegate delegate = new StageInfoConfigurationDelegate();
- final JSONRecordFetcher fetcher = new JSONRecordFetcher(configurationURL, authHeaderProvider);
- fetcher.fetch(delegate);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FetchMetaGlobalStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FetchMetaGlobalStage.java
deleted file mode 100644
index b4407b26b..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FetchMetaGlobalStage.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.stage;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.GlobalSession;
-import org.mozilla.gecko.sync.InfoCollections;
-import org.mozilla.gecko.sync.MetaGlobal;
-import org.mozilla.gecko.sync.PersistedMetaGlobal;
-import org.mozilla.gecko.sync.delegates.MetaGlobalDelegate;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-
-public class FetchMetaGlobalStage extends AbstractNonRepositorySyncStage {
- private static final String LOG_TAG = "FetchMetaGlobalStage";
- private static final String META_COLLECTION = "meta";
-
- public class StageMetaGlobalDelegate implements MetaGlobalDelegate {
-
- private final GlobalSession session;
- public StageMetaGlobalDelegate(GlobalSession session) {
- this.session = session;
- }
-
- @Override
- public void handleSuccess(MetaGlobal global, SyncStorageResponse response) {
- Logger.trace(LOG_TAG, "Persisting fetched meta/global and last modified.");
- PersistedMetaGlobal pmg = session.config.persistedMetaGlobal();
- pmg.persistMetaGlobal(global);
- // Take the timestamp from the response since it is later than the timestamp from info/collections.
- pmg.persistLastModified(response.normalizedWeaveTimestamp());
-
- session.processMetaGlobal(global);
- }
-
- @Override
- public void handleFailure(SyncStorageResponse response) {
- session.handleHTTPError(response, "Failure fetching meta/global.");
- }
-
- @Override
- public void handleError(Exception e) {
- session.abort(e, "Failure fetching meta/global.");
- }
-
- @Override
- public void handleMissing(MetaGlobal global, SyncStorageResponse response) {
- session.processMissingMetaGlobal(global);
- }
- }
-
- @Override
- public void execute() throws NoSuchStageException {
- InfoCollections infoCollections = session.config.infoCollections;
- if (infoCollections == null) {
- session.abort(null, "No info/collections set in FetchMetaGlobalStage.");
- return;
- }
-
- long lastModified = session.config.persistedMetaGlobal().lastModified();
- if (!infoCollections.updateNeeded(META_COLLECTION, lastModified)) {
- // Try to use our local collection keys for this session.
- Logger.info(LOG_TAG, "Trying to use persisted meta/global for this session.");
- MetaGlobal global = session.config.persistedMetaGlobal().metaGlobal(session.config.metaURL(), session.getAuthHeaderProvider());
- if (global != null) {
- Logger.info(LOG_TAG, "Using persisted meta/global for this session.");
- session.processMetaGlobal(global); // Calls session.advance().
- return;
- }
- Logger.info(LOG_TAG, "Failed to use persisted meta/global for this session.");
- }
-
- // We need an update: fetch or upload meta/global as necessary.
- Logger.info(LOG_TAG, "Fetching fresh meta/global for this session.");
- MetaGlobal global = new MetaGlobal(session.config.metaURL(), session.getAuthHeaderProvider());
- global.fetch(new StageMetaGlobalDelegate(session));
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FormHistoryServerSyncStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FormHistoryServerSyncStage.java
deleted file mode 100644
index 0a5d974b8..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/FormHistoryServerSyncStage.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.stage;
-
-import java.net.URISyntaxException;
-
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.repositories.ConstrainedServer11Repository;
-import org.mozilla.gecko.sync.repositories.RecordFactory;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.android.FormHistoryRepositorySession;
-import org.mozilla.gecko.sync.repositories.domain.FormHistoryRecord;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-import org.mozilla.gecko.sync.repositories.domain.VersionConstants;
-
-public class FormHistoryServerSyncStage extends ServerSyncStage {
-
- // Eventually this kind of sync stage will be data-driven,
- // and all this hard-coding can go away.
- private static final String FORM_HISTORY_SORT = "index";
- // Sanity limit. Batch and total limit are the same for now, and will be adjusted
- // once buffer and high water mark are in place. See Bug 730142.
- private static final long FORM_HISTORY_BATCH_LIMIT = 5000;
- private static final long FORM_HISTORY_TOTAL_LIMIT = 5000;
-
- @Override
- protected String getCollection() {
- return "forms";
- }
-
- @Override
- protected String getEngineName() {
- return "forms";
- }
-
- @Override
- public Integer getStorageVersion() {
- return VersionConstants.FORMS_ENGINE_VERSION;
- }
-
- @Override
- protected Repository getRemoteRepository() throws URISyntaxException {
- String collection = getCollection();
- return new ConstrainedServer11Repository(
- collection,
- session.config.storageURL(),
- session.getAuthHeaderProvider(),
- session.config.infoCollections,
- session.config.infoConfiguration,
- FORM_HISTORY_BATCH_LIMIT,
- FORM_HISTORY_TOTAL_LIMIT,
- FORM_HISTORY_SORT);
- }
-
- @Override
- protected Repository getLocalRepository() {
- return new FormHistoryRepositorySession.FormHistoryRepository();
- }
-
- public class FormHistoryRecordFactory extends RecordFactory {
-
- @Override
- public Record createRecord(Record record) {
- FormHistoryRecord r = new FormHistoryRecord();
- r.initFromEnvelope((CryptoRecord) record);
- return r;
- }
- }
-
- @Override
- protected RecordFactory getRecordFactory() {
- return new FormHistoryRecordFactory();
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/GlobalSyncStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/GlobalSyncStage.java
deleted file mode 100644
index 6dee71f90..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/GlobalSyncStage.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.stage;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.mozilla.gecko.sync.GlobalSession;
-
-
-public interface GlobalSyncStage {
- public static enum Stage {
- idle, // Start state.
- checkPreconditions, // Preparation of the basics. TODO: clear status
- fetchInfoCollections, // Take a look at timestamps.
- fetchInfoConfiguration, // Fetch server upload limits
- fetchMetaGlobal,
- ensureKeysStage,
- /*
- ensureSpecialRecords,
- updateEngineTimestamps,
- */
- syncClientsEngine(SyncClientsEngineStage.STAGE_NAME),
- /*
- processFirstSyncPref,
- processClientCommands,
- updateEnabledEngines,
- */
- syncTabs("tabs"),
- syncPasswords("passwords"),
- syncBookmarks("bookmarks"),
- syncHistory("history"),
- syncFormHistory("forms"),
-
- uploadMetaGlobal,
- completed;
-
- // Maintain a mapping from names ("bookmarks") to Stage enumerations (syncBookmarks).
- private static final Map<String, Stage> named = new HashMap<String, Stage>();
- static {
- for (Stage s : EnumSet.allOf(Stage.class)) {
- if (s.getRepositoryName() != null) {
- named.put(s.getRepositoryName(), s);
- }
- }
- }
-
- public static Stage byName(final String name) {
- if (name == null) {
- return null;
- }
- return named.get(name);
- }
-
- /**
- * @return an immutable collection of Stages.
- */
- public static Collection<Stage> getNamedStages() {
- return Collections.unmodifiableCollection(named.values());
- }
-
- // Each Stage tracks its repositoryName.
- private final String repositoryName;
- public String getRepositoryName() {
- return repositoryName;
- }
-
- private Stage() {
- this.repositoryName = null;
- }
-
- private Stage(final String name) {
- this.repositoryName = name;
- }
- }
-
- public void execute(GlobalSession session) throws NoSuchStageException;
- public void resetLocal(GlobalSession session);
- public void wipeLocal(GlobalSession session) throws Exception;
-
- /**
- * What storage version number this engine supports.
- * <p>
- * Used to generate a fresh meta/global record for upload.
- * @return a version number or <code>null</code> to never include this engine in a fresh meta/global record.
- */
- public Integer getStorageVersion();
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/NoSuchStageException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/NoSuchStageException.java
deleted file mode 100644
index 14c9bb43e..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/NoSuchStageException.java
+++ /dev/null
@@ -1,13 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.stage;
-
-public class NoSuchStageException extends Exception {
- private static final long serialVersionUID = 8338484472880746971L;
- GlobalSyncStage.Stage stage;
- public NoSuchStageException(GlobalSyncStage.Stage stage) {
- this.stage = stage;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/PasswordsServerSyncStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/PasswordsServerSyncStage.java
deleted file mode 100644
index c781ce2cc..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/PasswordsServerSyncStage.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.stage;
-
-import org.mozilla.gecko.sync.repositories.RecordFactory;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.android.PasswordsRepositorySession;
-import org.mozilla.gecko.sync.repositories.domain.PasswordRecordFactory;
-import org.mozilla.gecko.sync.repositories.domain.VersionConstants;
-
-public class PasswordsServerSyncStage extends ServerSyncStage {
- @Override
- protected String getCollection() {
- return "passwords";
- }
-
- @Override
- protected String getEngineName() {
- return "passwords";
- }
-
- @Override
- public Integer getStorageVersion() {
- return VersionConstants.PASSWORDS_ENGINE_VERSION;
- }
-
- @Override
- protected Repository getLocalRepository() {
- return new PasswordsRepositorySession.PasswordsRepository();
- }
-
- @Override
- protected RecordFactory getRecordFactory() {
- return new PasswordRecordFactory();
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/SafeConstrainedServer11Repository.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/SafeConstrainedServer11Repository.java
deleted file mode 100644
index 733c887f0..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/SafeConstrainedServer11Repository.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.stage;
-
-import java.net.URISyntaxException;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.InfoCollections;
-import org.mozilla.gecko.sync.InfoConfiguration;
-import org.mozilla.gecko.sync.InfoCounts;
-import org.mozilla.gecko.sync.JSONRecordFetcher;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.repositories.ConstrainedServer11Repository;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.Server11RepositorySession;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-
-import android.content.Context;
-
-/**
- * This is a constrained repository -- one which fetches a limited number
- * of records -- that additionally refuses to sync if the limit will
- * be exceeded on a first sync by the number of records on the server.
- *
- * You must pass an {@link InfoCounts} instance, which will be interrogated
- * in the event of a first sync.
- *
- * "First sync" means that our sync timestamp is not greater than zero.
- */
-public class SafeConstrainedServer11Repository extends ConstrainedServer11Repository {
-
- // This can be lazily evaluated if we need it.
- private final JSONRecordFetcher countFetcher;
-
- public SafeConstrainedServer11Repository(String collection,
- String storageURL,
- AuthHeaderProvider authHeaderProvider,
- InfoCollections infoCollections,
- InfoConfiguration infoConfiguration,
- long batchLimit,
- long totalLimit,
- String sort,
- JSONRecordFetcher countFetcher)
- throws URISyntaxException {
- super(collection, storageURL, authHeaderProvider, infoCollections, infoConfiguration,
- batchLimit, totalLimit, sort);
- if (countFetcher == null) {
- throw new IllegalArgumentException("countFetcher must not be null");
- }
- this.countFetcher = countFetcher;
- }
-
- @Override
- public void createSession(RepositorySessionCreationDelegate delegate,
- Context context) {
- delegate.onSessionCreated(new CountCheckingServer11RepositorySession(this, this.getDefaultBatchLimit()));
- }
-
- public class CountCheckingServer11RepositorySession extends Server11RepositorySession {
- private static final String LOG_TAG = "CountCheckingServer11RepositorySession";
-
- /**
- * The session will report no data available if this is a first sync
- * and the server has more data available than this limit.
- */
- private final long fetchLimit;
-
- public CountCheckingServer11RepositorySession(Repository repository, long fetchLimit) {
- super(repository);
- this.fetchLimit = fetchLimit;
- }
-
- @Override
- public boolean shouldSkip() {
- // If this is a first sync, verify that we aren't going to blow through our limit.
- final long lastSyncTimestamp = getLastSyncTimestamp();
- if (lastSyncTimestamp > 0) {
- Logger.info(LOG_TAG, "Collection " + collection + " has already had a first sync: " +
- "timestamp is " + lastSyncTimestamp + "; " +
- "ignoring any updated counts and syncing as usual.");
- } else {
- Logger.info(LOG_TAG, "Collection " + collection + " is starting a first sync; checking counts.");
-
- final InfoCounts counts;
- try {
- // This'll probably be the same object, but best to obey the API.
- counts = new InfoCounts(countFetcher.fetchBlocking());
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Skipping " + collection + " until we can fetch counts.", e);
- return true;
- }
-
- Integer c = counts.getCount(collection);
- if (c == null) {
- Logger.info(LOG_TAG, "Fetched counts does not include collection " + collection + "; syncing as usual.");
- return false;
- }
-
- Logger.info(LOG_TAG, "First sync for " + collection + ": " + c + " items.");
- if (c > fetchLimit) {
- Logger.warn(LOG_TAG, "Too many items to sync safely. Skipping.");
- return true;
- }
- }
- return super.shouldSkip();
- }
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/ServerSyncStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/ServerSyncStage.java
deleted file mode 100644
index 733e69da5..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/ServerSyncStage.java
+++ /dev/null
@@ -1,627 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.stage;
-
-import android.content.Context;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.EngineSettings;
-import org.mozilla.gecko.sync.GlobalSession;
-import org.mozilla.gecko.sync.HTTPFailureException;
-import org.mozilla.gecko.sync.MetaGlobalException;
-import org.mozilla.gecko.sync.NoCollectionKeysSetException;
-import org.mozilla.gecko.sync.NonObjectJSONException;
-import org.mozilla.gecko.sync.SynchronizerConfiguration;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.mozilla.gecko.sync.delegates.WipeServerDelegate;
-import org.mozilla.gecko.sync.middleware.Crypto5MiddlewareRepository;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.net.BaseResource;
-import org.mozilla.gecko.sync.net.SyncStorageRequest;
-import org.mozilla.gecko.sync.net.SyncStorageRequestDelegate;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-import org.mozilla.gecko.sync.repositories.InactiveSessionException;
-import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
-import org.mozilla.gecko.sync.repositories.RecordFactory;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
-import org.mozilla.gecko.sync.repositories.Server11Repository;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
-import org.mozilla.gecko.sync.synchronizer.ServerLocalSynchronizer;
-import org.mozilla.gecko.sync.synchronizer.Synchronizer;
-import org.mozilla.gecko.sync.synchronizer.SynchronizerDelegate;
-import org.mozilla.gecko.sync.synchronizer.SynchronizerSession;
-
-import java.io.IOException;
-import java.net.URISyntaxException;
-import java.util.Map;
-import java.util.concurrent.ExecutorService;
-
-/**
- * Fetch from a server collection into a local repository, encrypting
- * and decrypting along the way.
- *
- * @author rnewman
- *
- */
-public abstract class ServerSyncStage extends AbstractSessionManagingSyncStage implements SynchronizerDelegate {
-
- protected static final String LOG_TAG = "ServerSyncStage";
-
- protected long stageStartTimestamp = -1;
- protected long stageCompleteTimestamp = -1;
-
- /**
- * Override these in your subclasses.
- *
- * @return true if this stage should be executed.
- * @throws MetaGlobalException
- */
- protected boolean isEnabled() throws MetaGlobalException {
- EngineSettings engineSettings = null;
- try {
- engineSettings = getEngineSettings();
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Unable to get engine settings for " + this + ": fetching config failed.", e);
- // Fall through; null engineSettings will pass below.
- }
-
- // We can be disabled by the server's meta/global record, or malformed in the server's meta/global record,
- // or by the user manually in Sync Settings.
- // We catch the subclasses of MetaGlobalException to trigger various resets and wipes in execute().
- boolean enabledInMetaGlobal = session.isEngineRemotelyEnabled(this.getEngineName(), engineSettings);
-
- // Check for manual changes to engines by the user.
- checkAndUpdateUserSelectedEngines(enabledInMetaGlobal);
-
- // Check for changes on the server.
- if (!enabledInMetaGlobal) {
- Logger.debug(LOG_TAG, "Stage " + this.getEngineName() + " disabled by server meta/global.");
- return false;
- }
-
- // We can also be disabled just for this sync.
- boolean enabledThisSync = session.isEngineLocallyEnabled(this.getEngineName()); // For ServerSyncStage, stage name == engine name.
- if (!enabledThisSync) {
- Logger.debug(LOG_TAG, "Stage " + this.getEngineName() + " disabled just for this sync.");
- }
- return enabledThisSync;
- }
-
- /**
- * Compares meta/global engine state to user selected engines from Sync
- * Settings and throws an exception if they don't match and meta/global needs
- * to be updated.
- *
- * @param enabledInMetaGlobal
- * boolean of engine sync state in meta/global
- * @throws MetaGlobalException
- * if engine sync state has been changed in Sync Settings, with new
- * engine sync state.
- */
- protected void checkAndUpdateUserSelectedEngines(boolean enabledInMetaGlobal) throws MetaGlobalException {
- Map<String, Boolean> selectedEngines = session.config.userSelectedEngines;
- String thisEngine = this.getEngineName();
-
- if (selectedEngines != null && selectedEngines.containsKey(thisEngine)) {
- boolean enabledInSelection = selectedEngines.get(thisEngine);
- if (enabledInMetaGlobal != enabledInSelection) {
- // Engine enable state has been changed by the user.
- Logger.debug(LOG_TAG, "Engine state has been changed by user. Throwing exception.");
- throw new MetaGlobalException.MetaGlobalEngineStateChangedException(enabledInSelection);
- }
- }
- }
-
- protected EngineSettings getEngineSettings() throws NonObjectJSONException, IOException {
- Integer version = getStorageVersion();
- if (version == null) {
- Logger.warn(LOG_TAG, "null storage version for " + this + "; using version 0.");
- version = 0;
- }
-
- SynchronizerConfiguration config = this.getConfig();
- if (config == null) {
- return new EngineSettings(null, version);
- }
- return new EngineSettings(config.syncID, version);
- }
-
- protected abstract String getCollection();
- protected abstract String getEngineName();
- protected abstract Repository getLocalRepository();
- protected abstract RecordFactory getRecordFactory();
-
- // Override this in subclasses.
- protected Repository getRemoteRepository() throws URISyntaxException {
- String collection = getCollection();
- return new Server11Repository(collection,
- session.config.storageURL(),
- session.getAuthHeaderProvider(),
- session.config.infoCollections,
- session.config.infoConfiguration);
- }
-
- /**
- * Return a Crypto5Middleware-wrapped Server11Repository.
- *
- * @throws NoCollectionKeysSetException
- * @throws URISyntaxException
- */
- protected Repository wrappedServerRepo() throws NoCollectionKeysSetException, URISyntaxException {
- String collection = this.getCollection();
- KeyBundle collectionKey = session.keyBundleForCollection(collection);
- Crypto5MiddlewareRepository cryptoRepo = new Crypto5MiddlewareRepository(getRemoteRepository(), collectionKey);
- cryptoRepo.recordFactory = getRecordFactory();
- return cryptoRepo;
- }
-
- protected String bundlePrefix() {
- return this.getCollection() + ".";
- }
-
- protected SynchronizerConfiguration getConfig() throws NonObjectJSONException, IOException {
- return new SynchronizerConfiguration(session.config.getBranch(bundlePrefix()));
- }
-
- protected void persistConfig(SynchronizerConfiguration synchronizerConfiguration) {
- synchronizerConfiguration.persist(session.config.getBranch(bundlePrefix()));
- }
-
- public Synchronizer getConfiguredSynchronizer(GlobalSession session) throws NoCollectionKeysSetException, URISyntaxException, NonObjectJSONException, IOException {
- Repository remote = wrappedServerRepo();
-
- Synchronizer synchronizer = new ServerLocalSynchronizer();
- synchronizer.repositoryA = remote;
- synchronizer.repositoryB = this.getLocalRepository();
- synchronizer.load(getConfig());
-
- return synchronizer;
- }
-
- /**
- * Reset timestamps.
- */
- @Override
- protected void resetLocal() {
- resetLocalWithSyncID(null);
- }
-
- /**
- * Reset timestamps and possibly set syncID.
- * @param syncID if non-null, new syncID to persist.
- */
- protected void resetLocalWithSyncID(String syncID) {
- // Clear both timestamps.
- SynchronizerConfiguration config;
- try {
- config = this.getConfig();
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Unable to reset " + this + ": fetching config failed.", e);
- return;
- }
-
- if (syncID != null) {
- config.syncID = syncID;
- Logger.info(LOG_TAG, "Setting syncID for " + this + " to '" + syncID + "'.");
- }
- config.localBundle.setTimestamp(0L);
- config.remoteBundle.setTimestamp(0L);
- persistConfig(config);
- Logger.info(LOG_TAG, "Reset timestamps for " + this);
- }
-
- // Not thread-safe. Use with caution.
- private class WipeWaiter {
- public boolean sessionSucceeded = true;
- public boolean wipeSucceeded = true;
- public Exception error;
-
- public void notify(Exception e, boolean sessionSucceeded) {
- this.sessionSucceeded = sessionSucceeded;
- this.wipeSucceeded = false;
- this.error = e;
- this.notify();
- }
- }
-
- /**
- * Synchronously wipe this stage by instantiating a local repository session
- * and wiping that.
- * <p>
- * Logs and re-throws an exception on failure.
- */
- @Override
- protected void wipeLocal() throws Exception {
- // Reset, then clear data.
- this.resetLocal();
-
- final WipeWaiter monitor = new WipeWaiter();
- final Context context = session.getContext();
- final Repository r = this.getLocalRepository();
-
- final Runnable doWipe = new Runnable() {
- @Override
- public void run() {
- r.createSession(new RepositorySessionCreationDelegate() {
-
- @Override
- public void onSessionCreated(final RepositorySession session) {
- try {
- session.begin(new RepositorySessionBeginDelegate() {
-
- @Override
- public void onBeginSucceeded(final RepositorySession session) {
- session.wipe(new RepositorySessionWipeDelegate() {
- @Override
- public void onWipeSucceeded() {
- try {
- session.finish(new RepositorySessionFinishDelegate() {
-
- @Override
- public void onFinishSucceeded(RepositorySession session,
- RepositorySessionBundle bundle) {
- // Hurrah.
- synchronized (monitor) {
- monitor.notify();
- }
- }
-
- @Override
- public void onFinishFailed(Exception ex) {
- // Assume that no finish => no wipe.
- synchronized (monitor) {
- monitor.notify(ex, true);
- }
- }
-
- @Override
- public RepositorySessionFinishDelegate deferredFinishDelegate(ExecutorService executor) {
- return this;
- }
- });
- } catch (InactiveSessionException e) {
- // Cannot happen. Call for safety.
- synchronized (monitor) {
- monitor.notify(e, true);
- }
- }
- }
-
- @Override
- public void onWipeFailed(Exception ex) {
- session.abort();
- synchronized (monitor) {
- monitor.notify(ex, true);
- }
- }
-
- @Override
- public RepositorySessionWipeDelegate deferredWipeDelegate(ExecutorService executor) {
- return this;
- }
- });
- }
-
- @Override
- public void onBeginFailed(Exception ex) {
- session.abort();
- synchronized (monitor) {
- monitor.notify(ex, true);
- }
- }
-
- @Override
- public RepositorySessionBeginDelegate deferredBeginDelegate(ExecutorService executor) {
- return this;
- }
- });
- } catch (InvalidSessionTransitionException e) {
- session.abort();
- synchronized (monitor) {
- monitor.notify(e, true);
- }
- }
- }
-
- @Override
- public void onSessionCreateFailed(Exception ex) {
- synchronized (monitor) {
- monitor.notify(ex, false);
- }
- }
-
- @Override
- public RepositorySessionCreationDelegate deferredCreationDelegate() {
- return this;
- }
- }, context);
- }
- };
-
- final Thread wiping = new Thread(doWipe);
- synchronized (monitor) {
- wiping.start();
- try {
- monitor.wait();
- } catch (InterruptedException e) {
- Logger.error(LOG_TAG, "Wipe interrupted.");
- }
- }
-
- if (!monitor.sessionSucceeded) {
- Logger.error(LOG_TAG, "Failed to create session for wipe.");
- throw monitor.error;
- }
-
- if (!monitor.wipeSucceeded) {
- Logger.error(LOG_TAG, "Failed to wipe session.");
- throw monitor.error;
- }
-
- Logger.info(LOG_TAG, "Wiping stage complete.");
- }
-
- /**
- * Asynchronously wipe collection on server.
- */
- protected void wipeServer(final AuthHeaderProvider authHeaderProvider, final WipeServerDelegate wipeDelegate) {
- SyncStorageRequest request;
-
- try {
- request = new SyncStorageRequest(session.config.collectionURI(getCollection()));
- } catch (URISyntaxException ex) {
- Logger.warn(LOG_TAG, "Invalid URI in wipeServer.");
- wipeDelegate.onWipeFailed(ex);
- return;
- }
-
- request.delegate = new SyncStorageRequestDelegate() {
-
- @Override
- public String ifUnmodifiedSince() {
- return null;
- }
-
- @Override
- public void handleRequestSuccess(SyncStorageResponse response) {
- BaseResource.consumeEntity(response);
- resetLocal();
- wipeDelegate.onWiped(response.normalizedWeaveTimestamp());
- }
-
- @Override
- public void handleRequestFailure(SyncStorageResponse response) {
- Logger.warn(LOG_TAG, "Got request failure " + response.getStatusCode() + " in wipeServer.");
- // Process HTTP failures here to pick up backoffs, etc.
- session.interpretHTTPFailure(response.httpResponse());
- BaseResource.consumeEntity(response); // The exception thrown should not need the body of the response.
- wipeDelegate.onWipeFailed(new HTTPFailureException(response));
- }
-
- @Override
- public void handleRequestError(Exception ex) {
- Logger.warn(LOG_TAG, "Got exception in wipeServer.", ex);
- wipeDelegate.onWipeFailed(ex);
- }
-
- @Override
- public AuthHeaderProvider getAuthHeaderProvider() {
- return authHeaderProvider;
- }
- };
-
- request.delete();
- }
-
- /**
- * Synchronously wipe the server.
- * <p>
- * Logs and re-throws an exception on failure.
- */
- public void wipeServer(final GlobalSession session) throws Exception {
- this.session = session;
-
- final WipeWaiter monitor = new WipeWaiter();
-
- final Runnable doWipe = new Runnable() {
- @Override
- public void run() {
- wipeServer(session.getAuthHeaderProvider(), new WipeServerDelegate() {
- @Override
- public void onWiped(long timestamp) {
- synchronized (monitor) {
- monitor.notify();
- }
- }
-
- @Override
- public void onWipeFailed(Exception e) {
- synchronized (monitor) {
- monitor.notify(e, false);
- }
- }
- });
- }
- };
-
- final Thread wiping = new Thread(doWipe);
- synchronized (monitor) {
- wiping.start();
- try {
- monitor.wait();
- } catch (InterruptedException e) {
- Logger.error(LOG_TAG, "Server wipe interrupted.");
- }
- }
-
- if (!monitor.wipeSucceeded) {
- Logger.error(LOG_TAG, "Failed to wipe server.");
- throw monitor.error;
- }
-
- Logger.info(LOG_TAG, "Wiping server complete.");
- }
-
- @Override
- public void execute() throws NoSuchStageException {
- final String name = getEngineName();
- Logger.debug(LOG_TAG, "Starting execute for " + name);
-
- stageStartTimestamp = System.currentTimeMillis();
-
- try {
- if (!this.isEnabled()) {
- Logger.info(LOG_TAG, "Skipping stage " + name + ".");
- session.advance();
- return;
- }
- } catch (MetaGlobalException.MetaGlobalMalformedSyncIDException e) {
- // Bad engine syncID. This should never happen. Wipe the server.
- try {
- session.recordForMetaGlobalUpdate(name, new EngineSettings(Utils.generateGuid(), this.getStorageVersion()));
- Logger.info(LOG_TAG, "Wiping server because malformed engine sync ID was found in meta/global.");
- wipeServer(session);
- Logger.info(LOG_TAG, "Wiped server after malformed engine sync ID found in meta/global.");
- } catch (Exception ex) {
- session.abort(ex, "Failed to wipe server after malformed engine sync ID found in meta/global.");
- }
- } catch (MetaGlobalException.MetaGlobalMalformedVersionException e) {
- // Bad engine version. This should never happen. Wipe the server.
- try {
- session.recordForMetaGlobalUpdate(name, new EngineSettings(Utils.generateGuid(), this.getStorageVersion()));
- Logger.info(LOG_TAG, "Wiping server because malformed engine version was found in meta/global.");
- wipeServer(session);
- Logger.info(LOG_TAG, "Wiped server after malformed engine version found in meta/global.");
- } catch (Exception ex) {
- session.abort(ex, "Failed to wipe server after malformed engine version found in meta/global.");
- }
- } catch (MetaGlobalException.MetaGlobalStaleClientSyncIDException e) {
- // Our syncID is wrong. Reset client and take the server syncID.
- Logger.warn(LOG_TAG, "Remote engine syncID different from local engine syncID:" +
- " resetting local engine and assuming remote engine syncID.");
- this.resetLocalWithSyncID(e.serverSyncID);
- } catch (MetaGlobalException.MetaGlobalEngineStateChangedException e) {
- boolean isEnabled = e.isEnabled;
- if (!isEnabled) {
- // Engine has been disabled; update meta/global with engine removal for upload.
- session.removeEngineFromMetaGlobal(name);
- session.config.declinedEngineNames.add(name);
- } else {
- session.config.declinedEngineNames.remove(name);
- // Add engine with new syncID to meta/global for upload.
- String newSyncID = Utils.generateGuid();
- session.recordForMetaGlobalUpdate(name, new EngineSettings(newSyncID, this.getStorageVersion()));
- // Update SynchronizerConfiguration w/ new engine syncID.
- this.resetLocalWithSyncID(newSyncID);
- }
- try {
- // Engine sync status has changed. Wipe server.
- Logger.warn(LOG_TAG, "Wiping server because engine sync state changed.");
- wipeServer(session);
- Logger.warn(LOG_TAG, "Wiped server because engine sync state changed.");
- } catch (Exception ex) {
- session.abort(ex, "Failed to wipe server after engine sync state changed");
- }
- if (!isEnabled) {
- Logger.warn(LOG_TAG, "Stage has been disabled. Advancing to next stage.");
- session.advance();
- return;
- }
- } catch (MetaGlobalException e) {
- session.abort(e, "Inappropriate meta/global; refusing to execute " + name + " stage.");
- return;
- }
-
- Synchronizer synchronizer;
- try {
- synchronizer = this.getConfiguredSynchronizer(session);
- } catch (NoCollectionKeysSetException e) {
- session.abort(e, "No CollectionKeys.");
- return;
- } catch (URISyntaxException e) {
- session.abort(e, "Invalid URI syntax for server repository.");
- return;
- } catch (NonObjectJSONException | IOException e) {
- session.abort(e, "Invalid persisted JSON for config.");
- return;
- }
-
- Logger.debug(LOG_TAG, "Invoking synchronizer.");
- synchronizer.synchronize(session.getContext(), this);
- Logger.debug(LOG_TAG, "Reached end of execute.");
- }
-
- /**
- * Express the duration taken by this stage as a String, like "0.56 seconds".
- *
- * @return formatted string.
- */
- protected String getStageDurationString() {
- return Utils.formatDuration(stageStartTimestamp, stageCompleteTimestamp);
- }
-
- /**
- * We synced this engine! Persist timestamps and advance the session.
- *
- * @param synchronizer the <code>Synchronizer</code> that succeeded.
- */
- @Override
- public void onSynchronized(Synchronizer synchronizer) {
- stageCompleteTimestamp = System.currentTimeMillis();
- Logger.debug(LOG_TAG, "onSynchronized.");
-
- SynchronizerConfiguration newConfig = synchronizer.save();
- if (newConfig != null) {
- persistConfig(newConfig);
- } else {
- Logger.warn(LOG_TAG, "Didn't get configuration from synchronizer after success.");
- }
-
- final SynchronizerSession synchronizerSession = synchronizer.getSynchronizerSession();
- int inboundCount = synchronizerSession.getInboundCount();
- int outboundCount = synchronizerSession.getOutboundCount();
- Logger.info(LOG_TAG, "Stage " + getEngineName() +
- " received " + inboundCount + " and sent " + outboundCount +
- " records in " + getStageDurationString() + ".");
- Logger.info(LOG_TAG, "Advancing session.");
- session.advance();
- }
-
- /**
- * We failed to sync this engine! Do not persist timestamps (which means that
- * the next sync will include this sync's data), but do advance the session
- * (if we didn't get a Retry-After header).
- *
- * @param synchronizer the <code>Synchronizer</code> that failed.
- */
- @Override
- public void onSynchronizeFailed(Synchronizer synchronizer,
- Exception lastException, String reason) {
- stageCompleteTimestamp = System.currentTimeMillis();
- Logger.warn(LOG_TAG, "Synchronize failed: " + reason, lastException);
-
- // This failure could be due to a 503 or a 401 and it could have headers.
- // Interrogate the headers but only abort the global session if Retry-After header is set.
- if (lastException instanceof HTTPFailureException) {
- SyncStorageResponse response = ((HTTPFailureException)lastException).response;
- if (response.retryAfterInSeconds() > 0) {
- session.handleHTTPError(response, reason); // Calls session.abort().
- return;
- } else {
- session.interpretHTTPFailure(response.httpResponse()); // Does not call session.abort().
- }
- }
-
- Logger.info(LOG_TAG, "Advancing session even though stage failed (took " + getStageDurationString() +
- "). Timestamps not persisted.");
- session.advance();
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/SyncClientsEngineStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/SyncClientsEngineStage.java
deleted file mode 100644
index 04d3e7ce2..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/SyncClientsEngineStage.java
+++ /dev/null
@@ -1,691 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.stage;
-
-import android.accounts.Account;
-import android.content.Context;
-import android.support.annotation.NonNull;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.json.simple.JSONArray;
-import org.json.simple.JSONObject;
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.fxa.FxAccountClient;
-import org.mozilla.gecko.background.fxa.FxAccountClient20;
-import org.mozilla.gecko.background.fxa.FxAccountClientException;
-import org.mozilla.gecko.fxa.FirefoxAccounts;
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-import org.mozilla.gecko.sync.CommandProcessor;
-import org.mozilla.gecko.sync.CommandProcessor.Command;
-import org.mozilla.gecko.sync.CryptoRecord;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.HTTPFailureException;
-import org.mozilla.gecko.sync.NoCollectionKeysSetException;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.crypto.CryptoException;
-import org.mozilla.gecko.sync.crypto.KeyBundle;
-import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
-import org.mozilla.gecko.sync.net.AuthHeaderProvider;
-import org.mozilla.gecko.sync.net.BaseResource;
-import org.mozilla.gecko.sync.net.SyncStorageCollectionRequest;
-import org.mozilla.gecko.sync.net.SyncStorageRecordRequest;
-import org.mozilla.gecko.sync.net.SyncStorageResponse;
-import org.mozilla.gecko.sync.net.WBOCollectionRequestDelegate;
-import org.mozilla.gecko.sync.net.WBORequestDelegate;
-import org.mozilla.gecko.sync.repositories.NullCursorException;
-import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
-import org.mozilla.gecko.sync.repositories.android.RepoUtils;
-import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
-import org.mozilla.gecko.sync.repositories.domain.ClientRecordFactory;
-import org.mozilla.gecko.sync.repositories.domain.VersionConstants;
-
-import ch.boye.httpclientandroidlib.HttpStatus;
-
-public class SyncClientsEngineStage extends AbstractSessionManagingSyncStage {
- private static final String LOG_TAG = "SyncClientsEngineStage";
-
- public static final String COLLECTION_NAME = "clients";
- public static final String STAGE_NAME = COLLECTION_NAME;
- public static final int CLIENTS_TTL_REFRESH = 604800000; // 7 days in milliseconds.
- public static final int MAX_UPLOAD_FAILURE_COUNT = 5;
- public static final long NOTIFY_TAB_SENT_TTL_SECS = TimeUnit.SECONDS.convert(1L, TimeUnit.HOURS); // 1 hour
-
- protected final ClientRecordFactory factory = new ClientRecordFactory();
- protected ClientUploadDelegate clientUploadDelegate;
- protected ClientDownloadDelegate clientDownloadDelegate;
-
- // Be sure to use this safely via getClientsDatabaseAccessor/closeDataAccessor.
- protected ClientsDatabaseAccessor db;
-
- protected volatile boolean shouldWipe;
- protected volatile boolean shouldUploadLocalRecord; // Set if, e.g., we received commands or need to refresh our version.
- protected final AtomicInteger uploadAttemptsCount = new AtomicInteger();
- protected final List<ClientRecord> modifiedClientsToUpload = new ArrayList<ClientRecord>();
-
- protected int getClientsCount() {
- return getClientsDatabaseAccessor().clientsCount();
- }
-
- protected synchronized ClientsDatabaseAccessor getClientsDatabaseAccessor() {
- if (db == null) {
- db = new ClientsDatabaseAccessor(session.getContext());
- }
- return db;
- }
-
- protected synchronized void closeDataAccessor() {
- if (db == null) {
- return;
- }
- db.close();
- db = null;
- }
-
- /**
- * The following two delegates, ClientDownloadDelegate and ClientUploadDelegate
- * are both triggered in a chain, starting when execute() calls
- * downloadClientRecords().
- *
- * Client records are downloaded using a get() request. Upon success of the
- * get() request, the local client record is uploaded.
- *
- * @author Marina Samuel
- *
- */
- public class ClientDownloadDelegate extends WBOCollectionRequestDelegate {
-
- // We use this on each WBO, so lift it out.
- final ClientsDataDelegate clientsDelegate = session.getClientsDelegate();
- boolean localAccountGUIDDownloaded = false;
-
- @Override
- public AuthHeaderProvider getAuthHeaderProvider() {
- return session.getAuthHeaderProvider();
- }
-
- @Override
- public String ifUnmodifiedSince() {
- // TODO last client download time?
- return null;
- }
-
- @Override
- public void handleRequestSuccess(SyncStorageResponse response) {
-
- // Hang onto the server's last modified timestamp to use
- // in X-If-Unmodified-Since for upload.
- session.config.persistServerClientsTimestamp(response.normalizedWeaveTimestamp());
- BaseResource.consumeEntity(response);
-
- // Wipe the clients table if it still hasn't been wiped but needs to be.
- wipeAndStore(null);
-
- // If we successfully downloaded all records but ours was not one of them
- // then reset the timestamp.
- if (!localAccountGUIDDownloaded) {
- Logger.info(LOG_TAG, "Local client GUID does not exist on the server. Upload timestamp will be reset.");
- session.config.persistServerClientRecordTimestamp(0);
- }
- localAccountGUIDDownloaded = false;
-
- final int clientsCount;
- try {
- clientsCount = getClientsCount();
- } finally {
- // Close the database to clear cached readableDatabase/writableDatabase
- // after we've completed our last transaction (db.store()).
- closeDataAccessor();
- }
-
- Logger.debug(LOG_TAG, "Database contains " + clientsCount + " clients.");
- Logger.debug(LOG_TAG, "Server response asserts " + response.weaveRecords() + " records.");
-
- // TODO: persist the response timestamp to know whether to download next time (Bug 726055).
- clientUploadDelegate = new ClientUploadDelegate();
- clientsDelegate.setClientsCount(clientsCount);
-
- // If we upload remote records, checkAndUpload() will be called upon
- // upload success in the delegate. Otherwise call checkAndUpload() now.
- if (modifiedClientsToUpload.size() > 0) {
- // modifiedClientsToUpload is cleared in uploadRemoteRecords, save what we need here
- final List<String> devicesToNotify = new ArrayList<>();
- for (ClientRecord record : modifiedClientsToUpload) {
- if (!TextUtils.isEmpty(record.fxaDeviceId)) {
- devicesToNotify.add(record.fxaDeviceId);
- }
- }
-
- // This method is synchronous, there's no risk of notifying the clients
- // before we actually uploaded the records
- uploadRemoteRecords();
-
- // Notify the clients who got their record written
- notifyClients(devicesToNotify);
-
- return;
- }
- checkAndUpload();
- }
-
- private void notifyClients(final List<String> devicesToNotify) {
- final ExecutorService executor = Executors.newSingleThreadExecutor();
- final Context context = session.getContext();
- final Account account = FirefoxAccounts.getFirefoxAccount(context);
- if (account == null) {
- Log.e(LOG_TAG, "Can't notify other clients: no account");
- return;
- }
- final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
- final ExtendedJSONObject payload = createNotifyDevicesPayload();
-
- final byte[] sessionToken;
- try {
- sessionToken = fxAccount.getSessionToken();
- } catch (AndroidFxAccount.InvalidFxAState invalidFxAState) {
- Log.e(LOG_TAG, "Could not get session token", invalidFxAState);
- return;
- }
-
- // API doc : https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#post-v1accountdevicesnotify
- final FxAccountClient fxAccountClient = new FxAccountClient20(fxAccount.getAccountServerURI(), executor);
- fxAccountClient.notifyDevices(sessionToken, devicesToNotify, payload, NOTIFY_TAB_SENT_TTL_SECS, new FxAccountClient20.RequestDelegate<ExtendedJSONObject>() {
- @Override
- public void handleError(Exception e) {
- Log.e(LOG_TAG, "Error while notifying devices", e);
- }
-
- @Override
- public void handleFailure(FxAccountClientException.FxAccountClientRemoteException e) {
- Log.e(LOG_TAG, "Error while notifying devices", e);
- }
-
- @Override
- public void handleSuccess(ExtendedJSONObject result) {
- Log.i(LOG_TAG, devicesToNotify.size() + " devices notified");
- }
- });
- }
-
- @NonNull
- @SuppressWarnings("unchecked")
- private ExtendedJSONObject createNotifyDevicesPayload() {
- final ExtendedJSONObject payload = new ExtendedJSONObject();
- payload.put("version", 1);
- payload.put("command", "sync:collection_changed");
- final ExtendedJSONObject data = new ExtendedJSONObject();
- final JSONArray collections = new JSONArray();
- collections.add("clients");
- data.put("collections", collections);
- payload.put("data", data);
- return payload;
- }
-
- @Override
- public void handleRequestFailure(SyncStorageResponse response) {
- BaseResource.consumeEntity(response); // We don't need the response at all, and any exception handling shouldn't need the response body.
- localAccountGUIDDownloaded = false;
-
- try {
- Logger.info(LOG_TAG, "Client upload failed. Aborting sync.");
- session.abort(new HTTPFailureException(response), "Client download failed.");
- } finally {
- // Close the database upon failure.
- closeDataAccessor();
- }
- }
-
- @Override
- public void handleRequestError(Exception ex) {
- localAccountGUIDDownloaded = false;
- try {
- Logger.info(LOG_TAG, "Client upload error. Aborting sync.");
- session.abort(ex, "Failure fetching client record.");
- } finally {
- // Close the database upon error.
- closeDataAccessor();
- }
- }
-
- @Override
- public void handleWBO(CryptoRecord record) {
- ClientRecord r;
- try {
- r = (ClientRecord) factory.createRecord(record.decrypt());
- if (clientsDelegate.isLocalGUID(r.guid)) {
- Logger.info(LOG_TAG, "Local client GUID exists on server and was downloaded.");
- localAccountGUIDDownloaded = true;
- handleDownloadedLocalRecord(r);
- } else {
- // Only need to store record if it isn't our local one.
- wipeAndStore(r);
- addCommands(r);
- }
- RepoUtils.logClient(r);
- } catch (Exception e) {
- session.abort(e, "Exception handling client WBO.");
- return;
- }
- }
-
- @Override
- public KeyBundle keyBundle() {
- try {
- return session.keyBundleForCollection(COLLECTION_NAME);
- } catch (NoCollectionKeysSetException e) {
- return null;
- }
- }
- }
-
- public class ClientUploadDelegate extends WBORequestDelegate {
- protected static final String LOG_TAG = "ClientUploadDelegate";
- public Long currentlyUploadingRecordTimestamp;
- public boolean currentlyUploadingLocalRecord;
-
- @Override
- public AuthHeaderProvider getAuthHeaderProvider() {
- return session.getAuthHeaderProvider();
- }
-
- private void setUploadDetails(boolean isLocalRecord) {
- // Use the timestamp for the whole collection per Sync storage 1.1 spec.
- currentlyUploadingRecordTimestamp = session.config.getPersistedServerClientsTimestamp();
- currentlyUploadingLocalRecord = isLocalRecord;
- }
-
- @Override
- public String ifUnmodifiedSince() {
- Long timestampInMilliseconds = currentlyUploadingRecordTimestamp;
-
- // It's the first upload so we don't care about X-If-Unmodified-Since.
- if (timestampInMilliseconds <= 0) {
- return null;
- }
-
- return Utils.millisecondsToDecimalSecondsString(timestampInMilliseconds);
- }
-
- @Override
- public void handleRequestSuccess(SyncStorageResponse response) {
- Logger.debug(LOG_TAG, "Upload succeeded.");
- uploadAttemptsCount.set(0);
-
- // X-Weave-Timestamp is the modified time of uploaded records.
- // Always persist this.
- final long responseTimestamp = response.normalizedWeaveTimestamp();
- Logger.trace(LOG_TAG, "Timestamp from header is: " + responseTimestamp);
-
- if (responseTimestamp == -1) {
- final String message = "Response did not contain a valid timestamp.";
- session.abort(new RuntimeException(message), message);
- return;
- }
-
- BaseResource.consumeEntity(response);
- session.config.persistServerClientsTimestamp(responseTimestamp);
-
- // If we're not uploading our record, we're done here; just
- // clean up and finish.
- if (!currentlyUploadingLocalRecord) {
- // TODO: check failed uploads in body.
- clearRecordsToUpload();
- checkAndUpload();
- return;
- }
-
- // If we're processing our record, we have a little more cleanup
- // to do.
- shouldUploadLocalRecord = false;
- session.config.persistServerClientRecordTimestamp(responseTimestamp);
- session.advance();
- }
-
- @Override
- public void handleRequestFailure(SyncStorageResponse response) {
- int statusCode = response.getStatusCode();
-
- // If upload failed because of `ifUnmodifiedSince` then there are new
- // commands uploaded to our record. We must download and process them first.
- if (!shouldUploadLocalRecord ||
- statusCode == HttpStatus.SC_PRECONDITION_FAILED ||
- uploadAttemptsCount.incrementAndGet() > MAX_UPLOAD_FAILURE_COUNT) {
-
- Logger.debug(LOG_TAG, "Client upload failed. Aborting sync.");
- if (!currentlyUploadingLocalRecord) {
- modifiedClientsToUpload.clear(); // These will be redownloaded.
- }
- BaseResource.consumeEntity(response); // The exception thrown should need the response body.
- session.abort(new HTTPFailureException(response), "Client upload failed.");
- return;
- }
- Logger.trace(LOG_TAG, "Retrying upload…");
- // Preconditions:
- // shouldUploadLocalRecord == true &&
- // statusCode != 412 &&
- // uploadAttemptCount < MAX_UPLOAD_FAILURE_COUNT
- checkAndUpload();
- }
-
- @Override
- public void handleRequestError(Exception ex) {
- Logger.info(LOG_TAG, "Client upload error. Aborting sync.");
- session.abort(ex, "Client upload failed.");
- }
-
- @Override
- public KeyBundle keyBundle() {
- try {
- return session.keyBundleForCollection(COLLECTION_NAME);
- } catch (NoCollectionKeysSetException e) {
- return null;
- }
- }
- }
-
- @Override
- public void execute() throws NoSuchStageException {
- // We can be disabled just for this sync.
- boolean enabledThisSync = session.isEngineLocallyEnabled(STAGE_NAME);
- if (!enabledThisSync) {
- // These log messages look best when they match the messages in ServerSyncStage.
- Logger.debug(LOG_TAG, "Stage " + STAGE_NAME + " disabled just for this sync.");
- Logger.info(LOG_TAG, "Skipping stage " + STAGE_NAME + ".");
- session.advance();
- return;
- }
-
- if (shouldDownload()) {
- downloadClientRecords(); // Will kick off upload, too…
- } else {
- // Upload if necessary.
- }
- }
-
- @Override
- protected void resetLocal() {
- // Clear timestamps and local data.
- session.config.persistServerClientRecordTimestamp(0L); // TODO: roll these into one.
- session.config.persistServerClientsTimestamp(0L);
-
- session.getClientsDelegate().setClientsCount(0);
- try {
- getClientsDatabaseAccessor().wipeDB();
- } finally {
- closeDataAccessor();
- }
- }
-
- @Override
- protected void wipeLocal() throws Exception {
- // Nothing more to do.
- this.resetLocal();
- }
-
- @Override
- public Integer getStorageVersion() {
- return VersionConstants.CLIENTS_ENGINE_VERSION;
- }
-
- protected String getLocalClientVersion() {
- return AppConstants.MOZ_APP_VERSION;
- }
-
- @SuppressWarnings("unchecked")
- protected JSONArray getLocalClientProtocols() {
- final JSONArray protocols = new JSONArray();
- protocols.add(ClientRecord.PROTOCOL_LEGACY_SYNC);
- protocols.add(ClientRecord.PROTOCOL_FXA_SYNC);
- return protocols;
- }
-
- protected ClientRecord newLocalClientRecord(ClientsDataDelegate delegate) {
- final String ourGUID = delegate.getAccountGUID();
- final String ourName = delegate.getClientName();
-
- ClientRecord r = new ClientRecord(ourGUID);
- r.name = ourName;
- r.version = getLocalClientVersion();
- r.protocols = getLocalClientProtocols();
-
- r.os = "Android";
- r.application = AppConstants.MOZ_APP_DISPLAYNAME;
- r.appPackage = AppConstants.ANDROID_PACKAGE_NAME;
- r.device = android.os.Build.MODEL;
- r.formfactor = delegate.getFormFactor();
-
- Context context = session.getContext();
- final Account account = FirefoxAccounts.getFirefoxAccount(context);
- if (account != null) {
- final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
- final String deviceId = fxAccount.getDeviceId();
- if (!TextUtils.isEmpty(deviceId)) {
- r.fxaDeviceId = deviceId;
- }
- }
-
- return r;
- }
-
- // TODO: Bug 726055 - More considered handling of when to sync.
- protected boolean shouldDownload() {
- // Ask info/collections whether a download is needed.
- return true;
- }
-
- protected boolean shouldUpload() {
- if (shouldUploadLocalRecord) {
- return true;
- }
-
- long lastUpload = session.config.getPersistedServerClientRecordTimestamp(); // Defaults to 0.
- if (lastUpload == 0) {
- return true;
- }
-
- if (session.getClientsDelegate().getLastModifiedTimestamp() > lastUpload) {
- // Something's changed locally since we last uploaded.
- return true;
- }
-
- // Note the opportunity for clock drift problems here.
- // TODO: if we track download times, we can use the timestamp of most
- // recent download response instead of the current time.
- long now = System.currentTimeMillis();
- long age = now - lastUpload;
- return age >= CLIENTS_TTL_REFRESH;
- }
-
- protected void handleDownloadedLocalRecord(ClientRecord r) {
- session.config.persistServerClientRecordTimestamp(r.lastModified);
-
- if (!getLocalClientVersion().equals(r.version) ||
- !getLocalClientProtocols().equals(r.protocols)) {
- shouldUploadLocalRecord = true;
- }
- processCommands(r.commands);
- }
-
- protected void processCommands(JSONArray commands) {
- if (commands == null ||
- commands.size() == 0) {
- return;
- }
-
- shouldUploadLocalRecord = true;
- CommandProcessor processor = CommandProcessor.getProcessor();
-
- for (Object o : commands) {
- processor.processCommand(session, new ExtendedJSONObject((JSONObject) o));
- }
- }
-
- @SuppressWarnings("unchecked")
- protected void addCommands(ClientRecord record) throws NullCursorException {
- Logger.trace(LOG_TAG, "Adding commands to " + record.guid);
- List<Command> commands = db.fetchCommandsForClient(record.guid);
-
- if (commands == null || commands.size() == 0) {
- Logger.trace(LOG_TAG, "No commands to add.");
- return;
- }
-
- for (Command command : commands) {
- JSONObject jsonCommand = command.asJSONObject();
- if (record.commands == null) {
- record.commands = new JSONArray();
- }
- record.commands.add(jsonCommand);
- }
- modifiedClientsToUpload.add(record);
- }
-
- @SuppressWarnings("unchecked")
- protected void uploadRemoteRecords() {
- Logger.trace(LOG_TAG, "In uploadRemoteRecords. Uploading " + modifiedClientsToUpload.size() + " records" );
-
- for (ClientRecord r : modifiedClientsToUpload) {
- Logger.trace(LOG_TAG, ">> Uploading record " + r.guid + ": " + r.name);
- }
-
- if (modifiedClientsToUpload.size() == 1) {
- ClientRecord record = modifiedClientsToUpload.get(0);
- Logger.debug(LOG_TAG, "Only 1 remote record to upload.");
- Logger.debug(LOG_TAG, "Record last modified: " + record.lastModified);
- CryptoRecord cryptoRecord = encryptClientRecord(record);
- if (cryptoRecord != null) {
- clientUploadDelegate.setUploadDetails(false);
- this.uploadClientRecord(cryptoRecord);
- }
- return;
- }
-
- JSONArray cryptoRecords = new JSONArray();
- for (ClientRecord record : modifiedClientsToUpload) {
- Logger.trace(LOG_TAG, "Record " + record.guid + " is being uploaded" );
-
- CryptoRecord cryptoRecord = encryptClientRecord(record);
- cryptoRecords.add(cryptoRecord.toJSONObject());
- }
- Logger.debug(LOG_TAG, "Uploading records: " + cryptoRecords.size());
- clientUploadDelegate.setUploadDetails(false);
- this.uploadClientRecords(cryptoRecords);
- }
-
- protected void checkAndUpload() {
- if (!shouldUpload()) {
- Logger.debug(LOG_TAG, "Not uploading client record.");
- session.advance();
- return;
- }
-
- final ClientRecord localClient = newLocalClientRecord(session.getClientsDelegate());
- clientUploadDelegate.setUploadDetails(true);
- CryptoRecord cryptoRecord = encryptClientRecord(localClient);
- if (cryptoRecord != null) {
- this.uploadClientRecord(cryptoRecord);
- }
- }
-
- protected CryptoRecord encryptClientRecord(ClientRecord recordToUpload) {
- // Generate CryptoRecord from ClientRecord to upload.
- final String encryptionFailure = "Couldn't encrypt new client record.";
-
- try {
- CryptoRecord cryptoRecord = recordToUpload.getEnvelope();
- cryptoRecord.keyBundle = clientUploadDelegate.keyBundle();
- if (cryptoRecord.keyBundle == null) {
- session.abort(new NoCollectionKeysSetException(), "No collection keys set.");
- return null;
- }
- return cryptoRecord.encrypt();
- } catch (UnsupportedEncodingException e) {
- session.abort(e, encryptionFailure + " Unsupported encoding.");
- } catch (CryptoException e) {
- session.abort(e, encryptionFailure);
- }
- return null;
- }
-
- public void clearRecordsToUpload() {
- try {
- getClientsDatabaseAccessor().wipeCommandsTable();
- modifiedClientsToUpload.clear();
- } finally {
- closeDataAccessor();
- }
- }
-
- protected void downloadClientRecords() {
- shouldWipe = true;
- clientDownloadDelegate = makeClientDownloadDelegate();
-
- try {
- final URI getURI = session.config.collectionURI(COLLECTION_NAME, true);
- final SyncStorageCollectionRequest request = new SyncStorageCollectionRequest(getURI);
- request.delegate = clientDownloadDelegate;
-
- Logger.trace(LOG_TAG, "Downloading client records.");
- request.get();
- } catch (URISyntaxException e) {
- session.abort(e, "Invalid URI.");
- }
- }
-
- protected void uploadClientRecords(JSONArray records) {
- Logger.trace(LOG_TAG, "Uploading " + records.size() + " client records.");
- try {
- final URI postURI = session.config.collectionURI(COLLECTION_NAME, false);
- final SyncStorageRecordRequest request = new SyncStorageRecordRequest(postURI);
- request.delegate = clientUploadDelegate;
- request.post(records);
- } catch (URISyntaxException e) {
- session.abort(e, "Invalid URI.");
- } catch (Exception e) {
- session.abort(e, "Unable to parse body.");
- }
- }
-
- /**
- * Upload a client record via HTTP POST to the parent collection.
- */
- protected void uploadClientRecord(CryptoRecord record) {
- Logger.debug(LOG_TAG, "Uploading client record " + record.guid);
- try {
- final URI postURI = session.config.collectionURI(COLLECTION_NAME);
- final SyncStorageRecordRequest request = new SyncStorageRecordRequest(postURI);
- request.delegate = clientUploadDelegate;
- request.post(record);
- } catch (URISyntaxException e) {
- session.abort(e, "Invalid URI.");
- }
- }
-
- protected ClientDownloadDelegate makeClientDownloadDelegate() {
- return new ClientDownloadDelegate();
- }
-
- protected void wipeAndStore(ClientRecord record) {
- final ClientsDatabaseAccessor db = getClientsDatabaseAccessor();
- if (shouldWipe) {
- db.wipeClientsTable();
- shouldWipe = false;
- }
- if (record != null) {
- db.store(record);
- }
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/UploadMetaGlobalStage.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/UploadMetaGlobalStage.java
deleted file mode 100644
index 77846c212..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/UploadMetaGlobalStage.java
+++ /dev/null
@@ -1,18 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.stage;
-
-
-public class UploadMetaGlobalStage extends AbstractNonRepositorySyncStage {
- public static final String LOG_TAG = "UploadMGStage";
-
- @Override
- public void execute() throws NoSuchStageException {
- if (session.hasUpdatedMetaGlobal()) {
- session.uploadUpdatedMetaGlobal();
- }
- session.advance();
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/ConcurrentRecordConsumer.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/ConcurrentRecordConsumer.java
deleted file mode 100644
index 9b1ef3e85..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/ConcurrentRecordConsumer.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.synchronizer;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-/**
- * Consume records from a queue inside a RecordsChannel, as fast as we can.
- * TODO: rewrite this in terms of an ExecutorService and a CompletionService.
- * See Bug 713483.
- *
- * @author rnewman
- *
- */
-class ConcurrentRecordConsumer extends RecordConsumer {
- private static final String LOG_TAG = "CRecordConsumer";
-
- /**
- * When this is true and all records have been processed, the consumer
- * will notify its delegate.
- */
- protected boolean allRecordsQueued = false;
- private long counter = 0;
-
- public ConcurrentRecordConsumer(RecordsConsumerDelegate delegate) {
- this.delegate = delegate;
- }
-
- private final Object monitor = new Object();
- @Override
- public void doNotify() {
- synchronized (monitor) {
- monitor.notify();
- }
- }
-
- @Override
- public void queueFilled() {
- Logger.debug(LOG_TAG, "Queue filled.");
- synchronized (monitor) {
- this.allRecordsQueued = true;
- monitor.notify();
- }
- }
-
- @Override
- public void halt() {
- synchronized (monitor) {
- this.stopImmediately = true;
- monitor.notify();
- }
- }
-
- private final Object countMonitor = new Object();
- @Override
- public void stored() {
- Logger.trace(LOG_TAG, "Record stored. Notifying.");
- synchronized (countMonitor) {
- counter++;
- }
- }
-
- private void consumerIsDone() {
- Logger.debug(LOG_TAG, "Consumer is done. Processed " + counter + ((counter == 1) ? " record." : " records."));
- delegate.consumerIsDone(!allRecordsQueued);
- }
-
- @Override
- public void run() {
- Record record;
-
- while (true) {
- // The queue is concurrent-safe.
- while ((record = delegate.getQueue().poll()) != null) {
- synchronized (monitor) {
- Logger.trace(LOG_TAG, "run() took monitor.");
- if (stopImmediately) {
- Logger.debug(LOG_TAG, "Stopping immediately. Clearing queue.");
- delegate.getQueue().clear();
- Logger.debug(LOG_TAG, "Notifying consumer.");
- consumerIsDone();
- return;
- }
- Logger.debug(LOG_TAG, "run() dropped monitor.");
- }
-
- Logger.trace(LOG_TAG, "Storing record with guid " + record.guid + ".");
- try {
- delegate.store(record);
- } catch (Exception e) {
- // TODO: Bug 709371: track records that failed to apply.
- Logger.error(LOG_TAG, "Caught error in store.", e);
- }
- Logger.trace(LOG_TAG, "Done with record.");
- }
- synchronized (monitor) {
- Logger.trace(LOG_TAG, "run() took monitor.");
-
- if (allRecordsQueued) {
- Logger.debug(LOG_TAG, "Done with records and no more to come. Notifying consumerIsDone.");
- consumerIsDone();
- return;
- }
- if (stopImmediately) {
- Logger.debug(LOG_TAG, "Done with records and told to stop immediately. Notifying consumerIsDone.");
- consumerIsDone();
- return;
- }
- try {
- Logger.debug(LOG_TAG, "Not told to stop but no records. Waiting.");
- monitor.wait(10000);
- } catch (InterruptedException e) {
- // TODO
- }
- Logger.trace(LOG_TAG, "run() dropped monitor.");
- }
- }
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordConsumer.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordConsumer.java
deleted file mode 100644
index 35e57d9c2..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordConsumer.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.synchronizer;
-
-public abstract class RecordConsumer implements Runnable {
-
- public abstract void stored();
-
- /**
- * There are no more store items to arrive at the delegate.
- * When you're done, take care of finishing up.
- */
- public abstract void queueFilled();
- public abstract void halt();
-
- public abstract void doNotify();
-
- protected boolean stopImmediately = false;
- protected RecordsConsumerDelegate delegate;
-
- public RecordConsumer() {
- super();
- }
-} \ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordsChannel.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordsChannel.java
deleted file mode 100644
index f929cdc75..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordsChannel.java
+++ /dev/null
@@ -1,292 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.synchronizer;
-
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.ThreadPool;
-import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
-import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-import org.mozilla.gecko.sync.repositories.delegates.DeferredRepositorySessionBeginDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.DeferredRepositorySessionStoreDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-/**
- * Pulls records from `source`, applying them to `sink`.
- * Notifies its delegate of errors and completion.
- *
- * All stores (initiated by a fetch) must have been completed before storeDone
- * is invoked on the sink. This is to avoid the existing stored items being
- * considered as the total set, with onStoreCompleted being called when they're
- * done:
- *
- * store(A) store(B)
- * store(C) storeDone()
- * store(A) finishes. Store job begins.
- * store(C) finishes. Store job begins.
- * storeDone() finishes.
- * Storing of A complete.
- * Storing of C complete.
- * We're done! Call onStoreCompleted.
- * store(B) finishes... uh oh.
- *
- * In other words, storeDone must be gated on the synchronous invocation of every store.
- *
- * Similarly, we require that every store callback have returned before onStoreCompleted is invoked.
- *
- * This whole set of guarantees should be achievable thusly:
- *
- * * The fetch process must run in a single thread, and invoke store()
- * synchronously. After processing every incoming record, storeDone is called,
- * setting a flag.
- * If the fetch cannot be implicitly queued, it must be explicitly queued.
- * In this implementation, we assume that fetch callbacks are strictly ordered in this way.
- *
- * * The store process must be (implicitly or explicitly) queued. When the
- * queue empties, the consumer checks the storeDone flag. If it's set, and the
- * queue is exhausted, invoke onStoreCompleted.
- *
- * RecordsChannel exists to enforce this ordering of operations.
- *
- * @author rnewman
- *
- */
-public class RecordsChannel implements
- RepositorySessionFetchRecordsDelegate,
- RepositorySessionStoreDelegate,
- RecordsConsumerDelegate,
- RepositorySessionBeginDelegate {
-
- private static final String LOG_TAG = "RecordsChannel";
- public RepositorySession source;
- public RepositorySession sink;
- private final RecordsChannelDelegate delegate;
- private long fetchEnd = -1;
-
- protected final AtomicInteger numFetched = new AtomicInteger();
- protected final AtomicInteger numFetchFailed = new AtomicInteger();
- protected final AtomicInteger numStored = new AtomicInteger();
- protected final AtomicInteger numStoreFailed = new AtomicInteger();
-
- public RecordsChannel(RepositorySession source, RepositorySession sink, RecordsChannelDelegate delegate) {
- this.source = source;
- this.sink = sink;
- this.delegate = delegate;
- }
-
- /*
- * We push fetched records into a queue.
- * A separate thread is waiting for us to notify it of work to do.
- * When we tell it to stop, it'll stop. We do that when the fetch
- * is completed.
- * When it stops, we tell the sink that there are no more records,
- * and wait for the sink to tell us that storing is done.
- * Then we notify our delegate of completion.
- */
- private RecordConsumer consumer;
- private boolean waitingForQueueDone = false;
- private final ConcurrentLinkedQueue<Record> toProcess = new ConcurrentLinkedQueue<Record>();
-
- @Override
- public ConcurrentLinkedQueue<Record> getQueue() {
- return toProcess;
- }
-
- protected boolean isReady() {
- return source.isActive() && sink.isActive();
- }
-
- /**
- * Get the number of records fetched so far.
- *
- * @return number of fetches.
- */
- public int getFetchCount() {
- return numFetched.get();
- }
-
- /**
- * Get the number of fetch failures recorded so far.
- *
- * @return number of fetch failures.
- */
- public int getFetchFailureCount() {
- return numFetchFailed.get();
- }
-
- /**
- * Get the number of store attempts (successful or not) so far.
- *
- * @return number of stores attempted.
- */
- public int getStoreCount() {
- return numStored.get();
- }
-
- /**
- * Get the number of store failures recorded so far.
- *
- * @return number of store failures.
- */
- public int getStoreFailureCount() {
- return numStoreFailed.get();
- }
-
- /**
- * Start records flowing through the channel.
- */
- public void flow() {
- if (!isReady()) {
- RepositorySession failed = source;
- if (source.isActive()) {
- failed = sink;
- }
- this.delegate.onFlowBeginFailed(this, new SessionNotBegunException(failed));
- return;
- }
-
- if (!source.dataAvailable()) {
- Logger.info(LOG_TAG, "No data available: short-circuiting flow from source " + source);
- long now = System.currentTimeMillis();
- this.delegate.onFlowCompleted(this, now, now);
- return;
- }
-
- sink.setStoreDelegate(this);
- numFetched.set(0);
- numFetchFailed.set(0);
- numStored.set(0);
- numStoreFailed.set(0);
- // Start a consumer thread.
- this.consumer = new ConcurrentRecordConsumer(this);
- ThreadPool.run(this.consumer);
- waitingForQueueDone = true;
- source.fetchSince(source.getLastSyncTimestamp(), this);
- }
-
- /**
- * Begin both sessions, invoking flow() when done.
- * @throws InvalidSessionTransitionException
- */
- public void beginAndFlow() throws InvalidSessionTransitionException {
- Logger.trace(LOG_TAG, "Beginning source.");
- source.begin(this);
- }
-
- @Override
- public void store(Record record) {
- numStored.incrementAndGet();
- try {
- sink.store(record);
- } catch (NoStoreDelegateException e) {
- Logger.error(LOG_TAG, "Got NoStoreDelegateException in RecordsChannel.store(). This should not occur. Aborting.", e);
- delegate.onFlowStoreFailed(this, e, record.guid);
- }
- }
-
- @Override
- public void onFetchFailed(Exception ex, Record record) {
- Logger.warn(LOG_TAG, "onFetchFailed. Calling for immediate stop.", ex);
- numFetchFailed.incrementAndGet();
- this.consumer.halt();
- delegate.onFlowFetchFailed(this, ex);
- }
-
- @Override
- public void onFetchedRecord(Record record) {
- numFetched.incrementAndGet();
- this.toProcess.add(record);
- this.consumer.doNotify();
- }
-
- @Override
- public void onFetchCompleted(final long fetchEnd) {
- Logger.trace(LOG_TAG, "onFetchCompleted. Stopping consumer once stores are done.");
- Logger.trace(LOG_TAG, "Fetch timestamp is " + fetchEnd);
- this.fetchEnd = fetchEnd;
- this.consumer.queueFilled();
- }
-
- @Override
- public void onRecordStoreFailed(Exception ex, String recordGuid) {
- Logger.trace(LOG_TAG, "Failed to store record with guid " + recordGuid);
- numStoreFailed.incrementAndGet();
- this.consumer.stored();
- delegate.onFlowStoreFailed(this, ex, recordGuid);
- // TODO: abort?
- }
-
- @Override
- public void onRecordStoreSucceeded(String guid) {
- Logger.trace(LOG_TAG, "Stored record with guid " + guid);
- this.consumer.stored();
- }
-
-
- @Override
- public void consumerIsDone(boolean allRecordsQueued) {
- Logger.trace(LOG_TAG, "Consumer is done. Are we waiting for it? " + waitingForQueueDone);
- if (waitingForQueueDone) {
- waitingForQueueDone = false;
- this.sink.storeDone(); // Now we'll be waiting for onStoreCompleted.
- }
- }
-
- @Override
- public void onStoreCompleted(long storeEnd) {
- Logger.trace(LOG_TAG, "onStoreCompleted. Notifying delegate of onFlowCompleted. " +
- "Fetch end is " + fetchEnd + ", store end is " + storeEnd);
- // TODO: synchronize on consumer callback?
- delegate.onFlowCompleted(this, fetchEnd, storeEnd);
- }
-
- @Override
- public void onBeginFailed(Exception ex) {
- delegate.onFlowBeginFailed(this, ex);
- }
-
- @Override
- public void onBeginSucceeded(RepositorySession session) {
- if (session == source) {
- Logger.trace(LOG_TAG, "Source session began. Beginning sink session.");
- try {
- sink.begin(this);
- } catch (InvalidSessionTransitionException e) {
- onBeginFailed(e);
- return;
- }
- }
- if (session == sink) {
- Logger.trace(LOG_TAG, "Sink session began. Beginning flow.");
- this.flow();
- return;
- }
-
- // TODO: error!
- }
-
- @Override
- public RepositorySessionStoreDelegate deferredStoreDelegate(final ExecutorService executor) {
- return new DeferredRepositorySessionStoreDelegate(this, executor);
- }
-
- @Override
- public RepositorySessionBeginDelegate deferredBeginDelegate(final ExecutorService executor) {
- return new DeferredRepositorySessionBeginDelegate(this, executor);
- }
-
- @Override
- public RepositorySessionFetchRecordsDelegate deferredFetchDelegate(ExecutorService executor) {
- // Lie outright. We know that all of our fetch methods are safe.
- return this;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordsChannelDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordsChannelDelegate.java
deleted file mode 100644
index 8daeb7ad5..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordsChannelDelegate.java
+++ /dev/null
@@ -1,13 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.synchronizer;
-
-public interface RecordsChannelDelegate {
- public void onFlowCompleted(RecordsChannel recordsChannel, long fetchEnd, long storeEnd);
- public void onFlowBeginFailed(RecordsChannel recordsChannel, Exception ex);
- public void onFlowFetchFailed(RecordsChannel recordsChannel, Exception ex);
- public void onFlowStoreFailed(RecordsChannel recordsChannel, Exception ex, String recordGuid);
- public void onFlowFinishFailed(RecordsChannel recordsChannel, Exception ex);
-} \ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordsConsumerDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordsConsumerDelegate.java
deleted file mode 100644
index a00abf848..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/RecordsConsumerDelegate.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.synchronizer;
-
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-interface RecordsConsumerDelegate {
- public abstract ConcurrentLinkedQueue<Record> getQueue();
-
- /**
- * Called when no more items will be processed.
- * If forced is true, the consumer is terminating because it was told to halt;
- * not all items will necessarily have been processed.
- * If forced is false, the consumer has invoked store and received an onStoreCompleted callback.
- * @param forced
- */
- public abstract void consumerIsDone(boolean forced);
- public abstract void store(Record record);
-} \ No newline at end of file
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SerialRecordConsumer.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SerialRecordConsumer.java
deleted file mode 100644
index 6ee44ea2b..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SerialRecordConsumer.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.synchronizer;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.repositories.domain.Record;
-
-/**
- * Consume records from a queue inside a RecordsChannel, storing them serially.
- * @author rnewman
- *
- */
-class SerialRecordConsumer extends RecordConsumer {
- private static final String LOG_TAG = "SerialRecordConsumer";
- protected boolean stopEventually = false;
- private volatile long counter = 0;
-
- public SerialRecordConsumer(RecordsConsumerDelegate delegate) {
- this.delegate = delegate;
- }
-
- private final Object monitor = new Object();
- @Override
- public void doNotify() {
- synchronized (monitor) {
- monitor.notify();
- }
- }
-
- @Override
- public void queueFilled() {
- Logger.debug(LOG_TAG, "Queue filled.");
- synchronized (monitor) {
- this.stopEventually = true;
- monitor.notify();
- }
- }
-
- @Override
- public void halt() {
- Logger.debug(LOG_TAG, "Halting.");
- synchronized (monitor) {
- this.stopEventually = true;
- this.stopImmediately = true;
- monitor.notify();
- }
- }
-
- private final Object storeSerializer = new Object();
- @Override
- public void stored() {
- Logger.debug(LOG_TAG, "Record stored. Notifying.");
- synchronized (storeSerializer) {
- Logger.debug(LOG_TAG, "stored() took storeSerializer.");
- counter++;
- storeSerializer.notify();
- Logger.debug(LOG_TAG, "stored() dropped storeSerializer.");
- }
- }
- private void storeSerially(Record record) {
- Logger.debug(LOG_TAG, "New record to store.");
- synchronized (storeSerializer) {
- Logger.debug(LOG_TAG, "storeSerially() took storeSerializer.");
- Logger.debug(LOG_TAG, "Storing...");
- try {
- this.delegate.store(record);
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Got exception in store. Not waiting.", e);
- return; // So we don't block for a stored() that never comes.
- }
- try {
- Logger.debug(LOG_TAG, "Waiting...");
- storeSerializer.wait();
- } catch (InterruptedException e) {
- // TODO
- }
- Logger.debug(LOG_TAG, "storeSerially() dropped storeSerializer.");
- }
- }
-
- private void consumerIsDone() {
- long counterNow = this.counter;
- Logger.info(LOG_TAG, "Consumer is done. Processed " + counterNow + ((counterNow == 1) ? " record." : " records."));
- delegate.consumerIsDone(stopImmediately);
- }
-
- @Override
- public void run() {
- while (true) {
- synchronized (monitor) {
- Logger.debug(LOG_TAG, "run() took monitor.");
- if (stopImmediately) {
- Logger.debug(LOG_TAG, "Stopping immediately. Clearing queue.");
- delegate.getQueue().clear();
- Logger.debug(LOG_TAG, "Notifying consumer.");
- consumerIsDone();
- return;
- }
- Logger.debug(LOG_TAG, "run() dropped monitor.");
- }
- // The queue is concurrent-safe.
- while (!delegate.getQueue().isEmpty()) {
- Logger.debug(LOG_TAG, "Grabbing record...");
- Record record = delegate.getQueue().remove();
- // Block here, allowing us to process records
- // serially.
- Logger.debug(LOG_TAG, "Invoking storeSerially...");
- this.storeSerially(record);
- Logger.debug(LOG_TAG, "Done with record.");
- }
- synchronized (monitor) {
- Logger.debug(LOG_TAG, "run() took monitor.");
-
- if (stopEventually) {
- Logger.debug(LOG_TAG, "Done with records and told to stop. Notifying consumer.");
- consumerIsDone();
- return;
- }
- try {
- Logger.debug(LOG_TAG, "Not told to stop but no records. Waiting.");
- monitor.wait(10000);
- } catch (InterruptedException e) {
- // TODO
- }
- Logger.debug(LOG_TAG, "run() dropped monitor.");
- }
- }
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/ServerLocalSynchronizer.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/ServerLocalSynchronizer.java
deleted file mode 100644
index ac4f48789..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/ServerLocalSynchronizer.java
+++ /dev/null
@@ -1,18 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.synchronizer;
-
-/**
- * A <code>SynchronizerSession</code> designed to be used between a remote
- * server and a local repository.
- * <p>
- * See <code>ServerLocalSynchronizerSession</code> for error handling details.
- */
-public class ServerLocalSynchronizer extends Synchronizer {
- @Override
- public SynchronizerSession newSynchronizerSession() {
- return new ServerLocalSynchronizerSession(this, this);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/ServerLocalSynchronizerSession.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/ServerLocalSynchronizerSession.java
deleted file mode 100644
index dc9eb01a0..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/ServerLocalSynchronizerSession.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.synchronizer;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.repositories.FetchFailedException;
-import org.mozilla.gecko.sync.repositories.StoreFailedException;
-
-/**
- * A <code>SynchronizerSession</code> designed to be used between a remote
- * server and a local repository.
- * <p>
- * Handles failure cases as follows (in the order they will occur during a sync):
- * <ul>
- * <li>Remote fetch failures abort.</li>
- * <li>Local store failures are ignored.</li>
- * <li>Local fetch failures abort.</li>
- * <li>Remote store failures abort.</li>
- * </ul>
- */
-public class ServerLocalSynchronizerSession extends SynchronizerSession {
- protected static final String LOG_TAG = "ServLocSynchronizerSess";
-
- public ServerLocalSynchronizerSession(Synchronizer synchronizer, SynchronizerSessionDelegate delegate) {
- super(synchronizer, delegate);
- }
-
- @Override
- public void onFirstFlowCompleted(RecordsChannel recordsChannel, long fetchEnd, long storeEnd) {
- // Fetch failures always abort.
- int numRemoteFetchFailed = recordsChannel.getFetchFailureCount();
- if (numRemoteFetchFailed > 0) {
- final String message = "Got " + numRemoteFetchFailed + " failures fetching remote records!";
- Logger.warn(LOG_TAG, message + " Aborting session.");
- delegate.onSynchronizeFailed(this, new FetchFailedException(), message);
- return;
- }
- Logger.trace(LOG_TAG, "No failures fetching remote records.");
-
- // Local store failures are ignored.
- int numLocalStoreFailed = recordsChannel.getStoreFailureCount();
- if (numLocalStoreFailed > 0) {
- final String message = "Got " + numLocalStoreFailed + " failures storing local records!";
- Logger.warn(LOG_TAG, message + " Ignoring local store failures and continuing synchronizer session.");
- } else {
- Logger.trace(LOG_TAG, "No failures storing local records.");
- }
-
- super.onFirstFlowCompleted(recordsChannel, fetchEnd, storeEnd);
- }
-
- @Override
- public void onSecondFlowCompleted(RecordsChannel recordsChannel, long fetchEnd, long storeEnd) {
- // Fetch failures always abort.
- int numLocalFetchFailed = recordsChannel.getFetchFailureCount();
- if (numLocalFetchFailed > 0) {
- final String message = "Got " + numLocalFetchFailed + " failures fetching local records!";
- Logger.warn(LOG_TAG, message + " Aborting session.");
- delegate.onSynchronizeFailed(this, new FetchFailedException(), message);
- return;
- }
- Logger.trace(LOG_TAG, "No failures fetching local records.");
-
- // Remote store failures abort!
- int numRemoteStoreFailed = recordsChannel.getStoreFailureCount();
- if (numRemoteStoreFailed > 0) {
- final String message = "Got " + numRemoteStoreFailed + " failures storing remote records!";
- Logger.warn(LOG_TAG, message + " Aborting session.");
- delegate.onSynchronizeFailed(this, new StoreFailedException(), message);
- return;
- }
- Logger.trace(LOG_TAG, "No failures storing remote records.");
-
- super.onSecondFlowCompleted(recordsChannel, fetchEnd, storeEnd);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SessionNotBegunException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SessionNotBegunException.java
deleted file mode 100644
index 20c7fcd56..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SessionNotBegunException.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.synchronizer;
-
-import org.mozilla.gecko.sync.SyncException;
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-
-public class SessionNotBegunException extends SyncException {
-
- public RepositorySession failed;
-
- public SessionNotBegunException(RepositorySession failed) {
- this.failed = failed;
- }
-
- private static final long serialVersionUID = -4565241449897072841L;
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/Synchronizer.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/Synchronizer.java
deleted file mode 100644
index cc15b35a9..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/Synchronizer.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.synchronizer;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.SynchronizerConfiguration;
-import org.mozilla.gecko.sync.repositories.Repository;
-import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
-
-import android.content.Context;
-
-/**
- * I perform a sync.
- *
- * Initialize me by calling `load` with a SynchronizerConfiguration.
- *
- * Start synchronizing by calling `synchronize` with a SynchronizerDelegate. I
- * provide coarse-grained feedback by calling my delegate's callback methods.
- *
- * I always call exactly one of my delegate's `onSynchronized` or
- * `onSynchronizeFailed` callback methods. In addition, I call
- * `onSynchronizeAborted` before `onSynchronizeFailed` when I encounter a fetch,
- * store, or session error while synchronizing.
- *
- * After synchronizing, call `save` to get back a SynchronizerConfiguration with
- * updated bundle information.
- */
-public class Synchronizer implements SynchronizerSessionDelegate {
- public static final String LOG_TAG = "SyncDelSDelegate";
-
- protected String configSyncID; // Used to pass syncID from load() back into save().
-
- protected SynchronizerDelegate synchronizerDelegate;
-
- protected SynchronizerSession session = null;
-
- public SynchronizerSession getSynchronizerSession() {
- return session;
- }
-
- @Override
- public void onInitialized(SynchronizerSession session) {
- session.synchronize();
- }
-
- @Override
- public void onSynchronized(SynchronizerSession synchronizerSession) {
- Logger.debug(LOG_TAG, "Got onSynchronized.");
- Logger.debug(LOG_TAG, "Notifying SynchronizerDelegate.");
- this.synchronizerDelegate.onSynchronized(synchronizerSession.getSynchronizer());
- }
-
- @Override
- public void onSynchronizeSkipped(SynchronizerSession synchronizerSession) {
- Logger.debug(LOG_TAG, "Got onSynchronizeSkipped.");
- Logger.debug(LOG_TAG, "Notifying SynchronizerDelegate as if on success.");
- this.synchronizerDelegate.onSynchronized(synchronizerSession.getSynchronizer());
- }
-
- @Override
- public void onSynchronizeFailed(SynchronizerSession session,
- Exception lastException, String reason) {
- this.synchronizerDelegate.onSynchronizeFailed(session.getSynchronizer(), lastException, reason);
- }
-
- public Repository repositoryA;
- public Repository repositoryB;
- public RepositorySessionBundle bundleA;
- public RepositorySessionBundle bundleB;
-
- /**
- * Fetch a synchronizer session appropriate for this <code>Synchronizer</code>
- */
- protected SynchronizerSession newSynchronizerSession() {
- return new SynchronizerSession(this, this);
- }
-
- /**
- * Start synchronizing, calling delegate's callback methods.
- */
- public void synchronize(Context context, SynchronizerDelegate delegate) {
- this.synchronizerDelegate = delegate;
- this.session = newSynchronizerSession();
- this.session.init(context, bundleA, bundleB);
- }
-
- public SynchronizerConfiguration save() {
- return new SynchronizerConfiguration(configSyncID, bundleA, bundleB);
- }
-
- /**
- * Set my repository session bundles from a SynchronizerConfiguration.
- *
- * This method is not thread-safe.
- *
- * @param config
- */
- public void load(SynchronizerConfiguration config) {
- bundleA = config.remoteBundle;
- bundleB = config.localBundle;
- configSyncID = config.syncID;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SynchronizerDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SynchronizerDelegate.java
deleted file mode 100644
index a290188ab..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SynchronizerDelegate.java
+++ /dev/null
@@ -1,10 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.synchronizer;
-
-public interface SynchronizerDelegate {
- public void onSynchronized(Synchronizer synchronizer);
- public void onSynchronizeFailed(Synchronizer synchronizer, Exception lastException, String reason);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SynchronizerSession.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SynchronizerSession.java
deleted file mode 100644
index c4d244b4c..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SynchronizerSession.java
+++ /dev/null
@@ -1,425 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.synchronizer;
-
-
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.sync.repositories.InactiveSessionException;
-import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
-import org.mozilla.gecko.sync.repositories.delegates.DeferrableRepositorySessionCreationDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.DeferredRepositorySessionFinishDelegate;
-import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
-
-import android.content.Context;
-
-/**
- * I coordinate the moving parts of a sync started by
- * {@link Synchronizer#synchronize}.
- *
- * I flow records twice: first from A to B, and then from B to A. I provide
- * fine-grained feedback by calling my delegate's callback methods.
- *
- * Initialize me by creating me with a Synchronizer and a
- * SynchronizerSessionDelegate. Kick things off by calling `init` with two
- * RepositorySessionBundles, and then call `synchronize` in your `onInitialized`
- * callback.
- *
- * I always call exactly one of my delegate's `onInitialized` or
- * `onSessionError` callback methods from `init`.
- *
- * I call my delegate's `onSynchronizeSkipped` callback method if there is no
- * data to be synchronized in `synchronize`.
- *
- * In addition, I call `onFetchError`, `onStoreError`, and `onSessionError` when
- * I encounter a fetch, store, or session error while synchronizing.
- *
- * Typically my delegate will call `abort` in its error callbacks, which will
- * call my delegate's `onSynchronizeAborted` method and halt the sync.
- *
- * I always call exactly one of my delegate's `onSynchronized` or
- * `onSynchronizeFailed` callback methods if I have not seen an error.
- */
-public class SynchronizerSession
-extends DeferrableRepositorySessionCreationDelegate
-implements RecordsChannelDelegate,
- RepositorySessionFinishDelegate {
-
- protected static final String LOG_TAG = "SynchronizerSession";
- protected Synchronizer synchronizer;
- protected SynchronizerSessionDelegate delegate;
- protected Context context;
-
- /*
- * Computed during init.
- */
- private RepositorySession sessionA;
- private RepositorySession sessionB;
- private RepositorySessionBundle bundleA;
- private RepositorySessionBundle bundleB;
-
- // Bug 726054: just like desktop, we track our last interaction with the server,
- // not the last record timestamp that we fetched. This ensures that we don't re-
- // download the records we just uploaded, at the cost of skipping any records
- // that a concurrently syncing client has uploaded.
- private long pendingATimestamp = -1;
- private long pendingBTimestamp = -1;
- private long storeEndATimestamp = -1;
- private long storeEndBTimestamp = -1;
- private boolean flowAToBCompleted = false;
- private boolean flowBToACompleted = false;
-
- protected final AtomicInteger numInboundRecords = new AtomicInteger(-1);
- protected final AtomicInteger numOutboundRecords = new AtomicInteger(-1);
-
- /*
- * Public API: constructor, init, synchronize.
- */
- public SynchronizerSession(Synchronizer synchronizer, SynchronizerSessionDelegate delegate) {
- this.setSynchronizer(synchronizer);
- this.delegate = delegate;
- }
-
- public Synchronizer getSynchronizer() {
- return synchronizer;
- }
-
- public void setSynchronizer(Synchronizer synchronizer) {
- this.synchronizer = synchronizer;
- }
-
- public void init(Context context, RepositorySessionBundle bundleA, RepositorySessionBundle bundleB) {
- this.context = context;
- this.bundleA = bundleA;
- this.bundleB = bundleB;
- // Begin sessionA and sessionB, call onInitialized in callbacks.
- this.getSynchronizer().repositoryA.createSession(this, context);
- }
-
- /**
- * Get the number of records fetched from the first repository (usually the
- * server, hence inbound).
- * <p>
- * Valid only after first flow has completed.
- *
- * @return number of records, or -1 if not valid.
- */
- public int getInboundCount() {
- return numInboundRecords.get();
- }
-
- /**
- * Get the number of records fetched from the second repository (usually the
- * local store, hence outbound).
- * <p>
- * Valid only after second flow has completed.
- *
- * @return number of records, or -1 if not valid.
- */
- public int getOutboundCount() {
- return numOutboundRecords.get();
- }
-
- // These are accessed by `abort` and `synchronize`, both of which are synchronized.
- // Guarded by `this`.
- protected RecordsChannel channelAToB;
- protected RecordsChannel channelBToA;
-
- /**
- * Please don't call this until you've been notified with onInitialized.
- */
- public synchronized void synchronize() {
- numInboundRecords.set(-1);
- numOutboundRecords.set(-1);
-
- // First thing: decide whether we should.
- if (sessionA.shouldSkip() ||
- sessionB.shouldSkip()) {
- Logger.info(LOG_TAG, "Session requested skip. Short-circuiting sync.");
- sessionA.abort();
- sessionB.abort();
- this.delegate.onSynchronizeSkipped(this);
- return;
- }
-
- final SynchronizerSession session = this;
-
- // TODO: failed record handling.
-
- // This is the *second* record channel to flow.
- // I, SynchronizerSession, am the delegate for the *second* flow.
- channelBToA = new RecordsChannel(this.sessionB, this.sessionA, this);
-
- // This is the delegate for the *first* flow.
- RecordsChannelDelegate channelAToBDelegate = new RecordsChannelDelegate() {
- @Override
- public void onFlowCompleted(RecordsChannel recordsChannel, long fetchEnd, long storeEnd) {
- session.onFirstFlowCompleted(recordsChannel, fetchEnd, storeEnd);
- }
-
- @Override
- public void onFlowBeginFailed(RecordsChannel recordsChannel, Exception ex) {
- Logger.warn(LOG_TAG, "First RecordsChannel onFlowBeginFailed. Logging session error.", ex);
- session.delegate.onSynchronizeFailed(session, ex, "Failed to begin first flow.");
- }
-
- @Override
- public void onFlowFetchFailed(RecordsChannel recordsChannel, Exception ex) {
- Logger.warn(LOG_TAG, "First RecordsChannel onFlowFetchFailed. Logging remote fetch error.", ex);
- }
-
- @Override
- public void onFlowStoreFailed(RecordsChannel recordsChannel, Exception ex, String recordGuid) {
- Logger.warn(LOG_TAG, "First RecordsChannel onFlowStoreFailed. Logging local store error.", ex);
- }
-
- @Override
- public void onFlowFinishFailed(RecordsChannel recordsChannel, Exception ex) {
- Logger.warn(LOG_TAG, "First RecordsChannel onFlowFinishedFailed. Logging session error.", ex);
- session.delegate.onSynchronizeFailed(session, ex, "Failed to finish first flow.");
- }
- };
-
- // This is the *first* channel to flow.
- channelAToB = new RecordsChannel(this.sessionA, this.sessionB, channelAToBDelegate);
-
- Logger.trace(LOG_TAG, "Starting A to B flow. Channel is " + channelAToB);
- try {
- channelAToB.beginAndFlow();
- } catch (InvalidSessionTransitionException e) {
- onFlowBeginFailed(channelAToB, e);
- }
- }
-
- /**
- * Called after the first flow completes.
- * <p>
- * By default, any fetch and store failures are ignored.
- * @param recordsChannel the <code>RecordsChannel</code> (for error testing).
- * @param fetchEnd timestamp when fetches completed.
- * @param storeEnd timestamp when stores completed.
- */
- public void onFirstFlowCompleted(RecordsChannel recordsChannel, long fetchEnd, long storeEnd) {
- Logger.trace(LOG_TAG, "First RecordsChannel onFlowCompleted.");
- Logger.debug(LOG_TAG, "Fetch end is " + fetchEnd + ". Store end is " + storeEnd + ". Starting next.");
- pendingATimestamp = fetchEnd;
- storeEndBTimestamp = storeEnd;
- numInboundRecords.set(recordsChannel.getFetchCount());
- flowAToBCompleted = true;
- channelBToA.flow();
- }
-
- /**
- * Called after the second flow completes.
- * <p>
- * By default, any fetch and store failures are ignored.
- * @param recordsChannel the <code>RecordsChannel</code> (for error testing).
- * @param fetchEnd timestamp when fetches completed.
- * @param storeEnd timestamp when stores completed.
- */
- public void onSecondFlowCompleted(RecordsChannel recordsChannel, long fetchEnd, long storeEnd) {
- Logger.trace(LOG_TAG, "Second RecordsChannel onFlowCompleted.");
- Logger.debug(LOG_TAG, "Fetch end is " + fetchEnd + ". Store end is " + storeEnd + ". Finishing.");
-
- pendingBTimestamp = fetchEnd;
- storeEndATimestamp = storeEnd;
- numOutboundRecords.set(recordsChannel.getFetchCount());
- flowBToACompleted = true;
-
- // Finish the two sessions.
- try {
- this.sessionA.finish(this);
- } catch (InactiveSessionException e) {
- this.onFinishFailed(e);
- return;
- }
- }
-
- @Override
- public void onFlowCompleted(RecordsChannel recordsChannel, long fetchEnd, long storeEnd) {
- onSecondFlowCompleted(recordsChannel, fetchEnd, storeEnd);
- }
-
- @Override
- public void onFlowBeginFailed(RecordsChannel recordsChannel, Exception ex) {
- Logger.warn(LOG_TAG, "Second RecordsChannel onFlowBeginFailed. Logging session error.", ex);
- this.delegate.onSynchronizeFailed(this, ex, "Failed to begin second flow.");
- }
-
- @Override
- public void onFlowFetchFailed(RecordsChannel recordsChannel, Exception ex) {
- Logger.warn(LOG_TAG, "Second RecordsChannel onFlowFetchFailed. Logging local fetch error.", ex);
- }
-
- @Override
- public void onFlowStoreFailed(RecordsChannel recordsChannel, Exception ex, String recordGuid) {
- Logger.warn(LOG_TAG, "Second RecordsChannel onFlowStoreFailed. Logging remote store error.", ex);
- }
-
- @Override
- public void onFlowFinishFailed(RecordsChannel recordsChannel, Exception ex) {
- Logger.warn(LOG_TAG, "Second RecordsChannel onFlowFinishedFailed. Logging session error.", ex);
- this.delegate.onSynchronizeFailed(this, ex, "Failed to finish second flow.");
- }
-
- /*
- * RepositorySessionCreationDelegate methods.
- */
-
- /**
- * I could be called twice: once for sessionA and once for sessionB.
- *
- * I try to clean up sessionA if it is not null, since the creation of
- * sessionB must have failed.
- */
- @Override
- public void onSessionCreateFailed(Exception ex) {
- // Attempt to finish the first session, if the second is the one that failed.
- if (this.sessionA != null) {
- try {
- // We no longer need a reference to our context.
- this.context = null;
- this.sessionA.finish(this);
- } catch (Exception e) {
- // Never mind; best-effort finish.
- }
- }
- // We no longer need a reference to our context.
- this.context = null;
- this.delegate.onSynchronizeFailed(this, ex, "Failed to create session");
- }
-
- /**
- * I should be called twice: first for sessionA and second for sessionB.
- *
- * If I am called for sessionB, I call my delegate's `onInitialized` callback
- * method because my repository sessions are correctly initialized.
- */
- // TODO: some of this "finish and clean up" code can be refactored out.
- @Override
- public void onSessionCreated(RepositorySession session) {
- if (session == null ||
- this.sessionA == session) {
- // TODO: clean up sessionA.
- this.delegate.onSynchronizeFailed(this, new UnexpectedSessionException(session), "Failed to create session.");
- return;
- }
- if (this.sessionA == null) {
- this.sessionA = session;
-
- // Unbundle.
- try {
- this.sessionA.unbundle(this.bundleA);
- } catch (Exception e) {
- this.delegate.onSynchronizeFailed(this, new UnbundleError(e, sessionA), "Failed to unbundle first session.");
- // TODO: abort
- return;
- }
- this.getSynchronizer().repositoryB.createSession(this, this.context);
- return;
- }
- if (this.sessionB == null) {
- this.sessionB = session;
- // We no longer need a reference to our context.
- this.context = null;
-
- // Unbundle. We unbundled sessionA when that session was created.
- try {
- this.sessionB.unbundle(this.bundleB);
- } catch (Exception e) {
- this.delegate.onSynchronizeFailed(this, new UnbundleError(e, sessionA), "Failed to unbundle second session.");
- return;
- }
-
- this.delegate.onInitialized(this);
- return;
- }
- // TODO: need a way to make sure we don't call any more delegate methods.
- this.delegate.onSynchronizeFailed(this, new UnexpectedSessionException(session), "Failed to create session.");
- }
-
- /*
- * RepositorySessionFinishDelegate methods.
- */
-
- /**
- * I could be called twice: once for sessionA and once for sessionB.
- *
- * If sessionB couldn't be created, I don't fail again.
- */
- @Override
- public void onFinishFailed(Exception ex) {
- if (this.sessionB == null) {
- // Ah, it was a problem cleaning up. Never mind.
- Logger.warn(LOG_TAG, "Got exception cleaning up first after second session creation failed.", ex);
- return;
- }
- String session = (this.sessionA == null) ? "B" : "A";
- this.delegate.onSynchronizeFailed(this, ex, "Finish of session " + session + " failed.");
- }
-
- /**
- * I should be called twice: first for sessionA and second for sessionB.
- *
- * If I am called for sessionA, I try to finish sessionB.
- *
- * If I am called for sessionB, I call my delegate's `onSynchronized` callback
- * method because my flows should have completed.
- */
- @Override
- public void onFinishSucceeded(RepositorySession session,
- RepositorySessionBundle bundle) {
- Logger.debug(LOG_TAG, "onFinishSucceeded. Flows? " + flowAToBCompleted + ", " + flowBToACompleted);
-
- if (session == sessionA) {
- if (flowAToBCompleted) {
- Logger.debug(LOG_TAG, "onFinishSucceeded: bumping session A's timestamp to " + pendingATimestamp + " or " + storeEndATimestamp);
- bundle.bumpTimestamp(Math.max(pendingATimestamp, storeEndATimestamp));
- this.synchronizer.bundleA = bundle;
- } else {
- // Should not happen!
- this.delegate.onSynchronizeFailed(this, new UnexpectedSessionException(sessionA), "Failed to finish first session.");
- return;
- }
- if (this.sessionB != null) {
- Logger.trace(LOG_TAG, "Finishing session B.");
- // On to the next.
- try {
- this.sessionB.finish(this);
- } catch (InactiveSessionException e) {
- this.onFinishFailed(e);
- return;
- }
- }
- } else if (session == sessionB) {
- if (flowBToACompleted) {
- Logger.debug(LOG_TAG, "onFinishSucceeded: bumping session B's timestamp to " + pendingBTimestamp + " or " + storeEndBTimestamp);
- bundle.bumpTimestamp(Math.max(pendingBTimestamp, storeEndBTimestamp));
- this.synchronizer.bundleB = bundle;
- Logger.trace(LOG_TAG, "Notifying delegate.onSynchronized.");
- this.delegate.onSynchronized(this);
- } else {
- // Should not happen!
- this.delegate.onSynchronizeFailed(this, new UnexpectedSessionException(sessionB), "Failed to finish second session.");
- return;
- }
- } else {
- // TODO: hurrrrrr...
- }
-
- if (this.sessionB == null) {
- this.sessionA = null; // We're done.
- }
- }
-
- @Override
- public RepositorySessionFinishDelegate deferredFinishDelegate(final ExecutorService executor) {
- return new DeferredRepositorySessionFinishDelegate(this, executor);
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SynchronizerSessionDelegate.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SynchronizerSessionDelegate.java
deleted file mode 100644
index 1d55274e8..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/SynchronizerSessionDelegate.java
+++ /dev/null
@@ -1,13 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.synchronizer;
-
-public interface SynchronizerSessionDelegate {
- public void onInitialized(SynchronizerSession session);
-
- public void onSynchronized(SynchronizerSession session);
- public void onSynchronizeFailed(SynchronizerSession session, Exception lastException, String reason);
- public void onSynchronizeSkipped(SynchronizerSession synchronizerSession);
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/UnbundleError.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/UnbundleError.java
deleted file mode 100644
index fea779636..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/UnbundleError.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.synchronizer;
-
-import org.mozilla.gecko.sync.SyncException;
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-
-public class UnbundleError extends SyncException {
- private static final long serialVersionUID = -8709503281041697522L;
-
- public RepositorySession failedSession;
-
- public UnbundleError(Exception e, RepositorySession session) {
- super(e);
- this.failedSession = session;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/UnexpectedSessionException.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/UnexpectedSessionException.java
deleted file mode 100644
index 0237b884b..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/synchronizer/UnexpectedSessionException.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.synchronizer;
-
-import org.mozilla.gecko.sync.SyncException;
-import org.mozilla.gecko.sync.repositories.RepositorySession;
-
-/**
- * An exception class that indicates that a session was passed
- * to a begin callback and wasn't expected.
- *
- * This shouldn't occur.
- *
- * @author rnewman
- *
- */
-public class UnexpectedSessionException extends SyncException {
- private static final long serialVersionUID = 949010933527484721L;
- public RepositorySession session;
-
- public UnexpectedSessionException(RepositorySession session) {
- this.session = session;
- }
-}
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/telemetry/TelemetryContract.java b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/telemetry/TelemetryContract.java
deleted file mode 100644
index e3e134fe5..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/telemetry/TelemetryContract.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.sync.telemetry;
-
-public class TelemetryContract {
- /**
- * We are a Sync 1.1 (legacy) client, and we downloaded a migration sentinel.
- */
- public static final String SYNC11_MIGRATION_SENTINELS_SEEN = "FENNEC_SYNC11_MIGRATION_SENTINELS_SEEN";
-
- /**
- * We are a Sync 1.1 (legacy) client and we have downloaded a migration
- * sentinel, but there was an error creating a Firefox Account from that
- * sentinel.
- * <p>
- * We have logged the error and are ignoring that sentinel.
- */
- public static final String SYNC11_MIGRATIONS_FAILED = "FENNEC_SYNC11_MIGRATIONS_FAILED";
-
- /**
- * We are a Sync 1.1 (legacy) client and we have downloaded a migration
- * sentinel, and there was no reported error creating a Firefox Account from
- * that sentinel.
- * <p>
- * We have created a Firefox Account corresponding to the sentinel and have
- * queued the existing Old Sync account for removal.
- */
- public static final String SYNC11_MIGRATIONS_SUCCEEDED = "FENNEC_SYNC11_MIGRATIONS_SUCCEEDED";
-
- /**
- * We are (now) a Sync 1.5 (Firefox Accounts-based) client that migrated from
- * Sync 1.1. We have presented the user the "complete upgrade" notification.
- * <p>
- * We will offer every time a sync is triggered, including when a notification
- * is already pending.
- */
- public static final String SYNC11_MIGRATION_NOTIFICATIONS_OFFERED = "FENNEC_SYNC11_MIGRATION_NOTIFICATIONS_OFFERED";
-
- /**
- * We are (now) a Sync 1.5 (Firefox Accounts-based) client that migrated from
- * Sync 1.1. We have presented the user the "complete upgrade" notification
- * and they have successfully completed the upgrade process by entering their
- * Firefox Account credentials.
- */
- public static final String SYNC11_MIGRATIONS_COMPLETED = "FENNEC_SYNC11_MIGRATIONS_COMPLETED";
-
- public static final String SYNC_STARTED = "FENNEC_SYNC_NUMBER_OF_SYNCS_STARTED";
-
- public static final String SYNC_COMPLETED = "FENNEC_SYNC_NUMBER_OF_SYNCS_COMPLETED";
-
- public static final String SYNC_FAILED = "FENNEC_SYNC_NUMBER_OF_SYNCS_FAILED";
-
- public static final String SYNC_FAILED_BACKOFF = "FENNEC_SYNC_NUMBER_OF_SYNCS_FAILED_BACKOFF";
-}