qiniu_jxb 6.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/.gitignore +18 -0
  2. data/.rspec +1 -0
  3. data/.travis.yml +9 -0
  4. data/CHANGELOG.md +118 -0
  5. data/Gemfile +8 -0
  6. data/Gemfile.lock +37 -0
  7. data/LICENSE +22 -0
  8. data/README.md +47 -0
  9. data/Rakefile +21 -0
  10. data/docs/README.md +790 -0
  11. data/lib/qiniu-rs.rb +2 -0
  12. data/lib/qiniu.rb +209 -0
  13. data/lib/qiniu/abstract.rb +22 -0
  14. data/lib/qiniu/adt.rb +46 -0
  15. data/lib/qiniu/auth.rb +234 -0
  16. data/lib/qiniu/config.rb +58 -0
  17. data/lib/qiniu/exceptions.rb +120 -0
  18. data/lib/qiniu/fop.rb +4 -0
  19. data/lib/qiniu/http.rb +137 -0
  20. data/lib/qiniu/image.rb +38 -0
  21. data/lib/qiniu/log.rb +15 -0
  22. data/lib/qiniu/management.rb +128 -0
  23. data/lib/qiniu/misc.rb +33 -0
  24. data/lib/qiniu/pfop.rb +124 -0
  25. data/lib/qiniu/resumable_upload.rb +319 -0
  26. data/lib/qiniu/storage.rb +5 -0
  27. data/lib/qiniu/tokens/access_token.rb +21 -0
  28. data/lib/qiniu/tokens/download_token.rb +31 -0
  29. data/lib/qiniu/tokens/qbox_token.rb +38 -0
  30. data/lib/qiniu/tokens/upload_token.rb +47 -0
  31. data/lib/qiniu/upload.rb +138 -0
  32. data/lib/qiniu/utils.rb +109 -0
  33. data/lib/qiniu/version.rb +17 -0
  34. data/qiniu.gemspec +29 -0
  35. data/spec/qiniu/abstract_spec.rb +30 -0
  36. data/spec/qiniu/auth_spec.rb +81 -0
  37. data/spec/qiniu/image_logo_for_test.png +0 -0
  38. data/spec/qiniu/image_spec.rb +89 -0
  39. data/spec/qiniu/management_spec.rb +156 -0
  40. data/spec/qiniu/misc_spec.rb +59 -0
  41. data/spec/qiniu/pfop_spec.rb +89 -0
  42. data/spec/qiniu/qiniu_spec.rb +329 -0
  43. data/spec/qiniu/tokens/qbox_token_spec.rb +29 -0
  44. data/spec/qiniu/upload_spec.rb +308 -0
  45. data/spec/qiniu/utils_spec.rb +49 -0
  46. data/spec/qiniu/version_spec.rb +10 -0
  47. data/spec/spec_helper.rb +19 -0
  48. metadata +220 -0
data/lib/qiniu/misc.rb ADDED
@@ -0,0 +1,33 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module Qiniu
4
+ module Misc
5
+ class << self
6
+ def set_protected(bucket, protected_mode)
7
+ url = Config.settings[:pub_host] + %Q(/accessMode/#{bucket}/mode/#{protected_mode})
8
+ return HTTP.management_post(url)
9
+ end # set_protected
10
+
11
+ def set_separator(bucket, separator)
12
+ encoded_separator = Utils.urlsafe_base64_encode(separator)
13
+ url = Config.settings[:pub_host] + %Q(/separator/#{bucket}/sep/#{encoded_separator})
14
+ return HTTP.management_post(url)
15
+ end # set_separator
16
+
17
+ def set_style(bucket, name, style)
18
+ encoded_name = Utils.urlsafe_base64_encode(name)
19
+ encoded_style = Utils.urlsafe_base64_encode(style)
20
+ url = Config.settings[:pub_host] + %Q(/style/#{bucket}/name/#{encoded_name}/style/#{encoded_style})
21
+ return HTTP.management_post(url)
22
+ end # set_style
23
+
24
+ def unset_style(bucket, name)
25
+ encoded_name = Utils.urlsafe_base64_encode(name)
26
+ url = Config.settings[:pub_host] + %Q(/unstyle/#{bucket}/name/#{encoded_name})
27
+ return HTTP.management_post(url)
28
+ end # unset_style
29
+ end # class << self
30
+
31
+ end # module Misc
32
+ end # module Qiniu
33
+
data/lib/qiniu/pfop.rb ADDED
@@ -0,0 +1,124 @@
1
+ # -*- encoding: utf-8 -*-
2
+ # vim: sw=2 ts=2
3
+
4
+ require 'qiniu/adt'
5
+ require 'qiniu/http'
6
+
7
+ module Qiniu
8
+ module Fop
9
+ module Persistance
10
+
11
+ class PfopPolicy
12
+ include ADT::Policy
13
+
14
+ private
15
+ def initialize(bucket,
16
+ key,
17
+ fops,
18
+ notify_url)
19
+ @bucket = bucket
20
+ @key = key
21
+ @notify_url = notify_url
22
+
23
+ self.fops!(fops)
24
+ end # initialize
25
+
26
+ public
27
+ PARAMS = {
28
+ # 字符串类型参数
29
+ :bucket => "bucket",
30
+ :key => "key",
31
+ :fops => "fops",
32
+ :notify_url => "notifyURL",
33
+ :pipeline => "pipeline",
34
+
35
+ # 数值类型参数
36
+ :force => "force"
37
+ } # PARAMS
38
+
39
+ PARAMS.each_pair do |key, fld|
40
+ attr_accessor key
41
+ end
42
+
43
+ def params
44
+ return PARAMS
45
+ end # params
46
+
47
+ def fops! (fops)
48
+ if fops.is_a?(Hash) then
49
+ fops = fops.values
50
+ end
51
+
52
+ if fops.is_a?(Array) then
53
+ new_fops = []
54
+ fops.each do |v|
55
+ if v.is_a?(ApiSpecification) then
56
+ new_fops.push(v.to_s)
57
+ end
58
+ end
59
+
60
+ @fops = new_fops.join(";")
61
+ else
62
+ @fops = fops.to_s
63
+ end
64
+ end # fops!
65
+
66
+ def force!
67
+ @force = 1
68
+ end # force!
69
+
70
+ alias :to_s :to_json
71
+ end # class PfopPolicy
72
+
73
+ class << self
74
+
75
+ API_HOST = 'http://api.qiniu.com'
76
+
77
+ PFOP_URL = API_HOST + '/pfop/'
78
+
79
+ def pfop (args)
80
+ ### 生成fop指令串
81
+ if args.is_a?(PfopPolicy) then
82
+ # PfopPolicy的各个字段按固定顺序组织
83
+ body = args.to_query_string()
84
+ elsif args.is_a?(Hash) then
85
+ # 无法保证固定字段顺序
86
+ body = HTTP.generate_query_string(args)
87
+ else
88
+ # 由调用者保证固定字段顺序
89
+ body = args.to_s
90
+ end
91
+
92
+ ### 发送请求
93
+ return HTTP.management_post(PFOP_URL, body)
94
+ end # pfop
95
+
96
+ PREFOP_URL = API_HOST + '/status/get/prefop?id='
97
+
98
+ def prefop (persistent_id)
99
+ ### 抽取persistentId
100
+ if persistent_id.is_a?(Hash) then
101
+ pid = persistent_id['persistentId']
102
+ else
103
+ pid = persistent_id.to_s
104
+ end
105
+
106
+ ### 发送请求
107
+ url = PREFOP_URL + pid
108
+ return HTTP.api_get(url)
109
+ end # prefop
110
+
111
+ def generate_p1_url (url, fop)
112
+ # 如果fop是ApiSpecification,则各字段按固定顺序组织,保证一致性
113
+ # 否则由调用者保证固定字段顺序
114
+ fop = CGI.escape(fop.to_s).gsub('+', '%20')
115
+
116
+ ### 生成url
117
+ return url + '?p/1/' + fop
118
+ end # generate_pl_url
119
+
120
+ end # class << self
121
+
122
+ end # module Persistance
123
+ end # module Fop
124
+ end # module Qiniu
@@ -0,0 +1,319 @@
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/abstract'
10
+ require 'qiniu/exceptions'
11
+
12
+ module Qiniu
13
+ module Storage
14
+
15
+ module AbstractClass
16
+ class ChunkProgressNotifier
17
+ include Qiniu::Abstract
18
+ abstract_methods :notify
19
+ # def notify(block_index, block_put_progress); end
20
+ end
21
+
22
+ class BlockProgressNotifier
23
+ include Qiniu::Abstract
24
+ abstract_methods :notify
25
+ # def notify(block_index, checksum); end
26
+ end
27
+ end # module AbstractClass
28
+
29
+ class ChunkProgressNotifier < AbstractClass::ChunkProgressNotifier
30
+ def notify(index, progress)
31
+ logmsg = "chunk #{progress[:offset]/Config.settings[:chunk_size]} in block #{index} successfully uploaded.\n" + progress.to_s
32
+ Utils.debug(logmsg)
33
+ end
34
+ end # class ChunkProgressNotifier
35
+
36
+ class BlockProgressNotifier < AbstractClass::BlockProgressNotifier
37
+ def notify(index, checksum)
38
+ Utils.debug "block #{index}: {ctx: #{checksum}} successfully uploaded."
39
+ Utils.debug "block #{index}: {checksum: #{checksum}} successfully uploaded."
40
+ end
41
+ end # class BlockProgressNotifier
42
+
43
+ class << self
44
+ include Utils
45
+
46
+ def resumable_upload_with_token(uptoken,
47
+ local_file,
48
+ bucket,
49
+ key = nil,
50
+ mime_type = nil,
51
+ custom_meta = nil,
52
+ customer = nil,
53
+ callback_params = nil,
54
+ rotate = nil)
55
+ begin
56
+ ifile = File.open(local_file, 'rb')
57
+ fh = FileData.new(ifile)
58
+ fsize = fh.data_size
59
+ key = Digest::SHA1.hexdigest(local_file + fh.mtime.to_s) if key.nil?
60
+ if mime_type.nil? || mime_type.empty?
61
+ mime = MIME::Types.type_for local_file
62
+ mime_type = mime.empty? ? 'application/octet-stream' : mime[0].content_type
63
+ end
64
+ code, data = _resumable_upload(uptoken, fh, fsize, bucket, key, mime_type, custom_meta, customer, callback_params, rotate)
65
+ [code, data]
66
+ ensure
67
+ ifile.close unless ifile.nil?
68
+ end
69
+ end # resumable_upload_with_token
70
+
71
+ private
72
+
73
+ class FileData
74
+ attr_accessor :fh
75
+ def initialize(fh)
76
+ @fh = fh
77
+ end
78
+ def data_size
79
+ @fh.stat.size
80
+ end
81
+ def get_data(offset, length)
82
+ @fh.seek(offset)
83
+ @fh.read(length)
84
+ end
85
+ def path
86
+ @fh.path
87
+ end
88
+ def mtime
89
+ @fh.mtime
90
+ end
91
+ #delegate :path, :mtime, :to => :fh
92
+ end # class FileData
93
+
94
+ def _new_block_put_progress_data
95
+ {:ctx => nil, :offset => 0, :restsize => nil, :status_code => nil, :host => nil}
96
+ end # _new_block_put_progress_data
97
+
98
+ def _call_binary_with_token(uptoken, url, data, content_type = nil, retry_times = 0)
99
+ options = {
100
+ :headers => {
101
+ :content_type => 'application/octet-stream',
102
+ 'Authorization' => 'UpToken ' + uptoken
103
+ }
104
+ }
105
+ if !content_type.nil? && !content_type.empty? then
106
+ options[:headers][:content_type] = content_type
107
+ end
108
+
109
+ code, data, raw_headers = HTTP.api_post(url, data, options)
110
+ unless HTTP.is_response_ok?(code)
111
+ retry_times += 1
112
+ if Config.settings[:auto_reconnect] && retry_times < Config.settings[:max_retry_times]
113
+ return _call_binary_with_token(uptoken, url, data, options[:content_type], retry_times)
114
+ end
115
+ end
116
+ return code, data, raw_headers
117
+ end # _call_binary_with_token
118
+
119
+ def _mkblock(uptoken, block_size, body)
120
+ url = Config.settings[:up_host] + "/mkblk/#{block_size}"
121
+ _call_binary_with_token(uptoken, url, body)
122
+ end # _mkblock
123
+
124
+ def _putblock(uphost, uptoken, ctx, offset, body)
125
+ url = uphost + "/bput/#{ctx}/#{offset}"
126
+ _call_binary_with_token(uptoken, url, body)
127
+ end # _putblock
128
+
129
+ def _resumable_put_block(uptoken,
130
+ fh,
131
+ block_index,
132
+ block_size,
133
+ chunk_size,
134
+ progress,
135
+ retry_times,
136
+ notifier)
137
+ code, data = 0, {}
138
+ fpath = fh.path
139
+
140
+ # this block has never been uploaded.
141
+ if progress[:ctx] == nil || progress[:ctx].empty?
142
+ progress[:offset] = 0
143
+ progress[:restsize] = block_size
144
+ # choose the smaller one
145
+ body_length = [block_size, chunk_size].min
146
+ for i in 1..retry_times
147
+ seek_pos = block_index*Config.settings[:block_size]
148
+ body = fh.get_data(seek_pos, body_length)
149
+ result_length = body.length
150
+ if result_length != body_length
151
+ raise FileSeekReadError.new(fpath, block_index, seek_pos, body_length, result_length)
152
+ end
153
+
154
+ code, data, raw_headers = _mkblock(uptoken, block_size, body)
155
+ Utils.debug "Mkblk : #{code.inspect} #{data.inspect} #{raw_headers.inspect}"
156
+
157
+ body_crc32 = Zlib.crc32(body)
158
+ if HTTP.is_response_ok?(code) && data["crc32"] == body_crc32
159
+ progress[:ctx] = data["ctx"]
160
+ progress[:offset] = body_length
161
+ progress[:restsize] = block_size - body_length
162
+ progress[:status_code] = code
163
+ progress[:host] = data["host"]
164
+ if !notifier.nil? && notifier.respond_to?("notify")
165
+ notifier.notify(block_index, progress)
166
+ end
167
+ break
168
+ elsif i == retry_times && data["crc32"] != body_crc32
169
+ Log.logger.error %Q(Uploading block error. Expected crc32: #{body_crc32}, but got: #{data["crc32"]})
170
+ return code, data, raw_headers
171
+ end
172
+ end
173
+ elsif progress[:offset] + progress[:restsize] != block_size
174
+ raise BlockSizeNotMathchError.new(fpath, block_index, progress[:offset], progress[:restsize], block_size)
175
+ end
176
+
177
+ # loop uploading other chunks except the first one
178
+ while progress[:restsize].to_i > 0 && progress[:restsize] < block_size
179
+ # choose the smaller one
180
+ body_length = [progress[:restsize], chunk_size].min
181
+ for i in 1..retry_times
182
+ seek_pos = block_index*Config.settings[:block_size] + progress[:offset]
183
+ body = fh.get_data(seek_pos, body_length)
184
+ result_length = body.length
185
+ if result_length != body_length
186
+ raise FileSeekReadError.new(fpath, block_index, seek_pos, body_length, result_length)
187
+ end
188
+
189
+ code, data, raw_headers = _putblock(progress[:host], uptoken, progress[:ctx], progress[:offset], body)
190
+ Utils.debug "Bput : #{code.inspect} #{data.inspect} #{raw_headers.inspect}"
191
+
192
+ body_crc32 = Zlib.crc32(body)
193
+ if HTTP.is_response_ok?(code) && data["crc32"] == body_crc32
194
+ progress[:ctx] = data["ctx"]
195
+ progress[:offset] += body_length
196
+ progress[:restsize] -= body_length
197
+ progress[:status_code] = code
198
+ progress[:host] = data["host"]
199
+ if !notifier.nil? && notifier.respond_to?("notify")
200
+ notifier.notify(block_index, progress)
201
+ end
202
+ break
203
+ elsif i == retry_times && data["crc32"] != body_crc32
204
+ Log.logger.error %Q(Uploading block error. Expected crc32: #{body_crc32}, but got: #{data["crc32"]})
205
+ return code, data, raw_headers
206
+ end
207
+ end
208
+ end
209
+ # return
210
+ return code, data, raw_headers
211
+ end # _resumable_put_block
212
+
213
+ def _block_count(fsize)
214
+ ((fsize + Config.settings[:block_size] - 1) / Config.settings[:block_size]).to_i
215
+ end # _block_count
216
+
217
+ def _resumable_put(uptoken,
218
+ fh,
219
+ checksums,
220
+ progresses,
221
+ block_notifier = nil,
222
+ chunk_notifier = nil)
223
+ code, data = 0, {}
224
+ fsize = fh.data_size
225
+ block_count = _block_count(fsize)
226
+ checksum_count = checksums.length
227
+ progress_count = progresses.length
228
+ if checksum_count != block_count || progress_count != block_count
229
+ raise BlockCountNotMathchError.new(fh.path, block_count, checksum_count, progress_count)
230
+ end
231
+ 0.upto(block_count-1).each do |block_index|
232
+ if checksums[block_index].nil? || checksums[block_index].empty?
233
+ block_size = Config.settings[:block_size]
234
+ if block_index == block_count - 1
235
+ block_size = fsize - block_index*Config.settings[:block_size]
236
+ end
237
+ if progresses[block_index].nil?
238
+ progresses[block_index] = _new_block_put_progress_data
239
+ end
240
+ #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)
241
+ # Put the whole block as a chunk
242
+ code, data = _resumable_put_block(uptoken, fh, block_index, block_size, block_size, progresses[block_index], Config.settings[:max_retry_times], chunk_notifier)
243
+ if HTTP.is_response_ok?(code)
244
+ #checksums[block_index] = data["checksum"]
245
+ checksums[block_index] = data["ctx"]
246
+ if !block_notifier.nil? && block_notifier.respond_to?("notify")
247
+ block_notifier.notify(block_index, checksums[block_index])
248
+ end
249
+ end
250
+ end
251
+ end
252
+ return [code, data]
253
+ end # _resumable_put
254
+
255
+ def _mkfile(uphost,
256
+ uptoken,
257
+ entry_uri,
258
+ fsize,
259
+ checksums,
260
+ mime_type = nil,
261
+ custom_meta = nil,
262
+ customer = nil,
263
+ callback_params = nil,
264
+ rotate = nil)
265
+ path = '/rs-mkfile/' + Utils.urlsafe_base64_encode(entry_uri) + "/fsize/#{fsize}"
266
+ path += '/mimeType/' + Utils.urlsafe_base64_encode(mime_type) if !mime_type.nil? && !mime_type.empty?
267
+ path += '/meta/' + Utils.urlsafe_base64_encode(custom_meta) if !custom_meta.nil? && !custom_meta.empty?
268
+ path += '/customer/' + customer if !customer.nil? && !customer.empty?
269
+ callback_query_string = HTTP.generate_query_string(callback_params) if !callback_params.nil? && !callback_params.empty?
270
+ path += '/params/' + Utils.urlsafe_base64_encode(callback_query_string) if !callback_query_string.nil? && !callback_query_string.empty?
271
+ path += '/rotate/' + rotate if !rotate.nil? && rotate.to_i >= 0
272
+ url = uphost + path
273
+ #body = ''
274
+ #checksums.each do |checksum|
275
+ # body += Utils.urlsafe_base64_decode(checksum)
276
+ #end
277
+ body = checksums.join(',')
278
+ _call_binary_with_token(uptoken, url, body, 'text/plain')
279
+ end # _mkfile
280
+
281
+ def _resumable_upload(uptoken,
282
+ fh,
283
+ fsize,
284
+ bucket,
285
+ key,
286
+ mime_type = nil,
287
+ custom_meta = nil,
288
+ customer = nil,
289
+ callback_params = nil,
290
+ rotate = nil)
291
+
292
+ block_count = _block_count(fsize)
293
+
294
+ chunk_notifier = ChunkProgressNotifier.new()
295
+ block_notifier = BlockProgressNotifier.new()
296
+
297
+ progresses = []
298
+ block_count.times{progresses << _new_block_put_progress_data}
299
+ checksums = []
300
+ block_count.times{checksums << ''}
301
+
302
+ code, data, raw_headers = _resumable_put(uptoken, fh, checksums, progresses, block_notifier, chunk_notifier)
303
+
304
+ if HTTP.is_response_ok?(code)
305
+ uphost = data["host"]
306
+ entry_uri = bucket + ':' + key
307
+ code, data, raw_headers = _mkfile(uphost, uptoken, entry_uri, fsize, checksums, mime_type, custom_meta, customer, callback_params, rotate)
308
+ Utils.debug "Mkfile : #{code.inspect} #{data.inspect} #{raw_headers.inspect}"
309
+ end
310
+
311
+ if HTTP.is_response_ok?(code)
312
+ Utils.debug "File #{fh.path} {size: #{fsize}} successfully uploaded."
313
+ end
314
+
315
+ return code, data, raw_headers
316
+ end # _resumable_upload
317
+ end # self class
318
+ end # module Storage
319
+ end # module Qiniu