qcloud_cos 0.1.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +37 -0
- data/.travis.yml +4 -1
- data/Gemfile +1 -0
- data/README.md +79 -46
- data/Rakefile +15 -5
- data/bin/console +12 -3
- data/lib/qcloud_cos.rb +63 -2
- data/lib/qcloud_cos/api.rb +365 -0
- data/lib/qcloud_cos/authorization.rb +65 -0
- data/lib/qcloud_cos/configuration.rb +9 -0
- data/lib/qcloud_cos/convenient_api.rb +107 -0
- data/lib/qcloud_cos/error.rb +49 -0
- data/lib/qcloud_cos/http.rb +81 -0
- data/lib/qcloud_cos/model/file_object.rb +16 -0
- data/lib/qcloud_cos/model/folder_object.rb +35 -0
- data/lib/qcloud_cos/model/list.rb +34 -0
- data/lib/qcloud_cos/model/objectable.rb +9 -0
- data/lib/qcloud_cos/utils.rb +47 -0
- data/lib/qcloud_cos/version.rb +1 -1
- data/qcloud_cos.gemspec +19 -11
- data/wiki/get_started.md +761 -0
- metadata +122 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c46f40136350e35780ce86e5a216af26a75ec799
|
4
|
+
data.tar.gz: a90fe51412a6a35a83188e8509d7f51d48b469ec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9d889e680582184954f7bb93260762f9406acf285b7c7bb70ff439992bda8157e8e8c19cecc7e16ee367f2786982861d4decc8fe110bf9e73dbf4d38c4af0f30
|
7
|
+
data.tar.gz: ffd68ec5201c615ba2d12598a4595148214260a0a90f30d03c837b318768e0a5651be14e97197bcc2154854c8b298f05d4fb278c1ad416e6fa4e3068f7a91595
|
data/.rubocop.yml
ADDED
@@ -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
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,46 +1,79 @@
|
|
1
|
-
# Qcloud
|
2
|
-
|
3
|
-
It's the full featured
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
2
|
-
require
|
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 <<
|
6
|
-
t.libs <<
|
6
|
+
t.libs << 'test'
|
7
|
+
t.libs << 'lib'
|
7
8
|
t.test_files = FileList['test/**/*_test.rb']
|
8
9
|
end
|
9
10
|
|
10
|
-
task :
|
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
|
data/bin/console
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
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
|
-
|
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
|
data/lib/qcloud_cos.rb
CHANGED
@@ -1,5 +1,66 @@
|
|
1
|
-
require
|
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
|
-
|
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
|