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.
@@ -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