Skip to content

Commit

Permalink
Merge remote branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
Lucas Martin-King committed Oct 11, 2010
2 parents 9048bbb + cb0ef36 commit 28b88d6
Show file tree
Hide file tree
Showing 14 changed files with 219 additions and 52 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ pkg
.rvmrc

## PROJECT::SPECIFIC
keys.json
12 changes: 12 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
1.1.2 (2010-10-6)

* Added ability to perform OAuth authorization on behalf of the user.
* Specs now test authorization against actual Dropbox API server.

1.1.1 (2010-09-22)

* http-proxy support added.
* Session now persists SSL attribute.
* Core extensions moved to a namespaced path to prevent conflicts.
* Minor bug fixes.

1.1.0 (2010-05-27)

* Added thumbnails API method.
Expand Down
10 changes: 10 additions & 0 deletions README.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@ account, and then upload a file to their Dropbox.
* If you're using pingbacks, check out Dropbox::Event and Dropbox::Revision.
Those classes parse pingbacks from Dropbox into Ruby objects.

== Testing Your Code

The gem is fully specced. Run specs with +rake spec+. Before doing so, you will
need to create a file called +keys.json+ in the project root containing your
Dropbox API key and secret, as well as the email and password for a Dropbox
account. See the +keys.json.example+ file to get started.

fguillen has implemented a mock of the Dropbox API server:
http://github.com/fguillen/DummyDropbox

== Note on Patches/Pull Requests

* Fork the project.
Expand Down
1 change: 1 addition & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ begin
gem.add_runtime_dependency "oauth", ">= 0.3.6"
gem.add_runtime_dependency "json", ">= 1.2.0"
gem.add_runtime_dependency "multipart-post", ">= 1.0"
gem.add_runtime_dependency "mechanize", ">= 1.0.0"
end
Jeweler::GemcutterTasks.new
Jeweler::RubyforgeTasks.new
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.1.0
1.1.2
8 changes: 6 additions & 2 deletions dropbox.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

Gem::Specification.new do |s|
s.name = %q{dropbox}
s.version = "1.1.0"
s.version = "1.1.2"

s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Tim Morgan"]
s.date = %q{2010-09-03}
s.date = %q{2010-10-06}
s.description = %q{An easy-to-use client library for the official Dropbox API.}
s.email = %q{dropbox@timothymorgan.info}
s.extra_rdoc_files = [
Expand All @@ -26,6 +26,7 @@ Gem::Specification.new do |s|
"Rakefile",
"VERSION",
"dropbox.gemspec",
"keys.json.example",
"lib/dropbox.rb",
"lib/dropbox/api.rb",
"lib/dropbox/entry.rb",
Expand Down Expand Up @@ -72,17 +73,20 @@ Gem::Specification.new do |s|
s.add_runtime_dependency(%q<oauth>, [">= 0.3.6"])
s.add_runtime_dependency(%q<json>, [">= 1.2.0"])
s.add_runtime_dependency(%q<multipart-post>, [">= 1.0"])
s.add_runtime_dependency(%q<mechanize>, [">= 1.0.0"])
else
s.add_dependency(%q<rspec>, [">= 1.2.9"])
s.add_dependency(%q<oauth>, [">= 0.3.6"])
s.add_dependency(%q<json>, [">= 1.2.0"])
s.add_dependency(%q<multipart-post>, [">= 1.0"])
s.add_dependency(%q<mechanize>, [">= 1.0.0"])
end
else
s.add_dependency(%q<rspec>, [">= 1.2.9"])
s.add_dependency(%q<oauth>, [">= 0.3.6"])
s.add_dependency(%q<json>, [">= 1.2.0"])
s.add_dependency(%q<multipart-post>, [">= 1.0"])
s.add_dependency(%q<mechanize>, [">= 1.0.0"])
end
end

6 changes: 6 additions & 0 deletions keys.json.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"key": "abc123456789012",
"secret": "abc123456789012",
"email": "my@email.com",
"password": "mypassword123"
}
24 changes: 12 additions & 12 deletions lib/dropbox/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def account
# +mode+:: Temporarily changes the API mode. See the MODES array.

def download(path, options={})
path.sub! /^\//, ''
path = path.sub(/^\//, '')
rest = Dropbox.check_path(path).split('/')
rest << { :ssl => @ssl }
api_body :get, 'files', root(options), *rest
Expand Down Expand Up @@ -123,7 +123,7 @@ def thumbnail(*args)
size = args.shift
raise ArgumentError, "thumbnail takes a path, an optional size, and optional options" unless path.kind_of?(String) and (size.kind_of?(String) or size.nil?) and args.empty?

path.sub! /^\//, ''
path = path.sub(/^\//, '')
rest = Dropbox.check_path(path).split('/')
rest << { :ssl => @ssl }
rest.last[:size] = size if size
Expand Down Expand Up @@ -167,7 +167,7 @@ def upload(local_file, remote_path, options={})
raise ArgumentError, "local_file must be a File or file path"
end

remote_path.sub! /^\//, ''
remote_path = remote_path.sub(/^\//, '')
remote_path = Dropbox.check_path(remote_path).split('/')

remote_path << { :ssl => @ssl }
Expand Down Expand Up @@ -217,8 +217,8 @@ def upload(local_file, remote_path, options={})
# TODO The API documentation says this method returns 404/403 if the source or target is invalid, but it actually returns 5xx.

def copy(source, target, options={})
source.sub! /^\//, ''
target.sub! /^\//, ''
source = source.sub(/^\//, '')
target = target.sub(/^\//, '')
target << File.basename(source) if target.ends_with?('/')
begin
parse_metadata(post('fileops', 'copy', :from_path => Dropbox.check_path(source), :to_path => Dropbox.check_path(target), :root => root(options), :ssl => @ssl)).to_struct_recursively
Expand All @@ -243,7 +243,7 @@ def copy(source, target, options={})
# TODO The API documentation says this method returns 403 if the path already exists, but it actually appends " (1)" to the end of the name and returns 200.

def create_folder(path, options={})
path.sub! /^\//, ''
path = path.sub(/^\//, '')
path.sub! /\/$/, ''
begin
parse_metadata(post('fileops', 'create_folder', :path => Dropbox.check_path(path), :root => root(options), :ssl => @ssl)).to_struct_recursively
Expand All @@ -266,7 +266,7 @@ def create_folder(path, options={})
# TODO The API documentation says this method returns 404 if the path does not exist, but it actually returns 5xx.

def delete(path, options={})
path.sub! /^\//, ''
path = path.sub(/^\//, '')
path.sub! /\/$/, ''
begin
api_response(:post, 'fileops', 'delete', :path => Dropbox.check_path(path), :root => root(options), :ssl => @ssl)
Expand Down Expand Up @@ -296,8 +296,8 @@ def delete(path, options={})
# TODO The API documentation says this method returns 404/403 if the source or target is invalid, but it actually returns 5xx.

def move(source, target, options={})
source.sub! /^\//, ''
target.sub! /^\//, ''
source = source.sub(/^\//, '')
target = target.sub(/^\//, '')
target << File.basename(source) if target.ends_with?('/')
begin
parse_metadata(post('fileops', 'move', :from_path => Dropbox.check_path(source), :to_path => Dropbox.check_path(target), :root => root(options), :ssl => @ssl)).to_struct_recursively
Expand All @@ -322,7 +322,7 @@ def move(source, target, options={})

def rename(path, new_name, options={})
raise ArgumentError, "Names cannot have slashes in them" if new_name.include?('/')
path.sub! /\/$/, ''
path = path.sub(/\/$/, '')
destination = path.split('/')
destination[destination.size - 1] = new_name
destination = destination.join('/')
Expand All @@ -339,7 +339,7 @@ def rename(path, new_name, options={})
# +mode+:: Temporarily changes the API mode. See the MODES array.

def link(path, options={})
path.sub! /^\//, ''
path = path.sub(/^\//, '')
begin
rest = Dropbox.check_path(path).split('/')
rest << { :ssl => @ssl }
Expand Down Expand Up @@ -377,7 +377,7 @@ def link(path, options={})
# TODO hash option seems to return HTTPBadRequest for now

def metadata(path, options={})
path.sub! /^\//, ''
path = path.sub(/^\//, '')
args = [
'metadata',
root(options)
Expand Down
8 changes: 4 additions & 4 deletions lib/dropbox/extensions/hash.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def symbolize_keys_recursively # :nodoc:
hsh.each { |k, v| hsh[k] = v.symbolize_keys_recursively if v.kind_of?(Hash) }
hsh.each { |k, v| hsh[k] = v.map { |i| i.kind_of?(Hash) ? i.symbolize_keys_recursively : i } if v.kind_of?(Array) }
return hsh
end
end unless method_defined?(:symbolize_keys_recursively)

def stringify_keys # :nodoc:
inject({}) do |options, (key, value)|
Expand All @@ -40,7 +40,7 @@ def stringify_keys_recursively # :nodoc:
hsh.each { |k, v| hsh[k] = v.stringify_keys_recursively if v.kind_of?(Hash) }
hsh.each { |k, v| hsh[k] = v.map { |i| i.kind_of?(Hash) ? i.stringify_keys_recursively : i } if v.kind_of?(Array) }
return hsh
end
end unless method_defined?(:stringify_keys_recursively)

def to_struct # :nodoc:
struct = Struct.new(*keys).new(*values)
Expand All @@ -50,12 +50,12 @@ def to_struct # :nodoc:
struct.eigenclass.send(:define_method, key.to_sym) { return val }
end
return struct
end
end unless method_defined?(:to_struct)

def to_struct_recursively # :nodoc:
hsh = dup
hsh.each { |k, v| hsh[k] = v.to_struct_recursively if v.kind_of?(Hash) }
hsh.each { |k, v| hsh[k] = v.map { |i| i.kind_of?(Hash) ? i.to_struct_recursively : i } if v.kind_of?(Array) }
return hsh.to_struct
end
end unless method_defined?(:to_struct_recursively)
end
2 changes: 1 addition & 1 deletion lib/dropbox/extensions/object.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class Object # :nodoc:
def eigenclass # :nodoc:
(class << self; self; end)
end
end unless method_defined?(:eigenclass)
end
16 changes: 8 additions & 8 deletions lib/dropbox/extensions/to_bool.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
class Object # :nodoc:
def to_bool # :nodoc:
true
end
end

class FalseClass # :nodoc:
def to_bool # :nodoc:
false
end
end unless method_defined?(:to_bool)
end

class NilClass # :nodoc:
def to_bool # :nodoc:
false
end
end unless method_defined?(:to_bool)
end

class Object # :nodoc:
def to_bool # :nodoc:
true
end unless method_defined?(:to_bool)
end
48 changes: 46 additions & 2 deletions lib/dropbox/session.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Defines the Dropbox::Session class.

require 'oauth'
require 'mechanize'

module Dropbox

Expand Down Expand Up @@ -47,6 +48,11 @@ module Dropbox

class Session
include API

# The email of a Dropbox account to automatically authorize.
attr_accessor :authorizing_user
# The password of a Dropbox account to automatically authorize.
attr_accessor :authorizing_password

# Begins the authorization process. Provide the OAuth key and secret of your
# API account, assigned by Dropbox. This is the first step in the
Expand All @@ -55,11 +61,23 @@ class Session
# Options:
#
# +ssl+:: If true, uses SSL to connect to the Dropbox API server.
# +authorizing_user+:: The email of a Dropbox account to automatically
# authorize.
# +authorizing_password+:: The password of a Dropbox account to
# automatically authorize.

def initialize(oauth_key, oauth_secret, options={})
@ssl = options[:ssl].to_bool

# necessary for the automatic authorization in #authorize!
@authorizing_user = options[:authorizing_user] if options[:authorizing_user]
@authorizing_password = options[:authorizing_password] if options[:authorizing_password]

@proxy = options[:proxy] || ENV["HTTP_PROXY"] || ENV["http_proxy"]
@proxy = nil if options[:noproxy].to_bool
@consumer = OAuth::Consumer.new(oauth_key, oauth_secret,
:site => (@ssl ? Dropbox::SSL_HOST : Dropbox::HOST),
:proxy => @proxy,
:request_token_path => "/#{Dropbox::VERSION}/oauth/request_token",
:authorize_path => "/#{Dropbox::VERSION}/oauth/authorize",
:access_token_path => "/#{Dropbox::VERSION}/oauth/access_token")
Expand Down Expand Up @@ -100,6 +118,32 @@ def authorize(options={})
def authorized?
@access_token.to_bool
end

# Automatically complete the authorization step of the OAuth process. You
# must have provided the +authorizing_user+ and +authorizing_password+
# options. Raises a Dropbox::UnauthorizedError on failure.

def authorize!
begin
a = Mechanize.new
a.get(authorize_url) do |page|
login_form = page.form_with(:action => '/login')

login_form.login_email = @authorizing_user
login_form.login_password = @authorizing_password
auth_page = login_form.submit()

auth_form = auth_page.form_with(:action => 'authorize')
if auth_form
auth_button = auth_form.button_with(:value => "Allow")
auth_form.click_button
end
end
rescue OAuth::Unauthorized => e
raise Dropbox::UnauthorizedError
end
authorize
end

# Serializes this object into a string that can then be recreated with the
# Dropbox::Session.deserialize method.
Expand All @@ -111,7 +155,7 @@ def serialize
[ @consumer.key, @consumer.secret, authorized?, @request_token.token, @request_token.secret, @ssl ].to_yaml
end
end

# Deserializes an instance from a string created from the serialize method.
# Returns the recreated instance.

Expand All @@ -125,7 +169,7 @@ def self.deserialize(data)
else
session.instance_variable_set :@request_token, OAuth::RequestToken.new(session.instance_variable_get(:@consumer), token, token_secret)
end

return session
end

Expand Down
Loading

0 comments on commit 28b88d6

Please sign in to comment.