rdropbox 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +24 -0
- data/LICENSE +20 -0
- data/README.rdoc +84 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/dropbox.gemspec +87 -0
- data/lib/dropbox/api.rb +530 -0
- data/lib/dropbox/entry.rb +96 -0
- data/lib/dropbox/event.rb +109 -0
- data/lib/dropbox/memoization.rb +98 -0
- data/lib/dropbox/revision.rb +197 -0
- data/lib/dropbox/session.rb +160 -0
- data/lib/dropbox.rb +43 -0
- data/lib/extensions/array.rb +9 -0
- data/lib/extensions/hash.rb +61 -0
- data/lib/extensions/module.rb +22 -0
- data/lib/extensions/object.rb +5 -0
- data/lib/extensions/string.rb +9 -0
- data/lib/extensions/to_bool.rb +17 -0
- data/spec/dropbox/api_spec.rb +778 -0
- data/spec/dropbox/entry_spec.rb +144 -0
- data/spec/dropbox/event_spec.rb +122 -0
- data/spec/dropbox/revision_spec.rb +367 -0
- data/spec/dropbox/session_spec.rb +148 -0
- data/spec/dropbox_spec.rb +57 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +10 -0
- metadata +150 -0
@@ -0,0 +1,109 @@
|
|
1
|
+
# Defines the Dropbox::Event class.
|
2
|
+
|
3
|
+
nil # doc fix
|
4
|
+
|
5
|
+
module Dropbox
|
6
|
+
|
7
|
+
# The Dropbox::Event class stores information about which entries were
|
8
|
+
# modified during a pingback event. You initialize this class from the JSON
|
9
|
+
# string given to you by Dropbox during a pingback:
|
10
|
+
#
|
11
|
+
# event = Dropbox::Event.new(params[:target_events])
|
12
|
+
#
|
13
|
+
# Once this is complete, the Dropbox::Event instance contains references for
|
14
|
+
# each of the entries, with the basic information included in the pingback:
|
15
|
+
#
|
16
|
+
# event.user_ids #=> [ 1, 2, 3 ]
|
17
|
+
# event.entries(1).first #=> #<Dropbox::Revision 1:10:100>
|
18
|
+
#
|
19
|
+
# For any of these entries, you can load its content and metadata:
|
20
|
+
#
|
21
|
+
# event.entries(1).first.load(dropbox_session)
|
22
|
+
# event.entries(1).first.content #=> "Content of file..."
|
23
|
+
# event.entries(1).first.size #=> 2245
|
24
|
+
#
|
25
|
+
# You can also load only the metadata for all of a user's entries:
|
26
|
+
#
|
27
|
+
# event.load_metadata(first_users_dropbox_session)
|
28
|
+
# event.entries(1).first.size #=> 154365
|
29
|
+
|
30
|
+
class Event
|
31
|
+
def initialize(json_pingback) # :nodoc:
|
32
|
+
@json_pingback = json_pingback
|
33
|
+
begin
|
34
|
+
@metadata = JSON.parse(json_pingback).stringify_keys_recursively
|
35
|
+
rescue JSON::ParserError
|
36
|
+
raise Dropbox::ParseError, "Invalid pingback event data"
|
37
|
+
end
|
38
|
+
|
39
|
+
process_pingback
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns an array of Dropbox user ID's involved in this pingback.
|
43
|
+
|
44
|
+
def user_ids
|
45
|
+
@entries_by_user_id.keys
|
46
|
+
end
|
47
|
+
|
48
|
+
# When given no arguments, returns an array of all entries (as
|
49
|
+
# Dropbox::Revision instances). When given a user ID, filters the list
|
50
|
+
# to only entries belonging to that Dropbox user.
|
51
|
+
|
52
|
+
def entries(user_id=nil)
|
53
|
+
user_id ? (@entries_by_user_id[user_id.to_i] || []).dup : @entries.dup
|
54
|
+
end
|
55
|
+
|
56
|
+
# Loads the metadata for all entries belonging to a given Dropbox session.
|
57
|
+
# Does not load data for files that do not belong to the user owning the
|
58
|
+
# given Dropbox session.
|
59
|
+
#
|
60
|
+
# Future calls to this method will result in additional network requests,
|
61
|
+
# though the Dropbox::Revision instances do cache their metadata values.
|
62
|
+
#
|
63
|
+
# Options:
|
64
|
+
#
|
65
|
+
# +mode+:: Temporarily changes the API mode. See the Dropbox::API::MODES
|
66
|
+
# array.
|
67
|
+
|
68
|
+
def load_metadata(session, options={})
|
69
|
+
process_metadata session.event_metadata(@json_pingback, options).stringify_keys_recursively
|
70
|
+
end
|
71
|
+
|
72
|
+
def inspect # :nodoc:
|
73
|
+
"#<#{self.class.to_s} (#{@entries.size} entries)>"
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def process_pingback
|
79
|
+
@entries = Array.new
|
80
|
+
@entries_by_user_id = Hash.new
|
81
|
+
@entries_hashed = Hash.new
|
82
|
+
|
83
|
+
@metadata.each do |user_id, namespaces|
|
84
|
+
@entries_hashed[user_id.to_i] = Hash.new
|
85
|
+
@entries_by_user_id[user_id.to_i] = Array.new
|
86
|
+
namespaces.each do |namespace_id, journals|
|
87
|
+
@entries_hashed[user_id.to_i][namespace_id.to_i] = Hash.new
|
88
|
+
journals.each do |journal_id|
|
89
|
+
entry = Dropbox::Revision.new(user_id.to_i, namespace_id.to_i, journal_id.to_i)
|
90
|
+
@entries << entry
|
91
|
+
@entries_by_user_id[user_id.to_i] << entry
|
92
|
+
@entries_hashed[user_id.to_i][namespace_id.to_i][journal_id.to_i] = entry
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def process_metadata(metadata)
|
99
|
+
p metadata
|
100
|
+
metadata.each do |user_id, namespaces|
|
101
|
+
namespaces.each do |namespace_id, journals|
|
102
|
+
journals.each do |journal_id, attributes|
|
103
|
+
@entries_hashed[user_id.to_i][namespace_id.to_i][journal_id.to_i].process_metadata(attributes.symbolize_keys)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# Defines the Dropbox::Memoization module.
|
2
|
+
|
3
|
+
nil # doc fix
|
4
|
+
|
5
|
+
module Dropbox
|
6
|
+
|
7
|
+
# Adds methods to the Dropbox::Session class to support the temporary local
|
8
|
+
# storage of API results to reduce the number of network calls and simplify
|
9
|
+
# code.
|
10
|
+
#
|
11
|
+
# Memoization is <b>opt-in</b>; you must explicitly indicate that you want
|
12
|
+
# this functionality by calling the enable_memoization method on your
|
13
|
+
# Scribd::Session instance. Once memoization is enabled, subsequent calls to
|
14
|
+
# memoized methods will hit an in-memory cache as opposed to making identical
|
15
|
+
# network calls.
|
16
|
+
#
|
17
|
+
# If you would like to use your own caching strategy (for instance, your own
|
18
|
+
# memcache instance), set the +cache_proc+ and +cache_clear_proc+ attributes.
|
19
|
+
#
|
20
|
+
# Enabling memoization makes removes an instance's thread-safety.
|
21
|
+
#
|
22
|
+
# Example:
|
23
|
+
#
|
24
|
+
# session.metadata('file1') # network
|
25
|
+
#
|
26
|
+
# session.enable_memoization
|
27
|
+
# session.metadata('file1') # network
|
28
|
+
# session.metadata('file1') # cache
|
29
|
+
#
|
30
|
+
# session.metadata('file2') # network
|
31
|
+
# session.metadata('file2') # cache
|
32
|
+
#
|
33
|
+
# session.disable_memoization
|
34
|
+
# session.metadata('file2') # network
|
35
|
+
|
36
|
+
module Memoization
|
37
|
+
def self.included(base) # :nodoc:
|
38
|
+
base.extend ClassMethods
|
39
|
+
end
|
40
|
+
|
41
|
+
# The cache_proc is a proc with two arguments, the cache identifier and the
|
42
|
+
# proc to call and store in the event of a cache miss:
|
43
|
+
#
|
44
|
+
# instance.cache_proc = Proc.new do |identifier, calculate_proc|
|
45
|
+
# Rails.cache.fetch(identifier) { calculate_proc.call }
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# The Cache identifier will always be 64 lowercase hexadecimal characters.
|
49
|
+
# The second argument is a curried proc including all arguments to the
|
50
|
+
# original method call.
|
51
|
+
|
52
|
+
def cache_proc=(prc)
|
53
|
+
@_memo_cache_proc = prc
|
54
|
+
end
|
55
|
+
|
56
|
+
# The cache_clear_proc takes an identifier and should invalidate it from the
|
57
|
+
# cache:
|
58
|
+
#
|
59
|
+
# instance.cache_clear_proc = Proc.new { |identifier| Rails.cache.delete identifier }
|
60
|
+
|
61
|
+
def cache_clear_proc=(prc)
|
62
|
+
@_memo_cache_clear_proc = prc
|
63
|
+
end
|
64
|
+
|
65
|
+
# Begins memoizing the results of API calls. Memoization is off by default
|
66
|
+
# for new instances.
|
67
|
+
|
68
|
+
def enable_memoization
|
69
|
+
@_memoize = true
|
70
|
+
@_memo_identifiers ||= Set.new
|
71
|
+
end
|
72
|
+
|
73
|
+
# Halts memoization of API calls and clears the memoization cache.
|
74
|
+
|
75
|
+
def disable_memoization
|
76
|
+
@_memoize = false
|
77
|
+
@_memo_identifiers.each { |identifier| (@_memo_cache_clear_proc || Proc.new { |ident| eval "@_memo_#{ident} = nil" }).call(identifier) }
|
78
|
+
@_memo_identifiers.clear
|
79
|
+
end
|
80
|
+
|
81
|
+
module ClassMethods # :nodoc:
|
82
|
+
def memoize(*method_names) # :nodoc:
|
83
|
+
method_names.each do |meth|
|
84
|
+
define_method :"#{meth}_with_memo" do |*args|
|
85
|
+
if @_memoize then
|
86
|
+
identifier = Digest::SHA1.hexdigest(meth.to_s + ":" + args.to_yaml)
|
87
|
+
@_memo_identifiers << identifier
|
88
|
+
(@_memo_cache_proc || Proc.new { |ident, calculate_proc| eval "@_memo_#{ident} ||= calculate_proc.call" }).call identifier, Proc.new { send :"#{meth}_without_memo", *args }
|
89
|
+
else
|
90
|
+
send :"#{meth}_without_memo", *args
|
91
|
+
end
|
92
|
+
end
|
93
|
+
alias_method_chain meth, :memo
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
# Defines the Dropbox::Revision class.
|
2
|
+
|
3
|
+
nil # doc fix
|
4
|
+
|
5
|
+
module Dropbox
|
6
|
+
|
7
|
+
# A file or folder at a point in time as referenced by a Dropbox::Event
|
8
|
+
# pingback event. Instances start out as "shells" only storing enough
|
9
|
+
# information to uniquely identify a file/folder belonging to a user at a
|
10
|
+
# certain revision.
|
11
|
+
#
|
12
|
+
# Instances of this class only appear in a Dropbox::Event object. To load the
|
13
|
+
# metadata for a revision, use the Dropbox::Event#load_metadata method.
|
14
|
+
# To load the content of the file at this revision, use the load_content
|
15
|
+
# method on this class.
|
16
|
+
#
|
17
|
+
# Once the metadata has been loaded, you can access it directly:
|
18
|
+
#
|
19
|
+
# revision.size #=> 2962
|
20
|
+
#
|
21
|
+
# The +mtime+ attribute will be a Time instance. The +mtime+ and +size+
|
22
|
+
# attributes will be +nil+ (not -1) if the file was deleted. All other
|
23
|
+
# attributes are as defined in
|
24
|
+
# http://developers.getdropbox.com/base.html#event-metadata
|
25
|
+
#
|
26
|
+
# If the metadata could not be read for whatever reason, the HTTP error code
|
27
|
+
# will be stored in the +error+ attribute.
|
28
|
+
|
29
|
+
class Revision
|
30
|
+
# The ID of the Dropbox user that owns this file.
|
31
|
+
attr_reader :user_id
|
32
|
+
# The namespace ID of the file (Dropbox internal).
|
33
|
+
attr_reader :namespace_id
|
34
|
+
# The journal ID of the file (Dropbox internal).
|
35
|
+
attr_reader :journal_id
|
36
|
+
# The HTTP error code received when trying to load metadata, or +nil+ if no
|
37
|
+
# error has yet been received.
|
38
|
+
attr_reader :error
|
39
|
+
|
40
|
+
def initialize(uid, nid, jid) # :nodoc:
|
41
|
+
@user_id = uid
|
42
|
+
@namespace_id = nid
|
43
|
+
@journal_id = jid
|
44
|
+
end
|
45
|
+
|
46
|
+
# The unique identifier string used by some Dropbox event API methods.
|
47
|
+
|
48
|
+
def identifier
|
49
|
+
"#{user_id}:#{namespace_id}:#{journal_id}"
|
50
|
+
end
|
51
|
+
|
52
|
+
# Uses the given Dropbox::Session to load the content and metadata for a
|
53
|
+
# file at a specific revision.
|
54
|
+
#
|
55
|
+
# Options:
|
56
|
+
#
|
57
|
+
# +mode+:: Temporarily changes the API mode. See the Dropbox::API::MODES
|
58
|
+
# array.
|
59
|
+
|
60
|
+
def load(session, options={})
|
61
|
+
@content, @metadata = session.event_content(identifier, options)
|
62
|
+
@metadata.symbolize_keys!
|
63
|
+
|
64
|
+
postprocess_metadata
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns true if the content for this revision has been previously loaded
|
68
|
+
# and is cached in this object.
|
69
|
+
|
70
|
+
def content_loaded?
|
71
|
+
@content.to_bool
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns true if the metadata for this revision has been previously loaded
|
75
|
+
# and is cached in this object.
|
76
|
+
|
77
|
+
def metadata_loaded?
|
78
|
+
@metadata.to_bool
|
79
|
+
end
|
80
|
+
|
81
|
+
# Sugar for the +latest+ attribute.
|
82
|
+
|
83
|
+
def latest?
|
84
|
+
raise NotLoadedError.new(:metadata) unless metadata_loaded?
|
85
|
+
self.latest
|
86
|
+
end
|
87
|
+
|
88
|
+
# Sugar for the +is_dir+ attribute.
|
89
|
+
|
90
|
+
def directory?
|
91
|
+
raise NotLoadedError.new(:metadata) unless metadata_loaded?
|
92
|
+
self.is_dir
|
93
|
+
end
|
94
|
+
|
95
|
+
# Synonym for the +mtime+ attribute, for "duck" compatibility with the
|
96
|
+
# Dropbox +metadata+ API.
|
97
|
+
|
98
|
+
def modified
|
99
|
+
raise NotLoadedError.new(:metadata) unless metadata_loaded?
|
100
|
+
self.mtime
|
101
|
+
end
|
102
|
+
|
103
|
+
# Returns true if an error occurred when trying to load metadata.
|
104
|
+
|
105
|
+
def error?
|
106
|
+
error.to_bool
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns true if this change represents the file being deleted.
|
110
|
+
|
111
|
+
def deleted?
|
112
|
+
raise NotLoadedError.new(:metadata) unless metadata_loaded?
|
113
|
+
self.mtime.nil? and self.size.nil?
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns the contents of the file as a string. Returns nil for directories.
|
117
|
+
# You must call load first to retrieve the content from the network.
|
118
|
+
|
119
|
+
def content
|
120
|
+
raise NotLoadedError.new(:content) unless content_loaded?
|
121
|
+
@content
|
122
|
+
end
|
123
|
+
|
124
|
+
def inspect # :nodoc:
|
125
|
+
"#<#{self.class.to_s} #{identifier}>"
|
126
|
+
end
|
127
|
+
|
128
|
+
# Allows you to access metadata attributes directly:
|
129
|
+
#
|
130
|
+
# revision.size #=> 10526
|
131
|
+
#
|
132
|
+
# A NoMethodError will be raised if the metadata has not yet been loaded for
|
133
|
+
# this revision, so be sure to call metadata_loaded? beforehand.
|
134
|
+
|
135
|
+
def method_missing(meth, *args)
|
136
|
+
if args.empty? then
|
137
|
+
if @metadata and @metadata.include?(meth) then
|
138
|
+
return @metadata[meth]
|
139
|
+
else
|
140
|
+
super
|
141
|
+
end
|
142
|
+
else
|
143
|
+
super
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Loads the metadata for the latest revision of the entry and returns it as
|
148
|
+
# as <tt>Struct</tt> object. Uses the given session and calls
|
149
|
+
# Dropbox::API.metadata.
|
150
|
+
#
|
151
|
+
# If the metadata for this object has not yet been loaded, raises an error.
|
152
|
+
# Options are passed to Dropbox::API.metadata.
|
153
|
+
|
154
|
+
def metadata_for_latest_revision(session, options={})
|
155
|
+
raise NotLoadedError.new(:metadata) unless metadata_loaded?
|
156
|
+
session.metadata self.path, options
|
157
|
+
end
|
158
|
+
|
159
|
+
def process_metadata(metadata) # :nodoc:
|
160
|
+
if metadata[:error] then
|
161
|
+
@error = metadata[:error] unless @metadata
|
162
|
+
return
|
163
|
+
end
|
164
|
+
|
165
|
+
@error = nil
|
166
|
+
@metadata = Hash.new
|
167
|
+
metadata.each { |key, value| @metadata[key.to_sym] = value }
|
168
|
+
|
169
|
+
postprocess_metadata
|
170
|
+
end
|
171
|
+
|
172
|
+
private
|
173
|
+
|
174
|
+
def postprocess_metadata
|
175
|
+
@metadata[:size] = nil if @metadata[:size] == -1
|
176
|
+
@metadata[:mtime] = (@metadata[:mtime] == -1 ? nil : Time.at(@metadata[:mtime])) if @metadata[:mtime]
|
177
|
+
@metadata[:ts] = Time.parse(@metadata[:ts]) if @metadata[:ts]
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Raised when trying to access content metadata before it has been loaded.
|
182
|
+
|
183
|
+
class NotLoadedError < StandardError
|
184
|
+
|
185
|
+
# What data did you attempt to access before it was loaded? Either
|
186
|
+
# <tt>:content</tt> or <tt>:metadata</tt>.
|
187
|
+
attr_reader :data
|
188
|
+
|
189
|
+
def initialize(data) # :nodoc:
|
190
|
+
@data = data
|
191
|
+
end
|
192
|
+
|
193
|
+
def to_s # :nodoc:
|
194
|
+
"#{data.capitalize} not yet loaded -- call #load on the Dropbox::Revision instance beforehand"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# Defines the Dropbox::Session class.
|
2
|
+
|
3
|
+
require 'oauth'
|
4
|
+
|
5
|
+
module Dropbox
|
6
|
+
|
7
|
+
# This class is a portal to the Dropbox API and a façade over the Ruby OAuth
|
8
|
+
# gem allowing developers to authenticate their user's Dropbox accounts.
|
9
|
+
#
|
10
|
+
# == Authenticating a user
|
11
|
+
#
|
12
|
+
# You start by creating a new instance and providing your OAuth consumer key
|
13
|
+
# and secret. You then call the authorize_url method on your new instance to
|
14
|
+
# receive the authorization URL.
|
15
|
+
#
|
16
|
+
# Once your user visits the URL, it will complete the authorization process on
|
17
|
+
# the server side. You should call the authorize method:
|
18
|
+
#
|
19
|
+
# session = Dropbox::Session.new(my_key, my_secret)
|
20
|
+
# puts "Now visit #{session.authorize_url}. Hit enter when you have completed authorization."
|
21
|
+
# gets
|
22
|
+
# session.authorize
|
23
|
+
#
|
24
|
+
# The authorize method must be called on the same instance of Dropbox::Session
|
25
|
+
# that gave you the URL. If this is unfeasible (for instance, you are doing
|
26
|
+
# this in a stateless Rails application), you can serialize the Session for
|
27
|
+
# storage (e.g., in your Rails session):
|
28
|
+
#
|
29
|
+
# def authorize
|
30
|
+
# if params[:oauth_token] then
|
31
|
+
# dropbox_session = Dropbox::Session.deserialize(session[:dropbox_session])
|
32
|
+
# dropbox_session.authorize(params)
|
33
|
+
# session[:dropbox_session] = dropbox_session.serialize # re-serialize the authenticated session
|
34
|
+
#
|
35
|
+
# redirect_to :action => 'upload'
|
36
|
+
# else
|
37
|
+
# dropbox_session = Dropbox::Session.new('your_consumer_key', 'your_consumer_secret')
|
38
|
+
# session[:dropbox_session] = dropbox_session.serialize
|
39
|
+
# redirect_to dropbox_session.authorize_url(:oauth_callback => root_url)
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# == Working with the API
|
44
|
+
#
|
45
|
+
# This class includes the methods of the Dropbox::API module. See that module
|
46
|
+
# to learn how to continue using the API.
|
47
|
+
|
48
|
+
class Session
|
49
|
+
include API
|
50
|
+
|
51
|
+
# Begins the authorization process. Provide the OAuth key and secret of your
|
52
|
+
# API account, assigned by Dropbox. This is the first step in the
|
53
|
+
# authorization process.
|
54
|
+
#
|
55
|
+
# Options:
|
56
|
+
#
|
57
|
+
# +ssl+:: If true, uses SSL to connect to the Dropbox API server.
|
58
|
+
|
59
|
+
def initialize(oauth_key, oauth_secret, options={})
|
60
|
+
@ssl = options[:ssl].to_bool
|
61
|
+
@consumer = OAuth::Consumer.new(oauth_key, oauth_secret,
|
62
|
+
:site => (@ssl ? Dropbox::SSL_HOST : Dropbox::HOST),
|
63
|
+
:request_token_path => "/#{Dropbox::VERSION}/oauth/request_token",
|
64
|
+
:authorize_path => "/#{Dropbox::VERSION}/oauth/authorize",
|
65
|
+
:access_token_path => "/#{Dropbox::VERSION}/oauth/access_token")
|
66
|
+
@request_token = @consumer.get_request_token
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns a URL that is used to complete the authorization process. Visiting
|
70
|
+
# this URL is the second step in the authorization process, after creating
|
71
|
+
# the Session instance.
|
72
|
+
|
73
|
+
def authorize_url(*args)
|
74
|
+
if authorized? then
|
75
|
+
raise AlreadyAuthorizedError, "You have already been authorized; no need to get an authorization URL."
|
76
|
+
else
|
77
|
+
return @request_token.authorize_url(*args)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Authorizes a user from the information returned by Dropbox. This is the
|
82
|
+
# third step in the authorization process, after sending the user to the
|
83
|
+
# authorize_url.
|
84
|
+
#
|
85
|
+
# You can pass to this method a hash containing the keys and values of the
|
86
|
+
# OAuth parameters returned by Dropbox. An example in Rails:
|
87
|
+
#
|
88
|
+
# session.authorize :oauth_verifier => params[:oauth_verifier]
|
89
|
+
#
|
90
|
+
# Returns a boolean indicating if authentication was successful.
|
91
|
+
|
92
|
+
def authorize(options={})
|
93
|
+
@access_token = @request_token.get_access_token(options)
|
94
|
+
@request_token = nil if @access_token
|
95
|
+
return @access_token.to_bool
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns true if this session has been authorized.
|
99
|
+
|
100
|
+
def authorized?
|
101
|
+
@access_token.to_bool
|
102
|
+
end
|
103
|
+
|
104
|
+
# Serializes this object into a string that can then be recreated with the
|
105
|
+
# Dropbox::Session.deserialize method.
|
106
|
+
|
107
|
+
def serialize
|
108
|
+
if authorized? then
|
109
|
+
[ @consumer.key, @consumer.secret, authorized?, @access_token.token, @access_token.secret ].to_yaml
|
110
|
+
else
|
111
|
+
[ @consumer.key, @consumer.secret, authorized?, @request_token.token, @request_token.secret ].to_yaml
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Deserializes an instance from a string created from the serialize method.
|
116
|
+
# Returns the recreated instance.
|
117
|
+
|
118
|
+
def self.deserialize(data)
|
119
|
+
consumer_key, consumer_secret, authorized, token, token_secret = YAML.load(StringIO.new(data))
|
120
|
+
raise ArgumentError, "Must provide a properly serialized #{self.to_s} instance" unless [ consumer_key, consumer_secret, token, token_secret ].all? and authorized == true or authorized == false
|
121
|
+
|
122
|
+
session = self.new(consumer_key, consumer_secret)
|
123
|
+
if authorized then
|
124
|
+
session.instance_variable_set :@access_token, OAuth::AccessToken.new(session.instance_variable_get(:@consumer), token, token_secret)
|
125
|
+
else
|
126
|
+
session.instance_variable_set :@request_token, OAuth::RequestToken.new(session.instance_variable_get(:@consumer), token, token_secret)
|
127
|
+
end
|
128
|
+
|
129
|
+
return session
|
130
|
+
end
|
131
|
+
|
132
|
+
def inspect # :nodoc:
|
133
|
+
"#<#{self.class.to_s} #{@consumer.key} (#{'un' unless authorized?}authorized)>"
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
def access_token
|
139
|
+
@access_token || raise(UnauthorizedError, "You need to authorize the Dropbox user before you can call API methods")
|
140
|
+
end
|
141
|
+
|
142
|
+
def clone_with_host(host)
|
143
|
+
session = dup
|
144
|
+
consumer = OAuth::Consumer.new(@consumer.key, @consumer.secret, :site => host)
|
145
|
+
session.instance_variable_set :@consumer, consumer
|
146
|
+
session.instance_variable_set :@access_token, OAuth::AccessToken.new(consumer, @access_token.token, @access_token.secret)
|
147
|
+
return session
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Raised when trying to call Dropbox API methods without yet having completed
|
152
|
+
# the OAuth process.
|
153
|
+
|
154
|
+
class UnauthorizedError < StandardError; end
|
155
|
+
|
156
|
+
# Raised when trying to call Dropbox::Session#authorize_url on an already
|
157
|
+
# authorized session.
|
158
|
+
|
159
|
+
class AlreadyAuthorizedError < StandardError; end
|
160
|
+
end
|
data/lib/dropbox.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# Defines the Dropbox module.
|
2
|
+
|
3
|
+
require 'cgi'
|
4
|
+
require 'yaml'
|
5
|
+
require 'digest/sha1'
|
6
|
+
require 'thread'
|
7
|
+
require 'set'
|
8
|
+
require 'time'
|
9
|
+
require 'tempfile'
|
10
|
+
|
11
|
+
Dir.glob("#{File.expand_path File.dirname(__FILE__)}/extensions/*.rb") { |file| require file }
|
12
|
+
Dir.glob("#{File.expand_path File.dirname(__FILE__)}/dropbox/*.rb") { |file| require file }
|
13
|
+
|
14
|
+
# Container module for the all Dropbox API classes.
|
15
|
+
|
16
|
+
module Dropbox
|
17
|
+
# The API version this client works with.
|
18
|
+
VERSION = "0"
|
19
|
+
# The host serving API requests.
|
20
|
+
HOST = "http://api.dropbox.com"
|
21
|
+
# The SSL host serving API requests.
|
22
|
+
SSL_HOST = "https://api.dropbox.com"
|
23
|
+
# Alternate hosts for other API requests.
|
24
|
+
ALTERNATE_HOSTS = { 'event_content' => 'http://api-content.dropbox.com', 'files' => "http://api-content.dropbox.com" }
|
25
|
+
# Alternate SSL hosts for other API requests.
|
26
|
+
ALTERNATE_SSL_HOSTS = { 'event_content' => 'https://api-content.dropbox.com', 'files' => "https://api-content.dropbox.com" }
|
27
|
+
|
28
|
+
def self.api_url(*paths_and_options) # :nodoc:
|
29
|
+
params = paths_and_options.extract_options!
|
30
|
+
ssl = params.delete(:ssl)
|
31
|
+
host = (ssl ? ALTERNATE_SSL_HOSTS[paths_and_options.first] : ALTERNATE_HOSTS[paths_and_options.first]) || (ssl ? SSL_HOST : HOST)
|
32
|
+
url = "#{host}/#{VERSION}/#{paths_and_options.map { |path_elem| CGI.escape path_elem.to_s }.join('/')}"
|
33
|
+
url.gsub! '+', '%20' # dropbox doesn't really like plusses
|
34
|
+
url << "?#{params.map { |k,v| CGI.escape(k.to_s) + "=" + CGI.escape(v.to_s) }.join('&')}" unless params.empty?
|
35
|
+
return url
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.check_path(path) # :nodoc:
|
39
|
+
raise ArgumentError, "Backslashes are not allowed in Dropbox paths" if path.include?('\\')
|
40
|
+
raise ArgumentError, "Dropbox paths are limited to 256 characters in length" if path.size > 256
|
41
|
+
return path
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class Hash # :nodoc:
|
2
|
+
def slice(*keys) #:nodoc:
|
3
|
+
keys = keys.map! { |key| convert_key(key) } if respond_to?(:convert_key)
|
4
|
+
hash = self.class.new
|
5
|
+
keys.each { |k| hash[k] = self[k] if has_key?(k) }
|
6
|
+
hash
|
7
|
+
end unless method_defined?(:slice)
|
8
|
+
|
9
|
+
def symbolize_keys # :nodoc:
|
10
|
+
inject({}) do |options, (key, value)|
|
11
|
+
options[(key.to_sym rescue key) || key] = value
|
12
|
+
options
|
13
|
+
end
|
14
|
+
end unless method_defined?(:symbolize_keys)
|
15
|
+
|
16
|
+
def symbolize_keys! # :nodoc:
|
17
|
+
self.replace(self.symbolize_keys)
|
18
|
+
end unless method_defined?(:symbolize_keys!)
|
19
|
+
|
20
|
+
def symbolize_keys_recursively # :nodoc:
|
21
|
+
hsh = symbolize_keys
|
22
|
+
hsh.each { |k, v| hsh[k] = v.symbolize_keys_recursively if v.kind_of?(Hash) }
|
23
|
+
hsh.each { |k, v| hsh[k] = v.map { |i| i.kind_of?(Hash) ? i.symbolize_keys_recursively : i } if v.kind_of?(Array) }
|
24
|
+
return hsh
|
25
|
+
end
|
26
|
+
|
27
|
+
def stringify_keys # :nodoc:
|
28
|
+
inject({}) do |options, (key, value)|
|
29
|
+
options[(key.to_s rescue key) || key] = value
|
30
|
+
options
|
31
|
+
end
|
32
|
+
end unless method_defined?(:stringify_keys)
|
33
|
+
|
34
|
+
def stringify_keys! # :nodoc:
|
35
|
+
self.replace(self.stringify_keys)
|
36
|
+
end unless method_defined?(:stringify_keys!)
|
37
|
+
|
38
|
+
def stringify_keys_recursively # :nodoc:
|
39
|
+
hsh = stringify_keys
|
40
|
+
hsh.each { |k, v| hsh[k] = v.stringify_keys_recursively if v.kind_of?(Hash) }
|
41
|
+
hsh.each { |k, v| hsh[k] = v.map { |i| i.kind_of?(Hash) ? i.stringify_keys_recursively : i } if v.kind_of?(Array) }
|
42
|
+
return hsh
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_struct # :nodoc:
|
46
|
+
struct = Struct.new(*keys).new(*values)
|
47
|
+
# attach methods for any predicate keys, since Struct.new doesn't seem to do that
|
48
|
+
pred_keys = slice(*(keys.select { |key| key.to_s.ends_with?('?') }))
|
49
|
+
pred_keys.each do |key, val|
|
50
|
+
struct.eigenclass.send(:define_method, key.to_sym) { return val }
|
51
|
+
end
|
52
|
+
return struct
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_struct_recursively # :nodoc:
|
56
|
+
hsh = dup
|
57
|
+
hsh.each { |k, v| hsh[k] = v.to_struct_recursively if v.kind_of?(Hash) }
|
58
|
+
hsh.each { |k, v| hsh[k] = v.map { |i| i.kind_of?(Hash) ? i.to_struct_recursively : i } if v.kind_of?(Array) }
|
59
|
+
return hsh.to_struct
|
60
|
+
end
|
61
|
+
end
|