qcloud_cos 0.1.0 → 0.3.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 725d8ee2d23b4c1680e6af13c12936afedffd50a
4
- data.tar.gz: 77446b32de74008c5d1a31ab665e249df72fbc4d
3
+ metadata.gz: c46f40136350e35780ce86e5a216af26a75ec799
4
+ data.tar.gz: a90fe51412a6a35a83188e8509d7f51d48b469ec
5
5
  SHA512:
6
- metadata.gz: 6f48665e19689e1565beba79216581a69ff4e55bcd426c24a16fbce545c4c3c32c59653fa7ce8f6acdecc00a85e1a4343c3ba5f0b55b05cf80b1f01af3cc1851
7
- data.tar.gz: 08c879544700e8b0ac76d9b1d9aa7a8d9a9fe4058ded37fbe65f2cdc2f30c727dcb5348e8018484785589c2949131e3d94935c3f32179b5db06936a3babcc2cc
6
+ metadata.gz: 9d889e680582184954f7bb93260762f9406acf285b7c7bb70ff439992bda8157e8e8c19cecc7e16ee367f2786982861d4decc8fe110bf9e73dbf4d38c4af0f30
7
+ data.tar.gz: ffd68ec5201c615ba2d12598a4595148214260a0a90f30d03c837b318768e0a5651be14e97197bcc2154854c8b298f05d4fb278c1ad416e6fa4e3068f7a91595
@@ -0,0 +1,37 @@
1
+ # This is the configuration used to check the rubocop source code.
2
+
3
+ AllCops:
4
+ Exclude:
5
+ - 'demo/**/*'
6
+ - 'test/**/*'
7
+
8
+ Metrics/LineLength:
9
+ Max: 100
10
+ Enabled: false
11
+
12
+ Style/Documentation:
13
+ Enabled: false
14
+
15
+ Style/DoubleNegation:
16
+ Enabled: false
17
+
18
+ Metrics/ClassLength:
19
+ Enabled: false
20
+
21
+ Style/AccessorMethodName:
22
+ Enabled: false
23
+
24
+ Style/PredicateName:
25
+ Enabled: false
26
+
27
+ Style/Lambda:
28
+ Enabled: false
29
+
30
+ Lint/UnusedMethodArgument:
31
+ Enabled: false
32
+
33
+ Style/AsciiComments:
34
+ Enabled: false
35
+
36
+ Metrics/ModuleLength:
37
+ Enabled: false
@@ -1,4 +1,7 @@
1
1
  language: ruby
2
2
  rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.0
3
6
  - 2.2.0
4
- before_install: gem install bundler -v 1.10.6
7
+ before_install: gem install bundler
data/Gemfile CHANGED
@@ -2,3 +2,4 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in qcloud_cos.gemspec
4
4
  gemspec
5
+ gem 'simplecov'
data/README.md CHANGED
@@ -1,46 +1,79 @@
1
- # Qcloud Cos
2
-
3
- It's the full featured library for qcloud cos, Enjoy it!
4
-
5
- ## Installation
6
-
7
- Add this line to your application's Gemfile:
8
-
9
- ```ruby
10
- gem 'qcloud_cos'
11
- ```
12
-
13
- And then execute:
14
-
15
- $ bundle
16
-
17
- Or install it yourself as:
18
-
19
- $ gem install qcloud_cos
20
-
21
- ## Usage
22
-
23
- QCloud::Cos.configure do |config|
24
- config.app_id = ""
25
- config.access_key = ""
26
- config.secret_key = ""
27
- config.default_bucket = "bucket-name"
28
- end
29
-
30
- QCloud::Cos.list_buckets
31
-
32
- QCloud::Cos.buckets.list
33
-
34
- QCloud::Cos.bucket.files/folders
35
-
36
- QCloud::Cos.bucket.upload
37
-
38
- ## Development
39
-
40
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
41
-
42
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
43
-
44
- ## Contributing
45
-
46
- Bug reports and pull requests are welcome on GitHub at https://github.com/zlx/qcloud_cos.
1
+ # Qcloud COS
2
+
3
+ It's the full featured Ruby SDK for Qcloud COS(Cloud Object Service).
4
+
5
+ We keep API simple but powerful, to give you more freedom.
6
+
7
+ Enjoy it!
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+
14
+ gem 'qcloud_cos'
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install qcloud_cos
23
+
24
+ ## Usage
25
+
26
+ ```ruby
27
+ QcloudCos.configure do |config|
28
+ config.app_id = 'app-id'
29
+ config.secret_id = 'secret_id'
30
+ config.secret_key = 'secret_key'
31
+ config.endpoint = "http://web.file.myqcloud.com/files/v1/"
32
+ config.bucket = "default-bucket-name"
33
+ end
34
+
35
+ QcloudCos.list # 列出 / 目录下的文件和文件夹
36
+
37
+ QcloudCos.upload('/test.log', 'Hello World')
38
+ QcloudCos.upload('/test.log', File.new('path/to/log'))
39
+
40
+ QcloudCos.upload_slice('/video.mp4', 'path/to/video.mp4')
41
+
42
+ QcloudCos.create_folder('/test/') # 创建目录
43
+ ```
44
+
45
+ More Example and Scenario, visit our [Document](#document)
46
+
47
+
48
+ ## Document
49
+
50
+ Here is original Restful API, It has the most detailed and authoritative explanation for every API.
51
+
52
+ + [COS RESTful API文档](http://www.qcloud.com/wiki/RESTful_API%E6%96%87%E6%A1%A3)
53
+ + [COS 详细文档](http://www.qcloud.com/doc/product/227/%E4%BA%A7%E5%93%81%E4%BB%8B%E7%BB%8D)
54
+
55
+ Here is our RDoc Document, It's well format to help you find more detail about methods.
56
+
57
+ + [RDoc Document](http://www.rubydoc.info/gems/qcloud_cos/0.1.0)
58
+
59
+
60
+ Here are some more guides for help you. Welcome to advice.
61
+
62
+ + [Getting Started](http://git.oschina.net/newell_zlx/cos_ruby_sdk/blob/master/wiki/get_started.md)
63
+
64
+
65
+ ## Test
66
+
67
+ We use minitest for test and rubocop for Syntax checker, If you want to make contribute to this library. Confirm below Command is success:
68
+
69
+ bundle exec rake test
70
+
71
+
72
+ ## Authors && Contributors
73
+
74
+ - [Newell](https://github.com/zlx_star)
75
+
76
+
77
+ ## License
78
+
79
+ licensed under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0.html)
data/Rakefile CHANGED
@@ -1,10 +1,20 @@
1
- require "bundler/gem_tasks"
2
- require "rake/testtask"
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+ require 'rubocop/rake_task'
3
4
 
4
5
  Rake::TestTask.new(:test) do |t|
5
- t.libs << "test"
6
- t.libs << "lib"
6
+ t.libs << 'test'
7
+ t.libs << 'lib'
7
8
  t.test_files = FileList['test/**/*_test.rb']
8
9
  end
9
10
 
10
- task :default => :test
11
+ task default: :test
12
+
13
+ task :test do
14
+ Rake::Task['test'].invoke
15
+ # Rake::Task['rubocop'].invoke
16
+ end
17
+
18
+ RuboCop::RakeTask.new do |task|
19
+ task.fail_on_error = false
20
+ end
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "bundler/setup"
4
- require "qcloud_cos"
3
+ require 'bundler/setup'
4
+ require 'qcloud_cos'
5
5
 
6
6
  # You can add fixtures and/or initialization code here to make experimenting
7
7
  # with your gem easier. You can also use a different console, if you like.
@@ -10,5 +10,14 @@ require "qcloud_cos"
10
10
  # require "pry"
11
11
  # Pry.start
12
12
 
13
- require "irb"
13
+ QcloudCos.configure do |config|
14
+ config.app_id = ENV['QCLOUD_APP_ID'] || 'app-id'
15
+ config.secret_id = ENV['QCLOUD_SECRET_ID'] || 'secret_id'
16
+ config.secret_key = ENV['QCLOUD_SECRET_KEY'] || 'secret_key'
17
+ config.endpoint = ENV['QCLOUD_ENDPOINT'] || 'https://web.file.myqcloud.com/files/v1/'
18
+ config.bucket = ENV['QCLOUD_BUCKET'] || 'privatesdkdemo'
19
+ config.ssl_ca_file = ENV['SSL_CA_FILE'] || 'path/to/ssl/ca/file'
20
+ end
21
+
22
+ require 'irb'
14
23
  IRB.start
@@ -1,5 +1,66 @@
1
- require "qcloud_cos/version"
1
+ require 'qcloud_cos/version'
2
+ require 'qcloud_cos/configuration'
3
+ require 'qcloud_cos/authorization'
4
+ require 'qcloud_cos/http'
5
+ require 'qcloud_cos/api'
6
+ require 'qcloud_cos/convenient_api'
2
7
 
3
8
  module QcloudCos
4
- # Your code goes here...
9
+ EXPIRED_SECONDS = 60 # 60 seconds
10
+ PUBLIC_EXPIRED_SECONDS = 600 # 10 minutes
11
+ DEFAULT_SLICE_SIZE = 3_145_728 # 3M
12
+ MIN_SLICE_FILE_SIZE = 10 # 10M
13
+ MAX_RETRY_TIMES = 3
14
+
15
+ class << self
16
+ include Api
17
+ include ConvenientApi
18
+
19
+ def configure
20
+ @configuration ||= Configuration.new
21
+ yield @configuration
22
+ @configuration
23
+ end
24
+
25
+ def config
26
+ @configuration
27
+ end
28
+
29
+ private
30
+
31
+ def http
32
+ @http ||= Http.new(config)
33
+ end
34
+
35
+ def authorization
36
+ @authorization ||= Authorization.new(QcloudCos.config)
37
+ end
38
+
39
+ def validates(path, options, path_validate = :file_only)
40
+ Utils.stringify_keys!(options)
41
+ file_validates(path, path_validate)
42
+
43
+ bucket = options['bucket'] || config.bucket
44
+ fail MissingBucketError unless bucket
45
+
46
+ bucket
47
+ end
48
+
49
+ def fixed_path(path)
50
+ path.start_with?('/') ? path : "/#{path}"
51
+ end
52
+
53
+ def generate_rest_url(bucket, path)
54
+ "#{config.endpoint}#{config.app_id}/#{bucket}#{path}"
55
+ end
56
+
57
+ def file_validates(path, path_validate)
58
+ case path_validate.to_s
59
+ when 'file_only'
60
+ fail InvalidFilePathError if path.end_with?('/')
61
+ when 'folder_only'
62
+ FolderObject.validate(path)
63
+ end
64
+ end
65
+ end
5
66
  end
@@ -0,0 +1,365 @@
1
+ # encoding: utf-8
2
+ require 'qcloud_cos/utils'
3
+ require 'qcloud_cos/model/list'
4
+
5
+ module QcloudCos
6
+ module Api
7
+ # 列出所有文件或者目录
8
+ #
9
+ # @param path [String] 指定目标路径, 以 / 结尾, 则列出该目录下文件或者文件夹,不以 / 结尾,就搜索该前缀的文件或者文件夹
10
+ # @param options [Hash] 额外参数
11
+ # @option options [String] :bucket (config.bucket_name) 指定当前 bucket, 默认是配置里面的 bucket
12
+ # @option options [Integer] :num (100) 指定需要拉取的条目
13
+ # @option options [String] :pattern (eListBoth) 指定拉取的内容,可选值: eListBoth, eListDirOnly, eListFileOnly
14
+ # @option options [Integer] :order (0) 指定拉取文件的顺序, 默认为正序(=0), 可选值: 0, 1
15
+ # @option options [String] :context ("") 透传字段,查看第一页,则传空字符串。若需要翻页,需要将前一页返回值中的context透传到参数中。order用于指定翻页顺序。若order填0,则从当前页正序/往下翻页;若order填1,则从当前页倒序/往上翻页。
16
+ #
17
+ # @return [Hash]
18
+ def list(path = '/', options = {})
19
+ path = fixed_path(path)
20
+ bucket = validates(path, options, 'both')
21
+
22
+ query = {
23
+ 'op' => 'list',
24
+ 'num' => 100
25
+ }.merge(Utils.hash_slice(options, 'num', 'pattern', 'order', 'context'))
26
+
27
+ url = generate_rest_url(bucket, path)
28
+ sign = authorization.sign(bucket)
29
+
30
+ result = http.get(url, query: query, headers: { 'Authorization' => sign }).parsed_response
31
+ QcloudCos::List.new(result['data'])
32
+ end
33
+
34
+ # 列出所有文件
35
+ #
36
+ # @param path [String] 指定目标路径, 以 / 结尾, 则列出该目录下文件,不以 / 结尾,就搜索该前缀的文件
37
+ # @param options [Hash] 额外参数
38
+ # @option options [String] :bucket (config.bucket_name) 指定当前 bucket, 默认是配置里面的 bucket
39
+ # @option options [Integer] :num (100) 指定需要拉取的条目
40
+ # @option options [Integer] :order (0) 指定拉取文件的顺序, 默认为正序(=0), 可选值: 0, 1
41
+ # @option options [String] :context ("") 透传字段,查看第一页,则传空字符串。若需要翻页,需要将前一页返回值中的context透传到参数中。order用于指定翻页顺序。若order填0,则从当前页正序/往下翻页;若order填1,则从当前页倒序/往上翻页。
42
+ #
43
+ # @return [Hash]
44
+ def list_files(path = '/', options = {})
45
+ Utils.stringify_keys!(options)
46
+ list(path, options.merge('pattern' => 'eListFileOnly'))
47
+ end
48
+
49
+ # 列出所有目录
50
+ #
51
+ # @param path [String] 指定目标路径, 以 / 结尾, 则列出该目录下文件夹,不以 / 结尾,就搜索该前缀的文件夹
52
+ # @param options [Hash] 额外参数
53
+ # @option options [String] :bucket (config.bucket_name) 指定当前 bucket, 默认是配置里面的 bucket
54
+ # @option options [Integer] :num (100) 指定需要拉取的条目
55
+ # @option options [Integer] :order (0) 指定拉取文件的顺序, 默认为正序(=0), 可选值: 0, 1
56
+ # @option options [String] :context ("") 透传字段,查看第一页,则传空字符串。若需要翻页,需要将前一页返回值中的context透传到参数中。order用于指定翻页顺序。若order填0,则从当前页正序/往下翻页;若order填1,则从当前页倒序/往上翻页。
57
+ #
58
+ # @return [Hash]
59
+ def list_folders(path = '/', options = {})
60
+ Utils.stringify_keys!(options)
61
+ list(path, options.merge('pattern' => 'eListDirOnly'))
62
+ end
63
+
64
+ # 创建目录
65
+ #
66
+ # @param path [String] 指定要创建的文件夹名字,支持级联创建
67
+ # @param options [Hash] options
68
+ # @option options [String] :bucket (config.bucket_name) 指定当前 bucket, 默认是配置里面的 bucket
69
+ # @option options [Integer] :biz_attr 指定目录的 biz_attr 由业务端维护, 会在文件信息中返回
70
+ #
71
+ # @return [Hash]
72
+ def create_folder(path, options = {})
73
+ path = fixed_path(path)
74
+ bucket = validates(path, options, :folder_only)
75
+
76
+ url = generate_rest_url(bucket, path)
77
+
78
+ query = { 'op' => 'create' }.merge(Utils.hash_slice(options, 'biz_attr'))
79
+
80
+ headers = {
81
+ 'Authorization' => authorization.sign(bucket),
82
+ 'Content-Type' => 'application/json'
83
+ }
84
+
85
+ http.post(url, body: query.to_json, headers: headers).parsed_response
86
+ end
87
+
88
+ # 上传文件
89
+ #
90
+ # @param path [String] 指定上传文件的路径
91
+ # @param file_or_bin [File||String] 指定文件或者文件内容
92
+ # @param options [Hash] options
93
+ # @option options [String] :bucket (config.bucket_name) 指定当前 bucket, 默认是配置里面的 bucket
94
+ # @option options [Integer] :biz_attr 指定文件的 biz_attr 由业务端维护, 会在文件信息中返回
95
+ #
96
+ # @return [Hash]
97
+ def upload(path, file_or_bin, options = {})
98
+ path = fixed_path(path)
99
+ bucket = validates(path, options)
100
+
101
+ url = generate_rest_url(bucket, path)
102
+
103
+ query = {
104
+ 'op' => 'upload'
105
+ }.merge(Utils.hash_slice(options, 'biz_attr')).merge(generate_file_query(file_or_bin))
106
+
107
+ http.post(url, query: query, headers: { 'Authorization' => authorization.sign(bucket) }).parsed_response
108
+ end
109
+ alias_method :create, :upload
110
+
111
+ # 分片上传
112
+ #
113
+ # @param dst_path [String] 指定文件的目标路径
114
+ # @param src_path [String] 指定文件的本地路径
115
+ # @param options [Hash] options
116
+ # @option options [String] :bucket (config.bucket_name) 指定当前 bucket, 默认是配置里面的 bucket
117
+ # @option options [Integer] :biz_attr 指定文件的 biz_attr 由业务端维护, 会在文件信息中返回
118
+ # @option options [Integer] :session 指定本次分片上传的 session
119
+ # @option options [Integer] :slice_size 指定分片大小
120
+ #
121
+ # @raise [MissingSessionIdError] 如果缺少 session
122
+ # @raise [FileNotExistError] 如果本地文件不存在
123
+ # @raise [InvalidFilePathError] 如果目标路径是非法文件路径
124
+ #
125
+ # @return [Hash]
126
+ def upload_slice(dst_path, src_path, options = {})
127
+ dst_path = fixed_path(dst_path)
128
+ fail FileNotExistError unless File.exist?(src_path)
129
+ bucket = validates(dst_path, options)
130
+
131
+ filesize = File.size(src_path)
132
+ sha = Utils.generate_file_sha(src_path)
133
+ sign = authorization.sign(bucket)
134
+
135
+ resp = init_slice_upload(dst_path, filesize, sha, options.merge('sign' => sign))
136
+ puts "init slice upload: #{resp}"
137
+
138
+ data = resp['data']
139
+ return data if data.key?('url') # 妙传命中
140
+
141
+ slice_size = data['slice_size'] || DEFAULT_SLICE_SIZE
142
+ session = data['session'] || options['session']
143
+ offset = data['offset'] || 0
144
+
145
+ fail MissingSessionIdError unless session
146
+
147
+ while offset < filesize
148
+ filecontent = IO.read(src_path, slice_size, offset)
149
+
150
+ retry_times = 0
151
+ begin
152
+ result = upload_part(dst_path, session, offset, filecontent, options)
153
+ puts "upload part data: #{result}"
154
+
155
+ if result.key?('data') && result['data'].key?('session')
156
+ session = result['data']['session']
157
+ elsif result.key?('data') && result['data'].key?('url')
158
+ return result['data']
159
+ end
160
+ rescue => e
161
+ retry_times += 1
162
+ puts "retry part upload request with offset:#{offset}, slice_size:#{slice_size} ..."
163
+ retry if retry_times <= config.max_retry_times
164
+ raise e
165
+ end
166
+ offset += slice_size
167
+ end
168
+ end
169
+
170
+ # 初始化分片上传
171
+ # @private
172
+ #
173
+ # @param path [String] 指定上传文件的路径
174
+ # @param filesize [Integer] 指定文件总大小
175
+ # @param sha [String] 指定该文件的 sha 值
176
+ # @param options [Hash] options
177
+ # @option options [String] :bucket (config.bucket_name) 指定当前 bucket, 默认是配置里面的 bucket
178
+ # @option options [Integer] :biz_attr 指定文件的 biz_attr 由业务端维护, 会在文件信息中返回
179
+ # @option options [Integer] :session 如果想要断点续传,则带上上一次的session
180
+ # @option options [Integer] :slice_size 指定分片大小
181
+ #
182
+ # @return [Hash]
183
+ def init_slice_upload(path, filesize, sha, options = {})
184
+ path = fixed_path(path)
185
+ bucket = validates(path, options)
186
+
187
+ url = generate_rest_url(bucket, path)
188
+ query = generate_slice_upload_query(filesize, sha, options)
189
+ sign = options['sign'] || authorization.sign(bucket)
190
+
191
+ http.post(url, query: query, headers: { 'Authorization' => sign }).parsed_response
192
+ end
193
+ private :init_slice_upload
194
+
195
+ # 上传分片数据
196
+ # @private
197
+ #
198
+ # @param path [String] 指定上传文件的路径
199
+ # @param session [String] 指定分片上传的 session id
200
+ # @param offset [Integer] 本次分片位移
201
+ # @param content [Binary] 指定文件内容
202
+ # @param options [Hash] options
203
+ # @option options [String] :bucket (config.bucket_name) 指定当前 bucket, 默认是配置里面的 bucket
204
+ #
205
+ # @return [Hash]
206
+ def upload_part(path, session, offset, content, options = {})
207
+ path = fixed_path(path)
208
+ bucket = validates(path, options)
209
+
210
+ url = generate_rest_url(bucket, path)
211
+ query = generate_upload_part_query(session, offset, content)
212
+ sign = options['sign'] || authorization.sign(bucket)
213
+
214
+ http.post(url, query: query, headers: { 'Authorization' => sign }).parsed_response
215
+ end
216
+ private :upload_part
217
+
218
+ # 更新文件或者目录信息
219
+ #
220
+ # @param path [String] 指定文件或者目录路径
221
+ # @param biz_attr [String] 指定文件或者目录的 biz_attr
222
+ # @param options [Hash] 额外参数
223
+ # @option options [String] :bucket (config.bucket_name) 指定当前 bucket, 默认是配置里面的 bucket
224
+ #
225
+ # @return [Hash]
226
+ def update(path, biz_attr, options = {})
227
+ path = fixed_path(path)
228
+ bucket = validates(path, options, 'both')
229
+ url = generate_rest_url(bucket, path)
230
+
231
+ query = { 'op' => 'update', 'biz_attr' => biz_attr }
232
+
233
+ resource = "/#{bucket}#{Utils.url_encode(path)}"
234
+ headers = {
235
+ 'Authorization' => authorization.sign_once(bucket, resource),
236
+ 'Content-Type' => 'application/json'
237
+ }
238
+
239
+ http.post(url, body: query.to_json, headers: headers).parsed_response
240
+ end
241
+
242
+ # 删除文件或者目录
243
+ #
244
+ # @param path [String] 指定文件或者目录路径
245
+ # @param options [Hash] 额外参数
246
+ # @option options [String] :bucket (config.bucket_name) 指定当前 bucket, 默认是配置里面的 bucket
247
+ #
248
+ # @return [Hash]
249
+ def delete(path, options = {})
250
+ path = fixed_path(path)
251
+ bucket = validates(path, options, 'both')
252
+ url = generate_rest_url(bucket, path)
253
+
254
+ query = { 'op' => 'delete' }
255
+
256
+ resource = "/#{bucket}#{Utils.url_encode(path)}"
257
+ headers = {
258
+ 'Authorization' => authorization.sign_once(bucket, resource),
259
+ 'Content-Type' => 'application/json'
260
+ }
261
+
262
+ http.post(url, body: query.to_json, headers: headers).parsed_response
263
+ end
264
+
265
+ # 删除目录
266
+ #
267
+ # @param path [String] 指定目录路径
268
+ # @param options [Hash] 额外参数
269
+ # @option options [String] :bucket (config.bucket_name) 指定当前 bucket, 默认是配置里面的 bucket
270
+ # @option options [Boolean] :recursive (false) 指定是否需要级连删除
271
+ #
272
+ # @raise [InvalidFolderPathError] 如果路径是非法文件夹路径
273
+ #
274
+ # @return [Hash]
275
+ def delete_folder(path, options = {})
276
+ validates(path, options, 'folder_only')
277
+
278
+ return delete(path, options) if options['recursive'] != true
279
+
280
+ loop do
281
+ objects = list(path, options)
282
+ objects.each do |object|
283
+ if object.is_a?(QcloudCos::FolderObject)
284
+ delete_folder("#{path}#{object.name}/", options)
285
+ elsif object.is_a?(QcloudCos::FileObject)
286
+ delete_file("#{path}#{object.name}", options)
287
+ end
288
+ end
289
+ break unless objects.has_more
290
+ options['context'] = objects.context
291
+ end
292
+ delete(path)
293
+ end
294
+
295
+ # 删除文件
296
+ #
297
+ # @param path [String] 指定文件路径
298
+ # @param options [Hash] 额外参数
299
+ # @option options [String] :bucket (config.bucket_name) 指定当前 bucket, 默认是配置里面的 bucket
300
+ #
301
+ # @raise [InvalidFilePathError] 如果文件路径不合法
302
+ #
303
+ # @return [Hash]
304
+ def delete_file(path, options = {})
305
+ fail InvalidFilePathError if path.end_with?('/')
306
+ delete(path, options)
307
+ end
308
+
309
+ # 查看文件或者文件夹信息
310
+ #
311
+ # @param path [String] 指定文件或者文件夹目录
312
+ # @param options [Hash] 额外参数
313
+ # @option options [String] :bucket (config.bucket_name) 指定当前 bucket, 默认是配置里面的 bucket
314
+ #
315
+ # @return [Hash]
316
+ def stat(path, options = {})
317
+ path = fixed_path(path)
318
+ bucket = validates(path, options, 'both')
319
+ url = generate_rest_url(bucket, path)
320
+
321
+ query = { 'op' => 'stat' }
322
+ sign = authorization.sign(bucket)
323
+
324
+ http.get(url, query: query, headers: { 'Authorization' => sign }).parsed_response
325
+ end
326
+
327
+ private
328
+
329
+ def generate_slice_upload_query(filesize, sha, options)
330
+ {
331
+ 'op' => 'upload_slice',
332
+ 'filesize' => filesize,
333
+ 'sha' => sha,
334
+ 'filecontent' => Tempfile.new("temp-#{Time.now.to_i}")
335
+ }.merge(Utils.hash_slice(options, 'biz_attr', 'session', 'slice_size'))
336
+ end
337
+
338
+ def generate_upload_part_query(session, offset, content)
339
+ {
340
+ 'op' => 'upload_slice',
341
+ 'session' => session,
342
+ 'offset' => offset
343
+ }.merge(generate_file_query(content))
344
+ end
345
+
346
+ def generate_file_query(file_or_bin)
347
+ query = {}
348
+ if file_or_bin.respond_to?(:read)
349
+ query['filecontent'] = file_or_bin
350
+ query['sha'] = Utils.generate_sha(IO.binread(file_or_bin))
351
+ else
352
+ query['filecontent'] = generate_tempfile(file_or_bin)
353
+ query['sha'] = Utils.generate_sha(file_or_bin)
354
+ end
355
+ query
356
+ end
357
+
358
+ def generate_tempfile(file_or_bin)
359
+ tempfile = Tempfile.new("temp-#{Time.now.to_i}")
360
+ tempfile.write(file_or_bin)
361
+ tempfile.rewind
362
+ tempfile
363
+ end
364
+ end
365
+ end