ruby-box 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.
- checksums.yaml +15 -0
- data/.document +5 -0
- data/.gitignore +52 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +48 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +160 -0
- data/Rakefile +32 -0
- data/VERSION +1 -0
- data/lib/ruby-box.rb +16 -0
- data/lib/ruby-box/client.rb +97 -0
- data/lib/ruby-box/comment.rb +10 -0
- data/lib/ruby-box/discussion.rb +16 -0
- data/lib/ruby-box/exceptions.rb +17 -0
- data/lib/ruby-box/file.rb +70 -0
- data/lib/ruby-box/folder.rb +71 -0
- data/lib/ruby-box/item.rb +83 -0
- data/lib/ruby-box/session.rb +102 -0
- data/lib/ruby-box/user.rb +10 -0
- data/ruby-box.gemspec +50 -0
- data/spec/client_spec.rb +28 -0
- data/spec/file_spec.rb +95 -0
- data/spec/folder_spec.rb +101 -0
- data/spec/helper/account.example +3 -0
- data/spec/helper/account.rb +8 -0
- data/spec/integration_spec.rb +181 -0
- metadata +143 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
module RubyBox
|
2
|
+
class Discussion < Item
|
3
|
+
|
4
|
+
def comments
|
5
|
+
url = "#{RubyBox::API_URL}/#{resource_name}/#{id}/comments"
|
6
|
+
resp = @session.get( url )
|
7
|
+
resp['entries'].map {|i| Comment.new(@session, i)}
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def resource_name
|
13
|
+
'discussions'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module RubyBox
|
2
|
+
class RubyBoxError < StandardError
|
3
|
+
def initialize(error_json)
|
4
|
+
@error_json = error_json
|
5
|
+
end
|
6
|
+
|
7
|
+
def [](key)
|
8
|
+
@error_json[key]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class ObjectNotFound < StandardError; end
|
13
|
+
class AuthError < RubyBoxError; end
|
14
|
+
class RequestError < RubyBoxError; end
|
15
|
+
class ServerError < StandardError; end
|
16
|
+
class ItemNameInUse < RubyBoxError; end
|
17
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module RubyBox
|
2
|
+
class File < Item
|
3
|
+
|
4
|
+
def download
|
5
|
+
resp = stream.read
|
6
|
+
end
|
7
|
+
|
8
|
+
def stream( opts={} )
|
9
|
+
url = "#{RubyBox::API_URL}/#{resource_name}/#{id}/content"
|
10
|
+
@session.do_stream( url, opts )
|
11
|
+
end
|
12
|
+
|
13
|
+
def comments
|
14
|
+
url = "#{RubyBox::API_URL}/#{resource_name}/#{id}/comments"
|
15
|
+
resp = @session.get( url )
|
16
|
+
resp['entries'].map {|i| Comment.new(@session, i)}
|
17
|
+
end
|
18
|
+
|
19
|
+
def upload_content( data )
|
20
|
+
url = "#{RubyBox::UPLOAD_URL}/#{resource_name}/content"
|
21
|
+
uri = URI.parse(url)
|
22
|
+
request = Net::HTTP::Post::Multipart.new(uri.path, {
|
23
|
+
"filename" => UploadIO.new(data, "application/pdf", name),
|
24
|
+
"folder_id" => parent.id
|
25
|
+
})
|
26
|
+
|
27
|
+
resp = @session.request(uri, request)
|
28
|
+
if resp['entries']
|
29
|
+
@raw_item = resp['entries'][0]
|
30
|
+
else
|
31
|
+
@raw_item = resp
|
32
|
+
end
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def update_content( data )
|
37
|
+
|
38
|
+
url = "#{RubyBox::UPLOAD_URL}/#{resource_name}/#{id}/content"
|
39
|
+
uri = URI.parse(url)
|
40
|
+
|
41
|
+
request = Net::HTTP::Post::Multipart.new(uri.path, {
|
42
|
+
"filename" => prepare_upload(data, name),
|
43
|
+
"folder_id" => parent.id
|
44
|
+
}, {"if-match" => etag })
|
45
|
+
|
46
|
+
resp = @session.request(uri, request)
|
47
|
+
if resp['entries']
|
48
|
+
@raw_item = resp['entries'][0]
|
49
|
+
else
|
50
|
+
@raw_item = resp
|
51
|
+
end
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def resource_name
|
58
|
+
'files'
|
59
|
+
end
|
60
|
+
|
61
|
+
def update_fields
|
62
|
+
['name', 'description']
|
63
|
+
end
|
64
|
+
|
65
|
+
def prepare_upload(data, fname)
|
66
|
+
UploadIO.new(data, "application/pdf", fname)
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module RubyBox
|
2
|
+
class Folder < Item
|
3
|
+
def items(item_limit=100, offset=0)
|
4
|
+
Enumerator.new do |yielder|
|
5
|
+
while true
|
6
|
+
url = "#{RubyBox::API_URL}/#{resource_name}/#{id}/items?limit=#{item_limit}&offset=#{offset}"
|
7
|
+
resp = @session.get( url )
|
8
|
+
resp['entries'].each do |entry|
|
9
|
+
yielder.yield(RubyBox::Item.factory(@session, entry))
|
10
|
+
end
|
11
|
+
offset += resp['entries'].count
|
12
|
+
break if resp['offset'].to_i + resp['limit'].to_i >= resp['total_count'].to_i
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def files(name=nil, item_limit=100, offset=0)
|
18
|
+
items(item_limit, offset).select do |item|
|
19
|
+
item.kind_of? RubyBox::File and (name.nil? or item.name == name)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def folders(name=nil, item_limit=100, offset=0)
|
24
|
+
items(item_limit, offset).select do |item|
|
25
|
+
item.kind_of? RubyBox::Folder and (name.nil? or item.name == name)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def discussions
|
30
|
+
url = "#{RubyBox::API_URL}/#{resource_name}/#{id}/discussions"
|
31
|
+
resp = @session.get( url )
|
32
|
+
resp['entries'].map {|i| Discussion.new(@session, i)}
|
33
|
+
end
|
34
|
+
|
35
|
+
def upload_file(filename, data)
|
36
|
+
file = RubyBox::File.new(@session, {
|
37
|
+
'name' => filename,
|
38
|
+
'parent' => RubyBox::User.new(@session, {'id' => id})
|
39
|
+
})
|
40
|
+
|
41
|
+
begin
|
42
|
+
resp = file.upload_content(data) #write a new file. If there is a conflict, update the conflicted file.
|
43
|
+
rescue RubyBox::ItemNameInUse => e
|
44
|
+
file = RubyBox::File.new(@session, {
|
45
|
+
'id' => e['context_info']['conflicts'][0]['id']
|
46
|
+
})
|
47
|
+
data.rewind
|
48
|
+
resp = file.update_content( data )
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def create_subfolder(name)
|
53
|
+
url = "#{RubyBox::API_URL}/#{resource_name}"
|
54
|
+
uri = URI.parse(url)
|
55
|
+
request = Net::HTTP::Post.new( uri.request_uri )
|
56
|
+
request.body = JSON.dump({ "name" => name, "parent" => {"id" => id} })
|
57
|
+
resp = @session.request(uri, request)
|
58
|
+
RubyBox::Folder.new(@session, resp)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def resource_name
|
64
|
+
'folders'
|
65
|
+
end
|
66
|
+
|
67
|
+
def update_fields
|
68
|
+
['name', 'description']
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
module RubyBox
|
4
|
+
class Item
|
5
|
+
|
6
|
+
def initialize( session, raw_item )
|
7
|
+
@session = session
|
8
|
+
@raw_item = raw_item
|
9
|
+
end
|
10
|
+
|
11
|
+
def update
|
12
|
+
reload_meta unless etag
|
13
|
+
|
14
|
+
url = "#{RubyBox::API_URL}/#{resource_name}/#{id}"
|
15
|
+
uri = URI.parse(url)
|
16
|
+
|
17
|
+
request = Net::HTTP::Put.new(uri.path, {
|
18
|
+
"if-match" => etag,
|
19
|
+
"Content-Type" => 'application/json'
|
20
|
+
})
|
21
|
+
request.body = JSON.dump(serialize)
|
22
|
+
|
23
|
+
@raw_item = @session.request(uri, request)
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def delete
|
28
|
+
url = "#{RubyBox::API_URL}/#{resource_name}/#{id}"
|
29
|
+
resp = @session.delete( url )
|
30
|
+
end
|
31
|
+
|
32
|
+
def reload_meta
|
33
|
+
url = "#{RubyBox::API_URL}/#{resource_name}/#{@raw_item['id']}"
|
34
|
+
@raw_item = @session.get( url )
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def method_missing(method, *args, &block)
|
39
|
+
key = method.to_s
|
40
|
+
|
41
|
+
# update @raw_item hash if this appears to be a setter.
|
42
|
+
setter = method.to_s.end_with?('=')
|
43
|
+
key = key[0...-1] if setter
|
44
|
+
@raw_item[key] = args[0] if setter and update_fields.include?(key)
|
45
|
+
|
46
|
+
# we may have a mini version of the object loaded, fix this.
|
47
|
+
reload_meta if @raw_item[key].nil?
|
48
|
+
|
49
|
+
if @raw_item[key].kind_of?(Hash)
|
50
|
+
return RubyBox::Item.factory(@session, @raw_item[key])
|
51
|
+
elsif RubyBox::ISO_8601_TEST.match(@raw_item[key].to_s)
|
52
|
+
return Time.parse(@raw_item[key])
|
53
|
+
else
|
54
|
+
return @raw_item[key]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
protected
|
59
|
+
|
60
|
+
def self.factory(session, entry)
|
61
|
+
case entry['type']
|
62
|
+
when 'folder'
|
63
|
+
return RubyBox::Folder.new(session, entry)
|
64
|
+
when 'file'
|
65
|
+
return RubyBox::File.new(session, entry)
|
66
|
+
when 'comment'
|
67
|
+
return RubyBox::Comment.new(session, entry)
|
68
|
+
when 'user'
|
69
|
+
return RubyBox::User.new(session, entry)
|
70
|
+
when 'discussion'
|
71
|
+
return RubyBox::Discussion.new(session, entry)
|
72
|
+
end
|
73
|
+
entry
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def serialize
|
79
|
+
update_fields.inject({}) {|hash, field| hash[field] = @raw_item[field]; hash}
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'oauth2'
|
2
|
+
|
3
|
+
module RubyBox
|
4
|
+
class Session
|
5
|
+
attr_accessor :api_key, :auth_token, :oauth2_client, :access_token
|
6
|
+
|
7
|
+
OAUTH2_URLS = {
|
8
|
+
:site => 'https://www.box.com',
|
9
|
+
:authorize_url => "/api/oauth2/authorize",
|
10
|
+
:token_url => "/api/oauth2/token"
|
11
|
+
}
|
12
|
+
|
13
|
+
def initialize(opts={})
|
14
|
+
if opts[:client_id]
|
15
|
+
@oauth2_client = OAuth2::Client.new(opts[:client_id], opts[:client_secret], OAUTH2_URLS)
|
16
|
+
@access_token = OAuth2::AccessToken.new(oauth2_client, opts[:access_token]) if opts[:access_token]
|
17
|
+
else # Support legacy API for historical reasons.
|
18
|
+
@api_key = opts[:api_key]
|
19
|
+
@auth_token = opts[:auth_token]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def authorize_url(redirect_uri)
|
24
|
+
@redirect_uri = redirect_uri
|
25
|
+
@oauth2_client.auth_code.authorize_url(:redirect_uri => redirect_uri)
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_access_token(code)
|
29
|
+
@access_token = @oauth2_client.auth_code.get_token(code, { :redirect_uri => @redirect_uri, :token_method => :post })
|
30
|
+
@access_token.token
|
31
|
+
end
|
32
|
+
|
33
|
+
def build_auth_header
|
34
|
+
"BoxAuth api_key=#{@api_key}&auth_token=#{@auth_token}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def get(url, raw=false)
|
38
|
+
uri = URI.parse(url)
|
39
|
+
request = Net::HTTP::Get.new( uri.request_uri )
|
40
|
+
resp = request( uri, request, raw )
|
41
|
+
end
|
42
|
+
|
43
|
+
def delete(url, raw=false)
|
44
|
+
uri = URI.parse(url)
|
45
|
+
request = Net::HTTP::Delete.new( uri.request_uri )
|
46
|
+
resp = request( uri, request, raw )
|
47
|
+
end
|
48
|
+
|
49
|
+
def request(uri, request, raw=false)
|
50
|
+
|
51
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
52
|
+
http.use_ssl = true
|
53
|
+
http.ssl_version = :SSLv3
|
54
|
+
|
55
|
+
if @access_token
|
56
|
+
request.add_field('Authorization', "Bearer #{@access_token.token}")
|
57
|
+
else
|
58
|
+
request.add_field('Authorization', build_auth_header)
|
59
|
+
end
|
60
|
+
|
61
|
+
response = http.request(request)
|
62
|
+
|
63
|
+
if response.is_a? Net::HTTPNotFound
|
64
|
+
raise RubyBox::ObjectNotFound
|
65
|
+
end
|
66
|
+
handle_errors( response.code.to_i, response.body, raw )
|
67
|
+
end
|
68
|
+
|
69
|
+
def do_stream(url, opts)
|
70
|
+
params = {
|
71
|
+
:content_length_proc => opts[:content_length_proc],
|
72
|
+
:progress_proc => opts[:progress_proc]
|
73
|
+
}
|
74
|
+
|
75
|
+
if @access_token
|
76
|
+
params['Authorization'] = "Bearer #{@access_token.token}"
|
77
|
+
else
|
78
|
+
params['Authorization'] = build_auth_header
|
79
|
+
end
|
80
|
+
|
81
|
+
open(url, params)
|
82
|
+
end
|
83
|
+
|
84
|
+
def handle_errors( status, body, raw )
|
85
|
+
begin
|
86
|
+
parsed_body = JSON.parse(body)
|
87
|
+
rescue
|
88
|
+
msg = body.nil? || body.empty? ? "no data returned" : body
|
89
|
+
parsed_body = { "message" => msg }
|
90
|
+
end
|
91
|
+
case status / 100
|
92
|
+
when 4
|
93
|
+
raise(RubyBox::ItemNameInUse.new(parsed_body), parsed_body["message"]) if parsed_body["code"] == "item_name_in_use"
|
94
|
+
raise(RubyBox::AuthError.new(parsed_body), parsed_body["message"]) if parsed_body["code"] == "unauthorized" || status == 401
|
95
|
+
raise(RubyBox::RequestError.new(parsed_body), parsed_body["message"])
|
96
|
+
when 5
|
97
|
+
raise RubyBox::ServerError, parsed_body["message"]
|
98
|
+
end
|
99
|
+
raw ? body : parsed_body
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/ruby-box.gemspec
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "ruby-box"
|
8
|
+
s.version = "1.0.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Benjamin Coe", "Jesse Miller", "Larry Kang"]
|
12
|
+
s.date = "2013-03-02"
|
13
|
+
s.description = "ruby gem for box.com 2.0 api"
|
14
|
+
s.email = "ben@attachments.me"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.markdown"
|
18
|
+
]
|
19
|
+
s.files = `git ls-files`.gsub('"spec/fixtures/\351\201\240\345\277\227\346\225\231\346\216\210.jpg"', '').split($\)
|
20
|
+
s.homepage = "http://github.com/attachmentsme/ruby-box"
|
21
|
+
s.licenses = ["MIT"]
|
22
|
+
s.require_paths = ["lib"]
|
23
|
+
s.rubygems_version = "1.8.24"
|
24
|
+
s.summary = "ruby gem for box.com 2.0 api"
|
25
|
+
|
26
|
+
if s.respond_to? :specification_version then
|
27
|
+
s.specification_version = 3
|
28
|
+
|
29
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
30
|
+
s.add_runtime_dependency(%q<multipart-post>, ["~> 1.1.5"])
|
31
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
32
|
+
s.add_development_dependency(%q<bundler>, [">= 0"])
|
33
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
|
34
|
+
s.add_development_dependency(%q<webmock>, [">= 0"])
|
35
|
+
else
|
36
|
+
s.add_dependency(%q<multipart-post>, ["~> 1.1.5"])
|
37
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
38
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
39
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
40
|
+
s.add_dependency(%q<webmock>, [">= 0"])
|
41
|
+
end
|
42
|
+
else
|
43
|
+
s.add_dependency(%q<multipart-post>, ["~> 1.1.5"])
|
44
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
45
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
46
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
47
|
+
s.add_dependency(%q<webmock>, [">= 0"])
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#encoding: UTF-8
|
2
|
+
|
3
|
+
require 'helper/account'
|
4
|
+
require 'ruby-box'
|
5
|
+
require 'webmock/rspec'
|
6
|
+
|
7
|
+
describe RubyBox::Client do
|
8
|
+
before do
|
9
|
+
@session = RubyBox::Session.new
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#split_path' do
|
13
|
+
it "returns the appropriate path" do
|
14
|
+
client = RubyBox::Client.new(@session)
|
15
|
+
client.split_path('foo/bar').should == ['foo', 'bar']
|
16
|
+
end
|
17
|
+
|
18
|
+
it "leading / is ignored" do
|
19
|
+
client = RubyBox::Client.new(@session)
|
20
|
+
client.split_path('/foo/bar').should == ['foo', 'bar']
|
21
|
+
end
|
22
|
+
|
23
|
+
it "trailing / is ignored" do
|
24
|
+
client = RubyBox::Client.new(@session)
|
25
|
+
client.split_path('foo/bar/').should == ['foo', 'bar']
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|