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,62 @@
1
+ # coding: utf-8
2
+
3
+ require 'json'
4
+
5
+ module COS
6
+
7
+ class Checkpoint < Struct::Base
8
+
9
+ # 默认线程数 10
10
+ DEFAULT_THREADS = 10
11
+
12
+ def initialize(options = {})
13
+ super(options)
14
+
15
+ # 分片大小必须>0
16
+ if options[:options] and options[:options][:slice_size] and options[:options][:slice_size] <= 0
17
+ raise ClientError, 'slice_size must > 0'
18
+ end
19
+
20
+ @mutex = Mutex.new
21
+ @file_meta = {}
22
+ @num_threads = options[:threads] || DEFAULT_THREADS
23
+ @all_mutex = Mutex.new
24
+ @parts = []
25
+ @todo_mutex = Mutex.new
26
+ @todo_parts = []
27
+ end
28
+
29
+ private
30
+
31
+ # 写入断点续传状态
32
+ def write_checkpoint(states, file)
33
+ sha1 = Util.string_sha1(states.to_json)
34
+
35
+ @mutex.synchronize do
36
+ File.open(file, 'w') do |f|
37
+ f.write(states.merge(sha1: sha1).to_json)
38
+ end
39
+ end
40
+ end
41
+
42
+ # 加载断点续传状态
43
+ def load_checkpoint(file)
44
+ states = {}
45
+
46
+ @mutex.synchronize do
47
+ states = JSON.parse(File.read(file), symbolize_names: true)
48
+ end
49
+ sha1 = states.delete(:sha1)
50
+
51
+ raise CheckpointBrokenError, 'Missing SHA1 in checkpoint.' unless sha1
52
+
53
+ unless sha1 == Util.string_sha1(states.to_json)
54
+ raise CheckpointBrokenError, 'Unmatched checkpoint SHA1'
55
+ end
56
+
57
+ states
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,58 @@
1
+ # coding: utf-8
2
+
3
+ module COS
4
+
5
+ class Client
6
+
7
+ attr_reader :config, :api
8
+
9
+ # 初始化
10
+ #
11
+ # @see COS::Config
12
+ #
13
+ # # @example
14
+ # COS::Client.new(app_id: '', secret_id: '', secret_key: '')
15
+ #
16
+ # @param options [Hash] 客户端配置
17
+ #
18
+ # @return [COS::Client]
19
+ #
20
+ # @raise [AttrError] 如果缺少参数会抛出参数错误异常
21
+ def initialize(options = {})
22
+ @config = Config.new(options)
23
+ @api = API.new(@config)
24
+ @cache_buckets = {}
25
+ end
26
+
27
+ # 获取鉴权签名方法
28
+ #
29
+ # @see COS::Signature
30
+ #
31
+ # @return [COS::Signature]
32
+ def signature
33
+ api.http.signature
34
+ end
35
+
36
+ # 指定bucket 初始化Bucket类
37
+ #
38
+ # @note SDK会自动获取bucket的信息,包括读取权限等并进行缓存
39
+ # 如需在后台修改了bucket信息请重新初始化Client
40
+ #
41
+ # @param bucket_name [String] bucket名称
42
+ # 如果在初始化时的配置中设置了default_bucket则该字段可以为空,会获取默认的bucket
43
+ #
44
+ # @return [COS::Bucket]
45
+ #
46
+ # @raise [ClientError] 未指定bucket
47
+ # @raise [ServerError] bucket不存在
48
+ def bucket(bucket_name = nil)
49
+ unless @cache_buckets[bucket_name]
50
+ # 缓存bucket对象
51
+ @cache_buckets[bucket_name] = Bucket.new(self, bucket_name)
52
+ end
53
+ @cache_buckets[bucket_name]
54
+ end
55
+
56
+ end
57
+
58
+ end
@@ -0,0 +1,102 @@
1
+ # coding: utf-8
2
+
3
+ require 'yaml'
4
+
5
+ module COS
6
+
7
+ class Config < Struct::Base
8
+
9
+ # 默认服务HOST
10
+ DEFAULT_HOST = 'web.file.myqcloud.com'
11
+
12
+ # 默认多次签名过期时间(单位秒)
13
+ DEFAULT_MULTIPLE_SIGN_EXPIRE = 600
14
+
15
+ # 必选参数 [Hash]
16
+ required_attrs :app_id, :secret_id, :secret_key
17
+
18
+ # 可选参数 [Hash]
19
+ optional_attrs :host, :protocol, :open_timeout, :read_timeout, :config,
20
+ :log_src, :log_level, :multiple_sign_expire, :default_bucket
21
+
22
+ attr_reader :api_base
23
+
24
+ # 初始化
25
+ #
26
+ # @example
27
+ # COS::Config.new(app_id: '', secret_id: '', secret_key: '')
28
+ #
29
+ # @param options [Hash] 客户端配置
30
+ # @option options [String] :app_id COS分配的app_id
31
+ # @option options [String] :secret_id COS分配的secret_id
32
+ # @option options [String] :secret_key COS分配的secret_key
33
+ # @option options [String] :host COS服务host地址
34
+ # @option options [String] :protocol 使用协议,默认为http,可选https
35
+ # @option options [Integer] :open_timeout 接口通讯建立连接超时秒数
36
+ # @option options [Integer] :read_timeout 接口通讯读取数据超时秒数
37
+ # @option options [String] :config 配置文件路径
38
+ # @option options [String|Object] :log_src 日志输出
39
+ # STDOUT | STDERR | 'path/filename.log'
40
+ # @option options [Logger] :log_level 日志级别
41
+ # Logger::DEBUG | Logger::INFO | Logger::ERROR | Logger::FATAL
42
+ # @option options [String] :multiple_sign_expire 多次签名过期时间(单位秒)
43
+ # @option options [String] :default_bucket 默认bucket
44
+ #
45
+ # @return [COS::Config]
46
+ #
47
+ # @raise [AttrError] 如果缺少参数会抛出参数错误异常
48
+ def initialize(options = {})
49
+ # 从配置文件加载配置
50
+ if options[:config]
51
+ config = load_config_file(options[:config])
52
+ options.merge!(config)
53
+ end
54
+
55
+ super(options)
56
+
57
+ # log_src: STDOUT | STDERR | 'path/filename.log'
58
+ # log_level: Logger::DEBUG | Logger::INFO | Logger::ERROR | Logger::FATAL
59
+ if options[:log_src]
60
+ Logging.set_logger(
61
+ options[:log_src],
62
+ options[:log_level] || Logger::INFO
63
+ )
64
+ end
65
+
66
+ @protocol ||= 'http'
67
+ @host ||= DEFAULT_HOST
68
+ @api_base = "#{@protocol}://#{@host}/files/v1"
69
+ @multiple_sign_expire ||= DEFAULT_MULTIPLE_SIGN_EXPIRE
70
+ end
71
+
72
+ # 获取指定的bucket或从config中获取默认bucket
73
+ #
74
+ # @params custom_bucket [String] bucket名称
75
+ #
76
+ # @return [String]
77
+ #
78
+ # @raise [ClientError]
79
+ def get_bucket(custom_bucket)
80
+ b = custom_bucket || default_bucket
81
+ if b == nil
82
+ raise ClientError, 'Bucket name must be set'
83
+ end
84
+ b
85
+ end
86
+
87
+ private
88
+
89
+ # 加载yaml配置文件
90
+ def load_config_file(config_file)
91
+ hash = YAML.load(File.read(File.expand_path(config_file)))
92
+
93
+ # Hash key字符串转为symbol
94
+ hash.keys.each do |key|
95
+ hash[(key.to_sym rescue key) || key] = hash.delete(key)
96
+ end
97
+ hash
98
+ end
99
+
100
+ end
101
+
102
+ end
@@ -0,0 +1,301 @@
1
+ # coding: utf-8
2
+
3
+ module COS
4
+
5
+ # COS目录资源
6
+ class COSDir < ResourceOperator
7
+
8
+ # 初始化
9
+ #
10
+ # @param [Hash] attrs 参数
11
+ # @option attrs [Bucket] :bucket COS::Bucket对象
12
+ # @option attrs [String] :path 存储路径
13
+ # @option attrs [String] :name 文件名
14
+ # @option attrs [String] :ctime 创建时间unix时间戳
15
+ # @option attrs [String] :mtime 修改时间unix时间戳
16
+ # @option attrs [String] :biz_attr 业务信息
17
+ # @option attrs [String] :authority bucket权限(根目录bucket)
18
+ # @option attrs [Integer] :bucket_type bucket类型(根目录bucket)
19
+ # @option attrs [String] :migrate_source_domain 回源地址(根目录bucket)
20
+ # @option attrs [String] :need_preview need_preview(根目录bucket)
21
+ # @option attrs [Array<String>] :refers refers(根目录bucket)
22
+ #
23
+ # @raise [AttrError] 缺少参数
24
+ #
25
+ # @return [COS::COSDir]
26
+ def initialize(attrs = {})
27
+ super(attrs)
28
+ @type = 'dir'
29
+ end
30
+
31
+ # 在当前目录中上传文件,自动判断使用分片上传,断点续传及自动重试,多线程上传
32
+ #
33
+ # @param file_name [String] 文件名
34
+ # @param file_src [String] 本地文件路径
35
+ # @param options [Hash] 高级参数
36
+ # @option options [Boolean] :auto_create_folder 自动创建远端目录
37
+ # @option options [Integer] :min_slice_size 完整上传最小文件大小,
38
+ # 超过此大小将会使用分片多线程断点续传
39
+ # @option options [Integer] :upload_retry 上传重试次数, 默认10
40
+ # @option options [String] :biz_attr 目录属性, 业务端维护
41
+ # @option options [Boolean] :disable_cpt 是否禁用checkpoint功能,如
42
+ # 果设置为true,则在上传的过程中不会写checkpoint文件,这意味着
43
+ # 上传失败后不能断点续传,而只能重新上传整个文件。如果这个值为
44
+ # true,则:cpt_file会被忽略。
45
+ # @option options [Integer] :threads 多线程上传线程数, 默认为10
46
+ # @option options [Integer] :slice_size 设置分片上传时每个分片的大小
47
+ # 默认为3 MB, 目前服务端最大限制也为3MB。
48
+ # @option options [String] :cpt_file 断点续传的checkpoint文件,如果
49
+ # 指定的cpt文件不存在,则会在file所在目录创建一个默认的cpt文件,
50
+ # 命名方式为:file.cpt,其中file是用户要上传的文件名。在上传的过
51
+ # 程中会不断更新此文件,成功完成上传后会删除此文件;如果指定的
52
+ # cpt文件已存在,则从cpt文件中记录的点继续上传。
53
+ #
54
+ # @yield [Float] 上传进度百分比回调, 进度值是一个0-1之间的小数
55
+ #
56
+ # @raise [ServerError] 服务端异常返回
57
+ #
58
+ # @return [COS::COSFile]
59
+ #
60
+ # @example
61
+ # file = dir.upload('file1', '~/test/file1') do |p|
62
+ # puts "上传进度: #{(p*100).round(2)}%")
63
+ # end
64
+ # puts file.url
65
+ def upload(file_name, file_src, options = {}, &block)
66
+ bucket.upload(self, file_name, file_src, options, &block)
67
+ end
68
+
69
+ # 批量上传本地目录中的所有文件至此目录
70
+ #
71
+ # @note 已存在的文件不会再次上传, 本地目录中的隐藏文件(已"."开头的)不会上传
72
+ # ".cpt"文件不会上传, 不会上传子目录
73
+ #
74
+ # 目录路径如: '/', 'path1', 'path1/path2', sdk会补齐末尾的 '/'
75
+ # @param file_src_path [String] 本地文件夹路径
76
+ # @param options [Hash] 高级参数
77
+ # @option options [Boolean] :auto_create_folder 自动创建远端目录
78
+ # @option options [Integer] :min_slice_size 完整上传最小文件大小,
79
+ # 超过此大小将会使用分片多线程断点续传
80
+ # @option options [Integer] :upload_retry 上传重试次数, 默认10
81
+ # @option options [String] :biz_attr 目录属性, 业务端维护
82
+ # @option options [Boolean] :disable_cpt 是否禁用checkpoint功能,如
83
+ # 果设置为true,则在上传的过程中不会写checkpoint文件,这意味着
84
+ # 上传失败后不能断点续传,而只能重新上传整个文件。如果这个值为
85
+ # true,则:cpt_file会被忽略。
86
+ # @option options [Integer] :threads 多线程上传线程数, 默认为10
87
+ # @option options [Integer] :slice_size 设置分片上传时每个分片的大小
88
+ # 默认为3 MB, 目前服务端最大限制也为3MB。
89
+ # @option options [String] :cpt_file 断点续传的checkpoint文件,如果
90
+ # 指定的cpt文件不存在,则会在file所在目录创建一个默认的cpt文件,
91
+ # 命名方式为:file.cpt,其中file是用户要上传的文件名。在上传的过
92
+ # 程中会不断更新此文件,成功完成上传后会删除此文件;如果指定的
93
+ # cpt文件已存在,则从cpt文件中记录的点继续上传。
94
+ #
95
+ # @yield [Float] 上传进度百分比回调, 进度值是一个0-1之间的小数
96
+ #
97
+ # @raise [ServerError] 服务端异常返回
98
+ #
99
+ # @return [Array<COS::COSFile>]
100
+ #
101
+ # @example
102
+ # files = dir.upload_all('~/test') do |p|
103
+ # puts "上传进度: #{(p*100).round(2)}%")
104
+ # end
105
+ # files.each do |file|
106
+ # puts file.url
107
+ # end
108
+ def upload_all(file_src_path, options = {}, &block)
109
+ bucket.upload_all(self, file_src_path, options, &block)
110
+ end
111
+
112
+ # 批量下载当前目录中的所有文件(不含子目录)
113
+ #
114
+ # @note sdk会自动创建本地目录
115
+ #
116
+ # @param file_store_path [String] 本地文件存储目录
117
+ # @param options [Hash] 高级参数
118
+ # @option options [Integer] :min_slice_size 完整下载最小文件大小,
119
+ # 超过此大小将会使用分片多线程断点续传
120
+ # @option options [Integer] :disable_mkdir 禁止自动创建本地文件夹, 默认会创建
121
+ # @option options [Integer] :download_retry 下载重试次数, 默认10
122
+ # @option options [Boolean] :disable_cpt 是否禁用checkpoint功能,如
123
+ # 果设置为true,则在下载的过程中不会写checkpoint文件,这意味着
124
+ # 下载失败后不能断点续传,而只能重新下载整个文件。如果这个值为
125
+ # true,则:cpt_file会被忽略。
126
+ # @option options [Integer] :threads 多线程下载线程数, 默认为10
127
+ # @option options [Integer] :slice_size 设置分片下载时每个分片的大小
128
+ # 默认为5 MB。
129
+ # @option options [String] :cpt_file 断点续传的checkpoint文件,如果
130
+ # 指定的cpt文件不存在,则会在file所在目录创建一个默认的cpt文件,
131
+ # 命名方式为:file.cpt,其中file是用户要下载的文件名。在下载的过
132
+ # 程中会不断更新此文件,成功完成下载后会删除此文件;如果指定的
133
+ # cpt文件已存在,则从cpt文件中记录的点继续下载。
134
+ #
135
+ # @yield [Float] 下载进度百分比回调, 进度值是一个0-1之间的小数
136
+ #
137
+ # @raise [ServerError] 服务端异常返回
138
+ #
139
+ # @return [Array<String>] 本地文件路径数组
140
+ #
141
+ # @example
142
+ # files = dir.download_all('~/test/') do |p|
143
+ # puts "下载进度: #{(p*100).round(2)}%")
144
+ # end
145
+ # puts files
146
+ def download_all(file_store_path, options = {}, &block)
147
+ bucket.download_all(self, file_store_path, options, &block)
148
+ end
149
+
150
+ # 列出当前文件夹下的目录及文件
151
+ #
152
+ # @param options [Hash]
153
+ # @option options [String] :prefix 搜索前缀
154
+ # 如果填写prefix, 则列出含此前缀的所有文件及目录
155
+ # @option options [Integer] :num 每页拉取的数量, 默认20条
156
+ # @option options [Symbol] :pattern 获取方式
157
+ # :dir_only 只获取目录, :file_only 只获取文件, 默认为 :both 全部获取
158
+ # @option options [Symbol] :order 排序方式 :asc 正序, :desc 倒序 默认为 :asc
159
+ #
160
+ # @raise [ServerError] 服务端异常返回
161
+ #
162
+ # @return [Enumerator<Object>] 迭代器, 其中Object可能是COS::COSFile或COS::COSDir
163
+ #
164
+ # @example
165
+ # all = dir.list
166
+ # all.each do |o|
167
+ # if o.is_a?(COS::COSFile)
168
+ # puts "File: #{o.name} #{o.format_size}"
169
+ # else
170
+ # puts "Dir: #{o.name} #{o.created_at}"
171
+ # end
172
+ # end
173
+ def list(options = {})
174
+ bucket.list(path, options)
175
+ end
176
+
177
+ alias :ls :list
178
+
179
+ # 获取当前目录树形结构
180
+ #
181
+ # @param options [Hash]
182
+ # @option options [Integer] :depth 子目录深度,默认为5
183
+ #
184
+ # @raise [ServerError] 服务端异常返回
185
+ #
186
+ # @return [Hash]
187
+ #
188
+ # @example
189
+ # tree = dir.tree
190
+ # puts tree[:resource].name
191
+ # tree[:children].each do |r|
192
+ # puts r[:resource].name
193
+ # end
194
+ #
195
+ # {
196
+ # :resource => resource,
197
+ # :children => [
198
+ # {:resource => resource, :children => [...]},
199
+ # {:resource => resource, :children => [...]},
200
+ # ...
201
+ # ]
202
+ # }
203
+ def tree(options = {})
204
+ bucket.tree(self, options)
205
+ end
206
+
207
+ # 获取当前目录Hash格式的目录树形结构, 可用于直接to_json
208
+ #
209
+ # @param options [Hash]
210
+ # @option options [Integer] :depth 子目录深度,默认为5
211
+ #
212
+ # @raise [ServerError] 服务端异常返回
213
+ #
214
+ # @return [Hash<Object>]
215
+ #
216
+ # @example
217
+ # puts bucket.hash_tree.to_json
218
+ #
219
+ # {
220
+ # :resource => {name: '', mtime: ''...},
221
+ # :children => [
222
+ # {:resource => resource, :children => [...]},
223
+ # {:resource => resource, :children => [...]},
224
+ # ...
225
+ # ]
226
+ # }
227
+ def hash_tree(options = {})
228
+ bucket.hash_tree(self, options)
229
+ end
230
+
231
+ # 在当前目录下创建子目录
232
+ #
233
+ # @param options [Hash] 高级参数
234
+ # @option options [String] :biz_attr 目录属性, 业务端维护
235
+ #
236
+ # @return [COS::COSDir]
237
+ #
238
+ # @raise [ServerError] 服务端异常返回
239
+ def create_folder(dir_name, options = {})
240
+ bucket.create_folder("#{path}#{dir_name}", options)
241
+ end
242
+
243
+ alias :mkdir :create_folder
244
+
245
+ # 获取当前目录下得文件和子目录总数信息
246
+ #
247
+ # @param options [Hash]
248
+ # @option options [String] :prefix 搜索前缀
249
+ # 如果填写prefix, 则计算含此前缀的所有文件及目录个数
250
+ #
251
+ # @return [Hash]
252
+ # * :total [Integer] 文件和目录总数
253
+ # * :files [Integer] 文件数
254
+ # * :dirs [Integer] 目录数
255
+ #
256
+ # @raise [ServerError] 服务端异常返回
257
+ def list_count(options = {})
258
+ bucket.list_count(path, options)
259
+ end
260
+
261
+ # 获取当前目录下的文件及子目录总数
262
+ #
263
+ # @return [Integer] 文件及子目录总数
264
+ #
265
+ # @raise [ServerError] 服务端异常返回
266
+ def count
267
+ bucket.count(path)
268
+ end
269
+
270
+ alias :size :count
271
+
272
+ # 获取当前目录下的文件数
273
+ #
274
+ # @return [Integer] 文件数
275
+ #
276
+ # @raise [ServerError] 服务端异常返回
277
+ def count_files
278
+ bucket.count_files(path)
279
+ end
280
+
281
+ # 获取当前目录下的子目录数
282
+ #
283
+ # @return [Integer] 子目录数
284
+ #
285
+ # @raise [ServerError] 服务端异常返回
286
+ def count_dirs
287
+ bucket.count_dirs(path)
288
+ end
289
+
290
+ # 判断当前目录是否是空的
291
+ #
292
+ # @return [Boolean] 是否为空
293
+ #
294
+ # @raise [ServerError] 服务端异常返回
295
+ def empty?
296
+ bucket.empty?(path) == 0
297
+ end
298
+
299
+ end
300
+
301
+ end