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/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