google-cloud-storage 1.9.0 → 1.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +2 -2
- data/lib/google-cloud-storage.rb +27 -2
- data/lib/google/cloud/storage.rb +101 -11
- data/lib/google/cloud/storage/bucket.rb +53 -21
- data/lib/google/cloud/storage/bucket/acl.rb +24 -24
- data/lib/google/cloud/storage/bucket/cors.rb +3 -3
- data/lib/google/cloud/storage/bucket/list.rb +3 -3
- data/lib/google/cloud/storage/credentials.rb +9 -8
- data/lib/google/cloud/storage/errors.rb +2 -2
- data/lib/google/cloud/storage/file.rb +74 -15
- data/lib/google/cloud/storage/file/acl.rb +13 -13
- data/lib/google/cloud/storage/file/list.rb +3 -3
- data/lib/google/cloud/storage/file/signer.rb +12 -8
- data/lib/google/cloud/storage/file/verifier.rb +12 -10
- data/lib/google/cloud/storage/notification.rb +3 -3
- data/lib/google/cloud/storage/policy.rb +3 -3
- data/lib/google/cloud/storage/post_object.rb +2 -2
- data/lib/google/cloud/storage/project.rb +5 -15
- data/lib/google/cloud/storage/service.rb +187 -22
- data/lib/google/cloud/storage/version.rb +3 -3
- metadata +11 -11
@@ -1,10 +1,10 @@
|
|
1
|
-
# Copyright 2015 Google
|
1
|
+
# Copyright 2015 Google LLC
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
5
5
|
# You may obtain a copy of the License at
|
6
6
|
#
|
7
|
-
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
8
|
#
|
9
9
|
# Unless required by applicable law or agreed to in writing, software
|
10
10
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -125,7 +125,7 @@ module Google
|
|
125
125
|
@origin = Array(origin)
|
126
126
|
@methods = Array(methods)
|
127
127
|
@headers = Array(headers)
|
128
|
-
@max_age = (max_age||1800)
|
128
|
+
@max_age = (max_age || 1800)
|
129
129
|
end
|
130
130
|
|
131
131
|
def to_gapi
|
@@ -1,10 +1,10 @@
|
|
1
|
-
# Copyright 2015 Google
|
1
|
+
# Copyright 2015 Google LLC
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
5
5
|
# You may obtain a copy of the License at
|
6
6
|
#
|
7
|
-
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
8
|
#
|
9
9
|
# Unless required by applicable law or agreed to in writing, software
|
10
10
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -163,7 +163,7 @@ module Google
|
|
163
163
|
##
|
164
164
|
# Raise an error unless an active connection is available.
|
165
165
|
def ensure_service!
|
166
|
-
|
166
|
+
raise "Must have active connection" unless @service
|
167
167
|
end
|
168
168
|
end
|
169
169
|
end
|
@@ -1,10 +1,10 @@
|
|
1
|
-
# Copyright 2014 Google
|
1
|
+
# Copyright 2014 Google LLC
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
5
5
|
# You may obtain a copy of the License at
|
6
6
|
#
|
7
|
-
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
8
|
#
|
9
9
|
# Unless required by applicable law or agreed to in writing, software
|
10
10
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -38,19 +38,20 @@ module Google
|
|
38
38
|
# storage.project_id #=> "my-project"
|
39
39
|
#
|
40
40
|
class Credentials < Google::Auth::Credentials
|
41
|
-
SCOPE =
|
42
|
-
|
41
|
+
SCOPE = \
|
42
|
+
["https://www.googleapis.com/auth/devstorage.full_control"].freeze
|
43
|
+
PATH_ENV_VARS = %w[STORAGE_CREDENTIALS
|
43
44
|
STORAGE_KEYFILE
|
44
45
|
GOOGLE_CLOUD_CREDENTIALS
|
45
46
|
GOOGLE_CLOUD_KEYFILE
|
46
|
-
GCLOUD_KEYFILE
|
47
|
-
JSON_ENV_VARS = %w
|
47
|
+
GCLOUD_KEYFILE].freeze
|
48
|
+
JSON_ENV_VARS = %w[STORAGE_CREDENTIALS_JSON
|
48
49
|
STORAGE_KEYFILE_JSON
|
49
50
|
GOOGLE_CLOUD_CREDENTIALS_JSON
|
50
51
|
GOOGLE_CLOUD_KEYFILE_JSON
|
51
|
-
GCLOUD_KEYFILE_JSON
|
52
|
+
GCLOUD_KEYFILE_JSON].freeze
|
52
53
|
DEFAULT_PATHS = \
|
53
|
-
["~/.config/gcloud/application_default_credentials.json"]
|
54
|
+
["~/.config/gcloud/application_default_credentials.json"].freeze
|
54
55
|
end
|
55
56
|
end
|
56
57
|
end
|
@@ -1,10 +1,10 @@
|
|
1
|
-
# Copyright 2014 Google
|
1
|
+
# Copyright 2014 Google LLC
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
5
5
|
# You may obtain a copy of the License at
|
6
6
|
#
|
7
|
-
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
8
|
#
|
9
9
|
# Unless required by applicable law or agreed to in writing, software
|
10
10
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -1,10 +1,10 @@
|
|
1
|
-
# Copyright 2014 Google
|
1
|
+
# Copyright 2014 Google LLC
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
5
5
|
# You may obtain a copy of the License at
|
6
6
|
#
|
7
|
-
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
8
|
#
|
9
9
|
# Unless required by applicable law or agreed to in writing, software
|
10
10
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -13,11 +13,11 @@
|
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
15
|
|
16
|
-
require "uri"
|
17
16
|
require "google/cloud/storage/file/acl"
|
18
17
|
require "google/cloud/storage/file/list"
|
19
18
|
require "google/cloud/storage/file/verifier"
|
20
19
|
require "google/cloud/storage/file/signer"
|
20
|
+
require "zlib"
|
21
21
|
|
22
22
|
module Google
|
23
23
|
module Cloud
|
@@ -438,6 +438,13 @@ module Google
|
|
438
438
|
# @param [String] encryption_key Optional. The customer-supplied,
|
439
439
|
# AES-256 encryption key used to encrypt the file, if one was provided
|
440
440
|
# to {Bucket#create_file}.
|
441
|
+
# @param [Boolean] skip_decompress Optional. If `true`, the data for a
|
442
|
+
# Storage object returning a `Content-Encoding: gzip` response header
|
443
|
+
# will *not* be automatically decompressed by this client library. The
|
444
|
+
# default is `nil`. Note that all requests by this client library send
|
445
|
+
# the `Accept-Encoding: gzip` header, so decompressive transcoding is
|
446
|
+
# not performed in the Storage service. (See [Transcoding of
|
447
|
+
# gzip-compressed files](https://cloud.google.com/storage/docs/transcoding))
|
441
448
|
#
|
442
449
|
# @return [IO] Returns an IO object representing the file data. This
|
443
450
|
# will ordinarily be a `::File` object referencing the local file
|
@@ -497,7 +504,7 @@ module Google
|
|
497
504
|
# downloaded.rewind
|
498
505
|
# downloaded.read #=> "Hello world!"
|
499
506
|
#
|
500
|
-
# @example Download a public file with an unauthenticated client
|
507
|
+
# @example Download a public file with an unauthenticated client.
|
501
508
|
# require "google/cloud/storage"
|
502
509
|
#
|
503
510
|
# storage = Google::Cloud::Storage.anonymous
|
@@ -509,17 +516,49 @@ module Google
|
|
509
516
|
# downloaded.rewind
|
510
517
|
# downloaded.read #=> "Hello world!"
|
511
518
|
#
|
512
|
-
|
519
|
+
# @example Upload and download gzip-encoded file data.
|
520
|
+
# require "zlib"
|
521
|
+
# require "google/cloud/storage"
|
522
|
+
#
|
523
|
+
# storage = Google::Cloud::Storage.new
|
524
|
+
#
|
525
|
+
# gz = StringIO.new ""
|
526
|
+
# z = Zlib::GzipWriter.new gz
|
527
|
+
# z.write "Hello world!"
|
528
|
+
# z.close
|
529
|
+
# data = StringIO.new gz.string
|
530
|
+
#
|
531
|
+
# bucket = storage.bucket "my-bucket"
|
532
|
+
#
|
533
|
+
# bucket.create_file data, "path/to/gzipped.txt",
|
534
|
+
# content_encoding: "gzip"
|
535
|
+
#
|
536
|
+
# file = bucket.file "path/to/gzipped.txt"
|
537
|
+
#
|
538
|
+
# # The downloaded data is decompressed by default.
|
539
|
+
# file.download "path/to/downloaded/hello.txt"
|
540
|
+
#
|
541
|
+
# # The downloaded data remains compressed with skip_decompress.
|
542
|
+
# file.download "path/to/downloaded/gzipped.txt",
|
543
|
+
# skip_decompress: true
|
544
|
+
#
|
545
|
+
def download path = nil, verify: :md5, encryption_key: nil,
|
546
|
+
skip_decompress: nil
|
513
547
|
ensure_service!
|
514
548
|
if path.nil?
|
515
549
|
path = StringIO.new
|
516
550
|
path.set_encoding "ASCII-8BIT"
|
517
551
|
end
|
518
|
-
file = service.download_file \
|
552
|
+
file, resp = service.download_file \
|
519
553
|
bucket, name, path, key: encryption_key, user_project: user_project
|
520
554
|
# FIX: downloading with encryption key will return nil
|
521
555
|
file ||= ::File.new(path)
|
522
556
|
verify_file! file, verify
|
557
|
+
if !skip_decompress &&
|
558
|
+
Array(resp.header["Content-Encoding"]).include?("gzip")
|
559
|
+
file = gzip_decompress file
|
560
|
+
end
|
561
|
+
file
|
523
562
|
end
|
524
563
|
|
525
564
|
##
|
@@ -774,7 +813,7 @@ module Google
|
|
774
813
|
def public_url protocol: :https
|
775
814
|
"#{protocol}://storage.googleapis.com/#{bucket}/#{name}"
|
776
815
|
end
|
777
|
-
|
816
|
+
alias url public_url
|
778
817
|
|
779
818
|
##
|
780
819
|
# Access without authentication can be granted to a File for a specified
|
@@ -977,7 +1016,7 @@ module Google
|
|
977
1016
|
@lazy = nil
|
978
1017
|
self
|
979
1018
|
end
|
980
|
-
|
1019
|
+
alias refresh! reload!
|
981
1020
|
|
982
1021
|
##
|
983
1022
|
# Determines whether the file exists in the Storage service.
|
@@ -1037,7 +1076,7 @@ module Google
|
|
1037
1076
|
##
|
1038
1077
|
# Raise an error unless an active service is available.
|
1039
1078
|
def ensure_service!
|
1040
|
-
|
1079
|
+
raise "Must have active connection" unless service
|
1041
1080
|
end
|
1042
1081
|
|
1043
1082
|
##
|
@@ -1056,12 +1095,12 @@ module Google
|
|
1056
1095
|
|
1057
1096
|
ensure_service!
|
1058
1097
|
|
1059
|
-
if attributes.include? :storage_class
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1098
|
+
@gapi = if attributes.include? :storage_class
|
1099
|
+
rewrite_gapi bucket, name, update_gapi
|
1100
|
+
else
|
1101
|
+
service.patch_file \
|
1102
|
+
bucket, name, update_gapi, user_project: user_project
|
1103
|
+
end
|
1065
1104
|
end
|
1066
1105
|
|
1067
1106
|
def gapi_from_attrs *attributes
|
@@ -1107,6 +1146,26 @@ module Google
|
|
1107
1146
|
file
|
1108
1147
|
end
|
1109
1148
|
|
1149
|
+
# @return [IO] Returns an IO object representing the file data. This
|
1150
|
+
# will either be a `::File` object referencing the local file
|
1151
|
+
# system or a StringIO instance.
|
1152
|
+
def gzip_decompress local_file
|
1153
|
+
if local_file.respond_to? :path
|
1154
|
+
gz = ::File.open(Pathname(local_file).to_path, "rb") do |f|
|
1155
|
+
Zlib::GzipReader.new(StringIO.new(f.read))
|
1156
|
+
end
|
1157
|
+
uncompressed_string = gz.read
|
1158
|
+
::File.open(Pathname(local_file).to_path, "w") do |f|
|
1159
|
+
f.write uncompressed_string
|
1160
|
+
f
|
1161
|
+
end
|
1162
|
+
else # local_file is StringIO
|
1163
|
+
local_file.rewind
|
1164
|
+
gz = Zlib::GzipReader.new StringIO.new(local_file.read)
|
1165
|
+
StringIO.new gz.read
|
1166
|
+
end
|
1167
|
+
end
|
1168
|
+
|
1110
1169
|
def storage_class_for str
|
1111
1170
|
return nil if str.nil?
|
1112
1171
|
{ "durable_reduced_availability" => "DURABLE_REDUCED_AVAILABILITY",
|
@@ -1,10 +1,10 @@
|
|
1
|
-
# Copyright 2015 Google
|
1
|
+
# Copyright 2015 Google LLC
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
5
5
|
# You may obtain a copy of the License at
|
6
6
|
#
|
7
|
-
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
8
|
#
|
9
9
|
# Unless required by applicable law or agreed to in writing, software
|
10
10
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -48,7 +48,7 @@ module Google
|
|
48
48
|
"project_private" => "projectPrivate",
|
49
49
|
"publicRead" => "publicRead",
|
50
50
|
"public" => "publicRead",
|
51
|
-
"public_read" => "publicRead" }
|
51
|
+
"public_read" => "publicRead" }.freeze
|
52
52
|
|
53
53
|
##
|
54
54
|
# A boolean value or a project ID string to indicate the project to
|
@@ -101,7 +101,7 @@ module Google
|
|
101
101
|
@owners = entities_from_acls acls, "OWNER"
|
102
102
|
@readers = entities_from_acls acls, "READER"
|
103
103
|
end
|
104
|
-
|
104
|
+
alias refresh! reload!
|
105
105
|
|
106
106
|
##
|
107
107
|
# Lists the owners of the file.
|
@@ -303,10 +303,10 @@ module Google
|
|
303
303
|
def auth!
|
304
304
|
update_predefined_acl! "authenticatedRead"
|
305
305
|
end
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
306
|
+
alias authenticatedRead! auth!
|
307
|
+
alias auth_read! auth!
|
308
|
+
alias authenticated! auth!
|
309
|
+
alias authenticated_read! auth!
|
310
310
|
|
311
311
|
##
|
312
312
|
# Convenience method to apply the `bucketOwnerFullControl` predefined
|
@@ -325,7 +325,7 @@ module Google
|
|
325
325
|
def owner_full!
|
326
326
|
update_predefined_acl! "bucketOwnerFullControl"
|
327
327
|
end
|
328
|
-
|
328
|
+
alias bucketOwnerFullControl! owner_full!
|
329
329
|
|
330
330
|
##
|
331
331
|
# Convenience method to apply the `bucketOwnerRead` predefined ACL
|
@@ -344,7 +344,7 @@ module Google
|
|
344
344
|
def owner_read!
|
345
345
|
update_predefined_acl! "bucketOwnerRead"
|
346
346
|
end
|
347
|
-
|
347
|
+
alias bucketOwnerRead! owner_read!
|
348
348
|
|
349
349
|
##
|
350
350
|
# Convenience method to apply the `private` predefined ACL
|
@@ -381,7 +381,7 @@ module Google
|
|
381
381
|
def project_private!
|
382
382
|
update_predefined_acl! "projectPrivate"
|
383
383
|
end
|
384
|
-
|
384
|
+
alias projectPrivate! project_private!
|
385
385
|
|
386
386
|
##
|
387
387
|
# Convenience method to apply the `publicRead` predefined ACL
|
@@ -400,8 +400,8 @@ module Google
|
|
400
400
|
def public!
|
401
401
|
update_predefined_acl! "publicRead"
|
402
402
|
end
|
403
|
-
|
404
|
-
|
403
|
+
alias publicRead! public!
|
404
|
+
alias public_read! public!
|
405
405
|
|
406
406
|
protected
|
407
407
|
|
@@ -1,10 +1,10 @@
|
|
1
|
-
# Copyright 2015 Google
|
1
|
+
# Copyright 2015 Google LLC
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
5
5
|
# You may obtain a copy of the License at
|
6
6
|
#
|
7
|
-
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
8
|
#
|
9
9
|
# Unless required by applicable law or agreed to in writing, software
|
10
10
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -182,7 +182,7 @@ module Google
|
|
182
182
|
##
|
183
183
|
# Raise an error unless an active service is available.
|
184
184
|
def ensure_service!
|
185
|
-
|
185
|
+
raise "Must have active connection" unless @service
|
186
186
|
end
|
187
187
|
end
|
188
188
|
end
|
@@ -1,10 +1,10 @@
|
|
1
|
-
# Copyright 2017 Google
|
1
|
+
# Copyright 2017 Google LLC
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
5
5
|
# You may obtain a copy of the License at
|
6
6
|
#
|
7
|
-
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
8
|
#
|
9
9
|
# Unless required by applicable law or agreed to in writing, software
|
10
10
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -14,6 +14,7 @@
|
|
14
14
|
|
15
15
|
|
16
16
|
require "base64"
|
17
|
+
require "cgi"
|
17
18
|
require "openssl"
|
18
19
|
require "google/cloud/storage/errors"
|
19
20
|
|
@@ -41,7 +42,10 @@ module Google
|
|
41
42
|
##
|
42
43
|
# The external path to the file.
|
43
44
|
def ext_path
|
44
|
-
|
45
|
+
escaped_path = String(@path).split("/").map do |node|
|
46
|
+
CGI.escape node
|
47
|
+
end.join("/")
|
48
|
+
"/#{CGI.escape @bucket}/#{escaped_path}"
|
45
49
|
end
|
46
50
|
|
47
51
|
##
|
@@ -53,7 +57,7 @@ module Google
|
|
53
57
|
def apply_option_defaults options
|
54
58
|
adjusted_expires = (Time.now.utc + (options[:expires] || 300)).to_i
|
55
59
|
options[:expires] = adjusted_expires
|
56
|
-
options[:method]
|
60
|
+
options[:method] ||= "GET"
|
57
61
|
options
|
58
62
|
end
|
59
63
|
|
@@ -81,12 +85,12 @@ module Google
|
|
81
85
|
}
|
82
86
|
|
83
87
|
p = options[:policy] || {}
|
84
|
-
|
88
|
+
raise "Policy must be given in a Hash" unless p.is_a? Hash
|
85
89
|
|
86
90
|
i = determine_issuer options
|
87
91
|
s = determine_signing_key options
|
88
92
|
|
89
|
-
|
93
|
+
raise SignedUrlUnavailable unless i && s
|
90
94
|
|
91
95
|
policy_str = p.to_json
|
92
96
|
policy = Base64.strict_encode64(policy_str).delete("\n")
|
@@ -106,7 +110,7 @@ module Google
|
|
106
110
|
i = determine_issuer options
|
107
111
|
s = determine_signing_key options
|
108
112
|
|
109
|
-
|
113
|
+
raise SignedUrlUnavailable unless i && s
|
110
114
|
|
111
115
|
sig = generate_signature s, signature_str(options)
|
112
116
|
generate_signed_url i, sig, options[:expires], options[:query]
|
@@ -136,7 +140,7 @@ module Google
|
|
136
140
|
|
137
141
|
def format_extension_headers headers
|
138
142
|
return "" if headers.nil?
|
139
|
-
|
143
|
+
raise "Headers must be given in a Hash" unless headers.is_a? Hash
|
140
144
|
flatten = headers.map do |key, value|
|
141
145
|
"#{key.to_s.downcase}:#{value.gsub(/\s+/, ' ')}\n"
|
142
146
|
end
|
@@ -1,10 +1,10 @@
|
|
1
|
-
# Copyright 2014 Google
|
1
|
+
# Copyright 2014 Google LLC
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
5
5
|
# You may obtain a copy of the License at
|
6
6
|
#
|
7
|
-
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
8
|
#
|
9
9
|
# Unless required by applicable law or agreed to in writing, software
|
10
10
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
@@ -30,17 +30,15 @@ module Google
|
|
30
30
|
def self.verify_md5! gcloud_file, local_file
|
31
31
|
gcloud_digest = gcloud_file.md5
|
32
32
|
local_digest = md5_for local_file
|
33
|
-
if gcloud_digest
|
34
|
-
|
35
|
-
end
|
33
|
+
return if gcloud_digest == local_digest
|
34
|
+
raise FileVerificationError.for_md5(gcloud_digest, local_digest)
|
36
35
|
end
|
37
36
|
|
38
37
|
def self.verify_crc32c! gcloud_file, local_file
|
39
38
|
gcloud_digest = gcloud_file.crc32c
|
40
39
|
local_digest = crc32c_for local_file
|
41
|
-
if gcloud_digest
|
42
|
-
|
43
|
-
end
|
40
|
+
return if gcloud_digest == local_digest
|
41
|
+
raise FileVerificationError.for_crc32c(gcloud_digest, local_digest)
|
44
42
|
end
|
45
43
|
|
46
44
|
def self.verify_md5 gcloud_file, local_file
|
@@ -58,7 +56,9 @@ module Google
|
|
58
56
|
end
|
59
57
|
else # StringIO
|
60
58
|
local_file.rewind
|
61
|
-
::Digest::MD5.base64digest local_file.read
|
59
|
+
md5 = ::Digest::MD5.base64digest local_file.read
|
60
|
+
local_file.rewind
|
61
|
+
md5
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
@@ -69,7 +69,9 @@ module Google
|
|
69
69
|
end
|
70
70
|
else # StringIO
|
71
71
|
local_file.rewind
|
72
|
-
::Digest::CRC32c.base64digest local_file.read
|
72
|
+
crc32c = ::Digest::CRC32c.base64digest local_file.read
|
73
|
+
local_file.rewind
|
74
|
+
crc32c
|
73
75
|
end
|
74
76
|
end
|
75
77
|
end
|