RLMSyncSubscription.mm 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2018 Realm Inc.
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. //
  17. ////////////////////////////////////////////////////////////////////////////
  18. #import "RLMSyncSubscription.h"
  19. #import "RLMObjectSchema_Private.hpp"
  20. #import "RLMObject_Private.hpp"
  21. #import "RLMProperty_Private.hpp"
  22. #import "RLMRealm_Private.hpp"
  23. #import "RLMResults_Private.hpp"
  24. #import "RLMUtil.hpp"
  25. #import "object_store.hpp"
  26. #import "sync/partial_sync.hpp"
  27. using namespace realm;
  28. @interface RLMSyncSubscription ()
  29. @property (nonatomic, readwrite) RLMSyncSubscriptionState state;
  30. @property (nonatomic, readwrite, nullable) NSError *error;
  31. @end
  32. @implementation RLMSyncSubscription {
  33. partial_sync::SubscriptionNotificationToken _token;
  34. util::Optional<partial_sync::Subscription> _subscription;
  35. RLMRealm *_realm;
  36. }
  37. - (instancetype)initWithName:(NSString *)name results:(Results const&)results realm:(RLMRealm *)realm {
  38. if (!(self = [super init]))
  39. return nil;
  40. _name = [name copy];
  41. _realm = realm;
  42. try {
  43. _subscription = partial_sync::subscribe(results, name ? util::make_optional<std::string>(name.UTF8String) : util::none);
  44. }
  45. catch (std::exception const& e) {
  46. @throw RLMException(e);
  47. }
  48. self.state = (RLMSyncSubscriptionState)_subscription->state();
  49. __weak auto weakSelf = self;
  50. _token = _subscription->add_notification_callback([weakSelf] {
  51. auto self = weakSelf;
  52. if (!self)
  53. return;
  54. // Retrieve the current error and status. Update our properties only if the values have changed,
  55. // since clients use KVO to observe these properties.
  56. if (auto error = self->_subscription->error()) {
  57. try {
  58. std::rethrow_exception(error);
  59. } catch (...) {
  60. NSError *nsError;
  61. RLMRealmTranslateException(&nsError);
  62. if (!self.error || ![self.error isEqual:nsError])
  63. self.error = nsError;
  64. }
  65. }
  66. else if (self.error) {
  67. self.error = nil;
  68. }
  69. auto status = (RLMSyncSubscriptionState)self->_subscription->state();
  70. if (status != self.state) {
  71. if (status == RLMSyncSubscriptionStateCreating) {
  72. // If a subscription is deleted without going through this
  73. // object's unsubscribe() method the subscription will transition
  74. // back to Creating rather than Invalidated since it doesn't
  75. // have a good way to track that it previously existed
  76. if (self.state != RLMSyncSubscriptionStateInvalidated)
  77. self.state = RLMSyncSubscriptionStateInvalidated;
  78. }
  79. else {
  80. self.state = status;
  81. }
  82. }
  83. });
  84. return self;
  85. }
  86. - (void)unsubscribe {
  87. partial_sync::unsubscribe(*_subscription);
  88. }
  89. @end
  90. @interface RLMSyncSubscriptionObject : RLMObjectBase
  91. @end
  92. @implementation RLMSyncSubscriptionObject {
  93. util::Optional<NotificationToken> _token;
  94. Object _obj;
  95. }
  96. - (NSString *)name {
  97. return _row.is_attached() ? RLMStringDataToNSString(_row.get_string(_row.get_column_index("name"))) : nil;
  98. }
  99. - (RLMSyncSubscriptionState)state {
  100. if (!_row.is_attached()) {
  101. return RLMSyncSubscriptionStateInvalidated;
  102. }
  103. return (RLMSyncSubscriptionState)_row.get_int(_row.get_column_index("status"));
  104. }
  105. - (NSError *)error {
  106. if (!_row.is_attached()) {
  107. return nil;
  108. }
  109. StringData err = _row.get_string(_row.get_column_index("error_message"));
  110. if (!err.size()) {
  111. return nil;
  112. }
  113. return [NSError errorWithDomain:RLMErrorDomain
  114. code:RLMErrorFail
  115. userInfo:@{NSLocalizedDescriptionKey: RLMStringDataToNSString(err)}];
  116. }
  117. - (NSString *)descriptionWithMaxDepth:(NSUInteger)depth {
  118. if (depth == 0) {
  119. return @"<Maximum depth exceeded>";
  120. }
  121. auto objectType = _row.get_string(_row.get_column_index("matches_property"));
  122. objectType = objectType.substr(0, objectType.size() - strlen("_matches"));
  123. return [NSString stringWithFormat:@"RLMSyncSubscription {\n\tname = %@\n\tobjectType = %@\n\tquery = %@\n\tstatus = %@\n\terror = %@\n}",
  124. self.name, RLMStringDataToNSString(objectType),
  125. RLMStringDataToNSString(_row.get_string(_row.get_column_index("query"))),
  126. @(self.state), self.error];
  127. }
  128. - (void)unsubscribe {
  129. if (_row) {
  130. partial_sync::unsubscribe(Object(_realm->_realm, *_info->objectSchema, _row));
  131. }
  132. }
  133. - (void)addObserver:(id)observer
  134. forKeyPath:(NSString *)keyPath
  135. options:(NSKeyValueObservingOptions)options
  136. context:(void *)context {
  137. // Make the `state` property observable by using an object notifier to
  138. // trigger changes. The normal KVO mechanisms don't work for this class due
  139. // to it not being a normal part of the schema.
  140. if (!_token) {
  141. struct {
  142. __weak RLMSyncSubscriptionObject *weakSelf;
  143. void before(realm::CollectionChangeSet const&) {
  144. @autoreleasepool {
  145. [weakSelf willChangeValueForKey:@"state"];
  146. }
  147. }
  148. void after(realm::CollectionChangeSet const&) {
  149. @autoreleasepool {
  150. [weakSelf didChangeValueForKey:@"state"];
  151. }
  152. }
  153. void error(std::exception_ptr) {}
  154. } callback{self};
  155. _obj = Object(_realm->_realm, *_info->objectSchema, _row);
  156. _token = _obj.add_notification_callback(callback);
  157. }
  158. [super addObserver:observer forKeyPath:keyPath options:options context:context];
  159. }
  160. @end
  161. // RLMClassInfo stores pointers into the schema rather than owning the objects
  162. // it points to, so for a ClassInfo that's not part of the schema we need a
  163. // wrapper object that owns them
  164. RLMResultsSetInfo::RLMResultsSetInfo(__unsafe_unretained RLMRealm *const realm)
  165. : osObjectSchema(ObjectSchema(realm->_realm->read_group(), partial_sync::result_sets_type_name))
  166. , rlmObjectSchema([RLMObjectSchema objectSchemaForObjectStoreSchema:osObjectSchema])
  167. , info(realm, rlmObjectSchema, &osObjectSchema)
  168. {
  169. rlmObjectSchema.accessorClass = [RLMSyncSubscriptionObject class];
  170. }
  171. RLMClassInfo& RLMResultsSetInfo::get(__unsafe_unretained RLMRealm *const realm) {
  172. if (!realm->_resultsSetInfo) {
  173. realm->_resultsSetInfo = std::make_unique<RLMResultsSetInfo>(realm);
  174. }
  175. return realm->_resultsSetInfo->info;
  176. }
  177. @interface RLMSubscriptionResults : RLMResults
  178. @end
  179. @implementation RLMSubscriptionResults
  180. + (instancetype)resultsWithRealm:(RLMRealm *)realm {
  181. auto table = ObjectStore::table_for_object_type(realm->_realm->read_group(), partial_sync::result_sets_type_name);
  182. if (!table) {
  183. @throw RLMException(@"-[RLMRealm subscriptions] can only be called on a Realm using query-based sync");
  184. }
  185. // The server automatically adds a few subscriptions for the permissions
  186. // types which we want to hide. They're just an implementation detail and
  187. // deleting them won't work out well for the user.
  188. auto query = table->where().ends_with(table->get_column_index("matches_property"), "_matches");
  189. return [self resultsWithObjectInfo:RLMResultsSetInfo::get(realm)
  190. results:Results(realm->_realm, std::move(query))];
  191. }
  192. // These operations require a valid schema for the type. It's unclear how they
  193. // would be useful so it's probably not worth fixing this.
  194. - (RLMResults *)sortedResultsUsingDescriptors:(__unused NSArray<RLMSortDescriptor *> *)properties {
  195. @throw RLMException(@"Sorting subscription results is currently not implemented");
  196. }
  197. - (RLMResults *)distinctResultsUsingKeyPaths:(__unused NSArray<NSString *> *)keyPaths {
  198. @throw RLMException(@"Distincting subscription results is currently not implemented");
  199. }
  200. @end
  201. @implementation RLMResults (SyncSubscription)
  202. - (RLMSyncSubscription *)subscribe {
  203. return [[RLMSyncSubscription alloc] initWithName:nil results:_results realm:self.realm];
  204. }
  205. - (RLMSyncSubscription *)subscribeWithName:(NSString *)subscriptionName {
  206. return [[RLMSyncSubscription alloc] initWithName:subscriptionName results:_results realm:self.realm];
  207. }
  208. - (RLMSyncSubscription *)subscribeWithName:(NSString *)subscriptionName limit:(NSUInteger)limit {
  209. return [[RLMSyncSubscription alloc] initWithName:subscriptionName results:_results.limit(limit) realm:self.realm];
  210. }
  211. @end
  212. @implementation RLMRealm (SyncSubscription)
  213. - (RLMResults<RLMSyncSubscription *> *)subscriptions {
  214. [self verifyThread];
  215. return [RLMSubscriptionResults resultsWithRealm:self];
  216. }
  217. - (nullable RLMSyncSubscription *)subscriptionWithName:(NSString *)name {
  218. [self verifyThread];
  219. auto& info = RLMResultsSetInfo::get(self);
  220. if (!info.table()) {
  221. @throw RLMException(@"-[RLMRealm subcriptionWithName:] can only be called on a Realm using query-based sync");
  222. }
  223. auto row = info.table()->find_first(info.table()->get_column_index("name"),
  224. RLMStringDataWithNSString(name));
  225. if (row == npos) {
  226. return nil;
  227. }
  228. RLMObjectBase *acc = RLMCreateManagedAccessor(info.rlmObjectSchema.accessorClass, self, &info);
  229. acc->_row = info.table()->get(row);
  230. return (RLMSyncSubscription *)acc;
  231. }
  232. @end
  233. RLMSyncSubscription *RLMCastToSyncSubscription(id obj) {
  234. return obj;
  235. }