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.
- 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
|