elevate 0.5.0 → 0.6.0

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