dbox 0.6.13 → 0.6.14

Sign up to get free protection for your applications and to get access to all the features.
@@ -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