elevate 0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,100 @@
1
+ module Elevate
2
+ module HTTP
3
+ class HTTPClient
4
+ def initialize(base_url)
5
+ @base_url = NSURL.URLWithString(base_url)
6
+ @credentials = nil
7
+ end
8
+
9
+ def get(path, query={}, &block)
10
+ issue(:GET, path, nil, query: query, &block)
11
+ end
12
+
13
+ def post(path, body, &block)
14
+ issue(:post, path, body, &block)
15
+ end
16
+
17
+ def put(path, body, &block)
18
+ issue(:put, path, body, &block)
19
+ end
20
+
21
+ def delete(path, &block)
22
+ issue(:delete, path, nil, &block)
23
+ end
24
+
25
+ def set_credentials(username, password)
26
+ @credentials = { username: username, password: password }
27
+ end
28
+
29
+ private
30
+
31
+ def issue(method, path, body, options={}, &block)
32
+ url = url_for(path)
33
+
34
+ options[:headers] ||= {}
35
+ options[:headers]["Accept"] = "application/json"
36
+
37
+ if @credentials
38
+ options[:credentials] = @credentials
39
+ end
40
+
41
+ if body
42
+ options[:body] = NSJSONSerialization.dataWithJSONObject(body, options:0, error:nil)
43
+ options[:headers]["Content-Type"] = "application/json"
44
+ end
45
+
46
+ request = HTTPRequest.new(method, url, options)
47
+
48
+ coordinator = IOCoordinator.for_thread
49
+
50
+ coordinator.signal_blocked(request) if coordinator
51
+ response = JSONHTTPResponse.new(request.response)
52
+ coordinator.signal_unblocked(request) if coordinator
53
+
54
+ if response.error == nil && block_given?
55
+ result = yield response.body
56
+ puts result.inspect
57
+
58
+ result
59
+ else
60
+ response
61
+ end
62
+ end
63
+
64
+ def url_for(path)
65
+ path = CFURLCreateStringByAddingPercentEscapes(nil, path.to_s, "[]", ";=&,", KCFStringEncodingUTF8)
66
+
67
+ NSURL.URLWithString(path, relativeToURL:@base_url).absoluteString
68
+ end
69
+ end
70
+
71
+ class JSONHTTPResponse
72
+ def initialize(response)
73
+ @response = response
74
+ @body = decode(response.body)
75
+ end
76
+
77
+ def decode(data)
78
+ return nil if data.nil?
79
+
80
+ NSJSONSerialization.JSONObjectWithData(data, options:0, error:nil)
81
+ end
82
+
83
+ attr_reader :body
84
+
85
+ # TODO: delegate
86
+ def error
87
+ @response.error
88
+ end
89
+
90
+ def headers
91
+ @response.headers
92
+ end
93
+
94
+ def status_code
95
+ @response.status_code
96
+ end
97
+
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,95 @@
1
+ module Elevate
2
+ module HTTP
3
+ # TODO: redirects
4
+ class HTTPRequest
5
+ METHODS = [:get, :post, :put, :delete, :patch, :head, :options].freeze
6
+ QUEUE = NSOperationQueue.alloc.init
7
+
8
+ def initialize(method, url, options={})
9
+ raise ArgumentError, "invalid HTTP method" unless METHODS.include? method.downcase
10
+ raise ArgumentError, "invalid URL" unless url.start_with? "http"
11
+ raise ArgumentError, "invalid body type; must be NSData" if options[:body] && ! options[:body].is_a?(NSData)
12
+
13
+ unless options.fetch(:query, {}).empty?
14
+ url += "?" + URI.encode_query(options[:query])
15
+ end
16
+
17
+ @request = NSMutableURLRequest.alloc.init
18
+ @request.CachePolicy = NSURLRequestReloadIgnoringLocalCacheData
19
+ @request.HTTPBody = options[:body]
20
+ @request.HTTPMethod = method
21
+ @request.URL = NSURL.URLWithString(url)
22
+
23
+ headers = options.fetch(:headers, {})
24
+
25
+ if credentials = options[:credentials]
26
+ headers["Authorization"] = get_authorization_header(credentials)
27
+ end
28
+
29
+ headers.each do |key, value|
30
+ @request.setValue(value.to_s, forHTTPHeaderField:key.to_s)
31
+ end
32
+
33
+ @response = HTTPResponse.new
34
+
35
+ @connection = nil
36
+ @promise = Promise.new
37
+ end
38
+
39
+ def cancel
40
+ return unless started?
41
+
42
+ @connection.cancel()
43
+ @promise.set(nil)
44
+ end
45
+
46
+ def response
47
+ unless started?
48
+ start()
49
+ end
50
+
51
+ @promise.get()
52
+ end
53
+
54
+ def start
55
+ @connection = NSURLConnection.alloc.initWithRequest(@request, delegate:self, startImmediately:false)
56
+ @connection.setDelegateQueue(QUEUE)
57
+ @connection.start()
58
+ end
59
+
60
+ def started?
61
+ @connection != nil
62
+ end
63
+
64
+ private
65
+
66
+ def connection(connection, didReceiveResponse: response)
67
+ @response.headers = response.allHeaderFields
68
+ @response.status_code = response.statusCode
69
+ end
70
+
71
+ def connection(connection, didReceiveData: data)
72
+ @response.append_data(data)
73
+ end
74
+
75
+ def connection(connection, didFailWithError: error)
76
+ puts "ERROR: #{error.localizedDescription}"
77
+
78
+ @response.error = error
79
+ @response.freeze
80
+
81
+ @promise.set(@response)
82
+ end
83
+
84
+ def connectionDidFinishLoading(connection)
85
+ @response.freeze
86
+
87
+ @promise.set(@response)
88
+ end
89
+
90
+ def get_authorization_header(credentials)
91
+ "Basic " + Base64.encode("#{credentials[:username]}:#{credentials[:password]}")
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,22 @@
1
+ module Elevate
2
+ module HTTP
3
+ class HTTPResponse
4
+ def initialize
5
+ @body = nil
6
+ @headers = nil
7
+ @status_code = nil
8
+ @error = nil
9
+ end
10
+
11
+ def append_data(data)
12
+ @body ||= NSMutableData.alloc.init
13
+ @body.appendData(data)
14
+ end
15
+
16
+ attr_reader :body
17
+ attr_accessor :headers
18
+ attr_accessor :status_code
19
+ attr_accessor :error
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ module Elevate
2
+ module HTTP
3
+ module URI
4
+ def self.encode_query(hash)
5
+ return "" if hash.nil? || hash.empty?
6
+
7
+ hash.map do |key, value|
8
+ "#{URI.escape_query_component(key.to_s)}=#{URI.escape_query_component(value.to_s)}"
9
+ end.join("&")
10
+ end
11
+
12
+ def self.escape_query_component(component)
13
+ component.gsub(/([^ a-zA-Z0-9_.-]+)/) do
14
+ '%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
15
+ end.tr(' ', '+')
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,69 @@
1
+ module Elevate
2
+ class IOCoordinator
3
+ def self.for_thread
4
+ Thread.current[:io_coordinator]
5
+ end
6
+
7
+ def initialize
8
+ @lock = NSLock.alloc.init
9
+ @blocking_operation = nil
10
+ @cancelled = false
11
+ end
12
+
13
+ def cancel
14
+ blocking_operation = nil
15
+
16
+ @lock.lock()
17
+ @cancelled = true
18
+ blocking_operation = @blocking_operation
19
+ @lock.unlock()
20
+
21
+ if blocking_operation
22
+ blocking_operation.cancel()
23
+ end
24
+ end
25
+
26
+ def cancelled?
27
+ cancelled = nil
28
+
29
+ @lock.lock()
30
+ cancelled = @cancelled
31
+ @lock.unlock()
32
+
33
+ cancelled
34
+ end
35
+
36
+ def install
37
+ Thread.current[:io_coordinator] = self
38
+ end
39
+
40
+ def signal_blocked(operation)
41
+ check_for_cancellation
42
+
43
+ @lock.lock()
44
+ @blocking_operation = operation
45
+ @lock.unlock()
46
+ end
47
+
48
+ def signal_unblocked(operation)
49
+ @lock.lock()
50
+ @blocking_operation = nil
51
+ @lock.unlock()
52
+
53
+ check_for_cancellation
54
+ end
55
+
56
+ def uninstall
57
+ Thread.current[:io_coordinator] = nil
58
+ end
59
+
60
+ private
61
+
62
+ def check_for_cancellation
63
+ raise CancelledError if cancelled?
64
+ end
65
+ end
66
+
67
+ class CancelledError < StandardError
68
+ end
69
+ end
@@ -0,0 +1,74 @@
1
+ module Elevate
2
+ class ElevateOperation < NSOperation
3
+ def initWithTarget(target)
4
+ if init()
5
+ @target = target
6
+ @coordinator = IOCoordinator.new
7
+ @dispatcher = Dispatcher.new
8
+
9
+ setCompletionBlock(lambda do
10
+ @target = nil
11
+
12
+ @dispatcher.invoke_finished_callback() unless isCancelled()
13
+ @dispatcher.dispose()
14
+ end)
15
+ end
16
+
17
+ self
18
+ end
19
+
20
+ def cancel
21
+ @coordinator.cancel()
22
+
23
+ super
24
+ end
25
+
26
+ def dealloc
27
+ #puts 'dealloc!'
28
+
29
+ super
30
+ end
31
+
32
+ def inspect
33
+ details = []
34
+ details << "<canceled>" if @coordinator.cancelled?
35
+ details << "@target=#{@target.class.name}"
36
+
37
+ "#<#{self.class.name}: #{details.join(" ")}>"
38
+ end
39
+
40
+ def log(line)
41
+ puts line unless RUBYMOTION_ENV == "test"
42
+ end
43
+
44
+ def main
45
+ log " START: #{inspect}"
46
+
47
+ @coordinator.install()
48
+
49
+ begin
50
+ unless @coordinator.cancelled?
51
+ @result = @target.execute
52
+ end
53
+
54
+ rescue => e
55
+ @exception = e
56
+ end
57
+
58
+ @coordinator.uninstall()
59
+
60
+ log "FINISH: #{inspect}"
61
+ end
62
+
63
+ attr_reader :exception
64
+ attr_reader :result
65
+
66
+ def on_started=(callback)
67
+ @dispatcher.on_started = callback
68
+ end
69
+
70
+ def on_finished=(callback)
71
+ @dispatcher.on_finished = callback
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,27 @@
1
+ module Elevate
2
+ class Promise
3
+ OUTSTANDING = 0
4
+ FULFILLED = 1
5
+
6
+ def initialize
7
+ @lock = NSConditionLock.alloc.initWithCondition(OUTSTANDING)
8
+ @result = nil
9
+ end
10
+
11
+ def get
12
+ result = nil
13
+
14
+ @lock.lockWhenCondition(FULFILLED)
15
+ result = @result
16
+ @lock.unlockWithCondition(FULFILLED)
17
+
18
+ result
19
+ end
20
+
21
+ def set(result)
22
+ @lock.lockWhenCondition(OUTSTANDING)
23
+ @result = result
24
+ @lock.unlockWithCondition(FULFILLED)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,3 @@
1
+ module Elevate
2
+ VERSION = "0.3"
3
+ end
@@ -0,0 +1,23 @@
1
+ module Bacon
2
+ class Context
3
+ include ::Elevate
4
+ end
5
+ end
6
+
7
+ describe Elevate do
8
+ describe "#async" do
9
+ it "runs the specified interactor asynchronously" do
10
+
11
+ async Target.new() do
12
+ on_completed do |operation|
13
+ @called = true
14
+ resume
15
+ end
16
+ end
17
+
18
+ wait_max 1.0 do
19
+ @called.should.be.true
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,12 @@
1
+ describe Elevate::Callback do
2
+ describe "#call" do
3
+ it "invokes the block using instance_*" do
4
+ callback = Elevate::Callback.new(self, 42, lambda { |v| @value = v })
5
+ callback.call
6
+
7
+ @value.should == 42
8
+ end
9
+
10
+ end
11
+ end
12
+