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 +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
|