elevate 0.5.0 → 0.6.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.
@@ -1,22 +1,112 @@
1
1
  module Elevate
2
2
  module HTTP
3
- class HTTPResponse
3
+ # Encapsulates a response received from a HTTP server.
4
+ #
5
+ # @api public
6
+ class Response
4
7
  def initialize
5
8
  @body = nil
6
9
  @headers = nil
7
10
  @status_code = nil
8
11
  @error = nil
12
+ @raw_body = nil
13
+ @url = nil
9
14
  end
10
15
 
16
+ # Appends a chunk of data to the body.
17
+ #
18
+ # @api private
11
19
  def append_data(data)
12
- @body ||= NSMutableData.alloc.init
13
- @body.appendData(data)
20
+ @raw_body ||= NSMutableData.alloc.init
21
+ @raw_body.appendData(data)
14
22
  end
15
23
 
16
- attr_reader :body
24
+ # Returns the body of the response.
25
+ #
26
+ # If the body is JSON-encoded, it will be decoded and returned.
27
+ #
28
+ # @return [NSData, Hash, Array, nil]
29
+ # response body, if any. If the response is JSON-encoded, the decoded body.
30
+ #
31
+ # @api public
32
+ def body
33
+ @body ||= begin
34
+ if json?
35
+ NSJSONSerialization.JSONObjectWithData(@raw_body, options: 0, error: nil)
36
+ else
37
+ @raw_body
38
+ end
39
+ end
40
+ end
41
+
42
+ # Freezes this instance, making it immutable.
43
+ #
44
+ # @api private
45
+ def freeze
46
+ body
47
+
48
+ super
49
+ end
50
+
51
+ # Forwards unknown methods to +body+, enabling this object to behave like +body+.
52
+ #
53
+ # This only occurs if +body+ is a Ruby collection.
54
+ #
55
+ # @api public
56
+ def method_missing(m, *args, &block)
57
+ return super unless json?
58
+
59
+ body.send(m, *args, &block)
60
+ end
61
+
62
+ # Handles missing method queries, allowing +body+ masquerading.
63
+ #
64
+ # @api public
65
+ def respond_to_missing?(m, include_private = false)
66
+ return false unless json?
67
+
68
+ body.respond_to_missing?(m, include_private)
69
+ end
70
+
71
+ # Returns the HTTP headers
72
+ #
73
+ # @return [Hash]
74
+ # returned headers
75
+ #
76
+ # @api public
17
77
  attr_accessor :headers
78
+
79
+ # Returns the HTTP status code
80
+ #
81
+ # @return [Integer]
82
+ # status code of the response
83
+ #
84
+ # @api public
18
85
  attr_accessor :status_code
86
+
19
87
  attr_accessor :error
88
+
89
+ # Returns the raw body
90
+ #
91
+ # @return [NSData]
92
+ # response body
93
+ #
94
+ # @api public
95
+ attr_reader :raw_body
96
+
97
+ # Returns the URL
98
+ #
99
+ # @return [String]
100
+ # URL of the response
101
+ #
102
+ # @api public
103
+ attr_accessor :url
104
+
105
+ private
106
+
107
+ def json?
108
+ headers && headers["Content-Type"] =~ %r{application/json}
109
+ end
20
110
  end
21
111
  end
22
112
  end
@@ -1,6 +1,35 @@
1
1
  module Elevate
2
2
  module HTTP
3
3
  module URI
4
+ def self.encode_www_form(enum)
5
+ enum.map do |k,v|
6
+ if v.nil?
7
+ encode_www_form_component(k)
8
+ elsif v.respond_to?(:to_ary)
9
+ v.to_ary.map do |w|
10
+ str = encode_www_form_component(k)
11
+
12
+ if w.nil?
13
+ str
14
+ else
15
+ str + "=" + encode_www_form_component(w)
16
+ end
17
+ end.join('&')
18
+ else
19
+ encode_www_form_component(k) + "=" + encode_www_form_component(v)
20
+ end
21
+ end.join('&')
22
+ end
23
+
24
+ def self.encode_www_form_component(str)
25
+ # From AFNetworking :)
26
+ CFURLCreateStringByAddingPercentEscapes(nil,
27
+ str,
28
+ "[].",
29
+ ":/?&=;+!@\#$()~',*",
30
+ CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding))
31
+ end
32
+
4
33
  def self.encode_query(hash)
5
34
  return "" if hash.nil? || hash.empty?
6
35
 
@@ -1,58 +1,114 @@
1
1
  module Elevate
2
+ # Implements task cancellation.
3
+ #
4
+ # Compliant I/O mechanisms (such as HTTP requests) register long-running
5
+ # operations with a well-known instance of this class. When a cancellation
6
+ # request is received from another thread, the long-running operation is
7
+ # cancelled.
2
8
  class IOCoordinator
9
+ # Retrieves the current IOCoordinator for this thread.
10
+ #
11
+ # @return [IOCoordinator,nil]
12
+ # IOCoordinator previously installed to this thread
13
+ #
14
+ # @api public
3
15
  def self.for_thread
4
16
  Thread.current[:io_coordinator]
5
17
  end
6
18
 
19
+ # Initializes a new IOCoordinator with the default state.
20
+ #
21
+ # @api private
7
22
  def initialize
8
23
  @lock = NSLock.alloc.init
9
24
  @blocking_operation = nil
10
25
  @cancelled = false
26
+ @exception_class = nil
11
27
  end
12
28
 
13
- def cancel
29
+ # Cancels the I/O operation (if any), raising an exception of type
30
+ # +exception_class+ in the worker thread.
31
+ #
32
+ # If the thread is not currently blocked, then set a flag requesting cancellation.
33
+ #
34
+ # @return [void]
35
+ #
36
+ # @api private
37
+ def cancel(exception_class = CancelledError)
14
38
  blocking_operation = nil
15
39
 
16
- @lock.lock()
40
+ @lock.lock
17
41
  @cancelled = true
42
+ @exception_class = exception_class
18
43
  blocking_operation = @blocking_operation
19
- @lock.unlock()
44
+ @lock.unlock
20
45
 
21
46
  if blocking_operation
22
- blocking_operation.cancel()
47
+ blocking_operation.cancel
23
48
  end
24
49
  end
25
50
 
51
+ # Returns the cancelled flag.
52
+ #
53
+ # @return [Boolean]
54
+ # true if this coordinator has been +cancel+ed previously.
55
+ #
56
+ # @api private
26
57
  def cancelled?
27
58
  cancelled = nil
28
59
 
29
- @lock.lock()
60
+ @lock.lock
30
61
  cancelled = @cancelled
31
- @lock.unlock()
62
+ @lock.unlock
32
63
 
33
64
  cancelled
34
65
  end
35
66
 
67
+ # Installs this IOCoordinator to a well-known thread-local.
68
+ #
69
+ # @return [void]
70
+ #
71
+ # @api private
36
72
  def install
37
73
  Thread.current[:io_coordinator] = self
38
74
  end
39
75
 
76
+ # Marks the specified operation as one that will potentially block the
77
+ # worker thread for a significant amount of time.
78
+ #
79
+ # @param operation [#cancel]
80
+ # operation responsible for blocking
81
+ #
82
+ # @return [void]
83
+ #
84
+ # @api public
40
85
  def signal_blocked(operation)
41
86
  check_for_cancellation
42
87
 
43
- @lock.lock()
88
+ @lock.lock
44
89
  @blocking_operation = operation
45
- @lock.unlock()
90
+ @lock.unlock
46
91
  end
47
92
 
93
+ # Signals that the specified operation has completed, and is no longer
94
+ # responsible for blocking the worker thread.
95
+ #
96
+ # @return [void]
97
+ #
98
+ # @api public
48
99
  def signal_unblocked(operation)
49
- @lock.lock()
100
+ @lock.lock
50
101
  @blocking_operation = nil
51
- @lock.unlock()
102
+ @lock.unlock
52
103
 
53
104
  check_for_cancellation
54
105
  end
55
106
 
107
+ # Removes the thread-local for the calling thread.
108
+ #
109
+ # @return [void]
110
+ #
111
+ # @api private
56
112
  def uninstall
57
113
  Thread.current[:io_coordinator] = nil
58
114
  end
@@ -60,10 +116,19 @@ module Elevate
60
116
  private
61
117
 
62
118
  def check_for_cancellation
63
- raise CancelledError if cancelled?
119
+ raise @exception_class if cancelled?
64
120
  end
65
121
  end
66
122
 
123
+ # Raised when a task is cancelled.
124
+ #
125
+ # @api public
67
126
  class CancelledError < StandardError
68
127
  end
128
+
129
+ # Raised when a task's timeout expires
130
+ #
131
+ # @api public
132
+ class TimeoutError < CancelledError
133
+ end
69
134
  end
@@ -1,9 +1,19 @@
1
1
  module Elevate
2
+ # Executes an Elevate task, firing callbacks along the way.
3
+ #
2
4
  class ElevateOperation < NSOperation
5
+ # Designated initializer.
6
+ #
7
+ # @return [ElevateOperation]
8
+ # newly initialized instance
9
+ #
10
+ # @api private
3
11
  def initWithTarget(target, args:args)
4
- if init()
12
+ if init
5
13
  @coordinator = IOCoordinator.new
6
14
  @context = TaskContext.new(args, &target)
15
+ @timeout_callback = nil
16
+ @timer = nil
7
17
  @update_callback = nil
8
18
  @finish_callback = nil
9
19
 
@@ -14,6 +24,13 @@ module Elevate
14
24
 
15
25
  Dispatch::Queue.main.sync do
16
26
  @context = nil
27
+
28
+ if @timer
29
+ @timer.invalidate
30
+ @timer = nil
31
+ end
32
+
33
+ @timeout_callback = nil
17
34
  @update_callback = nil
18
35
  @finish_callback = nil
19
36
  end
@@ -23,12 +40,23 @@ module Elevate
23
40
  self
24
41
  end
25
42
 
43
+ # Cancels the currently running task.
44
+ #
45
+ # @return [void]
46
+ #
47
+ # @api public
26
48
  def cancel
27
49
  @coordinator.cancel
28
50
 
29
51
  super
30
52
  end
31
53
 
54
+ # Returns information about this task.
55
+ #
56
+ # @return [String]
57
+ # String suitable for debugging purposes.
58
+ #
59
+ # @api public
32
60
  def inspect
33
61
  details = []
34
62
  details << "<canceled>" if @coordinator.cancelled?
@@ -36,10 +64,20 @@ module Elevate
36
64
  "#<#{self.class.name}: #{details.join(" ")}>"
37
65
  end
38
66
 
67
+ # Logs debugging information in certain configurations.
68
+ #
69
+ # @return [void]
70
+ #
71
+ # @api private
39
72
  def log(line)
40
73
  puts line unless RUBYMOTION_ENV == "test"
41
74
  end
42
75
 
76
+ # Runs the specified task.
77
+ #
78
+ # @return [void]
79
+ #
80
+ # @api private
43
81
  def main
44
82
  log " START: #{inspect}"
45
83
 
@@ -54,6 +92,10 @@ module Elevate
54
92
 
55
93
  rescue Exception => e
56
94
  @exception = e
95
+
96
+ if e.is_a?(TimeoutError)
97
+ @timeout_callback.call if @timeout_callback
98
+ end
57
99
  end
58
100
 
59
101
  @coordinator.uninstall
@@ -61,13 +103,49 @@ module Elevate
61
103
  log "FINISH: #{inspect}"
62
104
  end
63
105
 
106
+ # Returns the exception that terminated this task, if any.
107
+ #
108
+ # If the task has not finished, returns nil.
109
+ #
110
+ # @return [Exception, nil]
111
+ # exception that terminated the task
112
+ #
113
+ # @api public
64
114
  attr_reader :exception
115
+
116
+ # Returns the result of the task block.
117
+ #
118
+ # If the task has not finished, returns nil.
119
+ #
120
+ # @return [Object, nil]
121
+ # result of the task block
122
+ #
123
+ # @api public
65
124
  attr_reader :result
66
125
 
126
+ # Sets the callback to be run upon completion of this task. Do not call
127
+ # this method after the task has started.
128
+ #
129
+ # @param callback [Elevate::Callback]
130
+ # completion callback
131
+ #
132
+ # @return [void]
133
+ #
134
+ # @api private
67
135
  def on_finish=(callback)
68
136
  @finish_callback = callback
69
137
  end
70
138
 
139
+ # Sets the callback to be run when this task is queued.
140
+ #
141
+ # Do not call this method after the task has started.
142
+ #
143
+ # @param callback [Elevate::Callback]
144
+ # callback to be invoked when queueing
145
+ #
146
+ # @return [void]
147
+ #
148
+ # @api private
71
149
  def on_start=(callback)
72
150
  start_callback = callback
73
151
  start_callback.retain
@@ -78,8 +156,63 @@ module Elevate
78
156
  end
79
157
  end
80
158
 
159
+ # Handles timeout expiration.
160
+ #
161
+ # @return [void]
162
+ #
163
+ # @api private
164
+ def on_timeout_elapsed(timer)
165
+ @coordinator.cancel(TimeoutError)
166
+ end
167
+
168
+ # Sets the timeout callback.
169
+ #
170
+ # @param callback [Elevate::Callback]
171
+ # callback to run on timeout
172
+ #
173
+ # @return [void]
174
+ #
175
+ # @api private
176
+ def on_timeout=(callback)
177
+ @timeout_callback = callback
178
+ end
179
+
180
+ # Sets the update callback, which is invoked for any yield statements in the task.
181
+ #
182
+ # @param callback [Elevate::Callback]
183
+ # @return [void]
184
+ #
185
+ # @api private
81
186
  def on_update=(callback)
82
187
  @update_callback = callback
83
188
  end
189
+
190
+ # Sets the timeout interval for this task.
191
+ #
192
+ # The timeout starts when the task is queued, not when it is started.
193
+ #
194
+ # @param interval [Fixnum]
195
+ # seconds to allow for task completion
196
+ #
197
+ # @return [void]
198
+ #
199
+ # @api private
200
+ def timeout=(interval)
201
+ @timer = NSTimer.scheduledTimerWithTimeInterval(interval,
202
+ target: self,
203
+ selector: :"on_timeout_elapsed:",
204
+ userInfo: nil,
205
+ repeats: false)
206
+ end
207
+
208
+ # Returns whether this task timed out.
209
+ #
210
+ # @return [Boolean]
211
+ # true if this task was aborted due to a time out.
212
+ #
213
+ # @api public
214
+ def timed_out?
215
+ @exception.class == TimeoutError
216
+ end
84
217
  end
85
218
  end