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,124 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.expand_path("../../../../lib", __FILE__))
|
4
|
+
require 'yaml'
|
5
|
+
require 'aliyun/oss'
|
6
|
+
|
7
|
+
##
|
8
|
+
# 一般来说用户在上传object和下载object时只需要指定文件名就可以满足需要:
|
9
|
+
# - 在上传的时候client会从指定的文件中读取数据上传到OSS
|
10
|
+
# - 在下载的时候client会把从OSS下载的数据写入到指定的文件中
|
11
|
+
#
|
12
|
+
# 在某些情况下用户可能会需要流式地上传和下载:
|
13
|
+
# - 用户要写入到object中的数据不能立即得到全部,而是从网络中流式获取,
|
14
|
+
# 然后再一段一段地写入到OSS中
|
15
|
+
# - 用户要写入到object的数据是经过运算得出,每次得到一部分,用户不希望
|
16
|
+
# 保留所有的数据然后一次性写入到OSS
|
17
|
+
# - 用户下载的object很大,用户不希望一次性把它们下载到内存中,而是希望
|
18
|
+
# 获取一部分就处理一部分;用户也不希望把它先下载到文件中,然后再从文
|
19
|
+
# 件中读出来处理,这样会让数据经历不必要的拷贝
|
20
|
+
#
|
21
|
+
# 当然,对于流式上传的需求,我们可以使用OSS的appendable object来满足。
|
22
|
+
# 但是即使是normal object,利用sdk的streaming功能,也可以实现流式上传
|
23
|
+
# 和下载。
|
24
|
+
|
25
|
+
# 初始化OSS client
|
26
|
+
AliyunSDK::Common::Logging.set_log_level(Logger::DEBUG)
|
27
|
+
conf_file = '~/.oss.yml'
|
28
|
+
conf = YAML.load(File.read(File.expand_path(conf_file)))
|
29
|
+
bucket = AliyunSDK::OSS::Client.new(
|
30
|
+
:endpoint => conf['endpoint'],
|
31
|
+
:cname => conf['cname'],
|
32
|
+
:access_key_id => conf['access_key_id'],
|
33
|
+
:access_key_secret => conf['access_key_secret']).get_bucket(conf['bucket'])
|
34
|
+
|
35
|
+
# 辅助打印函数
|
36
|
+
def demo(msg)
|
37
|
+
puts "######### #{msg} ########"
|
38
|
+
puts
|
39
|
+
yield
|
40
|
+
puts "-------------------------"
|
41
|
+
puts
|
42
|
+
end
|
43
|
+
|
44
|
+
# 例子1: 归并排序
|
45
|
+
# 有两个文件sort.1, sort.2,它们分别存了一些从小到大排列的整数,每个整
|
46
|
+
# 数1行,现在要将它们做归并排序的结果上传到OSS中,命名为sort.all
|
47
|
+
|
48
|
+
local_1, local_2 = 'sort.1', 'sort.2'
|
49
|
+
result_object = 'sort.all'
|
50
|
+
|
51
|
+
File.open(File.expand_path(local_1), 'w') do |f|
|
52
|
+
[1001, 2005, 2007, 2011, 2013, 2015].each do |i|
|
53
|
+
f.puts(i.to_s)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
File.open(File.expand_path(local_2), 'w') do |f|
|
58
|
+
[2009, 2010, 2012, 2017, 2020, 9999].each do |i|
|
59
|
+
f.puts(i.to_s)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
demo "Streaming upload" do
|
64
|
+
bucket.put_object(result_object) do |content|
|
65
|
+
f1 = File.open(File.expand_path(local_1))
|
66
|
+
f2 = File.open(File.expand_path(local_2))
|
67
|
+
v1, v2 = f1.readline, f2.readline
|
68
|
+
|
69
|
+
until f1.eof? or f2.eof?
|
70
|
+
if v1.to_i < v2.to_i
|
71
|
+
content << v1
|
72
|
+
v1 = f1.readline
|
73
|
+
else
|
74
|
+
content << v2
|
75
|
+
v2 = f2.readline
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
[v1, v2].sort.each{|i| content << i}
|
80
|
+
content << f1.readline until f1.eof?
|
81
|
+
content << f2.readline until f2.eof?
|
82
|
+
end
|
83
|
+
|
84
|
+
puts "Put object: #{result_object}"
|
85
|
+
|
86
|
+
# 将文件下载下来查看
|
87
|
+
bucket.get_object(result_object, :file => result_object)
|
88
|
+
puts "Get object: #{result_object}"
|
89
|
+
puts "Content: #{File.read(result_object)}"
|
90
|
+
end
|
91
|
+
|
92
|
+
# 例子2: 下载进度条
|
93
|
+
# 下载一个大文件(10M),在下载的过程中打印下载进度
|
94
|
+
|
95
|
+
large_file = 'large_file'
|
96
|
+
|
97
|
+
demo "Streaming download" do
|
98
|
+
puts "Begin put object: #{large_file}"
|
99
|
+
# 利用streaming上传
|
100
|
+
bucket.put_object(large_file) do |stream|
|
101
|
+
10.times { stream << "x" * (1024 * 1024) }
|
102
|
+
end
|
103
|
+
|
104
|
+
# 查看object大小
|
105
|
+
object_size = bucket.get_object(large_file).size
|
106
|
+
puts "Put object: #{large_file}, size: #{object_size}"
|
107
|
+
|
108
|
+
# 流式下载文件,仅打印进度,不保存文件
|
109
|
+
def to_percentile(v)
|
110
|
+
"#{(v * 100.0).round(2)} %"
|
111
|
+
end
|
112
|
+
|
113
|
+
puts "Begin download: #{large_file}"
|
114
|
+
last_got, got = 0, 0
|
115
|
+
bucket.get_object(large_file) do |chunk|
|
116
|
+
got += chunk.size
|
117
|
+
# 仅在下载进度大于10%的时候打印
|
118
|
+
if (got - last_got).to_f / object_size > 0.1
|
119
|
+
puts "Progress: #{to_percentile(got.to_f / object_size)}"
|
120
|
+
last_got = got
|
121
|
+
end
|
122
|
+
end
|
123
|
+
puts "Get object: #{large_file}, size: #{object_size}"
|
124
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.expand_path("../../../../lib", __FILE__))
|
4
|
+
require 'yaml'
|
5
|
+
require 'aliyun/sts'
|
6
|
+
require 'aliyun/oss'
|
7
|
+
|
8
|
+
# 初始化OSS client
|
9
|
+
AliyunSDK::Common::Logging.set_log_level(Logger::DEBUG)
|
10
|
+
conf_file = '~/.sts.yml'
|
11
|
+
conf = YAML.load(File.read(File.expand_path(conf_file)))
|
12
|
+
|
13
|
+
# 辅助打印函数
|
14
|
+
def demo(msg)
|
15
|
+
puts "######### #{msg} ########"
|
16
|
+
puts
|
17
|
+
yield
|
18
|
+
puts "-------------------------"
|
19
|
+
puts
|
20
|
+
end
|
21
|
+
|
22
|
+
demo "Using STS" do
|
23
|
+
sts = AliyunSDK::STS::Client.new(
|
24
|
+
:access_key_id => conf['access_key_id'],
|
25
|
+
:access_key_secret => conf['access_key_secret'])
|
26
|
+
|
27
|
+
token = sts.assume_role(
|
28
|
+
'acs:ram::52352:role/aliyunosstokengeneratorrole', 'app-1')
|
29
|
+
|
30
|
+
client = AliyunSDK::OSS::Client.new(
|
31
|
+
:endpoint => 'http://oss-cn-hangzhou.aliyuncs.com',
|
32
|
+
:sts_token => token.security_token,
|
33
|
+
:access_key_id => token.access_key_id,
|
34
|
+
:access_key_secret => token.access_key_secret)
|
35
|
+
|
36
|
+
unless client.bucket_exists?('bucket-for-sts-test')
|
37
|
+
client.create_bucket('bucket-for-sts-test')
|
38
|
+
end
|
39
|
+
|
40
|
+
bucket = client.get_bucket('bucket-for-sts-test')
|
41
|
+
|
42
|
+
bucket.put_object('hello') { |s| s << 'hello' }
|
43
|
+
bucket.put_object('world') { |s| s << 'world' }
|
44
|
+
|
45
|
+
bucket.list_objects.take(10).each do |obj|
|
46
|
+
puts "Object: #{obj.key}, size: #{obj.size}"
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.expand_path("../../../../lib", __FILE__))
|
4
|
+
require 'yaml'
|
5
|
+
require 'aliyun/sts'
|
6
|
+
|
7
|
+
AliyunSDK::Common::Logging.set_log_level(Logger::DEBUG)
|
8
|
+
conf_file = '~/.sts.yml'
|
9
|
+
conf = YAML.load(File.read(File.expand_path(conf_file)))
|
10
|
+
client = AliyunSDK::STS::Client.new(
|
11
|
+
:access_key_id => conf['access_key_id'],
|
12
|
+
:access_key_secret => conf['access_key_secret'])
|
13
|
+
|
14
|
+
# 辅助打印函数
|
15
|
+
def demo(msg)
|
16
|
+
puts "######### #{msg} ########"
|
17
|
+
puts
|
18
|
+
yield
|
19
|
+
puts "-------------------------"
|
20
|
+
puts
|
21
|
+
end
|
22
|
+
|
23
|
+
token = client.assume_role(
|
24
|
+
'acs:ram::52352:role/aliyunosstokengeneratorrole', 'app-1')
|
25
|
+
|
26
|
+
demo "Assume role" do
|
27
|
+
begin
|
28
|
+
token = client.assume_role(
|
29
|
+
'acs:ram::52352:role/aliyunosstokengeneratorrole', 'app-1')
|
30
|
+
|
31
|
+
puts "Credentials for session: #{token.session_name}"
|
32
|
+
puts "access key id: #{token.access_key_id}"
|
33
|
+
puts "access key secret: #{token.access_key_secret}"
|
34
|
+
puts "security token: #{token.security_token}"
|
35
|
+
puts "expiration at: #{token.expiration}"
|
36
|
+
rescue => e
|
37
|
+
puts "AssumeRole failed: #{e.message}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
demo "Assume role with policy" do
|
42
|
+
begin
|
43
|
+
policy = AliyunSDK::STS::Policy.new
|
44
|
+
policy.allow(
|
45
|
+
['oss:Get*', 'oss:PutObject'],
|
46
|
+
['acs:oss:*:*:my-bucket', 'acs:oss:*:*:my-bucket/*'])
|
47
|
+
|
48
|
+
token = client.assume_role(
|
49
|
+
'acs:ram::52352:role/aliyunosstokengeneratorrole', 'app-2', policy, 900)
|
50
|
+
|
51
|
+
puts "Credentials for session: #{token.session_name}"
|
52
|
+
puts "access key id: #{token.access_key_id}"
|
53
|
+
puts "access key secret: #{token.access_key_secret}"
|
54
|
+
puts "security token: #{token.security_token}"
|
55
|
+
puts "expiration at: #{token.expiration}"
|
56
|
+
rescue => e
|
57
|
+
puts "AssumeRole failed: #{e.message}"
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module AliyunSDK
|
4
|
+
module Common
|
5
|
+
|
6
|
+
##
|
7
|
+
# Base exception class
|
8
|
+
#
|
9
|
+
class Exception < RuntimeError
|
10
|
+
attr_reader :message
|
11
|
+
|
12
|
+
def initialize(message)
|
13
|
+
@message = message
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end # Common
|
18
|
+
end # Aliyun
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module AliyunSDK
|
6
|
+
module Common
|
7
|
+
##
|
8
|
+
# Logging support
|
9
|
+
# @example
|
10
|
+
# include Logging
|
11
|
+
# logger.info(xxx)
|
12
|
+
module Logging
|
13
|
+
|
14
|
+
DEFAULT_LOG_FILE = "./aliyun_sdk.log"
|
15
|
+
MAX_NUM_LOG = 100
|
16
|
+
ROTATE_SIZE = 10 * 1024 * 1024
|
17
|
+
|
18
|
+
# level = Logger::DEBUG | Logger::INFO | Logger::ERROR | Logger::FATAL
|
19
|
+
def self.set_log_level(level)
|
20
|
+
Logging.logger.level = level
|
21
|
+
end
|
22
|
+
|
23
|
+
# 设置日志输出的文件
|
24
|
+
def self.set_log_file(file)
|
25
|
+
@log_file = file
|
26
|
+
end
|
27
|
+
|
28
|
+
# 获取logger
|
29
|
+
def logger
|
30
|
+
Logging.logger
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def self.logger
|
36
|
+
unless @logger
|
37
|
+
@logger = Logger.new(
|
38
|
+
@log_file ||= DEFAULT_LOG_FILE, MAX_NUM_LOG, ROTATE_SIZE)
|
39
|
+
@logger.level = Logger::INFO
|
40
|
+
end
|
41
|
+
@logger
|
42
|
+
end
|
43
|
+
|
44
|
+
end # logging
|
45
|
+
end # Common
|
46
|
+
end # Aliyun
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module AliyunSDK
|
4
|
+
module Common
|
5
|
+
|
6
|
+
# Common structs used. It provides a 'attrs' helper method for
|
7
|
+
# subclass to define its attributes. 'attrs' is based on
|
8
|
+
# attr_reader and provide additional functionalities for classes
|
9
|
+
# that inherits Struct::Base :
|
10
|
+
# * the constuctor is provided to accept options and set the
|
11
|
+
# corresponding attibute automatically
|
12
|
+
# * the #to_s method is rewrite to concatenate the defined
|
13
|
+
# attributes keys and values
|
14
|
+
# @example
|
15
|
+
# class X < Struct::Base
|
16
|
+
# attrs :foo, :bar
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# x.new(:foo => 'hello', :bar => 'world')
|
20
|
+
# x.foo # == "hello"
|
21
|
+
# x.bar # == "world"
|
22
|
+
# x.to_s # == "foo: hello, bar: world"
|
23
|
+
module Struct
|
24
|
+
class Base
|
25
|
+
module AttrHelper
|
26
|
+
def attrs(*s)
|
27
|
+
define_method(:attrs) {s}
|
28
|
+
attr_reader(*s)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
extend AttrHelper
|
33
|
+
|
34
|
+
def initialize(opts = {})
|
35
|
+
extra_keys = opts.keys - attrs
|
36
|
+
unless extra_keys.empty?
|
37
|
+
fail Common::Exception,
|
38
|
+
"Unexpected extra keys: #{extra_keys.join(', ')}"
|
39
|
+
end
|
40
|
+
|
41
|
+
attrs.each do |attr|
|
42
|
+
instance_variable_set("@#{attr}", opts[attr])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_s
|
47
|
+
attrs.map do |attr|
|
48
|
+
v = instance_variable_get("@#{attr}")
|
49
|
+
"#{attr.to_s}: #{v}"
|
50
|
+
end.join(", ")
|
51
|
+
end
|
52
|
+
end # Base
|
53
|
+
end # Struct
|
54
|
+
|
55
|
+
end # Common
|
56
|
+
end # Aliyun
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require_relative 'common'
|
4
|
+
require_relative 'oss/util'
|
5
|
+
require_relative 'oss/exception'
|
6
|
+
require_relative 'oss/struct'
|
7
|
+
require_relative 'oss/config'
|
8
|
+
require_relative 'oss/http'
|
9
|
+
require_relative 'oss/protocol'
|
10
|
+
require_relative 'oss/multipart'
|
11
|
+
require_relative 'oss/upload'
|
12
|
+
require_relative 'oss/download'
|
13
|
+
require_relative 'oss/iterator'
|
14
|
+
require_relative 'oss/object'
|
15
|
+
require_relative 'oss/bucket'
|
16
|
+
require_relative 'oss/client'
|
@@ -0,0 +1,661 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module AliyunSDK
|
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 < Common::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] :marker 如果设置,则只返回名字在它之后
|
142
|
+
# (字典序,不包含marker)的object
|
143
|
+
# @option opts [String] :delimiter 用于获取公共前缀的分隔符,从
|
144
|
+
# 前缀后面开始到第一个分隔符出现的位置之前的字符,作为公共前缀。
|
145
|
+
# @example
|
146
|
+
# 假设我们有如下objects:
|
147
|
+
# /foo/bar/obj1
|
148
|
+
# /foo/bar/obj2
|
149
|
+
# ...
|
150
|
+
# /foo/bar/obj9999999
|
151
|
+
# /foo/xxx/
|
152
|
+
# 用'foo/'作为前缀, '/'作为分隔符, 则得到的公共前缀是:
|
153
|
+
# '/foo/bar/', '/foo/xxx/'。它们恰好就是目录'/foo/'下的所有子目
|
154
|
+
# 录。用delimiter获取公共前缀的方法避免了查询当前bucket下的所有
|
155
|
+
# object(可能数量巨大),是用于模拟目录结构的常用做法。
|
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(
|
169
|
+
@protocol, name, opts.merge(encoding: KeyEncoding::URL)).to_enum
|
170
|
+
end
|
171
|
+
|
172
|
+
# 向Bucket中上传一个object
|
173
|
+
# @param key [String] Object的名字
|
174
|
+
# @param opts [Hash] 上传object时的选项(可选)
|
175
|
+
# @option opts [String] :file 设置所上传的文件
|
176
|
+
# @option opts [String] :content_type 设置所上传的内容的
|
177
|
+
# Content-Type,默认是application/octet-stream
|
178
|
+
# @option opts [Hash] :metas 设置object的meta,这是一些用户自定
|
179
|
+
# 义的属性,它们会和object一起存储,在{#get_object}的时候会
|
180
|
+
# 返回这些meta。属性的key不区分大小写。例如:{ 'year' => '2015' }
|
181
|
+
# @option opts [Callback] :callback 指定操作成功后OSS的
|
182
|
+
# 上传回调,上传成功后OSS会向用户的应用服务器发一个HTTP POST请
|
183
|
+
# 求,`:callback`参数指定这个请求的相关参数
|
184
|
+
# @option opts [Hash] :headers 指定请求的HTTP Header,不区分大小
|
185
|
+
# 写。这里指定的值会覆盖通过`:content_type`和`:metas`设置的值。
|
186
|
+
# @yield [HTTP::StreamWriter] 如果调用的时候传递了block,则写入
|
187
|
+
# 到object的数据由block指定
|
188
|
+
# @example 流式上传数据
|
189
|
+
# put_object('x'){ |stream| 100.times { |i| stream << i.to_s } }
|
190
|
+
# put_object('x'){ |stream| stream << get_data }
|
191
|
+
# @example 上传文件
|
192
|
+
# put_object('x', :file => '/tmp/x')
|
193
|
+
# @example 指定Content-Type和metas
|
194
|
+
# put_object('x', :file => '/tmp/x', :content_type => 'text/html',
|
195
|
+
# :metas => {'year' => '2015', 'people' => 'mary'})
|
196
|
+
# @example 指定Callback
|
197
|
+
# callback = AliyunSDK::OSS::Callback.new(
|
198
|
+
# url: 'http://10.101.168.94:1234/callback',
|
199
|
+
# query: {user: 'put_object'},
|
200
|
+
# body: 'bucket=${bucket}&object=${object}'
|
201
|
+
# )
|
202
|
+
#
|
203
|
+
# bucket.put_object('files/hello', callback: callback)
|
204
|
+
# @raise [CallbackError] 如果文件上传成功而Callback调用失败,抛
|
205
|
+
# 出此错误
|
206
|
+
# @note 如果opts中指定了`:file`,则block会被忽略
|
207
|
+
# @note 如果指定了`:callback`,则可能文件上传成功,但是callback
|
208
|
+
# 执行失败,此时会抛出{OSS::CallbackError},用户可以选择接住这
|
209
|
+
# 个异常,以忽略Callback调用错误
|
210
|
+
def put_object(key, opts = {}, &block)
|
211
|
+
args = opts.dup
|
212
|
+
|
213
|
+
file = args[:file]
|
214
|
+
args[:content_type] ||= get_content_type(file) if file
|
215
|
+
args[:content_type] ||= get_content_type(key)
|
216
|
+
|
217
|
+
if file
|
218
|
+
@protocol.put_object(name, key, args) do |sw|
|
219
|
+
File.open(File.expand_path(file), 'rb') do |f|
|
220
|
+
sw << f.read(Protocol::STREAM_CHUNK_SIZE) until f.eof?
|
221
|
+
end
|
222
|
+
end
|
223
|
+
else
|
224
|
+
@protocol.put_object(name, key, args, &block)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# 从Bucket中下载一个object
|
229
|
+
# @param key [String] Object的名字
|
230
|
+
# @param opts [Hash] 下载Object的选项(可选)
|
231
|
+
# @option opts [Array<Integer>] :range 指定下载object的部分数据,
|
232
|
+
# range应只包含两个数字,表示一个*左开右闭*的bytes range
|
233
|
+
# @option opts [String] :file 指定将下载的object写入到文件中
|
234
|
+
# @option opts [Hash] :condition 指定下载object需要满足的条件
|
235
|
+
# * :if_modified_since (Time) 指定如果object的修改时间晚于这个值,则下载
|
236
|
+
# * :if_unmodified_since (Time) 指定如果object从这个时间后再无修改,则下载
|
237
|
+
# * :if_match_etag (String) 指定如果object的etag等于这个值,则下载
|
238
|
+
# * :if_unmatch_etag (String) 指定如果object的etag不等于这个值,则下载
|
239
|
+
# @option opts [Hash] :headers 指定请求的HTTP Header,不区分大小
|
240
|
+
# 写。这里指定的值会覆盖通过`:range`和`:condition`设置的值。
|
241
|
+
# @option opts [Hash] :rewrite 指定下载object时Server端返回的响应头部字段的值
|
242
|
+
# * :content_type (String) 指定返回的响应中Content-Type的值
|
243
|
+
# * :content_language (String) 指定返回的响应中Content-Language的值
|
244
|
+
# * :expires (Time) 指定返回的响应中Expires的值
|
245
|
+
# * :cache_control (String) 指定返回的响应中Cache-Control的值
|
246
|
+
# * :content_disposition (String) 指定返回的响应中Content-Disposition的值
|
247
|
+
# * :content_encoding (String) 指定返回的响应中Content-Encoding的值
|
248
|
+
# @return [OSS::Object] 返回Object对象
|
249
|
+
# @yield [String] 如果调用的时候传递了block,则获取到的object的数据交由block处理
|
250
|
+
# @example 流式下载文件
|
251
|
+
# get_object('x'){ |chunk| handle_chunk_data(chunk) }
|
252
|
+
# @example 下载到本地文件
|
253
|
+
# get_object('x', :file => '/tmp/x')
|
254
|
+
# @example 指定检查条件
|
255
|
+
# get_object('x', :file => '/tmp/x', :condition => {:if_match_etag => 'etag'})
|
256
|
+
# @example 指定重写响应的header信息
|
257
|
+
# get_object('x', :file => '/tmp/x', :rewrite => {:content_type => 'text/html'})
|
258
|
+
# @note 如果opts中指定了`:file`,则block会被忽略
|
259
|
+
# @note 如果既没有指定`:file`也没有指定block,则只获取Object
|
260
|
+
# meta而不下载Object内容
|
261
|
+
def get_object(key, opts = {}, &block)
|
262
|
+
obj = nil
|
263
|
+
file = opts[:file]
|
264
|
+
if file
|
265
|
+
File.open(File.expand_path(file), 'wb') do |f|
|
266
|
+
obj = @protocol.get_object(name, key, opts) do |chunk|
|
267
|
+
f.write(chunk)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
elsif block
|
271
|
+
obj = @protocol.get_object(name, key, opts, &block)
|
272
|
+
else
|
273
|
+
obj = @protocol.get_object_meta(name, key, opts)
|
274
|
+
end
|
275
|
+
|
276
|
+
obj
|
277
|
+
end
|
278
|
+
|
279
|
+
# 更新Object的metas
|
280
|
+
# @param key [String] Object的名字
|
281
|
+
# @param metas [Hash] Object的meta
|
282
|
+
# @param conditions [Hash] 指定更新Object meta需要满足的条件,
|
283
|
+
# 同{#get_object}
|
284
|
+
# @return [Hash] 更新后文件的信息
|
285
|
+
# * :etag [String] 更新后文件的ETag
|
286
|
+
# * :last_modified [Time] 更新后文件的最后修改时间
|
287
|
+
def update_object_metas(key, metas, conditions = {})
|
288
|
+
@protocol.copy_object(
|
289
|
+
name, key, key,
|
290
|
+
:meta_directive => MetaDirective::REPLACE,
|
291
|
+
:metas => metas,
|
292
|
+
:condition => conditions)
|
293
|
+
end
|
294
|
+
|
295
|
+
# 判断一个object是否存在
|
296
|
+
# @param key [String] Object的名字
|
297
|
+
# @return [Boolean] 如果Object存在返回true,否则返回false
|
298
|
+
def object_exists?(key)
|
299
|
+
begin
|
300
|
+
get_object(key)
|
301
|
+
return true
|
302
|
+
rescue ServerError => e
|
303
|
+
return false if e.http_code == 404
|
304
|
+
raise e
|
305
|
+
end
|
306
|
+
|
307
|
+
false
|
308
|
+
end
|
309
|
+
|
310
|
+
alias :object_exist? :object_exists?
|
311
|
+
|
312
|
+
# 向Bucket中的object追加内容。如果object不存在,则创建一个
|
313
|
+
# Appendable Object。
|
314
|
+
# @param key [String] Object的名字
|
315
|
+
# @param opts [Hash] 上传object时的选项(可选)
|
316
|
+
# @option opts [String] :file 指定追加的内容从文件中读取
|
317
|
+
# @option opts [String] :content_type 设置所上传的内容的
|
318
|
+
# Content-Type,默认是application/octet-stream
|
319
|
+
# @option opts [Hash] :metas 设置object的meta,这是一些用户自定
|
320
|
+
# 义的属性,它们会和object一起存储,在{#get_object}的时候会
|
321
|
+
# 返回这些meta。属性的key不区分大小写。例如:{ 'year' => '2015' }
|
322
|
+
# @option opts [Hash] :headers 指定请求的HTTP Header,不区分大小
|
323
|
+
# 写。这里指定的值会覆盖通过`:content_type`和`:metas`设置的值。
|
324
|
+
# @example 流式上传数据
|
325
|
+
# pos = append_object('x', 0){ |stream| 100.times { |i| stream << i.to_s } }
|
326
|
+
# append_object('x', pos){ |stream| stream << get_data }
|
327
|
+
# @example 上传文件
|
328
|
+
# append_object('x', 0, :file => '/tmp/x')
|
329
|
+
# @example 指定Content-Type和metas
|
330
|
+
# append_object('x', 0, :file => '/tmp/x', :content_type => 'text/html',
|
331
|
+
# :metas => {'year' => '2015', 'people' => 'mary'})
|
332
|
+
# @return [Integer] 返回下次append的位置
|
333
|
+
# @yield [HTTP::StreamWriter] 同 {#put_object}
|
334
|
+
def append_object(key, pos, opts = {}, &block)
|
335
|
+
args = opts.dup
|
336
|
+
|
337
|
+
file = args[:file]
|
338
|
+
args[:content_type] ||= get_content_type(file) if file
|
339
|
+
args[:content_type] ||= get_content_type(key)
|
340
|
+
|
341
|
+
if file
|
342
|
+
next_pos = @protocol.append_object(name, key, pos, args) do |sw|
|
343
|
+
File.open(File.expand_path(file), 'rb') do |f|
|
344
|
+
sw << f.read(Protocol::STREAM_CHUNK_SIZE) until f.eof?
|
345
|
+
end
|
346
|
+
end
|
347
|
+
else
|
348
|
+
next_pos = @protocol.append_object(name, key, pos, args, &block)
|
349
|
+
end
|
350
|
+
|
351
|
+
next_pos
|
352
|
+
end
|
353
|
+
|
354
|
+
# 将Bucket中的一个object拷贝成另外一个object
|
355
|
+
# @param source [String] 源object名字
|
356
|
+
# @param dest [String] 目标object名字
|
357
|
+
# @param opts [Hash] 拷贝object时的选项(可选)
|
358
|
+
# @option opts [String] :src_bucket 源object所属的Bucket,默认与
|
359
|
+
# 目标文件为同一个Bucket。源Bucket与目标Bucket必须属于同一个Region。
|
360
|
+
# @option opts [String] :acl 目标文件的acl属性,默认为private
|
361
|
+
# @option opts [String] :meta_directive 指定是否拷贝源object的
|
362
|
+
# meta信息,默认为{OSS::MetaDirective::COPY}:即拷贝object的时
|
363
|
+
# 候也拷贝meta信息。
|
364
|
+
# @option opts [Hash] :metas 设置object的meta,这是一些用户自定
|
365
|
+
# 义的属性,它们会和object一起存储,在{#get_object}的时候会
|
366
|
+
# 返回这些meta。属性的key不区分大小写。例如:{ 'year' => '2015'
|
367
|
+
# }。如果:meta_directive为{OSS::MetaDirective::COPY},则:metas
|
368
|
+
# 会被忽略。
|
369
|
+
# @option opts [Hash] :condition 指定拷贝object需要满足的条件,
|
370
|
+
# 同 {#get_object}
|
371
|
+
# @return [Hash] 目标文件的信息
|
372
|
+
# * :etag [String] 目标文件的ETag
|
373
|
+
# * :last_modified [Time] 目标文件的最后修改时间
|
374
|
+
def copy_object(source, dest, opts = {})
|
375
|
+
args = opts.dup
|
376
|
+
|
377
|
+
args[:content_type] ||= get_content_type(dest)
|
378
|
+
@protocol.copy_object(name, source, dest, args)
|
379
|
+
end
|
380
|
+
|
381
|
+
# 删除一个object
|
382
|
+
# @param key [String] Object的名字
|
383
|
+
def delete_object(key)
|
384
|
+
@protocol.delete_object(name, key)
|
385
|
+
end
|
386
|
+
|
387
|
+
# 批量删除object
|
388
|
+
# @param keys [Array<String>] Object的名字集合
|
389
|
+
# @param opts [Hash] 删除object的选项(可选)
|
390
|
+
# @option opts [Boolean] :quiet 指定是否允许Server返回成功删除的
|
391
|
+
# object,默认为false,即返回删除结果
|
392
|
+
# @return [Array<String>] 成功删除的object的名字,如果指定
|
393
|
+
# 了:quiet参数,则返回[]
|
394
|
+
def batch_delete_objects(keys, opts = {})
|
395
|
+
@protocol.batch_delete_objects(
|
396
|
+
name, keys, opts.merge(encoding: KeyEncoding::URL))
|
397
|
+
end
|
398
|
+
|
399
|
+
# 设置object的ACL
|
400
|
+
# @param key [String] Object的名字
|
401
|
+
# @param acl [String] Object的{OSS::ACL ACL}
|
402
|
+
def set_object_acl(key, acl)
|
403
|
+
@protocol.put_object_acl(name, key, acl)
|
404
|
+
end
|
405
|
+
|
406
|
+
# 获取object的ACL
|
407
|
+
# @param key [String] Object的名字
|
408
|
+
# @return [String] object的{OSS::ACL ACL}
|
409
|
+
def get_object_acl(key)
|
410
|
+
@protocol.get_object_acl(name, key)
|
411
|
+
end
|
412
|
+
|
413
|
+
# 获取object的CORS规则
|
414
|
+
# @param key [String] Object的名字
|
415
|
+
# @return [OSS::CORSRule]
|
416
|
+
def get_object_cors(key)
|
417
|
+
@protocol.get_object_cors(name, key)
|
418
|
+
end
|
419
|
+
|
420
|
+
##
|
421
|
+
# 断点续传相关的API
|
422
|
+
#
|
423
|
+
|
424
|
+
# 上传一个本地文件到bucket中的一个object,支持断点续传。指定的文
|
425
|
+
# 件会被分成多个分片进行上传,只有所有分片都上传成功整个文件才
|
426
|
+
# 上传成功。
|
427
|
+
# @param key [String] Object的名字
|
428
|
+
# @param file [String] 本地文件的路径
|
429
|
+
# @param opts [Hash] 上传文件的可选项
|
430
|
+
# @option opts [String] :content_type 设置所上传的内容的
|
431
|
+
# Content-Type,默认是application/octet-stream
|
432
|
+
# @option opts [Hash] :metas 设置object的meta,这是一些用户自定
|
433
|
+
# 义的属性,它们会和object一起存储,在{#get_object}的时候会
|
434
|
+
# 返回这些meta。属性的key不区分大小写。例如:{ 'year' => '2015' }
|
435
|
+
# @option opts [Integer] :part_size 设置分片上传时每个分片的大小,
|
436
|
+
# 默认为10 MB。断点上传最多允许10000个分片,如果文件大于10000个
|
437
|
+
# 分片的大小,则每个分片的大小会大于10MB。
|
438
|
+
# @option opts [String] :cpt_file 断点续传的checkpoint文件,如果
|
439
|
+
# 指定的cpt文件不存在,则会在file所在目录创建一个默认的cpt文件,
|
440
|
+
# 命名方式为:file.cpt,其中file是用户要上传的文件。在上传的过
|
441
|
+
# 程中会不断更新此文件,成功完成上传后会删除此文件;如果指定的
|
442
|
+
# cpt文件已存在,则从cpt文件中记录的点继续上传。
|
443
|
+
# @option opts [Boolean] :disable_cpt 是否禁用checkpoint功能,如
|
444
|
+
# 果设置为true,则在上传的过程中不会写checkpoint文件,这意味着
|
445
|
+
# 上传失败后不能断点续传,而只能重新上传整个文件。如果这个值为
|
446
|
+
# true,则:cpt_file会被忽略。
|
447
|
+
# @option opts [Callback] :callback 指定文件上传成功后OSS的
|
448
|
+
# 上传回调,上传成功后OSS会向用户的应用服务器发一个HTTP POST请
|
449
|
+
# 求,`:callback`参数指定这个请求的相关参数
|
450
|
+
# @option opts [Hash] :headers 指定请求的HTTP Header,不区分大小
|
451
|
+
# 写。这里指定的值会覆盖通过`:content_type`和`:metas`设置的值。
|
452
|
+
# @yield [Float] 如果调用的时候传递了block,则会将上传进度交由
|
453
|
+
# block处理,进度值是一个0-1之间的小数
|
454
|
+
# @raise [CheckpointBrokenError] 如果cpt文件被损坏,则抛出此错误
|
455
|
+
# @raise [FileInconsistentError] 如果指定的文件与cpt中记录的不一
|
456
|
+
# 致,则抛出此错误
|
457
|
+
# @raise [CallbackError] 如果文件上传成功而Callback调用失败,抛
|
458
|
+
# 出此错误
|
459
|
+
# @example
|
460
|
+
# bucket.resumable_upload('my-object', '/tmp/x') do |p|
|
461
|
+
# puts "Progress: #{(p * 100).round(2)} %"
|
462
|
+
# end
|
463
|
+
# @example 指定Callback
|
464
|
+
# callback = AliyunSDK::OSS::Callback.new(
|
465
|
+
# url: 'http://10.101.168.94:1234/callback',
|
466
|
+
# query: {user: 'put_object'},
|
467
|
+
# body: 'bucket=${bucket}&object=${object}'
|
468
|
+
# )
|
469
|
+
#
|
470
|
+
# bucket.resumable_upload('files/hello', '/tmp/x', callback: callback)
|
471
|
+
# @note 如果指定了`:callback`,则可能文件上传成功,但是callback
|
472
|
+
# 执行失败,此时会抛出{OSS::CallbackError},用户可以选择接住这
|
473
|
+
# 个异常,以忽略Callback调用错误
|
474
|
+
def resumable_upload(key, file, opts = {}, &block)
|
475
|
+
args = opts.dup
|
476
|
+
|
477
|
+
args[:content_type] ||= get_content_type(file)
|
478
|
+
args[:content_type] ||= get_content_type(key)
|
479
|
+
cpt_file = args[:cpt_file] || get_cpt_file(file)
|
480
|
+
|
481
|
+
Multipart::Upload.new(
|
482
|
+
@protocol, options: args,
|
483
|
+
progress: block,
|
484
|
+
object: key, bucket: name, creation_time: Time.now,
|
485
|
+
file: File.expand_path(file), cpt_file: cpt_file
|
486
|
+
).run
|
487
|
+
end
|
488
|
+
|
489
|
+
# 下载bucket中的一个object到本地文件,支持断点续传。指定的object
|
490
|
+
# 会被分成多个分片进行下载,只有所有的分片都下载成功整个object才
|
491
|
+
# 下载成功。对于每个下载的分片,会在file所在目录建立一个临时文件
|
492
|
+
# file.part.N,下载成功后这些part文件会被合并成最后的file然后删
|
493
|
+
# 除。
|
494
|
+
# @param key [String] Object的名字
|
495
|
+
# @param file [String] 本地文件的路径
|
496
|
+
# @param opts [Hash] 下载文件的可选项
|
497
|
+
# @option opts [Integer] :part_size 设置分片上传时每个分片的大小,
|
498
|
+
# 默认为10 MB。断点下载最多允许100个分片,如果文件大于100个分片,
|
499
|
+
# 则每个分片的大小会大于10 MB
|
500
|
+
# @option opts [String] :cpt_file 断点续传的checkpoint文件,如果
|
501
|
+
# 指定的cpt文件不存在,则会在file所在目录创建一个默认的cpt文件,
|
502
|
+
# 命名方式为:file.cpt,其中file是用户要下载的文件名。在下载的过
|
503
|
+
# 程中会不断更新此文件,成功完成下载后会删除此文件;如果指定的
|
504
|
+
# cpt文件已存在,则从cpt文件中记录的点继续下载。
|
505
|
+
# @option opts [Boolean] :disable_cpt 是否禁用checkpoint功能,如
|
506
|
+
# 果设置为true,则在下载的过程中不会写checkpoint文件,这意味着
|
507
|
+
# 下载失败后不能断点续传,而只能重新下载整个文件。如果这个值为true,
|
508
|
+
# 则:cpt_file会被忽略。
|
509
|
+
# @option opts [Hash] :condition 指定下载object需要满足的条件,
|
510
|
+
# 同 {#get_object}
|
511
|
+
# @option opts [Hash] :headers 指定请求的HTTP Header,不区分大小
|
512
|
+
# 写。这里指定的值会覆盖通过`:condition`设置的值。
|
513
|
+
# @option opts [Hash] :rewrite 指定下载object时Server端返回的响
|
514
|
+
# 应头部字段的值,同 {#get_object}
|
515
|
+
# @yield [Float] 如果调用的时候传递了block,则会将下载进度交由
|
516
|
+
# block处理,进度值是一个0-1之间的小数
|
517
|
+
# @raise [CheckpointBrokenError] 如果cpt文件被损坏,则抛出此错误
|
518
|
+
# @raise [ObjectInconsistentError] 如果指定的object的etag与cpt文
|
519
|
+
# 件中记录的不一致,则抛出错误
|
520
|
+
# @raise [PartMissingError] 如果已下载的部分(.part文件)找不到,
|
521
|
+
# 则抛出此错误
|
522
|
+
# @raise [PartInconsistentError] 如果已下载的部分(.part文件)的
|
523
|
+
# MD5值与cpt文件记录的不一致,则抛出此错误
|
524
|
+
# @note 已经下载的部分会在file所在的目录创建.part文件,命名方式
|
525
|
+
# 为file.part.N
|
526
|
+
# @example
|
527
|
+
# bucket.resumable_download('my-object', '/tmp/x') do |p|
|
528
|
+
# puts "Progress: #{(p * 100).round(2)} %"
|
529
|
+
# end
|
530
|
+
def resumable_download(key, file, opts = {}, &block)
|
531
|
+
args = opts.dup
|
532
|
+
|
533
|
+
args[:content_type] ||= get_content_type(file)
|
534
|
+
args[:content_type] ||= get_content_type(key)
|
535
|
+
cpt_file = args[:cpt_file] || get_cpt_file(file)
|
536
|
+
|
537
|
+
Multipart::Download.new(
|
538
|
+
@protocol, options: args,
|
539
|
+
progress: block,
|
540
|
+
object: key, bucket: name, creation_time: Time.now,
|
541
|
+
file: File.expand_path(file), cpt_file: cpt_file
|
542
|
+
).run
|
543
|
+
end
|
544
|
+
|
545
|
+
# 列出此Bucket中正在进行的multipart上传请求,不包括已经完成或者
|
546
|
+
# 被取消的。
|
547
|
+
# @param [Hash] opts 可选项
|
548
|
+
# @option opts [String] :key_marker object key的标记,根据有没有
|
549
|
+
# 设置:id_marker,:key_marker的含义不同:
|
550
|
+
# 1. 如果未设置:id_marker,则只返回object key在:key_marker之后
|
551
|
+
# (字典序,不包含marker)的upload请求
|
552
|
+
# 2. 如果设置了:id_marker,则返回object key在:key_marker之后
|
553
|
+
# (字典序,不包含marker)的uplaod请求*和*Object
|
554
|
+
# key与:key_marker相等,*且*upload id在:id_marker之后(字母
|
555
|
+
# 表顺序排序,不包含marker)的upload请求
|
556
|
+
# @option opts [String] :id_marker upload id的标记,如
|
557
|
+
# 果:key_marker没有设置,则此参数会被忽略;否则与:key_marker一起
|
558
|
+
# 决定返回的结果(见上)
|
559
|
+
# @option opts [String] :prefix 如果指定,则只返回object key中符
|
560
|
+
# 合指定前缀的upload请求
|
561
|
+
# @return [Enumerator<Multipart::Transaction>] 其中每一个元素表
|
562
|
+
# 示一个upload请求
|
563
|
+
# @example
|
564
|
+
# key_marker = 1, id_marker = null
|
565
|
+
# # return <2, 0>, <2, 1>, <3, 0> ...
|
566
|
+
# key_marker = 1, id_marker = 5
|
567
|
+
# # return <1, 6>, <1, 7>, <2, 0>, <3, 0> ...
|
568
|
+
def list_uploads(opts = {})
|
569
|
+
Iterator::Uploads.new(
|
570
|
+
@protocol, name, opts.merge(encoding: KeyEncoding::URL)).to_enum
|
571
|
+
end
|
572
|
+
|
573
|
+
# 取消一个multipart上传请求,一般用于清除Bucket下因断点上传而产
|
574
|
+
# 生的文件碎片。成功取消后属于这个上传请求的分片都会被清除。
|
575
|
+
# @param [String] upload_id 上传请求的id,可通过{#list_uploads}
|
576
|
+
# 获得
|
577
|
+
# @param [String] key Object的名字
|
578
|
+
def abort_upload(upload_id, key)
|
579
|
+
@protocol.abort_multipart_upload(name, key, upload_id)
|
580
|
+
end
|
581
|
+
|
582
|
+
# 获取Bucket的URL
|
583
|
+
# @return [String] Bucket的URL
|
584
|
+
def bucket_url
|
585
|
+
@protocol.get_request_url(name)
|
586
|
+
end
|
587
|
+
|
588
|
+
# 获取Object的URL
|
589
|
+
# @param [String] key Object的key
|
590
|
+
# @param [Boolean] sign 是否对URL进行签名,默认为是
|
591
|
+
# @param [Fixnum] expiry URL的有效时间,单位为秒,默认为60s
|
592
|
+
# @return [String] 用于直接访问Object的URL
|
593
|
+
def object_url(key, sign = true, expiry = 60)
|
594
|
+
url = @protocol.get_request_url(name, key)
|
595
|
+
return url unless sign
|
596
|
+
|
597
|
+
expires = Time.now.to_i + expiry
|
598
|
+
query = {
|
599
|
+
'Expires' => expires.to_s,
|
600
|
+
'OSSAccessKeyId' => CGI.escape(access_key_id)
|
601
|
+
}
|
602
|
+
|
603
|
+
sub_res = []
|
604
|
+
if @protocol.get_sts_token
|
605
|
+
sub_res << "security-token=#{@protocol.get_sts_token}"
|
606
|
+
query['security-token'] = CGI.escape(@protocol.get_sts_token)
|
607
|
+
end
|
608
|
+
|
609
|
+
resource = "/#{name}/#{key}"
|
610
|
+
unless sub_res.empty?
|
611
|
+
resource << "?#{sub_res.join('&')}"
|
612
|
+
end
|
613
|
+
|
614
|
+
string_to_sign = "" <<
|
615
|
+
"GET\n" << # method
|
616
|
+
"\n" << # Content-MD5
|
617
|
+
"\n" << # Content-Type
|
618
|
+
"#{expires}\n" <<
|
619
|
+
"#{resource}"
|
620
|
+
|
621
|
+
signature = sign(string_to_sign)
|
622
|
+
query_string =
|
623
|
+
query.merge('Signature' => CGI.escape(signature))
|
624
|
+
.map { |k, v| "#{k}=#{v}" }.join('&')
|
625
|
+
|
626
|
+
[url, query_string].join('?')
|
627
|
+
end
|
628
|
+
|
629
|
+
# 获取用户所设置的ACCESS_KEY_ID
|
630
|
+
# @return [String] 用户的ACCESS_KEY_ID
|
631
|
+
def access_key_id
|
632
|
+
@protocol.get_access_key_id
|
633
|
+
end
|
634
|
+
|
635
|
+
# 用ACCESS_KEY_SECRET对内容进行签名
|
636
|
+
# @param [String] string_to_sign 要进行签名的内容
|
637
|
+
# @return [String] 生成的签名
|
638
|
+
def sign(string_to_sign)
|
639
|
+
@protocol.sign(string_to_sign)
|
640
|
+
end
|
641
|
+
|
642
|
+
private
|
643
|
+
# Infer the file's content type using MIME::Types
|
644
|
+
# @param file [String] the file path
|
645
|
+
# @return [String] the infered content type or nil if it fails
|
646
|
+
# to infer the content type
|
647
|
+
def get_content_type(file)
|
648
|
+
t = MIME::Types.of(file)
|
649
|
+
t.first.content_type unless t.empty?
|
650
|
+
end
|
651
|
+
|
652
|
+
# Get the checkpoint file path for file
|
653
|
+
# @param file [String] the file path
|
654
|
+
# @return [String] the checkpoint file path
|
655
|
+
def get_cpt_file(file)
|
656
|
+
"#{File.expand_path(file)}.cpt"
|
657
|
+
end
|
658
|
+
|
659
|
+
end # Bucket
|
660
|
+
end # OSS
|
661
|
+
end # Aliyun
|