jomz-google-api-client 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/CHANGELOG.md +144 -0
  2. data/CONTRIBUTING.md +32 -0
  3. data/Gemfile +41 -0
  4. data/LICENSE +202 -0
  5. data/README.md +192 -0
  6. data/Rakefile +46 -0
  7. data/lib/cacerts.pem +2183 -0
  8. data/lib/compat/multi_json.rb +16 -0
  9. data/lib/google/api_client.rb +672 -0
  10. data/lib/google/api_client/auth/compute_service_account.rb +28 -0
  11. data/lib/google/api_client/auth/file_storage.rb +87 -0
  12. data/lib/google/api_client/auth/installed_app.rb +122 -0
  13. data/lib/google/api_client/auth/jwt_asserter.rb +126 -0
  14. data/lib/google/api_client/auth/key_utils.rb +93 -0
  15. data/lib/google/api_client/auth/pkcs12.rb +41 -0
  16. data/lib/google/api_client/batch.rb +323 -0
  17. data/lib/google/api_client/client_secrets.rb +176 -0
  18. data/lib/google/api_client/discovery.rb +19 -0
  19. data/lib/google/api_client/discovery/api.rb +300 -0
  20. data/lib/google/api_client/discovery/media.rb +77 -0
  21. data/lib/google/api_client/discovery/method.rb +363 -0
  22. data/lib/google/api_client/discovery/resource.rb +156 -0
  23. data/lib/google/api_client/discovery/schema.rb +121 -0
  24. data/lib/google/api_client/environment.rb +42 -0
  25. data/lib/google/api_client/errors.rb +60 -0
  26. data/lib/google/api_client/gzip.rb +28 -0
  27. data/lib/google/api_client/logging.rb +32 -0
  28. data/lib/google/api_client/media.rb +259 -0
  29. data/lib/google/api_client/railtie.rb +16 -0
  30. data/lib/google/api_client/reference.rb +27 -0
  31. data/lib/google/api_client/request.rb +351 -0
  32. data/lib/google/api_client/result.rb +253 -0
  33. data/lib/google/api_client/service.rb +233 -0
  34. data/lib/google/api_client/service/batch.rb +103 -0
  35. data/lib/google/api_client/service/request.rb +144 -0
  36. data/lib/google/api_client/service/resource.rb +40 -0
  37. data/lib/google/api_client/service/result.rb +162 -0
  38. data/lib/google/api_client/service/simple_file_store.rb +151 -0
  39. data/lib/google/api_client/service/stub_generator.rb +59 -0
  40. data/lib/google/api_client/service_account.rb +18 -0
  41. data/lib/google/api_client/version.rb +31 -0
  42. data/lib/google/inflection.rb +28 -0
  43. data/spec/fixtures/files/privatekey.p12 +0 -0
  44. data/spec/fixtures/files/sample.txt +33 -0
  45. data/spec/fixtures/files/secret.pem +19 -0
  46. data/spec/google/api_client/batch_spec.rb +249 -0
  47. data/spec/google/api_client/discovery_spec.rb +652 -0
  48. data/spec/google/api_client/gzip_spec.rb +86 -0
  49. data/spec/google/api_client/media_spec.rb +179 -0
  50. data/spec/google/api_client/request_spec.rb +30 -0
  51. data/spec/google/api_client/result_spec.rb +203 -0
  52. data/spec/google/api_client/service_account_spec.rb +164 -0
  53. data/spec/google/api_client/service_spec.rb +586 -0
  54. data/spec/google/api_client/simple_file_store_spec.rb +137 -0
  55. data/spec/google/api_client_spec.rb +253 -0
  56. data/spec/spec_helper.rb +56 -0
  57. data/tasks/gem.rake +97 -0
  58. data/tasks/git.rake +45 -0
  59. data/tasks/metrics.rake +22 -0
  60. data/tasks/spec.rake +57 -0
  61. data/tasks/wiki.rake +82 -0
  62. data/tasks/yard.rake +29 -0
  63. metadata +309 -0
@@ -0,0 +1,156 @@
1
+ # Copyright 2010 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require 'addressable/uri'
17
+
18
+ require 'google/inflection'
19
+ require 'google/api_client/discovery/method'
20
+
21
+
22
+ module Google
23
+ class APIClient
24
+ ##
25
+ # A resource that has been described by a discovery document.
26
+ class Resource
27
+
28
+ ##
29
+ # Creates a description of a particular version of a resource.
30
+ #
31
+ # @param [Google::APIClient::API] api
32
+ # The API this resource belongs to.
33
+ # @param [Addressable::URI] method_base
34
+ # The base URI for the service.
35
+ # @param [String] resource_name
36
+ # The identifier for the resource.
37
+ # @param [Hash] discovery_document
38
+ # The section of the discovery document that applies to this resource.
39
+ #
40
+ # @return [Google::APIClient::Resource] The constructed resource object.
41
+ def initialize(api, method_base, resource_name, discovery_document)
42
+ @api = api
43
+ @method_base = method_base
44
+ @name = resource_name
45
+ @discovery_document = discovery_document
46
+ metaclass = (class <<self; self; end)
47
+ self.discovered_resources.each do |resource|
48
+ method_name = Google::INFLECTOR.underscore(resource.name).to_sym
49
+ if !self.respond_to?(method_name)
50
+ metaclass.send(:define_method, method_name) { resource }
51
+ end
52
+ end
53
+ self.discovered_methods.each do |method|
54
+ method_name = Google::INFLECTOR.underscore(method.name).to_sym
55
+ if !self.respond_to?(method_name)
56
+ metaclass.send(:define_method, method_name) { method }
57
+ end
58
+ end
59
+ end
60
+
61
+ # @return [String] unparsed discovery document for the resource
62
+ attr_reader :discovery_document
63
+
64
+ ##
65
+ # Returns the identifier for the resource.
66
+ #
67
+ # @return [String] The resource identifier.
68
+ attr_reader :name
69
+
70
+ ##
71
+ # Returns the base URI for this resource.
72
+ #
73
+ # @return [Addressable::URI] The base URI that methods are joined to.
74
+ attr_reader :method_base
75
+
76
+ ##
77
+ # Returns a human-readable description of the resource.
78
+ #
79
+ # @return [Hash] The API description.
80
+ def description
81
+ return @discovery_document['description']
82
+ end
83
+
84
+ ##
85
+ # Updates the hierarchy of resources and methods with the new base.
86
+ #
87
+ # @param [Addressable::URI, #to_str, String] new_method_base
88
+ # The new base URI to use for the resource.
89
+ def method_base=(new_method_base)
90
+ @method_base = Addressable::URI.parse(new_method_base)
91
+ self.discovered_resources.each do |resource|
92
+ resource.method_base = @method_base
93
+ end
94
+ self.discovered_methods.each do |method|
95
+ method.method_base = @method_base
96
+ end
97
+ end
98
+
99
+ ##
100
+ # A list of sub-resources available on this resource.
101
+ #
102
+ # @return [Array] A list of {Google::APIClient::Resource} objects.
103
+ def discovered_resources
104
+ return @discovered_resources ||= (
105
+ (@discovery_document['resources'] || []).inject([]) do |accu, (k, v)|
106
+ accu << Google::APIClient::Resource.new(
107
+ @api, self.method_base, k, v
108
+ )
109
+ accu
110
+ end
111
+ )
112
+ end
113
+
114
+ ##
115
+ # A list of methods available on this resource.
116
+ #
117
+ # @return [Array] A list of {Google::APIClient::Method} objects.
118
+ def discovered_methods
119
+ return @discovered_methods ||= (
120
+ (@discovery_document['methods'] || []).inject([]) do |accu, (k, v)|
121
+ accu << Google::APIClient::Method.new(@api, self.method_base, k, v)
122
+ accu
123
+ end
124
+ )
125
+ end
126
+
127
+ ##
128
+ # Converts the resource to a flat mapping of RPC names and method
129
+ # objects.
130
+ #
131
+ # @return [Hash] All methods available on the resource.
132
+ def to_h
133
+ return @hash ||= (begin
134
+ methods_hash = {}
135
+ self.discovered_methods.each do |method|
136
+ methods_hash[method.id] = method
137
+ end
138
+ self.discovered_resources.each do |resource|
139
+ methods_hash.merge!(resource.to_h)
140
+ end
141
+ methods_hash
142
+ end)
143
+ end
144
+
145
+ ##
146
+ # Returns a <code>String</code> representation of the resource's state.
147
+ #
148
+ # @return [String] The resource's state, as a <code>String</code>.
149
+ def inspect
150
+ sprintf(
151
+ "#<%s:%#0x NAME:%s>", self.class.to_s, self.object_id, self.name
152
+ )
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,121 @@
1
+ # Copyright 2010 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require 'time'
17
+ require 'multi_json'
18
+ require 'compat/multi_json'
19
+ require 'base64'
20
+ require 'autoparse'
21
+ require 'addressable/uri'
22
+ require 'addressable/template'
23
+
24
+ require 'google/inflection'
25
+ require 'google/api_client/errors'
26
+
27
+
28
+ module Google
29
+ class APIClient
30
+ ##
31
+ # @api private
32
+ module Schema
33
+ def self.parse(api, schema_data)
34
+ # This method is super-long, but hard to break up due to the
35
+ # unavoidable dependence on closures and execution context.
36
+ schema_name = schema_data['id']
37
+
38
+ # Due to an oversight, schema IDs may not be URI references.
39
+ # TODO(bobaman): Remove this code once this has been resolved.
40
+ schema_uri = (
41
+ api.document_base +
42
+ (schema_name[0..0] != '#' ? '#' + schema_name : schema_name)
43
+ )
44
+ # puts schema_uri
45
+
46
+ # Due to an oversight, schema IDs may not be URI references.
47
+ # TODO(bobaman): Remove this whole lambda once this has been resolved.
48
+ reformat_references = lambda do |data|
49
+ # This code is not particularly efficient due to recursive traversal
50
+ # and excess object creation, but this hopefully shouldn't be an
51
+ # issue since it should only be called only once per schema per
52
+ # process.
53
+ if data.kind_of?(Hash) &&
54
+ data['$ref'] && !data['$ref'].kind_of?(Hash)
55
+ if data['$ref'].respond_to?(:to_str)
56
+ reference = data['$ref'].to_str
57
+ else
58
+ raise TypeError, "Expected String, got #{data['$ref'].class}"
59
+ end
60
+ reference = '#' + reference if reference[0..0] != '#'
61
+ data.merge({
62
+ '$ref' => reference
63
+ })
64
+ elsif data.kind_of?(Hash)
65
+ data.inject({}) do |accu, (key, value)|
66
+ if value.kind_of?(Hash)
67
+ accu[key] = reformat_references.call(value)
68
+ else
69
+ accu[key] = value
70
+ end
71
+ accu
72
+ end
73
+ else
74
+ data
75
+ end
76
+ end
77
+ schema_data = reformat_references.call(schema_data)
78
+ # puts schema_data.inspect
79
+
80
+ if schema_name
81
+ api_name_string =
82
+ Google::INFLECTOR.camelize(api.name)
83
+ api_version_string =
84
+ Google::INFLECTOR.camelize(api.version).gsub('.', '_')
85
+ # This is for compatibility with Ruby 1.8.7.
86
+ # TODO(bobaman) Remove this when we eventually stop supporting 1.8.7.
87
+ args = []
88
+ args << false if Class.method(:const_defined?).arity != 1
89
+ if Google::APIClient::Schema.const_defined?(api_name_string, *args)
90
+ api_name = Google::APIClient::Schema.const_get(
91
+ api_name_string, *args
92
+ )
93
+ else
94
+ api_name = Google::APIClient::Schema.const_set(
95
+ api_name_string, Module.new
96
+ )
97
+ end
98
+ if api_name.const_defined?(api_version_string, *args)
99
+ api_version = api_name.const_get(api_version_string, *args)
100
+ else
101
+ api_version = api_name.const_set(api_version_string, Module.new)
102
+ end
103
+ if api_version.const_defined?(schema_name, *args)
104
+ schema_class = api_version.const_get(schema_name, *args)
105
+ end
106
+ end
107
+
108
+ # It's possible the schema has already been defined. If so, don't
109
+ # redefine it. This means that reloading a schema which has already
110
+ # been loaded into memory is not possible.
111
+ unless schema_class
112
+ schema_class = AutoParse.generate(schema_data, :uri => schema_uri)
113
+ if schema_name
114
+ api_version.const_set(schema_name, schema_class)
115
+ end
116
+ end
117
+ return schema_class
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,42 @@
1
+ # Copyright 2010 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ module Google
17
+ class APIClient
18
+ module ENV
19
+ OS_VERSION = begin
20
+ if RUBY_PLATFORM =~ /mswin|win32|mingw|bccwin|cygwin/
21
+ # TODO(bobaman)
22
+ # Confirm that all of these Windows environments actually have access
23
+ # to the `ver` command.
24
+ `ver`.sub(/\s*\[Version\s*/, '/').sub(']', '').strip
25
+ elsif RUBY_PLATFORM =~ /darwin/i
26
+ "Mac OS X/#{`sw_vers -productVersion`}"
27
+ elsif RUBY_PLATFORM == 'java'
28
+ # Get the information from java system properties to avoid spawning a
29
+ # sub-process, which is not friendly in some contexts (web servers).
30
+ require 'java'
31
+ name = java.lang.System.getProperty('os.name')
32
+ version = java.lang.System.getProperty('os.version')
33
+ "#{name}/#{version}"
34
+ else
35
+ `uname -sr`.sub(' ', '/')
36
+ end
37
+ rescue Exception
38
+ RUBY_PLATFORM
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,60 @@
1
+ # Copyright 2010 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ module Google
17
+ class APIClient
18
+ ##
19
+ # An error which is raised when there is an unexpected response or other
20
+ # transport error that prevents an operation from succeeding.
21
+ class TransmissionError < StandardError
22
+ attr_reader :result
23
+ def initialize(message = nil, result = nil)
24
+ super(message)
25
+ @result = result
26
+ end
27
+ end
28
+
29
+ ##
30
+ # An exception that is raised if a redirect is required
31
+ #
32
+ class RedirectError < TransmissionError
33
+ end
34
+
35
+ ##
36
+ # An exception that is raised if a method is called with missing or
37
+ # invalid parameter values.
38
+ class ValidationError < StandardError
39
+ end
40
+
41
+ ##
42
+ # A 4xx class HTTP error occurred.
43
+ class ClientError < TransmissionError
44
+ end
45
+
46
+ ##
47
+ # A 5xx class HTTP error occurred.
48
+ class ServerError < TransmissionError
49
+ end
50
+
51
+ ##
52
+ # An exception that is raised if an ID token could not be validated.
53
+ class InvalidIDTokenError < StandardError
54
+ end
55
+
56
+ # Error class for problems in batch requests.
57
+ class BatchError < StandardError
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,28 @@
1
+ require 'faraday'
2
+ require 'zlib'
3
+
4
+ module Google
5
+ class APIClient
6
+ class Gzip < Faraday::Response::Middleware
7
+ include Google::APIClient::Logging
8
+
9
+ def on_complete(env)
10
+ encoding = env[:response_headers]['content-encoding'].to_s.downcase
11
+ case encoding
12
+ when 'gzip'
13
+ logger.debug { "Decompressing gzip encoded response (#{env[:body].length} bytes)" }
14
+ env[:body] = Zlib::GzipReader.new(StringIO.new(env[:body])).read
15
+ env[:response_headers].delete('content-encoding')
16
+ logger.debug { "Decompressed (#{env[:body].length} bytes)" }
17
+ when 'deflate'
18
+ logger.debug{ "Decompressing deflate encoded response (#{env[:body].length} bytes)" }
19
+ env[:body] = Zlib::Inflate.inflate(env[:body])
20
+ env[:response_headers].delete('content-encoding')
21
+ logger.debug { "Decompressed (#{env[:body].length} bytes)" }
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ Faraday::Response.register_middleware :gzip => Google::APIClient::Gzip
@@ -0,0 +1,32 @@
1
+ require 'logger'
2
+
3
+ module Google
4
+ class APIClient
5
+
6
+ class << self
7
+ ##
8
+ # Logger for the API client
9
+ #
10
+ # @return [Logger] logger instance.
11
+ attr_accessor :logger
12
+ end
13
+
14
+ self.logger = Logger.new(STDOUT)
15
+ self.logger.level = Logger::WARN
16
+
17
+ ##
18
+ # Module to make accessing the logger simpler
19
+ module Logging
20
+ ##
21
+ # Logger for the API client
22
+ #
23
+ # @return [Logger] logger instance.
24
+ def logger
25
+ Google::APIClient.logger
26
+ end
27
+ end
28
+
29
+ end
30
+
31
+
32
+ end
@@ -0,0 +1,259 @@
1
+ # Copyright 2010 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ require 'google/api_client/reference'
15
+
16
+ module Google
17
+ class APIClient
18
+ ##
19
+ # Uploadable media support. Holds an IO stream & content type.
20
+ #
21
+ # @see Faraday::UploadIO
22
+ # @example
23
+ # media = Google::APIClient::UploadIO.new('mymovie.m4v', 'video/mp4')
24
+ class UploadIO < Faraday::UploadIO
25
+
26
+ # @return [Fixnum] Size of chunks to upload. Default is nil, meaning upload the entire file in a single request
27
+ attr_accessor :chunk_size
28
+
29
+ ##
30
+ # Get the length of the stream
31
+ #
32
+ # @return [Fixnum]
33
+ # Length of stream, in bytes
34
+ def length
35
+ io.respond_to?(:length) ? io.length : File.size(local_path)
36
+ end
37
+ end
38
+
39
+ ##
40
+ # Wraps an input stream and limits data to a given range
41
+ #
42
+ # @example
43
+ # chunk = Google::APIClient::RangedIO.new(io, 0, 1000)
44
+ class RangedIO
45
+ ##
46
+ # Bind an input stream to a specific range.
47
+ #
48
+ # @param [IO] io
49
+ # Source input stream
50
+ # @param [Fixnum] offset
51
+ # Starting offset of the range
52
+ # @param [Fixnum] length
53
+ # Length of range
54
+ def initialize(io, offset, length)
55
+ @io = io
56
+ @offset = offset
57
+ @length = length
58
+ self.rewind
59
+ end
60
+
61
+ ##
62
+ # @see IO#read
63
+ def read(amount = nil, buf = nil)
64
+ buffer = buf || ''
65
+ if amount.nil?
66
+ size = @length - @pos
67
+ done = ''
68
+ elsif amount == 0
69
+ size = 0
70
+ done = ''
71
+ else
72
+ size = [@length - @pos, amount].min
73
+ done = nil
74
+ end
75
+
76
+ if size > 0
77
+ result = @io.read(size)
78
+ result.force_encoding("BINARY") if result.respond_to?(:force_encoding)
79
+ buffer << result if result
80
+ @pos = @pos + size
81
+ end
82
+
83
+ if buffer.length > 0
84
+ buffer
85
+ else
86
+ done
87
+ end
88
+ end
89
+
90
+ ##
91
+ # @see IO#rewind
92
+ def rewind
93
+ self.pos = 0
94
+ end
95
+
96
+ ##
97
+ # @see IO#pos
98
+ def pos
99
+ @pos
100
+ end
101
+
102
+ ##
103
+ # @see IO#pos=
104
+ def pos=(pos)
105
+ @pos = pos
106
+ @io.pos = @offset + pos
107
+ end
108
+ end
109
+
110
+ ##
111
+ # Resumable uploader.
112
+ #
113
+ class ResumableUpload < Request
114
+ # @return [Fixnum] Max bytes to send in a single request
115
+ attr_accessor :chunk_size
116
+
117
+ ##
118
+ # Creates a new uploader.
119
+ #
120
+ # @param [Hash] options
121
+ # Request options
122
+ def initialize(options={})
123
+ super options
124
+ self.uri = options[:uri]
125
+ self.http_method = :put
126
+ @offset = options[:offset] || 0
127
+ @complete = false
128
+ @expired = false
129
+ end
130
+
131
+ ##
132
+ # Sends all remaining chunks to the server
133
+ #
134
+ # @deprecated Pass the instance to {Google::APIClient#execute} instead
135
+ #
136
+ # @param [Google::APIClient] api_client
137
+ # API Client instance to use for sending
138
+ def send_all(api_client)
139
+ result = nil
140
+ until complete?
141
+ result = send_chunk(api_client)
142
+ break unless result.status == 308
143
+ end
144
+ return result
145
+ end
146
+
147
+
148
+ ##
149
+ # Sends the next chunk to the server
150
+ #
151
+ # @deprecated Pass the instance to {Google::APIClient#execute} instead
152
+ #
153
+ # @param [Google::APIClient] api_client
154
+ # API Client instance to use for sending
155
+ def send_chunk(api_client)
156
+ return api_client.execute(self)
157
+ end
158
+
159
+ ##
160
+ # Check if upload is complete
161
+ #
162
+ # @return [TrueClass, FalseClass]
163
+ # Whether or not the upload complete successfully
164
+ def complete?
165
+ return @complete
166
+ end
167
+
168
+ ##
169
+ # Check if the upload URL expired (upload not completed in alotted time.)
170
+ # Expired uploads must be restarted from the beginning
171
+ #
172
+ # @return [TrueClass, FalseClass]
173
+ # Whether or not the upload has expired and can not be resumed
174
+ def expired?
175
+ return @expired
176
+ end
177
+
178
+ ##
179
+ # Check if upload is resumable. That is, neither complete nor expired
180
+ #
181
+ # @return [TrueClass, FalseClass] True if upload can be resumed
182
+ def resumable?
183
+ return !(self.complete? or self.expired?)
184
+ end
185
+
186
+ ##
187
+ # Convert to an HTTP request. Returns components in order of method, URI,
188
+ # request headers, and body
189
+ #
190
+ # @api private
191
+ #
192
+ # @return [Array<(Symbol, Addressable::URI, Hash, [#read,#to_str])>]
193
+ def to_http_request
194
+ if @complete
195
+ raise Google::APIClient::ClientError, "Upload already complete"
196
+ elsif @offset.nil?
197
+ self.headers.update({
198
+ 'Content-Length' => "0",
199
+ 'Content-Range' => "bytes */#{media.length}" })
200
+ else
201
+ start_offset = @offset
202
+ remaining = self.media.length - start_offset
203
+ chunk_size = self.media.chunk_size || self.chunk_size || self.media.length
204
+ content_length = [remaining, chunk_size].min
205
+ chunk = RangedIO.new(self.media.io, start_offset, content_length)
206
+ end_offset = start_offset + content_length - 1
207
+ self.headers.update({
208
+ 'Content-Length' => "#{content_length}",
209
+ 'Content-Type' => self.media.content_type,
210
+ 'Content-Range' => "bytes #{start_offset}-#{end_offset}/#{media.length}" })
211
+ self.body = chunk
212
+ end
213
+ super
214
+ end
215
+
216
+ ##
217
+ # Check the result from the server, updating the offset and/or location
218
+ # if available.
219
+ #
220
+ # @api private
221
+ #
222
+ # @param [Faraday::Response] response
223
+ # HTTP response
224
+ #
225
+ # @return [Google::APIClient::Result]
226
+ # Processed API response
227
+ def process_http_response(response)
228
+ case response.status
229
+ when 200...299
230
+ @complete = true
231
+ when 308
232
+ range = response.headers['range']
233
+ if range
234
+ @offset = range.scan(/\d+/).collect{|x| Integer(x)}.last + 1
235
+ end
236
+ if response.headers['location']
237
+ self.uri = response.headers['location']
238
+ end
239
+ when 400...499
240
+ @expired = true
241
+ when 500...599
242
+ # Invalidate the offset to mark it needs to be queried on the
243
+ # next request
244
+ @offset = nil
245
+ end
246
+ return Google::APIClient::Result.new(self, response)
247
+ end
248
+
249
+ ##
250
+ # Hashified verison of the API request
251
+ #
252
+ # @return [Hash]
253
+ def to_hash
254
+ super.merge(:offset => @offset)
255
+ end
256
+
257
+ end
258
+ end
259
+ end