bubble-wrap 1.2.0 → 1.3.0.osx

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