iora-ruby-box 1.14.0

Sign up to get free protection for your applications and to get access to all the features.
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,19 @@
1
+ module RubyBox
2
+ class Collaboration < Item
3
+
4
+ private
5
+
6
+ def resource_name
7
+ 'collaborations'
8
+ end
9
+
10
+ def update_fields
11
+ ['role', 'status']
12
+ end
13
+
14
+ def has_mini_format?
15
+ true
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ module RubyBox
2
+ class Comment < Item
3
+
4
+ private
5
+
6
+ def resource_name
7
+ 'comments'
8
+ end
9
+
10
+ def has_mini_format?
11
+ true
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ module RubyBox
2
+ class Discussion < Item
3
+ has_many :comments
4
+
5
+ private
6
+
7
+ def resource_name
8
+ 'discussions'
9
+ end
10
+
11
+ def has_mini_format?
12
+ true
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,4 @@
1
+ module RubyBox
2
+ class Event < Item
3
+ end
4
+ end
@@ -0,0 +1,17 @@
1
+ module RubyBox
2
+ class EventResponse < Item
3
+
4
+ def events
5
+ @events ||= entries.collect {|ev|
6
+ RubyBox::Event.new(@session, ev)
7
+ }
8
+ end
9
+
10
+ private
11
+
12
+ def resource_name
13
+ 'events'
14
+ end
15
+
16
+ end
17
+ 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