ruby-box 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|