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.
@@ -0,0 +1,10 @@
1
+ module RubyBox
2
+ class Comment < Item
3
+
4
+ private
5
+
6
+ def resource_name
7
+ 'comments'
8
+ end
9
+ end
10
+ end
@@ -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
@@ -0,0 +1,10 @@
1
+ module RubyBox
2
+ class User < Item
3
+
4
+ private
5
+
6
+ def resource_name
7
+ 'users'
8
+ end
9
+ end
10
+ 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
+
@@ -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