aliyun-sdk 0.1.1

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.
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