aliyun-oss-ruby-sdk 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +95 -0
- data/README.md +423 -0
- data/examples/aliyun/oss/bucket.rb +144 -0
- data/examples/aliyun/oss/callback.rb +61 -0
- data/examples/aliyun/oss/object.rb +182 -0
- data/examples/aliyun/oss/resumable_download.rb +42 -0
- data/examples/aliyun/oss/resumable_upload.rb +49 -0
- data/examples/aliyun/oss/streaming.rb +124 -0
- data/examples/aliyun/oss/using_sts.rb +48 -0
- data/examples/aliyun/sts/assume_role.rb +59 -0
- data/lib/aliyun_sdk/common.rb +6 -0
- data/lib/aliyun_sdk/common/exception.rb +18 -0
- data/lib/aliyun_sdk/common/logging.rb +46 -0
- data/lib/aliyun_sdk/common/struct.rb +56 -0
- data/lib/aliyun_sdk/oss.rb +16 -0
- data/lib/aliyun_sdk/oss/bucket.rb +661 -0
- data/lib/aliyun_sdk/oss/client.rb +106 -0
- data/lib/aliyun_sdk/oss/config.rb +39 -0
- data/lib/aliyun_sdk/oss/download.rb +255 -0
- data/lib/aliyun_sdk/oss/exception.rb +108 -0
- data/lib/aliyun_sdk/oss/http.rb +338 -0
- data/lib/aliyun_sdk/oss/iterator.rb +92 -0
- data/lib/aliyun_sdk/oss/multipart.rb +74 -0
- data/lib/aliyun_sdk/oss/object.rb +15 -0
- data/lib/aliyun_sdk/oss/protocol.rb +1499 -0
- data/lib/aliyun_sdk/oss/struct.rb +208 -0
- data/lib/aliyun_sdk/oss/upload.rb +238 -0
- data/lib/aliyun_sdk/oss/util.rb +89 -0
- data/lib/aliyun_sdk/sts.rb +9 -0
- data/lib/aliyun_sdk/sts/client.rb +38 -0
- data/lib/aliyun_sdk/sts/config.rb +22 -0
- data/lib/aliyun_sdk/sts/exception.rb +53 -0
- data/lib/aliyun_sdk/sts/protocol.rb +130 -0
- data/lib/aliyun_sdk/sts/struct.rb +64 -0
- data/lib/aliyun_sdk/sts/util.rb +48 -0
- data/lib/aliyun_sdk/version.rb +7 -0
- data/spec/aliyun/oss/bucket_spec.rb +597 -0
- data/spec/aliyun/oss/client/bucket_spec.rb +554 -0
- data/spec/aliyun/oss/client/client_spec.rb +297 -0
- data/spec/aliyun/oss/client/resumable_download_spec.rb +220 -0
- data/spec/aliyun/oss/client/resumable_upload_spec.rb +413 -0
- data/spec/aliyun/oss/http_spec.rb +83 -0
- data/spec/aliyun/oss/multipart_spec.rb +686 -0
- data/spec/aliyun/oss/object_spec.rb +785 -0
- data/spec/aliyun/oss/service_spec.rb +142 -0
- data/spec/aliyun/oss/util_spec.rb +50 -0
- data/spec/aliyun/sts/client_spec.rb +150 -0
- data/spec/aliyun/sts/util_spec.rb +39 -0
- data/tests/config.rb +31 -0
- data/tests/test_content_encoding.rb +54 -0
- data/tests/test_content_type.rb +95 -0
- data/tests/test_custom_headers.rb +70 -0
- data/tests/test_encoding.rb +77 -0
- data/tests/test_large_file.rb +66 -0
- data/tests/test_multipart.rb +97 -0
- data/tests/test_object_acl.rb +49 -0
- data/tests/test_object_key.rb +68 -0
- data/tests/test_object_url.rb +69 -0
- data/tests/test_resumable.rb +40 -0
- metadata +240 -0
@@ -0,0 +1,106 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module AliyunSDK
|
4
|
+
module OSS
|
5
|
+
|
6
|
+
##
|
7
|
+
# OSS服务的客户端,用于获取bucket列表,创建/删除bucket。Object相关
|
8
|
+
# 的操作请使用{OSS::Bucket}。
|
9
|
+
# @example 创建Client
|
10
|
+
# endpoint = 'oss-cn-hangzhou.aliyuncs.com'
|
11
|
+
# client = Client.new(
|
12
|
+
# :endpoint => endpoint,
|
13
|
+
# :access_key_id => 'access_key_id',
|
14
|
+
# :access_key_secret => 'access_key_secret')
|
15
|
+
# buckets = client.list_buckets
|
16
|
+
# client.create_bucket('my-bucket')
|
17
|
+
# client.delete_bucket('my-bucket')
|
18
|
+
# bucket = client.get_bucket('my-bucket')
|
19
|
+
class Client
|
20
|
+
|
21
|
+
# 构造OSS client,用于操作buckets。
|
22
|
+
# @param opts [Hash] 构造Client时的参数选项
|
23
|
+
# @option opts [String] :endpoint [必填]OSS服务的地址,可以是以
|
24
|
+
# oss-cn-hangzhou.aliyuncs.com的标准域名,也可以是用户绑定的域名
|
25
|
+
# @option opts [String] :access_key_id [可选]用户的ACCESS KEY ID,
|
26
|
+
# 如果不填则会尝试匿名访问
|
27
|
+
# @option opts [String] :access_key_secret [可选]用户的ACCESS
|
28
|
+
# KEY SECRET,如果不填则会尝试匿名访问
|
29
|
+
# @option opts [Boolean] :cname [可选] 指定endpoint是否是用户绑
|
30
|
+
# 定的域名
|
31
|
+
# @option opts [String] :sts_token [可选] 指定STS的
|
32
|
+
# SecurityToken,如果指定,则使用STS授权访问
|
33
|
+
# @option opts [Fixnum] :open_timeout [可选] 指定建立连接的超时
|
34
|
+
# 时间,默认为10秒
|
35
|
+
# @option opts [Fixnum] :read_timeout [可选] 指定等待响应的超时
|
36
|
+
# 时间,默认为120秒
|
37
|
+
# @example 标准endpoint
|
38
|
+
# oss-cn-hangzhou.aliyuncs.com
|
39
|
+
# oss-cn-beijing.aliyuncs.com
|
40
|
+
# @example 用户绑定的域名
|
41
|
+
# my-domain.com
|
42
|
+
# foo.bar.com
|
43
|
+
def initialize(opts)
|
44
|
+
fail ClientError, "Endpoint must be provided" unless opts[:endpoint]
|
45
|
+
|
46
|
+
@config = Config.new(opts)
|
47
|
+
@protocol = Protocol.new(@config)
|
48
|
+
end
|
49
|
+
|
50
|
+
# 列出当前所有的bucket
|
51
|
+
# @param opts [Hash] 查询选项
|
52
|
+
# @option opts [String] :prefix 如果设置,则只返回以它为前缀的bucket
|
53
|
+
# @option opts [String] :marker 如果设置,则只返回名字在它之后
|
54
|
+
# (字典序,不包含marker)的bucket
|
55
|
+
# @return [Enumerator<Bucket>] Bucket的迭代器
|
56
|
+
def list_buckets(opts = {})
|
57
|
+
if @config.cname
|
58
|
+
fail ClientError, "Cannot list buckets for a CNAME endpoint."
|
59
|
+
end
|
60
|
+
|
61
|
+
Iterator::Buckets.new(@protocol, opts).to_enum
|
62
|
+
end
|
63
|
+
|
64
|
+
# 创建一个bucket
|
65
|
+
# @param name [String] Bucket名字
|
66
|
+
# @param opts [Hash] 创建Bucket的属性(可选)
|
67
|
+
# @option opts [:location] [String] 指定bucket所在的区域,默认为oss-cn-hangzhou
|
68
|
+
def create_bucket(name, opts = {})
|
69
|
+
@protocol.create_bucket(name, opts)
|
70
|
+
end
|
71
|
+
|
72
|
+
# 删除一个bucket
|
73
|
+
# @param name [String] Bucket名字
|
74
|
+
# @note 如果要删除的Bucket不为空(包含有object),则删除会失败
|
75
|
+
def delete_bucket(name)
|
76
|
+
@protocol.delete_bucket(name)
|
77
|
+
end
|
78
|
+
|
79
|
+
# 判断一个bucket是否存在
|
80
|
+
# @param name [String] Bucket名字
|
81
|
+
# @return [Boolean] 如果Bucket存在则返回true,否则返回false
|
82
|
+
def bucket_exists?(name)
|
83
|
+
exist = false
|
84
|
+
|
85
|
+
begin
|
86
|
+
@protocol.get_bucket_acl(name)
|
87
|
+
exist = true
|
88
|
+
rescue ServerError => e
|
89
|
+
raise unless e.http_code == 404
|
90
|
+
end
|
91
|
+
|
92
|
+
exist
|
93
|
+
end
|
94
|
+
|
95
|
+
alias :bucket_exist? :bucket_exists?
|
96
|
+
|
97
|
+
# 获取一个Bucket对象,用于操作bucket中的objects。
|
98
|
+
# @param name [String] Bucket名字
|
99
|
+
# @return [Bucket] Bucket对象
|
100
|
+
def get_bucket(name)
|
101
|
+
Bucket.new({:name => name}, @protocol)
|
102
|
+
end
|
103
|
+
|
104
|
+
end # Client
|
105
|
+
end # OSS
|
106
|
+
end # Aliyun
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module AliyunSDK
|
4
|
+
module OSS
|
5
|
+
|
6
|
+
##
|
7
|
+
# A place to store various configurations: credentials, api
|
8
|
+
# timeout, retry mechanism, etc
|
9
|
+
#
|
10
|
+
class Config < Common::Struct::Base
|
11
|
+
|
12
|
+
attrs :endpoint, :cname, :sts_token,
|
13
|
+
:access_key_id, :access_key_secret,
|
14
|
+
:open_timeout, :read_timeout
|
15
|
+
|
16
|
+
def initialize(opts = {})
|
17
|
+
super(opts)
|
18
|
+
|
19
|
+
@access_key_id = @access_key_id.strip if @access_key_id
|
20
|
+
@access_key_secret = @access_key_secret.strip if @access_key_secret
|
21
|
+
normalize_endpoint if endpoint
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def normalize_endpoint
|
27
|
+
uri = URI.parse(endpoint)
|
28
|
+
uri = URI.parse("http://#{endpoint}") unless uri.scheme
|
29
|
+
|
30
|
+
if uri.scheme != 'http' and uri.scheme != 'https'
|
31
|
+
fail ClientError, "Only HTTP and HTTPS endpoint are accepted."
|
32
|
+
end
|
33
|
+
|
34
|
+
@endpoint = uri
|
35
|
+
end
|
36
|
+
|
37
|
+
end # Config
|
38
|
+
end # OSS
|
39
|
+
end # Aliyun
|
@@ -0,0 +1,255 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module AliyunSDK
|
4
|
+
module OSS
|
5
|
+
module Multipart
|
6
|
+
##
|
7
|
+
# A multipart download transaction
|
8
|
+
#
|
9
|
+
class Download < Transaction
|
10
|
+
|
11
|
+
include Common::Logging
|
12
|
+
|
13
|
+
PART_SIZE = 10 * 1024 * 1024
|
14
|
+
READ_SIZE = 16 * 1024
|
15
|
+
NUM_THREAD = 10
|
16
|
+
|
17
|
+
def initialize(protocol, opts)
|
18
|
+
args = opts.dup
|
19
|
+
@protocol = protocol
|
20
|
+
@progress = args.delete(:progress)
|
21
|
+
@file = args.delete(:file)
|
22
|
+
@cpt_file = args.delete(:cpt_file)
|
23
|
+
super(args)
|
24
|
+
|
25
|
+
@object_meta = {}
|
26
|
+
@num_threads = options[:threads] || NUM_THREAD
|
27
|
+
@all_mutex = Mutex.new
|
28
|
+
@parts = []
|
29
|
+
@todo_mutex = Mutex.new
|
30
|
+
@todo_parts = []
|
31
|
+
end
|
32
|
+
|
33
|
+
# Run the download transaction, which includes 3 stages:
|
34
|
+
# * 1a. initiate(new downlaod) and divide parts
|
35
|
+
# * 1b. rebuild states(resumed download)
|
36
|
+
# * 2. download each unfinished part
|
37
|
+
# * 3. combine the downloaded parts into the final file
|
38
|
+
def run
|
39
|
+
logger.info("Begin download, file: #{@file}, "\
|
40
|
+
"checkpoint file: #{@cpt_file}, "\
|
41
|
+
"threads: #{@num_threads}")
|
42
|
+
|
43
|
+
# Rebuild transaction states from checkpoint file
|
44
|
+
# Or initiate new transaction states
|
45
|
+
rebuild
|
46
|
+
|
47
|
+
# Divide the target object into parts to download by ranges
|
48
|
+
divide_parts if @parts.empty?
|
49
|
+
|
50
|
+
# Download each part(object range)
|
51
|
+
@todo_parts = @parts.reject { |p| p[:done] }
|
52
|
+
|
53
|
+
(1..@num_threads).map {
|
54
|
+
Thread.new {
|
55
|
+
loop {
|
56
|
+
p = sync_get_todo_part
|
57
|
+
break unless p
|
58
|
+
download_part(p)
|
59
|
+
}
|
60
|
+
}
|
61
|
+
}.map(&:join)
|
62
|
+
|
63
|
+
# Combine the parts into the final file
|
64
|
+
commit
|
65
|
+
|
66
|
+
logger.info("Done download, file: #{@file}")
|
67
|
+
end
|
68
|
+
|
69
|
+
# Checkpoint structures:
|
70
|
+
# @example
|
71
|
+
# states = {
|
72
|
+
# :id => 'download_id',
|
73
|
+
# :file => 'file',
|
74
|
+
# :object_meta => {
|
75
|
+
# :etag => 'xxx',
|
76
|
+
# :size => 1024
|
77
|
+
# },
|
78
|
+
# :parts => [
|
79
|
+
# {:number => 1, :range => [0, 100], :md5 => 'xxx', :done => false},
|
80
|
+
# {:number => 2, :range => [100, 200], :md5 => 'yyy', :done => true}
|
81
|
+
# ],
|
82
|
+
# :md5 => 'states_md5'
|
83
|
+
# }
|
84
|
+
def checkpoint
|
85
|
+
logger.debug("Begin make checkpoint, disable_cpt: "\
|
86
|
+
"#{options[:disable_cpt] == true}")
|
87
|
+
|
88
|
+
ensure_object_not_changed
|
89
|
+
|
90
|
+
parts = sync_get_all_parts
|
91
|
+
states = {
|
92
|
+
:id => id,
|
93
|
+
:file => @file,
|
94
|
+
:object_meta => @object_meta,
|
95
|
+
:parts => parts
|
96
|
+
}
|
97
|
+
|
98
|
+
# report progress
|
99
|
+
if @progress
|
100
|
+
done = parts.count { |p| p[:done] }
|
101
|
+
@progress.call(done.to_f / parts.size) if done > 0
|
102
|
+
end
|
103
|
+
|
104
|
+
write_checkpoint(states, @cpt_file) unless options[:disable_cpt]
|
105
|
+
|
106
|
+
logger.debug("Done make checkpoint, states: #{states}")
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
# Combine the downloaded parts into the final file
|
111
|
+
# @todo avoid copy all part files
|
112
|
+
def commit
|
113
|
+
logger.info("Begin commit transaction, id: #{id}")
|
114
|
+
|
115
|
+
parts = sync_get_all_parts
|
116
|
+
# concat all part files into the target file
|
117
|
+
File.open(@file, 'w') do |w|
|
118
|
+
parts.sort{ |x, y| x[:number] <=> y[:number] }.each do |p|
|
119
|
+
File.open(get_part_file(p)) do |r|
|
120
|
+
w.write(r.read(READ_SIZE)) until r.eof?
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
File.delete(@cpt_file) unless options[:disable_cpt]
|
126
|
+
parts.each{ |p| File.delete(get_part_file(p)) }
|
127
|
+
|
128
|
+
logger.info("Done commit transaction, id: #{id}")
|
129
|
+
end
|
130
|
+
|
131
|
+
# Rebuild the states of the transaction from checkpoint file
|
132
|
+
def rebuild
|
133
|
+
logger.info("Begin rebuild transaction, checkpoint: #{@cpt_file}")
|
134
|
+
|
135
|
+
if options[:disable_cpt] || !File.exists?(@cpt_file)
|
136
|
+
initiate
|
137
|
+
else
|
138
|
+
states = load_checkpoint(@cpt_file)
|
139
|
+
|
140
|
+
states[:parts].select{ |p| p[:done] }.each do |p|
|
141
|
+
part_file = get_part_file(p)
|
142
|
+
|
143
|
+
unless File.exist?(part_file)
|
144
|
+
fail PartMissingError, "The part file is missing: #{part_file}."
|
145
|
+
end
|
146
|
+
|
147
|
+
if p[:md5] != get_file_md5(part_file)
|
148
|
+
fail PartInconsistentError,
|
149
|
+
"The part file is changed: #{part_file}."
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
@id = states[:id]
|
154
|
+
@object_meta = states[:object_meta]
|
155
|
+
@parts = states[:parts]
|
156
|
+
end
|
157
|
+
|
158
|
+
logger.info("Done rebuild transaction, states: #{states}")
|
159
|
+
end
|
160
|
+
|
161
|
+
def initiate
|
162
|
+
logger.info("Begin initiate transaction")
|
163
|
+
|
164
|
+
@id = generate_download_id
|
165
|
+
obj = @protocol.get_object_meta(bucket, object)
|
166
|
+
@object_meta = {
|
167
|
+
:etag => obj.etag,
|
168
|
+
:size => obj.size
|
169
|
+
}
|
170
|
+
checkpoint
|
171
|
+
|
172
|
+
logger.info("Done initiate transaction, id: #{id}")
|
173
|
+
end
|
174
|
+
|
175
|
+
# Download a part
|
176
|
+
def download_part(p)
|
177
|
+
logger.debug("Begin download part: #{p}")
|
178
|
+
|
179
|
+
part_file = get_part_file(p)
|
180
|
+
File.open(part_file, 'w') do |w|
|
181
|
+
@protocol.get_object(
|
182
|
+
bucket, object,
|
183
|
+
@options.merge(range: p[:range])) { |chunk| w.write(chunk) }
|
184
|
+
end
|
185
|
+
|
186
|
+
sync_update_part(p.merge(done: true, md5: get_file_md5(part_file)))
|
187
|
+
|
188
|
+
checkpoint
|
189
|
+
|
190
|
+
logger.debug("Done download part: #{p}")
|
191
|
+
end
|
192
|
+
|
193
|
+
# Devide the object to download into parts to download
|
194
|
+
def divide_parts
|
195
|
+
logger.info("Begin divide parts, object: #{@object}")
|
196
|
+
|
197
|
+
max_parts = 100
|
198
|
+
object_size = @object_meta[:size]
|
199
|
+
part_size =
|
200
|
+
[@options[:part_size] || PART_SIZE, object_size / max_parts].max
|
201
|
+
num_parts = (object_size - 1) / part_size + 1
|
202
|
+
@parts = (1..num_parts).map do |i|
|
203
|
+
{
|
204
|
+
:number => i,
|
205
|
+
:range => [(i - 1) * part_size, [i * part_size, object_size].min],
|
206
|
+
:done => false
|
207
|
+
}
|
208
|
+
end
|
209
|
+
|
210
|
+
checkpoint
|
211
|
+
|
212
|
+
logger.info("Done divide parts, parts: #{@parts}")
|
213
|
+
end
|
214
|
+
|
215
|
+
def sync_get_todo_part
|
216
|
+
@todo_mutex.synchronize {
|
217
|
+
@todo_parts.shift
|
218
|
+
}
|
219
|
+
end
|
220
|
+
|
221
|
+
def sync_update_part(p)
|
222
|
+
@all_mutex.synchronize {
|
223
|
+
@parts[p[:number] - 1] = p
|
224
|
+
}
|
225
|
+
end
|
226
|
+
|
227
|
+
def sync_get_all_parts
|
228
|
+
@all_mutex.synchronize {
|
229
|
+
@parts.dup
|
230
|
+
}
|
231
|
+
end
|
232
|
+
|
233
|
+
# Ensure file not changed during uploading
|
234
|
+
def ensure_object_not_changed
|
235
|
+
obj = @protocol.get_object_meta(bucket, object)
|
236
|
+
unless obj.etag == @object_meta[:etag]
|
237
|
+
fail ObjectInconsistentError,
|
238
|
+
"The object to download is changed: #{object}."
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
# Generate a download id
|
243
|
+
def generate_download_id
|
244
|
+
"download_#{bucket}_#{object}_#{Time.now.to_i}"
|
245
|
+
end
|
246
|
+
|
247
|
+
# Get name for part file
|
248
|
+
def get_part_file(p)
|
249
|
+
"#{@file}.part.#{p[:number]}"
|
250
|
+
end
|
251
|
+
end # Download
|
252
|
+
|
253
|
+
end # Multipart
|
254
|
+
end # OSS
|
255
|
+
end # Aliyun
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'nokogiri'
|
4
|
+
|
5
|
+
module AliyunSDK
|
6
|
+
module OSS
|
7
|
+
|
8
|
+
##
|
9
|
+
# ServerError represents exceptions from the OSS
|
10
|
+
# service. i.e. Client receives a HTTP response whose status is
|
11
|
+
# NOT OK. #message provides the error message and #to_s gives
|
12
|
+
# detailed information probably including the OSS request id.
|
13
|
+
#
|
14
|
+
class ServerError < Common::Exception
|
15
|
+
|
16
|
+
attr_reader :http_code, :error_code, :message, :request_id
|
17
|
+
|
18
|
+
def initialize(response)
|
19
|
+
@http_code = response.code
|
20
|
+
@attrs = {'RequestId' => get_request_id(response)}
|
21
|
+
|
22
|
+
doc = Nokogiri::XML(response.body) do |config|
|
23
|
+
config.options |= Nokogiri::XML::ParseOptions::NOBLANKS
|
24
|
+
end rescue nil
|
25
|
+
|
26
|
+
if doc and doc.root
|
27
|
+
doc.root.children.each do |n|
|
28
|
+
@attrs[n.name] = n.text
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
@error_code = @attrs['Code']
|
33
|
+
@message = @attrs['Message']
|
34
|
+
@request_id = @attrs['RequestId']
|
35
|
+
end
|
36
|
+
|
37
|
+
def message
|
38
|
+
msg = @attrs['Message'] || "UnknownError[#{http_code}]."
|
39
|
+
"#{msg} RequestId: #{request_id}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
@attrs.merge({'HTTPCode' => @http_code}).map do |k, v|
|
44
|
+
[k, v].join(": ")
|
45
|
+
end.join(", ")
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def get_request_id(response)
|
51
|
+
r = response.headers[:x_oss_request_id] if response.headers
|
52
|
+
r.to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
end # ServerError
|
56
|
+
|
57
|
+
class CallbackError < ServerError
|
58
|
+
end # CallbackError
|
59
|
+
|
60
|
+
##
|
61
|
+
# ClientError represents client exceptions caused mostly by
|
62
|
+
# invalid parameters.
|
63
|
+
#
|
64
|
+
class ClientError < Common::Exception
|
65
|
+
end # ClientError
|
66
|
+
|
67
|
+
##
|
68
|
+
# FileInconsistentError happens in a resumable upload transaction,
|
69
|
+
# when the file to upload has changed during the uploading
|
70
|
+
# process. Which means the transaction cannot go on. Or user may
|
71
|
+
# have inconsistent data uploaded to OSS.
|
72
|
+
#
|
73
|
+
class FileInconsistentError < ClientError; end
|
74
|
+
|
75
|
+
##
|
76
|
+
# ObjectInconsistentError happens in a resumable download transaction,
|
77
|
+
# when the object to download has changed during the downloading
|
78
|
+
# process. Which means the transaction cannot go on. Or user may
|
79
|
+
# have inconsistent data downloaded to OSS.
|
80
|
+
#
|
81
|
+
class ObjectInconsistentError < ClientError; end
|
82
|
+
|
83
|
+
##
|
84
|
+
# PartMissingError happens in a resumable download transaction,
|
85
|
+
# when a downloaded part cannot be found as the client tries to
|
86
|
+
# resume download. The process cannot go on until the part is
|
87
|
+
# restored.
|
88
|
+
#
|
89
|
+
class PartMissingError < ClientError; end
|
90
|
+
|
91
|
+
##
|
92
|
+
# PartMissingError happens in a resumable download transaction,
|
93
|
+
# when a downloaded part has changed(MD5 mismatch) as the client
|
94
|
+
# tries to resume download. The process cannot go on until the
|
95
|
+
# part is restored.
|
96
|
+
#
|
97
|
+
class PartInconsistentError < ClientError; end
|
98
|
+
|
99
|
+
##
|
100
|
+
# CheckpointBrokenError happens in a resumable upload/download
|
101
|
+
# transaction, when the client finds the checkpoint file has
|
102
|
+
# changed(MD5 mismatch) as it tries to resume upload/download. The
|
103
|
+
# process cannot go on until the checkpoint file is restored.
|
104
|
+
#
|
105
|
+
class CheckpointBrokenError < ClientError; end
|
106
|
+
|
107
|
+
end # OSS
|
108
|
+
end # Aliyun
|