qiniu-rs 3.0.6 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- qiniu-rs (3.0.6)
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.2)
27
+ rspec-mocks (2.11.3)
28
28
  ruby-hmac (0.4.0)
29
29
 
30
30
  PLATFORMS
@@ -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,否则返回 `false`:
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 场景处理
@@ -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
- code, data = IO.upload_with_token(opts[:uptoken],
103
- opts[:file],
104
- opts[:bucket],
105
- opts[:key],
106
- opts[:mime_type],
107
- opts[:note],
108
- opts[:callback_params],
109
- opts[:enable_crc32_check])
110
- code == StatusOK ? data : false
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
@@ -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 == 200
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 == 200
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 == 200
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)
@@ -8,7 +8,7 @@
8
8
  # Qbox::Config.load "path/to/your_project/config/qiniu.yml"
9
9
  #
10
10
 
11
- require "qiniu/rs/version"
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-' + VERSION + '()',
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:10100",
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 => 5
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 = [:client_id, :client_secret, :auth_url, :rs_host, :io_host]
40
+ REQUIRED_OPTION_KEYS = [:access_key, :secret_key]
37
41
 
38
42
  attr_reader :settings, :default_params
39
43
 
@@ -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 ')
@@ -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
@@ -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
- #elsif !options[:upload_signature_token].nil? && !options[:upload_signature_token].empty?
46
- # auth_token = 'UpToken ' + options[:upload_signature_token]
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
- if code != 200
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 : {}
@@ -2,6 +2,18 @@
2
2
 
3
3
  module Qiniu
4
4
  module RS
5
- VERSION = "3.0.6"
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
@@ -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::VERSION
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
@@ -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
@@ -3,8 +3,8 @@
3
3
  require 'spec_helper'
4
4
  require 'qiniu/rs/version'
5
5
 
6
- describe Qiniu::RS do
6
+ describe Qiniu::RS::Version do
7
7
  it "should has a VERSION" do
8
- Qiniu::RS::VERSION.should =~ /^\d+\.\d+\.\d+?$/
8
+ Qiniu::RS::Version.to_s.should =~ /^\d+\.\d+\.\d+?$/
9
9
  end
10
10
  end
@@ -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)
@@ -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.6
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-09-12 00:00:00.000000000 Z
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: 3420259766085414451
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: 3420259766085414451
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