aliyun-oss-ruby-sdk 0.4.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.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +95 -0
  3. data/README.md +423 -0
  4. data/examples/aliyun/oss/bucket.rb +144 -0
  5. data/examples/aliyun/oss/callback.rb +61 -0
  6. data/examples/aliyun/oss/object.rb +182 -0
  7. data/examples/aliyun/oss/resumable_download.rb +42 -0
  8. data/examples/aliyun/oss/resumable_upload.rb +49 -0
  9. data/examples/aliyun/oss/streaming.rb +124 -0
  10. data/examples/aliyun/oss/using_sts.rb +48 -0
  11. data/examples/aliyun/sts/assume_role.rb +59 -0
  12. data/lib/aliyun_sdk/common.rb +6 -0
  13. data/lib/aliyun_sdk/common/exception.rb +18 -0
  14. data/lib/aliyun_sdk/common/logging.rb +46 -0
  15. data/lib/aliyun_sdk/common/struct.rb +56 -0
  16. data/lib/aliyun_sdk/oss.rb +16 -0
  17. data/lib/aliyun_sdk/oss/bucket.rb +661 -0
  18. data/lib/aliyun_sdk/oss/client.rb +106 -0
  19. data/lib/aliyun_sdk/oss/config.rb +39 -0
  20. data/lib/aliyun_sdk/oss/download.rb +255 -0
  21. data/lib/aliyun_sdk/oss/exception.rb +108 -0
  22. data/lib/aliyun_sdk/oss/http.rb +338 -0
  23. data/lib/aliyun_sdk/oss/iterator.rb +92 -0
  24. data/lib/aliyun_sdk/oss/multipart.rb +74 -0
  25. data/lib/aliyun_sdk/oss/object.rb +15 -0
  26. data/lib/aliyun_sdk/oss/protocol.rb +1499 -0
  27. data/lib/aliyun_sdk/oss/struct.rb +208 -0
  28. data/lib/aliyun_sdk/oss/upload.rb +238 -0
  29. data/lib/aliyun_sdk/oss/util.rb +89 -0
  30. data/lib/aliyun_sdk/sts.rb +9 -0
  31. data/lib/aliyun_sdk/sts/client.rb +38 -0
  32. data/lib/aliyun_sdk/sts/config.rb +22 -0
  33. data/lib/aliyun_sdk/sts/exception.rb +53 -0
  34. data/lib/aliyun_sdk/sts/protocol.rb +130 -0
  35. data/lib/aliyun_sdk/sts/struct.rb +64 -0
  36. data/lib/aliyun_sdk/sts/util.rb +48 -0
  37. data/lib/aliyun_sdk/version.rb +7 -0
  38. data/spec/aliyun/oss/bucket_spec.rb +597 -0
  39. data/spec/aliyun/oss/client/bucket_spec.rb +554 -0
  40. data/spec/aliyun/oss/client/client_spec.rb +297 -0
  41. data/spec/aliyun/oss/client/resumable_download_spec.rb +220 -0
  42. data/spec/aliyun/oss/client/resumable_upload_spec.rb +413 -0
  43. data/spec/aliyun/oss/http_spec.rb +83 -0
  44. data/spec/aliyun/oss/multipart_spec.rb +686 -0
  45. data/spec/aliyun/oss/object_spec.rb +785 -0
  46. data/spec/aliyun/oss/service_spec.rb +142 -0
  47. data/spec/aliyun/oss/util_spec.rb +50 -0
  48. data/spec/aliyun/sts/client_spec.rb +150 -0
  49. data/spec/aliyun/sts/util_spec.rb +39 -0
  50. data/tests/config.rb +31 -0
  51. data/tests/test_content_encoding.rb +54 -0
  52. data/tests/test_content_type.rb +95 -0
  53. data/tests/test_custom_headers.rb +70 -0
  54. data/tests/test_encoding.rb +77 -0
  55. data/tests/test_large_file.rb +66 -0
  56. data/tests/test_multipart.rb +97 -0
  57. data/tests/test_object_acl.rb +49 -0
  58. data/tests/test_object_key.rb +68 -0
  59. data/tests/test_object_url.rb +69 -0
  60. data/tests/test_resumable.rb +40 -0
  61. 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,6 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require_relative 'version'
4
+ require_relative 'common/struct'
5
+ require_relative 'common/logging'
6
+ require_relative 'common/exception'
@@ -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