iora-ruby-box 1.14.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/.document +5 -0
- data/.travis.yml +6 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +51 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +349 -0
- data/Rakefile +32 -0
- data/VERSION +1 -0
- data/lib/ruby-box.rb +21 -0
- data/lib/ruby-box/client.rb +163 -0
- data/lib/ruby-box/collaboration.rb +19 -0
- data/lib/ruby-box/comment.rb +14 -0
- data/lib/ruby-box/discussion.rb +15 -0
- data/lib/ruby-box/event.rb +4 -0
- data/lib/ruby-box/event_response.rb +17 -0
- data/lib/ruby-box/exceptions.rb +22 -0
- data/lib/ruby-box/file.rb +96 -0
- data/lib/ruby-box/folder.rb +113 -0
- data/lib/ruby-box/item.rb +181 -0
- data/lib/ruby-box/session.rb +136 -0
- data/lib/ruby-box/shared_link.rb +10 -0
- data/lib/ruby-box/user.rb +16 -0
- data/lib/ruby-box/web_link.rb +4 -0
- data/ruby-box.gemspec +99 -0
- data/spec/client_spec.rb +64 -0
- data/spec/event_spec.rb +70 -0
- data/spec/file_spec.rb +110 -0
- data/spec/fixtures/comment_create.json +18 -0
- data/spec/fixtures/events.json +161 -0
- data/spec/fixtures/me.json +17 -0
- data/spec/fixtures/users.json +39 -0
- data/spec/folder_spec.rb +181 -0
- data/spec/helper/account.example +3 -0
- data/spec/helper/account.rb +8 -0
- data/spec/integration_spec.rb +387 -0
- data/spec/item_spec.rb +31 -0
- data/spec/me_spec.rb +20 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/users_spec.rb +24 -0
- metadata +214 -0
@@ -0,0 +1,181 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'addressable/uri'
|
3
|
+
|
4
|
+
module RubyBox
|
5
|
+
class Item
|
6
|
+
|
7
|
+
@@has_many = []
|
8
|
+
@@has_many_paginated = []
|
9
|
+
|
10
|
+
def initialize( session, raw_item )
|
11
|
+
@session = session
|
12
|
+
@raw_item = raw_item
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.has_many(*keys)
|
16
|
+
keys.each {|key| @@has_many << key.to_s}
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.has_many_paginated(*keys)
|
20
|
+
keys.each {|key| @@has_many_paginated << key.to_s}
|
21
|
+
end
|
22
|
+
|
23
|
+
def move_to( folder_id, name=nil )
|
24
|
+
# Allow either a folder_id or a folder object
|
25
|
+
# to be passed in.
|
26
|
+
folder_id = folder_id.id if folder_id.instance_of?(RubyBox::Folder)
|
27
|
+
|
28
|
+
self.name = name if name
|
29
|
+
self.parent = {"id" => folder_id}
|
30
|
+
|
31
|
+
update
|
32
|
+
end
|
33
|
+
|
34
|
+
def update
|
35
|
+
reload_meta unless etag
|
36
|
+
|
37
|
+
url = "#{RubyBox::API_URL}/#{resource_name}/#{id}"
|
38
|
+
uri = URI.parse(url)
|
39
|
+
|
40
|
+
request = Net::HTTP::Put.new(uri.path, {
|
41
|
+
"if-match" => etag,
|
42
|
+
"Content-Type" => 'application/json'
|
43
|
+
})
|
44
|
+
request.body = JSON.dump(serialize)
|
45
|
+
|
46
|
+
@raw_item = @session.request(uri, request)
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def create
|
51
|
+
url = "#{RubyBox::API_URL}/#{resource_name}"
|
52
|
+
uri = URI.parse(url)
|
53
|
+
request = Net::HTTP::Post.new( uri.request_uri )
|
54
|
+
request.body = JSON.dump(@raw_item)
|
55
|
+
resp = @session.request(uri, request)
|
56
|
+
@raw_item = resp
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
def delete(opts={})
|
61
|
+
url = "#{RubyBox::API_URL}/#{resource_name}/#{id}"
|
62
|
+
url = "#{url}#{Addressable::URI.new(:query_values => opts).to_s}" unless opts.keys.empty?
|
63
|
+
resp = @session.delete( url )
|
64
|
+
end
|
65
|
+
|
66
|
+
def reload_meta
|
67
|
+
url = "#{RubyBox::API_URL}/#{resource_name}/#{@raw_item['id']}"
|
68
|
+
@raw_item = @session.get( url )
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
def method_missing(method, *args, &block)
|
73
|
+
key = method.to_s
|
74
|
+
|
75
|
+
# Support has many and paginated has many relationships.
|
76
|
+
return many(key) if @@has_many.include?(key)
|
77
|
+
return paginated(key, args[0] || 100, args[1] || 0, args[2]) if @@has_many_paginated.include?(key)
|
78
|
+
|
79
|
+
# update @raw_item hash if this appears to be a setter.
|
80
|
+
setter = method.to_s.end_with?('=')
|
81
|
+
key = key[0...-1] if setter
|
82
|
+
@raw_item[key] = args[0] if setter and update_fields.include?(key)
|
83
|
+
|
84
|
+
# we may have a mini version of the object loaded, fix this.
|
85
|
+
reload_meta if @raw_item[key].nil? and has_mini_format?
|
86
|
+
|
87
|
+
if @raw_item[key].kind_of?(Hash)
|
88
|
+
return RubyBox::Item.factory(@session, @raw_item[key])
|
89
|
+
elsif RubyBox::ISO_8601_TEST.match(@raw_item[key].to_s)
|
90
|
+
return Time.parse(@raw_item[key])
|
91
|
+
else
|
92
|
+
return @raw_item[key]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# see http://developers.box.com/docs/#folders-create-a-shared-link-for-a-folder
|
97
|
+
# for a list of valid options.
|
98
|
+
def create_shared_link(opts={})
|
99
|
+
raise UnshareableResource unless ['folder', 'file'].include?(type)
|
100
|
+
|
101
|
+
opts = {
|
102
|
+
access: 'open'
|
103
|
+
}.merge(opts) if opts
|
104
|
+
|
105
|
+
url = "#{RubyBox::API_URL}/#{resource_name}/#{id}"
|
106
|
+
uri = URI.parse(url)
|
107
|
+
|
108
|
+
request = Net::HTTP::Put.new(uri.path, {
|
109
|
+
"Content-Type" => 'application/json'
|
110
|
+
})
|
111
|
+
|
112
|
+
request.body = JSON.dump({
|
113
|
+
shared_link: opts
|
114
|
+
})
|
115
|
+
|
116
|
+
@raw_item = @session.request(uri, request)
|
117
|
+
self
|
118
|
+
end
|
119
|
+
|
120
|
+
def disable_shared_link(opts={})
|
121
|
+
create_shared_link(nil)
|
122
|
+
end
|
123
|
+
|
124
|
+
def shared_link
|
125
|
+
return nil unless @raw_item['shared_link']
|
126
|
+
RubyBox::Item.factory(@session, @raw_item['shared_link'].merge('type' => 'shared_link'))
|
127
|
+
end
|
128
|
+
|
129
|
+
def as_json(opts={})
|
130
|
+
@raw_item
|
131
|
+
end
|
132
|
+
|
133
|
+
protected
|
134
|
+
|
135
|
+
def self.factory(session, entry)
|
136
|
+
type = entry['type'] ? entry['type'].split('_').map(&:capitalize).join('').to_sym : nil
|
137
|
+
if RubyBox.constants.include? type
|
138
|
+
RubyBox.const_get(type).new(session, entry)
|
139
|
+
else
|
140
|
+
RubyBox::Item.new(session, entry)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def has_mini_format?
|
145
|
+
false
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
def many(key)
|
151
|
+
url = "#{RubyBox::API_URL}/#{resource_name}/#{id}/#{key}"
|
152
|
+
resp = @session.get( url )
|
153
|
+
resp['entries'].map {|i| RubyBox::Item.factory(@session, i)}
|
154
|
+
end
|
155
|
+
|
156
|
+
def paginated(key, item_limit=100, offset=0, fields=nil)
|
157
|
+
Enumerator.new do |yielder|
|
158
|
+
while true
|
159
|
+
url = "#{RubyBox::API_URL}/#{resource_name}/#{id}/#{key}?limit=#{item_limit}&offset=#{offset}"
|
160
|
+
url = "#{url}&fields=#{fields.map(&:to_s).join(',')}" if fields
|
161
|
+
resp = @session.get( url )
|
162
|
+
resp['entries'].each do |entry|
|
163
|
+
yielder.yield(RubyBox::Item.factory(@session, entry))
|
164
|
+
end
|
165
|
+
offset += resp['entries'].count
|
166
|
+
break if resp['offset'].to_i + resp['limit'].to_i >= resp['total_count'].to_i
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def serialize
|
172
|
+
update_fields.inject({}) {|hash, field| hash[field] = @raw_item[field]; hash}
|
173
|
+
end
|
174
|
+
|
175
|
+
def update_fields
|
176
|
+
['name', 'description', 'parent']
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'oauth2'
|
2
|
+
|
3
|
+
module RubyBox
|
4
|
+
class Session
|
5
|
+
|
6
|
+
OAUTH2_URLS = {
|
7
|
+
:site => 'https://www.box.com',
|
8
|
+
:authorize_url => "/api/oauth2/authorize",
|
9
|
+
:token_url => "/api/oauth2/token"
|
10
|
+
}
|
11
|
+
|
12
|
+
def initialize(opts={}, backoff=0.1)
|
13
|
+
|
14
|
+
@backoff = backoff # try not to excessively hammer API.
|
15
|
+
|
16
|
+
if opts[:client_id]
|
17
|
+
@oauth2_client = OAuth2::Client.new(opts[:client_id], opts[:client_secret], OAUTH2_URLS.dup)
|
18
|
+
@access_token = OAuth2::AccessToken.new(@oauth2_client, opts[:access_token]) if opts[:access_token]
|
19
|
+
@refresh_token = opts[:refresh_token]
|
20
|
+
@as_user = opts[:as_user]
|
21
|
+
else # Support legacy API for historical reasons.
|
22
|
+
@api_key = opts[:api_key]
|
23
|
+
@auth_token = opts[:auth_token]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def authorize_url(redirect_uri, state=nil)
|
28
|
+
opts = { :redirect_uri => redirect_uri }
|
29
|
+
opts[:state] = state if state
|
30
|
+
|
31
|
+
@oauth2_client.auth_code.authorize_url(opts)
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_access_token(code)
|
35
|
+
@access_token = @oauth2_client.auth_code.get_token(code)
|
36
|
+
end
|
37
|
+
|
38
|
+
def refresh_token(refresh_token)
|
39
|
+
refresh_access_token_obj = OAuth2::AccessToken.new(@oauth2_client, @access_token.token, {'refresh_token' => refresh_token})
|
40
|
+
@access_token = refresh_access_token_obj.refresh!
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_auth_header
|
44
|
+
"BoxAuth api_key=#{@api_key}&auth_token=#{@auth_token}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def get(url, raw=false)
|
48
|
+
uri = URI.parse(url)
|
49
|
+
request = Net::HTTP::Get.new( uri.request_uri )
|
50
|
+
resp = request( uri, request, raw )
|
51
|
+
end
|
52
|
+
|
53
|
+
def delete(url, raw=false)
|
54
|
+
uri = URI.parse(url)
|
55
|
+
request = Net::HTTP::Delete.new( uri.request_uri )
|
56
|
+
resp = request( uri, request, raw )
|
57
|
+
end
|
58
|
+
|
59
|
+
def request(uri, request, raw=false, retries=0)
|
60
|
+
|
61
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
62
|
+
http.use_ssl = true
|
63
|
+
#http.set_debug_output($stdout)
|
64
|
+
|
65
|
+
if @access_token
|
66
|
+
request.add_field('Authorization', "Bearer #{@access_token.token}")
|
67
|
+
else
|
68
|
+
request.add_field('Authorization', build_auth_header)
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
request.add_field('As-User', "#{@as_user}") if @as_user
|
73
|
+
|
74
|
+
response = http.request(request)
|
75
|
+
|
76
|
+
if response.is_a? Net::HTTPNotFound
|
77
|
+
raise RubyBox::ObjectNotFound
|
78
|
+
end
|
79
|
+
|
80
|
+
# Got unauthorized (401) status, try to refresh the token
|
81
|
+
if response.code.to_i == 401 and @refresh_token and retries == 0
|
82
|
+
refresh_token(@refresh_token)
|
83
|
+
return request(uri, request, raw, retries + 1)
|
84
|
+
end
|
85
|
+
|
86
|
+
sleep(@backoff) # try not to excessively hammer API.
|
87
|
+
|
88
|
+
handle_errors( response, raw )
|
89
|
+
end
|
90
|
+
|
91
|
+
def do_stream(url, opts)
|
92
|
+
params = {
|
93
|
+
:content_length_proc => opts[:content_length_proc],
|
94
|
+
:progress_proc => opts[:progress_proc]
|
95
|
+
}
|
96
|
+
|
97
|
+
if @access_token
|
98
|
+
params['Authorization'] = "Bearer #{@access_token.token}"
|
99
|
+
else
|
100
|
+
params['Authorization'] = build_auth_header
|
101
|
+
end
|
102
|
+
|
103
|
+
params['As-User'] = @as_user if @as_user
|
104
|
+
|
105
|
+
open(url, params)
|
106
|
+
end
|
107
|
+
|
108
|
+
def handle_errors( response, raw )
|
109
|
+
status = response.code.to_i
|
110
|
+
body = response.body
|
111
|
+
begin
|
112
|
+
parsed_body = JSON.parse(body)
|
113
|
+
rescue
|
114
|
+
msg = body.nil? || body.empty? ? "no data returned" : body
|
115
|
+
parsed_body = { "message" => msg }
|
116
|
+
end
|
117
|
+
|
118
|
+
# status is used to determine whether
|
119
|
+
# we need to refresh the access token.
|
120
|
+
parsed_body["status"] = status
|
121
|
+
|
122
|
+
case status / 100
|
123
|
+
when 3
|
124
|
+
# 302 Found. We should return the url
|
125
|
+
parsed_body["location"] = response["Location"] if status == 302
|
126
|
+
when 4
|
127
|
+
raise(RubyBox::ItemNameInUse.new(parsed_body, status, body), parsed_body["message"]) if parsed_body["code"] == "item_name_in_use"
|
128
|
+
raise(RubyBox::AuthError.new(parsed_body, status, body), parsed_body["message"]) if parsed_body["code"] == "unauthorized" || status == 401
|
129
|
+
raise(RubyBox::RequestError.new(parsed_body, status, body), parsed_body["message"])
|
130
|
+
when 5
|
131
|
+
raise(RubyBox::ServerError.new(parsed_body, status, body), parsed_body["message"])
|
132
|
+
end
|
133
|
+
raw ? body : parsed_body
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
data/ruby-box.gemspec
ADDED
@@ -0,0 +1,99 @@
|
|
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
|
+
# stub: ruby-box 1.14.0 ruby lib
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "iora-ruby-box"
|
9
|
+
s.version = "1.14.0"
|
10
|
+
|
11
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
|
+
s.require_paths = ["lib"]
|
13
|
+
s.authors = ["Patrick Robertson"]
|
14
|
+
s.date = "2014-03-07"
|
15
|
+
s.description = "ruby gem for box.com 2.0 api"
|
16
|
+
s.email = "patricksrobertson@gmail.com"
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"LICENSE.txt",
|
19
|
+
"README.markdown"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
".document",
|
23
|
+
".travis.yml",
|
24
|
+
"Gemfile",
|
25
|
+
"Gemfile.lock",
|
26
|
+
"LICENSE.txt",
|
27
|
+
"README.markdown",
|
28
|
+
"Rakefile",
|
29
|
+
"VERSION",
|
30
|
+
"lib/ruby-box.rb",
|
31
|
+
"lib/ruby-box/client.rb",
|
32
|
+
"lib/ruby-box/collaboration.rb",
|
33
|
+
"lib/ruby-box/comment.rb",
|
34
|
+
"lib/ruby-box/discussion.rb",
|
35
|
+
"lib/ruby-box/event.rb",
|
36
|
+
"lib/ruby-box/event_response.rb",
|
37
|
+
"lib/ruby-box/exceptions.rb",
|
38
|
+
"lib/ruby-box/file.rb",
|
39
|
+
"lib/ruby-box/folder.rb",
|
40
|
+
"lib/ruby-box/item.rb",
|
41
|
+
"lib/ruby-box/session.rb",
|
42
|
+
"lib/ruby-box/shared_link.rb",
|
43
|
+
"lib/ruby-box/user.rb",
|
44
|
+
"lib/ruby-box/web_link.rb",
|
45
|
+
"ruby-box.gemspec",
|
46
|
+
"spec/client_spec.rb",
|
47
|
+
"spec/event_spec.rb",
|
48
|
+
"spec/file_spec.rb",
|
49
|
+
"spec/fixtures/comment_create.json",
|
50
|
+
"spec/fixtures/events.json",
|
51
|
+
"spec/fixtures/me.json",
|
52
|
+
"spec/fixtures/users.json",
|
53
|
+
"spec/folder_spec.rb",
|
54
|
+
"spec/helper/account.example",
|
55
|
+
"spec/helper/account.rb",
|
56
|
+
"spec/integration_spec.rb",
|
57
|
+
"spec/item_spec.rb",
|
58
|
+
"spec/me_spec.rb",
|
59
|
+
"spec/spec_helper.rb",
|
60
|
+
"spec/users_spec.rb"
|
61
|
+
]
|
62
|
+
s.homepage = "http://github.com/attachmentsme/ruby-box"
|
63
|
+
s.licenses = ["MIT"]
|
64
|
+
s.rubygems_version = "2.2.1"
|
65
|
+
s.summary = "ruby gem for box.com 2.0 api"
|
66
|
+
|
67
|
+
if s.respond_to? :specification_version then
|
68
|
+
s.specification_version = 4
|
69
|
+
|
70
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
71
|
+
s.add_runtime_dependency(%q<multipart-post>, [">= 0"])
|
72
|
+
s.add_runtime_dependency(%q<oauth2>, [">= 0"])
|
73
|
+
s.add_runtime_dependency(%q<json>, [">= 0"])
|
74
|
+
s.add_runtime_dependency(%q<addressable>, [">= 0"])
|
75
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
76
|
+
s.add_development_dependency(%q<bundler>, [">= 0"])
|
77
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
|
78
|
+
s.add_development_dependency(%q<webmock>, [">= 0"])
|
79
|
+
else
|
80
|
+
s.add_dependency(%q<multipart-post>, [">= 0"])
|
81
|
+
s.add_dependency(%q<oauth2>, [">= 0"])
|
82
|
+
s.add_dependency(%q<json>, [">= 0"])
|
83
|
+
s.add_dependency(%q<addressable>, [">= 0"])
|
84
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
85
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
86
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
87
|
+
s.add_dependency(%q<webmock>, [">= 0"])
|
88
|
+
end
|
89
|
+
else
|
90
|
+
s.add_dependency(%q<multipart-post>, [">= 0"])
|
91
|
+
s.add_dependency(%q<oauth2>, [">= 0"])
|
92
|
+
s.add_dependency(%q<json>, [">= 0"])
|
93
|
+
s.add_dependency(%q<addressable>, [">= 0"])
|
94
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
95
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
96
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
97
|
+
s.add_dependency(%q<webmock>, [">= 0"])
|
98
|
+
end
|
99
|
+
end
|