dbox 0.6.13 → 0.6.14

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.
@@ -1,3 +1,8 @@
1
+ == 0.6.14 / 2012-07-26
2
+ * Minor Enhancements
3
+ * Made concurrency adjustable via DROPBOX_CONCURRENCY environment variable.
4
+ * Updated to latest Dropbox Ruby SDK.
5
+
1
6
  == 0.6.13 / 2012-06-04
2
7
  * Bug Fixes
3
8
  * Fixed issue with dbox delete command-line util.
data/README.md CHANGED
@@ -263,3 +263,12 @@ $ export DROPBOX_AUTH_SECRET=pqej9rmnj0i1gcxr4
263
263
  > File.read("#{ENV['HOME']}/Dropbox/Public/hello.txt")
264
264
  => "Oh, Hello"
265
265
  ```
266
+
267
+ Advanced
268
+ --------
269
+
270
+ To speed up your syncs, you can manually set the number concurrent dropbox operations to execute. (The default is 2.)
271
+
272
+ ```sh
273
+ $ export DROPBOX_CONCURRENCY=5
274
+ ```
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.13
1
+ 0.6.14
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "dbox"
8
- s.version = "0.6.13"
8
+ s.version = "0.6.14"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Ken Pratt"]
12
- s.date = "2012-06-04"
12
+ s.date = "2012-07-26"
13
13
  s.description = "An easy-to-use Dropbox client with fine-grained control over syncs."
14
14
  s.email = "ken@kenpratt.net"
15
15
  s.executables = ["dbox"]
@@ -38,15 +38,19 @@ Gem::Specification.new do |s|
38
38
  "sample_polling_script.rb",
39
39
  "spec/dbox_spec.rb",
40
40
  "spec/spec_helper.rb",
41
- "vendor/dropbox-ruby-sdk/CHANGELOG",
42
- "vendor/dropbox-ruby-sdk/LICENSE",
43
- "vendor/dropbox-ruby-sdk/README",
44
- "vendor/dropbox-ruby-sdk/cli_example.rb",
45
- "vendor/dropbox-ruby-sdk/dropbox_controller.rb",
46
- "vendor/dropbox-ruby-sdk/gemspec.rb",
47
- "vendor/dropbox-ruby-sdk/lib/dropbox_sdk.rb",
48
- "vendor/dropbox-ruby-sdk/lib/trusted-certs.crt",
49
- "vendor/dropbox-ruby-sdk/web_file_browser.rb"
41
+ "vendor/dropbox-ruby-sdk-1.3.1/.search_cache.rb.swp",
42
+ "vendor/dropbox-ruby-sdk-1.3.1/CHANGELOG",
43
+ "vendor/dropbox-ruby-sdk-1.3.1/LICENSE",
44
+ "vendor/dropbox-ruby-sdk-1.3.1/README",
45
+ "vendor/dropbox-ruby-sdk-1.3.1/cli_example.rb",
46
+ "vendor/dropbox-ruby-sdk-1.3.1/copy_between_accounts.rb",
47
+ "vendor/dropbox-ruby-sdk-1.3.1/dropbox_controller.rb",
48
+ "vendor/dropbox-ruby-sdk-1.3.1/gemspec.rb",
49
+ "vendor/dropbox-ruby-sdk-1.3.1/lib/dropbox_sdk.rb",
50
+ "vendor/dropbox-ruby-sdk-1.3.1/lib/trusted-certs.crt",
51
+ "vendor/dropbox-ruby-sdk-1.3.1/search_cache.json",
52
+ "vendor/dropbox-ruby-sdk-1.3.1/search_cache.rb",
53
+ "vendor/dropbox-ruby-sdk-1.3.1/web_file_browser.rb"
50
54
  ]
51
55
  s.homepage = "http://github.com/kenpratt/dbox"
52
56
  s.licenses = ["MIT"]
@@ -1,6 +1,6 @@
1
1
  module Dbox
2
2
  class Syncer
3
- MAX_PARALLEL_DBOX_OPS = 3
3
+ DEFAULT_CONCURRENCY = 2
4
4
  MIN_BYTES_TO_STREAM_DOWNLOAD = 1024 * 100 # 100kB
5
5
 
6
6
  include Loggable
@@ -36,6 +36,11 @@ module Dbox
36
36
  @@_api ||= API.connect
37
37
  end
38
38
 
39
+ def self.concurrency
40
+ n = ENV["DROPBOX_CONCURRENCY"].to_i
41
+ n > 0 ? n : DEFAULT_CONCURRENCY
42
+ end
43
+
39
44
  class Operation
40
45
  include Loggable
41
46
  include Utils
@@ -184,7 +189,7 @@ module Dbox
184
189
  changelist = { :created => [], :deleted => [], :updated => [], :failed => [] }
185
190
 
186
191
  # spin up a parallel task queue
187
- ptasks = ParallelTasks.new(MAX_PARALLEL_DBOX_OPS - 1) { clone_api_into_current_thread() }
192
+ ptasks = ParallelTasks.new(Syncer.concurrency) { clone_api_into_current_thread() }
188
193
  ptasks.start
189
194
 
190
195
  changes.each do |op, c|
@@ -445,7 +450,7 @@ module Dbox
445
450
  changelist = { :created => [], :deleted => [], :updated => [], :failed => [] }
446
451
 
447
452
  # spin up a parallel task queue
448
- ptasks = ParallelTasks.new(MAX_PARALLEL_DBOX_OPS - 1) { clone_api_into_current_thread() }
453
+ ptasks = ParallelTasks.new(Syncer.concurrency) { clone_api_into_current_thread() }
449
454
  ptasks.start
450
455
 
451
456
  changes.each do |op, c|
@@ -1,3 +1,11 @@
1
+ 1.3.1 (2012-5-16)
2
+ * Increase metadata() file list limit to 25,000 (used to be 10,000).
3
+ * Use CGI.escape() instead of the deprecated URI.escape().
4
+
5
+ 1.3 (2012-3-26)
6
+ * Add support for the /delta API.
7
+ * Add support for the "copy ref" API.
8
+
1
9
  1.2 (2012-1-11)
2
10
  * Adds a method to the SDK that returns the file metadata when downloading a
3
11
  file or its thumbnail.
@@ -0,0 +1,155 @@
1
+ require './lib/dropbox_sdk'
2
+ require 'json'
3
+
4
+ # You must use your Dropbox App key and secret to use the API.
5
+ # Find this at https://www.dropbox.com/developers
6
+ APP_KEY = ''
7
+ APP_SECRET = ''
8
+ ACCESS_TYPE = :app_folder #The two valid values here are :app_folder and :dropbox
9
+ #The default is :app_folder, but your application might be
10
+ #set to have full :dropbox access. Check your app at
11
+ #https://www.dropbox.com/developers/apps
12
+
13
+ STATE_FILE = 'copy_between_accounts.json'
14
+
15
+ def main()
16
+ prog_name = __FILE__
17
+ if APP_KEY == '' or APP_SECRET == ''
18
+ warn "ERROR: Set your APP_KEY and APP_SECRET at the top of #{prog_name}"
19
+ exit
20
+ end
21
+ args = ARGV
22
+ if args.size == 0
23
+ warn("Usage:\n")
24
+ warn(" #{prog_name} link Link to a user's account. Also displays UID.")
25
+ warn(" #{prog_name} list List linked users including UID.")
26
+ warn(" #{prog_name} copy '<uid>:<path>' '<uid>:<path>' Copies a file from the first user's path, to the second user's path.")
27
+ warn("\n\n <uid> is the account UID shown when linked. <path> is a path to a file on that user's dropbox.")
28
+ exit
29
+ end
30
+
31
+ command = args[0]
32
+ if command == 'link'
33
+ command_link(args)
34
+ elsif command == 'list'
35
+ command_list(args)
36
+ elsif command == 'copy'
37
+ command_copy(args)
38
+ else
39
+ warn "ERROR: Unknown command: #{command}"
40
+ warn "Run with no arguments for help."
41
+ exit(1)
42
+ end
43
+ end
44
+
45
+ def command_link(args)
46
+ if args.size != 1
47
+ warn "ERROR: \"link\" doesn't take any arguments"
48
+ exit
49
+ end
50
+
51
+ sess = DropboxSession.new(APP_KEY, APP_SECRET)
52
+ sess.get_request_token
53
+
54
+ # Make the user log in and authorize this token
55
+ url = sess.get_authorize_url
56
+ puts "1. Go to: #{url}"
57
+ puts "2. Authorize this app."
58
+ puts "After you're done, press ENTER."
59
+ STDIN.gets
60
+
61
+ # This will fail if the user didn't visit the above URL and hit 'Allow'
62
+ sess.get_access_token
63
+ access_token = sess.access_token
64
+ c = DropboxClient.new(sess, ACCESS_TYPE)
65
+ account_info = c.account_info()
66
+
67
+ puts "Link successful. #{account_info['display_name']} is uid #{account_info['uid']} "
68
+
69
+ state = load_state()
70
+ state[account_info['uid']] = {
71
+ 'access_token' => [access_token.key, access_token.secret],
72
+ 'display_name' => account_info['display_name'],
73
+ }
74
+
75
+ save_state(state)
76
+ end
77
+
78
+ def command_list(args)
79
+ if args.size != 1
80
+ warn "ERROR: \"list\" doesn't take any arguments"
81
+ exit
82
+ end
83
+
84
+ state = load_state()
85
+ for e in state.keys()
86
+ puts "#{state[e]['display_name']} is uid #{e}"
87
+ end
88
+ end
89
+
90
+ def command_copy(args)
91
+ if args.size != 3
92
+ warn "ERROR: \"copy\" takes exactly two arguments"
93
+ exit
94
+ end
95
+
96
+ state = load_state()
97
+
98
+ if state.keys().length < 2
99
+ warn "ERROR: You can't use the copy command until at least two users have linked"
100
+ exit
101
+ end
102
+
103
+ from = args[1].gsub(/['"]/,'')
104
+ to = args[2].gsub(/['"]/,'')
105
+
106
+ if not to.index(':') or not from.index(':')
107
+ warn "ERROR: Ill-formated paths. Run #{prog_name} without arugments to see documentation."
108
+ exit
109
+ end
110
+
111
+ from_uid, from_path = from.split ":"
112
+ to_uid, to_path = to.split ":"
113
+
114
+ if not state.has_key?(to_uid) or not state.has_key?(from_uid)
115
+ warn "ERROR: Those UIDs have not linked. Run #{prog_name} list to see linked UIDs."
116
+ exit
117
+ end
118
+
119
+ from_token = state[from_uid]['access_token']
120
+ to_token = state[to_uid]['access_token']
121
+
122
+ from_session = DropboxSession.new(APP_KEY, APP_SECRET)
123
+ to_session = DropboxSession.new(APP_KEY, APP_SECRET)
124
+
125
+ from_session.set_access_token(*from_token)
126
+ to_session.set_access_token(*to_token)
127
+
128
+ from_client = DropboxClient.new(from_session, ACCESS_TYPE)
129
+ to_client = DropboxClient.new(to_session, ACCESS_TYPE)
130
+
131
+ #Create a copy ref under the identity of the from user
132
+ copy_ref = from_client.create_copy_ref(from_path)['copy_ref']
133
+
134
+ metadata = to_client.add_copy_ref(to_path, copy_ref)
135
+
136
+ puts "File successly copied from #{state[from_uid]['display_name']} to #{state[to_uid]['display_name']}!"
137
+ puts "The file now exists at #{metadata['path']}"
138
+
139
+ end
140
+
141
+ def save_state(state)
142
+ File.open(STATE_FILE,"w") do |f|
143
+ f.write(JSON.pretty_generate(state))
144
+ end
145
+ end
146
+
147
+ def load_state()
148
+ if not FileTest.exists?(STATE_FILE)
149
+ return {}
150
+ end
151
+ JSON.parse(File.read(STATE_FILE))
152
+ end
153
+
154
+
155
+ main()
@@ -2,7 +2,7 @@
2
2
  Gem::Specification.new do |s|
3
3
  s.name = "dropbox-sdk"
4
4
 
5
- s.version = "1.2"
5
+ s.version = "1.3.1"
6
6
  s.license = 'MIT'
7
7
 
8
8
  s.authors = ["Dropbox, Inc."]
@@ -20,6 +20,6 @@ Gem::Specification.new do |s|
20
20
  s.files = [
21
21
  "CHANGELOG", "LICENSE", "README",
22
22
  "cli_example.rb", "dropbox_controller.rb", "web_file_browser.rb",
23
- "lib/dropbox_sdk.rb", "data/trusted-certs.crt",
23
+ "lib/dropbox_sdk.rb", "lib/trusted-certs.crt",
24
24
  ]
25
25
  end
@@ -5,13 +5,13 @@ require 'cgi'
5
5
  require 'json'
6
6
  require 'yaml'
7
7
 
8
- module Dropbox
8
+ module Dropbox # :nodoc:
9
9
  API_SERVER = "api.dropbox.com"
10
10
  API_CONTENT_SERVER = "api-content.dropbox.com"
11
11
  WEB_SERVER = "www.dropbox.com"
12
12
 
13
13
  API_VERSION = 1
14
- SDK_VERSION = "1.2"
14
+ SDK_VERSION = "1.3.1"
15
15
 
16
16
  TRUSTED_CERT_FILE = File.join(File.dirname(__FILE__), 'trusted-certs.crt')
17
17
  end
@@ -125,6 +125,7 @@ class DropboxSession
125
125
  raise DropboxAuthError.new("#{error_message_prefix} Server returned #{response.code}: #{response.message}.", response)
126
126
  end
127
127
  parts = CGI.parse(response.body)
128
+
128
129
  if !parts.has_key? "oauth_token" and parts["oauth_token"].length != 1
129
130
  raise DropboxAuthError.new("Invalid response from #{url_end}: missing \"oauth_token\" parameter: #{response.body}", response)
130
131
  end
@@ -244,7 +245,7 @@ end
244
245
 
245
246
 
246
247
  # A class that represents either an OAuth request token or an OAuth access token.
247
- class OAuthToken
248
+ class OAuthToken # :nodoc:
248
249
  def initialize(key, secret)
249
250
  @key = key
250
251
  @secret = secret
@@ -339,14 +340,14 @@ class DropboxClient
339
340
  begin
340
341
  return JSON.parse(response.body)
341
342
  rescue JSON::ParserError
342
- raise DropboxError.new("Unable to parse JSON response", response)
343
+ raise DropboxError.new("Unable to parse JSON response: #{response.body}", response)
343
344
  end
344
345
  end
345
346
 
346
347
  # Returns account info in a Hash object
347
348
  #
348
349
  # For a detailed description of what this call returns, visit:
349
- # https://www.dropbox.com/developers/docs#account-info
350
+ # https://www.dropbox.com/developers/reference/api#account-info
350
351
  def account_info()
351
352
  response = @session.do_get build_url("/account/info")
352
353
  parse_response(response)
@@ -478,7 +479,7 @@ class DropboxClient
478
479
  # Returns:
479
480
  # * A hash with the metadata of the new copy of the file or folder.
480
481
  # For a detailed description of what this call returns, visit:
481
- # https://www.dropbox.com/developers/docs#fileops-copy
482
+ # https://www.dropbox.com/developers/reference/api#fileops-copy
482
483
  def file_copy(from_path, to_path)
483
484
  params = {
484
485
  "root" => @root,
@@ -497,7 +498,7 @@ class DropboxClient
497
498
  # Returns:
498
499
  # * A hash with the metadata of the newly created folder.
499
500
  # For a detailed description of what this call returns, visit:
500
- # https://www.dropbox.com/developers/docs#fileops-create-folder
501
+ # https://www.dropbox.com/developers/reference/api#fileops-create-folder
501
502
  def file_create_folder(path)
502
503
  params = {
503
504
  "root" => @root,
@@ -516,7 +517,7 @@ class DropboxClient
516
517
  # Returns:
517
518
  # * A Hash with the metadata of file just deleted.
518
519
  # For a detailed description of what this call returns, visit:
519
- # https://www.dropbox.com/developers/docs#fileops-delete
520
+ # https://www.dropbox.com/developers/reference/api#fileops-delete
520
521
  def file_delete(path)
521
522
  params = {
522
523
  "root" => @root,
@@ -536,7 +537,7 @@ class DropboxClient
536
537
  # Returns:
537
538
  # * A Hash with the metadata of file or folder just moved.
538
539
  # For a detailed description of what this call returns, visit:
539
- # https://www.dropbox.com/developers/docs#fileops-delete
540
+ # https://www.dropbox.com/developers/reference/api#fileops-delete
540
541
  def file_move(from_path, to_path)
541
542
  params = {
542
543
  "root" => @root,
@@ -556,23 +557,29 @@ class DropboxClient
556
557
  # * file_limit: The maximum number of file entries to return within
557
558
  # a folder. If the number of files in the directory exceeds this
558
559
  # limit, an exception is raised. The server will return at max
559
- # 10,000 files within a folder.
560
+ # 25,000 files within a folder.
560
561
  # * hash: Every directory listing has a hash parameter attached that
561
562
  # can then be passed back into this function later to save on
562
563
  # bandwidth. Rather than returning an unchanged folder's contents, if
563
564
  # the hash matches a DropboxNotModified exception is raised.
565
+ # * rev: Optional. The revision of the file to retrieve the metadata for.
566
+ # This parameter only applies for files. If omitted, you'll receive
567
+ # the most recent revision metadata.
568
+ # * include_deleted: Specifies whether to include deleted files in metadata results.
564
569
  #
565
570
  # Returns:
566
571
  # * A Hash object with the metadata of the file or folder (and contained files if
567
572
  # appropriate). For a detailed description of what this call returns, visit:
568
- # https://www.dropbox.com/developers/docs#metadata
569
- def metadata(path, file_limit=10000, list=true, hash=nil)
573
+ # https://www.dropbox.com/developers/reference/api#metadata
574
+ def metadata(path, file_limit=25000, list=true, hash=nil, rev=nil, include_deleted=false)
570
575
  params = {
571
576
  "file_limit" => file_limit.to_s,
572
- "list" => list.to_s
577
+ "list" => list.to_s,
578
+ "include_deleted" => include_deleted.to_s
573
579
  }
574
580
 
575
581
  params["hash"] = hash if hash
582
+ params["rev"] = rev if rev
576
583
 
577
584
  response = @session.do_get build_url("/metadata/#{@root}#{format_path(path)}", params=params)
578
585
  if response.kind_of? Net::HTTPRedirection
@@ -594,7 +601,7 @@ class DropboxClient
594
601
  # Returns:
595
602
  # * A Hash object with a list the metadata of the file or folders matching query
596
603
  # inside path. For a detailed description of what this call returns, visit:
597
- # https://www.dropbox.com/developers/docs#search
604
+ # https://www.dropbox.com/developers/reference/api#search
598
605
  def search(path, query, file_limit=1000, include_deleted=false)
599
606
  params = {
600
607
  'query' => query,
@@ -618,7 +625,7 @@ class DropboxClient
618
625
  # * A Hash object with a list of the metadata of the all the revisions of
619
626
  # all matches files (up to rev_limit entries)
620
627
  # For a detailed description of what this call returns, visit:
621
- # https://www.dropbox.com/developers/docs#revisions
628
+ # https://www.dropbox.com/developers/reference/api#revisions
622
629
  def revisions(path, rev_limit=1000)
623
630
 
624
631
  params = {
@@ -639,7 +646,7 @@ class DropboxClient
639
646
  # Returns:
640
647
  # * A Hash object with a list the metadata of the file or folders restored
641
648
  # For a detailed description of what this call returns, visit:
642
- # https://www.dropbox.com/developers/docs#search
649
+ # https://www.dropbox.com/developers/reference/api#search
643
650
  def restore(path, rev)
644
651
  params = {
645
652
  'rev' => rev.to_s
@@ -679,7 +686,7 @@ class DropboxClient
679
686
  # * A Hash object that looks like the following example:
680
687
  # {'url': 'http://www.dropbox.com/s/m/a2mbDa2', 'expires': 'Thu, 16 Sep 2011 01:01:25 +0000'}
681
688
  # For a detailed description of what this call returns, visit:
682
- # https://www.dropbox.com/developers/docs#share
689
+ # https://www.dropbox.com/developers/reference/api#shares
683
690
  def shares(path)
684
691
  response = @session.do_get build_url("/shares/#{@root}#{format_path(path)}")
685
692
  parse_response(response)
@@ -692,7 +699,7 @@ class DropboxClient
692
699
  # * size: A string describing the desired thumbnail size. At this time,
693
700
  # 'small', 'medium', and 'large' are officially supported sizes
694
701
  # (32x32, 64x64, and 128x128 respectively), though others may
695
- # be available. Check https://www.dropbox.com/developers/docs#thumbnails
702
+ # be available. Check https://www.dropbox.com/developers/reference/api#thumbnails
696
703
  # for more details. [defaults to large]
697
704
  # Returns:
698
705
  # * The thumbnail data
@@ -717,6 +724,60 @@ class DropboxClient
717
724
  return parsed_response, metadata
718
725
  end
719
726
 
727
+ # A way of letting you keep a local representation of the Dropbox folder
728
+ # heirarchy. You can periodically call delta() to get a list of "delta
729
+ # entries", which are instructions on how to update your local state to
730
+ # match the server's state.
731
+ #
732
+ # Arguments:
733
+ # * +cursor+: On the first call, omit this argument (or pass in +nil+). On
734
+ # subsequent calls, pass in the +cursor+ string returned by the previous
735
+ # call.
736
+ #
737
+ # Returns: A hash with three fields.
738
+ # * +entries+: A list of "delta entries" (described below)
739
+ # * +reset+: If +true+, you should reset local state to be an empty folder
740
+ # before processing the list of delta entries. This is only +true+ only
741
+ # in rare situations.
742
+ # * +cursor+: A string that is used to keep track of your current state.
743
+ # On the next call to delta(), pass in this value to return entries
744
+ # that were recorded since the cursor was returned.
745
+ # * +has_more+: If +true+, then there are more entries available; you can
746
+ # call delta() again immediately to retrieve those entries. If +false+,
747
+ # then wait at least 5 minutes (preferably longer) before checking again.
748
+ #
749
+ # Delta Entries: Each entry is a 2-item list of one of following forms:
750
+ # * [_path_, _metadata_]: Indicates that there is a file/folder at the given
751
+ # path. You should add the entry to your local state. (The _metadata_
752
+ # value is the same as what would be returned by the #metadata() call.)
753
+ # * If the path refers to parent folders that don't yet exist in your
754
+ # local state, create those parent folders in your local state. You
755
+ # will eventually get entries for those parent folders.
756
+ # * If the new entry is a file, replace whatever your local state has at
757
+ # _path_ with the new entry.
758
+ # * If the new entry is a folder, check what your local state has at
759
+ # _path_. If it's a file, replace it with the new entry. If it's a
760
+ # folder, apply the new _metadata_ to the folder, but do not modify
761
+ # the folder's children.
762
+ # * [path, +nil+]: Indicates that there is no file/folder at the _path_ on
763
+ # Dropbox. To update your local state to match, delete whatever is at
764
+ # _path_, including any children (you will sometimes also get separate
765
+ # delta entries for each child, but this is not guaranteed). If your
766
+ # local state doesn't have anything at _path_, ignore this entry.
767
+ #
768
+ # Remember: Dropbox treats file names in a case-insensitive but case-preserving
769
+ # way. To facilitate this, the _path_ strings above are lower-cased versions of
770
+ # the actual path. The _metadata_ dicts have the original, case-preserved path.
771
+ def delta(cursor=nil)
772
+ params = {}
773
+ if cursor
774
+ params['cursor'] = cursor
775
+ end
776
+
777
+ response = @session.do_post build_url("/delta", params)
778
+ parse_response(response)
779
+ end
780
+
720
781
  # Download a thumbnail (helper method - don't call this directly).
721
782
  #
722
783
  # Args:
@@ -741,6 +802,45 @@ class DropboxClient
741
802
  end
742
803
  private :thumbnail_impl
743
804
 
805
+
806
+ # Creates and returns a copy ref for a specific file. The copy ref can be
807
+ # used to instantly copy that file to the Dropbox of another account.
808
+ #
809
+ # Args:
810
+ # * path: The path to the file for a copy ref to be created on.
811
+ #
812
+ # Returns:
813
+ # * A Hash object that looks like the following example:
814
+ # {"expires"=>"Fri, 31 Jan 2042 21:01:05 +0000", "copy_ref"=>"z1X6ATl6aWtzOGq0c3g5Ng"}
815
+ def create_copy_ref(path)
816
+ path = "/copy_ref/#{@root}#{format_path(path)}"
817
+
818
+ response = @session.do_get(build_url(path, {}))
819
+
820
+ parse_response(response)
821
+ end
822
+
823
+ # Adds the file referenced by the copy ref to the specified path
824
+ #
825
+ # Args:
826
+ # * copy_ref: A copy ref string that was returned from a create_copy_ref call.
827
+ # The copy_ref can be created from any other Dropbox account, or from the same account.
828
+ # * to_path: The path to where the file will be created.
829
+ #
830
+ # Returns:
831
+ # * A hash with the metadata of the new file.
832
+ def add_copy_ref(to_path, copy_ref)
833
+ path = "/fileops/copy"
834
+
835
+ params = {'from_copy_ref' => copy_ref,
836
+ 'to_path' => "#{format_path(to_path)}",
837
+ 'root' => @root}
838
+
839
+ response = @session.do_post(build_url(path, params))
840
+
841
+ parse_response(response)
842
+ end
843
+
744
844
  def build_url(url, params=nil, content_server=false) # :nodoc:
745
845
  port = 443
746
846
  host = content_server ? Dropbox::API_CONTENT_SERVER : Dropbox::API_SERVER
@@ -756,7 +856,7 @@ class DropboxClient
756
856
 
757
857
  if params
758
858
  target.query = params.collect {|k,v|
759
- URI.escape(k) + "=" + URI.escape(v)
859
+ CGI.escape(k) + "=" + CGI.escape(v)
760
860
  }.join("&")
761
861
  end
762
862