-
Notifications
You must be signed in to change notification settings - Fork 2.2k
/
Copy pathSync.swift
1618 lines (1360 loc) · 66.8 KB
/
Sync.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2016 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
import Realm
import Realm.Private
/**
An object representing a Realm Object Server user.
- see: `RLMSyncUser`
*/
public typealias SyncUser = RLMSyncUser
/**
An immutable data object representing information retrieved from the Realm Object
Server about a particular user.
- see: `RLMSyncUserInfo`
*/
public typealias SyncUserInfo = RLMSyncUserInfo
/**
An immutable data object representing an account belonging to a particular user.
- see: `SyncUserInfo`, `RLMSyncUserAccountInfo`
*/
public typealias SyncUserAccountInfo = RLMSyncUserAccountInfo
/**
A singleton which configures and manages the Realm Object Server synchronization-related
functionality.
- see: `RLMSyncManager`
*/
public typealias SyncManager = RLMSyncManager
extension SyncManager {
/// The sole instance of the singleton.
public static var shared: SyncManager {
return __shared()
}
}
/**
Options for configuring timeouts and intervals in the sync client.
- see: `RLMSyncTimeoutOptions`
*/
public typealias SyncTimeoutOptions = RLMSyncTimeoutOptions
/**
A session object which represents communication between the client and server for a specific
Realm.
- see: `RLMSyncSession`
*/
public typealias SyncSession = RLMSyncSession
/**
A closure type for a closure which can be set on the `SyncManager` to allow errors to be reported
to the application.
- see: `RLMSyncErrorReportingBlock`
*/
public typealias ErrorReportingBlock = RLMSyncErrorReportingBlock
/**
A closure type for a closure which is used by certain APIs to asynchronously return a `SyncUser`
object to the application.
- see: `RLMUserCompletionBlock`
*/
public typealias UserCompletionBlock = RLMUserCompletionBlock
/**
An error associated with the SDK's synchronization functionality. All errors reported by
an error handler registered on the `SyncManager` are of this type.
- see: `RLMSyncError`
*/
public typealias SyncError = RLMSyncError
extension SyncError {
/**
An opaque token allowing the user to take action after certain types of
errors have been reported.
- see: `RLMSyncErrorActionToken`
*/
public typealias ActionToken = RLMSyncErrorActionToken
/**
Given a client reset error, extract and return the recovery file path
and the action token.
The action token can be passed into `SyncSession.immediatelyHandleError(_:)`
to immediately delete the local copy of the Realm which experienced the
client reset error. The local copy of the Realm must be deleted before
your application attempts to open the Realm again.
The recovery file path is the path to which the current copy of the Realm
on disk will be saved once the client reset occurs.
- warning: Do not call `SyncSession.immediatelyHandleError(_:)` until you are
sure that all references to the Realm and managed objects belonging
to the Realm have been nil'ed out, and that all autorelease pools
containing these references have been drained.
- see: `SyncError.ActionToken`, `SyncSession.immediatelyHandleError(_:)`
*/
public func clientResetInfo() -> (String, SyncError.ActionToken)? {
if code == SyncError.clientResetError,
let recoveryPath = userInfo[kRLMSyncPathOfRealmBackupCopyKey] as? String,
let token = _nsError.__rlmSync_errorActionToken() {
return (recoveryPath, token)
}
return nil
}
/**
Given a permission denied error, extract and return the action token.
This action token can be passed into `SyncSession.immediatelyHandleError(_:)`
to immediately delete the local copy of the Realm which experienced the
permission denied error. The local copy of the Realm must be deleted before
your application attempts to open the Realm again.
- warning: Do not call `SyncSession.immediatelyHandleError(_:)` until you are
sure that all references to the Realm and managed objects belonging
to the Realm have been nil'ed out, and that all autorelease pools
containing these references have been drained.
- see: `SyncError.ActionToken`, `SyncSession.immediatelyHandleError(_:)`
*/
public func deleteRealmUserInfo() -> SyncError.ActionToken? {
return _nsError.__rlmSync_errorActionToken()
}
}
/**
An error associated with network requests made to the authentication server. This type of error
may be returned in the callback block to `SyncUser.logIn()` upon certain types of failed login
attempts (for example, if the request is malformed or if the server is experiencing an issue).
- see: `RLMSyncAuthError`
*/
public typealias SyncAuthError = RLMSyncAuthError
/**
An enum which can be used to specify the level of logging.
- see: `RLMSyncLogLevel`
*/
public typealias SyncLogLevel = RLMSyncLogLevel
/**
A data type whose values represent different authentication providers that can be used with
the Realm Object Server.
- see: `RLMIdentityProvider`
*/
public typealias Provider = RLMIdentityProvider
/**
* How the Realm client should validate the identity of the server for secure connections.
*
* By default, when connecting to the Realm Object Server over HTTPS, Realm will
* validate the server's HTTPS certificate using the system trust store and root
* certificates. For additional protection against man-in-the-middle (MITM)
* attacks and similar vulnerabilities, you can pin a certificate or public key,
* and reject all others, even if they are signed by a trusted CA.
*/
public enum ServerValidationPolicy {
/// Perform no validation and accept potentially invalid certificates.
///
/// - warning: DO NOT USE THIS OPTION IN PRODUCTION.
case none
/// Use the default server trust evaluation based on the system-wide CA
/// store. Any certificate signed by a trusted CA will be accepted.
case system
/// Use a specific pinned certificate to validate the server identify.
///
/// This will only connect to a server if one of the server certificates
/// matches the certificate stored at the given local path and that
/// certificate has a valid trust chain.
///
/// On macOS, the certificate files may be in any of the formats supported
/// by SecItemImport(), including PEM and .cer (see SecExternalFormat for a
/// complete list of possible formats). On iOS and other platforms, only
/// DER .cer files are supported.
case pinCertificate(path: URL)
}
/**
A `SyncConfiguration` represents configuration parameters for Realms intended to sync with
a Realm Object Server.
*/
public struct SyncConfiguration {
/// The `SyncUser` who owns the Realm that this configuration should open.
public let user: SyncUser
/**
The URL of the Realm on the Realm Object Server that this configuration should open.
- warning: The URL must be absolute (e.g. `realms://example.com/~/foo`), and cannot end with
`.realm`, `.realm.lock` or `.realm.management`.
*/
public let realmURL: URL
/**
A policy that determines what should happen when all references to Realms opened by this
configuration go out of scope.
*/
internal let stopPolicy: RLMSyncStopPolicy
/**
How the SSL certificate of the Realm Object Server should be validated.
*/
public let serverValidationPolicy: ServerValidationPolicy
/// :nodoc:
@available(*, unavailable, message: "Use serverValidationPolicy instead")
public var enableSSLValidation: Bool {
fatalError()
}
/// :nodoc:
@available(*, unavailable, message: "Use fullSynchronization instead")
public var isPartial: Bool {
fatalError()
}
/**
Whether this Realm should be a fully synchronized Realm.
Synchronized Realms comes in two flavors: Query-based and Fully synchronized.
A fully synchronized Realm will automatically synchronize the entire Realm in the background
while a query-based Realm will only synchronize the data being subscribed to.
Synchronized realms are by default query-based unless this boolean is set.
*/
public let fullSynchronization: Bool
/**
The prefix that is prepended to the path in the HTTP request
that initiates a sync connection. The value specified must match with the server's expectation.
Changing the value of `urlPrefix` should be matched with a corresponding
change of the server's configuration.
If no value is specified here then the default `/realm-sync` path is used.
*/
public let urlPrefix: String?
/**
By default, Realm.asyncOpen() swallows non-fatal connection errors such as
a connection attempt timing out and simply retries until it succeeds. If
this is set to `true`, instead the error will be reported to the callback
and the async open will be cancelled.
*/
public let cancelAsyncOpenOnNonFatalErrors: Bool
internal init(config: RLMSyncConfiguration) {
self.user = config.user
self.realmURL = config.realmURL
self.stopPolicy = config.stopPolicy
if let certificateURL = config.pinnedCertificateURL {
self.serverValidationPolicy = .pinCertificate(path: certificateURL)
} else {
self.serverValidationPolicy = config.enableSSLValidation ? .system : .none
}
self.fullSynchronization = config.fullSynchronization
self.urlPrefix = config.urlPrefix
self.cancelAsyncOpenOnNonFatalErrors = config.cancelAsyncOpenOnNonFatalErrors
}
func asConfig() -> RLMSyncConfiguration {
var validateSSL = true
var certificate: URL?
switch serverValidationPolicy {
case .none:
validateSSL = false
case .system:
break
case .pinCertificate(let path):
certificate = path
}
let c = RLMSyncConfiguration(user: user, realmURL: realmURL,
isPartial: !fullSynchronization,
urlPrefix: urlPrefix,
stopPolicy: stopPolicy,
enableSSLValidation: validateSSL,
certificatePath: certificate)
c.cancelAsyncOpenOnNonFatalErrors = cancelAsyncOpenOnNonFatalErrors
return c
}
/// :nodoc:
@available(*, unavailable, message: "Use SyncUser.configuration() instead")
public init(user: SyncUser, realmURL: URL, enableSSLValidation: Bool = true, isPartial: Bool = false, urlPrefix: String? = nil) {
fatalError()
}
/// :nodoc:
@available(*, unavailable, message: "Use SyncUser.configuration() instead")
public static func automatic() -> Realm.Configuration {
fatalError()
}
/// :nodoc:
@available(*, unavailable, message: "Use SyncUser.configuration() instead")
public static func automatic(user: SyncUser) -> Realm.Configuration {
fatalError()
}
}
/// A `SyncCredentials` represents data that uniquely identifies a Realm Object Server user.
public struct SyncCredentials {
/// An account token serialized as a string
public typealias Token = String
internal var token: Token
internal var provider: Provider
internal var userInfo: [String: Any]
/**
Initialize new credentials using a custom token, authentication provider, and user information
dictionary. In most cases, the convenience initializers should be used instead.
*/
public init(customToken token: Token, provider: Provider, userInfo: [String: Any] = [:]) {
self.token = token
self.provider = provider
self.userInfo = userInfo
}
internal init(_ credentials: RLMSyncCredentials) {
self.token = credentials.token
self.provider = credentials.provider
self.userInfo = credentials.userInfo
}
/// Initialize new credentials using a Facebook account token.
public static func facebook(token: Token) -> SyncCredentials {
return SyncCredentials(RLMSyncCredentials(facebookToken: token))
}
/// Initialize new credentials using a Google account token.
public static func google(token: Token) -> SyncCredentials {
return SyncCredentials(RLMSyncCredentials(googleToken: token))
}
/// Initialize new credentials using a CloudKit account token.
public static func cloudKit(token: Token) -> SyncCredentials {
return SyncCredentials(RLMSyncCredentials(cloudKitToken: token))
}
/// Initialize new credentials using a Realm Object Server username and password.
public static func usernamePassword(username: String,
password: String,
register: Bool = false) -> SyncCredentials {
return SyncCredentials(RLMSyncCredentials(username: username, password: password, register: register))
}
/// Initialize new credentials using a Realm Object Server access token.
public static func accessToken(_ accessToken: String, identity: String) -> SyncCredentials {
return SyncCredentials(RLMSyncCredentials(accessToken: accessToken, identity: identity))
}
/// Initialize new credentials using a JSON Web Token.
public static func jwt(_ token: Token) -> SyncCredentials {
return SyncCredentials(RLMSyncCredentials(jwt: token))
}
/// Initialize new credentials using a nickname.
@available(*, deprecated, message: "Use usernamePassword instead.")
public static func nickname(_ nickname: String, isAdmin: Bool = false) -> SyncCredentials {
return SyncCredentials(RLMSyncCredentials(nickname: nickname, isAdmin: isAdmin))
}
/// Initialize new credentials anonymously
public static func anonymous() -> SyncCredentials {
return SyncCredentials(RLMSyncCredentials.anonymous())
}
/// Initialize new credentials using an externally-issued refresh token
public static func customRefreshToken(_ token: String, identity: String, isAdmin: Bool = false) -> SyncCredentials {
return SyncCredentials(RLMSyncCredentials(customRefreshToken: token, identity: identity, isAdmin: isAdmin))
}
}
extension RLMSyncCredentials {
internal convenience init(_ credentials: SyncCredentials) {
self.init(customToken: credentials.token, provider: credentials.provider, userInfo: credentials.userInfo)
}
}
extension SyncUser {
/**
Log in a user and asynchronously retrieve a user object.
If the log in completes successfully, the completion block will be called, and a
`SyncUser` representing the logged-in user will be passed to it. This user object
can be used to open `Realm`s and retrieve `SyncSession`s. Otherwise, the
completion block will be called with an error.
- parameter credentials: A `SyncCredentials` object representing the user to log in.
- parameter authServerURL: The URL of the authentication server (e.g. "http://realm.example.org:9080").
- parameter timeout: How long the network client should wait, in seconds, before timing out.
- parameter callbackQueue: The dispatch queue upon which the callback should run. Defaults to the main queue.
- parameter completion: A callback block to be invoked once the log in completes.
*/
public static func logIn(with credentials: SyncCredentials,
server authServerURL: URL,
timeout: TimeInterval = 30,
callbackQueue queue: DispatchQueue = DispatchQueue.main,
onCompletion completion: @escaping UserCompletionBlock) {
return SyncUser.__logIn(with: RLMSyncCredentials(credentials),
authServerURL: authServerURL,
timeout: timeout,
callbackQueue: queue,
onCompletion: completion)
}
/// A dictionary of all valid, logged-in user identities corresponding to their `SyncUser` objects.
public static var all: [String: SyncUser] {
return __allUsers()
}
/**
The logged-in user. `nil` if none exists. Only use this property if your application expects
no more than one logged-in user at any given time.
- warning: Throws an Objective-C exception if more than one logged-in user exists.
*/
public static var current: SyncUser? {
return __current()
}
/**
An optional error handler which can be set to notify the host application when
the user encounters an error.
- note: Check for `.invalidAccessToken` to see if the user has been remotely logged
out because its refresh token expired, or because the third party authentication
service providing the user's identity has logged the user out.
- warning: Regardless of whether an error handler is defined, certain user errors
will automatically cause the user to enter the logged out state.
*/
@nonobjc public var errorHandler: ((SyncUser, SyncAuthError) -> Void)? {
get {
return __errorHandler
}
set {
if let newValue = newValue {
__errorHandler = { (user, error) in
newValue(user, error as! SyncAuthError)
}
} else {
__errorHandler = nil
}
}
}
/**
Create a permission offer for a Realm.
A permission offer is used to grant access to a Realm this user manages to another
user. Creating a permission offer produces a string token which can be passed to the
recepient in any suitable way (for example, via e-mail).
The operation will take place asynchronously. The token can be accepted by the recepient
using the `SyncUser.acceptOffer(forToken:, callback:)` method.
- parameter url: The URL of the Realm for which the permission offer should pertain. This
may be the URL of any Realm which this user is allowed to manage. If the URL
has a `~` wildcard it will be replaced with this user's user identity.
- parameter accessLevel: What access level to grant to whoever accepts the token.
- parameter expiration: Optionally, a date which indicates when the offer expires. If the
recepient attempts to accept the offer after the date it will be rejected.
If nil, the offer will never expire.
- parameter callback: A callback indicating whether the operation succeeded or failed. If it
succeeded the token will be passed in as a string.
*/
public func createOfferForRealm(at url: URL,
accessLevel: SyncAccessLevel,
expiration: Date? = nil,
callback: @escaping (String?, Error?) -> Void) {
self.__createOfferForRealm(at: url, accessLevel: accessLevel, expiration: expiration, callback: callback)
}
/**
Create a sync configuration instance.
Additional settings can be optionally specified. Descriptions of these
settings follow.
`enableSSLValidation` is true by default. It can be disabled for debugging
purposes.
- warning: The URL must be absolute (e.g. `realms://example.com/~/foo`), and cannot end with
`.realm`, `.realm.lock` or `.realm.management`.
- warning: NEVER disable SSL validation for a system running in production.
*/
public func configuration(realmURL: URL? = nil, fullSynchronization: Bool = false,
enableSSLValidation: Bool, urlPrefix: String? = nil) -> Realm.Configuration {
let config = self.__configuration(with: realmURL,
fullSynchronization: fullSynchronization,
enableSSLValidation: enableSSLValidation,
urlPrefix: urlPrefix)
return ObjectiveCSupport.convert(object: config)
}
/**
Create a sync configuration instance.
- parameter realmURL: The URL to connect to. If not set, the default Realm
derived from the authentication URL is used. The URL must be absolute (e.g.
`realms://example.com/~/foo`), and cannot end with `.realm`, `.realm.lock`
or `.realm.management`.
- parameter serverValidationPolicy: How the SSL certificate of the Realm Object
Server should be validated. By default the system SSL validation is used,
but it can be set to `.pinCertificate` to pin a specific SSL certificate,
or `.none` for debugging.
- parameter fullSynchronization: Whether this Realm should be a fully
synchronized or a query-based Realm.
- parameter urlPrefix: The prefix that is prepended to the path in the HTTP
request that initiates a sync connection. The value specified must match
with the server's expectation, and this parameter only needs to be set if
you have changed the configuration of the server.
- parameter cancelAsyncOpenOnNonFatalErrors: By default, Realm.asyncOpen()
swallows non-fatal connection errors such as a connection attempt timing
out and simply retries until it succeeds. If this is set to `true`, instead
the error will be reported to the callback and the async open will be
cancelled.
- warning: NEVER disable SSL validation for a system running in production.
*/
public func configuration(realmURL: URL? = nil, fullSynchronization: Bool = false,
serverValidationPolicy: ServerValidationPolicy = .system,
urlPrefix: String? = nil,
cancelAsyncOpenOnNonFatalErrors: Bool = false) -> Realm.Configuration {
let config = self.__configuration(with: realmURL, fullSynchronization: fullSynchronization)
let syncConfig = config.syncConfiguration!
syncConfig.urlPrefix = urlPrefix
syncConfig.cancelAsyncOpenOnNonFatalErrors = cancelAsyncOpenOnNonFatalErrors
switch serverValidationPolicy {
case .none:
syncConfig.enableSSLValidation = false
case .system:
break
case .pinCertificate(let path):
syncConfig.pinnedCertificateURL = path
}
config.syncConfiguration = syncConfig
return ObjectiveCSupport.convert(object: config)
}
}
/**
A value which represents a permission granted to a user to interact
with a Realm. These values are passed into APIs on `SyncUser`, and
returned from `SyncPermissionResults`.
- see: `RLMSyncPermission`
*/
public typealias SyncPermission = RLMSyncPermission
/**
An enumeration describing possible access levels.
- see: `RLMSyncAccessLevel`
*/
public typealias SyncAccessLevel = RLMSyncAccessLevel
public extension SyncSession {
/**
The current state of the session represented by a session object.
- see: `RLMSyncSessionState`
*/
typealias State = RLMSyncSessionState
/**
The current state of a sync session's connection.
- see: `RLMSyncConnectionState`
*/
typealias ConnectionState = RLMSyncConnectionState
/**
The transfer direction (upload or download) tracked by a given progress notification block.
Progress notification blocks can be registered on sessions if your app wishes to be informed
how many bytes have been uploaded or downloaded, for example to show progress indicator UIs.
*/
enum ProgressDirection {
/// For monitoring upload progress.
case upload
/// For monitoring download progress.
case download
}
/**
The desired behavior of a progress notification block.
Progress notification blocks can be registered on sessions if your app wishes to be informed
how many bytes have been uploaded or downloaded, for example to show progress indicator UIs.
*/
enum ProgressMode {
/**
The block will be called forever, or until it is unregistered by calling
`ProgressNotificationToken.invalidate()`.
Notifications will always report the latest number of transferred bytes, and the
most up-to-date number of total transferrable bytes.
*/
case reportIndefinitely
/**
The block will, upon registration, store the total number of bytes
to be transferred. When invoked, it will always report the most up-to-date number
of transferrable bytes out of that original number of transferrable bytes.
When the number of transferred bytes reaches or exceeds the
number of transferrable bytes, the block will be unregistered.
*/
case forCurrentlyOutstandingWork
}
/**
A token corresponding to a progress notification block.
Call `invalidate()` on the token to stop notifications. If the notification block has already
been automatically stopped, calling `invalidate()` does nothing. `invalidate()` should be called
before the token is destroyed.
*/
typealias ProgressNotificationToken = RLMProgressNotificationToken
/**
A struct encapsulating progress information, as well as useful helper methods.
*/
struct Progress {
/// The number of bytes that have been transferred.
public let transferredBytes: Int
/**
The total number of transferrable bytes (bytes that have been transferred,
plus bytes pending transfer).
If the notification block is tracking downloads, this number represents the size of the
changesets generated by all other clients using the Realm.
If the notification block is tracking uploads, this number represents the size of the
changesets representing the local changes on this client.
*/
public let transferrableBytes: Int
/// The fraction of bytes transferred out of all transferrable bytes. If this value is 1,
/// no bytes are waiting to be transferred (either all bytes have already been transferred,
/// or there are no bytes to be transferred in the first place).
public var fractionTransferred: Double {
if transferrableBytes == 0 {
return 1
}
let percentage = Double(transferredBytes) / Double(transferrableBytes)
return percentage > 1 ? 1 : percentage
}
/// Whether all pending bytes have already been transferred.
public var isTransferComplete: Bool {
return transferredBytes >= transferrableBytes
}
internal init(transferred: UInt, transferrable: UInt) {
transferredBytes = Int(transferred)
transferrableBytes = Int(transferrable)
}
}
/**
Register a progress notification block.
If the session has already received progress information from the
synchronization subsystem, the block will be called immediately. Otherwise, it
will be called as soon as progress information becomes available.
Multiple blocks can be registered with the same session at once. Each block
will be invoked on a side queue devoted to progress notifications.
The token returned by this method must be retained as long as progress
notifications are desired, and the `invalidate()` method should be called on it
when notifications are no longer needed and before the token is destroyed.
If no token is returned, the notification block will never be called again.
There are a number of reasons this might be true. If the session has previously
experienced a fatal error it will not accept progress notification blocks. If
the block was configured in the `forCurrentlyOutstandingWork` mode but there
is no additional progress to report (for example, the number of transferrable bytes
and transferred bytes are equal), the block will not be called again.
- parameter direction: The transfer direction (upload or download) to track in this progress notification block.
- parameter mode: The desired behavior of this progress notification block.
- parameter block: The block to invoke when notifications are available.
- returns: A token which must be held for as long as you want notifications to be delivered.
- see: `ProgressDirection`, `Progress`, `ProgressNotificationToken`
*/
func addProgressNotification(for direction: ProgressDirection,
mode: ProgressMode,
block: @escaping (Progress) -> Void) -> ProgressNotificationToken? {
return __addProgressNotification(for: (direction == .upload ? .upload : .download),
mode: (mode == .reportIndefinitely
? .reportIndefinitely
: .forCurrentlyOutstandingWork)) { transferred, transferrable in
block(Progress(transferred: transferred, transferrable: transferrable))
}
}
}
extension Realm {
/// :nodoc:
@available(*, unavailable, message: "Use Results.subscribe()")
public func subscribe<T: Object>(to objects: T.Type, where: String,
completion: @escaping (Results<T>?, Swift.Error?) -> Void) {
fatalError()
}
/**
Get the SyncSession used by this Realm. Will be nil if this is not a
synchronized Realm.
*/
public var syncSession: SyncSession? {
return SyncSession(for: rlmRealm)
}
}
// MARK: - Permissions and permission results
extension SyncPermission: RealmCollectionValue { }
/**
An array containing sync permission results.
*/
public typealias SyncPermissionResults = [SyncPermission]
// MARK: - Partial sync subscriptions
/// The possible states of a sync subscription.
public enum SyncSubscriptionState: Equatable {
/// The subscription is being created, but has not yet been written to the synced Realm.
case creating
/// The subscription has been created, and is waiting to be processed by the server.
case pending
/// The subscription has been processed by the server, and objects matching the subscription
/// are now being synchronized to this client.
case complete
/// The subscription has been removed.
case invalidated
/// An error occurred while creating the subscription or while the server was processing it.
case error(Error)
internal init(_ rlmSubscription: RLMSyncSubscription) {
switch rlmSubscription.state {
case .creating:
self = .creating
case .pending:
self = .pending
case .complete:
self = .complete
case .invalidated:
self = .invalidated
case .error:
self = .error(rlmSubscription.error!)
}
}
public static func == (lhs: SyncSubscriptionState, rhs: SyncSubscriptionState) -> Bool {
switch (lhs, rhs) {
case (.creating, .creating), (.pending, .pending), (.complete, .complete), (.invalidated, .invalidated):
return true
case (.error(let e1), .error(let e2)):
return e1 == e2
default:
return false
}
}
}
/// `SyncSubscription` represents a subscription to a set of objects in a synced Realm.
///
/// When partial sync is enabled for a synced Realm, the only objects that the server synchronizes to the
/// client are those that match a sync subscription registered by that client. A subscription consists of
/// of a query (represented by a `Results`) and an optional name.
///
/// Changes to the state of the subscription can be observed using `SyncSubscription.observe(_:options:_:)`.
///
/// Subscriptions are created using `Results.subscribe()` or `Results.subscribe(named:)`.
public struct SyncSubscription: RealmCollectionValue {
private let rlmSubscription: RLMSyncSubscription
/// The name of the subscription.
///
/// This will be `nil` if a name was not provided when the subscription was created.
public var name: String? { return rlmSubscription.name }
/// The state of the subscription.
public var state: SyncSubscriptionState { return SyncSubscriptionState(rlmSubscription) }
/**
The raw query which this subscription is running on the server.
This string is a serialized representation of the Results which the
subscription was created from. This representation does *not* use NSPredicate
syntax, and is not guaranteed to remain consistent between versions of Realm.
Any use of this other than manual inspection when debugging is likely to be
incorrect.
This is `nil` while the subscription is in the Creating state.
*/
public var query: String? { return rlmSubscription.query }
/**
When this subscription was first created.
This value will be `nil` for subscriptions created with older versions of
Realm which did not store the creation date. Newly created subscriptions
should always have a non-nil creation date.
*/
public var createdAt: Date? { return rlmSubscription.createdAt }
/**
When this subscription was last updated.
This value will be `nil` for subscriptions created with older versions of
Realm which did not store the update date. Newly created subscriptions
should always have a non-nil update date.
The update date is the time when the subscription was last updated by a call
to `Results.subscribe()`, and not when the set of objects which match the
subscription last changed.
*/
public var updatedAt: Date? { return rlmSubscription.updatedAt }
/**
When this subscription will be automatically removed.
If the `timeToLive` parameter is set when creating a sync subscription, the
subscription will be automatically removed the first time that any subscription
is created, modified, or deleted after that time has elapsed.
This property will be `nil` if the `timeToLive` option was not enabled.
*/
public var expiresAt: Date? { return rlmSubscription.expiresAt }
/**
How long this subscription will persist after last being updated.
If the `timeToLive` parameter is set when creating a sync subscription, the
subscription will be automatically removed the first time that any subscription
is created, modified, or deleted after that time has elapsed.
This property will be nil if the `timeToLive` option was not enabled.
*/
public var timeToLive: TimeInterval? {
let ttl = rlmSubscription.timeToLive
return ttl.isNaN ? nil : ttl
}
internal init(_ rlmSubscription: RLMSyncSubscription) {
self.rlmSubscription = rlmSubscription
}
public static func == (lhs: SyncSubscription, rhs: SyncSubscription) -> Bool {
return lhs.rlmSubscription == rhs.rlmSubscription
}
/// Observe the subscription for state changes.
///
/// When the state of the subscription changes, `block` will be invoked and
/// passed the new state.
///
/// The token returned from this function does not hold a strong reference to
/// this subscription object. This means that you must hold a reference to
/// the subscription object itself along with the returned token in order to
/// actually receive updates about the state.
///
/// - parameter keyPath: The path to observe. Must be `\.state`.
/// - parameter options: Options for the observation. Only `NSKeyValueObservingOptions.initial` option is
/// is supported at this time.
/// - parameter block: The block to be called whenever a change occurs.
/// - returns: A token which must be held for as long as you want updates to be delivered.
public func observe(_ keyPath: KeyPath<SyncSubscription, SyncSubscriptionState>,
options: NSKeyValueObservingOptions = [],
_ block: @escaping (SyncSubscriptionState) -> Void) -> NotificationToken {
let observation = rlmSubscription.observe(\.state, options: options) { rlmSubscription, _ in
block(SyncSubscriptionState(rlmSubscription))
}
return KeyValueObservationNotificationToken(observation)
}
/// Remove this subscription
///
/// Removing a subscription will delete all objects from the local Realm that were matched
/// only by that subscription and not any remaining subscriptions. The deletion is performed
/// by the server, and so has no immediate impact on the contents of the local Realm. If the
/// device is currently offline, the removal will not be processed until the device returns online.
public func unsubscribe() {
rlmSubscription.unsubscribe()
}
}
// :nodoc:
extension SyncSubscription: CustomObjectiveCBridgeable {
static func bridging(objCValue: Any) -> SyncSubscription {
return ObjectiveCSupport.convert(object: RLMCastToSyncSubscription(objCValue))
}
var objCValue: Any {
return 0
}
}
extension Results {
// MARK: Sync
/// Subscribe to the query represented by this `Results`
///
/// Subscribing to a query asks the server to synchronize all objects to the
/// client which match the query, along with all objects which are reachable
/// from those objects via links. This happens asynchronously, and the local
/// client Realm may not immediately have all objects which match the query.
/// Observe the `state` property of the returned subscription object to be
/// notified of when the subscription has been processed by the server and
/// all objects matching the query are available.
///
/// ---
///
/// Creating a new subscription with the same name and query as an existing
/// subscription will not create a new subscription, but instead will return
/// an object referring to the existing sync subscription. This means that
/// performing the same subscription twice followed by removing it once will
/// result in no subscription existing.
///
/// By default trying to create a subscription with a name as an existing
/// subscription with a different query or options will fail. If `update` is
/// `true`, instead the existing subscription will be changed to use the
/// query and options from the new subscription. This only works if the new
/// subscription is for the same type of objects as the existing
/// subscription, and trying to overwrite a subscription with a subscription
/// of a different type of objects will still fail.
///
/// ---
///
/// The number of top-level objects which are included in the subscription
/// can optionally be limited by setting the `limit` paramter. If more
/// top-level objects than the limit match the query, only the first
/// `limit` objects will be included. This respects the sort and distinct
/// order of the query being subscribed to for the determination of what the
/// "first" objects are.
///
/// The limit does not count or apply to objects which are added indirectly
/// due to being linked to by the objects in the subscription or due to
/// being listed in `includeLinkingObjects`. If the limit is larger than the
/// number of objects which match the query, all objects will be
/// included.
///
/// ---
///
/// By default subscriptions are persistent, and last until they are
/// explicitly removed by calling `unsubscribe()`. Subscriptions can instead
/// be made temporary by setting the time to live to how long the
/// subscription should remain. After that time has elapsed the subscription
/// will be automatically removed.
///
/// ---
///
/// Outgoing links (i.e. `List` and `Object` properties) are automatically
/// included in sync subscriptions. That is, if you subscribe to a query
/// which matches one object, every object which is reachable via links
/// from that object are also included in the subscription. By default,
/// `LinkingObjects` properties do not work this way and instead, they only
/// report objects which happen to be included in a subscription. Specific