bubble-wrap 1.0.0 → 1.1.0

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.
Files changed (65) hide show
  1. data/.gitignore +0 -1
  2. data/.yardopts +2 -0
  3. data/CHANGELOG.md +15 -0
  4. data/Gemfile +1 -1
  5. data/Gemfile.lock +24 -0
  6. data/HACKING.md +2 -2
  7. data/README.md +363 -21
  8. data/Rakefile +6 -2
  9. data/lib/bubble-wrap.rb +0 -1
  10. data/lib/bubble-wrap/all.rb +4 -0
  11. data/lib/bubble-wrap/camera.rb +7 -0
  12. data/lib/bubble-wrap/core.rb +3 -2
  13. data/lib/bubble-wrap/ext/motion_project_app.rb +2 -2
  14. data/lib/bubble-wrap/http.rb +1 -0
  15. data/lib/bubble-wrap/loader.rb +19 -12
  16. data/lib/bubble-wrap/location.rb +6 -0
  17. data/lib/bubble-wrap/reactor.rb +10 -0
  18. data/lib/bubble-wrap/requirement.rb +11 -3
  19. data/lib/bubble-wrap/rss_parser.rb +2 -0
  20. data/lib/bubble-wrap/ui.rb +4 -0
  21. data/lib/bubble-wrap/version.rb +1 -1
  22. data/motion/core.rb +8 -2
  23. data/motion/core/app.rb +34 -6
  24. data/motion/core/device.rb +10 -1
  25. data/motion/core/device/camera.rb +219 -0
  26. data/motion/core/device/camera_wrapper.rb +45 -0
  27. data/motion/core/ns_url_request.rb +10 -0
  28. data/motion/core/persistence.rb +7 -0
  29. data/motion/core/pollute.rb +1 -2
  30. data/motion/core/string.rb +23 -0
  31. data/motion/http.rb +142 -83
  32. data/motion/location/location.rb +152 -0
  33. data/motion/location/pollute.rb +5 -0
  34. data/motion/reactor.rb +105 -0
  35. data/motion/reactor/default_deferrable.rb +9 -0
  36. data/motion/reactor/deferrable.rb +126 -0
  37. data/motion/reactor/eventable.rb +24 -0
  38. data/motion/reactor/future.rb +22 -0
  39. data/motion/reactor/periodic_timer.rb +27 -0
  40. data/motion/reactor/queue.rb +60 -0
  41. data/motion/reactor/timer.rb +25 -0
  42. data/motion/rss_parser.rb +131 -0
  43. data/motion/shortcut.rb +18 -0
  44. data/motion/{core → ui}/gestures.rb +15 -9
  45. data/motion/ui/pollute.rb +5 -0
  46. data/motion/{core → ui}/ui_control.rb +0 -0
  47. data/motion/{core → ui}/ui_view_controller.rb +0 -0
  48. data/resources/atom.xml +1705 -0
  49. data/spec/lib/bubble-wrap/ext/motion_project_app_spec.rb +7 -11
  50. data/spec/lib/bubble-wrap/requirement_spec.rb +4 -4
  51. data/spec/lib/bubble-wrap_spec.rb +2 -2
  52. data/spec/motion/core/app_spec.rb +118 -14
  53. data/spec/motion/core/device/camera_spec.rb +130 -0
  54. data/spec/motion/core/device/camera_wrapper_spec.rb +45 -0
  55. data/spec/motion/core/device_spec.rb +0 -50
  56. data/spec/motion/core/gestures_spec.rb +5 -0
  57. data/spec/motion/core/persistence_spec.rb +24 -2
  58. data/spec/motion/core/string_spec.rb +55 -0
  59. data/spec/motion/core_spec.rb +72 -1
  60. data/spec/motion/http_spec.rb +100 -65
  61. data/spec/motion/location/location_spec.rb +152 -0
  62. data/spec/motion/reactor/eventable_spec.rb +40 -0
  63. data/spec/motion/reactor_spec.rb +163 -0
  64. data/spec/motion/rss_parser_spec.rb +36 -0
  65. metadata +75 -16
@@ -0,0 +1,152 @@
1
+ # Provides a nice DSL for interacting with the standard
2
+ # CLLocationManager
3
+ #
4
+ module BubbleWrap
5
+ module CLLocationWrap
6
+
7
+ def latitude
8
+ self.coordinate.latitude
9
+ end
10
+
11
+ def longitude
12
+ self.coordinate.longitude
13
+ end
14
+ end
15
+
16
+ module Location
17
+ module Error
18
+ DISABLED=0
19
+ PERMISSION_DENIED=1
20
+ NETWORK_FAILURE=2
21
+ LOCATION_UNKNOWN=3
22
+ end
23
+
24
+ module_function
25
+ # Start getting locations
26
+ # @param [Hash] options = {
27
+ # significant: true/false; whether to listen for significant location changes or
28
+ # all location changes (see Apple docs for info); default == false
29
+ # distance_filter: minimum change in distance to be updated about, in meters;
30
+ # default == uses KCLDistanceFilterNone,
31
+ # desired_accuracy: minimum accuracy for updates to arrive;
32
+ # any of :best_for_navigation, :best, :nearest_ten_meters,
33
+ # :hundred_meters, :kilometer, or :three_kilometers; default == :best
34
+ # purpose: string to display when the system asks user for location,
35
+ # retries: if location cant be found. how many errors do we retry; default == 5
36
+ # }
37
+ # @block for callback. takes one argument, `result`.
38
+ # - On error or cancelled, is called with a hash {error: BW::Location::Error::<Type>}
39
+ # - On success, is called with a hash {to: #<CLLocation>, from: #<CLLocation>}
40
+ #
41
+ # Example
42
+ # BW::Location.get(distance_filter: 10, desired_accuracy: :nearest_ten_meters) do |result|
43
+ # result[:to].class == CLLocation
44
+ # result[:from].class == CLLocation
45
+ # p "Lat #{result[:to].latitude}, Long #{result[:to].longitude}"
46
+ # end
47
+ def get(options = {}, &block)
48
+ @callback = block
49
+ @options = options
50
+
51
+ @options[:significant] = false if @options[:significant].nil?
52
+ @options[:distance_filter] ||= KCLDistanceFilterNone
53
+ @options[:desired_accuracy] ||= KCLLocationAccuracyBest
54
+ @options[:retries] ||= 5
55
+ @retries = 0
56
+
57
+ if not enabled?
58
+ error(Error::DISABLED) and return
59
+ end
60
+
61
+ self.location_manager.distanceFilter = @options[:distance_filter]
62
+ self.location_manager.desiredAccuracy = const_int_get("KCLLocationAccuracy", @options[:desired_accuracy])
63
+ self.location_manager.purpose = @options[:purpose] if @options[:purpose]
64
+
65
+ if @options[:significant]
66
+ self.location_manager.startMonitoringSignificantLocationChanges
67
+ else
68
+ self.location_manager.startUpdatingLocation
69
+ end
70
+ end
71
+
72
+ def get_significant(options = {}, &block)
73
+ get(options.merge(significant: true), &block)
74
+ end
75
+
76
+ # Stop getting locations
77
+ def stop
78
+ if @options[:significant]
79
+ self.location_manager.stopMonitoringSignificantLocationChanges
80
+ else
81
+ self.location_manager.stopUpdatingLocation
82
+ end
83
+ end
84
+
85
+ def location_manager
86
+ @location_manager ||= CLLocationManager.alloc.init
87
+ @location_manager.delegate ||= self
88
+ @location_manager
89
+ end
90
+
91
+ # returns true/false whether services are enabled for the _device_
92
+ def enabled?
93
+ CLLocationManager.locationServicesEnabled
94
+ end
95
+
96
+ def error(type)
97
+ @callback && @callback.call({ error: type })
98
+ @callback = nil
99
+ self.location_manager.stopUpdatingLocation
100
+ end
101
+
102
+ ##########
103
+ # CLLocationManagerDelegate Methods
104
+ def locationManager(manager, didUpdateToLocation:newLocation, fromLocation:oldLocation)
105
+ @callback.call({to: newLocation, from: oldLocation})
106
+ end
107
+
108
+ def locationManager(manager, didFailWithError:error)
109
+ if error.domain == KCLErrorDomain
110
+ case error.code
111
+ when KCLErrorDenied
112
+ error(Error::PERMISSION_DENIED)
113
+ when KCLErrorLocationUnknown
114
+ # Docs specify that this is a temporary error,
115
+ # so we stop/start updating to try again.
116
+ @retries += 1
117
+ if @retries > @options[:retries]
118
+ error(Error::LOCATION_UNKNOWN)
119
+ else
120
+ self.locationManager.stopUpdatingLocation
121
+ self.locationManager.startUpdatingLocation
122
+ end
123
+ when KCLErrorNetwork
124
+ error(Error::NETWORK_FAILURE)
125
+ end
126
+ end
127
+ end
128
+
129
+ def locationManager(manager, didChangeAuthorizationStatus:status)
130
+ case status
131
+ when KCLAuthorizationStatusRestricted
132
+ error(Error::PERMISSION_DENIED)
133
+ when KCLAuthorizationStatusDenied
134
+ error(Error::PERMISSION_DENIED)
135
+ end
136
+ end
137
+
138
+ def const_int_get(base, value)
139
+ return value if value.is_a? Numeric
140
+ value = value.to_s.camelize
141
+ Kernel.const_get("#{base}#{value}")
142
+ end
143
+
144
+ def load_constants_hack
145
+ [KCLLocationAccuracyBestForNavigation, KCLLocationAccuracyBest,
146
+ KCLLocationAccuracyNearestTenMeters, KCLLocationAccuracyHundredMeters,
147
+ KCLLocationAccuracyKilometer, KCLLocationAccuracyThreeKilometers,
148
+ ]
149
+ end
150
+ end
151
+ end
152
+ ::Location = BubbleWrap::Location
@@ -0,0 +1,5 @@
1
+ [
2
+ [CLLocation, BubbleWrap::CLLocationWrap],
3
+ ].each do |base, wrapper|
4
+ base.send(:include, wrapper)
5
+ end
data/motion/reactor.rb ADDED
@@ -0,0 +1,105 @@
1
+ module BubbleWrap
2
+ module Reactor
3
+ module_function
4
+
5
+ # Always returns true - for compatibility with EM
6
+ def reactor_running?
7
+ true
8
+ end
9
+ alias reactor_thread? reactor_running?
10
+
11
+ # Call `callback` or the passed block in `interval` seconds.
12
+ # Returns a timer signature that can be passed into
13
+ # `cancel_timer`
14
+ def add_timer(interval, callback=nil, &blk)
15
+ @timers ||= {}
16
+ timer = Timer.new(interval,callback,&blk)
17
+ timer.on(:fired) do
18
+ @timers.delete(timer.object_id)
19
+ end
20
+ timer.on(:cancelled) do
21
+ @timers.delete(timer.object_id)
22
+ end
23
+ @timers[timer.object_id] = timer
24
+ timer.object_id
25
+ end
26
+
27
+ # Cancel a timer by passing in either a Timer object or
28
+ # a timer id (as returned by `add_timer` and
29
+ # `add_periodic_timer`).
30
+ def cancel_timer(timer)
31
+ return timer.cancel if timer.respond_to?(:cancel)
32
+ @timers ||= {}
33
+ return @timers[timer].cancel if @timers[timer]
34
+ false
35
+ end
36
+
37
+ # Call `callback` or the passed block every `interval` seconds.
38
+ # Returns a timer signature that can be passed into
39
+ # `cancel_timer`
40
+ def add_periodic_timer(interval, callback=nil, &blk)
41
+ @timers ||= {}
42
+ timer = PeriodicTimer.new(interval,callback,&blk)
43
+ timer.on(:cancelled) do
44
+ @timers.delete(timer)
45
+ end
46
+ @timers[timer.object_id] = timer
47
+ timer.object_id
48
+ end
49
+
50
+ # Defer is for integrating blocking operations into the reactor's control
51
+ # flow.
52
+ # Call defer with one or two blocks, the second block is optional.
53
+ # operator = proc do
54
+ # # perform a long running operation here
55
+ # "result"
56
+ # end
57
+ # callback = proc do |result|
58
+ # # do something with the result here, such as trigger a UI change
59
+ # end
60
+ # BubbleWrap::Reactor.defer(operation,callback)
61
+ # The action of `defer` is to take the block specified in the first
62
+ # parameter (the "operation") and schedule it for asynchronous execution
63
+ # on a GCD concurrency queue. When the operation completes the result (if any)
64
+ # is passed into the callback (if present).
65
+ def defer(op=nil,cb=nil,&blk)
66
+ schedule do
67
+ result = (op||blk).call
68
+ schedule(result, &cb) if cb
69
+ end
70
+ end
71
+
72
+ # A version of `defer` which schedules both the operator
73
+ # and callback operations on the application's main thread.
74
+ def defer_on_main(op=nil,cb=nil,&blk)
75
+ schedule_on_main do
76
+ result = (op||blk).call
77
+ schedule_on_main(result, &cb) if cb
78
+ end
79
+ end
80
+
81
+ # Schedule a block for execution on the reactor queue.
82
+ def schedule(*args, &blk)
83
+ @queue ||= ::Dispatch::Queue.concurrent("#{NSBundle.mainBundle.bundleIdentifier}.reactor")
84
+
85
+ cb = proc do
86
+ blk.call(*args)
87
+ end
88
+ @queue.async &cb
89
+ nil
90
+ end
91
+
92
+ # Schedule a block for execution on your application's main thread.
93
+ # This is useful as UI updates need to be executed from the main
94
+ # thread.
95
+ def schedule_on_main(*args, &blk)
96
+ cb = proc do
97
+ blk.call(*args)
98
+ end
99
+ ::Dispatch::Queue.main.async &cb
100
+ end
101
+
102
+ end
103
+ end
104
+
105
+ ::EM = ::BubbleWrap::Reactor # Yes I dare!
@@ -0,0 +1,9 @@
1
+ module BubbleWrap
2
+ module Reactor
3
+ # A basic class which includes Deferrable when all
4
+ # you need is a deferrable without any added behaviour.
5
+ class DefaultDeferrable
6
+ include ::BubbleWrap::Reactor::Deferrable
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,126 @@
1
+ module BubbleWrap
2
+ module Reactor
3
+ # Provides a mixin for deferrable jobs.
4
+ module Deferrable
5
+
6
+ # def self.included(base)
7
+ # base.extend ::BubbleWrap::Reactor::Future
8
+ # end
9
+
10
+ # Specify a block to be executed if and when the Deferrable object
11
+ # receives a status of :succeeded. See set_deferred_status for more
12
+ # information.
13
+ # Calling this method on a Deferrable object whose status is not yet
14
+ # known will cause the callback block to be stored on an internal
15
+ # list. If you call this method on a Deferrable whose status is
16
+ # :succeeded, the block will be executed immediately, receiving
17
+ # the parameters given to the prior set_deferred_status call.
18
+ def callback(&blk)
19
+ return unless blk
20
+ @deferred_status ||= :unknown
21
+ if @deferred_status == :succeeded
22
+ blk.call(*@deferred_args)
23
+ elsif @deferred_status != :failed
24
+ @callbacks ||= []
25
+ @callbacks.unshift blk
26
+ end
27
+ end
28
+
29
+ # Cancels an outstanding timeout if any. Undoes the action of timeout.
30
+ def cancel_timeout
31
+ @deferred_timeout ||= nil
32
+ if @deferred_timeout
33
+ @deferred_timeout.cancel
34
+ @deferred_timeout = nil
35
+ end
36
+ end
37
+
38
+ # Specify a block to be executed if and when the Deferrable object
39
+ # receives a status of :failed. See set_deferred_status for more
40
+ # information.
41
+ def errback(&blk)
42
+ return unless blk
43
+ @deferred_status ||= :unknown
44
+ if @deferred_status == :failed
45
+ blk.call(*@deferred_args)
46
+ elsif @deferred_status != :succeeded
47
+ @errbacks ||= []
48
+ @errbacks.unshift blk
49
+ end
50
+ end
51
+
52
+ # Sugar for set_deferred_status(:failed, …)
53
+ def fail(*args)
54
+ set_deferred_status :failed, *args
55
+ end
56
+ alias set_deferred_failure fail
57
+
58
+ # Sets the “disposition” (status) of the Deferrable object. See also
59
+ # the large set of sugarings for this method. Note that if you call
60
+ # this method without arguments, no arguments will be passed to the
61
+ # callback/errback. If the user has coded these with arguments,
62
+ # then the user code will throw an argument exception. Implementors
63
+ # of deferrable classes must document the arguments they will supply
64
+ # to user callbacks.
65
+ # OBSERVE SOMETHING VERY SPECIAL here: you may call this method even
66
+ # on the INSIDE of a callback. This is very useful when a
67
+ # previously-registered callback wants to change the parameters that
68
+ # will be passed to subsequently-registered ones.
69
+ # You may give either :succeeded or :failed as the status argument.
70
+ # If you pass :succeeded, then all of the blocks passed to the object
71
+ # using the callback method (if any) will be executed BEFORE the
72
+ # set_deferred_status method returns. All of the blocks passed to the
73
+ # object using errback will be discarded.
74
+ # If you pass :failed, then all of the blocks passed to the object
75
+ # using the errback method (if any) will be executed BEFORE the
76
+ # set_deferred_status method returns. All of the blocks passed to the
77
+ # object using # callback will be discarded.
78
+ # If you pass any arguments to set_deferred_status in addition to the
79
+ # status argument, they will be passed as arguments to any callbacks
80
+ # or errbacks that are executed. It’s your responsibility to ensure
81
+ # that the argument lists specified in your callbacks and errbacks match
82
+ # the arguments given in calls to set_deferred_status, otherwise Ruby
83
+ # will raise an ArgumentError.
84
+ def set_deferred_status(status, *args)
85
+ cancel_timeout
86
+ @errbacks ||= nil
87
+ @callbacks ||= nil
88
+ @deferred_status = status
89
+ @deferred_args = args
90
+ case @deferred_status
91
+ when :succeeded
92
+ if @callbacks
93
+ while cb = @callbacks.pop
94
+ cb.call(*@deferred_args)
95
+ end
96
+ end
97
+ @errbacks.clear if @errbacks
98
+ when :failed
99
+ if @errbacks
100
+ while eb = @errbacks.pop
101
+ eb.call(*@deferred_args)
102
+ end
103
+ end
104
+ @callbacks.clear if @callbacks
105
+ end
106
+ end
107
+
108
+ # Sugar for set_deferred_status(:succeeded, …)
109
+ def succeed(*args)
110
+ set_deferred_status :succeeded, *args
111
+ end
112
+ alias set_deferred_success succeed
113
+
114
+ # Setting a timeout on a Deferrable causes it to go into the failed
115
+ # state after the Timeout expires (passing no arguments to the object’s
116
+ # errbacks). Setting the status at any time prior to a call to the
117
+ # expiration of the timeout will cause the timer to be cancelled.
118
+ def timeout(seconds)
119
+ cancel_timeout
120
+ me = self
121
+ @deferred_timeout = Timer.new(seconds) {me.fail}
122
+ end
123
+
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,24 @@
1
+ module BubbleWrap
2
+ module Reactor
3
+ # A simple mixin that adds events to your object.
4
+ module Eventable
5
+
6
+ # When `event` is triggered the block will execute
7
+ # and be passed the arguments that are passed to
8
+ # `trigger`.
9
+ def on(event, &blk)
10
+ @events ||= Hash.new { |h,k| h[k] = [] }
11
+ @events[event].push blk
12
+ end
13
+
14
+ # Trigger an event
15
+ def trigger(event, *args)
16
+ @events ||= Hash.new { |h,k| h[k] = [] }
17
+ @events[event].map do |event|
18
+ event.call(*args)
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,22 @@
1
+ module BubbleWrap
2
+ module Reactor
3
+ module Future
4
+
5
+ # A future is a sugaring of a typical deferrable usage.
6
+ def future arg, cb=nil, eb=nil, &blk
7
+ arg = arg.call if arg.respond_to?(:call)
8
+
9
+ if arg.respond_to?(:set_deferred_status)
10
+ if cb || eb
11
+ arg.callback(&cb) if cb
12
+ arg.errback(&eb) if eb
13
+ else
14
+ arg.callback(&blk) if blk
15
+ end
16
+ end
17
+
18
+ arg
19
+ end
20
+ end
21
+ end
22
+ end