qiniu-rs 1.0.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/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ *.swp
4
+ .DS_Store
5
+ .bundle
6
+ .config
7
+ .yardoc
8
+ Gemfile.lock
9
+ InstalledFiles
10
+ _yardoc
11
+ coverage
12
+ doc/
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour -f nested
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in qiniu-s3.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 0.9"
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 why404
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # 关于
2
+
3
+ 此 Ruby SDK 适用于 Ruby 1.8.x, 1.9.x 版本,基于 [七牛云存储官方API](http://docs.qiniutek.com/v1/api/) 构建。使用此 SDK 构建您的网络应用程序,能让您以非常便捷地方式将数据安全地存储到七牛云存储上。无论您的网络应用是一个网站程序,还是包括从云端(服务端程序)到终端(手持设备应用)的架构的服务或应用,通过七牛云存储及其 SDK,都能让您应用程序的终端用户高速上传和下载,同时也让您的服务端更加轻盈。
4
+
5
+ ## 安装
6
+
7
+ 在您 Ruby 应用程序的 `Gemfile` 文件中,添加如下一行代码:
8
+
9
+ gem 'qiniu-rs'
10
+
11
+ 然后,在应用程序所在的目录下,可以运行 `bundle` 安装依赖包:
12
+
13
+ $ bundle
14
+
15
+ 或者,可以使用 Ruby 的包管理器 `gem` 进行安装:
16
+
17
+ $ gem install qiniu-rs
18
+
19
+ ## 使用
20
+
21
+ 参考文档:[七牛云存储 Ruby SDK 使用指南](http://docs.qiniutek.com/v1/sdk/ruby/)
22
+
23
+ ## 贡献代码
24
+
25
+ 1. Fork
26
+ 2. 创建您的特性分支 (`git checkout -b my-new-feature`)
27
+ 3. 提交您的改动 (`git commit -am 'Added some feature'`)
28
+ 4. 将您的修改记录提交到远程 `git` 仓库 (`git push origin my-new-feature`)
29
+ 5. 然后到 github 网站的该 `git` 远程仓库的 `my-new-feature` 分支下发起 Pull Request
30
+
31
+ ## 许可证
32
+
33
+ Copyright (c) 2012 why404
34
+
35
+ 基于 MIT 协议发布:
36
+
37
+ * [www.opensource.org/licenses/MIT](http://www.opensource.org/licenses/MIT)
38
+
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,74 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'qiniu/rs/exceptions'
4
+
5
+ module Qiniu
6
+ module RS
7
+ module Auth
8
+ class << self
9
+ include Utils
10
+
11
+ def exchange_by_password!(username, password)
12
+ @username = username
13
+ @password = password
14
+ post_data = {
15
+ :client_id => Config.settings[:client_id],
16
+ :grant_type => "password",
17
+ :username => username,
18
+ :password => password
19
+ }
20
+ code, data = http_request Config.settings[:auth_url], post_data
21
+ reset_token(data["access_token"], data["refresh_token"]) if code == 200
22
+ [code, data]
23
+ end
24
+
25
+ def exchange_by_refresh_token!(refresh_token)
26
+ post_data = {
27
+ :client_id => Config.settings[:client_id],
28
+ :grant_type => "refresh_token",
29
+ :refresh_token => refresh_token
30
+ }
31
+ code, data = http_request Config.settings[:auth_url], post_data
32
+ reset_token(data["access_token"], data["refresh_token"]) if code == 200
33
+ [code, data]
34
+ end
35
+
36
+ def reset_token(access_token, refresh_token)
37
+ @access_token = access_token
38
+ @refresh_token = refresh_token
39
+ end
40
+
41
+ def call(url, data, retry_times = 0)
42
+ raise MissingAccessToken if @access_token.nil?
43
+ code, data = http_request url, data, {:access_token => @access_token}
44
+ if code == 401
45
+ raise MissingRefreshToken if @refresh_token.nil?
46
+ code, data = exchange_by_refresh_token!(@refresh_token)
47
+ if code == 401
48
+ raise MissingUsernameOrPassword if (@username.nil? || @password.nil?)
49
+ code, data = exchange_by_password!(@username, @password)
50
+ end
51
+ if code == 200
52
+ retry_times += 1
53
+ if Config.settings[:auto_reconnect] && retry_times < Config.settings[:max_retry_times]
54
+ return call(url, data, retry_times)
55
+ end
56
+ end
57
+ end
58
+ [code, data]
59
+ end
60
+
61
+ def request(url, data = nil)
62
+ begin
63
+ code, data = Auth.call(url, data)
64
+ rescue [MissingAccessToken, MissingRefreshToken, MissingUsernameOrPassword] => e
65
+ Log.logger.error e
66
+ code, data = 401, {}
67
+ end
68
+ [code, data]
69
+ end
70
+
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,54 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # USAGE WAY 1:
4
+ # Qbox::Config.initialize_connect :client_id => "<ClientID>",
5
+ # :client_secret => "<ClientSecret>"
6
+ #
7
+ # USAGE WAY 2:
8
+ # Qbox::Config.load "path/to/your_project/config/qiniu.yml"
9
+ #
10
+
11
+ require "qiniu/rs/version"
12
+
13
+ module Qiniu
14
+ module RS
15
+ module Config
16
+ class << self
17
+
18
+ DEFAULT_OPTIONS = {
19
+ :user_agent => 'Qiniu-RS-Ruby-SDK-' + VERSION + '()',
20
+ :method => :post,
21
+ :content_type => 'application/x-www-form-urlencoded',
22
+ :auth_url => "https://acc.qbox.me/oauth2/token",
23
+ :rs_host => "http://rs.qbox.me:10100",
24
+ :io_host => "http://io.qbox.me",
25
+ :client_id => "<YOUR_APP_CLIENT_ID>",
26
+ :client_secret => "<YOUR_APP_CLIENT_SECRET>",
27
+ :auto_reconnect => true,
28
+ :max_retry_times => 5
29
+ }
30
+
31
+ REQUIRED_OPTION_KEYS = [:client_id, :client_secret, :auth_url, :rs_host, :io_host]
32
+
33
+ attr_reader :settings, :default_params
34
+
35
+ def load config_file
36
+ if File.exist?(config_file)
37
+ config_options = YAML.load_file(config_file)
38
+ initialize_connect(config_options)
39
+ else
40
+ raise MissingConfError, config_file
41
+ end
42
+ end
43
+
44
+ def initialize_connect options = {}
45
+ @settings = DEFAULT_OPTIONS.merge(options)
46
+ REQUIRED_OPTION_KEYS.each do |opt|
47
+ raise MissingArgsError, [opt] unless @settings.has_key?(opt)
48
+ end
49
+ end
50
+
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,88 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module Qiniu
4
+ module RS
5
+
6
+ class Exception < RuntimeError
7
+ def to_s
8
+ inspect
9
+ end
10
+ end
11
+
12
+ class ResponseError < Exception
13
+ attr_reader :response
14
+
15
+ def initialize(message, response = nil)
16
+ @response = response
17
+ super(message)
18
+ end
19
+
20
+ def http_code
21
+ @response.code.to_i if @response
22
+ end
23
+
24
+ def http_body
25
+ @response.body if @response
26
+ end
27
+
28
+ def inspect
29
+ "#{message}: #{http_body}"
30
+ end
31
+ end
32
+
33
+ class RequestFailed < ResponseError
34
+ def message
35
+ "HTTP status code #{http_code}"
36
+ end
37
+
38
+ def to_s
39
+ message
40
+ end
41
+ end
42
+
43
+ class MissingArgsError < Exception
44
+ def initialize(missing_keys)
45
+ key_list = missing_keys.map {|key| key.to_s}.join(' and the ')
46
+ super("You did not provide both required args. Please provide the #{key_list}.")
47
+ end
48
+ end
49
+
50
+ class MissingAccessToken < MissingArgsError
51
+ def initialize
52
+ super([:access_token])
53
+ end
54
+ end
55
+
56
+ class MissingRefreshToken < MissingArgsError
57
+ def initialize
58
+ super([:refresh_token])
59
+ end
60
+ end
61
+
62
+ class MissingUsernameOrPassword < MissingArgsError
63
+ def initialize
64
+ super([:username, :password])
65
+ end
66
+ end
67
+
68
+ class InvalidArgsError < Exception
69
+ def initialize(invalid_keys)
70
+ key_list = invalid_keys.map {|key| key.to_s}.join(' and the ')
71
+ super("#{key_list} should not be empty.")
72
+ end
73
+ end
74
+
75
+ class MissingConfError < Exception
76
+ def initialize(missing_conf_file)
77
+ super("Error, missing #{missing_conf_file}. You must have #{missing_conf_file} to configure your client id and secret.")
78
+ end
79
+ end
80
+
81
+ class NoSuchFileError < Exception
82
+ def initialize(missing_file)
83
+ super("Error, no such file #{missing_file}.")
84
+ end
85
+ end
86
+
87
+ end
88
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module Qiniu
4
+ module RS
5
+ module Image
6
+ class << self
7
+ include Utils
8
+
9
+ def info(url)
10
+ Utils.http_request url + '/imageInfo', nil, {:method => :get}
11
+ end
12
+
13
+ def preivew_url(url, spec)
14
+ url + '/imagePreview/' + spec.to_s
15
+ end
16
+
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,41 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'mime/types'
4
+ require 'digest/sha1'
5
+ require 'qiniu/rs/exceptions'
6
+
7
+ module Qiniu
8
+ module RS
9
+ module IO
10
+ class << self
11
+ include Utils
12
+
13
+ def put_auth(expires_in = nil, callback_url = nil)
14
+ url = Config.settings[:io_host] + "/put-auth/"
15
+ url += "#{expires_in}" if !expires_in.nil? && expires_in > 0
16
+ if !callback_url.nil? && !callback_url.empty?
17
+ encoded_callback_url = Utils.urlsafe_base64_encode(callback_url)
18
+ url += "/callback/#{encoded_callback_url}"
19
+ end
20
+ Auth.request(url)
21
+ end
22
+
23
+ def put_file(url, local_file, bucket = '', key = '', mime_type = '', custom_meta = '', callback_params = '')
24
+ raise NoSuchFileError unless File.exist?(local_file)
25
+ key = Digest::SHA1.hexdigest(local_file + Time.now.to_s) if key.empty?
26
+ entry_uri = bucket + ':' + key
27
+ if mime_type.empty?
28
+ mime = MIME::Types.type_for local_file
29
+ mime_type = mime.empty? ? 'application/octet-stream' : mime[0].content_type
30
+ end
31
+ action_params = '/rs-put/' + Utils.urlsafe_base64_encode(entry_uri) + '/mimeType/' + Utils.urlsafe_base64_encode(mime_type)
32
+ action_params += '/meta/' + Utils.urlsafe_base64_encode(custom_meta) unless custom_meta.empty?
33
+ callback_params = {:bucket => bucket, :key => key, :mime_type => mime_type} if callback_params.empty?
34
+ callback_query_string = Utils.generate_query_string(callback_params)
35
+ Utils.upload_multipart_data(url, local_file, action_params, callback_query_string)
36
+ end
37
+
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'logger'
4
+
5
+ module Qiniu
6
+ module RS
7
+ module Log
8
+ class << self
9
+ attr_accessor :logger
10
+
11
+ def logger
12
+ @logger ||= Logger.new(STDERR)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,63 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module Qiniu
4
+ module RS
5
+ module RS
6
+ class << self
7
+ include Utils
8
+
9
+ def stat(bucket, key)
10
+ Auth.request Config.settings[:rs_host] + '/stat/' + encode_entry_uri(bucket, key)
11
+ end
12
+
13
+ def get(bucket, key, save_as = nil, expires_in = nil, version = nil)
14
+ url = Config.settings[:rs_host] + '/get/' + encode_entry_uri(bucket, key)
15
+ url += '/base/' + version unless version.nil?
16
+ url += '/attName/' + Utils.urlsafe_base64_encode(save_as) unless save_as.nil?
17
+ url += '/expires/' + expires_in.to_s if !expires_in.nil? && expires_in > 0
18
+ Auth.request url
19
+ end
20
+
21
+ def delete(bucket, key)
22
+ Auth.request Config.settings[:rs_host] + '/delete/' + encode_entry_uri(bucket, key)
23
+ end
24
+
25
+ def publish(domain, bucket)
26
+ encoded_domain = Utils.urlsafe_base64_encode(domain)
27
+ Auth.request Config.settings[:rs_host] + "/publish/#{encoded_domain}/from/#{bucket}"
28
+ end
29
+
30
+ def unpublish(domain)
31
+ encoded_domain = Utils.urlsafe_base64_encode(domain)
32
+ Auth.request Config.settings[:rs_host] + "/unpublish/#{encoded_domain}"
33
+ end
34
+
35
+ def drop(bucket)
36
+ Auth.request Config.settings[:rs_host] + "/drop/#{bucket}"
37
+ end
38
+
39
+ def batch(command, bucket, keys)
40
+ execs = []
41
+ keys.each do |key|
42
+ encoded_uri = encode_entry_uri(bucket, key)
43
+ execs << "op=/#{command}/#{encoded_uri}"
44
+ end
45
+ Auth.request Config.settings[:rs_host] + "/batch?" + execs.join("&")
46
+ end
47
+
48
+ def batch_get(bucket, keys)
49
+ batch("get", bucket, keys)
50
+ end
51
+
52
+ def batch_stat(bucket, keys)
53
+ batch("stat", bucket, keys)
54
+ end
55
+
56
+ def batch_delete(bucket, keys)
57
+ batch("delete", bucket, keys)
58
+ end
59
+
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,124 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'uri'
4
+ require 'json'
5
+ require 'base64'
6
+ require 'rest_client'
7
+ require 'qiniu/rs/exceptions'
8
+
9
+ module Qiniu
10
+ module RS
11
+ module Utils extend self
12
+
13
+ def urlsafe_base64_encode content
14
+ Base64.encode64(content).strip.gsub('+', '-').gsub('/','_').gsub(/\r?\n/, '')
15
+ end
16
+
17
+ def urlsafe_base64_decode encoded_content
18
+ Base64.decode64 encoded_content.gsub('_','/').gsub('-', '+')
19
+ end
20
+
21
+ def encode_entry_uri(bucket, key)
22
+ entry_uri = bucket + ':' + key
23
+ urlsafe_base64_encode(entry_uri)
24
+ end
25
+
26
+ def safe_json_parse(data)
27
+ JSON.parse(data)
28
+ rescue JSON::ParserError
29
+ {}
30
+ end
31
+
32
+ def send_request_with url, data = nil, options = {}
33
+ options[:method] = Config.settings[:method] unless options[:method]
34
+ options[:content_type] = Config.settings[:content_type] unless options[:content_type]
35
+ header_options = {
36
+ :accept => :json,
37
+ :user_agent => Config.settings[:user_agent]
38
+ }
39
+ header_options.merge!('Authorization' => "Bearer #{options[:access_token]}") if options[:access_token]
40
+ case options[:method]
41
+ when :get
42
+ response = RestClient.get url, header_options
43
+ when :post
44
+ header_options.merge!(:content_type => options[:content_type])
45
+ response = RestClient.post url, data, header_options
46
+ end
47
+ code = response.respond_to?(:code) ? response.code.to_i : 0
48
+ if code != 200
49
+ raise RequestFailed.new(response)
50
+ else
51
+ data = {}
52
+ body = response.respond_to?(:body) ? response.body : {}
53
+ data = safe_json_parse(body) unless body.empty?
54
+ end
55
+ [code, data]
56
+ end
57
+
58
+ def http_request url, data = nil, options = {}
59
+ retry_times = 0
60
+ begin
61
+ retry_times += 1
62
+ send_request_with url, data, options
63
+ rescue Errno::ECONNRESET => err
64
+ if Config.settings[:auto_reconnect] && retry_times < Config.settings[:max_retry_times]
65
+ retry
66
+ else
67
+ Log.logger.error err
68
+ end
69
+ rescue => e
70
+ Log.logger.warn "#{e.message} => Utils.http_request('#{url}')"
71
+ code = 0
72
+ data = {}
73
+ body = {}
74
+ if e.respond_to? :response
75
+ res = e.response
76
+ code = res.code.to_i if res.respond_to? :code
77
+ body = res.respond_to?(:body) ? res.body : ""
78
+ data = safe_json_parse(body) unless body.empty?
79
+ end
80
+ [code, data]
81
+ end
82
+ end
83
+
84
+ def upload_multipart_data(url, filepath, action_string, callback_query_string = '')
85
+ code, data = 0, {}
86
+ begin
87
+ header_options = {
88
+ :accept => :json,
89
+ :user_agent => Config.settings[:user_agent]
90
+ }
91
+ post_data = {
92
+ :file => File.new(filepath, 'rb'),
93
+ :params => callback_query_string,
94
+ :action => action_string,
95
+ :multipart => true
96
+ }
97
+ response = RestClient.post url, post_data, header_options
98
+ body = response.respond_to?(:body) ? response.body : ""
99
+ data = safe_json_parse(body) unless body.empty?
100
+ code = response.code.to_i if response.respond_to?(:code)
101
+ rescue Errno::ECONNRESET => err
102
+ Log.logger.error err
103
+ rescue => e
104
+ Log.logger.warn "#{e.message} => Utils.http_request('#{url}')"
105
+ res = e.response
106
+ if e.respond_to? :response
107
+ res = e.response
108
+ code = res.code.to_i if res.respond_to? :code
109
+ body = res.respond_to?(:body) ? res.body : ""
110
+ data = safe_json_parse(body) unless body.empty?
111
+ end
112
+ end
113
+ [code, data]
114
+ end
115
+
116
+ def generate_query_string(params)
117
+ return params if params.is_a?(String)
118
+ total_param = params.map { |key, value| key.to_s+"="+value.to_s }
119
+ URI.escape(total_param.join("&"))
120
+ end
121
+
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,7 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module Qiniu
4
+ module RS
5
+ VERSION = "1.0.0"
6
+ end
7
+ end