bubble-wrap 1.0.0 → 1.1.0

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