rmx-firebase 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,372 @@
1
+ class RMXFirebaseCollection
2
+
3
+ include RMXCommonMethods
4
+
5
+ attr_reader :ref, :snaps
6
+
7
+ # public
8
+ def ready?
9
+ !!@ready
10
+ end
11
+
12
+ # public
13
+ def cancelled?
14
+ !!@cancelled
15
+ end
16
+
17
+ # overridable
18
+ def transform(snap)
19
+ if block = @transform_block
20
+ block.call(snap)
21
+ else
22
+ snap
23
+ end
24
+ end
25
+
26
+ def transformed_async(&block)
27
+ transformed(:async, &block)
28
+ end
29
+
30
+ # public, completes with ready transformations
31
+ def transformed(queue=nil, &block)
32
+ items = @transformations.dup
33
+ if (snap = items.first) && snap.is_a?(RMXFirebaseModel)
34
+ RMXFirebaseBatch.new(items).once(queue, &block)
35
+ else
36
+ RMXFirebase.block_on_queue(queue, items, &block)
37
+ end
38
+ self
39
+ end
40
+
41
+ def once_async(&block)
42
+ once(:async, &block)
43
+ end
44
+
45
+ # completes with `self` once, when the collection is ready.
46
+ # retains `self` and the sender until complete
47
+ def once(queue=nil, &block)
48
+ RMXFirebase::QUEUE.barrier_async do
49
+ if ready?
50
+ RMXFirebase.block_on_queue(queue, self, &block)
51
+ else
52
+ RMX(self).once(:ready, :strong => true, :queue => queue, &block)
53
+ end
54
+ end
55
+ self
56
+ end
57
+
58
+ def always_async(&block)
59
+ always(:async, &block)
60
+ end
61
+
62
+ # completes with `self` immediately if ready, and every time the collection :ready fires.
63
+ # does not retain `self` or the sender.
64
+ # returns an "unbinder" that can be called to stop listening.
65
+ def always(queue=nil, &block)
66
+ return false if cancelled?
67
+ if ready?
68
+ RMXFirebase.block_on_queue(queue, self, &block)
69
+ end
70
+ RMX(self).on(:ready, :queue => queue, &block)
71
+ end
72
+
73
+ # completes with `self` every time the collection :changed fires.
74
+ # does not retain `self` or the sender.
75
+ # returns an "unbinder" that can be called to stop listening.
76
+ def changed(queue=nil, &block)
77
+ return false if cancelled?
78
+ RMX(self).on(:changed, :queue => queue, &block)
79
+ end
80
+
81
+ # completes with `self`, `snap`, `prev` every time the collection :added fires.
82
+ # does not retain `self` or the sender.
83
+ # returns an "unbinder" that can be called to stop listening.
84
+ def added(queue=nil, &block)
85
+ return false if cancelled?
86
+ RMX(self).on(:added, :queue => queue, &block)
87
+ end
88
+
89
+ # completes with `self`, `snap` every time the collection :removed fires.
90
+ # does not retain `self` or the sender.
91
+ # returns an "unbinder" that can be called to stop listening.
92
+ def removed(queue=nil, &block)
93
+ return false if cancelled?
94
+ RMX(self).on(:removed, :queue => queue, &block)
95
+ end
96
+
97
+ # completes with `self`, `snap`, `prev` every time the collection :moved fires.
98
+ # does not retain `self` or the sender.
99
+ # returns an "unbinder" that can be called to stop listening.
100
+ def moved(queue=nil, &block)
101
+ return false if cancelled?
102
+ RMX(self).on(:moved, :queue => queue, &block)
103
+ end
104
+
105
+ def self.new(ref)
106
+ x = super()
107
+ RMXFirebase::QUEUE.barrier_async do
108
+ x.setup_ref(ref)
109
+ end
110
+ x
111
+ end
112
+
113
+ # internal
114
+ def initialize
115
+ @snaps_by_name = {}
116
+ @snaps = []
117
+ @transformations_table = {}
118
+ @transformations = []
119
+ end
120
+
121
+ # internal
122
+ def setup_ref(_ref)
123
+ RMX(self).require_queue!(RMXFirebase::QUEUE, __FILE__, __LINE__) if RMX::DEBUG_QUEUES
124
+ _clear_current_ref!
125
+ @ref = _ref
126
+ @ready = false
127
+ @cancelled = false
128
+ cancel_block = lambda do |err|
129
+ @cancelled = err
130
+ cancelled!
131
+ end
132
+ @added_handler = @ref.on(:added) do |snap, prev|
133
+ # p "NORMAL ", snap.name, prev
134
+ RMXFirebase::QUEUE.barrier_async do
135
+ # p "BARRIER", snap.name, prev
136
+ add(snap, prev)
137
+ end
138
+ end
139
+ @removed_handler = @ref.on(:removed) do |snap|
140
+ RMXFirebase::QUEUE.barrier_async do
141
+ remove(snap)
142
+ end
143
+ end
144
+ @moved_handler = @ref.on(:moved) do |snap, prev|
145
+ RMXFirebase::QUEUE.barrier_async do
146
+ add(snap, prev)
147
+ end
148
+ end
149
+ @value_handler = @ref.once(:value, { :disconnect => cancel_block }) do |collection|
150
+ @value_handler = nil
151
+ RMXFirebase::QUEUE.barrier_async do
152
+ ready!
153
+ end
154
+ end
155
+ RMX(self).on(:cancelled, :exclusive => [ :ready, :finished, :changed, :added, :removed, :moved ], :queue => :async)
156
+ end
157
+
158
+ def refresh_order!
159
+ RMXFirebase::QUEUE.barrier_async do
160
+ next unless @ref
161
+ if @added_handler
162
+ @ref.off(@added_handler)
163
+ @added_handler = nil
164
+ end
165
+ @added_handler = @ref.on(:added) do |snap, prev|
166
+ # p "NORMAL ", snap.name, prev
167
+ RMXFirebase::QUEUE.barrier_async do
168
+ # p "BARRIER", snap.name, prev
169
+ add(snap, prev)
170
+ end
171
+ end
172
+ end
173
+ end
174
+
175
+ # mess up the order on purpose
176
+ def _test_scatter!
177
+ RMXFirebase::QUEUE.barrier_async do
178
+ _snaps = @snaps.dup
179
+ p "before scatter", @snaps.map(&:name)
180
+ p "before scatter snaps_by_name", @snaps_by_name
181
+
182
+ _snaps.each do |snap|
183
+ others = _snaps - [ snap ]
184
+ random = others.sample
185
+ add(snap, random.name)
186
+ end
187
+ end
188
+ end
189
+
190
+ def rmx_dealloc
191
+ _clear_current_ref!
192
+ end
193
+
194
+ def _clear_current_ref!
195
+ if @ref
196
+ if @added_handler
197
+ @ref.off(@added_handler)
198
+ @added_handler = nil
199
+ end
200
+ if @removed_handler
201
+ @ref.off(@removed_handler)
202
+ @removed_handler = nil
203
+ end
204
+ if @moved_handler
205
+ @ref.off(@moved_handler)
206
+ @moved_handler = nil
207
+ end
208
+ if @value_handler
209
+ @ref.off(@value_handler)
210
+ @value_handler = nil
211
+ end
212
+ @ref = nil
213
+ end
214
+ end
215
+
216
+
217
+ # internal
218
+ def ready!
219
+ RMXFirebase::QUEUE.barrier_async do
220
+ @ready = true
221
+ RMX(self).trigger(:ready, self)
222
+ RMX(self).trigger(:changed, self)
223
+ RMX(self).trigger(:finished, self)
224
+ end
225
+ end
226
+
227
+ # internal
228
+ def cancelled!
229
+ RMXFirebase::QUEUE.barrier_async do
230
+ @cancelled = true
231
+ RMX(self).trigger(:cancelled, self)
232
+ RMX(self).trigger(:finished, self)
233
+ end
234
+ end
235
+
236
+ # internal, allows the user to pass a block for transformations instead of subclassing
237
+ # and overriding #transform, for one-off cases
238
+ def transform=(block)
239
+ if block
240
+ @transform_block = RMX.safe_block(block)
241
+ else
242
+ @transform_block = nil
243
+ end
244
+ end
245
+
246
+ # internal
247
+ def store_transform(snap)
248
+ RMX(self).require_queue!(RMXFirebase::QUEUE, __FILE__, __LINE__) if RMX::DEBUG_QUEUES
249
+ @transformations_table[snap] ||= transform(snap)
250
+ end
251
+
252
+ def _log_snap_names
253
+ RMXFirebase::QUEUE.barrier_async do
254
+ puts "snaps_by_name:"
255
+ _log_hash(@snaps_by_name)
256
+ end
257
+ end
258
+
259
+
260
+ def _log_hash(hash)
261
+ hash.to_a.sort_by { |x| x[1] }.each do |pair|
262
+ puts pair.inspect
263
+ end
264
+ end
265
+
266
+ # internal
267
+ def add(snap, prev)
268
+ RMX(self).require_queue!(RMXFirebase::QUEUE, __FILE__, __LINE__) if RMX::DEBUG_QUEUES
269
+ moved = false
270
+
271
+ if current_index = @snaps_by_name[snap.name]
272
+ if current_index == 0 && prev.nil?
273
+ return
274
+ elsif current_index > 0 && prev && @snaps_by_name[prev] == current_index - 1
275
+ return
276
+ end
277
+ moved = true
278
+ @snaps.delete_at(current_index)
279
+ @transformations.delete_at(current_index)
280
+ if was_index = @snaps_by_name.delete(snap.name)
281
+ @snaps_by_name.keys.each do |k|
282
+ v = @snaps_by_name[k]
283
+ if v > was_index
284
+ @snaps_by_name[k] -= 1
285
+ end
286
+ end
287
+ end
288
+ # raise if snaps_by_name.values.uniq.size != snaps_by_name.values.size
289
+ end
290
+ # raise if snaps_by_name.values.uniq.size != snaps_by_name.values.size
291
+ if prev && (index = @snaps_by_name[prev])
292
+ new_index = index + 1
293
+ @snaps.insert(new_index, snap)
294
+ @transformations.insert(new_index, store_transform(snap))
295
+ @snaps_by_name.keys.each do |k|
296
+ v = @snaps_by_name[k]
297
+ if v >= new_index
298
+ @snaps_by_name[k] += 1
299
+ end
300
+ end
301
+ @snaps_by_name[snap.name] = new_index
302
+ # raise if snaps_by_name.values.uniq.size != snaps_by_name.values.size
303
+ else
304
+ @snaps.unshift(snap)
305
+ @transformations.unshift(store_transform(snap))
306
+ @snaps_by_name.keys.each do |k|
307
+ v = @snaps_by_name[k]
308
+ @snaps_by_name[k] += 1
309
+ end
310
+ @snaps_by_name[snap.name] = 0
311
+ # raise if snaps_by_name.values.uniq.size != snaps_by_name.values.size
312
+ end
313
+ if moved
314
+ RMX(self).trigger(:moved, self, snap, prev)
315
+ else
316
+ RMX(self).trigger(:added, self, snap, prev)
317
+ end
318
+ RMX(self).trigger(:changed, self)
319
+ if ready?
320
+ RMX(self).trigger(:ready, self)
321
+ RMX(self).trigger(:finished, self)
322
+ end
323
+ end
324
+
325
+ # internal
326
+ def remove(snap)
327
+ if current_index = @snaps_by_name[snap.name]
328
+ @snaps.delete_at(current_index)
329
+ @transformations.delete_at(current_index)
330
+ @snaps_by_name.keys.each do |k|
331
+ v = @snaps_by_name[k]
332
+ if v > current_index
333
+ @snaps_by_name[k] -= 1
334
+ end
335
+ end
336
+ RMX(self).trigger(:removed, self, snap)
337
+ RMX(self).trigger(:changed, self)
338
+ if ready?
339
+ RMX(self).trigger(:ready, self)
340
+ RMX(self).trigger(:finished, self)
341
+ end
342
+ end
343
+ end
344
+
345
+ # this is the method you should call
346
+ def self.get(ref)
347
+ if ref && existing = identity_map[[ className, ref.description ]]
348
+ if RMXFirebase::DEBUG_IDENTITY_MAP
349
+ p "HIT!", className, ref.description, existing.retainCount
350
+ end
351
+ return existing
352
+ else
353
+ if RMXFirebase::DEBUG_IDENTITY_MAP
354
+ p "MISS!", className, ref.description
355
+ end
356
+ res = new(ref)
357
+ if ref
358
+ identity_map[[ className, ref.description ]] = res
359
+ end
360
+ res
361
+ end
362
+ end
363
+
364
+ Dispatch.once do
365
+ @@identity_map = RMXSynchronizedStrongToWeakHash.new
366
+ end
367
+
368
+ def self.identity_map
369
+ @@identity_map
370
+ end
371
+
372
+ end
@@ -0,0 +1,109 @@
1
+ class RMXFirebaseCoordinator
2
+
3
+ include RMXCommonMethods
4
+
5
+ attr_reader :watches
6
+
7
+ def initialize
8
+ @watches = {}
9
+ end
10
+
11
+ def clear!
12
+ RMXFirebase::QUEUE.barrier_async do
13
+ @cancelled = false
14
+ @ready = false
15
+ keys = watches.keys.dup
16
+ while keys.size > 0
17
+ name = keys.shift
18
+ watch = watches[name]
19
+ watch.stop!
20
+ end
21
+ watches.clear
22
+ end
23
+ self
24
+ end
25
+
26
+ def ready?
27
+ !!@ready
28
+ end
29
+
30
+ def cancelled?
31
+ !!@cancelled
32
+ end
33
+
34
+ def ready!
35
+ RMXFirebase::QUEUE.barrier_async do
36
+ @ready = true
37
+ RMX(self).trigger(:ready, self)
38
+ RMX(self).trigger(:finished, self)
39
+ end
40
+ end
41
+
42
+ def cancelled!
43
+ RMXFirebase::QUEUE.barrier_async do
44
+ @cancelled = true
45
+ RMX(self).trigger(:cancelled, self)
46
+ RMX(self).trigger(:finished, self)
47
+ end
48
+ end
49
+
50
+ def stub(name)
51
+ RMX(self).require_queue!(RMXFirebase::QUEUE, __FILE__, __LINE__) if RMX::DEBUG_QUEUES
52
+ watches[name] ||= RMXFirebaseListener.new
53
+ end
54
+
55
+ def watch(name, ref, opts={}, &block)
56
+ RMX(self).require_queue!(RMXFirebase::QUEUE, __FILE__, __LINE__) if RMX::DEBUG_QUEUES
57
+ data = RMXFirebaseListener.new
58
+ data.ref = ref
59
+ if opts[:required]
60
+ data.value_required = true
61
+ end
62
+ unless block.nil?
63
+ data.callback = block.weak!
64
+ data.callback_owner = block.owner
65
+ end
66
+ if current = watches[name]
67
+ if current == data || current.description == data.description
68
+ return
69
+ end
70
+ current.stop!
71
+ RMX(current).off(:finished, self)
72
+ end
73
+ watches[name] = data
74
+ RMX(data).on(:finished, :queue => RMXFirebase::QUEUE) do
75
+ RMX(self).require_queue!(RMXFirebase::QUEUE, __FILE__, __LINE__) if RMX::DEBUG_QUEUES
76
+ readies = 0
77
+ cancelled = 0
78
+ size = watches.size
79
+ watches.each_pair do |k, v|
80
+ if v.ready?
81
+ readies += 1
82
+ elsif v.cancelled?
83
+ cancelled += 1
84
+ break
85
+ end
86
+ end
87
+ if cancelled > 0
88
+ cancelled!
89
+ elsif readies == size
90
+ ready!
91
+ end
92
+ end
93
+ data.start!
94
+ data
95
+ end
96
+
97
+ def attr(keypath)
98
+ valueForKeyPath(keypath)
99
+ end
100
+
101
+ def valueForKey(key)
102
+ watches[key]
103
+ end
104
+
105
+ def valueForUndefinedKey(key)
106
+ nil
107
+ end
108
+
109
+ end
@@ -0,0 +1,53 @@
1
+ class RMXFirebaseDataSnapshot
2
+
3
+ include RMXCommonMethods
4
+
5
+ attr_accessor :snap
6
+
7
+ def initialize(snap)
8
+ @snap = snap
9
+ end
10
+
11
+ def hasValue?
12
+ !value.nil?
13
+ end
14
+
15
+ def attr(keypath)
16
+ valueForKeyPath(keypath)
17
+ end
18
+
19
+ def valueForKey(key)
20
+ if v = value
21
+ v[key]
22
+ end
23
+ end
24
+
25
+ def valueForUndefinedKey(key)
26
+ nil
27
+ end
28
+
29
+ def value
30
+ snap.value
31
+ end
32
+
33
+ def ref
34
+ snap.ref
35
+ end
36
+
37
+ def name
38
+ snap.name
39
+ end
40
+
41
+ def priority
42
+ snap.priority
43
+ end
44
+
45
+ def count
46
+ snap.childrenCount
47
+ end
48
+
49
+ def children
50
+ snap.children.each.map { |x| RMXFirebaseDataSnapshot.new(x) }
51
+ end
52
+
53
+ end
@@ -0,0 +1,10 @@
1
+ module RMXFirebaseHandleModel
2
+ def handle(key)
3
+ define_method(key) do
4
+ self.model
5
+ end
6
+ define_method("#{key}=") do |val|
7
+ self.model = val
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,76 @@
1
+ class RMXFirebaseListener
2
+
3
+ include RMXCommonMethods
4
+
5
+ attr_accessor :snapshot, :ref, :callback, :handle, :value_required
6
+ RMX(self).weak_attr_accessor :callback_owner
7
+
8
+ def rmx_dealloc
9
+ if ref && handle
10
+ ref.off(handle)
11
+ end
12
+ end
13
+
14
+ def ready?
15
+ !!@ready
16
+ end
17
+
18
+ def cancelled?
19
+ !!@cancelled
20
+ end
21
+
22
+ def start!
23
+ RMX(self).require_queue!(RMXFirebase::QUEUE, __FILE__, __LINE__) if RMX::DEBUG_QUEUES
24
+ cancel_block = lambda do |err|
25
+ @cancelled = err
26
+ RMX(self).trigger(:cancelled, self)
27
+ RMX(self).trigger(:finished, self)
28
+ end
29
+ @handle = ref.on(:value, { :disconnect => cancel_block }) do |snap|
30
+ RMXFirebase::QUEUE.barrier_async do
31
+ @snapshot = snap
32
+ if value_required && !snap.hasValue?
33
+ cancel_block.call(NSError.errorWithDomain("requirement failure", code:0, userInfo:{
34
+ :error => "requirement_failure"
35
+ }))
36
+ else
37
+ callback.call(snap) if callback && callback_owner
38
+ @ready = true
39
+ RMX(self).trigger(:ready, self)
40
+ RMX(self).trigger(:finished, self)
41
+ # p "ready__"
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ def stop!
48
+ RMX(self).require_queue!(RMXFirebase::QUEUE, __FILE__, __LINE__) if RMX::DEBUG_QUEUES
49
+ @cancelled = false
50
+ @ready = false
51
+ if ref && handle
52
+ ref.off(handle)
53
+ end
54
+ end
55
+
56
+ def hasValue?
57
+ !!(snapshot && snapshot.hasValue?)
58
+ end
59
+
60
+ def toValue
61
+ snapshot && snapshot.value
62
+ end
63
+
64
+ def attr(keypath)
65
+ valueForKeyPath(keypath)
66
+ end
67
+
68
+ def valueForKey(key)
69
+ snapshot && snapshot.valueForKey(key)
70
+ end
71
+
72
+ def valueForUndefinedKey(key)
73
+ nil
74
+ end
75
+
76
+ end