rmx-firebase 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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