jomz-google-api-client 0.7.1
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.
- data/CHANGELOG.md +144 -0
- data/CONTRIBUTING.md +32 -0
- data/Gemfile +41 -0
- data/LICENSE +202 -0
- data/README.md +192 -0
- data/Rakefile +46 -0
- data/lib/cacerts.pem +2183 -0
- data/lib/compat/multi_json.rb +16 -0
- data/lib/google/api_client.rb +672 -0
- data/lib/google/api_client/auth/compute_service_account.rb +28 -0
- data/lib/google/api_client/auth/file_storage.rb +87 -0
- data/lib/google/api_client/auth/installed_app.rb +122 -0
- data/lib/google/api_client/auth/jwt_asserter.rb +126 -0
- data/lib/google/api_client/auth/key_utils.rb +93 -0
- data/lib/google/api_client/auth/pkcs12.rb +41 -0
- data/lib/google/api_client/batch.rb +323 -0
- data/lib/google/api_client/client_secrets.rb +176 -0
- data/lib/google/api_client/discovery.rb +19 -0
- data/lib/google/api_client/discovery/api.rb +300 -0
- data/lib/google/api_client/discovery/media.rb +77 -0
- data/lib/google/api_client/discovery/method.rb +363 -0
- data/lib/google/api_client/discovery/resource.rb +156 -0
- data/lib/google/api_client/discovery/schema.rb +121 -0
- data/lib/google/api_client/environment.rb +42 -0
- data/lib/google/api_client/errors.rb +60 -0
- data/lib/google/api_client/gzip.rb +28 -0
- data/lib/google/api_client/logging.rb +32 -0
- data/lib/google/api_client/media.rb +259 -0
- data/lib/google/api_client/railtie.rb +16 -0
- data/lib/google/api_client/reference.rb +27 -0
- data/lib/google/api_client/request.rb +351 -0
- data/lib/google/api_client/result.rb +253 -0
- data/lib/google/api_client/service.rb +233 -0
- data/lib/google/api_client/service/batch.rb +103 -0
- data/lib/google/api_client/service/request.rb +144 -0
- data/lib/google/api_client/service/resource.rb +40 -0
- data/lib/google/api_client/service/result.rb +162 -0
- data/lib/google/api_client/service/simple_file_store.rb +151 -0
- data/lib/google/api_client/service/stub_generator.rb +59 -0
- data/lib/google/api_client/service_account.rb +18 -0
- data/lib/google/api_client/version.rb +31 -0
- data/lib/google/inflection.rb +28 -0
- data/spec/fixtures/files/privatekey.p12 +0 -0
- data/spec/fixtures/files/sample.txt +33 -0
- data/spec/fixtures/files/secret.pem +19 -0
- data/spec/google/api_client/batch_spec.rb +249 -0
- data/spec/google/api_client/discovery_spec.rb +652 -0
- data/spec/google/api_client/gzip_spec.rb +86 -0
- data/spec/google/api_client/media_spec.rb +179 -0
- data/spec/google/api_client/request_spec.rb +30 -0
- data/spec/google/api_client/result_spec.rb +203 -0
- data/spec/google/api_client/service_account_spec.rb +164 -0
- data/spec/google/api_client/service_spec.rb +586 -0
- data/spec/google/api_client/simple_file_store_spec.rb +137 -0
- data/spec/google/api_client_spec.rb +253 -0
- data/spec/spec_helper.rb +56 -0
- data/tasks/gem.rake +97 -0
- data/tasks/git.rake +45 -0
- data/tasks/metrics.rake +22 -0
- data/tasks/spec.rake +57 -0
- data/tasks/wiki.rake +82 -0
- data/tasks/yard.rake +29 -0
- 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
|