iora-ruby-box 1.14.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'rspec/core/rake_task'
|
15
|
+
RSpec::Core::RakeTask.new(:spec)
|
16
|
+
|
17
|
+
require 'jeweler'
|
18
|
+
Jeweler::Tasks.new do |gem|
|
19
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
20
|
+
gem.name = "ruby-box"
|
21
|
+
gem.homepage = "http://github.com/attachmentsme/ruby-box"
|
22
|
+
gem.license = "MIT"
|
23
|
+
gem.summary = %Q{ruby gem for box.com 2.0 api}
|
24
|
+
gem.description = %Q{ruby gem for box.com 2.0 api}
|
25
|
+
gem.email = "ben@attachments.me"
|
26
|
+
gem.authors = ["Attachments.me"]
|
27
|
+
gem.files.exclude 'spec/*.test'
|
28
|
+
# dependencies defined in Gemfile
|
29
|
+
end
|
30
|
+
Jeweler::RubygemsDotOrgTasks.new
|
31
|
+
|
32
|
+
task :default => :spec
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.14.0
|
data/lib/ruby-box.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'ruby-box/client'
|
2
|
+
require 'ruby-box/session'
|
3
|
+
|
4
|
+
require 'ruby-box/item'
|
5
|
+
require 'ruby-box/file'
|
6
|
+
require 'ruby-box/folder'
|
7
|
+
require 'ruby-box/user'
|
8
|
+
require 'ruby-box/comment'
|
9
|
+
require 'ruby-box/collaboration'
|
10
|
+
require 'ruby-box/discussion'
|
11
|
+
require 'ruby-box/exceptions'
|
12
|
+
require 'ruby-box/event_response'
|
13
|
+
require 'ruby-box/event'
|
14
|
+
require 'ruby-box/web_link'
|
15
|
+
require 'ruby-box/shared_link'
|
16
|
+
|
17
|
+
module RubyBox
|
18
|
+
API_URL = 'https://api.box.com/2.0'
|
19
|
+
UPLOAD_URL = 'https://upload.box.com/api/2.0'
|
20
|
+
ISO_8601_TEST = Regexp.new(/^[0-9]{4}-[0-9]{2}-[0-9]{2}T/)
|
21
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/https'
|
3
|
+
require 'json'
|
4
|
+
require 'net/http/post/multipart'
|
5
|
+
require 'open-uri'
|
6
|
+
|
7
|
+
module RubyBox
|
8
|
+
class Client
|
9
|
+
|
10
|
+
def initialize(session)
|
11
|
+
@session = session
|
12
|
+
end
|
13
|
+
|
14
|
+
def root_folder
|
15
|
+
folder_by_id('0')
|
16
|
+
end
|
17
|
+
|
18
|
+
def folder_by_id(id)
|
19
|
+
folder = Folder.new(@session, {'id' => id})
|
20
|
+
folder.reload_meta
|
21
|
+
end
|
22
|
+
|
23
|
+
def folder(path='/')
|
24
|
+
path = path.sub(/(^\.$)|(^\.\/)/, '') if path # handle folders with leading '.'
|
25
|
+
return root_folder if ['', '/'].member?(path)
|
26
|
+
folder_from_split_path( split_path(path) )
|
27
|
+
end
|
28
|
+
|
29
|
+
def file_by_id(id)
|
30
|
+
file = File.new(@session, {'id' => id})
|
31
|
+
file.reload_meta
|
32
|
+
end
|
33
|
+
|
34
|
+
def file(path)
|
35
|
+
path = split_path( path.sub(/^\.\//, '') )
|
36
|
+
file_name = path.pop
|
37
|
+
folder = folder_from_split_path( path )
|
38
|
+
folder.files(file_name).first if folder
|
39
|
+
end
|
40
|
+
|
41
|
+
def item(path)
|
42
|
+
path = split_path( path.sub(/^\.\//, '') )
|
43
|
+
item_name = path.pop
|
44
|
+
folder = folder_from_split_path( path )
|
45
|
+
|
46
|
+
folder.items.select do |item|
|
47
|
+
item.instance_variable_get(:@raw_item)['name'] and item.name == item_name
|
48
|
+
end.first
|
49
|
+
end
|
50
|
+
|
51
|
+
def download(path)
|
52
|
+
file = file(path)
|
53
|
+
file.download if file
|
54
|
+
end
|
55
|
+
|
56
|
+
def stream(path, opts={})
|
57
|
+
file = file(path)
|
58
|
+
file.stream(opts) if file
|
59
|
+
end
|
60
|
+
|
61
|
+
def search(query, item_limit=100, offset=0)
|
62
|
+
Enumerator.new do |yielder|
|
63
|
+
while true
|
64
|
+
url = "#{RubyBox::API_URL}/search?query=#{URI::encode(query)}&limit=#{item_limit}&offset=#{offset}"
|
65
|
+
resp = @session.get( url )
|
66
|
+
resp['entries'].each do |entry|
|
67
|
+
yielder.yield(RubyBox::Item.factory(@session, entry))
|
68
|
+
end
|
69
|
+
offset += resp['entries'].count
|
70
|
+
break if resp['offset'].to_i + resp['limit'].to_i >= resp['total_count'].to_i
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def create_folder(path)
|
76
|
+
folder = root_folder
|
77
|
+
folder_names = split_path(path)
|
78
|
+
folder_names.each do |folder_name|
|
79
|
+
new_folder = folder.folders(folder_name).first
|
80
|
+
if !new_folder
|
81
|
+
begin
|
82
|
+
new_folder = folder.create_subfolder(folder_name)
|
83
|
+
rescue RubyBox::ItemNameInUse => e
|
84
|
+
new_folder = folder.folders(folder_name).first
|
85
|
+
end
|
86
|
+
end
|
87
|
+
folder = new_folder
|
88
|
+
end
|
89
|
+
folder
|
90
|
+
end
|
91
|
+
|
92
|
+
def upload_data(path, data, overwrite=true)
|
93
|
+
path = split_path(path)
|
94
|
+
file_name = path.pop
|
95
|
+
folder = create_folder(path.join('/'))
|
96
|
+
folder.upload_file(file_name, data, overwrite) if folder
|
97
|
+
end
|
98
|
+
|
99
|
+
def upload_file(local_path, remote_path, overwrite=true)
|
100
|
+
folder = create_folder( remote_path )
|
101
|
+
upload_file_to_folder(local_path, folder, overwrite)
|
102
|
+
end
|
103
|
+
|
104
|
+
def upload_file_by_folder_id(local_path, folder_id, overwrite=true)
|
105
|
+
folder = folder_by_id(folder_id)
|
106
|
+
upload_file_to_folder(local_path, folder, overwrite)
|
107
|
+
end
|
108
|
+
|
109
|
+
def split_path(path)
|
110
|
+
path.gsub!(/(^\/)|(\/$)/, '')
|
111
|
+
path.split('/')
|
112
|
+
end
|
113
|
+
|
114
|
+
def event_response(stream_position=0, stream_type=:all, limit=100)
|
115
|
+
q = fmt_events_args stream_position, stream_type, limit
|
116
|
+
url = "#{RubyBox::API_URL}/events?#{q}"
|
117
|
+
resp = @session.get( url )
|
118
|
+
EventResponse.new(@session, resp)
|
119
|
+
end
|
120
|
+
|
121
|
+
def me
|
122
|
+
resp = @session.get( "#{RubyBox::API_URL}/users/me" )
|
123
|
+
User.new(@session, resp)
|
124
|
+
end
|
125
|
+
|
126
|
+
def users(filter_term = "", limit = 100, offset = 0)
|
127
|
+
url = "#{RubyBox::API_URL}/users?filter_term=#{URI::encode(filter_term)}&limit=#{limit}&offset=#{offset}"
|
128
|
+
resp = @session.get( url )
|
129
|
+
resp['entries'].map do |entry|
|
130
|
+
RubyBox::Item.factory(@session, entry)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
def upload_file_to_folder(local_path, folder, overwrite)
|
137
|
+
file_name = local_path.split('/').pop
|
138
|
+
return unless folder
|
139
|
+
::File.open(local_path, 'rb') do |data|
|
140
|
+
folder.upload_file(file_name, data, overwrite)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def folder_from_split_path(path)
|
145
|
+
folder = root_folder
|
146
|
+
path.each do |folder_name|
|
147
|
+
folder = folder.folders(folder_name).first
|
148
|
+
return nil unless folder
|
149
|
+
end
|
150
|
+
folder
|
151
|
+
end
|
152
|
+
|
153
|
+
def fmt_events_args(stream_position, stream_type, limit)
|
154
|
+
unless stream_position.to_s == 'now'
|
155
|
+
stream_position = stream_position.kind_of?(Numeric) ? stream_position : 0
|
156
|
+
end
|
157
|
+
stream_type = [:all, :changes, :sync, :admin_logs].include?(stream_type) ? stream_type : :all
|
158
|
+
limit = limit.kind_of?(Fixnum) ? limit : 100
|
159
|
+
"stream_position=#{stream_position}&stream_type=#{stream_type}&limit=#{limit}"
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module RubyBox
|
2
|
+
class RubyBoxError < StandardError
|
3
|
+
attr_accessor :body, :status
|
4
|
+
|
5
|
+
def initialize(error_json, status, body)
|
6
|
+
@status = status
|
7
|
+
@body = body
|
8
|
+
@error_json = error_json
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](key)
|
12
|
+
@error_json[key]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class ObjectNotFound < StandardError; end
|
17
|
+
class AuthError < RubyBoxError; end
|
18
|
+
class RequestError < RubyBoxError; end
|
19
|
+
class ServerError < RubyBoxError; end
|
20
|
+
class ItemNameInUse < RubyBoxError; end
|
21
|
+
class UnshareableResource < StandardError; end
|
22
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module RubyBox
|
2
|
+
class File < Item
|
3
|
+
|
4
|
+
has_many :comments
|
5
|
+
|
6
|
+
def download
|
7
|
+
resp = stream.read
|
8
|
+
end
|
9
|
+
|
10
|
+
def download_url
|
11
|
+
@session.get( file_content_url )["location"]
|
12
|
+
end
|
13
|
+
|
14
|
+
def copy_to( folder_id, name=nil )
|
15
|
+
|
16
|
+
# Allow either a folder_id or a folder object
|
17
|
+
# to be passed in.
|
18
|
+
folder_id = folder_id.id if folder_id.instance_of?(RubyBox::Folder)
|
19
|
+
|
20
|
+
uri = URI.parse( "#{RubyBox::API_URL}/#{resource_name}/#{id}/copy" )
|
21
|
+
request = Net::HTTP::Post.new( uri.request_uri )
|
22
|
+
request.body = JSON.dump({
|
23
|
+
"parent" => {"id" => folder_id},
|
24
|
+
"name" => name
|
25
|
+
})
|
26
|
+
|
27
|
+
resp = @session.request(uri, request)
|
28
|
+
end
|
29
|
+
|
30
|
+
def stream( opts={} )
|
31
|
+
open(download_url, opts)
|
32
|
+
end
|
33
|
+
|
34
|
+
def upload_content( data )
|
35
|
+
url = "#{RubyBox::UPLOAD_URL}/#{resource_name}/content"
|
36
|
+
uri = URI.parse(url)
|
37
|
+
request = Net::HTTP::Post::Multipart.new(uri.path, {
|
38
|
+
"filename" => UploadIO.new(data, "application/pdf", name),
|
39
|
+
"folder_id" => parent.id
|
40
|
+
})
|
41
|
+
|
42
|
+
resp = @session.request(uri, request)
|
43
|
+
if resp['entries']
|
44
|
+
@raw_item = resp['entries'][0]
|
45
|
+
else
|
46
|
+
@raw_item = resp
|
47
|
+
end
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def update_content( data )
|
52
|
+
|
53
|
+
url = "#{RubyBox::UPLOAD_URL}/#{resource_name}/#{id}/content"
|
54
|
+
uri = URI.parse(url)
|
55
|
+
|
56
|
+
request = Net::HTTP::Post::Multipart.new(uri.path, {
|
57
|
+
"filename" => prepare_upload(data, name),
|
58
|
+
"folder_id" => parent.id
|
59
|
+
}, {"if-match" => etag })
|
60
|
+
|
61
|
+
resp = @session.request(uri, request)
|
62
|
+
if resp['entries']
|
63
|
+
@raw_item = resp['entries'][0]
|
64
|
+
else
|
65
|
+
@raw_item = resp
|
66
|
+
end
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
def create_comment(message)
|
71
|
+
RubyBox::Comment.new(@session, {
|
72
|
+
'item' => {'id' => id, 'type' => type},
|
73
|
+
'message' => message
|
74
|
+
}).create
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
def file_content_url
|
79
|
+
"#{RubyBox::API_URL}/#{resource_name}/#{id}/content"
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
def resource_name
|
84
|
+
'files'
|
85
|
+
end
|
86
|
+
|
87
|
+
def has_mini_format?
|
88
|
+
true
|
89
|
+
end
|
90
|
+
|
91
|
+
def prepare_upload(data, fname)
|
92
|
+
UploadIO.new(data, "application/pdf", fname)
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module RubyBox
|
2
|
+
class Folder < Item
|
3
|
+
|
4
|
+
has_many :discussions
|
5
|
+
has_many :collaborations
|
6
|
+
has_many_paginated :items
|
7
|
+
|
8
|
+
def files(name=nil, item_limit=100, offset=0, fields=nil)
|
9
|
+
items_by_type(RubyBox::File, name, item_limit, offset, fields)
|
10
|
+
end
|
11
|
+
|
12
|
+
def folders(name=nil, item_limit=100, offset=0, fields=nil)
|
13
|
+
items_by_type(RubyBox::Folder, name, item_limit, offset, fields)
|
14
|
+
end
|
15
|
+
|
16
|
+
def upload_file(filename, data, overwrite=true)
|
17
|
+
file = RubyBox::File.new(@session, {
|
18
|
+
'name' => filename,
|
19
|
+
'parent' => RubyBox::Folder.new(@session, {'id' => id})
|
20
|
+
})
|
21
|
+
|
22
|
+
begin
|
23
|
+
resp = file.upload_content(data) #write a new file. If there is a conflict, update the conflicted file.
|
24
|
+
rescue RubyBox::ItemNameInUse => e
|
25
|
+
|
26
|
+
# if overwrite flag is false, simply raise exception.
|
27
|
+
raise e unless overwrite
|
28
|
+
|
29
|
+
# otherwise let's attempt to overwrite the file.
|
30
|
+
data.rewind
|
31
|
+
|
32
|
+
# The Box API occasionally does not return
|
33
|
+
# context info for an ItemNameInUse exception.
|
34
|
+
# This is a workaround around:
|
35
|
+
begin
|
36
|
+
# were were given context information about this conflict?
|
37
|
+
file = RubyBox::File.new(@session, {
|
38
|
+
'id' => e['context_info']['conflicts'][0]['id']
|
39
|
+
})
|
40
|
+
rescue
|
41
|
+
# we were not given context information about this conflict.
|
42
|
+
# attempt to lookup the file.
|
43
|
+
file = files(filename, 1000).pop
|
44
|
+
end
|
45
|
+
|
46
|
+
raise e unless file # re-raise the ItemNameInUse exception.
|
47
|
+
resp = file.update_content( data )
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def create_subfolder(name)
|
52
|
+
url = "#{RubyBox::API_URL}/#{resource_name}"
|
53
|
+
uri = URI.parse(url)
|
54
|
+
request = Net::HTTP::Post.new( uri.request_uri )
|
55
|
+
request.body = JSON.dump({ "name" => name, "parent" => {"id" => id} })
|
56
|
+
resp = @session.request(uri, request)
|
57
|
+
RubyBox::Folder.new(@session, resp)
|
58
|
+
end
|
59
|
+
|
60
|
+
# see http://developers.box.com/docs/#collaborations-collaboration-object
|
61
|
+
# for a list of valid roles.
|
62
|
+
def create_collaboration(email, role=:viewer)
|
63
|
+
RubyBox::Collaboration.new(@session, {
|
64
|
+
'item' => {'id' => id, 'type' => type},
|
65
|
+
'accessible_by' => {'login' => email},
|
66
|
+
'role' => role.to_s
|
67
|
+
}).create
|
68
|
+
end
|
69
|
+
|
70
|
+
# see http://developers.box.com/docs/#folders-copy-a-folder
|
71
|
+
# for a description of the behavior
|
72
|
+
def copy_to(destination, name=nil)
|
73
|
+
parent = {'parent' => {'id' => destination.id}}
|
74
|
+
parent.merge!('name' => name) if name
|
75
|
+
|
76
|
+
RubyBox::Folder.new(@session, post(folder_method(:copy), parent))
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
def post(extra_url, body)
|
81
|
+
uri = URI.parse("#{RubyBox::API_URL}/#{extra_url}")
|
82
|
+
post = Net::HTTP::Post.new(uri.request_uri)
|
83
|
+
post.body = JSON.dump(body)
|
84
|
+
@session.request(uri, post)
|
85
|
+
end
|
86
|
+
|
87
|
+
def resource_name
|
88
|
+
'folders'
|
89
|
+
end
|
90
|
+
|
91
|
+
def folder_method(method)
|
92
|
+
"folders/#{id}/#{method}"
|
93
|
+
end
|
94
|
+
|
95
|
+
def has_mini_format?
|
96
|
+
true
|
97
|
+
end
|
98
|
+
|
99
|
+
def items_by_type(type, name, item_limit, offset, fields)
|
100
|
+
|
101
|
+
# allow paramters to be set via
|
102
|
+
# a hash rather than a list of arguments.
|
103
|
+
if name.is_a?(Hash)
|
104
|
+
return items_by_type(type, name[:name], name[:item_limit], name[:offset], name[:fields])
|
105
|
+
end
|
106
|
+
|
107
|
+
items(item_limit, offset, fields).select do |item|
|
108
|
+
item.kind_of? type and (name.nil? or item.name.casecmp(name) == 0)
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|