azure-storage-file 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,343 @@
1
+ # frozen_string_literal: true
2
+
3
+ #-------------------------------------------------------------------------
4
+ # # Copyright (c) Microsoft and contributors. All rights reserved.
5
+ #
6
+ # The MIT License(MIT)
7
+
8
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ # of this software and associated documentation files(the "Software"), to deal
10
+ # in the Software without restriction, including without limitation the rights
11
+ # to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
12
+ # copies of the Software, and to permit persons to whom the Software is
13
+ # furnished to do so, subject to the following conditions :
14
+
15
+ # The above copyright notice and this permission notice shall be included in
16
+ # all copies or substantial portions of the Software.
17
+
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
21
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ # THE SOFTWARE.
25
+ #--------------------------------------------------------------------------
26
+ require "azure/storage/file/file"
27
+
28
+ module Azure::Storage
29
+ include Azure::Storage::Common::Service
30
+ StorageService = Azure::Storage::Common::Service::StorageService
31
+
32
+ module File
33
+ class FileService < StorageService
34
+ include Azure::Storage::Common::Core::Utility
35
+ include Azure::Storage::File::Share
36
+ include Azure::Storage::File::Directory
37
+ include Azure::Storage::File
38
+
39
+ class << self
40
+ # Public: Creates an instance of [Azure::Storage::File::FileService]
41
+ #
42
+ # ==== Attributes
43
+ #
44
+ # * +options+ - Hash. Optional parameters.
45
+ #
46
+ # ==== Options
47
+ #
48
+ # Accepted key/value pairs in options parameter are:
49
+ #
50
+ # * +:use_development_storage+ - TrueClass|FalseClass. Whether to use storage emulator.
51
+ # * +:development_storage_proxy_uri+ - String. Used with +:use_development_storage+ if emulator is hosted other than localhost.
52
+ # * +:storage_account_name+ - String. The name of the storage account.
53
+ # * +:storage_access_key+ - Base64 String. The access key of the storage account.
54
+ # * +:storage_sas_token+ - String. The signed access signature for the storage account or one of its service.
55
+ # * +:storage_file_host+ - String. Specified File service endpoint or hostname
56
+ # * +:storage_dns_suffix+ - String. The suffix of a regional Storage Service, to
57
+ # * +:default_endpoints_protocol+ - String. http or https
58
+ # * +:use_path_style_uri+ - String. Whether use path style URI for specified endpoints
59
+ # * +:ca_file+ - String. File path of the CA file if having issue with SSL
60
+ # * +:user_agent_prefix+ - String. The user agent prefix that can identify the application calls the library
61
+ #
62
+ # The valid set of options include:
63
+ # * Storage Emulator: +:use_development_storage+ required, +:development_storage_proxy_uri+ optionally
64
+ # * Storage account name and key: +:storage_account_name+ and +:storage_access_key+ required, set +:storage_dns_suffix+ necessarily
65
+ # * Storage account name and SAS token: +:storage_account_name+ and +:storage_sas_token+ required, set +:storage_dns_suffix+ necessarily
66
+ # * Specified hosts and SAS token: At least one of the service host and SAS token. It's up to user to ensure the SAS token is suitable for the serivce
67
+ # * Anonymous File: only +:storage_file_host+, if it is to only access files within a container
68
+ #
69
+ # Additional notes:
70
+ # * Specified hosts can be set when use account name with access key or sas token
71
+ # * +:default_endpoints_protocol+ can be set if the scheme is not specified in hosts
72
+ # * Storage emulator always use path style URI
73
+ # * +:ca_file+ is independent.
74
+ #
75
+ # When empty options are given, it will try to read settings from Environment Variables. Refer to [Azure::Storage::Common::ClientOptions.env_vars_mapping] for the mapping relationship
76
+ #
77
+ # @return [Azure::Storage::File::FileService]
78
+ def create(options = {}, &block)
79
+ service_options = { client: Azure::Storage::Common::Client::create(options, &block), api_version: Azure::Storage::File::Default::STG_VERSION }
80
+ service_options[:user_agent_prefix] = options[:user_agent_prefix] if options[:user_agent_prefix]
81
+ Azure::Storage::File::FileService.new(service_options, &block)
82
+ end
83
+
84
+ # Public: Creates an instance of [Azure::Storage::File::FileService] with Storage Emulator
85
+ #
86
+ # ==== Attributes
87
+ #
88
+ # * +proxy_uri+ - String. Used with +:use_development_storage+ if emulator is hosted other than localhost.
89
+ #
90
+ # @return [Azure::Storage::File::FileService]
91
+ def create_development(proxy_uri = nil, &block)
92
+ service_options = { client: Azure::Storage::Common::Client::create_development(proxy_uri, &block), api_version: Azure::Storage::File::Default::STG_VERSION }
93
+ Azure::Storage::File::FileService.new(service_options, &block)
94
+ end
95
+
96
+ # Public: Creates an instance of [Azure::Storage::File::FileService] from Environment Variables
97
+ #
98
+ # @return [Azure::Storage::File::FileService]
99
+ def create_from_env(&block)
100
+ service_options = { client: Azure::Storage::Common::Client::create_from_env(&block), api_version: Azure::Storage::File::Default::STG_VERSION }
101
+ Azure::Storage::File::FileService.new(service_options, &block)
102
+ end
103
+
104
+ # Public: Creates an instance of [Azure::Storage::File::FileService] from Environment Variables
105
+ #
106
+ # ==== Attributes
107
+ #
108
+ # * +connection_string+ - String. Please refer to https://azure.microsoft.com/en-us/documentation/articles/storage-configure-connection-string/.
109
+ #
110
+ # @return [Azure::Storage::File::FileService]
111
+ def create_from_connection_string(connection_string, &block)
112
+ service_options = { client: Azure::Storage::Common::Client::create_from_connection_string(connection_string, &block), api_version: Azure::Storage::File::Default::STG_VERSION }
113
+ Azure::Storage::File::FileService.new(service_options, &block)
114
+ end
115
+ end
116
+
117
+ # Public: Initializes an instance of [Azure::Storage::File::FileService]
118
+ #
119
+ # ==== Attributes
120
+ #
121
+ # * +options+ - Hash. Optional parameters.
122
+ #
123
+ # ==== Options
124
+ #
125
+ # Accepted key/value pairs in options parameter are:
126
+ #
127
+ # * +:use_development_storage+ - TrueClass|FalseClass. Whether to use storage emulator.
128
+ # * +:development_storage_proxy_uri+ - String. Used with +:use_development_storage+ if emulator is hosted other than localhost.
129
+ # * +:storage_connection_string+ - String. The storage connection string.
130
+ # * +:storage_account_name+ - String. The name of the storage account.
131
+ # * +:storage_access_key+ - Base64 String. The access key of the storage account.
132
+ # * +:storage_sas_token+ - String. The signed access signature for the storage account or one of its service.
133
+ # * +:storage_file_host+ - String. Specified File serivce endpoint or hostname
134
+ # * +:storage_table_host+ - String. Specified Table serivce endpoint or hostname
135
+ # * +:storage_queue_host+ - String. Specified Queue serivce endpoint or hostname
136
+ # * +:storage_dns_suffix+ - String. The suffix of a regional Storage Serivce, to
137
+ # * +:default_endpoints_protocol+ - String. http or https
138
+ # * +:use_path_style_uri+ - String. Whether use path style URI for specified endpoints
139
+ # * +:ca_file+ - String. File path of the CA file if having issue with SSL
140
+ # * +:user_agent_prefix+ - String. The user agent prefix that can identify the application calls the library
141
+ # * +:client+ - Azure::Storage::Common::Client. The common client used to initalize the service.
142
+ #
143
+ # The valid set of options include:
144
+ # * Storage Emulator: +:use_development_storage+ required, +:development_storage_proxy_uri+ optionally
145
+ # * Storage account name and key: +:storage_account_name+ and +:storage_access_key+ required, set +:storage_dns_suffix+ necessarily
146
+ # * Storage account name and SAS token: +:storage_account_name+ and +:storage_sas_token+ required, set +:storage_dns_suffix+ necessarily
147
+ # * Specified hosts and SAS token: At least one of the service host and SAS token. It's up to user to ensure the SAS token is suitable for the serivce
148
+ # * Azure::Storage::Common::Client: The common client used to initalize the service. This client can be initalized and used repeatedly.
149
+ # * Anonymous File: only +:storage_file_host+, if it is to only access files within a container
150
+ #
151
+ # Additional notes:
152
+ # * Specified hosts can be set when use account name with access key or sas token
153
+ # * +:default_endpoints_protocol+ can be set if the scheme is not specified in hosts
154
+ # * Storage emulator always use path style URI
155
+ # * +:ca_file+ is independent.
156
+ #
157
+ # When empty options are given, it will try to read settings from Environment Variables. Refer to [Azure::Storage::Common::ClientOptions.env_vars_mapping] for the mapping relationship
158
+ def initialize(options = {}, &block)
159
+ service_options = options.clone
160
+ client_config = service_options[:client] ||= Azure::Storage::Common::Client::create(service_options, &block)
161
+ @user_agent_prefix = service_options[:user_agent_prefix] if service_options[:user_agent_prefix]
162
+ @api_version = service_options[:api_version] || Azure::Storage::File::Default::STG_VERSION
163
+ signer = service_options[:signer] || client_config.signer || Azure::Storage::Common::Core::Auth::SharedKey.new(client_config.storage_account_name, client_config.storage_access_key)
164
+ signer.api_ver = @api_version if signer.is_a? Azure::Storage::Common::Core::Auth::SharedAccessSignatureSigner
165
+ super(signer, client_config.storage_account_name, service_options, &block)
166
+ @storage_service_host[:primary] = client.storage_file_host
167
+ @storage_service_host[:secondary] = client.storage_file_host true
168
+ end
169
+
170
+ def call(method, uri, body = nil, headers = {}, options = {})
171
+ content_type = get_or_apply_content_type(body, headers[Azure::Storage::Common::HeaderConstants::FILE_CONTENT_TYPE])
172
+ headers[Azure::Storage::Common::HeaderConstants::FILE_CONTENT_TYPE] = content_type if content_type
173
+ headers["x-ms-version"] = @api_version ? @api_version : Default::STG_VERSION
174
+ headers["User-Agent"] = @user_agent_prefix ? "#{@user_agent_prefix}; #{Default::USER_AGENT}" : Default::USER_AGENT
175
+
176
+ response = super
177
+
178
+ # Force the response.body to the content charset of specified in the header.
179
+ # Content-Type is echo'd back for the file and is used to store the encoding of the octet stream
180
+ if !response.nil? && !response.body.nil? && response.headers["Content-Type"]
181
+ charset = parse_charset_from_content_type(response.headers["Content-Type"])
182
+ response.body.force_encoding(charset) if charset && charset.length > 0
183
+ end
184
+
185
+ response
186
+ end
187
+
188
+ # Public: Get a list of Shares from the server.
189
+ #
190
+ # ==== Attributes
191
+ #
192
+ # * +options+ - Hash. Optional parameters.
193
+ #
194
+ # ==== Options
195
+ #
196
+ # Accepted key/value pairs in options parameter are:
197
+ # * +:prefix+ - String. Filters the results to return only shares
198
+ # whose name begins with the specified prefix. (optional)
199
+ #
200
+ # * +:marker+ - String. An identifier the specifies the portion of the
201
+ # list to be returned. This value comes from the property
202
+ # Azure::Storage::Common::EnumerationResults.continuation_token when there
203
+ # are more shares available than were returned. The
204
+ # marker value may then be used here to request the next set
205
+ # of list items. (optional)
206
+ #
207
+ # * +:max_results+ - Integer. Specifies the maximum number of shares to return.
208
+ # If max_results is not specified, or is a value greater than
209
+ # 5,000, the server will return up to 5,000 items. If it is set
210
+ # to a value less than or equal to zero, the server will return
211
+ # status code 400 (Bad Request). (optional)
212
+ #
213
+ # * +:metadata+ - Boolean. Specifies whether or not to return the share metadata.
214
+ # (optional, Default=false)
215
+ #
216
+ # * +:timeout+ - Integer. A timeout in seconds.
217
+ #
218
+ # * +:request_id+ - String. Provides a client-generated, opaque value with a 1 KB character limit that is recorded
219
+ # in the analytics logs when storage analytics logging is enabled.
220
+ #
221
+ # * +:location_mode+ - LocationMode. Specifies the location mode used to decide
222
+ # which location the request should be sent to.
223
+ #
224
+ # See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/list-shares
225
+ #
226
+ # Returns an Azure::Storage::Common::EnumerationResults
227
+ #
228
+ def list_shares(options = {})
229
+ query = {}
230
+ if options
231
+ StorageService.with_query query, "prefix", options[:prefix]
232
+ StorageService.with_query query, "marker", options[:marker]
233
+ StorageService.with_query query, "maxresults", options[:max_results].to_s if options[:max_results]
234
+ StorageService.with_query query, "include", "metadata" if options[:metadata] == true
235
+ StorageService.with_query query, "timeout", options[:timeout].to_s if options[:timeout]
236
+ end
237
+
238
+ options[:request_location_mode] = Azure::Storage::Common::RequestLocationMode::PRIMARY_OR_SECONDARY
239
+ uri = shares_uri(query, options)
240
+ response = call(:get, uri, nil, {}, options)
241
+
242
+ Serialization.share_enumeration_results_from_xml(response.body)
243
+ end
244
+
245
+ # Protected: Generate the URI for the collection of shares.
246
+ #
247
+ # ==== Attributes
248
+ #
249
+ # * +query+ - A Hash of key => value query parameters.
250
+ #
251
+ # Returns a URI.
252
+ #
253
+ protected
254
+ def shares_uri(query = {}, options = {})
255
+ query = { "comp" => "list" }.merge(query)
256
+ generate_uri("", query, options)
257
+ end
258
+
259
+ # Protected: Generate the URI for a specific share.
260
+ #
261
+ # ==== Attributes
262
+ #
263
+ # * +name+ - The share name. If this is a URI, we just return this.
264
+ # * +query+ - A Hash of key => value query parameters.
265
+ #
266
+ # Returns a URI.
267
+ #
268
+ protected
269
+ def share_uri(name, query = {}, options = {})
270
+ return name if name.kind_of? ::URI
271
+ query = { restype: "share" }.merge(query)
272
+ generate_uri(name, query, options)
273
+ end
274
+
275
+ # Protected: Generate the URI for a specific directory.
276
+ #
277
+ # ==== Attributes
278
+ #
279
+ # * +share+ - String representing the name of the share.
280
+ # * +directory_path+ - String representing the path to the directory.
281
+ # * +directory+ - String representing the name to the directory.
282
+ # * +query+ - A Hash of key => value query parameters.
283
+ #
284
+ # Returns a URI.
285
+ #
286
+ protected
287
+ def directory_uri(share, directory_path, query = {}, options = {})
288
+ path = directory_path.nil? ? share : ::File.join(share, directory_path)
289
+ query = { restype: "directory" }.merge(query)
290
+ options = { encode: true }.merge(options)
291
+ generate_uri(path, query, options)
292
+ end
293
+
294
+ # Protected: Generate the URI for a specific file.
295
+ #
296
+ # ==== Attributes
297
+ #
298
+ # * +share+ - String representing the name of the share.
299
+ # * +directory_path+ - String representing the path to the directory.
300
+ # * +file+ - String representing the name to the file.
301
+ # * +query+ - A Hash of key => value query parameters.
302
+ #
303
+ # Returns a URI.
304
+ #
305
+ protected
306
+ def file_uri(share, directory_path, file, query = {}, options = {})
307
+ if directory_path.nil?
308
+ path = ::File.join(share, file)
309
+ else
310
+ path = ::File.join(share, directory_path, file)
311
+ end
312
+ options = { encode: true }.merge(options)
313
+ generate_uri(path, query, options)
314
+ end
315
+
316
+ # Get the content type according to the content type header and request body.
317
+ #
318
+ # headers - The request body
319
+ # content_type - The request content type
320
+ protected
321
+ def get_or_apply_content_type(body, content_type = nil)
322
+ unless body.nil?
323
+ if (body.is_a? String) && body.encoding.to_s != "ASCII_8BIT" && !body.empty?
324
+ if content_type.nil?
325
+ content_type = "text/plain; charset=#{body.encoding}"
326
+ else
327
+ # Force the request.body to the content encoding of specified in the header
328
+ charset = parse_charset_from_content_type(content_type)
329
+ body.force_encoding(charset) if charset
330
+ end
331
+ else
332
+ # It is either that the body is not a string, or that the body's encoding is ASCII_8BIT, which is a binary
333
+ # In this case, set the content type to be default content-type
334
+ content_type = Default::CONTENT_TYPE_VALUE unless content_type
335
+ end
336
+ end
337
+ content_type
338
+ end
339
+ end
340
+ end
341
+ end
342
+
343
+ Azure::Storage::FileService = Azure::Storage::File::FileService
@@ -0,0 +1,228 @@
1
+ # frozen_string_literal: true
2
+
3
+ #-------------------------------------------------------------------------
4
+ # # Copyright (c) Microsoft and contributors. All rights reserved.
5
+ #
6
+ # The MIT License(MIT)
7
+
8
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ # of this software and associated documentation files(the "Software"), to deal
10
+ # in the Software without restriction, including without limitation the rights
11
+ # to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
12
+ # copies of the Software, and to permit persons to whom the Software is
13
+ # furnished to do so, subject to the following conditions :
14
+
15
+ # The above copyright notice and this permission notice shall be included in
16
+ # all copies or substantial portions of the Software.
17
+
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
21
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ # THE SOFTWARE.
25
+ #--------------------------------------------------------------------------
26
+ require "azure/storage/common/service/serialization"
27
+
28
+ module Azure::Storage
29
+ module File
30
+ module Serialization
31
+ include Azure::Storage::Common::Service::Serialization
32
+
33
+ def self.share_enumeration_results_from_xml(xml)
34
+ xml = slopify(xml)
35
+ expect_node("EnumerationResults", xml)
36
+
37
+ results = enumeration_results_from_xml(xml, Azure::Storage::Common::Service::EnumerationResults.new)
38
+
39
+ return results unless (xml > "Shares").any? && ((xml > "Shares") > "Share").any?
40
+
41
+ if xml.Shares.Share.count == 0
42
+ results.push(share_from_xml(xml.Shares.Share))
43
+ else
44
+ xml.Shares.Share.each { |share_node|
45
+ results.push(share_from_xml(share_node))
46
+ }
47
+ end
48
+
49
+ results
50
+ end
51
+
52
+ def self.share_from_xml(xml)
53
+ xml = slopify(xml)
54
+ expect_node("Share", xml)
55
+
56
+ Share::Share.new do |share|
57
+ share.name = xml.Name.text if (xml > "Name").any?
58
+ share.properties = share_properties_from_xml(xml.Properties) if (xml > "Properties").any?
59
+ share.metadata = metadata_from_xml(xml.Metadata) if (xml > "Metadata").any?
60
+ end
61
+ end
62
+
63
+ def self.share_properties_from_xml(xml)
64
+ xml = slopify(xml)
65
+ expect_node("Properties", xml)
66
+
67
+ props = {}
68
+ props[:last_modified] = (xml > "Last-Modified").text if (xml > "Last-Modified").any?
69
+ props[:etag] = xml.ETag.text if (xml > "ETag").any?
70
+ props[:quota] = xml.Quota.text if (xml > "Quota").any?
71
+ props
72
+ end
73
+
74
+ def self.share_from_headers(headers)
75
+ Share::Share.new do |share|
76
+ share.properties = share_properties_from_headers(headers)
77
+ share.quota = quota_from_headers(headers)
78
+ share.metadata = metadata_from_headers(headers)
79
+ end
80
+ end
81
+
82
+ def self.share_properties_from_headers(headers)
83
+ props = {}
84
+ props[:last_modified] = headers["Last-Modified"]
85
+ props[:etag] = headers["ETag"]
86
+ props
87
+ end
88
+
89
+ def self.quota_from_headers(headers)
90
+ headers["x-ms-share-quota"] ? headers["x-ms-share-quota"].to_i : nil
91
+ end
92
+
93
+ def self.share_stats_from_xml(xml)
94
+ xml = slopify(xml)
95
+ expect_node("ShareStats", xml)
96
+ xml.ShareUsage.text.to_i
97
+ end
98
+
99
+ def self.directories_and_files_enumeration_results_from_xml(xml)
100
+ xml = slopify(xml)
101
+ expect_node("EnumerationResults", xml)
102
+
103
+ results = enumeration_results_from_xml(xml, Azure::Storage::Common::Service::EnumerationResults.new)
104
+
105
+ return results unless (xml > "Entries").any?
106
+
107
+ if ((xml > "Entries") > "File").any?
108
+ if xml.Entries.File.count == 0
109
+ results.push(file_from_xml(xml.Entries.File))
110
+ else
111
+ xml.Entries.File.each { |file_node|
112
+ results.push(file_from_xml(file_node))
113
+ }
114
+ end
115
+ end
116
+
117
+ if ((xml > "Entries") > "Directory").any?
118
+ if xml.Entries.Directory.count == 0
119
+ results.push(directory_from_xml(xml.Entries.Directory))
120
+ else
121
+ xml.Entries.Directory.each { |directory_node|
122
+ results.push(directory_from_xml(directory_node))
123
+ }
124
+ end
125
+ end
126
+
127
+ results
128
+ end
129
+
130
+ def self.file_from_xml(xml)
131
+ xml = slopify(xml)
132
+ expect_node("File", xml)
133
+
134
+ File.new do |file|
135
+ file.name = xml.Name.text if (xml > "Name").any?
136
+ file.properties = file_properties_from_xml(xml.Properties) if (xml > "Properties").any?
137
+ end
138
+ end
139
+
140
+ def self.file_properties_from_xml(xml)
141
+ xml = slopify(xml)
142
+ expect_node("Properties", xml)
143
+
144
+ props = {}
145
+ props[:content_length] = (xml > "Content-Length").text.to_i if (xml > "Content-Length").any?
146
+ props
147
+ end
148
+
149
+ def self.directory_from_xml(xml)
150
+ xml = slopify(xml)
151
+ expect_node("Directory", xml)
152
+
153
+ Directory::Directory.new do |directory|
154
+ directory.name = xml.Name.text if (xml > "Name").any?
155
+ end
156
+ end
157
+
158
+ def self.directory_from_headers(headers)
159
+ Directory::Directory.new do |directory|
160
+ directory.properties = directory_properties_from_headers(headers)
161
+ directory.metadata = metadata_from_headers(headers)
162
+ end
163
+ end
164
+
165
+ def self.directory_properties_from_headers(headers)
166
+ props = {}
167
+ props[:last_modified] = headers["Last-Modified"]
168
+ props[:etag] = headers["ETag"]
169
+ props
170
+ end
171
+
172
+ def self.file_from_headers(headers)
173
+ File.new do |file|
174
+ file.properties = file_properties_from_headers(headers)
175
+ file.metadata = metadata_from_headers(headers)
176
+ end
177
+ end
178
+
179
+ def self.file_properties_from_headers(headers)
180
+ props = {}
181
+
182
+ props[:last_modified] = headers["Last-Modified"]
183
+ props[:etag] = headers["ETag"]
184
+ props[:type] = headers["x-ms-type"]
185
+
186
+ props[:content_length] = headers["Content-Length"].to_i unless headers["Content-Length"].nil?
187
+ props[:content_length] = headers["x-ms-content-length"].to_i unless headers["x-ms-content-length"].nil?
188
+
189
+ props[:content_type] = headers["Content-Type"]
190
+ props[:content_encoding] = headers["Content-Encoding"]
191
+ props[:content_language] = headers["Content-Language"]
192
+ props[:content_disposition] = headers["Content-Disposition"]
193
+ props[:content_md5] = headers["x-ms-content-md5"] || headers["Content-MD5"]
194
+ props[:range_md5] = headers["Content-MD5"] if headers["x-ms-content-md5"] && headers["Content-MD5"]
195
+ props[:cache_control] = headers["Cache-Control"]
196
+
197
+ props[:copy_id] = headers["x-ms-copy-id"]
198
+ props[:copy_status] = headers["x-ms-copy-status"]
199
+ props[:copy_source] = headers["x-ms-copy-source"]
200
+ props[:copy_progress] = headers["x-ms-copy-progress"]
201
+ props[:copy_completion_time] = headers["x-ms-copy-completion-time"]
202
+ props[:copy_status_description] = headers["x-ms-copy-status-description"]
203
+
204
+ props[:accept_ranges] = headers["Accept-Ranges"].to_i if headers["Accept-Ranges"]
205
+
206
+ props
207
+ end
208
+
209
+ def self.range_list_from_xml(xml)
210
+ xml = slopify(xml)
211
+ expect_node("Ranges", xml)
212
+
213
+ range_list = []
214
+ return range_list unless (xml > "Range").any?
215
+
216
+ if xml.Range.count == 0
217
+ range_list.push [xml.Range.Start.text.to_i, xml.Range.End.text.to_i]
218
+ else
219
+ xml.Range.each { |range|
220
+ range_list.push [range.Start.text.to_i, range.End.text.to_i]
221
+ }
222
+ end
223
+
224
+ range_list
225
+ end
226
+ end
227
+ end
228
+ end