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.
- data/.gitignore +0 -1
- data/.yardopts +2 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +24 -0
- data/HACKING.md +2 -2
- data/README.md +363 -21
- data/Rakefile +6 -2
- data/lib/bubble-wrap.rb +0 -1
- data/lib/bubble-wrap/all.rb +4 -0
- data/lib/bubble-wrap/camera.rb +7 -0
- data/lib/bubble-wrap/core.rb +3 -2
- data/lib/bubble-wrap/ext/motion_project_app.rb +2 -2
- data/lib/bubble-wrap/http.rb +1 -0
- data/lib/bubble-wrap/loader.rb +19 -12
- data/lib/bubble-wrap/location.rb +6 -0
- data/lib/bubble-wrap/reactor.rb +10 -0
- data/lib/bubble-wrap/requirement.rb +11 -3
- data/lib/bubble-wrap/rss_parser.rb +2 -0
- data/lib/bubble-wrap/ui.rb +4 -0
- data/lib/bubble-wrap/version.rb +1 -1
- data/motion/core.rb +8 -2
- data/motion/core/app.rb +34 -6
- data/motion/core/device.rb +10 -1
- data/motion/core/device/camera.rb +219 -0
- data/motion/core/device/camera_wrapper.rb +45 -0
- data/motion/core/ns_url_request.rb +10 -0
- data/motion/core/persistence.rb +7 -0
- data/motion/core/pollute.rb +1 -2
- data/motion/core/string.rb +23 -0
- data/motion/http.rb +142 -83
- data/motion/location/location.rb +152 -0
- data/motion/location/pollute.rb +5 -0
- data/motion/reactor.rb +105 -0
- data/motion/reactor/default_deferrable.rb +9 -0
- data/motion/reactor/deferrable.rb +126 -0
- data/motion/reactor/eventable.rb +24 -0
- data/motion/reactor/future.rb +22 -0
- data/motion/reactor/periodic_timer.rb +27 -0
- data/motion/reactor/queue.rb +60 -0
- data/motion/reactor/timer.rb +25 -0
- data/motion/rss_parser.rb +131 -0
- data/motion/shortcut.rb +18 -0
- data/motion/{core → ui}/gestures.rb +15 -9
- data/motion/ui/pollute.rb +5 -0
- data/motion/{core → ui}/ui_control.rb +0 -0
- data/motion/{core → ui}/ui_view_controller.rb +0 -0
- data/resources/atom.xml +1705 -0
- data/spec/lib/bubble-wrap/ext/motion_project_app_spec.rb +7 -11
- data/spec/lib/bubble-wrap/requirement_spec.rb +4 -4
- data/spec/lib/bubble-wrap_spec.rb +2 -2
- data/spec/motion/core/app_spec.rb +118 -14
- data/spec/motion/core/device/camera_spec.rb +130 -0
- data/spec/motion/core/device/camera_wrapper_spec.rb +45 -0
- data/spec/motion/core/device_spec.rb +0 -50
- data/spec/motion/core/gestures_spec.rb +5 -0
- data/spec/motion/core/persistence_spec.rb +24 -2
- data/spec/motion/core/string_spec.rb +55 -0
- data/spec/motion/core_spec.rb +72 -1
- data/spec/motion/http_spec.rb +100 -65
- data/spec/motion/location/location_spec.rb +152 -0
- data/spec/motion/reactor/eventable_spec.rb +40 -0
- data/spec/motion/reactor_spec.rb +163 -0
- data/spec/motion/rss_parser_spec.rb +36 -0
- 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
|
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,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
|