cos 0.1.0 → 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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +12 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +13 -2
  5. data/Gemfile +4 -1
  6. data/LICENSE +191 -0
  7. data/README.md +2014 -17
  8. data/Rakefile +23 -6
  9. data/bin/cos +325 -0
  10. data/bin/setup +1 -3
  11. data/cos.gemspec +24 -13
  12. data/lib/cos.rb +41 -4
  13. data/lib/cos/api.rb +289 -0
  14. data/lib/cos/bucket.rb +731 -0
  15. data/lib/cos/checkpoint.rb +62 -0
  16. data/lib/cos/client.rb +58 -0
  17. data/lib/cos/config.rb +102 -0
  18. data/lib/cos/dir.rb +301 -0
  19. data/lib/cos/download.rb +252 -0
  20. data/lib/cos/exception.rb +62 -0
  21. data/lib/cos/file.rb +152 -0
  22. data/lib/cos/http.rb +95 -0
  23. data/lib/cos/logging.rb +47 -0
  24. data/lib/cos/resource.rb +201 -0
  25. data/lib/cos/signature.rb +119 -0
  26. data/lib/cos/slice.rb +292 -0
  27. data/lib/cos/struct.rb +49 -0
  28. data/lib/cos/tree.rb +165 -0
  29. data/lib/cos/util.rb +82 -0
  30. data/lib/cos/version.rb +2 -2
  31. data/spec/cos/bucket_spec.rb +562 -0
  32. data/spec/cos/client_spec.rb +77 -0
  33. data/spec/cos/dir_spec.rb +195 -0
  34. data/spec/cos/download_spec.rb +105 -0
  35. data/spec/cos/http_spec.rb +70 -0
  36. data/spec/cos/signature_spec.rb +83 -0
  37. data/spec/cos/slice_spec.rb +302 -0
  38. data/spec/cos/struct_spec.rb +38 -0
  39. data/spec/cos/tree_spec.rb +322 -0
  40. data/spec/cos/util_spec.rb +106 -0
  41. data/test/download_test.rb +44 -0
  42. data/test/list_test.rb +43 -0
  43. data/test/upload_test.rb +48 -0
  44. metadata +132 -21
  45. data/.idea/.name +0 -1
  46. data/.idea/cos.iml +0 -49
  47. data/.idea/encodings.xml +0 -6
  48. data/.idea/misc.xml +0 -14
  49. data/.idea/modules.xml +0 -8
  50. data/.idea/workspace.xml +0 -465
  51. data/bin/console +0 -14
@@ -0,0 +1,95 @@
1
+ # coding: utf-8
2
+
3
+ require 'rest-client'
4
+ require 'uri'
5
+
6
+ module COS
7
+
8
+ class HTTP
9
+
10
+ # 默认content-type
11
+ DEFAULT_CONTENT_TYPE = 'application/json'
12
+
13
+ # 请求创建超时
14
+ OPEN_TIMEOUT = 15
15
+
16
+ # 响应读取超时
17
+ READ_TIMEOUT = 120
18
+
19
+ include Logging
20
+
21
+ attr_reader :config, :signature
22
+
23
+ def initialize(config)
24
+ @config = config
25
+ @signature = Signature.new(config)
26
+ end
27
+
28
+ # GET请求
29
+ def get(path, headers, signature = nil)
30
+ do_request('GET', path, headers, signature)
31
+ end
32
+
33
+ # POST请求
34
+ def post(path, headers, signature, payload)
35
+ do_request('POST', path, headers, signature, payload)
36
+ end
37
+
38
+ private
39
+
40
+ # 发送请求
41
+ def do_request(method, path, headers, signature = nil, payload = nil)
42
+
43
+ # 整理头部信息
44
+ headers['content-type'] ||= DEFAULT_CONTENT_TYPE
45
+ headers['user-agent'] = get_user_agent
46
+ headers['authorization'] = signature
47
+ headers['accept'] = 'application/json'
48
+
49
+ # 请求地址
50
+ url = "#{config.api_base}#{path}"
51
+
52
+ logger.debug("Send HTTP request, method: #{method}, url: " \
53
+ "#{url}, headers: #{headers}, payload: #{payload}")
54
+
55
+ response = RestClient::Request.execute(
56
+ :method => method,
57
+ :url => URI.encode(url),
58
+ :headers => headers,
59
+ :payload => payload,
60
+ :open_timeout => @config.open_timeout || OPEN_TIMEOUT,
61
+ :timeout => @config.read_timeout || READ_TIMEOUT
62
+ ) do |resp, request, result, &blk|
63
+
64
+ # 捕获异常
65
+ if resp.code >= 300
66
+ e = ServerError.new(resp)
67
+ logger.warn(e.to_s)
68
+ raise e
69
+ else
70
+ resp.return!(request, result, &blk)
71
+ end
72
+
73
+ end
74
+
75
+ logger.debug("Received HTTP response, code: #{response.code}, headers: " \
76
+ "#{response.headers}, body: #{response.body}")
77
+
78
+ # 解析JSON结果
79
+ parse_data(response)
80
+ end
81
+
82
+ # 解析结果json 取出data部分
83
+ def parse_data(response)
84
+ j = JSON.parse(response.body, symbolize_names: true)
85
+ j[:data]
86
+ end
87
+
88
+ # 获取user agent
89
+ def get_user_agent
90
+ "cos-ruby-sdk/#{VERSION} ruby-#{RUBY_VERSION}/#{RUBY_PLATFORM}"
91
+ end
92
+
93
+ end
94
+
95
+ end
@@ -0,0 +1,47 @@
1
+ # coding: utf-8
2
+
3
+ require 'logger'
4
+
5
+ module COS
6
+
7
+ module Logging
8
+
9
+ # 默认日志存储
10
+ DEFAULT_LOG_FILE = './cos-sdk.log'
11
+ # 日志最大数量
12
+ MAX_NUM_LOG = 100
13
+ # 日志覆盖大小
14
+ ROTATE_SIZE = 10 * 1024 * 1024
15
+
16
+ # 设置日志输出的文件
17
+ # level = Logger::DEBUG | Logger::INFO | Logger::ERROR | Logger::FATAL
18
+ def self.set_logger(file, level)
19
+ if file == STDOUT or file == STDERR
20
+ @logger = Logger.new(file)
21
+ @logger.level = level
22
+ else
23
+ @logger = Logger.new(file, MAX_NUM_LOG, ROTATE_SIZE)
24
+ @logger.level = level
25
+ end
26
+ end
27
+
28
+ # 获取logger
29
+ def logger
30
+ Logging.logger
31
+ end
32
+
33
+ private
34
+
35
+ # 实例方法使用logger
36
+ def self.logger
37
+ unless @logger
38
+ @logger = Logger.new(DEFAULT_LOG_FILE, MAX_NUM_LOG, ROTATE_SIZE)
39
+ @logger.level = Logger::INFO
40
+ end
41
+
42
+ @logger
43
+ end
44
+
45
+ end
46
+
47
+ end
@@ -0,0 +1,201 @@
1
+ # coding: utf-8
2
+
3
+ module COS
4
+
5
+ class Resource
6
+
7
+ attr_reader :bucket, :path, :dir_count, :file_count
8
+
9
+ # 实例化COS资源迭代器
10
+ def initialize(bucket, path, options = {})
11
+ @bucket = bucket
12
+ @path = path
13
+ @more = options
14
+ @results = Array.new
15
+ @dir_count = 0
16
+ @file_count = 0
17
+ end
18
+
19
+ # 实现迭代器
20
+ def next
21
+ loop do
22
+ # 从接口获取下一页结果
23
+ fetch_more if @results.empty?
24
+
25
+ # 取出结果
26
+ r = @results.shift
27
+ break unless r
28
+
29
+ yield r
30
+ end
31
+ end
32
+
33
+ # 返回迭代器
34
+ def to_enum
35
+ self.enum_for(:next)
36
+ end
37
+
38
+ # 获取列表
39
+ def fetch
40
+ client = bucket.client
41
+ resp = client.api.list(path, @more.merge({bucket: bucket.bucket_name}))
42
+
43
+ # 遍历结果转换为对应的对象
44
+ @results = resp[:infos].map do |r|
45
+ if r[:filesize].nil?
46
+ # 目录
47
+ COSDir.new(r.merge({
48
+ bucket: bucket,
49
+ path: Util.get_list_path(path, r[:name])
50
+ }))
51
+ else
52
+ # 文件
53
+ COSFile.new(r.merge({
54
+ bucket: bucket,
55
+ path: Util.get_list_path(path, r[:name], true)
56
+ }))
57
+ end
58
+ end || []
59
+
60
+ @dir_count = resp[:dir_count]
61
+ @file_count = resp[:file_count]
62
+
63
+ @more[:context] = resp[:context]
64
+ @more[:has_more] = resp[:has_more]
65
+ end
66
+
67
+ private
68
+
69
+ # 如果有更多页继续获取下一页
70
+ def fetch_more
71
+ return if @more[:has_more] == false
72
+ fetch
73
+ end
74
+
75
+ end
76
+
77
+ # COS资源,文件与目录的共有操作
78
+ class ResourceOperator < Struct::Base
79
+
80
+ required_attrs :bucket, :path, :name, :ctime, :mtime
81
+ optional_attrs :biz_attr, :filesize, :filelen, :sha, :access_url,
82
+ # 根目录(bucket)参数
83
+ :authority, :bucket_type, :migrate_source_domain,
84
+ :need_preview, :refers, :blackrefers, :brower_exec,
85
+ :cnames, :nugc_flag, :SKIP_EXTRA
86
+
87
+ # 资源类型
88
+ attr_reader :type
89
+
90
+ alias :file_size :filesize
91
+ alias :file_len :filelen
92
+
93
+ def initialize(attrs)
94
+ super(attrs)
95
+ end
96
+
97
+ # 创建时间Time类型
98
+ #
99
+ # @return [Time]
100
+ def created_at
101
+ Time.at(ctime.to_i)
102
+ end
103
+
104
+ # 更新时间Time类型
105
+ #
106
+ # @return [Time]
107
+ def updated_at
108
+ Time.at(mtime.to_i)
109
+ end
110
+
111
+ # 参数转化为Hash类型
112
+ #
113
+ # @return [Hash]
114
+ def to_hash
115
+ hash = {
116
+ type: type,
117
+ bucket: bucket.bucket_name,
118
+ path: path,
119
+ name: name,
120
+ ctime: ctime,
121
+ mtime: mtime,
122
+ }
123
+
124
+ optional_attrs.each do |key|
125
+ hash[key] = send(key.to_s) if respond_to?(key) and send(key.to_s) != nil
126
+ end
127
+
128
+ hash
129
+ end
130
+
131
+ # 判断当前资源是否存在
132
+ #
133
+ # @raise [ServerError] 服务端异常返回
134
+ #
135
+ # @return [Boolean] 是否存在
136
+ #
137
+ # @example
138
+ # puts resource.exist?
139
+ def exist?
140
+ bucket.exist?(path)
141
+ end
142
+
143
+ alias :exists? :exist?
144
+
145
+ # 获取(刷新)当前资源的状态
146
+ #
147
+ # @note 如查询根目录('/', '')可以获取到bucket信息, 返回COSDir
148
+ #
149
+ # @raise [ServerError] 服务端异常返回
150
+ #
151
+ # @return [COSFile|COSDir] 如果是目录则返回COSDir资源对象,是文件则返回COSFile资源对象
152
+ #
153
+ # @example
154
+ # puts resource.stat.name
155
+ def stat
156
+ bucket.stat(path)
157
+ end
158
+
159
+ # 更新当前资源的属性
160
+ #
161
+ # @note 根目录('/') 不可更新, 否则会抛出异常
162
+ #
163
+ # @param biz_attr [String] 目录/文件属性,业务端维护
164
+ #
165
+ # @raise [ServerError] 服务端异常返回
166
+ #
167
+ # @example
168
+ # resource.update('i am the attr')
169
+ def update(biz_attr)
170
+ bucket.update(path, biz_attr)
171
+ @mtime = Time.now.to_i.to_s
172
+ @biz_attr = biz_attr
173
+ self
174
+ end
175
+
176
+ # 删除当前资源
177
+ #
178
+ # @note 非空目录及根目录不可删除,会抛出异常
179
+ #
180
+ # @raise [ServerError] 服务端异常返回
181
+ #
182
+ # @example
183
+ # resource.delete
184
+ def delete
185
+ bucket.delete(path)
186
+ self
187
+ end
188
+
189
+ # 删除当前资源, 不会抛出异常而是返回布尔值
190
+ #
191
+ # @note 非空目录及根目录不可删除, 返回false
192
+ #
193
+ # @example
194
+ # puts resource.delete!
195
+ def delete!
196
+ bucket.delete!(path)
197
+ end
198
+
199
+ end
200
+
201
+ end
@@ -0,0 +1,119 @@
1
+ # coding: utf-8
2
+
3
+ require 'time'
4
+ require 'base64'
5
+ require 'openssl'
6
+ require 'uri'
7
+
8
+ module COS
9
+
10
+ class Signature
11
+
12
+ attr_reader :config, :expire_seconds, :file_id
13
+
14
+ # 初始化
15
+ #
16
+ # @example
17
+ # COS::Signature.new(config)
18
+ #
19
+ # @param config [COS::Config] 客户端设置
20
+ #
21
+ # @see COS::Config
22
+ def initialize(config)
23
+ @config = config
24
+ end
25
+
26
+ # 单次有效签名
27
+ #
28
+ # @see http://www.qcloud.com/wiki/%E9%89%B4%E6%9D%83%E6%8A%80%E6%9C%AF%E6%9C%8D%E5%8A%A1%E6%96%B9%E6%A1%88#1_.E7.AD.BE.E5.90.8D.E4.B8.8E.E9.89.B4.E6.9D.83
29
+ #
30
+ # @note 用于删除目录、文件, 更新目录、文件
31
+ #
32
+ # @param bucket [String] bucket名称
33
+ # @param path [String] 文件或目录路径, 如 /path/, /path/file
34
+ #
35
+ # @return [String] 签名字符串
36
+ def once(bucket, path)
37
+ # 单次签名,需要指定资源存储的唯一标识
38
+ unless path.start_with?('/')
39
+ path = "/#{path}"
40
+ end
41
+
42
+ # 资源存储的唯一标识,格式为"/app_id/bucket/用户自定义路径或资源名"
43
+ @file_id = "/#{config.app_id}/#{bucket}#{path}"
44
+
45
+ sign(:once, bucket)
46
+ end
47
+
48
+
49
+ # 多次有效签名
50
+ #
51
+ # @see http://www.qcloud.com/wiki/%E9%89%B4%E6%9D%83%E6%8A%80%E6%9C%AF%E6%9C%8D%E5%8A%A1%E6%96%B9%E6%A1%88#1_.E7.AD.BE.E5.90.8D.E4.B8.8E.E9.89.B4.E6.9D.83
52
+ #
53
+ # @note 用于上传,查询目录、文件,查询目录、文件,创建目录,下载(开启token防盗链)
54
+ #
55
+ # @param bucket [String] bucket名称
56
+ # @param expire_seconds [Integer] 签名有效时间(单位秒)
57
+ #
58
+ # @return [String] 签名字符串
59
+ def multiple(bucket, expire_seconds = config.multiple_sign_expire)
60
+ # 多次签名时,过期时间应大于当前时间
61
+ if expire_seconds <= 0
62
+ raise AttrError, 'Multiple signature expire_seconds must greater than 0'
63
+ end
64
+
65
+ @expire_seconds = expire_seconds
66
+
67
+ sign(:multiple, bucket)
68
+ end
69
+
70
+ private
71
+
72
+ # 生成签名
73
+ #
74
+ # @see http://www.qcloud.com/wiki/%E9%89%B4%E6%9D%83%E6%8A%80%E6%9C%AF%E6%9C%8D%E5%8A%A1%E6%96%B9%E6%A1%88#2.3.09.E7.94.9F.E6.88.90.E7.AD.BE.E5.90.8D
75
+ def sign(sign_type, bucket)
76
+ # 准备待签名字符串
77
+ sign_string = string_to_sign(sign_type, bucket)
78
+
79
+ # HMAC-SHA1算法进行签名
80
+ hmac_sha1 = OpenSSL::HMAC.digest('sha1', config.secret_key, sign_string)
81
+
82
+ # 然后将原字符串附加到签名结果的末尾,再进行Base64编码
83
+ Base64.strict_encode64("#{hmac_sha1}#{sign_string}")
84
+ end
85
+
86
+ # 准备签名字符串
87
+ #
88
+ # @see http://www.qcloud.com/wiki/%E9%89%B4%E6%9D%83%E6%8A%80%E6%9C%AF%E6%9C%8D%E5%8A%A1%E6%96%B9%E6%A1%88#2_.E7.AD.BE.E5.90.8D.E7.AE.97.E6.B3.95
89
+ def string_to_sign(sign_type, bucket)
90
+ # 随机串,无符号10进制整数,最长10位
91
+ r = rand(99999)
92
+
93
+ # 当前时间戳,是一个符合UNIX Epoch时间戳规范的数值,单位为秒
94
+ t = Time.now.to_i
95
+
96
+ if sign_type == :once
97
+ # 单次签名时e为0
98
+ e = 0
99
+
100
+ # 需要对其中非'/'字符进行urlencode编码
101
+ f = URI.encode(file_id)
102
+
103
+ elsif sign_type == :multiple
104
+ e = t + expire_seconds
105
+
106
+ # 多次签名不需要提供file_id
107
+ f = nil
108
+
109
+ else
110
+ raise Exception, 'Unknown sign type when prepare sign string'
111
+ end
112
+
113
+ # 拼接待签名字符串
114
+ "a=#{config.app_id}&b=#{bucket}&k=#{config.secret_id}&e=#{e}&t=#{t}&r=#{r}&f=#{f}"
115
+ end
116
+
117
+ end
118
+
119
+ end