fog-aliyun 0.3.18 → 0.3.19

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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -1
  3. data/fog-aliyun.gemspec +2 -2
  4. data/lib/fog/aliyun/models/storage/directories.rb +30 -53
  5. data/lib/fog/aliyun/models/storage/directory.rb +96 -17
  6. data/lib/fog/aliyun/models/storage/file.rb +127 -126
  7. data/lib/fog/aliyun/models/storage/files.rb +48 -127
  8. data/lib/fog/aliyun/requests/storage/abort_multipart_upload.rb +22 -0
  9. data/lib/fog/aliyun/requests/storage/complete_multipart_upload.rb +21 -0
  10. data/lib/fog/aliyun/requests/storage/copy_object.rb +14 -18
  11. data/lib/fog/aliyun/requests/storage/delete_bucket.rb +3 -9
  12. data/lib/fog/aliyun/requests/storage/delete_multiple_objects.rb +20 -0
  13. data/lib/fog/aliyun/requests/storage/delete_object.rb +10 -11
  14. data/lib/fog/aliyun/requests/storage/get_bucket.rb +22 -99
  15. data/lib/fog/aliyun/requests/storage/get_bucket_location.rb +33 -0
  16. data/lib/fog/aliyun/requests/storage/get_object.rb +20 -12
  17. data/lib/fog/aliyun/requests/storage/get_object_acl.rb +30 -0
  18. data/lib/fog/aliyun/requests/storage/get_object_http_url.rb +6 -9
  19. data/lib/fog/aliyun/requests/storage/get_object_https_url.rb +6 -9
  20. data/lib/fog/aliyun/requests/storage/get_service.rb +13 -0
  21. data/lib/fog/aliyun/requests/storage/head_object.rb +25 -13
  22. data/lib/fog/aliyun/requests/storage/initiate_multipart_upload.rb +19 -0
  23. data/lib/fog/aliyun/requests/storage/list_buckets.rb +5 -25
  24. data/lib/fog/aliyun/requests/storage/list_objects.rb +10 -62
  25. data/lib/fog/aliyun/requests/storage/put_bucket.rb +2 -8
  26. data/lib/fog/aliyun/requests/storage/put_object.rb +16 -122
  27. data/lib/fog/aliyun/requests/storage/upload_part.rb +24 -0
  28. data/lib/fog/aliyun/storage.rb +20 -4
  29. data/lib/fog/aliyun/version.rb +1 -1
  30. metadata +14 -10
  31. data/lib/fog/aliyun/requests/storage/delete_container.rb +0 -30
  32. data/lib/fog/aliyun/requests/storage/get_container.rb +0 -57
  33. data/lib/fog/aliyun/requests/storage/get_containers.rb +0 -61
  34. data/lib/fog/aliyun/requests/storage/put_container.rb +0 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cff686947955adaead48c9bb70ec405a2ca807240ba53d7e86fe1da27b63ab4f
4
- data.tar.gz: bec5610976802d1cefccbe9f8788943747cfe31059cf0d6b04d0ae589caace46
3
+ metadata.gz: 5f527fdb37f55f3f14e68b37fa7d038ba1b351e798d25bc761e18af8e2543c82
4
+ data.tar.gz: c7b4cdb85fe538f7934eafe57e4108d8fd411084fb3bb3cdd7c1cd0a28714923
5
5
  SHA512:
6
- metadata.gz: 7bb3975226a2af2d130be85b64f057d61cab8564bd6ef9fc349080efa0008d79dff5149a3124142436bcf30ef893314e27d195b8706c9bc6c634d88c5452a33b
7
- data.tar.gz: e21803702da802d2a3051c63428af92b0fb66c9f8a95f62c2c9c76a31ea80fa6ae0ee74081c9ee72d5c0570dae0f60c2764f8094d9692c39f8d93e1ade320ea0
6
+ metadata.gz: ef5fcc535228173a33fc33294c132ca1e6153bee3e9ac15c9626c06313eaba4e7e0e6a9f88457198e872042ee4a14697d040d772ec6e43203e891c89ee591fb4
7
+ data.tar.gz: 19054a55dd975915129f3e7cc0893e81aae38a844bc4d04363de0daf2d0cad484ea1cb62496f0813cb466d7689c65d1a3773a75c597ccbb7f5fcc30073e40d7b
@@ -1,4 +1,10 @@
1
- ## 0.3.19 (Unreleased)
1
+ ## 0.3.20 (Unreleased)
2
+ ## 0.3.19 (August 17, 2020)
3
+
4
+ IMPROVEMENTS:
5
+
6
+ - Upgrade oss ruby sdk to support setting log level [GH-152](https://github.com/fog/fog-aliyun/pull/152)
7
+
2
8
  ## 0.3.18 (August 03, 2020)
3
9
 
4
10
  IMPROVEMENTS:
@@ -28,9 +28,9 @@ Gem::Specification.new do |spec|
28
28
  spec.add_development_dependency 'rubocop'
29
29
  spec.add_development_dependency 'simplecov'
30
30
  spec.add_development_dependency 'memory_profiler'
31
- spec.add_development_dependency 'aliyun-sdk', '~> 0.7.3'
31
+ spec.add_development_dependency 'aliyun-sdk', '~> 0.8.0'
32
32
 
33
- spec.add_dependency 'aliyun-sdk', '~> 0.7.3'
33
+ spec.add_dependency 'aliyun-sdk', '~> 0.8.0'
34
34
  spec.add_dependency 'fog-core'
35
35
  spec.add_dependency 'fog-json'
36
36
  spec.add_dependency 'ipaddress', '~> 0.8'
@@ -10,70 +10,47 @@ module Fog
10
10
  model Fog::Aliyun::Storage::Directory
11
11
 
12
12
  def all
13
- containers = service.get_containers
14
- return nil if containers.nil?
13
+ buckets = service.get_service[0]
14
+ return nil if buckets.size < 1
15
15
  data = []
16
16
  i = 0
17
- containers.each do |entry|
18
- key = entry['Prefix'][0]
19
- key[-1] = ''
20
- data[i] = { key: key }
17
+ buckets.each do |b|
18
+ data[i] = { key: b.name }
21
19
  i += 1
22
20
  end
23
-
24
21
  load(data)
25
22
  end
26
23
 
27
- # get method used to get a specified directory.
28
- # If the directory is not exist, this method will create a new with 'key'
29
- # In order to support multi-buckets scenario which making bucket as a solo directory, it have been expanded.
30
- # If key is a directory(including /), return an existed or a new one;
31
- # If key does not contain /, if bucket, return '', else return an existed or a new one directory;
24
+
32
25
  def get(key, options = {})
33
- if key.is_a? Array
34
- key = key[0]
26
+ data = service.get_bucket(key, options)
27
+
28
+ directory = new(:key => key, :is_persisted => true)
29
+
30
+ options = data[1]
31
+ options[:max_keys] = options[:limit]
32
+ directory.files.merge_attributes(options)
33
+
34
+ objects = []
35
+ i = 0
36
+ data[0].each do |o|
37
+ objects[i] = {
38
+ 'Key' => o.key,
39
+ 'Type' => o.type,
40
+ 'Size' => o.size,
41
+ 'ETag' => o.etag,
42
+ 'LastModified' => o.last_modified
43
+ }
44
+ i += 1
35
45
  end
36
- if !key.nil? && key != '' && key != '.'
37
- key = key.chomp('/')
38
- if key.include? '/'
39
- dir = key + '/'
40
- ret = service.head_object(dir, options)
41
- new(key: key) if ret.data[:status] == 200
42
- else
43
- remap_attributes(options, {
44
- :delimiter => 'delimiter',
45
- :marker => 'marker',
46
- :max_keys => 'max-keys',
47
- :prefix => 'prefix'
48
- })
49
- data = service.get_bucket(key, options)
50
- directory = new(:key => data['Name'], :is_persisted => true)
51
- options = {}
52
- for k, v in data
53
- if ['CommonPrefixes', 'Delimiter', 'IsTruncated', 'Marker', 'MaxKeys', 'Prefix'].include?(k)
54
- # Sometimes, the v will be a Array, like "Name"=>["blobstore-droplet1"], "Prefix"=>[{}], "Marker"=>[{}], "MaxKeys"=>["100"], "Delimiter"=>[{}], "IsTruncated"=>["false"]
55
- # and there needs to parse them
56
- if !v.nil? && (v.is_a? Array) && (v.size > 0)
57
- if v[0].is_a? Hash
58
- v = nil
59
- else
60
- v = v[0]
61
- end
62
- end
63
- options[k] = v
64
- end
65
- end
66
- directory.files.merge_attributes(options)
67
- if data.key?('Contents') && !data['Contents'].nil?
68
- directory.files.load(data['Contents'])
69
- end
70
- directory
71
- end
46
+ directory.files.load(objects)
47
+ directory
48
+ rescue AliyunOssSdk::ServerError => error
49
+ if error.error_code == "NoSuchBucket"
50
+ nil
72
51
  else
73
- new(key: '')
52
+ raise(error)
74
53
  end
75
- rescue Fog::Aliyun::Storage::NotFound
76
- nil
77
54
  end
78
55
  end
79
56
  end
@@ -7,24 +7,62 @@ module Fog
7
7
  module Aliyun
8
8
  class Storage
9
9
  class Directory < Fog::Model
10
+ VALID_ACLS = ['private', 'public-read', 'public-read-write']
11
+
12
+ attr_reader :acl
10
13
  identity :key, :aliases => ['Key', 'Name', 'name']
11
14
 
15
+ attribute :creation_date, :aliases => 'CreationDate', :type => 'time'
16
+
17
+ def acl=(new_acl)
18
+ unless VALID_ACLS.include?(new_acl)
19
+ raise ArgumentError.new("acl must be one of [#{VALID_ACLS.join(', ')}]")
20
+ else
21
+ @acl = new_acl
22
+ end
23
+ end
24
+
12
25
  def destroy
13
26
  requires :key
14
- prefix = key + '/'
15
- ret = service.list_objects(prefix: prefix)['Contents']
16
-
17
- if ret.nil?
27
+ service.delete_bucket(key)
28
+ true
29
+ rescue AliyunOssSdk::ServerError => error
30
+ if error.error_code == "NoSuchBucket"
18
31
  false
19
- elsif ret.size == 1
20
- service.delete_container(key)
21
- true
22
32
  else
23
- raise Fog::Aliyun::Storage::Error, ' Forbidden: Direction not empty!'
33
+ raise(error)
34
+ end
35
+ end
36
+
37
+ def destroy!(options = {})
38
+ requires :key
39
+ options = {
40
+ timeout: Fog.timeout,
41
+ interval: Fog.interval,
42
+ }.merge(options)
43
+
44
+ begin
45
+ clear!
46
+ Fog.wait_for(options[:timeout], options[:interval]) { objects_keys.size == 0 }
47
+ service.delete_bucket(key)
48
+ true
49
+ rescue AliyunOssSdk::ServerError
24
50
  false
25
51
  end
26
52
  end
27
53
 
54
+ def location
55
+ region = @aliyun_region_id
56
+ region ||= Storage::DEFAULT_REGION
57
+ @location = (bucket_location || 'oss-' + region)
58
+ end
59
+
60
+ # NOTE: you can't change the region once the bucket is created
61
+ def location=(new_location)
62
+ new_location = 'oss-' + new_location unless new_location.start_with?('oss-')
63
+ @location = new_location
64
+ end
65
+
28
66
  def files
29
67
  @files ||= begin
30
68
  Fog::Aliyun::Storage::Files.new(
@@ -34,6 +72,12 @@ module Fog
34
72
  end
35
73
  end
36
74
 
75
+ # TODO
76
+ def public=(new_public)
77
+ nil
78
+ end
79
+
80
+ # TODO
37
81
  def public_url
38
82
  nil
39
83
  end
@@ -41,18 +85,53 @@ module Fog
41
85
  def save
42
86
  requires :key
43
87
 
44
- # Checking whether the key is a bucket and meet the multi-bucket scenario.
45
- # If the key is a existing bucket, return it directly.
46
- key = key.chomp('/')
47
- if !key.nil? && key != '' && key != '.' && !(key.include? '/')
48
- data = service.get_bucket(key)
49
- if data.class == Hash && data.key?('Code') && !data['Code'].nil? && !data['Code'].empty?
50
- service.put_container(key)
51
- end
52
- end
88
+ options = {}
89
+
90
+ options['x-oss-acl'] = acl if acl
91
+
92
+ # https://help.aliyun.com/document_detail/31959.html
93
+ # if !persisted?
94
+ # # There is a sdk bug that location can not be set
95
+ # options[:location] = location
96
+ # end
97
+
98
+ service.put_bucket(key, options)
99
+ attributes[:is_persisted] = true
53
100
 
54
101
  true
55
102
  end
103
+
104
+ def persisted?
105
+ # is_persisted is true in case of directories.get or after #save
106
+ # creation_date is set in case of directories.all
107
+ attributes[:is_persisted] || !!attributes[:creation_date]
108
+ end
109
+
110
+ private
111
+
112
+ def bucket_location
113
+ requires :key
114
+ return nil unless persisted?
115
+ service.get_bucket_location(key)
116
+ end
117
+
118
+ def objects_keys
119
+ requires :key
120
+ bucket_query = service.get_bucket(key)
121
+
122
+ object_keys = []
123
+ i = 0
124
+ bucket_query[0].each do |o|
125
+ object_keys[i] = o.key
126
+ i += 1
127
+ end
128
+ object_keys
129
+ end
130
+
131
+ def clear!
132
+ requires :key
133
+ service.delete_multiple_objects(key, objects_keys) if objects_keys.size > 0
134
+ end
56
135
  end
57
136
  end
58
137
  end
@@ -7,77 +7,123 @@ module Fog
7
7
  class Storage
8
8
  class File < Fog::Model
9
9
  identity :key, aliases: ['Key', 'Name', 'name']
10
+
11
+ attr_writer :body
12
+ attribute :cache_control, aliases: 'Cache-Control'
13
+ attribute :content_encoding, aliases: 'Content-Encoding'
10
14
  attribute :date, aliases: 'Date'
11
- attribute :content_length, aliases: 'Content-Length', type: :integer
15
+ attribute :content_length, aliases: ['Content-Length', 'Size'], type: :integer
16
+ attribute :content_md5, aliases: 'Content-MD5'
12
17
  attribute :content_type, aliases: 'Content-Type'
13
18
  attribute :connection, aliases: 'Connection'
14
19
  attribute :content_disposition, aliases: 'Content-Disposition'
15
- attribute :etag, aliases: 'Etag'
20
+ attribute :etag, aliases: ['Etag', 'ETag']
21
+ attribute :expires, aliases: 'Expires'
22
+ attribute :metadata
23
+ attribute :owner, aliases: 'Owner'
16
24
  attribute :last_modified, aliases: 'Last-Modified', type: :time
17
25
  attribute :accept_ranges, aliases: 'Accept-Ranges'
18
26
  attribute :server, aliases: 'Server'
19
- attribute :object_type, aliases: 'x-oss-object-type'
27
+ attribute :object_type, aliases: ['x-oss-object-type', 'x_oss_object_type']
28
+
29
+ # @note Chunk size to use for multipart uploads.
30
+ # Use small chunk sizes to minimize memory. E.g. 5242880 = 5mb
31
+ attr_reader :multipart_chunk_size
32
+ def multipart_chunk_size=(mp_chunk_size)
33
+ raise ArgumentError.new("minimum multipart_chunk_size is 5242880") if mp_chunk_size < 5242880
34
+ @multipart_chunk_size = mp_chunk_size
35
+ end
36
+
37
+ def acl
38
+ requires :directory, :key
39
+ service.get_object_acl(directory.key, key)
40
+ end
41
+
42
+ def acl=(new_acl)
43
+ valid_acls = ['private', 'public-read', 'public-read-write', 'default']
44
+ unless valid_acls.include?(new_acl)
45
+ raise ArgumentError.new("acl must be one of [#{valid_acls.join(', ')}]")
46
+ end
47
+ @acl = new_acl
48
+ end
20
49
 
21
50
  def body
22
- attributes[:body] ||=
23
- if last_modified
24
- collection.get(identity).body
25
- else
26
- ''
27
- end
51
+ return attributes[:body] if attributes[:body]
52
+ return '' unless last_modified
53
+
54
+ file = collection.get(identity)
55
+ if file
56
+ attributes[:body] = file.body
57
+ else
58
+ attributes[:body] = ''
59
+ end
28
60
  end
29
61
 
30
62
  def body=(new_body)
31
63
  attributes[:body] = new_body
32
64
  end
33
65
 
34
- attr_reader :directory
66
+ def directory
67
+ @directory
68
+ end
35
69
 
70
+ # Copy object from one bucket to other bucket.
71
+ #
72
+ # required attributes: directory, key
73
+ #
74
+ # @param target_directory_key [String]
75
+ # @param target_file_key [String]
76
+ # @param options [Hash] options for copy_object method
77
+ # @return [String] Fog::Aliyun::Files#head status of directory contents
78
+ #
36
79
  def copy(target_directory_key, target_file_key, options = {})
37
80
  requires :directory, :key
38
- source_bucket, directory_key = collection.check_directory_key(directory.key)
39
- source_object = if directory_key == ''
40
- key
41
- else
42
- directory_key + '/' + key
43
- end
44
- target_bucket, target_directory_key = collection.check_directory_key(target_directory_key)
45
- target_object = if target_directory_key == ''
46
- target_file_key
47
- else
48
- target_directory_key + '/' + target_file_key
49
- end
50
- service.copy_object(source_bucket, source_object, target_bucket, target_object, options)
51
- target_directory = service.directories.new(key: target_directory_key)
52
- target_directory.files.get(target_file_key)
53
- end
54
-
55
- def destroy
81
+ service.copy_object(directory.key, key, target_directory_key, target_file_key, options)
82
+ target_directory = service.directories.new(:key => target_directory_key)
83
+ target_directory.files.head(target_file_key)
84
+ end
85
+
86
+ def destroy(options = {})
56
87
  requires :directory, :key
57
- bucket_name, directory_key = collection.check_directory_key(directory.key)
58
- object = if directory_key == ''
59
- key
60
- else
61
- directory_key + '/' + key
62
- end
63
- service.delete_object(object, bucket: bucket_name)
88
+ # TODO support versionId
89
+ # attributes[:body] = nil if options['versionId'] == version
90
+ service.delete_object(directory.key, key, options)
64
91
  true
65
92
  end
66
93
 
94
+ remove_method :metadata
67
95
  def metadata
68
- attributes[:metadata] ||= headers_to_metadata
96
+ attributes.reject {|key, value| !(key.to_s =~ /^x-oss-/)}
97
+ end
98
+
99
+ remove_method :metadata=
100
+ def metadata=(new_metadata)
101
+ merge_attributes(new_metadata)
69
102
  end
70
103
 
104
+ remove_method :owner=
71
105
  def owner=(new_owner)
72
106
  if new_owner
73
107
  attributes[:owner] = {
74
- display_name: new_owner['DisplayName'],
75
- id: new_owner['ID']
108
+ :display_name => new_owner['DisplayName'] || new_owner[:display_name],
109
+ :id => new_owner['ID'] || new_owner[:id]
76
110
  }
77
111
  end
78
112
  end
79
113
 
114
+ # Set Access-Control-List permissions.
115
+ #
116
+ # valid new_publics: public_read, private
117
+ #
118
+ # @param [String] new_public
119
+ # @return [String] new_public
120
+ #
80
121
  def public=(new_public)
122
+ if new_public
123
+ @acl = 'public-read'
124
+ else
125
+ @acl = 'private'
126
+ end
81
127
  new_public
82
128
  end
83
129
 
@@ -86,50 +132,32 @@ module Fog
86
132
  # required attributes: directory, key
87
133
  #
88
134
  # @param expires [String] number of seconds (since 1970-01-01 00:00) before url expires
89
- # @param options [Hash]
135
+ # @param options[Hash] No need to use
90
136
  # @return [String] url
91
137
  #
92
138
  def url(expires, options = {})
93
-
94
- expires = expires.nil? ? 0 : expires.to_i
95
-
96
- requires :directory, :key
97
- bucket_name, directory_key = collection.check_directory_key(directory.key)
98
- object = if directory_key == ''
99
- key
100
- else
101
- directory_key + '/' + key
102
- end
103
- service.get_object_http_url_public(object, expires, options.merge(bucket: bucket_name))
104
- end
105
-
106
- def public_url
107
139
  requires :key
108
- collection.get_url(key)
140
+ service.get_object_http_url_public(directory.key, key, expires)
109
141
  end
110
142
 
111
143
  def save(options = {})
112
144
  requires :body, :directory, :key
113
- options['Content-Type'] = content_type if content_type
145
+ options['x-oss-object-acl'] ||= @acl if @acl
146
+ options['Cache-Control'] = cache_control if cache_control
114
147
  options['Content-Disposition'] = content_disposition if content_disposition
115
- options.merge!(metadata_to_headers)
116
- bucket_name, directory_key = collection.check_directory_key(directory.key)
117
- object = if directory_key == ''
118
- key
119
- else
120
- directory_key + '/' + key
121
- end
122
- if body.is_a?(::File)
123
- service.put_object(object, body, options.merge(bucket: bucket_name))
124
- data = service.head_object(object, bucket: bucket_name)
125
- elsif body.is_a?(String)
126
- data = service.put_object_with_body(object, body, options.merge(bucket: bucket_name)).data
148
+ options['Content-Encoding'] = content_encoding if content_encoding
149
+ options['Content-MD5'] = content_md5 if content_md5
150
+ options['Content-Type'] = content_type if content_type
151
+ options['Expires'] = expires if expires
152
+ options.merge!(metadata)
153
+
154
+ self.multipart_chunk_size = 5242880 if !multipart_chunk_size && Fog::Storage.get_body_size(body) > 5368709120
155
+ if multipart_chunk_size && Fog::Storage.get_body_size(body) >= multipart_chunk_size && body.respond_to?(:read)
156
+ multipart_save(options)
127
157
  else
128
- raise Fog::Aliyun::Storage::Error, " Forbidden: Invalid body type: #{body.class}!"
158
+ service.put_object(directory.key, key, body, options)
129
159
  end
130
- update_attributes_from(data)
131
- refresh_metadata
132
-
160
+ self.etag = self.etag.gsub('"','') if self.etag
133
161
  self.content_length = Fog::Storage.get_body_size(body)
134
162
  self.content_type ||= Fog::Storage.get_content_type(body)
135
163
  true
@@ -137,70 +165,43 @@ module Fog
137
165
 
138
166
  private
139
167
 
140
- attr_writer :directory
141
-
142
- def refresh_metadata
143
- metadata.reject! { |_k, v| v.nil? }
144
- end
145
-
146
- def headers_to_metadata
147
- key_map = key_mapping
148
- Hash[metadata_attributes.map { |k, v| [key_map[k], v] }]
149
- end
150
-
151
- def key_mapping
152
- key_map = metadata_attributes
153
- key_map.each_pair { |k, _v| key_map[k] = header_to_key(k) }
168
+ def directory=(new_directory)
169
+ @directory = new_directory
154
170
  end
155
171
 
156
- def header_to_key(opt)
157
- opt.gsub(metadata_prefix, '').split('-').map { |k| k[0, 1].downcase + k[1..-1] }.join('_').to_sym
158
- end
159
-
160
- def metadata_to_headers
161
- header_map = header_mapping
162
- Hash[metadata.map { |k, v| [header_map[k], v] }]
163
- end
164
-
165
- def header_mapping
166
- header_map = metadata.dup
167
- header_map.each_pair { |k, _v| header_map[k] = key_to_header(k) }
168
- end
169
-
170
- def key_to_header(key)
171
- metadata_prefix + key.to_s.split(/[-_]/).map(&:capitalize).join('-')
172
- end
172
+ def multipart_save(options)
173
+ # Initiate the upload
174
+ upload_id = service.initiate_multipart_upload(directory.key, key, options)
173
175
 
174
- def metadata_attributes
175
- if last_modified
176
- bucket_name, directory_key = collection.check_directory_key(directory.key)
177
- object = if directory_key == ''
178
- key
179
- else
180
- directory_key + '/' + key
181
- end
176
+ # Store ETags of upload parts
177
+ part_tags = []
182
178
 
183
- data = service.head_object(object, bucket: bucket_name).data
184
- if data[:status] == 200
185
- headers = data[:headers]
186
- headers.select! { |k, _v| metadata_attribute?(k) }
187
- end
188
- else
189
- {}
179
+ # Upload each part
180
+ # TODO: optionally upload chunks in parallel using threads
181
+ # (may cause network performance problems with many small chunks)
182
+ # TODO: Support large chunk sizes without reading the chunk into memory
183
+ if body.respond_to?(:rewind)
184
+ body.rewind rescue nil
185
+ end
186
+ while (chunk = body.read(multipart_chunk_size)) do
187
+ part_upload = service.upload_part(directory.key, key, upload_id, part_tags.size + 1, chunk)
188
+ part_tags << part_upload
190
189
  end
191
- end
192
190
 
193
- def metadata_attribute?(key)
194
- key.to_s =~ /^#{metadata_prefix}/
195
- end
191
+ if part_tags.empty? #it is an error to have a multipart upload with no parts
192
+ part_upload = service.upload_part(directory.key, key, upload_id, 1, '')
193
+ part_tags << part_upload
194
+ end
196
195
 
197
- def metadata_prefix
198
- 'X-Object-Meta-'
196
+ rescue
197
+ # Abort the upload & reraise
198
+ service.abort_multipart_upload(directory.key, key, upload_id) if upload_id
199
+ raise
200
+ else
201
+ # Complete the upload
202
+ service.complete_multipart_upload(directory.key, key, upload_id, part_tags)
199
203
  end
200
204
 
201
- def update_attributes_from(data)
202
- merge_attributes(data[:headers].reject { |key, _value| ['Content-Length', 'Content-Type'].include?(key) })
203
- end
204
205
  end
205
206
  end
206
207
  end