dropbox 0.0.10 → 1.0.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.
@@ -0,0 +1,96 @@
1
+ # Defines the Dropbox::Entry class.
2
+
3
+ nil # doc fix
4
+
5
+ module Dropbox
6
+
7
+ # A façade over a Dropbox::Session that allows the programmer to interact with
8
+ # Dropbox files in an object-oriented manner. The Dropbox::Entry instance is
9
+ # created by calling the Dropbox::API#entry method:
10
+ #
11
+ # file = session.file('remote/file.pdf')
12
+ # dir = session.directory('remote/dir') # these calls are actually identical
13
+ #
14
+ # Note that no network calls are made; this merely creates a façade that will
15
+ # delegate future calls to the session:
16
+ #
17
+ # file.move('new/path') # identical to calling session.move('remote/file.pdf', 'new/path')
18
+ #
19
+ # The internal path is updated as the file is moved and renamed:
20
+ #
21
+ # file = session.file('first_name.txt')
22
+ # file.rename('second_name.txt')
23
+ # file.rename('third_name.txt') # works as the internal path is updated with the first rename
24
+
25
+ class Entry
26
+ # The remote path of the file.
27
+ attr_reader :path
28
+
29
+ def initialize(session, path) # :nodoc:
30
+ @session = session
31
+ @path = path
32
+ end
33
+
34
+ # Delegates to Dropbox::API#metadata.
35
+
36
+ def metadata(options={})
37
+ @session.metadata path, options
38
+ end
39
+ alias :info :metadata
40
+
41
+ # Delegates to Dropbox::API#list
42
+
43
+ def list(options={})
44
+ @session.list path, options
45
+ end
46
+ alias :ls :list
47
+
48
+ # Delegates to Dropbox::API#move.
49
+
50
+ def move(dest, options={})
51
+ result = @session.move(path, dest, options)
52
+ @path = result.path.gsub(/^\//, '')
53
+ return result
54
+ end
55
+ alias :mv :move
56
+
57
+ # Delegates to Dropbox::API#rename.
58
+
59
+ def rename(name, options={})
60
+ result = @session.rename(path, name, options)
61
+ @path = result.path.gsub(/^\//, '')
62
+ return result
63
+ end
64
+
65
+ # Delegates to Dropbox::API#copy.
66
+
67
+ def copy(dest, options={})
68
+ @session.copy path, dest, options
69
+ end
70
+ alias :cp :copy
71
+
72
+ # Delegates to Dropbox::API#delete.
73
+
74
+ def delete(options={})
75
+ @session.delete path, options
76
+ end
77
+ alias :rm :delete
78
+
79
+ # Delegates to Dropbox::API#download.
80
+
81
+ def download(options={})
82
+ @session.download path, options
83
+ end
84
+ alias :body :download
85
+
86
+ # Delegates to Dropbox::API#link.
87
+
88
+ def link(options={})
89
+ @session.link path, options
90
+ end
91
+
92
+ def inspect # :nodoc:
93
+ "#<#{self.class.to_s} #{path}>"
94
+ end
95
+ end
96
+ end
@@ -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