cos 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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