qiniu-rs 3.0.6 → 3.1.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.
- data/Gemfile.lock +2 -2
- data/docs/README.md +42 -1
- data/lib/qiniu/rs.rb +30 -11
- data/lib/qiniu/rs/abstract.rb +24 -0
- data/lib/qiniu/rs/auth.rb +3 -3
- data/lib/qiniu/rs/config.rb +9 -5
- data/lib/qiniu/rs/exceptions.rb +38 -1
- data/lib/qiniu/rs/up.rb +337 -0
- data/lib/qiniu/rs/utils.rb +18 -4
- data/lib/qiniu/rs/version.rb +13 -1
- data/qiniu-rs.gemspec +1 -1
- data/spec/qiniu/rs/abstract_spec.rb +30 -0
- data/spec/qiniu/rs/rs_spec.rb +3 -3
- data/spec/qiniu/rs/up_spec.rb +51 -0
- data/spec/qiniu/rs/version_spec.rb +2 -2
- data/spec/qiniu/rs_spec.rb +36 -0
- data/spec/spec_helper.rb +5 -0
- metadata +10 -4
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
qiniu-rs (3.0
|
4
|
+
qiniu-rs (3.1.0)
|
5
5
|
json (~> 1.7)
|
6
6
|
mime-types (~> 1.19)
|
7
7
|
rest-client (~> 1.6)
|
@@ -24,7 +24,7 @@ GEM
|
|
24
24
|
rspec-core (2.11.1)
|
25
25
|
rspec-expectations (2.11.3)
|
26
26
|
diff-lcs (~> 1.1.3)
|
27
|
-
rspec-mocks (2.11.
|
27
|
+
rspec-mocks (2.11.3)
|
28
28
|
ruby-hmac (0.4.0)
|
29
29
|
|
30
30
|
PLATFORMS
|
data/docs/README.md
CHANGED
@@ -17,6 +17,7 @@ title: Ruby SDK 使用指南 | 七牛云存储
|
|
17
17
|
- [上传文件](#upload)
|
18
18
|
- [获取用于上传文件的临时授权凭证](#generate-upload-token)
|
19
19
|
- [服务端上传文件](#upload-server-side)
|
20
|
+
- [断点续上传](#resumable-upload)
|
20
21
|
- [针对 NotFound 场景处理](#upload-file-for-not-found)
|
21
22
|
- [客户端直传文件](#upload-client-side)
|
22
23
|
- [查看文件属性信息](#stat)
|
@@ -185,10 +186,50 @@ title: Ruby SDK 使用指南 | 七牛云存储
|
|
185
186
|
|
186
187
|
**返回值**
|
187
188
|
|
188
|
-
上传成功,返回如下一个 Hash
|
189
|
+
上传成功,返回如下一个 Hash:
|
189
190
|
|
190
191
|
{"hash"=>"FgHk-_iqpnZji6PsNr4ghsK5qEwR"}
|
191
192
|
|
193
|
+
上传失败,会抛出 `UploadFailedError` 异常。
|
194
|
+
|
195
|
+
<a name="resumable-upload"></a>
|
196
|
+
|
197
|
+
##### 断点续上传
|
198
|
+
|
199
|
+
无需任何额外改动,SDK 提供的 `Qiniu::RS.upload_file()` 方法缺省支持断点续上传。默认情况下,SDK 会自动启用断点续上传的方式来上传超过 4MB 大小的文件。您也可以在 [应用接入](/v3/sdk/ruby/#establish_connection!) 时通过修改缺省配置来设置该阀值:
|
200
|
+
|
201
|
+
Qiniu::RS.establish_connection! :access_key => YOUR_APP_ACCESS_KEY,
|
202
|
+
:secret_key => YOUR_APP_SECRET_KEY,
|
203
|
+
:block_size => 1024*1024*4,
|
204
|
+
:chunk_size => 1024*256,
|
205
|
+
:tmpdir => Dir.tmpdir + File::SEPARATOR + 'Qiniu-RS-Ruby-SDK',
|
206
|
+
:enable_debug => true,
|
207
|
+
:auto_reconnect => true,
|
208
|
+
:max_retry_times => 3
|
209
|
+
|
210
|
+
**参数详解**
|
211
|
+
|
212
|
+
应用接入初始化时,以下配置参数均为可选:
|
213
|
+
|
214
|
+
:block_size
|
215
|
+
: 整型,指定断点续上传针对大文件所使用的分块大小,缺省为 4MB ,小于该阀值的文件不启用断点续上传。
|
216
|
+
|
217
|
+
:chunk_size
|
218
|
+
: 整型,指定断点续上传每次http请求上传的数据块大小,缺省为 256KB。该设置尽量不要超过实际使用的上行带宽,且不能超过 `:block_size` 定义的值。
|
219
|
+
|
220
|
+
:tmpdir
|
221
|
+
: 字符串类型,指定持久化保存断点续上传进度状态临时文件的目录,缺省放置于操作系统的临时目录中。
|
222
|
+
|
223
|
+
:enable_debug
|
224
|
+
: 布尔值,是否启用调试模式,缺省启用(true),启用后会打印相关日志。该参数 SDK 全局有效。
|
225
|
+
|
226
|
+
:auto_reconnect
|
227
|
+
: 布尔值,指定每次 http 若请求失败是否启用重试,缺省启用(true)。该参数 SDK 全局有效。
|
228
|
+
|
229
|
+
:max_retry_times
|
230
|
+
: 整型,指定每次 http 若请求失败最多可以重试的次数,缺省为3次。该参数 SDK 全局有效。
|
231
|
+
|
232
|
+
|
192
233
|
<a name="upload-file-for-not-found"></a>
|
193
234
|
|
194
235
|
##### 针对 NotFound 场景处理
|
data/lib/qiniu/rs.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
|
3
|
-
require 'qiniu/rs/version'
|
4
|
-
|
5
3
|
module Qiniu
|
6
4
|
module RS
|
5
|
+
autoload :Version, 'qiniu/rs/version'
|
7
6
|
autoload :Config, 'qiniu/rs/config'
|
8
7
|
autoload :Log, 'qiniu/rs/log'
|
9
8
|
autoload :Exception, 'qiniu/rs/exceptions'
|
10
9
|
autoload :Utils, 'qiniu/rs/utils'
|
11
10
|
autoload :Auth, 'qiniu/rs/auth'
|
12
11
|
autoload :IO, 'qiniu/rs/io'
|
12
|
+
autoload :UP, 'qiniu/rs/up'
|
13
13
|
autoload :RS, 'qiniu/rs/rs'
|
14
14
|
autoload :EU, 'qiniu/rs/eu'
|
15
15
|
autoload :Pub, 'qiniu/rs/pub'
|
@@ -17,6 +17,7 @@ module Qiniu
|
|
17
17
|
autoload :AccessToken, 'qiniu/tokens/access_token'
|
18
18
|
autoload :QboxToken, 'qiniu/tokens/qbox_token'
|
19
19
|
autoload :UploadToken, 'qiniu/tokens/upload_token'
|
20
|
+
autoload :Abstract, 'qiniu/rs/abstract'
|
20
21
|
|
21
22
|
class << self
|
22
23
|
|
@@ -99,15 +100,33 @@ module Qiniu
|
|
99
100
|
end
|
100
101
|
|
101
102
|
def upload_file opts = {}
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
103
|
+
[:uptoken, :file, :bucket, :key].each do |opt|
|
104
|
+
raise MissingArgsError, [opt] unless opts.has_key?(opt)
|
105
|
+
end
|
106
|
+
source_file = opts[:file]
|
107
|
+
raise NoSuchFileError, source_file unless File.exist?(source_file)
|
108
|
+
opts[:enable_resumable_upload] = true unless opts.has_key?(:enable_resumable_upload)
|
109
|
+
if opts[:enable_resumable_upload] && File::size(source_file) > Config.settings[:block_size]
|
110
|
+
code, data = UP.upload_with_token(opts[:uptoken],
|
111
|
+
opts[:file],
|
112
|
+
opts[:bucket],
|
113
|
+
opts[:key],
|
114
|
+
opts[:mime_type],
|
115
|
+
opts[:note],
|
116
|
+
opts[:customer],
|
117
|
+
opts[:callback_params])
|
118
|
+
else
|
119
|
+
code, data = IO.upload_with_token(opts[:uptoken],
|
120
|
+
opts[:file],
|
121
|
+
opts[:bucket],
|
122
|
+
opts[:key],
|
123
|
+
opts[:mime_type],
|
124
|
+
opts[:note],
|
125
|
+
opts[:callback_params],
|
126
|
+
opts[:enable_crc32_check])
|
127
|
+
end
|
128
|
+
raise UploadFailedError.new(code, data) if code != StatusOK
|
129
|
+
return data
|
111
130
|
end
|
112
131
|
|
113
132
|
def stat(bucket, key)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module Qiniu
|
4
|
+
module RS
|
5
|
+
module Abstract
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def abstract_methods(*args)
|
12
|
+
args.each do |name|
|
13
|
+
class_eval <<-END
|
14
|
+
def #{name}(*args)
|
15
|
+
errmsg = %Q(class \#{self.class.name} must implement abstract method #{self.name}##{name}().)
|
16
|
+
raise NotImplementedError.new(errmsg)
|
17
|
+
end
|
18
|
+
END
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/qiniu/rs/auth.rb
CHANGED
@@ -18,7 +18,7 @@ module Qiniu
|
|
18
18
|
:password => password
|
19
19
|
}
|
20
20
|
code, data = http_request Config.settings[:auth_url], post_data
|
21
|
-
reset_token(data["access_token"], data["refresh_token"]) if code
|
21
|
+
reset_token(data["access_token"], data["refresh_token"]) if Utils.is_response_ok?(code)
|
22
22
|
[code, data]
|
23
23
|
end
|
24
24
|
|
@@ -29,7 +29,7 @@ module Qiniu
|
|
29
29
|
:refresh_token => refresh_token
|
30
30
|
}
|
31
31
|
code, data = http_request Config.settings[:auth_url], post_data
|
32
|
-
reset_token(data["access_token"], data["refresh_token"]) if code
|
32
|
+
reset_token(data["access_token"], data["refresh_token"]) if Utils.is_response_ok?(code)
|
33
33
|
[code, data]
|
34
34
|
end
|
35
35
|
|
@@ -48,7 +48,7 @@ module Qiniu
|
|
48
48
|
raise MissingUsernameOrPassword if (@username.nil? || @password.nil?)
|
49
49
|
code, data = exchange_by_password!(@username, @password)
|
50
50
|
end
|
51
|
-
if code
|
51
|
+
if Utils.is_response_ok?(code)
|
52
52
|
retry_times += 1
|
53
53
|
if Config.settings[:auto_reconnect] && retry_times < Config.settings[:max_retry_times]
|
54
54
|
return call_with_logged_in(url, data, retry_times)
|
data/lib/qiniu/rs/config.rb
CHANGED
@@ -8,7 +8,7 @@
|
|
8
8
|
# Qbox::Config.load "path/to/your_project/config/qiniu.yml"
|
9
9
|
#
|
10
10
|
|
11
|
-
require
|
11
|
+
require 'tmpdir'
|
12
12
|
|
13
13
|
module Qiniu
|
14
14
|
module RS
|
@@ -16,11 +16,11 @@ module Qiniu
|
|
16
16
|
class << self
|
17
17
|
|
18
18
|
DEFAULT_OPTIONS = {
|
19
|
-
:user_agent => 'Qiniu-RS-Ruby-SDK-' +
|
19
|
+
:user_agent => 'Qiniu-RS-Ruby-SDK-' + Version.to_s + '()',
|
20
20
|
:method => :post,
|
21
21
|
:content_type => 'application/x-www-form-urlencoded',
|
22
22
|
:auth_url => "https://acc.qbox.me/oauth2/token",
|
23
|
-
:rs_host => "http://rs.qbox.me
|
23
|
+
:rs_host => "http://rs.qbox.me",
|
24
24
|
:io_host => "http://iovip.qbox.me",
|
25
25
|
:up_host => "http://up.qbox.me",
|
26
26
|
:pub_host => "http://pu.qbox.me:10200",
|
@@ -30,10 +30,14 @@ module Qiniu
|
|
30
30
|
:access_key => "",
|
31
31
|
:secret_key => "",
|
32
32
|
:auto_reconnect => true,
|
33
|
-
:max_retry_times =>
|
33
|
+
:max_retry_times => 3,
|
34
|
+
:block_size => 1024*1024*4,
|
35
|
+
:chunk_size => 1024*256,
|
36
|
+
:enable_debug => true,
|
37
|
+
:tmpdir => Dir.tmpdir + File::SEPARATOR + 'Qiniu-RS-Ruby-SDK'
|
34
38
|
}
|
35
39
|
|
36
|
-
REQUIRED_OPTION_KEYS = [:
|
40
|
+
REQUIRED_OPTION_KEYS = [:access_key, :secret_key]
|
37
41
|
|
38
42
|
attr_reader :settings, :default_params
|
39
43
|
|
data/lib/qiniu/rs/exceptions.rb
CHANGED
@@ -32,7 +32,7 @@ module Qiniu
|
|
32
32
|
|
33
33
|
class RequestFailed < ResponseError
|
34
34
|
def message
|
35
|
-
"HTTP status code #{http_code}"
|
35
|
+
"HTTP status code: #{http_code}. Response body: #{http_body}"
|
36
36
|
end
|
37
37
|
|
38
38
|
def to_s
|
@@ -40,6 +40,43 @@ module Qiniu
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
+
class UploadFailedError < Exception
|
44
|
+
def initialize(status_code, response_data)
|
45
|
+
data_string = response_data.map { |key, value| %Q(:#{key.to_s} => #{value.to_s}) }
|
46
|
+
msg = %Q(Uploading Failed. HTTP Status Code: #{status_code}. HTTP response body: #{data_string.join(', ')}.)
|
47
|
+
super(msg)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class FileSeekReadError < Exception
|
52
|
+
def initialize(fpath, block_index, seek_pos, read_length, result_length)
|
53
|
+
msg = "Reading file: #{fpath}, "
|
54
|
+
msg += "at block index: #{block_index}. "
|
55
|
+
msg += "Expected seek_pos:#{seek_pos} and read_length:#{read_length}, "
|
56
|
+
msg += "but got result_length: #{result_length}."
|
57
|
+
super(msg)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class BlockSizeNotMathchError < Exception
|
62
|
+
def initialize(fpath, block_index, offset, restsize, block_size)
|
63
|
+
msg = "Reading file: #{fpath}, "
|
64
|
+
msg += "at block index: #{block_index}. "
|
65
|
+
msg += "Expected offset: #{offset}, restsize: #{restsize} and block_size: #{block_size}, "
|
66
|
+
msg += "but got offset+restsize=#{offset+restsize}."
|
67
|
+
super(msg)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class BlockCountNotMathchError < Exception
|
72
|
+
def initialize(fpath, block_count, checksum_count, progress_count)
|
73
|
+
msg = "Reading file: #{fpath}, "
|
74
|
+
msg += "Expected block_count, checksum_count, progress_count is: #{block_count}, "
|
75
|
+
msg += "but got checksum_count: #{checksum_count}, progress_count: #{progress_count}."
|
76
|
+
super(msg)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
43
80
|
class MissingArgsError < Exception
|
44
81
|
def initialize(missing_keys)
|
45
82
|
key_list = missing_keys.map {|key| key.to_s}.join(' and the ')
|
data/lib/qiniu/rs/up.rb
ADDED
@@ -0,0 +1,337 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'zlib'
|
4
|
+
require 'yaml'
|
5
|
+
require 'tmpdir'
|
6
|
+
require 'fileutils'
|
7
|
+
require 'mime/types'
|
8
|
+
require 'digest/sha1'
|
9
|
+
require 'qiniu/rs/abstract'
|
10
|
+
require 'qiniu/rs/exceptions'
|
11
|
+
require 'qiniu/rs/io'
|
12
|
+
|
13
|
+
module Qiniu
|
14
|
+
module RS
|
15
|
+
module UP
|
16
|
+
|
17
|
+
module AbstractClass
|
18
|
+
class ChunkProgressNotifier
|
19
|
+
include Qiniu::RS::Abstract
|
20
|
+
abstract_methods :notify
|
21
|
+
# def notify(block_index, block_put_progress); end
|
22
|
+
end
|
23
|
+
|
24
|
+
class BlockProgressNotifier
|
25
|
+
include Qiniu::RS::Abstract
|
26
|
+
abstract_methods :notify
|
27
|
+
# def notify(block_index, checksum); end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class ChunkProgressNotifier < AbstractClass::ChunkProgressNotifier
|
32
|
+
def initialize(id)
|
33
|
+
@data = UP::ProgressData.new(id)
|
34
|
+
end
|
35
|
+
def notify(index, progress)
|
36
|
+
@data.set_progresses(index, progress)
|
37
|
+
logmsg = "chunk #{progress[:offset]/Config.settings[:chunk_size]} in block #{index} successfully uploaded.\n" +
|
38
|
+
"{ctx:#{progress[:ctx]}, offset:#{progress[:offset]}, restsize:#{progress[:restsize]}, status_code:#{progress[:status_code]}}"
|
39
|
+
Utils.debug(logmsg)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class BlockProgressNotifier < AbstractClass::BlockProgressNotifier
|
44
|
+
def initialize(id)
|
45
|
+
@data = UP::ProgressData.new(id)
|
46
|
+
end
|
47
|
+
def notify(index, checksum)
|
48
|
+
@data.set_checksums(index, checksum)
|
49
|
+
Utils.debug "block #{index}: {checksum: #{checksum}} successfully uploaded."
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class ProgressData
|
54
|
+
def initialize(id)
|
55
|
+
@id = id
|
56
|
+
@tmpdir = Config.settings[:tmpdir] + File::SEPARATOR + @id
|
57
|
+
FileUtils.mkdir_p(@tmpdir) unless Dir.exists?(@tmpdir)
|
58
|
+
@checksum_file = @tmpdir + File::SEPARATOR + 'checksums'
|
59
|
+
@progress_file = @tmpdir + File::SEPARATOR + 'progresses'
|
60
|
+
end
|
61
|
+
|
62
|
+
def get_checksums
|
63
|
+
File.exist?(@checksum_file) ? YAML.load_file(@checksum_file) : []
|
64
|
+
end
|
65
|
+
|
66
|
+
def get_progresses
|
67
|
+
File.exist?(@progress_file) ? YAML.load_file(@progress_file) : []
|
68
|
+
end
|
69
|
+
|
70
|
+
def set_checksums(index, checksum)
|
71
|
+
checksums = get_checksums
|
72
|
+
checksums[index] = checksum
|
73
|
+
File.open(@checksum_file, "w") do |f|
|
74
|
+
YAML::dump(checksums, f)
|
75
|
+
Utils.debug %Q(Updating tmpfile: #{@checksum_file})
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def set_progresses(index, progress)
|
80
|
+
progresses = get_progresses
|
81
|
+
progresses[index] = progress
|
82
|
+
File.open(@progress_file, "w") do |f|
|
83
|
+
YAML::dump(progresses, f)
|
84
|
+
Utils.debug %Q(Updating tmpfile: #{@progress_file})
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def init_checksums(checksums)
|
89
|
+
File.open(@checksum_file, "w") do |f|
|
90
|
+
YAML::dump(checksums, f)
|
91
|
+
Utils.debug %Q(Initializing tmpfile: #{@checksum_file})
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def init_progresses(progresses)
|
96
|
+
File.open(@progress_file, "w") do |f|
|
97
|
+
YAML::dump(progresses, f)
|
98
|
+
Utils.debug %Q(Initializing tmpfile: #{@progress_file})
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def sweep!
|
103
|
+
FileUtils.rm_r(@tmpdir)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
class << self
|
109
|
+
include Utils
|
110
|
+
|
111
|
+
def upload_with_token(uptoken,
|
112
|
+
local_file,
|
113
|
+
bucket,
|
114
|
+
key = nil,
|
115
|
+
mime_type = nil,
|
116
|
+
custom_meta = nil,
|
117
|
+
customer = nil,
|
118
|
+
callback_params = nil)
|
119
|
+
raise NoSuchFileError, local_file unless File.exist?(local_file)
|
120
|
+
begin
|
121
|
+
ifile = File.open(local_file, 'rb')
|
122
|
+
fh = FileData.new(ifile)
|
123
|
+
fsize = fh.data_size
|
124
|
+
key = Digest::SHA1.hexdigest(local_file + fh.mtime.to_s) if key.nil?
|
125
|
+
if mime_type.nil? || mime_type.empty?
|
126
|
+
mime = MIME::Types.type_for local_file
|
127
|
+
mime_type = mime.empty? ? 'application/octet-stream' : mime[0].content_type
|
128
|
+
end
|
129
|
+
if fsize > Config.settings[:block_size]
|
130
|
+
code, data = _resumable_upload(uptoken, fh, fsize, bucket, key, mime_type, custom_meta, customer, callback_params)
|
131
|
+
else
|
132
|
+
code, data = IO.upload_with_token(uptoken, local_file, bucket, key, mime_type, custom_meta, callback_params, true)
|
133
|
+
end
|
134
|
+
[code, data]
|
135
|
+
ensure
|
136
|
+
ifile.close unless ifile.nil?
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
class FileData
|
143
|
+
attr_accessor :fh
|
144
|
+
def initialize(fh)
|
145
|
+
@fh = fh
|
146
|
+
end
|
147
|
+
def data_size
|
148
|
+
@fh.stat.size
|
149
|
+
end
|
150
|
+
def get_data(offset, length)
|
151
|
+
@fh.seek(offset)
|
152
|
+
@fh.read(length)
|
153
|
+
end
|
154
|
+
def path
|
155
|
+
@fh.path
|
156
|
+
end
|
157
|
+
def mtime
|
158
|
+
@fh.mtime
|
159
|
+
end
|
160
|
+
#delegate :path, :mtime, :to => :fh
|
161
|
+
end
|
162
|
+
|
163
|
+
def _new_block_put_progress_data
|
164
|
+
{:ctx => nil, :offset => 0, :restsize => nil, :status_code => nil}
|
165
|
+
end
|
166
|
+
|
167
|
+
def _call_binary_with_token(uptoken, url, data, retry_times = 0)
|
168
|
+
options = {
|
169
|
+
:method => :post,
|
170
|
+
:content_type => 'application/octet-stream',
|
171
|
+
:upload_signature_token => uptoken
|
172
|
+
}
|
173
|
+
code, data = http_request url, data, options
|
174
|
+
unless Utils.is_response_ok?(code)
|
175
|
+
retry_times += 1
|
176
|
+
if Config.settings[:auto_reconnect] && retry_times < Config.settings[:max_retry_times]
|
177
|
+
return _call_binary_with_token(uptoken, url, data, retry_times)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
[code, data]
|
181
|
+
end
|
182
|
+
|
183
|
+
def _mkblock(uptoken, block_size, body)
|
184
|
+
url = Config.settings[:up_host] + "/mkblk/#{block_size}"
|
185
|
+
_call_binary_with_token(uptoken, url, body)
|
186
|
+
end
|
187
|
+
|
188
|
+
def _putblock(uptoken, ctx, offset, body)
|
189
|
+
url = Config.settings[:up_host] + "/bput/#{ctx}/#{offset}"
|
190
|
+
_call_binary_with_token(uptoken, url, body)
|
191
|
+
end
|
192
|
+
|
193
|
+
def _resumable_put_block(uptoken, fh, block_index, block_size, chunk_size, progress, retry_times = 1, notifier)
|
194
|
+
code, data = 0, {}
|
195
|
+
fpath = fh.path
|
196
|
+
# this block has never been uploaded.
|
197
|
+
if progress[:ctx] == nil || progress[:ctx].empty?
|
198
|
+
progress[:offset] = 0
|
199
|
+
progress[:restsize] = block_size
|
200
|
+
# choose the smaller one
|
201
|
+
body_length = [block_size, chunk_size].min
|
202
|
+
for i in 1..retry_times
|
203
|
+
seek_pos = block_index*Config.settings[:block_size]
|
204
|
+
body = fh.get_data(seek_pos, body_length)
|
205
|
+
result_length = body.length
|
206
|
+
if result_length != body_length
|
207
|
+
raise FileSeekReadError.new(fpath, block_index, seek_pos, body_length, result_length)
|
208
|
+
end
|
209
|
+
code, data = _mkblock(uptoken, block_size, body)
|
210
|
+
body_crc32 = Zlib.crc32(body)
|
211
|
+
if Utils.is_response_ok?(code) && data["crc32"] == body_crc32
|
212
|
+
progress[:ctx] = data["ctx"]
|
213
|
+
progress[:offset] = body_length
|
214
|
+
progress[:restsize] = block_size - body_length
|
215
|
+
progress[:status_code] = code
|
216
|
+
if !notifier.nil? && notifier.respond_to?("notify")
|
217
|
+
notifier.notify(block_index, progress)
|
218
|
+
end
|
219
|
+
break
|
220
|
+
elsif i == retry_times && data["crc32"] != body_crc32
|
221
|
+
Log.logger.error %Q(Uploading block error. Expected crc32: #{body_crc32}, but got: #{data["crc32"]})
|
222
|
+
end
|
223
|
+
end
|
224
|
+
elsif progress[:offset] + progress[:restsize] != block_size
|
225
|
+
raise BlockSizeNotMathchError.new(fpath, block_index, progress[:offset], progress[:restsize], block_size)
|
226
|
+
end
|
227
|
+
# loop uploading other chunks except the first one
|
228
|
+
while progress[:restsize].to_i > 0 && progress[:restsize] < block_size
|
229
|
+
# choose the smaller one
|
230
|
+
body_length = [progress[:restsize], chunk_size].min
|
231
|
+
for i in 1..retry_times
|
232
|
+
seek_pos = block_index*Config.settings[:block_size] + progress[:offset]
|
233
|
+
body = fh.get_data(seek_pos, body_length)
|
234
|
+
result_length = body.length
|
235
|
+
if result_length != body_length
|
236
|
+
raise FileSeekReadError.new(fpath, block_index, seek_pos, body_length, result_length)
|
237
|
+
end
|
238
|
+
code, data = _putblock(uptoken, progress[:ctx], progress[:offset], body)
|
239
|
+
body_crc32 = Zlib.crc32(body)
|
240
|
+
if Utils.is_response_ok?(code) && data["crc32"] == body_crc32
|
241
|
+
progress[:ctx] = data["ctx"]
|
242
|
+
progress[:offset] += body_length
|
243
|
+
progress[:restsize] -= body_length
|
244
|
+
progress[:status_code] = code
|
245
|
+
if !notifier.nil? && notifier.respond_to?("notify")
|
246
|
+
notifier.notify(block_index, progress)
|
247
|
+
end
|
248
|
+
break
|
249
|
+
elsif i == retry_times && data["crc32"] != body_crc32
|
250
|
+
Log.logger.error %Q(Uploading block error. Expected crc32: #{body_crc32}, but got: #{data["crc32"]})
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
# return
|
255
|
+
return [code, data]
|
256
|
+
end
|
257
|
+
|
258
|
+
def _block_count(fsize)
|
259
|
+
((fsize + Config.settings[:block_size] - 1) / Config.settings[:block_size]).to_i
|
260
|
+
end
|
261
|
+
|
262
|
+
def _resumable_put(uptoken, fh, checksums, progresses, block_notifier = nil, chunk_notifier = nil)
|
263
|
+
code, data = 0, {}
|
264
|
+
fsize = fh.data_size
|
265
|
+
block_count = _block_count(fsize)
|
266
|
+
checksum_count = checksums.length
|
267
|
+
progress_count = progresses.length
|
268
|
+
if checksum_count != block_count || progress_count != block_count
|
269
|
+
raise BlockCountNotMathchError.new(fh.path, block_count, checksum_count, progress_count)
|
270
|
+
end
|
271
|
+
0.upto(block_count-1).each do |block_index|
|
272
|
+
if checksums[block_index].nil? || checksums[block_index].empty?
|
273
|
+
block_size = Config.settings[:block_size]
|
274
|
+
if block_index == block_count - 1
|
275
|
+
block_size = fsize - block_index*Config.settings[:block_size]
|
276
|
+
end
|
277
|
+
if progresses[block_index].nil?
|
278
|
+
progresses[block_index] = _new_block_put_progress_data
|
279
|
+
end
|
280
|
+
code, data = _resumable_put_block(uptoken, fh, block_index, block_size, Config.settings[:chunk_size], progresses[block_index], Config.settings[:max_retry_times], chunk_notifier)
|
281
|
+
if Utils.is_response_ok?(code)
|
282
|
+
checksums[block_index] = data["checksum"]
|
283
|
+
if !block_notifier.nil? && block_notifier.respond_to?("notify")
|
284
|
+
block_notifier.notify(block_index, checksums[block_index])
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
return [code, data]
|
290
|
+
end
|
291
|
+
|
292
|
+
def _mkfile(uptoken, entry_uri, fsize, checksums, mime_type = nil, custom_meta = nil, customer = nil, callback_params = nil)
|
293
|
+
path = '/rs-mkfile/' + Utils.urlsafe_base64_encode(entry_uri) + "/fsize/#{fsize}"
|
294
|
+
path += '/mimeType/' + Utils.urlsafe_base64_encode(mime_type) if !mime_type.nil? && !mime_type.empty?
|
295
|
+
path += '/meta/' + Utils.urlsafe_base64_encode(custom_meta) if !custom_meta.nil? && !custom_meta.empty?
|
296
|
+
path += '/customer/' + customer if !customer.nil? && !customer.empty?
|
297
|
+
callback_query_string = Utils.generate_query_string(callback_params) if !callback_params.nil? && !callback_params.empty?
|
298
|
+
path += '/params/' + Utils.urlsafe_base64_encode(callback_query_string) if !callback_query_string.nil? && !callback_query_string.empty?
|
299
|
+
url = Config.settings[:up_host] + path
|
300
|
+
body = ''
|
301
|
+
checksums.each do |checksum|
|
302
|
+
body += Utils.urlsafe_base64_decode(checksum)
|
303
|
+
end
|
304
|
+
_call_binary_with_token(uptoken, url, body)
|
305
|
+
end
|
306
|
+
|
307
|
+
def _resumable_upload(uptoken, fh, fsize, bucket, key, mime_type = nil, custom_meta = nil, customer = nil, callback_params = nil)
|
308
|
+
block_count = _block_count(fsize)
|
309
|
+
progress_data = ProgressData.new(key)
|
310
|
+
checksums = progress_data.get_checksums
|
311
|
+
progresses = progress_data.get_progresses
|
312
|
+
if checksums.empty?
|
313
|
+
block_count.times{checksums << ''}
|
314
|
+
progress_data.init_checksums(checksums)
|
315
|
+
end
|
316
|
+
if progresses.empty?
|
317
|
+
block_count.times{progresses << _new_block_put_progress_data}
|
318
|
+
progress_data.init_progresses(progresses)
|
319
|
+
end
|
320
|
+
chunk_notifier = ChunkProgressNotifier.new(key)
|
321
|
+
block_notifier = BlockProgressNotifier.new(key)
|
322
|
+
code, data = _resumable_put(uptoken, fh, checksums, progresses, block_notifier, chunk_notifier)
|
323
|
+
if Utils.is_response_ok?(code)
|
324
|
+
entry_uri = bucket + ':' + key
|
325
|
+
code, data = _mkfile(uptoken, entry_uri, fsize, checksums, mime_type, custom_meta, customer, callback_params)
|
326
|
+
end
|
327
|
+
if Utils.is_response_ok?(code)
|
328
|
+
Utils.debug "File #{fh.path} {size: #{fsize}} successfully uploaded."
|
329
|
+
progress_data.sweep!
|
330
|
+
end
|
331
|
+
[code, data]
|
332
|
+
end
|
333
|
+
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
data/lib/qiniu/rs/utils.rb
CHANGED
@@ -32,6 +32,20 @@ module Qiniu
|
|
32
32
|
{}
|
33
33
|
end
|
34
34
|
|
35
|
+
def is_response_ok?(status_code)
|
36
|
+
status_code/100 == 2
|
37
|
+
end
|
38
|
+
|
39
|
+
def response_error(status_code, errmsg)
|
40
|
+
[status_code, {"error" => errmsg}]
|
41
|
+
end
|
42
|
+
|
43
|
+
def debug(msg)
|
44
|
+
if Config.settings[:enable_debug]
|
45
|
+
Log.logger.debug(msg)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
35
49
|
def send_request_with url, data = nil, options = {}
|
36
50
|
options[:method] = Config.settings[:method] unless options[:method]
|
37
51
|
options[:content_type] = Config.settings[:content_type] unless options[:content_type]
|
@@ -42,8 +56,8 @@ module Qiniu
|
|
42
56
|
auth_token = nil
|
43
57
|
if !options[:qbox_signature_token].nil? && !options[:qbox_signature_token].empty?
|
44
58
|
auth_token = 'QBox ' + options[:qbox_signature_token]
|
45
|
-
|
46
|
-
|
59
|
+
elsif !options[:upload_signature_token].nil? && !options[:upload_signature_token].empty?
|
60
|
+
auth_token = 'UpToken ' + options[:upload_signature_token]
|
47
61
|
elsif options[:access_token]
|
48
62
|
auth_token = 'Bearer ' + options[:access_token]
|
49
63
|
end
|
@@ -56,8 +70,8 @@ module Qiniu
|
|
56
70
|
response = RestClient.post(url, data, header_options)
|
57
71
|
end
|
58
72
|
code = response.respond_to?(:code) ? response.code.to_i : 0
|
59
|
-
|
60
|
-
raise RequestFailed.new(response)
|
73
|
+
unless is_response_ok?(code)
|
74
|
+
raise RequestFailed.new("Request Failed", response)
|
61
75
|
else
|
62
76
|
data = {}
|
63
77
|
body = response.respond_to?(:body) ? response.body : {}
|
data/lib/qiniu/rs/version.rb
CHANGED
@@ -2,6 +2,18 @@
|
|
2
2
|
|
3
3
|
module Qiniu
|
4
4
|
module RS
|
5
|
-
|
5
|
+
module Version
|
6
|
+
MAJOR = 3
|
7
|
+
MINOR = 1
|
8
|
+
PATCH = 0
|
9
|
+
# Returns a version string by joining <tt>MAJOR</tt>, <tt>MINOR</tt>, and <tt>PATCH</tt> with <tt>'.'</tt>
|
10
|
+
#
|
11
|
+
# Example
|
12
|
+
#
|
13
|
+
# Version.to_s # '1.0.2'
|
14
|
+
def self.to_s
|
15
|
+
[MAJOR, MINOR, PATCH].join('.')
|
16
|
+
end
|
17
|
+
end
|
6
18
|
end
|
7
19
|
end
|
data/qiniu-rs.gemspec
CHANGED
@@ -14,7 +14,7 @@ Gem::Specification.new do |gem|
|
|
14
14
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
15
15
|
gem.name = "qiniu-rs"
|
16
16
|
gem.require_paths = ["lib"]
|
17
|
-
gem.version = Qiniu::RS::
|
17
|
+
gem.version = Qiniu::RS::Version.to_s
|
18
18
|
|
19
19
|
# specify any dependencies here; for example:
|
20
20
|
gem.add_development_dependency "rake", "~> 0.9"
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'qiniu/rs/abstract'
|
5
|
+
|
6
|
+
describe Qiniu::RS::Abstract do
|
7
|
+
before(:each) do
|
8
|
+
@klass = Class.new do
|
9
|
+
include Qiniu::RS::Abstract
|
10
|
+
|
11
|
+
abstract_methods :foo, :bar
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
it "raises NotImplementedError" do
|
16
|
+
proc {
|
17
|
+
@klass.new.foo
|
18
|
+
}.should raise_error(NotImplementedError)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "can be overridden" do
|
22
|
+
subclass = Class.new(@klass) do
|
23
|
+
def foo
|
24
|
+
:overridden
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
subclass.new.foo.should == :overridden
|
29
|
+
end
|
30
|
+
end
|
data/spec/qiniu/rs/rs_spec.rb
CHANGED
@@ -17,22 +17,22 @@ module Qiniu
|
|
17
17
|
@domain = 'iovip.qbox.me/test'
|
18
18
|
|
19
19
|
code, data = Qiniu::RS::RS.mkbucket(@bucket)
|
20
|
-
code.should == 200
|
21
20
|
puts data.inspect
|
21
|
+
code.should == 200
|
22
22
|
end
|
23
23
|
|
24
24
|
context "IO.upload_file" do
|
25
25
|
it "should works" do
|
26
26
|
code, data = Qiniu::RS::IO.put_auth()
|
27
|
+
puts data.inspect
|
27
28
|
code.should == 200
|
28
29
|
data["url"].should_not be_empty
|
29
30
|
data["expiresIn"].should_not be_zero
|
30
|
-
puts data.inspect
|
31
31
|
@put_url = data["url"]
|
32
32
|
|
33
33
|
code2, data2 = Qiniu::RS::IO.upload_file(@put_url, __FILE__, @bucket, @key)
|
34
|
-
code2.should == 200
|
35
34
|
puts data2.inspect
|
35
|
+
code2.should == 200
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# Utils.-*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'digest/sha1'
|
4
|
+
require 'spec_helper'
|
5
|
+
require 'qiniu/rs/rs'
|
6
|
+
require 'qiniu/rs/up'
|
7
|
+
|
8
|
+
module Qiniu
|
9
|
+
module RS
|
10
|
+
describe UP do
|
11
|
+
|
12
|
+
before :all do
|
13
|
+
@localfile = "bigfile.txt"
|
14
|
+
File.open(@localfile, "w"){|f| 9437184.times{f.write(Random.rand(9).to_s)}}
|
15
|
+
@bucket = "test"
|
16
|
+
@key = Digest::SHA1.hexdigest(@localfile+Time.now.to_s)
|
17
|
+
end
|
18
|
+
|
19
|
+
after :all do
|
20
|
+
File.unlink(@localfile) if File.exists?(@localfile)
|
21
|
+
end
|
22
|
+
|
23
|
+
context ".upload_with_token" do
|
24
|
+
it "should works" do
|
25
|
+
upopts = {:scope => @bucket, :expires_in => 3600, :customer => "awhy.xu@gmail.com"}
|
26
|
+
uptoken = Qiniu::RS.generate_upload_token(upopts)
|
27
|
+
code, data = Qiniu::RS::UP.upload_with_token(uptoken, @localfile, @bucket, @key)
|
28
|
+
puts data.inspect
|
29
|
+
(code/100).should == 2
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context ".stat" do
|
34
|
+
it "should exists" do
|
35
|
+
code, data = Qiniu::RS::RS.stat(@bucket, @key)
|
36
|
+
puts data.inspect
|
37
|
+
code.should == 200
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context ".delete" do
|
42
|
+
it "should works" do
|
43
|
+
code, data = Qiniu::RS::RS.delete(@bucket, @key)
|
44
|
+
puts data.inspect
|
45
|
+
code.should == 200
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/spec/qiniu/rs_spec.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
|
3
|
+
require 'digest/sha1'
|
3
4
|
require 'spec_helper'
|
4
5
|
require 'qiniu/rs'
|
5
6
|
|
@@ -129,6 +130,41 @@ module Qiniu
|
|
129
130
|
end
|
130
131
|
end
|
131
132
|
|
133
|
+
context ".resumable_upload_file" do
|
134
|
+
it "should works" do
|
135
|
+
# generate bigfile for testing
|
136
|
+
localfile = "test_bigfile"
|
137
|
+
File.open(localfile, "w"){|f| 5242888.times{f.write(Random.rand(9).to_s)}}
|
138
|
+
key = Digest::SHA1.hexdigest(localfile+Time.now.to_s)
|
139
|
+
# generate the upload token
|
140
|
+
uptoken_opts = {:scope => @bucket, :expires_in => 3600, :customer => "awhy.xu@gmail.com"}
|
141
|
+
uptoken = Qiniu::RS.generate_upload_token(uptoken_opts)
|
142
|
+
# uploading
|
143
|
+
upload_opts = {
|
144
|
+
:uptoken => uptoken,
|
145
|
+
:file => localfile,
|
146
|
+
:bucket => @bucket,
|
147
|
+
:key => key
|
148
|
+
}
|
149
|
+
#uploading
|
150
|
+
result1 = Qiniu::RS.upload_file(upload_opts)
|
151
|
+
#drop the bigfile
|
152
|
+
File.unlink(localfile) if File.exists?(localfile)
|
153
|
+
#expect
|
154
|
+
puts result1.inspect
|
155
|
+
result1.should_not be_false
|
156
|
+
result1.should_not be_empty
|
157
|
+
#stat
|
158
|
+
result2 = Qiniu::RS.stat(@bucket, key)
|
159
|
+
puts result2.inspect
|
160
|
+
result2.should_not be_false
|
161
|
+
#delete
|
162
|
+
result3 = Qiniu::RS.delete(@bucket, key)
|
163
|
+
puts result3.inspect
|
164
|
+
result3.should_not be_false
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
132
168
|
context ".stat" do
|
133
169
|
it "should works" do
|
134
170
|
result = Qiniu::RS.stat(@bucket, @key)
|
data/spec/spec_helper.rb
CHANGED
@@ -14,5 +14,10 @@ RSpec.configure do |config|
|
|
14
14
|
:up_host => "http://m1.qbox.me:13019",
|
15
15
|
:pub_host => "http://m1.qbox.me:13012",
|
16
16
|
:eu_host => "http://m1.qbox.me:13050"
|
17
|
+
|
18
|
+
=begin
|
19
|
+
Qiniu::RS.establish_connection! :access_key => "aPoWOtE9EFca1fLxFCtlkeZAOV7aADVMTLdSydmr",
|
20
|
+
:secret_key => "L3ShtjCQTCagVCDPfHJoOix7JO_o3qHz3ScyflUG"
|
21
|
+
=end
|
17
22
|
end
|
18
23
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: qiniu-rs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0
|
4
|
+
version: 3.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-10-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -141,6 +141,7 @@ files:
|
|
141
141
|
- docs/README.md
|
142
142
|
- lib/qiniu-rs.rb
|
143
143
|
- lib/qiniu/rs.rb
|
144
|
+
- lib/qiniu/rs/abstract.rb
|
144
145
|
- lib/qiniu/rs/auth.rb
|
145
146
|
- lib/qiniu/rs/config.rb
|
146
147
|
- lib/qiniu/rs/eu.rb
|
@@ -150,12 +151,14 @@ files:
|
|
150
151
|
- lib/qiniu/rs/log.rb
|
151
152
|
- lib/qiniu/rs/pub.rb
|
152
153
|
- lib/qiniu/rs/rs.rb
|
154
|
+
- lib/qiniu/rs/up.rb
|
153
155
|
- lib/qiniu/rs/utils.rb
|
154
156
|
- lib/qiniu/rs/version.rb
|
155
157
|
- lib/qiniu/tokens/access_token.rb
|
156
158
|
- lib/qiniu/tokens/qbox_token.rb
|
157
159
|
- lib/qiniu/tokens/upload_token.rb
|
158
160
|
- qiniu-rs.gemspec
|
161
|
+
- spec/qiniu/rs/abstract_spec.rb
|
159
162
|
- spec/qiniu/rs/auth_spec.rb
|
160
163
|
- spec/qiniu/rs/eu_spec.rb
|
161
164
|
- spec/qiniu/rs/image_logo_for_test.png
|
@@ -163,6 +166,7 @@ files:
|
|
163
166
|
- spec/qiniu/rs/io_spec.rb
|
164
167
|
- spec/qiniu/rs/pub_spec.rb
|
165
168
|
- spec/qiniu/rs/rs_spec.rb
|
169
|
+
- spec/qiniu/rs/up_spec.rb
|
166
170
|
- spec/qiniu/rs/utils_spec.rb
|
167
171
|
- spec/qiniu/rs/version_spec.rb
|
168
172
|
- spec/qiniu/rs_spec.rb
|
@@ -181,7 +185,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
181
185
|
version: '0'
|
182
186
|
segments:
|
183
187
|
- 0
|
184
|
-
hash:
|
188
|
+
hash: -1624363921159069089
|
185
189
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
186
190
|
none: false
|
187
191
|
requirements:
|
@@ -190,7 +194,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
190
194
|
version: '0'
|
191
195
|
segments:
|
192
196
|
- 0
|
193
|
-
hash:
|
197
|
+
hash: -1624363921159069089
|
194
198
|
requirements: []
|
195
199
|
rubyforge_project:
|
196
200
|
rubygems_version: 1.8.24
|
@@ -198,6 +202,7 @@ signing_key:
|
|
198
202
|
specification_version: 3
|
199
203
|
summary: Qiniu Resource (Cloud) Storage SDK for Ruby
|
200
204
|
test_files:
|
205
|
+
- spec/qiniu/rs/abstract_spec.rb
|
201
206
|
- spec/qiniu/rs/auth_spec.rb
|
202
207
|
- spec/qiniu/rs/eu_spec.rb
|
203
208
|
- spec/qiniu/rs/image_logo_for_test.png
|
@@ -205,6 +210,7 @@ test_files:
|
|
205
210
|
- spec/qiniu/rs/io_spec.rb
|
206
211
|
- spec/qiniu/rs/pub_spec.rb
|
207
212
|
- spec/qiniu/rs/rs_spec.rb
|
213
|
+
- spec/qiniu/rs/up_spec.rb
|
208
214
|
- spec/qiniu/rs/utils_spec.rb
|
209
215
|
- spec/qiniu/rs/version_spec.rb
|
210
216
|
- spec/qiniu/rs_spec.rb
|