aliyun-sdk 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8d7df8217a8d711e910788b4478a63a9de6d3d19
4
+ data.tar.gz: 4a675680ae738c788a650425002973a1a90e27ac
5
+ SHA512:
6
+ metadata.gz: f82087353b70116909deaf0453bc4b65c213dfb37e8566d8a99f3ab1bc8e93dc6704c5c918400cfbe9c4799aa06e69d10eb45ab2d7e530e4f25864a6f189a92f
7
+ data.tar.gz: e61556df4633d1f99354d05078fbffd70c7ecbc13b44e3606553230d9faa4a969195a4f1aa2f4cd4a3602491ef8a89d6a17635c83e5a88e04f0ae9a8c63a0fb6
data/README.md ADDED
@@ -0,0 +1,364 @@
1
+ # Aliyun OSS SDK for Ruby
2
+
3
+ ![Build Status](http://cise.alibaba-inc.com/task/119062/build/10/status.svg)
4
+
5
+ -----
6
+
7
+ Aliyun::OSS是用于方便访问阿里云OSS(Object Storage Service)RESTful
8
+ API的Ruby客户端程序。了解OSS的的更多信息请访问OSS官网:
9
+ http://www.aliyun.com/product/oss
10
+
11
+ ## 运行环境
12
+
13
+ - Ruby版本 >= 1.9.2
14
+ - 支持Ruby运行环境的Windows/Linux/OS X
15
+
16
+ 安装Ruby请参考:https://www.ruby-lang.org/zh_cn/downloads/
17
+
18
+ ## 快速开始
19
+
20
+ ### 开通OSS账号
21
+
22
+ 登录官网:http://www.aliyun.com/product/oss ,点击“立即开通”。按照提示
23
+ 开通OSS服务。开通服务之后请在“管理控制台”中查看您的AccessKeyId和
24
+ AccessKeySecret,在使用Aliyun OSS SDK时需要提供您的这两个信息。
25
+
26
+ ### 安装Aliyun OSS SDK for Ruby
27
+
28
+ gem install aliyun-sdk
29
+
30
+ 并在你的程序中或者`irb`命令下包含:
31
+
32
+ require 'aliyun/oss'
33
+
34
+ **注意:**
35
+
36
+ 1. SDK依赖的一些gem是本地扩展的形式,因此安装完Ruby之后还需要安装
37
+ ruby-dev以支持编译本地扩展的gem
38
+ 2. SDK依赖的处理XML的gem(nokogiri)要求环境中包含zlib库
39
+
40
+ 以Ubuntu为例,安装上述依赖的方法:
41
+
42
+ sudo apt-get install ruby-dev
43
+ sudo apt-get install zlib1g-dev
44
+
45
+ 其他系统类似。
46
+
47
+ ### 创建Client
48
+
49
+ client = Aliyun::OSS::Client.new(
50
+ :endpoint => endpoint,
51
+ :access_key_id => 'access_key_id',
52
+ :access_key_secret => 'access_key_secret')
53
+
54
+ 其中`endpoint`是OSS服务的地址,根据节点区域不同,这个地址可能不一样,例如
55
+ 杭州节点的地址是:`http://oss-cn-hangzhou.oss.aliyuncs.com`,其他节点的地址见:
56
+ [节点列表][1]
57
+
58
+ `access_key_id`和`access_key_secret`是您的服务凭证,在官网的“管理控制
59
+ 台”上面可以查看。**请妥善保管您的AccessKeySecret,泄露之后可能影响您的
60
+ 数据安全**
61
+
62
+ #### 使用用户绑定的域名作为endpoint
63
+
64
+ OSS支持自定义域名绑定,允许用户将自己的域名指向阿里云OSS的服务地址
65
+ (CNAME),这样用户迁移到OSS上时应用内资源的路径可以不用修改。绑定的域
66
+ 名指向OSS的一个bucket。绑定域名的操作只能在OSS控制台进行。更多关于自定
67
+ 义域名绑定的内容请到官网了解:[OSS自定义域名绑定][2]
68
+
69
+ 用户绑定了域名后,使用SDK时指定的endpoint可以使用标准的OSS服务地址,也
70
+ 可以使用用户绑定的域名:
71
+
72
+ client = Aliyun::OSS::Client.new(
73
+ :endpoint => 'http://img.my-domain.com',
74
+ :access_key_id => 'access_key_id',
75
+ :access_key_secret => 'access_key_secret',
76
+ :cname => true)
77
+
78
+ 有以下几点需要注意:
79
+
80
+ 1. 在Client初始化时必须指定:cname为true
81
+ 2. 自定义域名绑定了OSS的一个bucket,所以用这种方式创建的client不能进行
82
+ list_buckets操作
83
+ 3. 在{Aliyun::OSS::Client#get_bucket}时仍需要指定bucket名字,并且要与
84
+ 域名所绑定的bucket名字相同
85
+
86
+ ### 列出当前所有的Bucket
87
+
88
+ buckets = client.list_buckets
89
+ buckets.each{ |b| puts b.name }
90
+
91
+ `list_buckets`返回的是一个迭代器,用户依次获取每个Bucket的信息。Bucket
92
+ 对象的结构请查看API文档中的{Aliyun::OSS::Bucket}
93
+
94
+ ### 创建一个Bucket
95
+
96
+ bucket = client.create_bucket('my-bucket')
97
+
98
+ ### 列出Bucket中所有的Object
99
+
100
+ bucket = client.get_bucket('my-bucket')
101
+ objects = bucket.list_objects
102
+ objects.each{ |o| puts o.key }
103
+
104
+ `list_objects`返回的是一个迭代器,用户依次获取每个Object的信息。Object
105
+ 对象的结构请查看API文档中的{Aliyun::OSS::Object}
106
+
107
+ ### 在Bucket中创建一个Object
108
+
109
+ bucket.put_object(object_key){ |stream| stream << 'hello world' }
110
+
111
+ 用户也可以通过上传本地文件创建一个Object:
112
+
113
+ bucket.put_object(object_key, :file => local_file)
114
+
115
+ ### 从Bucket中下载一个Object
116
+
117
+ bucket.get_object(object_key){ |content| puts content }
118
+
119
+ 用户也可以将Object下载到本地文件中:
120
+
121
+ bucket.get_object(object_key, :file => local_file)
122
+
123
+ ### 拷贝Object
124
+
125
+ bucket.copy_object(from_key, to_key)
126
+
127
+ ### 判断一个Object是否存在
128
+
129
+ bucket.object_exists?(object_key)
130
+
131
+ 更多Bucket的操作请参考API文档中的{Aliyun::OSS::Bucket}
132
+
133
+ ## 模拟目录结构
134
+
135
+ OSS是Object存储服务,本身不支持目录结构,所有的object都是“平”的。但是
136
+ 用户可以通过设置object的key为"/foo/bar/file"这样的形式来模拟目录结构。
137
+ 假设现在有以下Objects:
138
+
139
+ /foo/x
140
+ /foo/bar/f1
141
+ /foo/bar/dir/file
142
+ /foo/hello/file
143
+
144
+ 列出"/foo/"目录下的所有文件就是以"/foo/"为prefix进行`list_objects`,但
145
+ 是这样也会把"/foo/bar/"下的所有object也列出来。为此需要用到delimiter参
146
+ 数,其含义是从prefix往后遇到第一个delimiter时停止,这中间的key作为
147
+ Object的common prefix,包含在`list_objects`的结果中。
148
+
149
+ objs = bucket.list_objects(:prefix => '/foo/', :delimiter => '/')
150
+ objs.each do |i|
151
+ if i.is_a?(Object) # a object
152
+ puts "object: #{i.key}"
153
+ else
154
+ puts "common prefix: #{i}"
155
+ end
156
+ end
157
+ # output
158
+ object: /foo/x
159
+ common prefix: /foo/bar/
160
+ common prefix: /foo/hello/
161
+
162
+ Common prefix让用户不需要遍历所有的object(可能数量巨大)而找出前缀,
163
+ 在模拟目录结构时非常有用。
164
+
165
+ ## 断点上传/下载
166
+
167
+ OSS支持大文件的存储,用户如果上传/下载大文件(Object)的时候中断了(网络
168
+ 闪断、程序崩溃、机器断电等),重新上传/下载是件很费资源的事情。OSS支持
169
+ Multipart的功能,可以在上传/下载时将大文件进行分片传输。Aliyun OSS SDK
170
+ 基于此提供了断点上传/下载的功能。如果发生中断,可以从上次中断的地方继
171
+ 续进行上传/下载。对于文件大小超过100MB的文件,都建议采用断点上传/下载
172
+ 的方式进行。
173
+
174
+ ### 断点上传
175
+
176
+ bucket.resumable_upload(object_key, local_file, :cpt_file => cpt_file)
177
+
178
+ 其中`:cpt_file`指定保存上传中间状态的checkpoint文件所在的位置,如果用户
179
+ 没有指定,SDK将为用户在`local_file`所在的目录生成一个
180
+ `local_file.cpt`。上传中断后,只需要提供相同的cpt文件,上传将会从
181
+ 中断的点继续上传。所以典型的上传代码是:
182
+
183
+ retry_times = 5
184
+ retry_times.times do
185
+ begin
186
+ bucket.resumable_upload(object_key, local_file)
187
+ rescue => e
188
+ logger.error(e.message)
189
+ end
190
+ end
191
+
192
+ 注意:
193
+
194
+ 1. SDK会将上传的中间状态信息记录在cpt文件中,所以要确保用户对cpt文
195
+ 件有写权限
196
+ 2. cpt文件记录了上传的中间状态信息并自带了校验,用户不能去编辑它,如
197
+ 果cpt文件损坏则上传无法继续。整个上传完成后cpt文件会被删除。
198
+
199
+ ### 断点下载
200
+
201
+ bucket.resumable_download(object_key, local_file, :cpt_file => cpt_file)
202
+
203
+ 其中`:cpt_file`指定保存下载中间状态的checkpoint文件所在的位置,如果用户
204
+ 没有指定,SDK将为用户在`local_file`所在的目录生成一个
205
+ `local_file.cpt`。下载中断后,只需要提供相同的cpt文件,下载将会从
206
+ 中断的点继续下载。所以典型的下载代码是:
207
+
208
+ retry_times = 5
209
+ retry_times.times do
210
+ begin
211
+ bucket.resumable_download(object_key, local_file)
212
+ rescue => e
213
+ logger.error(e.message)
214
+ end
215
+ end
216
+
217
+ 注意:
218
+
219
+ 1. 在下载过程中,对于下载完成的每个分片,会在`local_file`所在的目录生
220
+ 成一个`local_file.part.N`的临时文件。整个下载完成后这些文件会被删除。
221
+ 用户不能去编辑或删除part文件,否则下载不能继续。
222
+ 2. SDK会将下载的中间状态信息记录在cpt文件中,所以要确保用户对cpt文
223
+ 件有写权限
224
+ 3. cpt文件记录了下载的中间状态信息并自带了校验,用户不能去编辑它,如
225
+ 果cpt文件损坏则下载无法继续。整个下载完成后cpt文件会被删除。
226
+
227
+
228
+ ## 可追加的文件
229
+
230
+ 阿里云OSS中的Object分为两种类型:Normal和Appendable。
231
+
232
+ - 对于Normal Object,每次上传都是作为一个整体,如果一个Object已存在,
233
+ 两次上传相同key的Object将会覆盖原有的Object
234
+ - 对于Appendable Object,第一次通过`append_object`创建它,后续的
235
+ `append_object`不会覆盖原有的内容,而是在Object末尾追加内容
236
+ - 不能向Normal Object追加内容
237
+ - 不能拷贝一个Appendable Object
238
+
239
+ ### 创建一个Appendable Object
240
+
241
+ bucket.append_object(object_key, 0){ |stream| stream << "hello world" }
242
+
243
+ 第二个参数是追加的位置,对一个Object第一次追加时,这个参数为0。后续的
244
+ 追加这个参数要求是追加前Object的长度。
245
+
246
+ 当然,也可以从文件中读取追加的内容:
247
+
248
+ bucket.append_object(object_key, 0, :file => local_file)
249
+
250
+ ### 向Object追加内容
251
+
252
+ pos = bucket.get_object_meta(object_key).size
253
+ next_pos = bucket.append_object(object_key, pos, :file => local_file)
254
+
255
+ 程序第一次追加时,可以通过{Aliyun::OSS::Bucket#get_object_meta}获取文件的长度,
256
+ 后续追加时,可以根据{Aliyun::OSS::Bucket#append_object}返回的下次追加长度。
257
+
258
+ 注意:如果并发地`append_object`,`next_pos`并不总是对的。
259
+
260
+ ## Object meta信息
261
+
262
+ 在上传Object时,除了Object内容,OSS还允许用户为Object设置一些"meta信息
263
+ ",这些meta信息是一个个的Key-Value对,用于标识Object特有的属性信息。这
264
+ 些meta信息会跟Object一起存储,并在`get_object`和`get_object_meta`时返
265
+ 回给用户。
266
+
267
+ bucket.put_object(object_key, :file => local_file,
268
+ :metas => {
269
+ 'key1' => 'value1',
270
+ 'key2' => 'value2'})
271
+
272
+ obj = bucket.get_object(object_key, :file => localfile)
273
+ puts obj.metas
274
+
275
+ 关于meta信息有以下几点需要注意:
276
+ 1. meta信息的key和value都只能是简单的ASCII非换行字符,并且总的大小不能超过8KB。
277
+ 2. Copy object时默认将拷贝源object的meta信息,如果用户不希望这么做,需要
278
+ 显式地将`:meta_directive`设置成{Aliyun::OSS::MetaDirective::REPLACE}
279
+
280
+ ## 权限控制
281
+
282
+ OSS允许用户对Bucket和Object分别设置访问权限,方便用户控制自己的资源可
283
+ 以被如何访问。对于Bucket,有三种访问权限:
284
+
285
+ - public-read-write 允许匿名用户向该Bucket中创建/获取/删除Object
286
+ - public-read 允许匿名用户获取该Bucket中的Object
287
+ - private 不允许匿名访问,所有的访问都要经过签名
288
+
289
+ 创建Bucket时,默认是private权限。之后用户可以通过`bucket.acl=`来设置
290
+ Bucket的权限。
291
+
292
+ bucket.acl = Aliyun::OSS::ACL::PUBLIC_READ
293
+ puts bucket.acl # public-read
294
+
295
+ 对于Object,有四种访问权限:
296
+
297
+ - default 继承所属的Bucket的访问权限,即与所属Bucket的权限值一样
298
+ - public-read-write 允许匿名用户读写该Object
299
+ - public-read 允许匿名用户读该Object
300
+ - private 不允许匿名访问,所有的访问都要经过签名
301
+
302
+ 创建Object时,默认为default权限。之后用户可以通过
303
+ `bucket.set_object_acl`来设置Object的权限。
304
+
305
+ acl = bucket.get_object_acl(object_key)
306
+ puts acl # default
307
+ bucket.set_object_acl(object_key, Aliyun::OSS::ACL::PUBLIC_READ)
308
+ acl = bucket.get_object_acl(object_key)
309
+ puts acl # public-read
310
+
311
+ 需要注意的是:
312
+
313
+ 1. 如果设置了Object的权限,则访问该Object时进行权限认证时会优先判断
314
+ Object的权限,而Bucket的权限设置会被忽略。
315
+ 2. 允许匿名访问时(设置了public-read或者public-read-write权限),用户
316
+ 可以直接通过浏览器访问,例如:
317
+
318
+ http://bucket-name.oss-cn-hangzhou.aliyuncs.com/object.jpg
319
+
320
+ 3. 访问具有public权限的Bucket/Object时,也可以通过创建匿名的Client来进行:
321
+
322
+ # 不填access_key_id和access_key_secret,将创建匿名Client,只能访问
323
+ # 具有public权限的Bucket/Object
324
+ client = Client.new(:endpoint => 'oss-cn-hangzhou.aliyuncs.com')
325
+ bucket = client.get_bucket('public-bucket')
326
+ obj = bucket.get_object('public-object', :file => local_file)
327
+
328
+ ## 运行examples
329
+
330
+ SDK的examples/目录下有一些展示SDK功能的示例程序,用户稍加配置就可以直
331
+ 接运行。examples需要的权限信息和bucket信息从用户`HOME`目录下的配置文件
332
+ `~/.oss.yml`中读取,其中应该包含以下字段:
333
+
334
+ endpoint: oss-cn-hangzhou.aliyuncs.com
335
+ cname: false
336
+ id: ACCESS KEY ID
337
+ key: ACCESS KEY SECRET
338
+ bucket: BUCKET NAME
339
+
340
+ 用户需要创建(如果不存在)或者修改其中的内容,然后运行:
341
+
342
+ ruby examples/aliyun/oss/bucket.rb
343
+
344
+ ## 运行测试
345
+
346
+ SDK采用rspec进行测试,如果要对SDK进行修改,请确保没有break现有测试。测
347
+ 试运行的方法是,在ruby-sdk/目录下运行:
348
+
349
+ rspec
350
+
351
+ 或者用bundle和rake:
352
+
353
+ bundle exec rake spec
354
+
355
+ ## 更多
356
+
357
+ 更多文档请查看:
358
+
359
+ - 阿里云官网文档:http://help.aliyun.com/product/8314910_oss.html
360
+
361
+
362
+ [1]: http://help.aliyun.com/document_detail/oss/product-documentation/domain-region.html
363
+
364
+ [2]: http://help.aliyun.com/document_detail/oss/product-documentation/function/cname.html
data/lib/aliyun/oss.rb ADDED
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require_relative 'oss/version'
4
+ require_relative 'oss/logging'
5
+ require_relative 'oss/util'
6
+ require_relative 'oss/exception'
7
+ require_relative 'oss/struct'
8
+ require_relative 'oss/config'
9
+ require_relative 'oss/http'
10
+ require_relative 'oss/protocol'
11
+ require_relative 'oss/multipart'
12
+ require_relative 'oss/upload'
13
+ require_relative 'oss/download'
14
+ require_relative 'oss/iterator'
15
+ require_relative 'oss/object'
16
+ require_relative 'oss/bucket'
17
+ require_relative 'oss/client'
@@ -0,0 +1,555 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module Aliyun
4
+ module OSS
5
+ ##
6
+ # Bucket是用户的Object相关的操作的client,主要包括三部分功能:
7
+ # 1. bucket相关:获取/设置bucket的属性(acl, logging, referer,
8
+ # website, lifecycle, cors)
9
+ # 2. object相关:上传、下载、追加、拷贝object等
10
+ # 3. multipart相关:断点续传、断点续载
11
+ class Bucket < Struct::Base
12
+
13
+ attrs :name, :location, :creation_time
14
+
15
+ def initialize(opts = {}, protocol = nil)
16
+ super(opts)
17
+ @protocol = protocol
18
+ end
19
+
20
+ ### Bucket相关的API ###
21
+
22
+ # 获取Bucket的ACL
23
+ # @return [String] Bucket的{OSS::ACL ACL}
24
+ def acl
25
+ @protocol.get_bucket_acl(name)
26
+ end
27
+
28
+ # 设置Bucket的ACL
29
+ # @param acl [String] Bucket的{OSS::ACL ACL}
30
+ def acl=(acl)
31
+ @protocol.put_bucket_acl(name, acl)
32
+ end
33
+
34
+ # 获取Bucket的logging配置
35
+ # @return [BucketLogging] Bucket的logging配置
36
+ def logging
37
+ @protocol.get_bucket_logging(name)
38
+ end
39
+
40
+ # 设置Bucket的logging配置
41
+ # @param logging [BucketLogging] logging配置
42
+ def logging=(logging)
43
+ if logging.enabled?
44
+ @protocol.put_bucket_logging(name, logging)
45
+ else
46
+ @protocol.delete_bucket_logging(name)
47
+ end
48
+ end
49
+
50
+ # 获取Bucket的website配置
51
+ # @return [BucketWebsite] Bucket的website配置
52
+ def website
53
+ begin
54
+ w = @protocol.get_bucket_website(name)
55
+ rescue ServerError => e
56
+ raise unless e.http_code == 404
57
+ end
58
+
59
+ w || BucketWebsite.new
60
+ end
61
+
62
+ # 设置Bucket的website配置
63
+ # @param website [BucketWebsite] website配置
64
+ def website=(website)
65
+ if website.enabled?
66
+ @protocol.put_bucket_website(name, website)
67
+ else
68
+ @protocol.delete_bucket_website(name)
69
+ end
70
+ end
71
+
72
+ # 获取Bucket的Referer配置
73
+ # @return [BucketReferer] Bucket的Referer配置
74
+ def referer
75
+ @protocol.get_bucket_referer(name)
76
+ end
77
+
78
+ # 设置Bucket的Referer配置
79
+ # @param referer [BucketReferer] Referer配置
80
+ def referer=(referer)
81
+ @protocol.put_bucket_referer(name, referer)
82
+ end
83
+
84
+ # 获取Bucket的生命周期配置
85
+ # @return [Array<OSS::LifeCycleRule>] Bucket的生命周期规则,如果
86
+ # 当前Bucket未设置lifecycle,则返回[]
87
+ def lifecycle
88
+ begin
89
+ r = @protocol.get_bucket_lifecycle(name)
90
+ rescue ServerError => e
91
+ raise unless e.http_code == 404
92
+ end
93
+
94
+ r || []
95
+ end
96
+
97
+ # 设置Bucket的生命周期配置
98
+ # @param rules [Array<OSS::LifeCycleRule>] 生命
99
+ # 周期配置规则
100
+ # @see OSS::LifeCycleRule 查看如何设置生命周期规则
101
+ # @note 如果rules为空,则会删除这个bucket上的lifecycle配置
102
+ def lifecycle=(rules)
103
+ if rules.empty?
104
+ @protocol.delete_bucket_lifecycle(name)
105
+ else
106
+ @protocol.put_bucket_lifecycle(name, rules)
107
+ end
108
+ end
109
+
110
+ # 获取Bucket的跨域资源共享(CORS)的规则
111
+ # @return [Array<OSS::CORSRule>] Bucket的CORS规则,如果当前
112
+ # Bucket未设置CORS规则,则返回[]
113
+ def cors
114
+ begin
115
+ r = @protocol.get_bucket_cors(name)
116
+ rescue ServerError => e
117
+ raise unless e.http_code == 404
118
+ end
119
+
120
+ r || []
121
+ end
122
+
123
+ # 设置Bucket的跨域资源共享(CORS)的规则
124
+ # @param rules [Array<OSS::CORSRule>] CORS规则
125
+ # @note 如果rules为空,则会删除这个bucket上的CORS配置
126
+ def cors=(rules)
127
+ if rules.empty?
128
+ @protocol.delete_bucket_cors(name)
129
+ else
130
+ @protocol.set_bucket_cors(name, rules)
131
+ end
132
+ end
133
+
134
+ ### Object相关的API ###
135
+
136
+
137
+ # 列出bucket中的object
138
+ # @param opts [Hash] 查询选项
139
+ # @option opts [String] :prefix 返回的object的前缀,如果设置则只
140
+ # 返回那些名字以它为前缀的object
141
+ # @option opts [String] :delimiter 用于获取公共前缀的分隔符,从
142
+ # 前缀后面开始到第一个分隔符出现的位置之前的字符,作为公共前缀。
143
+ # @example
144
+ # 假设我们有如下objects:
145
+ # /foo/bar/obj1
146
+ # /foo/bar/obj2
147
+ # ...
148
+ # /foo/bar/obj9999999
149
+ # /foo/xxx/
150
+ # 用'foo/'作为前缀, '/'作为分隔符, 则得到的公共前缀是:
151
+ # '/foo/bar/', '/foo/xxx/'。它们恰好就是目录'/foo/'下的所有子目
152
+ # 录。用delimiter获取公共前缀的方法避免了查询当前bucket下的所有
153
+ # object(可能数量巨大),是用于模拟目录结构的常用做法。
154
+ # @option opts [String] :encoding 指定返回的响应中object名字的编
155
+ # 码方法,目前只支持{OSS::KeyEncoding::URL}编码方式。
156
+ # @return [Enumerator<Object>] 其中Object可能是{OSS::Object},也
157
+ # 可能是{String},此时它是一个公共前缀
158
+ # @example
159
+ # all = bucket.list_objects
160
+ # all.each do |i|
161
+ # if i.is_a?(Object)
162
+ # puts "Object: #{i.key}"
163
+ # else
164
+ # puts "Common prefix: #{i}"
165
+ # end
166
+ # end
167
+ def list_objects(opts = {})
168
+ Iterator::Objects.new(@protocol, name, opts).to_enum
169
+ end
170
+
171
+ # 向Bucket中上传一个object
172
+ # @param key [String] Object的名字
173
+ # @param opts [Hash] 上传object时的选项(可选)
174
+ # @option opts [String] :file 设置所上传的文件
175
+ # @option opts [String] :content_type 设置所上传的内容的
176
+ # Content-Type,默认是application/octet-stream
177
+ # @option opts [Hash] :metas 设置object的meta,这是一些用户自定
178
+ # 义的属性,它们会和object一起存储,在{#get_object_meta}的时候会
179
+ # 返回这些meta。属性的key不区分大小写。例如:{ 'year' => '2015' }
180
+ # @yield [HTTP::StreamWriter] 如果调
181
+ # 用的时候传递了block,则写入到object的数据由block指定
182
+ # @example 流式上传数据
183
+ # put_object('x'){ |stream| 100.times { |i| stream << i.to_s } }
184
+ # put_object('x'){ |stream| stream << get_data }
185
+ # @example 上传文件
186
+ # put_object('x', :file => '/tmp/x')
187
+ # @example 指定Content-Type和metas
188
+ # put_object('x', :file => '/tmp/x', :content_type => 'text/html',
189
+ # :metas => {'year' => '2015', 'people' => 'mary'})
190
+ # @note 采用streaming的方式时,提供的数据必须是有结束标记的数据。
191
+ # 因为put_object会不断地从StreamWriter中读取数据上传到OSS,直到
192
+ # 它读到的数据为nil停止。
193
+ # @note 如果opts中指定了:file,则block会被忽略
194
+ def put_object(key, opts = {}, &block)
195
+ file = opts[:file]
196
+ if file
197
+ opts[:content_type] = get_content_type(file)
198
+
199
+ @protocol.put_object(name, key, opts) do |sw|
200
+ File.open(File.expand_path(file), 'rb') do |f|
201
+ sw << f.read(Protocol::STREAM_CHUNK_SIZE) until f.eof?
202
+ end
203
+ end
204
+ else
205
+ @protocol.put_object(name, key, opts, &block)
206
+ end
207
+ end
208
+
209
+ # 从Bucket中下载一个object
210
+ # @param key [String] Object的名字
211
+ # @param opts [Hash] 下载Object的选项(可选)
212
+ # @option opts [Array<Integer>] :range 指定下载object的部分数据,
213
+ # range应只包含两个数字,表示一个*左开右闭*的bytes range
214
+ # @option opts [String] :file 指定将下载的object写入到文件中
215
+ # @option opts [Hash] :condition 指定下载object需要满足的条件
216
+ # * :if_modified_since (Time) 指定如果object的修改时间晚于这个值,则下载
217
+ # * :if_unmodified_since (Time) 指定如果object从这个时间后再无修改,则下载
218
+ # * :if_match_etag (String) 指定如果object的etag等于这个值,则下载
219
+ # * :if_unmatch_etag (String) 指定如果object的etag不等于这个值,则下载
220
+ # @option opts [Hash] :rewrite 指定下载object时Server端返回的响应头部字段的值
221
+ # * :content_type (String) 指定返回的响应中Content-Type的值
222
+ # * :content_language (String) 指定返回的响应中Content-Language的值
223
+ # * :expires (Time) 指定返回的响应中Expires的值
224
+ # * :cache_control (String) 指定返回的响应中Cache-Control的值
225
+ # * :content_disposition (String) 指定返回的响应中Content-Disposition的值
226
+ # * :content_encoding (String) 指定返回的响应中Content-Encoding的值
227
+ # @return [OSS::Object] 返回Object对象
228
+ # @yield [String] 如果调用的时候传递了block,则获取到的object的数据交由block处理
229
+ # @example 流式下载文件
230
+ # get_object('x'){ |chunk| handle_chunk_data(chunk) }
231
+ # @example 下载到本地文件
232
+ # get_object('x', :file => '/tmp/x')
233
+ # @example 指定检查条件
234
+ # get_object('x', :file => '/tmp/x', :condition => {:if_match_etag => 'etag'})
235
+ # @example 指定重写响应的header信息
236
+ # get_object('x', :file => '/tmp/x', :rewrite => {:content_type => 'text/html'})
237
+ # @note 如果opts中指定了`:file`,则block会被忽略
238
+ # @note 如果既没有指定`:file`也没有指定block,则只获取Object
239
+ # meta而不下载Object内容
240
+ def get_object(key, opts = {}, &block)
241
+ obj = nil
242
+ file = opts[:file]
243
+ if file
244
+ File.open(File.expand_path(file), 'wb') do |f|
245
+ obj = @protocol.get_object(name, key, opts) do |chunk|
246
+ f.write(chunk)
247
+ end
248
+ end
249
+ elsif block
250
+ obj = @protocol.get_object(name, key, opts, &block)
251
+ else
252
+ obj = @protocol.get_object_meta(name, key, opts)
253
+ end
254
+
255
+ obj
256
+ end
257
+
258
+ # 更新Object的metas
259
+ # @param key [String] Object的名字
260
+ # @param metas [Hash] Object的meta
261
+ # @param conditions [Hash] 指定更新Object meta需要满足的条件,
262
+ # 同{#get_object}
263
+ # @return [Hash] 更新后文件的信息
264
+ # * :etag [String] 更新后文件的ETag
265
+ # * :last_modified [Time] 更新后文件的最后修改时间
266
+ def update_object_metas(key, metas, conditions = {})
267
+ @protocol.copy_object(
268
+ name, key, key,
269
+ :meta_directive => MetaDirective::REPLACE,
270
+ :metas => metas,
271
+ :condition => conditions)
272
+ end
273
+
274
+ # 判断一个object是否存在
275
+ # @param key [String] Object的名字
276
+ # @return [Boolean] 如果Object存在返回true,否则返回false
277
+ def object_exists?(key)
278
+ begin
279
+ get_object(key)
280
+ return true
281
+ rescue ServerError => e
282
+ return false if e.http_code == 404
283
+ raise e
284
+ end
285
+
286
+ false
287
+ end
288
+
289
+ alias :object_exist? :object_exists?
290
+
291
+ # 向Bucket中的object追加内容。如果object不存在,则创建一个
292
+ # Appendable Object。
293
+ # @param key [String] Object的名字
294
+ # @param opts [Hash] 上传object时的选项(可选)
295
+ # @option opts [String] :file 指定追加的内容从文件中读取
296
+ # @option opts [String] :content_type 设置所上传的内容的
297
+ # Content-Type,默认是application/octet-stream
298
+ # @option opts [Hash] :metas 设置object的meta,这是一些用户自定
299
+ # 义的属性,它们会和object一起存储,在{#get_object_meta}的时候会
300
+ # 返回这些meta。属性的key不区分大小写。例如:{ 'year' => '2015' }
301
+ # @example 流式上传数据
302
+ # pos = append_object('x', 0){ |stream| 100.times { |i| stream << i.to_s } }
303
+ # append_object('x', pos){ |stream| stream << get_data }
304
+ # @example 上传文件
305
+ # append_object('x', 0, :file => '/tmp/x')
306
+ # @example 指定Content-Type和metas
307
+ # append_object('x', 0, :file => '/tmp/x', :content_type => 'text/html',
308
+ # :metas => {'year' => '2015', 'people' => 'mary'})
309
+ # @return [Integer] 返回下次append的位置
310
+ # @yield [HTTP::StreamWriter] 同 {#put_object}
311
+ def append_object(key, pos, opts = {}, &block)
312
+ next_pos = -1
313
+ file = opts[:file]
314
+ if file
315
+ opts[:content_type] = get_content_type(file)
316
+
317
+ next_pos = @protocol.append_object(name, key, pos, opts) do |sw|
318
+ File.open(File.expand_path(file), 'rb') do |f|
319
+ sw << f.read(Protocol::STREAM_CHUNK_SIZE) until f.eof?
320
+ end
321
+ end
322
+ else
323
+ next_pos = @protocol.append_object(name, key, pos, opts, &block)
324
+ end
325
+
326
+ next_pos
327
+ end
328
+
329
+ # 将Bucket中的一个object拷贝成另外一个object
330
+ # @param source [String] 源object名字
331
+ # @param dest [String] 目标object名字
332
+ # @param opts [Hash] 拷贝object时的选项(可选)
333
+ # @option opts [String] :acl 目标文件的acl属性,默认为private
334
+ # @option opts [String] :meta_directive 指定是否拷贝源object的
335
+ # meta信息,默认为{OSS::MetaDirective::COPY}:即拷贝object的时
336
+ # 候也拷贝meta信息。
337
+ # @option opts [Hash] :metas 设置object的meta,这是一些用户自定
338
+ # 义的属性,它们会和object一起存储,在{#get_object_meta}的时候会
339
+ # 返回这些meta。属性的key不区分大小写。例如:{ 'year' => '2015'
340
+ # }。如果:meta_directive为{OSS::MetaDirective::COPY},则:metas
341
+ # 会被忽略。
342
+ # @option opts [Hash] :condition 指定拷贝object需要满足的条件,
343
+ # 同 {#get_object}
344
+ # @return [Hash] 目标文件的信息
345
+ # * :etag [String] 目标文件的ETag
346
+ # * :last_modified [Time] 目标文件的最后修改时间
347
+ def copy_object(source, dest, opts = {})
348
+ @protocol.copy_object(name, source, dest, opts)
349
+ end
350
+
351
+ # 删除一个object
352
+ # @param key [String] Object的名字
353
+ def delete_object(key)
354
+ @protocol.delete_object(name, key)
355
+ end
356
+
357
+ # 批量删除object
358
+ # @param keys [Array<String>] Object的名字集合
359
+ # @param opts [Hash] 删除object的选项(可选)
360
+ # @option opts [Boolean] :quiet 指定是否允许Server返回成功删除的
361
+ # object
362
+ # @option opts [String] :encoding 指定Server返回的成功删除的
363
+ # object的名字的编码方式,目前只支持{OSS::KeyEncoding::URL}
364
+ # @return [Array<String>] 成功删除的object的名字,如果指定
365
+ # 了:quiet参数,则返回[]
366
+ def batch_delete_objects(keys, opts = {})
367
+ @protocol.batch_delete_objects(name, keys, opts)
368
+ end
369
+
370
+ # 设置object的ACL
371
+ # @param key [String] Object的名字
372
+ # @param acl [String] Object的{OSS::ACL ACL}
373
+ def set_object_acl(key, acl)
374
+ @protocol.put_object_acl(name, key, acl)
375
+ end
376
+
377
+ # 获取object的ACL
378
+ # @param key [String] Object的名字
379
+ # @return [String] object的{OSS::ACL ACL}
380
+ def get_object_acl(key)
381
+ @protocol.get_object_acl(name, key)
382
+ end
383
+
384
+ # 获取object的CORS规则
385
+ # @param key [String] Object的名字
386
+ # @return [OSS::CORSRule]
387
+ def get_object_cors(key)
388
+ @protocol.get_object_cors(name, key)
389
+ end
390
+
391
+ ##
392
+ # 断点续传相关的API
393
+ #
394
+
395
+ # 上传一个本地文件到bucket中的一个object,支持断点续传。指定的文
396
+ # 件会被分成多个分片进行上传,只有所有分片都上传成功整个文件才
397
+ # 上传成功。
398
+ # @param key [String] Object的名字
399
+ # @param file [String] 本地文件的路径
400
+ # @param opts [Hash] 上传文件的可选项
401
+ # @option opts [String] :content_type 设置所上传的内容的
402
+ # Content-Type,默认是application/octet-stream
403
+ # @option opts [Hash] :metas 设置object的meta,这是一些用户自定
404
+ # 义的属性,它们会和object一起存储,在{#get_object_meta}的时候会
405
+ # 返回这些meta。属性的key不区分大小写。例如:{ 'year' => '2015' }
406
+ # @option opts [Integer] :part_size 设置分片上传时每个分片的大小,
407
+ # 默认为1 MB。断点上传最多允许10000个分片,如果文件大于10000个
408
+ # 分片的大小,则每个分片的大小会大于1MB。
409
+ # @option opts [String] :cpt_file 断点续传的checkpoint文件,如果
410
+ # 指定的cpt文件不存在,则会在file所在目录创建一个默认的cpt文件,
411
+ # 命名方式为:file.cpt,其中file是用户要上传的文件。在上传的过
412
+ # 程中会不断更新此文件,成功完成上传后会删除此文件;如果指定的
413
+ # cpt文件已存在,则从cpt文件中记录的点继续上传。
414
+ # @option opts [Boolean] :disable_cpt 是否禁用checkpoint功能,如
415
+ # 果设置为true,则在上传的过程中不会写checkpoint文件,这意味着
416
+ # 上传失败后不能断点续传,而只能重新上传整个文件。如果这个值为
417
+ # true,则:cpt_file会被忽略。
418
+ # @yield [Float] 如果调用的时候传递了block,则会将上传进度交由
419
+ # block处理,进度值是一个0-1之间的小数
420
+ # @raise [CheckpointBrokenError] 如果cpt文件被损坏,则抛出此错误
421
+ # @raise [FileInconsistentError] 如果指定的文件与cpt中记录的不一
422
+ # 致,则抛出此错误
423
+ # @example
424
+ # bucket.resumable_upload('my-object', '/tmp/x') do |p|
425
+ # puts "Progress: #{(p * 100).round(2)} %"
426
+ # end
427
+ def resumable_upload(key, file, opts = {}, &block)
428
+ unless cpt_file = opts[:cpt_file]
429
+ cpt_file = get_cpt_file(file)
430
+ end
431
+
432
+ Multipart::Upload.new(
433
+ @protocol, options: opts,
434
+ progress: block,
435
+ object: key, bucket: name, creation_time: Time.now,
436
+ file: File.expand_path(file), cpt_file: cpt_file
437
+ ).run
438
+ end
439
+
440
+ # 下载bucket中的一个object到本地文件,支持断点续传。指定的object
441
+ # 会被分成多个分片进行下载,只有所有的分片都下载成功整个object才
442
+ # 下载成功。对于每个下载的分片,会在file所在目录建立一个临时文件
443
+ # file.part.N,下载成功后这些part文件会被合并成最后的file然后删
444
+ # 除。
445
+ # @param key [String] Object的名字
446
+ # @param file [String] 本地文件的路径
447
+ # @param opts [Hash] 下载文件的可选项
448
+ # @option opts [Integer] :part_size 设置分片上传时每个分片的大小,
449
+ # 默认为1 MB。断点下载最多允许100个分片,如果文件大于100个分片,
450
+ # 则每个分片的大小会大于1MB
451
+ # @option opts [String] :cpt_file 断点续传的checkpoint文件,如果
452
+ # 指定的cpt文件不存在,则会在file所在目录创建一个默认的cpt文件,
453
+ # 命名方式为:file.cpt,其中file是用户要下载的文件名。在下载的过
454
+ # 程中会不断更新此文件,成功完成下载后会删除此文件;如果指定的
455
+ # cpt文件已存在,则从cpt文件中记录的点继续下载。
456
+ # @option opts [Boolean] :disable_cpt 是否禁用checkpoint功能,如
457
+ # 果设置为true,则在下载的过程中不会写checkpoint文件,这意味着
458
+ # 下载失败后不能断点续传,而只能重新下载整个文件。如果这个值为true,
459
+ # 则:cpt_file会被忽略。
460
+ # @option opts [Hash] :condition 指定下载object需要满足的条件,
461
+ # 同 {#get_object}
462
+ # @option opts [Hash] :rewrite 指定下载object时Server端返回的响
463
+ # 应头部字段的值,同 {#get_object}
464
+ # @yield [Float] 如果调用的时候传递了block,则会将下载进度交由
465
+ # block处理,进度值是一个0-1之间的小数
466
+ # @raise [CheckpointBrokenError] 如果cpt文件被损坏,则抛出此错误
467
+ # @raise [ObjectInconsistentError] 如果指定的object的etag与cpt文
468
+ # 件中记录的不一致,则抛出错误
469
+ # @raise [PartMissingError] 如果已下载的部分(.part文件)找不到,
470
+ # 则抛出此错误
471
+ # @raise [PartInconsistentError] 如果已下载的部分(.part文件)的
472
+ # MD5值与cpt文件记录的不一致,则抛出此错误
473
+ # @note 已经下载的部分会在file所在的目录创建.part文件,命名方式
474
+ # 为file.part.N
475
+ # @example
476
+ # bucket.resumable_download('my-object', '/tmp/x') do |p|
477
+ # puts "Progress: #{(p * 100).round(2)} %"
478
+ # end
479
+ def resumable_download(key, file, opts = {}, &block)
480
+ unless cpt_file = opts[:cpt_file]
481
+ cpt_file = get_cpt_file(file)
482
+ end
483
+
484
+ Multipart::Download.new(
485
+ @protocol, options: opts,
486
+ progress: block,
487
+ object: key, bucket: name, creation_time: Time.now,
488
+ file: File.expand_path(file), cpt_file: cpt_file
489
+ ).run
490
+ end
491
+
492
+ # 获取Bucket的URL
493
+ # @return [String] Bucket的URL
494
+ def bucket_url
495
+ @protocol.get_request_url(name)
496
+ end
497
+
498
+ # 获取Object的URL
499
+ # @param [String] key Object的key
500
+ # @param [Boolean] sign 是否对URL进行签名,默认为是
501
+ # @param [Fixnum] expiry URL的有效时间,单位为秒,默认为60s
502
+ # @return [String] 用于直接访问Object的URL
503
+ def object_url(key, sign = true, expiry = 60)
504
+ url = @protocol.get_request_url(name, key)
505
+ return url unless sign
506
+
507
+ expires = Time.now.to_i + expiry
508
+ string_to_sign = "GET\n" +
509
+ "\n\n" +
510
+ "#{expires}\n" +
511
+ "/#{name}/#{key}"
512
+ signature = sign(string_to_sign)
513
+
514
+ query_string = {
515
+ 'Expires' => expires.to_s,
516
+ 'OSSAccessKeyId' => CGI.escape(access_key_id),
517
+ 'Signature' => CGI.escape(signature)
518
+ }.map { |k, v| "#{k}=#{v}" }.join('&')
519
+
520
+ [url, query_string].join('?')
521
+ end
522
+
523
+ # 获取用户所设置的ACCESS_KEY_ID
524
+ # @return [String] 用户的ACCESS_KEY_ID
525
+ def access_key_id
526
+ @protocol.get_access_key_id
527
+ end
528
+
529
+ # 用ACCESS_KEY_SECRET对内容进行签名
530
+ # @param [String] string_to_sign 要进行签名的内容
531
+ # @return [String] 生成的签名
532
+ def sign(string_to_sign)
533
+ @protocol.sign(string_to_sign)
534
+ end
535
+
536
+ private
537
+ # Infer the file's content type using MIME::Types
538
+ # @param file [String] the file path
539
+ # @return [String] the infered content type or nil if it fails
540
+ # to infer the content type
541
+ def get_content_type(file)
542
+ t = MIME::Types.of(file)
543
+ t.first.content_type unless t.empty?
544
+ end
545
+
546
+ # Get the checkpoint file path for file
547
+ # @param file [String] the file path
548
+ # @return [String] the checkpoint file path
549
+ def get_cpt_file(file)
550
+ "#{File.expand_path(file)}.cpt"
551
+ end
552
+
553
+ end # Bucket
554
+ end # OSS
555
+ end # Aliyun