bubble-wrap 1.1.2 → 1.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +1 -1
- data/README.md +32 -9
- data/Rakefile +1 -5
- data/lib/bubble-wrap/all.rb +1 -1
- data/lib/bubble-wrap/http.rb +1 -0
- data/lib/bubble-wrap/media.rb +8 -0
- data/lib/bubble-wrap/version.rb +1 -1
- data/motion/core/app.rb +5 -0
- data/motion/core/device.rb +1 -1
- data/motion/core/json.rb +1 -0
- data/motion/core/ns_url_request.rb +4 -2
- data/motion/core/persistence.rb +7 -1
- data/motion/core/string.rb +2 -2
- data/motion/http.rb +81 -65
- data/motion/location/location.rb +2 -2
- data/motion/media/media.rb +15 -0
- data/motion/media/player.rb +139 -0
- data/motion/test_suite_delegate.rb +1 -0
- data/resources/test.mp3 +0 -0
- data/samples/camera/Gemfile +3 -0
- data/samples/camera/README.md +4 -0
- data/samples/camera/Rakefile +9 -0
- data/samples/camera/app/app_delegate.rb +8 -0
- data/samples/camera/app/controllers/camera_controller.rb +61 -0
- data/samples/camera/spec/main_spec.rb +9 -0
- data/samples/location/.gitignore +5 -0
- data/samples/location/Gemfile +3 -0
- data/samples/location/README.md +4 -0
- data/samples/location/Rakefile +10 -0
- data/samples/location/app/app_delegate.rb +8 -0
- data/samples/location/app/controllers/image_list_controller.rb +30 -0
- data/samples/location/app/models/places.rb +15 -0
- data/samples/location/spec/main_spec.rb +9 -0
- data/spec/motion/core/app_spec.rb +7 -1
- data/spec/motion/core/json_spec.rb +4 -0
- data/spec/motion/core/persistence_spec.rb +7 -3
- data/spec/motion/core/string_spec.rb +24 -0
- data/spec/motion/http_spec.rb +73 -61
- data/spec/motion/media/player_spec.rb +77 -0
- metadata +57 -14
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -65,6 +65,12 @@ If you wish to only include the `Location` wrapper:
|
|
65
65
|
require 'bubble-wrap/location'
|
66
66
|
```
|
67
67
|
|
68
|
+
If you wish to only include the `Media` wrapper:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
require 'bubble-wrap/media'
|
72
|
+
```
|
73
|
+
|
68
74
|
If you want to include everything (ie kitchen sink mode) you can save time and do:
|
69
75
|
|
70
76
|
```ruby
|
@@ -162,6 +168,7 @@ Other available methods:
|
|
162
168
|
* `App.states`
|
163
169
|
* `App.frame`
|
164
170
|
* `App.delegate`
|
171
|
+
* `App.shared`
|
165
172
|
* `App.current_locale`
|
166
173
|
|
167
174
|
|
@@ -243,7 +250,7 @@ def viewWillAppear(animated)
|
|
243
250
|
@foreground_observer = App.notification_center.observe UIApplicationWillEnterForegroundNotification do |notification|
|
244
251
|
loadAndRefresh
|
245
252
|
end
|
246
|
-
|
253
|
+
|
247
254
|
@reload_observer = App.notification_center.observe ReloadNotification do |notification|
|
248
255
|
loadAndRefresh
|
249
256
|
end
|
@@ -290,7 +297,7 @@ class ExampleViewController < UIViewController
|
|
290
297
|
@label = UILabel.alloc.initWithFrame [[20,20],[280,44]]
|
291
298
|
@label.text = ""
|
292
299
|
view.addSubview @label
|
293
|
-
|
300
|
+
|
294
301
|
observe(@label, :text) do |old_value, new_value|
|
295
302
|
puts "Hello from viewDidLoad!"
|
296
303
|
end
|
@@ -341,6 +348,22 @@ end
|
|
341
348
|
|
342
349
|
Also available is `BW::Location.get_significant`, for monitoring significant location changes.
|
343
350
|
|
351
|
+
## Media
|
352
|
+
|
353
|
+
Added wrapper for playing remote and local media. Available are `modal` and custom presentation styles:
|
354
|
+
|
355
|
+
```ruby
|
356
|
+
# Plays in your custom frame
|
357
|
+
local_file = NSURL.fileURLWithPath(File.join(NSBundle.mainBundle.resourcePath, 'test.mp3'))
|
358
|
+
BW::Media.play(local_file) do |media_player|
|
359
|
+
media_player.view.frame = [[10, 100], [100, 100]]
|
360
|
+
self.view.addSubview media_player.view
|
361
|
+
end
|
362
|
+
|
363
|
+
# Plays in an independent modal controller
|
364
|
+
BW::Media.play_modal("http://www.hrupin.com/wp-content/uploads/mp3/testsong_20_sec.mp3")
|
365
|
+
```
|
366
|
+
|
344
367
|
## UI
|
345
368
|
|
346
369
|
### Gestures
|
@@ -498,7 +521,7 @@ feed_parser = BW::RSSParser.new(feed, true)
|
|
498
521
|
**Since: > version 1.0.0**
|
499
522
|
|
500
523
|
`BubbleWrap::Reactor` is a simplified, mostly complete implementation of
|
501
|
-
the [Event Machine](http://rubyeventmachine.com/) API. In fact
|
524
|
+
the [Event Machine](http://rubyeventmachine.com/) API. In fact
|
502
525
|
`BubbleWrap::Reactor` is aliased to `EM` in the runtime environment.
|
503
526
|
|
504
527
|
### Deferables
|
@@ -588,11 +611,11 @@ Great scott!
|
|
588
611
|
|
589
612
|
### Scheduling operations
|
590
613
|
|
591
|
-
You can use `EM.schedule` to schedule blocks to be executed
|
614
|
+
You can use `EM.schedule` to schedule blocks to be executed
|
592
615
|
asynchronously. BubbleWrap deviates from the EventMachine
|
593
616
|
API here in that it also provides `EM.schedule_on_main` which
|
594
|
-
makes sure that the task is run asynchronously, but on the
|
595
|
-
application's main thread - this is necessary if you are
|
617
|
+
makes sure that the task is run asynchronously, but on the
|
618
|
+
application's main thread - this is necessary if you are
|
596
619
|
updating the user interface.
|
597
620
|
|
598
621
|
```ruby
|
@@ -607,15 +630,15 @@ updating the user interface.
|
|
607
630
|
### Deferrable operations
|
608
631
|
|
609
632
|
You can also use `EM.defer` in much the same way as `EM.schedule`
|
610
|
-
with one important difference, you can pass in a second `proc`
|
633
|
+
with one important difference, you can pass in a second `proc`
|
611
634
|
which will be called when the first has completed, and be passed
|
612
635
|
it's result as an argument. Just like `EM.schedule`, `EM.defer`
|
613
636
|
also has an `EM.defer_on_main` version.
|
614
637
|
|
615
638
|
```ruby
|
616
|
-
> operation = proc {
|
639
|
+
> operation = proc { 88 }
|
617
640
|
=> #<Proc:0x6d763c0>
|
618
|
-
> callback = proc { |speed| puts speed >=
|
641
|
+
> callback = proc { |speed| puts speed >= 88 ? "Time travel!" : "Conventional travel!" }
|
619
642
|
=> #<Proc:0x8bd3910>
|
620
643
|
> EM.defer(operation, callback)
|
621
644
|
=> nil
|
data/Rakefile
CHANGED
@@ -10,11 +10,7 @@ require 'bubble-wrap/test'
|
|
10
10
|
Motion::Project::App.setup do |app|
|
11
11
|
app.name = 'testSuite'
|
12
12
|
app.identifier = 'io.bubblewrap.testSuite'
|
13
|
-
app.specs_dir = './spec/motion
|
14
|
-
# Hold your breath, we're going API spelunking!
|
15
|
-
spec_files = app.spec_files + Dir.glob(File.join(app.specs_dir, '**/*.rb'))
|
16
|
-
spec_files.uniq!
|
17
|
-
app.instance_variable_set(:@spec_files, spec_files)
|
13
|
+
app.specs_dir = './spec/motion'
|
18
14
|
end
|
19
15
|
|
20
16
|
namespace :spec do
|
data/lib/bubble-wrap/all.rb
CHANGED
data/lib/bubble-wrap/http.rb
CHANGED
@@ -0,0 +1,8 @@
|
|
1
|
+
require 'bubble-wrap/loader'
|
2
|
+
BubbleWrap.require('motion/core/string.rb')
|
3
|
+
BubbleWrap.require('motion/core/ns_notification_center.rb')
|
4
|
+
BubbleWrap.require('motion/media/**/*.rb') do
|
5
|
+
file('motion/media/media.rb').depends_on('motion/media/player.rb')
|
6
|
+
file('motion/media/player.rb').depends_on 'motion/core/string.rb'
|
7
|
+
file('motion/media/player.rb').uses_framework('MediaPlayer')
|
8
|
+
end
|
data/lib/bubble-wrap/version.rb
CHANGED
data/motion/core/app.rb
CHANGED
@@ -105,6 +105,11 @@ module BubbleWrap
|
|
105
105
|
UIApplication.sharedApplication.delegate
|
106
106
|
end
|
107
107
|
|
108
|
+
# the Application object.
|
109
|
+
def shared
|
110
|
+
UIApplication.sharedApplication
|
111
|
+
end
|
112
|
+
|
108
113
|
# @return [NSLocale] locale of user settings
|
109
114
|
def current_locale
|
110
115
|
languages = NSLocale.preferredLanguages
|
data/motion/core/device.rb
CHANGED
@@ -23,7 +23,7 @@ module BubbleWrap
|
|
23
23
|
|
24
24
|
# Verifies that the device running has a front facing camera.
|
25
25
|
# @return [TrueClass, FalseClass] true will be returned if the device has a front facing camera, false otherwise.
|
26
|
-
def front_camera?
|
26
|
+
def front_camera?(picker=UIImagePickerController)
|
27
27
|
p "This method (front_camera?) is DEPRECATED. Transition to using Device.camera.front?"
|
28
28
|
picker.isCameraDeviceAvailable(UIImagePickerControllerCameraDeviceFront)
|
29
29
|
end
|
data/motion/core/json.rb
CHANGED
@@ -13,6 +13,7 @@ module BubbleWrap
|
|
13
13
|
#
|
14
14
|
# TODO: support options like the C Ruby module does
|
15
15
|
def self.parse(str_data, &block)
|
16
|
+
return nil unless str_data
|
16
17
|
data = str_data.respond_to?(:to_data) ? str_data.to_data : str_data
|
17
18
|
opts = NSJSONReadingMutableContainers & NSJSONReadingMutableLeaves & NSJSONReadingAllowFragments
|
18
19
|
error = Pointer.new(:id)
|
@@ -3,8 +3,10 @@ class NSURLRequest
|
|
3
3
|
# Provides a to_s method so we can use inspect in instances and get
|
4
4
|
# valuable information.
|
5
5
|
def to_s
|
6
|
-
"#<#{self.class}:#{self.object_id} - url: #{self.URL.description},
|
7
|
-
|
6
|
+
"#<#{self.class}:#{self.object_id} - url: #{self.URL.description},
|
7
|
+
headers: #{self.allHTTPHeaderFields.inspect},
|
8
|
+
cache policy: #{self.cachePolicy}, Pipelining: #{self.HTTPShouldUsePipelining}, main doc url: #{mainDocumentURL},\
|
9
|
+
timeout: #{self.timeoutInterval}, network service type: #{self.networkServiceType} >"
|
8
10
|
end
|
9
11
|
|
10
12
|
end
|
data/motion/core/persistence.rb
CHANGED
@@ -13,7 +13,13 @@ module BubbleWrap
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def [](key)
|
16
|
-
storage.objectForKey storage_key(key)
|
16
|
+
value = storage.objectForKey storage_key(key)
|
17
|
+
|
18
|
+
# RubyMotion currently has a bug where the strings returned from
|
19
|
+
# standardUserDefaults are missing some methods (e.g. to_data).
|
20
|
+
# And because the returned object is slightly different than a normal
|
21
|
+
# String, we can't just use `value.is_a?(String)`
|
22
|
+
value.class.to_s == 'String' ? value.dup : value
|
17
23
|
end
|
18
24
|
|
19
25
|
def merge(values)
|
data/motion/core/string.rb
CHANGED
@@ -13,7 +13,7 @@ module BubbleWrap
|
|
13
13
|
new_word = "/#{new_word}" if $1 == '/'
|
14
14
|
new_word
|
15
15
|
end
|
16
|
-
if uppercase_first_letter
|
16
|
+
if uppercase_first_letter && uppercase_first_letter != :lower
|
17
17
|
string[0] = string[0].upcase
|
18
18
|
else
|
19
19
|
string[0] = string[0].downcase
|
@@ -35,7 +35,7 @@ module BubbleWrap
|
|
35
35
|
|
36
36
|
def to_color
|
37
37
|
# First check if it is a color keyword
|
38
|
-
keyword_selector = "#{self.camelize(
|
38
|
+
keyword_selector = "#{self.camelize(:lower)}Color"
|
39
39
|
return UIColor.send(keyword_selector) if UIColor.respond_to? keyword_selector
|
40
40
|
|
41
41
|
# Next attempt to convert from hex
|
data/motion/http.rb
CHANGED
@@ -19,38 +19,37 @@ module BubbleWrap
|
|
19
19
|
# end
|
20
20
|
#
|
21
21
|
def self.get(url, options={}, &block)
|
22
|
-
|
23
|
-
HTTP::Query.new(url, :get, options)
|
22
|
+
create_query(url, :get, options, block)
|
24
23
|
end
|
25
24
|
|
26
25
|
# Make a POST request
|
27
26
|
def self.post(url, options={}, &block)
|
28
|
-
|
29
|
-
HTTP::Query.new(url, :post, options)
|
27
|
+
create_query(url, :post, options, block)
|
30
28
|
end
|
31
29
|
|
32
30
|
# Make a PUT request
|
33
31
|
def self.put(url, options={}, &block)
|
34
|
-
|
35
|
-
HTTP::Query.new(url, :put, options)
|
32
|
+
create_query(url, :put, options, block)
|
36
33
|
end
|
37
34
|
|
38
35
|
# Make a DELETE request
|
39
36
|
def self.delete(url, options={}, &block)
|
40
|
-
|
41
|
-
HTTP::Query.new(url, :delete, options)
|
37
|
+
create_query(url, :delete, options, block)
|
42
38
|
end
|
43
39
|
|
44
40
|
# Make a HEAD request
|
45
41
|
def self.head(url, options={}, &block)
|
46
|
-
|
47
|
-
HTTP::Query.new(url, :head, options)
|
42
|
+
create_query(url, :head, options, block)
|
48
43
|
end
|
49
44
|
|
50
45
|
# Make a PATCH request
|
51
46
|
def self.patch(url, options={}, &block)
|
52
|
-
|
53
|
-
|
47
|
+
create_query(url, :patch, options, block)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.create_query(url, method, options, block)
|
51
|
+
options[:action] = block if block
|
52
|
+
HTTP::Query.new(url, method, options)
|
54
53
|
end
|
55
54
|
|
56
55
|
# Response class wrapping the results of a Query's response
|
@@ -95,7 +94,7 @@ module BubbleWrap
|
|
95
94
|
attr_reader :response_headers
|
96
95
|
attr_reader :response_size
|
97
96
|
attr_reader :options
|
98
|
-
|
97
|
+
CLRF = "\r\n"
|
99
98
|
# ==== Parameters
|
100
99
|
# url<String>:: url of the resource to download
|
101
100
|
# http_method<Symbol>:: Value representing the HTTP method to use
|
@@ -127,7 +126,7 @@ module BubbleWrap
|
|
127
126
|
@url = create_url(url_string)
|
128
127
|
@body = create_request_body
|
129
128
|
@request = create_request
|
130
|
-
|
129
|
+
|
131
130
|
@connection = create_connection(request, self)
|
132
131
|
@connection.start
|
133
132
|
|
@@ -157,15 +156,18 @@ Cache policy: #{@cache_policy}, response: #{@response.inspect} >"
|
|
157
156
|
end
|
158
157
|
|
159
158
|
def connection(connection, willSendRequest:request, redirectResponse:redirect_response)
|
160
|
-
@
|
161
|
-
@
|
162
|
-
log "##{@
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
159
|
+
@redirect_count ||= 0
|
160
|
+
@redirect_count += 1
|
161
|
+
log "##{@redirect_count} HTTP redirect_count: #{request.inspect} - #{self.description}"
|
162
|
+
|
163
|
+
if @redirect_count >= 30
|
164
|
+
@response.error_message = "Too many redirections"
|
165
|
+
@request.done_loading!
|
166
|
+
call_delegator_with_response
|
167
|
+
nil
|
168
|
+
else
|
169
|
+
request
|
170
|
+
end
|
169
171
|
end
|
170
172
|
|
171
173
|
def connection(connection, didFailWithError: error)
|
@@ -212,6 +214,7 @@ Cache policy: #{@cache_policy}, response: #{@response.inspect} >"
|
|
212
214
|
cachePolicy:@cache_policy,
|
213
215
|
timeoutInterval:@timeout)
|
214
216
|
request.setHTTPMethod(@method)
|
217
|
+
set_content_type
|
215
218
|
request.setAllHTTPHeaderFields(@headers)
|
216
219
|
request.setHTTPBody(@body)
|
217
220
|
patch_nsurl_request(request)
|
@@ -219,6 +222,30 @@ Cache policy: #{@cache_policy}, response: #{@response.inspect} >"
|
|
219
222
|
request
|
220
223
|
end
|
221
224
|
|
225
|
+
def set_content_type
|
226
|
+
return if headers_provided?
|
227
|
+
return if (@method == "GET" || @method == "HEAD")
|
228
|
+
@headers ||= {}
|
229
|
+
@headers["Content-Type"] = case @format
|
230
|
+
when :json
|
231
|
+
"application/json"
|
232
|
+
when :xml
|
233
|
+
"application/xml"
|
234
|
+
when :text
|
235
|
+
"text/plain"
|
236
|
+
else
|
237
|
+
if @format == :form_data || @payload_or_files_were_appended
|
238
|
+
"multipart/form-data; boundary=#{@boundary}"
|
239
|
+
else
|
240
|
+
"application/x-www-form-urlencoded"
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def headers_provided?
|
246
|
+
@headers && @headers.keys.find {|k| k.downcase == 'content-type'}
|
247
|
+
end
|
248
|
+
|
222
249
|
def create_request_body
|
223
250
|
return nil if (@method == "GET" || @method == "HEAD")
|
224
251
|
return nil unless (@payload || @files)
|
@@ -227,36 +254,17 @@ Cache policy: #{@cache_policy}, response: #{@response.inspect} >"
|
|
227
254
|
|
228
255
|
append_payload(body) if @payload
|
229
256
|
append_files(body) if @files
|
230
|
-
append_body_boundary(body) if @
|
257
|
+
append_body_boundary(body) if @payload_or_files_were_appended
|
231
258
|
|
232
259
|
log "Built HTTP body: \n #{body.to_str}"
|
233
260
|
body
|
234
261
|
end
|
235
262
|
|
236
|
-
def set_content_type
|
237
|
-
# if no headers provided, set content-type automatically
|
238
|
-
if @headers.nil? || !@headers.keys.find {|k| k.downcase == 'content-type'}
|
239
|
-
@headers ||= {}
|
240
|
-
@headers["Content-Type"] = case @format
|
241
|
-
when :json
|
242
|
-
"application/json"
|
243
|
-
when :xml
|
244
|
-
"application/xml"
|
245
|
-
when :text
|
246
|
-
"text/plain"
|
247
|
-
else
|
248
|
-
if @format == :form_data || @set_body_to_close_boundary
|
249
|
-
"multipart/form-data; boundary=#{@boundary}"
|
250
|
-
else
|
251
|
-
"application/x-www-form-urlencoded"
|
252
|
-
end
|
253
|
-
end
|
254
|
-
end
|
255
|
-
end
|
256
|
-
|
257
263
|
def append_payload(body)
|
258
264
|
if @payload.is_a?(NSData)
|
259
265
|
body.appendData(@payload)
|
266
|
+
elsif @payload.is_a?(String)
|
267
|
+
body.appendData(@payload.dataUsingEncoding NSUTF8StringEncoding)
|
260
268
|
else
|
261
269
|
append_form_params(body)
|
262
270
|
end
|
@@ -264,40 +272,37 @@ Cache policy: #{@cache_policy}, response: #{@response.inspect} >"
|
|
264
272
|
end
|
265
273
|
|
266
274
|
def append_form_params(body)
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
s += value.to_s
|
277
|
-
form_data.appendData(s.dataUsingEncoding NSUTF8StringEncoding)
|
278
|
-
body.appendData(form_data)
|
279
|
-
end
|
280
|
-
@set_body_to_close_boundary = true
|
275
|
+
list = process_payload_hash(@payload)
|
276
|
+
list.each do |key, value|
|
277
|
+
form_data = NSMutableData.new
|
278
|
+
s = "--#{@boundary}\r\n"
|
279
|
+
s += "Content-Disposition: form-data; name=\"#{key}\"\r\n\r\n"
|
280
|
+
s += value.to_s
|
281
|
+
s += "\r\n"
|
282
|
+
form_data.appendData(s.dataUsingEncoding NSUTF8StringEncoding)
|
283
|
+
body.appendData(form_data)
|
281
284
|
end
|
285
|
+
@payload_or_files_were_appended = true
|
282
286
|
body
|
283
287
|
end
|
284
288
|
|
285
289
|
def append_files(body)
|
286
290
|
@files.each do |key, value|
|
287
291
|
file_data = NSMutableData.new
|
288
|
-
s = "
|
292
|
+
s = "--#{@boundary}\r\n"
|
289
293
|
s += "Content-Disposition: form-data; name=\"#{key}\"; filename=\"#{key}\"\r\n"
|
290
294
|
s += "Content-Type: application/octet-stream\r\n\r\n"
|
291
295
|
file_data.appendData(s.dataUsingEncoding NSUTF8StringEncoding)
|
292
296
|
file_data.appendData(value)
|
297
|
+
file_data.appendData("\r\n".dataUsingEncoding NSUTF8StringEncoding)
|
293
298
|
body.appendData(file_data)
|
294
299
|
end
|
295
|
-
@
|
300
|
+
@payload_or_files_were_appended = true
|
296
301
|
body
|
297
302
|
end
|
298
303
|
|
299
304
|
def append_body_boundary(body)
|
300
|
-
body.appendData("
|
305
|
+
body.appendData("--#{@boundary}--\r\n".dataUsingEncoding NSUTF8StringEncoding)
|
301
306
|
end
|
302
307
|
|
303
308
|
def create_url(url_string)
|
@@ -305,7 +310,16 @@ Cache policy: #{@cache_policy}, response: #{@response.inspect} >"
|
|
305
310
|
convert_payload_to_url if @payload.is_a?(Hash)
|
306
311
|
url_string += "?#{@payload}"
|
307
312
|
end
|
308
|
-
NSURL.URLWithString(url_string.stringByAddingPercentEscapesUsingEncoding NSUTF8StringEncoding)
|
313
|
+
url = NSURL.URLWithString(url_string.stringByAddingPercentEscapesUsingEncoding NSUTF8StringEncoding)
|
314
|
+
|
315
|
+
validate_url(url)
|
316
|
+
url
|
317
|
+
end
|
318
|
+
|
319
|
+
def validate_url(url)
|
320
|
+
if !NSURLConnection.canHandleRequest(NSURLRequest.requestWithURL(url))
|
321
|
+
raise InvalidURLError, "Invalid URL provided (Make sure you include a valid URL scheme, e.g. http:// or similar)."
|
322
|
+
end
|
309
323
|
end
|
310
324
|
|
311
325
|
def convert_payload_to_url
|
@@ -342,14 +356,14 @@ Cache policy: #{@cache_policy}, response: #{@response.inspect} >"
|
|
342
356
|
return nil if hash.nil?
|
343
357
|
escaped_hash = {}
|
344
358
|
|
345
|
-
hash.each{|k,v| escaped_hash[k] = v.gsub("\n",
|
359
|
+
hash.each{|k,v| escaped_hash[k] = v.gsub("\n", CLRF) }
|
346
360
|
escaped_hash
|
347
361
|
end
|
348
362
|
|
349
363
|
def patch_nsurl_request(request)
|
350
364
|
request.instance_variable_set("@done_loading", false)
|
351
365
|
|
352
|
-
def request.done_loading
|
366
|
+
def request.done_loading?; @done_loading; end
|
353
367
|
def request.done_loading!; @done_loading = true; end
|
354
368
|
end
|
355
369
|
|
@@ -367,3 +381,5 @@ Cache policy: #{@cache_policy}, response: #{@response.inspect} >"
|
|
367
381
|
end
|
368
382
|
end
|
369
383
|
end
|
384
|
+
|
385
|
+
class InvalidURLError < StandardError; end
|