dropbox 0.0.10 → 1.0.0

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