bubble-wrap 1.2.0 → 1.3.0.osx

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.
Files changed (79) hide show
  1. checksums.yaml +8 -8
  2. data/CHANGELOG.md +4 -2
  3. data/Gemfile.lock +1 -1
  4. data/README.md +217 -7
  5. data/Rakefile +23 -2
  6. data/lib/bubble-wrap/camera.rb +10 -6
  7. data/lib/bubble-wrap/core.rb +14 -1
  8. data/lib/bubble-wrap/ext/motion_project_app.rb +8 -0
  9. data/lib/bubble-wrap/font.rb +3 -1
  10. data/lib/bubble-wrap/http.rb +2 -0
  11. data/lib/bubble-wrap/loader.rb +17 -2
  12. data/lib/bubble-wrap/location.rb +9 -6
  13. data/lib/bubble-wrap/media.rb +10 -6
  14. data/lib/bubble-wrap/test.rb +6 -1
  15. data/lib/bubble-wrap/ui.rb +5 -2
  16. data/lib/bubble-wrap/version.rb +2 -7
  17. data/motion/core.rb +6 -1
  18. data/motion/core/app.rb +3 -64
  19. data/motion/core/device.rb +0 -55
  20. data/motion/core/device/{camera.rb → ios/camera.rb} +0 -0
  21. data/motion/core/device/{camera_wrapper.rb → ios/camera_wrapper.rb} +0 -0
  22. data/motion/core/device/ios/screen.rb +75 -0
  23. data/motion/core/device/osx/screen.rb +18 -0
  24. data/motion/core/device/screen.rb +1 -69
  25. data/motion/core/ios/app.rb +71 -0
  26. data/motion/core/ios/device.rb +59 -0
  27. data/motion/core/osx/app.rb +15 -0
  28. data/motion/core/osx/device.rb +6 -0
  29. data/motion/core/string.rb +3 -2
  30. data/motion/http.rb +0 -364
  31. data/motion/http/query.rb +367 -0
  32. data/motion/http/response.rb +32 -0
  33. data/motion/test_suite_delegate.rb +58 -0
  34. data/motion/ui/ui_alert_view.rb +169 -0
  35. data/motion/ui/ui_bar_button_item.rb +55 -53
  36. data/motion/util/constants.rb +34 -32
  37. data/samples/alert/.gitignore +16 -0
  38. data/samples/alert/Gemfile +3 -0
  39. data/samples/alert/Rakefile +10 -0
  40. data/samples/alert/app/app_delegate.rb +8 -0
  41. data/samples/alert/app/controllers/alert_view_controller.rb +74 -0
  42. data/samples/alert/resources/Default-568h@2x.png +0 -0
  43. data/samples/alert/spec/main_spec.rb +9 -0
  44. data/samples/media/.gitignore +16 -0
  45. data/samples/media/Rakefile +11 -0
  46. data/samples/media/app/app_delegate.rb +8 -0
  47. data/samples/media/app/controllers/play_controller.rb +46 -0
  48. data/samples/media/resources/Default-568h@2x.png +0 -0
  49. data/samples/media/resources/test.mp3 +0 -0
  50. data/samples/media/spec/main_spec.rb +9 -0
  51. data/samples/osx/Gemfile +3 -0
  52. data/samples/osx/Gemfile.lock +10 -0
  53. data/samples/osx/Rakefile +11 -0
  54. data/samples/osx/app/app_delegate.rb +69 -0
  55. data/samples/osx/app/menu.rb +108 -0
  56. data/samples/osx/resources/Credits.rtf +29 -0
  57. data/samples/osx/spec/main_spec.rb +9 -0
  58. data/spec/motion/core/app_spec.rb +5 -164
  59. data/spec/motion/core/device/{camera_spec.rb → ios/camera_spec.rb} +0 -0
  60. data/spec/motion/core/device/{camera_wrapper_spec.rb → ios/camera_wrapper_spec.rb} +0 -0
  61. data/spec/motion/core/device/ios/device_spec.rb +74 -0
  62. data/spec/motion/core/device/{screen_spec.rb → ios/screen_spec.rb} +2 -1
  63. data/spec/motion/core/device/osx/screen_spec.rb +26 -0
  64. data/spec/motion/core/device_spec.rb +0 -71
  65. data/spec/motion/core/ios/app_spec.rb +180 -0
  66. data/spec/motion/core/kvo_spec.rb +23 -7
  67. data/spec/motion/core/ns_index_path_spec.rb +10 -2
  68. data/spec/motion/core/osx/app_spec.rb +15 -0
  69. data/spec/motion/core/string_spec.rb +11 -5
  70. data/spec/motion/core_spec.rb +13 -2
  71. data/spec/motion/http/query_spec.rb +731 -0
  72. data/spec/motion/http/response_spec.rb +44 -0
  73. data/spec/motion/http_spec.rb +0 -722
  74. data/spec/motion/{core → ui}/gestures_spec.rb +0 -0
  75. data/spec/motion/ui/ui_alert_view_spec.rb +1188 -0
  76. data/spec/motion/{core → ui}/ui_bar_button_item_spec.rb +80 -24
  77. data/spec/motion/{core → ui}/ui_control_spec.rb +0 -0
  78. data/spec/motion/util/constants_spec.rb +4 -4
  79. metadata +86 -26
@@ -0,0 +1,367 @@
1
+ # Class wrapping NSConnection and often used indirectly by the BubbleWrap::HTTP module methods.
2
+ class BubbleWrap::HTTP::Query
3
+ attr_accessor :request
4
+ attr_accessor :connection
5
+ attr_accessor :credentials # username & password has a hash
6
+ attr_accessor :proxy_credential # credential supplied to proxy servers
7
+ attr_accessor :post_data
8
+ attr_reader :method
9
+
10
+ attr_reader :response
11
+ attr_reader :status_code
12
+ attr_reader :response_headers
13
+ attr_reader :response_size
14
+ attr_reader :options
15
+ CLRF = "\r\n"
16
+ # ==== Parameters
17
+ # url<String>:: url of the resource to download
18
+ # http_method<Symbol>:: Value representing the HTTP method to use
19
+ # options<Hash>:: optional options used for the query
20
+ #
21
+ # ==== Options
22
+ # :payload<String> - data to pass to a POST, PUT, DELETE query.
23
+ # :delegator - Proc, class or object to call when the file is downloaded.
24
+ # a proc will receive a Response object while the passed object
25
+ # will receive the handle_query_response method
26
+ # :headers<Hash> - headers send with the request
27
+ # :cookies<Boolean> - Set whether cookies should be sent with request or not (Default: true)
28
+ # Anything else will be available via the options attribute reader.
29
+ #
30
+ def initialize(url_string, http_method = :get, options={})
31
+ @method = http_method.upcase.to_s
32
+ @delegator = options.delete(:action) || self
33
+ @payload = options.delete(:payload)
34
+ @files = options.delete(:files)
35
+ @boundary = options.delete(:boundary) || BW.create_uuid
36
+ @credentials = options.delete(:credentials) || {}
37
+ @credentials = {:username => nil, :password => nil}.merge(@credentials)
38
+ @timeout = options.delete(:timeout) || 30.0
39
+ @headers = escape_line_feeds(options.delete :headers)
40
+ @format = options.delete(:format)
41
+ @cache_policy = options.delete(:cache_policy) || NSURLRequestUseProtocolCachePolicy
42
+ @credential_persistence = options.delete(:credential_persistence) || NSURLCredentialPersistenceForSession
43
+ @cookies = options.key?(:cookies) ? options.delete(:cookies) : true
44
+ @options = options
45
+ @response = BubbleWrap::HTTP::Response.new
46
+ @follow_urls = options[:follow_urls] || true
47
+ @present_credentials = options[:present_credentials] == nil ? true : options.delete(:present_credentials)
48
+
49
+ @url = create_url(url_string)
50
+ @body = create_request_body
51
+ @request = create_request
52
+ @original_url = @url.copy
53
+
54
+ @connection = create_connection(request, self)
55
+ @connection.start
56
+
57
+ show_status_indicator true
58
+ end
59
+
60
+ def to_s
61
+ "#<#{self.class}:#{self.object_id} - Method: #{@method}, url: #{@url.description}, body: #{@body.description}, Payload: #{@payload}, Headers: #{@headers} Credentials: #{@credentials}, Timeout: #{@timeout}, \
62
+ Cache policy: #{@cache_policy}, response: #{@response.inspect} >"
63
+ end
64
+ alias description to_s
65
+
66
+ def connection(connection, didReceiveResponse:response)
67
+ # On OSX, if using an FTP connection, this method will fire *immediately* after creating an
68
+ # NSURLConnection, even if the connection has not yet started. The `response`
69
+ # object will be a NSURLResponse, *not* an `NSHTTPURLResponse`, and so will start to crash.
70
+ if App.osx? && !response.is_a?(NSHTTPURLResponse)
71
+ return
72
+ end
73
+ @status_code = response.statusCode
74
+ @response_headers = response.allHeaderFields
75
+ @response_size = response.expectedContentLength.to_f
76
+ end
77
+
78
+ # This delegate method get called every time a chunk of data is being received
79
+ def connection(connection, didReceiveData:received_data)
80
+ @received_data ||= NSMutableData.new
81
+ @received_data.appendData(received_data)
82
+
83
+ if download_progress = options[:download_progress]
84
+ download_progress.call(@received_data.length.to_f, response_size)
85
+ end
86
+ end
87
+
88
+ def connection(connection, willSendRequest:request, redirectResponse:redirect_response)
89
+ # abort early if the user has explicitly disabled redirects
90
+ if @options[:no_redirect] and redirect_response then
91
+ return nil
92
+ end
93
+ @redirect_count ||= 0
94
+ @redirect_count += 1
95
+ log "##{@redirect_count} HTTP redirect_count: #{request.inspect} - #{self.description}"
96
+
97
+ if @redirect_count >= 30
98
+ @response.error_message = "Too many redirections"
99
+ @request.done_loading!
100
+ call_delegator_with_response
101
+ nil
102
+ else
103
+ @url = request.URL if @follow_urls
104
+ request
105
+ end
106
+ end
107
+
108
+ def connection(connection, didFailWithError: error)
109
+ log "HTTP Connection to #{@url.absoluteString} failed #{error.localizedDescription}"
110
+ show_status_indicator false
111
+ @request.done_loading!
112
+ @response.error_message = error.localizedDescription
113
+ call_delegator_with_response
114
+ end
115
+
116
+ def connection(connection, didSendBodyData:sending, totalBytesWritten:written, totalBytesExpectedToWrite:expected)
117
+ if upload_progress = options[:upload_progress]
118
+ upload_progress.call(sending, written, expected)
119
+ end
120
+ end
121
+
122
+ def connectionDidFinishLoading(connection)
123
+ show_status_indicator false
124
+ @request.done_loading!
125
+ response_body = NSData.dataWithData(@received_data) if @received_data
126
+ @response.update(status_code: status_code, body: response_body, headers: response_headers, url: @url, original_url: @original_url)
127
+
128
+ call_delegator_with_response
129
+ end
130
+
131
+ def connection(connection, didReceiveAuthenticationChallenge:challenge)
132
+ if (challenge.previousFailureCount == 0)
133
+ if credentials[:username].to_s.empty? && credentials[:password].to_s.empty?
134
+ challenge.sender.continueWithoutCredentialForAuthenticationChallenge(challenge)
135
+ log 'Continue without credentials to get 401 status in response'
136
+ else
137
+ new_credential = NSURLCredential.credentialWithUser(credentials[:username], password:credentials[:password], persistence:@credential_persistence)
138
+ challenge.sender.useCredential(new_credential, forAuthenticationChallenge:challenge)
139
+ log "auth challenged, answered with credentials: #{credentials.inspect}"
140
+ end
141
+ else
142
+ challenge.sender.cancelAuthenticationChallenge(challenge)
143
+ log 'Auth Failed :('
144
+ end
145
+ end
146
+
147
+
148
+ private
149
+
150
+ private
151
+ def show_status_indicator(show)
152
+ if !App.osx?
153
+ UIApplication.sharedApplication.networkActivityIndicatorVisible = show
154
+ end
155
+ end
156
+
157
+ def create_request
158
+ log "BubbleWrap::HTTP building a NSRequest for #{@url.description}"
159
+
160
+ request = NSMutableURLRequest.requestWithURL(@url,
161
+ cachePolicy:@cache_policy,
162
+ timeoutInterval:@timeout)
163
+ request.setHTTPMethod(@method)
164
+ set_content_type
165
+ append_auth_header
166
+ request.setAllHTTPHeaderFields(@headers)
167
+ request.setHTTPBody(@body)
168
+ request.setHTTPShouldHandleCookies(@cookies)
169
+ patch_nsurl_request(request)
170
+
171
+ request
172
+ end
173
+
174
+ def set_content_type
175
+ return if headers_provided?
176
+ return if (@method == "GET" || @method == "HEAD")
177
+ @headers ||= {}
178
+ @headers["Content-Type"] = case @format
179
+ when :json
180
+ "application/json"
181
+ when :xml
182
+ "application/xml"
183
+ when :text
184
+ "text/plain"
185
+ else
186
+ if @format == :form_data || @payload_or_files_were_appended
187
+ "multipart/form-data; boundary=#{@boundary}"
188
+ else
189
+ "application/x-www-form-urlencoded"
190
+ end
191
+ end
192
+ end
193
+
194
+ def headers_provided?
195
+ @headers && @headers.keys.find {|k| k.downcase == 'content-type'}
196
+ end
197
+
198
+ def credentials_provided?
199
+ @credentials[:username] && @credentials[:password]
200
+ end
201
+
202
+ def create_request_body
203
+ return nil if (@method == "GET" || @method == "HEAD")
204
+ return nil unless (@payload || @files)
205
+
206
+ body = NSMutableData.data
207
+
208
+ append_payload(body) if @payload
209
+ append_files(body) if @files
210
+ append_body_boundary(body) if @payload_or_files_were_appended
211
+
212
+ log "Built HTTP body: \n #{body.to_str}"
213
+ body
214
+ end
215
+
216
+ def append_payload(body)
217
+ if @payload.is_a?(NSData)
218
+ body.appendData(@payload)
219
+ elsif @payload.is_a?(String)
220
+ body.appendData(@payload.dataUsingEncoding NSUTF8StringEncoding)
221
+ else
222
+ append_form_params(body)
223
+ end
224
+ body
225
+ end
226
+
227
+ def append_form_params(body)
228
+ list = process_payload_hash(@payload)
229
+ list.each do |key, value|
230
+ s = "--#{@boundary}\r\n"
231
+ s += "Content-Disposition: form-data; name=\"#{key}\"\r\n\r\n"
232
+ s += value.to_s
233
+ s += "\r\n"
234
+ body.appendData(s.dataUsingEncoding NSUTF8StringEncoding)
235
+ end
236
+ @payload_or_files_were_appended = true
237
+ body
238
+ end
239
+
240
+ def append_auth_header
241
+ return if @headers && @headers["Authorization"]
242
+
243
+ if credentials_provided? && @present_credentials
244
+ mock_request = CFHTTPMessageCreateRequest(nil, nil, nil, nil)
245
+ CFHTTPMessageAddAuthentication(mock_request, nil, @credentials[:username], @credentials[:password], KCFHTTPAuthenticationSchemeBasic, false)
246
+
247
+ @headers ||= {}
248
+ @headers["Authorization"] = CFHTTPMessageCopyHeaderFieldValue(mock_request, "Authorization")
249
+ end
250
+ end
251
+
252
+ def parse_file(key, value)
253
+ if value.is_a?(Hash)
254
+ raise InvalidFileError if value[:data].nil?
255
+ {data: value[:data], filename: value[:filename] || key}
256
+ else
257
+ {data: value, filename: key}
258
+ end
259
+ end
260
+
261
+ def append_files(body)
262
+ @files.each do |key, value|
263
+ file = parse_file(key, value)
264
+ s = "--#{@boundary}\r\n"
265
+ s += "Content-Disposition: form-data; name=\"#{key}\"; filename=\"#{file[:filename]}\"\r\n"
266
+ s += "Content-Type: application/octet-stream\r\n\r\n"
267
+ file_data = NSMutableData.new
268
+ file_data.appendData(s.dataUsingEncoding NSUTF8StringEncoding)
269
+ file_data.appendData(file[:data])
270
+ file_data.appendData("\r\n".dataUsingEncoding NSUTF8StringEncoding)
271
+ body.appendData(file_data)
272
+ end
273
+ @payload_or_files_were_appended = true
274
+ body
275
+ end
276
+
277
+ def append_body_boundary(body)
278
+ body.appendData("--#{@boundary}--\r\n".dataUsingEncoding NSUTF8StringEncoding)
279
+ end
280
+
281
+ def create_url(url_string)
282
+ url_string = url_string.stringByAddingPercentEscapesUsingEncoding NSUTF8StringEncoding
283
+ if (@method == "GET" || @method == "HEAD") && @payload
284
+ unless @payload.empty?
285
+ convert_payload_to_url if @payload.is_a?(Hash)
286
+ url_string += "?#{@payload}"
287
+ end
288
+ end
289
+ url = NSURL.URLWithString(url_string)
290
+
291
+ validate_url(url)
292
+ url
293
+ end
294
+
295
+ def validate_url(url)
296
+ if !NSURLConnection.canHandleRequest(NSURLRequest.requestWithURL(url))
297
+ raise InvalidURLError, "Invalid URL provided (Make sure you include a valid URL scheme, e.g. http:// or similar)."
298
+ end
299
+ end
300
+
301
+ def escape(string)
302
+ if string
303
+ CFURLCreateStringByAddingPercentEscapes nil, string.to_s, nil, "!*'();:@&=+$,/?%#[]", KCFStringEncodingUTF8
304
+ end
305
+ end
306
+
307
+ def convert_payload_to_url
308
+ params_array = process_payload_hash(@payload)
309
+ params_array.map! { |key, value| "#{escape key}=#{escape value}" }
310
+ @payload = params_array.join("&")
311
+ end
312
+
313
+ def process_payload_hash(payload, prefix=nil)
314
+ list = []
315
+ payload.each do |k,v|
316
+ if v.is_a?(Hash)
317
+ new_prefix = prefix ? "#{prefix}[#{k.to_s}]" : k.to_s
318
+ param = process_payload_hash(v, new_prefix)
319
+ list += param
320
+ elsif v.is_a?(Array)
321
+ v.each do |val|
322
+ param = prefix ? "#{prefix}[#{k.to_s}][]" : "#{k.to_s}[]"
323
+ if val.is_a?(Hash)
324
+ list += process_payload_hash(val, param)
325
+ else
326
+ list << [param, val]
327
+ end
328
+ end
329
+ else
330
+ param = prefix ? "#{prefix}[#{k.to_s}]" : k.to_s
331
+ list << [param, v]
332
+ end
333
+ end
334
+ list
335
+ end
336
+
337
+ def log(message)
338
+ NSLog message if BubbleWrap.debug?
339
+ end
340
+
341
+ def escape_line_feeds(hash)
342
+ return nil if hash.nil?
343
+ escaped_hash = {}
344
+
345
+ hash.each{|k,v| escaped_hash[k] = v.gsub("\n", CLRF) if v }
346
+ escaped_hash
347
+ end
348
+
349
+ def patch_nsurl_request(request)
350
+ request.instance_variable_set("@done_loading", false)
351
+
352
+ def request.done_loading?; @done_loading; end
353
+ def request.done_loading!; @done_loading = true; end
354
+ end
355
+
356
+ def call_delegator_with_response
357
+ if @delegator.respond_to?(:call)
358
+ @delegator.call( @response, self )
359
+ end
360
+ end
361
+
362
+ # This is a temporary method used for mocking.
363
+ def create_connection(request, delegate)
364
+ NSURLConnection.connectionWithRequest(request, delegate:delegate)
365
+ end
366
+
367
+ end
@@ -0,0 +1,32 @@
1
+ # Response class wrapping the results of a Query's response
2
+ class BubbleWrap::HTTP::Response
3
+ attr_reader :body
4
+ attr_reader :headers
5
+ attr_accessor :status_code, :status_description, :error_message
6
+ attr_reader :url
7
+ attr_reader :original_url
8
+
9
+ def initialize(values={})
10
+ self.update(values)
11
+ end
12
+
13
+ def update(values)
14
+ values.each do |k,v|
15
+ self.instance_variable_set("@#{k}", v)
16
+ end
17
+ update_status_description
18
+ end
19
+
20
+ def ok?
21
+ status_code.to_s =~ /20\d/ ? true : false
22
+ end
23
+
24
+ def to_s
25
+ "#<#{self.class}:#{self.object_id} - url: #{self.url}, body: #{self.body}, headers: #{self.headers}, status code: #{self.status_code}, error message: #{self.error_message} >"
26
+ end
27
+ alias description to_s
28
+
29
+ def update_status_description
30
+ @status_description = status_code.nil? ? nil : NSHTTPURLResponse.localizedStringForStatusCode(status_code)
31
+ end
32
+ end
@@ -8,3 +8,61 @@ class TestSuiteDelegate
8
8
  true
9
9
  end
10
10
  end
11
+
12
+ class TestSuiteOSXDelegate
13
+ def applicationDidFinishLaunching(notification)
14
+ buildMenu
15
+ buildWindow
16
+ end
17
+
18
+ def buildWindow
19
+ @mainWindow = NSWindow.alloc.initWithContentRect([[240, 180], [480, 360]],
20
+ styleMask: NSTitledWindowMask|NSClosableWindowMask|NSMiniaturizableWindowMask|NSResizableWindowMask,
21
+ backing: NSBackingStoreBuffered,
22
+ defer: false)
23
+ @mainWindow.title = "BubbleWrap Tests"
24
+ end
25
+
26
+ def buildMenu
27
+ @mainMenu = NSMenu.new
28
+
29
+ appName = "BubbleWrap Tests"
30
+ addMenu(appName) do
31
+ addItemWithTitle("About #{appName}", action: 'orderFrontStandardAboutPanel:', keyEquivalent: '')
32
+ addItem(NSMenuItem.separatorItem)
33
+ addItemWithTitle('Preferences', action: 'openPreferences:', keyEquivalent: ',')
34
+ addItem(NSMenuItem.separatorItem)
35
+ servicesItem = addItemWithTitle('Services', action: nil, keyEquivalent: '')
36
+ NSApp.servicesMenu = servicesItem.submenu = NSMenu.new
37
+ addItem(NSMenuItem.separatorItem)
38
+ addItemWithTitle("Hide #{appName}", action: 'hide:', keyEquivalent: 'h')
39
+ item = addItemWithTitle('Hide Others', action: 'hideOtherApplications:', keyEquivalent: 'H')
40
+ item.keyEquivalentModifierMask = NSCommandKeyMask|NSAlternateKeyMask
41
+ addItemWithTitle('Show All', action: 'unhideAllApplications:', keyEquivalent: '')
42
+ addItem(NSMenuItem.separatorItem)
43
+ addItemWithTitle("Quit #{appName}", action: 'terminate:', keyEquivalent: 'q')
44
+ end
45
+
46
+ NSApp.helpMenu = addMenu('Help') do
47
+ addItemWithTitle("#{appName} Help", action: 'showHelp:', keyEquivalent: '?')
48
+ end.menu
49
+
50
+ NSApp.mainMenu = @mainMenu
51
+ end
52
+
53
+ private
54
+
55
+ def addMenu(title, &b)
56
+ item = createMenu(title, &b)
57
+ @mainMenu.addItem item
58
+ item
59
+ end
60
+
61
+ def createMenu(title, &b)
62
+ menu = NSMenu.alloc.initWithTitle(title)
63
+ menu.instance_eval(&b) if b
64
+ item = NSMenuItem.alloc.initWithTitle(title, action: nil, keyEquivalent: '')
65
+ item.submenu = menu
66
+ item
67
+ end
68
+ end