cloudfiles 1.4.10 → 1.4.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,56 +1,36 @@
1
1
  module CloudFiles
2
2
  class Container
3
3
  # See COPYING for license information.
4
- # Copyright (c) 2009, Rackspace US, Inc.
4
+ # Copyright (c) 2011, Rackspace US, Inc.
5
5
 
6
6
  # Name of the container which corresponds to the instantiated container class
7
7
  attr_reader :name
8
8
 
9
- # Size of the container (in bytes)
10
- attr_reader :bytes
11
-
12
- # Number of objects in the container
13
- attr_reader :count
14
-
15
- # True if container is public, false if container is private
16
- attr_reader :cdn_enabled
17
-
18
- # CDN container TTL (if container is public)
19
- attr_reader :cdn_ttl
20
-
21
- # CDN container URL (if container if public)
22
- attr_reader :cdn_url
23
-
24
9
  # The parent CloudFiles::Connection object for this container
25
10
  attr_reader :connection
26
-
27
- # The container ACL on the User Agent
28
- attr_reader :user_agent_acl
29
-
30
- # The container ACL on the site Referrer
31
- attr_reader :referrer_acl
32
11
 
33
12
  # Retrieves an existing CloudFiles::Container object tied to the current CloudFiles::Connection. If the requested
34
- # container does not exist, it will raise a NoSuchContainerException.
35
- #
13
+ # container does not exist, it will raise a CloudFiles::Exception::NoSuchContainer Exception.
14
+ #
36
15
  # Will likely not be called directly, instead use connection.container('container_name') to retrieve the object.
37
- def initialize(connection,name)
16
+ def initialize(connection, name)
38
17
  @connection = connection
39
18
  @name = name
40
19
  @storagehost = self.connection.storagehost
41
- @storagepath = self.connection.storagepath + "/" + URI.encode(@name).gsub(/&/,'%26')
20
+ @storagepath = self.connection.storagepath + "/" + CloudFiles.escape(@name)
42
21
  @storageport = self.connection.storageport
43
22
  @storagescheme = self.connection.storagescheme
44
23
  @cdnmgmthost = self.connection.cdnmgmthost
45
- @cdnmgmtpath = self.connection.cdnmgmtpath + "/" + URI.encode(@name).gsub(/&/,'%26')
24
+ @cdnmgmtpath = self.connection.cdnmgmtpath + "/" + CloudFiles.escape(@name) if self.connection.cdnmgmtpath
46
25
  @cdnmgmtport = self.connection.cdnmgmtport
47
26
  @cdnmgmtscheme = self.connection.cdnmgmtscheme
48
- populate
27
+ # Load the metadata now, so we'll get a CloudFiles::Exception::NoSuchContainer exception should the container
28
+ # not exist.
29
+ self.metadata
49
30
  end
50
31
 
51
- # Retrieves data about the container and populates class variables. It is automatically called
52
- # when the Container class is instantiated. If you need to refresh the variables, such as
53
- # size, count, cdn_enabled, cdn_ttl, and cdn_url, this method can be called again.
32
+ # Refreshes data about the container and populates class variables. Items are otherwise
33
+ # loaded in a lazy loaded fashion.
54
34
  #
55
35
  # container.count
56
36
  # => 2
@@ -60,49 +40,103 @@ module CloudFiles
60
40
  # container.populate
61
41
  # container.count
62
42
  # => 3
63
- def populate
64
- # Get the size and object count
65
- response = self.connection.cfreq("HEAD",@storagehost,@storagepath+"/",@storageport,@storagescheme)
66
- raise NoSuchContainerException, "Container #{@name} does not exist" unless (response.code =~ /^20/)
67
- @bytes = response["x-container-bytes-used"].to_i
68
- @count = response["x-container-object-count"].to_i
43
+ def refresh
44
+ @metadata = @cdn_metadata = nil
45
+ true
46
+ end
47
+ alias :populate :refresh
69
48
 
70
- # Get the CDN-related details
71
- response = self.connection.cfreq("HEAD",@cdnmgmthost,@cdnmgmtpath,@cdnmgmtport,@cdnmgmtscheme)
72
- @cdn_enabled = ((response["x-cdn-enabled"] || "").downcase == "true") ? true : false
73
- @cdn_ttl = @cdn_enabled ? response["x-ttl"].to_i : false
74
- @cdn_url = @cdn_enabled ? response["x-cdn-uri"] : false
75
- @user_agent_acl = response["x-user-agent-acl"]
76
- @referrer_acl = response["x-referrer-acl"]
77
- if @cdn_enabled
78
- @cdn_log = response["x-log-retention"] == "False" ? false : true
79
- else
80
- @cdn_log = false
81
- end
49
+ # Retrieves Metadata for the container
50
+ def metadata
51
+ @metadata ||= (
52
+ response = self.connection.cfreq("HEAD", @storagehost, @storagepath + "/", @storageport, @storagescheme)
53
+ raise CloudFiles::Exception::NoSuchContainer, "Container #{@name} does not exist" unless (response.code =~ /^20/)
54
+ {:bytes => response["x-container-bytes-used"].to_i, :count => response["x-container-object-count"].to_i}
55
+ )
56
+ end
82
57
 
83
- true
58
+ # Retrieves CDN-Enabled Meta Data
59
+ def cdn_metadata
60
+ @cdn_metadata ||= (
61
+ response = self.connection.cfreq("HEAD", @cdnmgmthost, @cdnmgmtpath, @cdnmgmtport, @cdnmgmtscheme)
62
+ cdn_enabled = ((response["x-cdn-enabled"] || "").downcase == "true") ? true : false
63
+ {
64
+ :cdn_enabled => cdn_enabled,
65
+ :cdn_ttl => cdn_enabled ? response["x-ttl"].to_i : nil,
66
+ :cdn_url => cdn_enabled ? response["x-cdn-uri"] : nil,
67
+ :user_agent_acl => response["x-user-agent-acl"],
68
+ :referrer_acl => response["x-referrer-acl"],
69
+ :cdn_log => (cdn_enabled and response["x-log-retention"] == "True") ? true : false
70
+ }
71
+ )
72
+ end
73
+
74
+ # Size of the container (in bytes)
75
+ def bytes
76
+ self.metadata[:bytes]
77
+ end
78
+
79
+ # Number of objects in the container
80
+ def count
81
+ self.metadata[:count]
82
+ end
83
+
84
+ # Returns true if the container is public and CDN-enabled. Returns false otherwise.
85
+ #
86
+ # Aliased as container.public?
87
+ #
88
+ # public_container.cdn_enabled?
89
+ # => true
90
+ #
91
+ # private_container.public?
92
+ # => false
93
+ def cdn_enabled
94
+ self.cdn_metadata[:cdn_enabled]
95
+ end
96
+ alias :cdn_enabled? :cdn_enabled
97
+ alias :public? :cdn_enabled
98
+
99
+ # CDN container TTL (if container is public)
100
+ def cdn_ttl
101
+ self.cdn_metadata[:cdn_ttl]
102
+ end
103
+
104
+ # CDN container URL (if container if public)
105
+ def cdn_url
106
+ self.cdn_metadata[:cdn_url]
107
+ end
108
+
109
+ # The container ACL on the User Agent
110
+ def user_agent_acl
111
+ self.cdn_metadata[:user_agent_acl]
84
112
  end
85
- alias :refresh :populate
86
-
113
+
114
+ # The container ACL on the site Referrer
115
+ def referrer_acl
116
+ self.cdn_metadata[:referrer_acl]
117
+ end
118
+
87
119
  # Returns true if log retention is enabled on this container, false otherwise
88
- def log_retention?
89
- @cdn_log
120
+ def cdn_log
121
+ self.cdn_metadata[:cdn_log]
90
122
  end
91
-
123
+ alias :log_retention? :cdn_log
124
+ alias :cdn_log? :cdn_log
125
+
92
126
  # Change the log retention status for this container. Values are true or false.
93
127
  #
94
- # These logs will be periodically (at unpredictable intervals) compressed and uploaded
128
+ # These logs will be periodically (at unpredictable intervals) compressed and uploaded
95
129
  # to a “.CDN_ACCESS_LOGS” container in the form of “container_name.YYYYMMDDHH-XXXX.gz”.
96
130
  def log_retention=(value)
97
- response = self.connection.cfreq("POST",@cdnmgmthost,@cdnmgmtpath,@cdnmgmtport,@cdnmgmtscheme,{"x-log-retention" => value.to_s.capitalize})
98
- raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code == "201" or response.code == "202")
99
- return true
131
+ response = self.connection.cfreq("POST", @cdnmgmthost, @cdnmgmtpath, @cdnmgmtport, @cdnmgmtscheme, {"x-log-retention" => value.to_s.capitalize})
132
+ raise CloudFiles::Exception::InvalidResponse, "Invalid response code #{response.code}" unless (response.code == "201" or response.code == "202")
133
+ return true
100
134
  end
101
-
135
+
102
136
 
103
137
  # Returns the CloudFiles::StorageObject for the named object. Refer to the CloudFiles::StorageObject class for available
104
138
  # methods. If the object exists, it will be returned. If the object does not exist, a NoSuchObjectException will be thrown.
105
- #
139
+ #
106
140
  # object = container.object('test.txt')
107
141
  # object.data
108
142
  # => "This is test data"
@@ -110,13 +144,13 @@ module CloudFiles
110
144
  # object = container.object('newfile.txt')
111
145
  # => NoSuchObjectException: Object newfile.txt does not exist
112
146
  def object(objectname)
113
- o = CloudFiles::StorageObject.new(self,objectname,true)
147
+ o = CloudFiles::StorageObject.new(self, objectname, true)
114
148
  return o
115
149
  end
116
150
  alias :get_object :object
117
-
118
151
 
119
- # Gathers a list of all available objects in the current container and returns an array of object names.
152
+
153
+ # Gathers a list of all available objects in the current container and returns an array of object names.
120
154
  # container = cf.container("My Container")
121
155
  # container.objects #=> [ "cat", "dog", "donkey", "monkeydir", "monkeydir/capuchin"]
122
156
  # Pass a limit argument to limit the list to a number of objects:
@@ -127,21 +161,23 @@ module CloudFiles
127
161
  # container.objects(:prefix => "do") #=> [ "dog", "donkey" ]
128
162
  # Only search within a certain pseudo-filesystem path:
129
163
  # container.objects(:path => 'monkeydir') #=> ["monkeydir/capuchin"]
164
+ # Only grab "virtual directories", based on a single-character delimiter (no "directory" objects required):
165
+ # container.objects(:delimiter => '/') #=> ["monkeydir"]
130
166
  # All arguments to this method are optional.
131
- #
167
+ #
132
168
  # Returns an empty array if no object exist in the container. Throws an InvalidResponseException
133
169
  # if the request fails.
134
170
  def objects(params = {})
135
- params[:marker] ||= params[:offset]
136
- paramarr = []
137
- paramarr << ["limit=#{URI.encode(params[:limit].to_s).gsub(/&/,'%26')}"] if params[:limit]
138
- paramarr << ["marker=#{URI.encode(params[:marker].to_s).gsub(/&/,'%26')}"] if params[:marker]
139
- paramarr << ["prefix=#{URI.encode(params[:prefix]).gsub(/&/,'%26')}"] if params[:prefix]
140
- paramarr << ["path=#{URI.encode(params[:path]).gsub(/&/,'%26')}"] if params[:path]
141
- paramstr = (paramarr.size > 0)? paramarr.join("&") : "" ;
142
- response = self.connection.cfreq("GET",@storagehost,"#{@storagepath}?#{paramstr}",@storageport,@storagescheme)
171
+ params[:marker] ||= params[:offset] unless params[:offset].nil?
172
+ query = []
173
+ params.each do |param, value|
174
+ if [:limit, :marker, :prefix, :path, :delimiter].include? param
175
+ query << "#{param}=#{CloudFiles.escape(value.to_s)}"
176
+ end
177
+ end
178
+ response = self.connection.cfreq("GET", @storagehost, "#{@storagepath}?#{query.join '&'}", @storageport, @storagescheme)
143
179
  return [] if (response.code == "204")
144
- raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code == "200")
180
+ raise CloudFiles::Exception::InvalidResponse, "Invalid response code #{response.code}" unless (response.code == "200")
145
181
  return CloudFiles.lines(response.body)
146
182
  end
147
183
  alias :list_objects :objects
@@ -149,31 +185,30 @@ module CloudFiles
149
185
  # Retrieves a list of all objects in the current container along with their size in bytes, hash, and content_type.
150
186
  # If no objects exist, an empty hash is returned. Throws an InvalidResponseException if the request fails. Takes a
151
187
  # parameter hash as an argument, in the same form as the objects method.
152
- #
188
+ #
153
189
  # Returns a hash in the same format as the containers_detail from the CloudFiles class.
154
190
  #
155
191
  # container.objects_detail
156
- # => {"test.txt"=>{:content_type=>"application/octet-stream",
157
- # :hash=>"e2a6fcb4771aa3509f6b27b6a97da55b",
158
- # :last_modified=>Mon Jan 19 10:43:36 -0600 2009,
159
- # :bytes=>"16"},
160
- # "new.txt"=>{:content_type=>"application/octet-stream",
161
- # :hash=>"0aa820d91aed05d2ef291d324e47bc96",
162
- # :last_modified=>Wed Jan 28 10:16:26 -0600 2009,
192
+ # => {"test.txt"=>{:content_type=>"application/octet-stream",
193
+ # :hash=>"e2a6fcb4771aa3509f6b27b6a97da55b",
194
+ # :last_modified=>Mon Jan 19 10:43:36 -0600 2009,
195
+ # :bytes=>"16"},
196
+ # "new.txt"=>{:content_type=>"application/octet-stream",
197
+ # :hash=>"0aa820d91aed05d2ef291d324e47bc96",
198
+ # :last_modified=>Wed Jan 28 10:16:26 -0600 2009,
163
199
  # :bytes=>"22"}
164
200
  # }
165
201
  def objects_detail(params = {})
166
- params[:marker] ||= params[:offset]
167
- paramarr = []
168
- paramarr << ["format=xml"]
169
- paramarr << ["limit=#{URI.encode(params[:limit].to_s).gsub(/&/,'%26')}"] if params[:limit]
170
- paramarr << ["marker=#{URI.encode(params[:marker].to_s).gsub(/&/,'%26')}"] if params[:marker]
171
- paramarr << ["prefix=#{URI.encode(params[:prefix]).gsub(/&/,'%26')}"] if params[:prefix]
172
- paramarr << ["path=#{URI.encode(params[:path]).gsub(/&/,'%26')}"] if params[:path]
173
- paramstr = (paramarr.size > 0)? paramarr.join("&") : "" ;
174
- response = self.connection.cfreq("GET",@storagehost,"#{@storagepath}?#{paramstr}",@storageport,@storagescheme)
202
+ params[:marker] ||= params[:offset] unless params[:offset].nil?
203
+ query = ["format=xml"]
204
+ params.each do |param, value|
205
+ if [:limit, :marker, :prefix, :path, :delimiter].include? param
206
+ query << "#{param}=#{CloudFiles.escape(value.to_s)}"
207
+ end
208
+ end
209
+ response = self.connection.cfreq("GET", @storagehost, "#{@storagepath}?#{query.join '&'}", @storageport, @storagescheme)
175
210
  return {} if (response.code == "204")
176
- raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code == "200")
211
+ raise CloudFiles::Exception::InvalidResponse, "Invalid response code #{response.code}" unless (response.code == "200")
177
212
  doc = REXML::Document.new(response.body)
178
213
  detailhash = {}
179
214
  doc.elements.each("container/object") { |o|
@@ -184,17 +219,6 @@ module CloudFiles
184
219
  end
185
220
  alias :list_objects_info :objects_detail
186
221
 
187
- # Returns true if the container is public and CDN-enabled. Returns false otherwise.
188
- #
189
- # public_container.public?
190
- # => true
191
- #
192
- # private_container.public?
193
- # => false
194
- def public?
195
- return @cdn_enabled
196
- end
197
-
198
222
  # Returns true if a container is empty and returns false otherwise.
199
223
  #
200
224
  # new_container.empty?
@@ -203,7 +227,7 @@ module CloudFiles
203
227
  # full_container.empty?
204
228
  # => false
205
229
  def empty?
206
- return (@count.to_i == 0)? true : false
230
+ return (metadata[:count].to_i == 0)? true : false
207
231
  end
208
232
 
209
233
  # Returns true if object exists and returns false otherwise.
@@ -214,23 +238,23 @@ module CloudFiles
214
238
  # container.object_exists?('badfile.txt')
215
239
  # => false
216
240
  def object_exists?(objectname)
217
- response = self.connection.cfreq("HEAD",@storagehost,"#{@storagepath}/#{URI.encode(objectname).gsub(/&/,'%26')}",@storageport,@storagescheme)
241
+ response = self.connection.cfreq("HEAD", @storagehost, "#{@storagepath}/#{CloudFiles.escape objectname}", @storageport, @storagescheme)
218
242
  return (response.code =~ /^20/)? true : false
219
243
  end
220
244
 
221
- # Creates a new CloudFiles::StorageObject in the current container.
245
+ # Creates a new CloudFiles::StorageObject in the current container.
222
246
  #
223
247
  # If an object with the specified name exists in the current container, that object will be returned. Otherwise,
224
248
  # an empty new object will be returned.
225
249
  #
226
250
  # Passing in the optional make_path argument as true will create zero-byte objects to simulate a filesystem path
227
- # to the object, if an objectname with path separators ("/path/to/myfile.mp3") is supplied. These path objects can
251
+ # to the object, if an objectname with path separators ("/path/to/myfile.mp3") is supplied. These path objects can
228
252
  # be used in the Container.objects method.
229
- def create_object(objectname,make_path = false)
230
- CloudFiles::StorageObject.new(self,objectname,false,make_path)
253
+ def create_object(objectname, make_path = false)
254
+ CloudFiles::StorageObject.new(self, objectname, false, make_path)
231
255
  end
232
-
233
- # Removes an CloudFiles::StorageObject from a container. True is returned if the removal is successful. Throws
256
+
257
+ # Removes an CloudFiles::StorageObject from a container. True is returned if the removal is successful. Throws
234
258
  # NoSuchObjectException if the object doesn't exist. Throws InvalidResponseException if the request fails.
235
259
  #
236
260
  # container.delete_object('new.txt')
@@ -239,15 +263,15 @@ module CloudFiles
239
263
  # container.delete_object('nonexistent_file.txt')
240
264
  # => NoSuchObjectException: Object nonexistent_file.txt does not exist
241
265
  def delete_object(objectname)
242
- response = self.connection.cfreq("DELETE",@storagehost,"#{@storagepath}/#{URI.encode(objectname).gsub(/&/,'%26')}",@storageport,@storagescheme)
243
- raise NoSuchObjectException, "Object #{objectname} does not exist" if (response.code == "404")
244
- raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code =~ /^20/)
266
+ response = self.connection.cfreq("DELETE", @storagehost, "#{@storagepath}/#{CloudFiles.escape objectname}", @storageport, @storagescheme)
267
+ raise CloudFiles::Exception::NoSuchObject, "Object #{objectname} does not exist" if (response.code == "404")
268
+ raise CloudFiles::Exception::InvalidResponse, "Invalid response code #{response.code}" unless (response.code =~ /^20/)
245
269
  true
246
270
  end
247
271
 
248
272
  # Makes a container publicly available via the Cloud Files CDN and returns true upon success. Throws NoSuchContainerException
249
273
  # if the container doesn't exist or if the request fails.
250
- #
274
+ #
251
275
  # Takes an optional hash of options, including:
252
276
  #
253
277
  # :ttl, which is the CDN cache TTL in seconds (default 86400 seconds or 1 day, minimum 3600 or 1 hour, maximum 259200 or 3 days)
@@ -264,16 +288,16 @@ module CloudFiles
264
288
  ttl = options
265
289
  options = {:ttl => ttl}
266
290
  end
267
-
268
- response = self.connection.cfreq("PUT",@cdnmgmthost,@cdnmgmtpath,@cdnmgmtport,@cdnmgmtscheme)
269
- raise NoSuchContainerException, "Container #{@name} does not exist" unless (response.code == "201" || response.code == "202")
291
+
292
+ response = self.connection.cfreq("PUT", @cdnmgmthost, @cdnmgmtpath, @cdnmgmtport, @cdnmgmtscheme)
293
+ raise CloudFiles::Exception::NoSuchContainer, "Container #{@name} does not exist" unless (response.code == "201" || response.code == "202")
270
294
 
271
295
  headers = { "X-TTL" => options[:ttl].to_s , "X-CDN-Enabled" => "True" }
272
296
  headers["X-User-Agent-ACL"] = options[:user_agent_acl] if options[:user_agent_acl]
273
297
  headers["X-Referrer-ACL"] = options[:referrer_acl] if options[:referrer_acl]
274
- response = self.connection.cfreq("POST",@cdnmgmthost,@cdnmgmtpath,@cdnmgmtport,@cdnmgmtscheme,headers)
275
- raise NoSuchContainerException, "Container #{@name} does not exist" unless (response.code == "201" || response.code == "202")
276
- populate
298
+ response = self.connection.cfreq("POST", @cdnmgmthost, @cdnmgmtpath, @cdnmgmtport, @cdnmgmtscheme, headers)
299
+ raise CloudFiles::Exception::NoSuchContainer, "Container #{@name} does not exist" unless (response.code == "201" || response.code == "202")
300
+ refresh
277
301
  true
278
302
  end
279
303
 
@@ -286,12 +310,12 @@ module CloudFiles
286
310
  # => true
287
311
  def make_private
288
312
  headers = { "X-CDN-Enabled" => "False" }
289
- response = self.connection.cfreq("POST",@cdnmgmthost,@cdnmgmtpath,@cdnmgmtport,@cdnmgmtscheme,headers)
290
- raise NoSuchContainerException, "Container #{@name} does not exist" unless (response.code == "201" || response.code == "202")
291
- populate
313
+ response = self.connection.cfreq("POST", @cdnmgmthost, @cdnmgmtpath, @cdnmgmtport, @cdnmgmtscheme, headers)
314
+ raise CloudFiles::Exception::NoSuchContainer, "Container #{@name} does not exist" unless (response.code == "201" || response.code == "202")
315
+ refresh
292
316
  true
293
317
  end
294
-
318
+
295
319
  def to_s # :nodoc:
296
320
  @name
297
321
  end
@@ -0,0 +1,64 @@
1
+ # The deprecated old exception types. Will go away in a couple of releases.
2
+
3
+ class SyntaxException < StandardError # :nodoc:
4
+ end
5
+ class ConnectionException < StandardError # :nodoc:
6
+ end
7
+ class AuthenticationException < StandardError # :nodoc:
8
+ end
9
+ class InvalidResponseException < StandardError # :nodoc:
10
+ end
11
+ class NonEmptyContainerException < StandardError # :nodoc:
12
+ end
13
+ class NoSuchObjectException < StandardError # :nodoc:
14
+ end
15
+ class NoSuchContainerException < StandardError # :nodoc:
16
+ end
17
+ class NoSuchAccountException < StandardError # :nodoc:
18
+ end
19
+ class MisMatchedChecksumException < StandardError # :nodoc:
20
+ end
21
+ class IOException < StandardError # :nodoc:
22
+ end
23
+ class CDNNotEnabledException < StandardError # :nodoc:
24
+ end
25
+ class ObjectExistsException < StandardError # :nodoc:
26
+ end
27
+ class ExpiredAuthTokenException < StandardError # :nodoc:
28
+ end
29
+
30
+ # The new properly scoped exceptions.
31
+
32
+ module CloudFiles
33
+ class Exception
34
+
35
+ class Syntax < SyntaxException
36
+ end
37
+ class Connection < ConnectionException # :nodoc:
38
+ end
39
+ class Authentication < AuthenticationException # :nodoc:
40
+ end
41
+ class InvalidResponse < InvalidResponseException # :nodoc:
42
+ end
43
+ class NonEmptyContainer < NonEmptyContainerException # :nodoc:
44
+ end
45
+ class NoSuchObject < NoSuchObjectException # :nodoc:
46
+ end
47
+ class NoSuchContainer < NoSuchContainerException # :nodoc:
48
+ end
49
+ class NoSuchAccount < NoSuchAccountException # :nodoc:
50
+ end
51
+ class MisMatchedChecksum < MisMatchedChecksumException # :nodoc:
52
+ end
53
+ class IO < IOException # :nodoc:
54
+ end
55
+ class CDNNotEnabled < CDNNotEnabledException # :nodoc:
56
+ end
57
+ class ObjectExists < ObjectExistsException # :nodoc:
58
+ end
59
+ class ExpiredAuthToken < ExpiredAuthTokenException # :nodoc:
60
+ end
61
+
62
+ end
63
+ end
64
+