harbor 0.16.1

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.
Files changed (84) hide show
  1. data/Rakefile +76 -0
  2. data/bin/harbor +7 -0
  3. data/lib/harbor.rb +17 -0
  4. data/lib/harbor/accessor_injector.rb +30 -0
  5. data/lib/harbor/application.rb +172 -0
  6. data/lib/harbor/auth/basic.rb +51 -0
  7. data/lib/harbor/block_io.rb +63 -0
  8. data/lib/harbor/cache.rb +90 -0
  9. data/lib/harbor/cache/disk.rb +99 -0
  10. data/lib/harbor/cache/item.rb +48 -0
  11. data/lib/harbor/cache/memory.rb +35 -0
  12. data/lib/harbor/cascade.rb +75 -0
  13. data/lib/harbor/console.rb +34 -0
  14. data/lib/harbor/container.rb +134 -0
  15. data/lib/harbor/contrib/debug.rb +236 -0
  16. data/lib/harbor/contrib/session/data_mapper.rb +74 -0
  17. data/lib/harbor/daemon.rb +105 -0
  18. data/lib/harbor/errors.rb +49 -0
  19. data/lib/harbor/events.rb +45 -0
  20. data/lib/harbor/exception_notifier.rb +59 -0
  21. data/lib/harbor/file.rb +66 -0
  22. data/lib/harbor/file_store.rb +69 -0
  23. data/lib/harbor/file_store/file.rb +100 -0
  24. data/lib/harbor/file_store/local.rb +71 -0
  25. data/lib/harbor/file_store/mosso.rb +154 -0
  26. data/lib/harbor/file_store/mosso/private.rb +8 -0
  27. data/lib/harbor/generator.rb +56 -0
  28. data/lib/harbor/generator/help.rb +34 -0
  29. data/lib/harbor/generator/setup.rb +82 -0
  30. data/lib/harbor/generator/skeletons/basic/config.ru.skel +21 -0
  31. data/lib/harbor/generator/skeletons/basic/lib/appname.rb.skel +49 -0
  32. data/lib/harbor/generator/skeletons/basic/lib/appname/controllers/home.rb +9 -0
  33. data/lib/harbor/generator/skeletons/basic/lib/appname/views/home/index.html.erb.skel +23 -0
  34. data/lib/harbor/generator/skeletons/basic/lib/appname/views/layouts/application.html.erb.skel +48 -0
  35. data/lib/harbor/generator/skeletons/basic/lib/appname/views/layouts/exception.html.erb.skel +13 -0
  36. data/lib/harbor/generator/skeletons/basic/lib/appname/views/layouts/login.html.erb.skel +11 -0
  37. data/lib/harbor/generator/skeletons/basic/log/development.log +0 -0
  38. data/lib/harbor/hooks.rb +105 -0
  39. data/lib/harbor/json_cookies.rb +37 -0
  40. data/lib/harbor/layouts.rb +61 -0
  41. data/lib/harbor/locale.rb +50 -0
  42. data/lib/harbor/locales.txt +22 -0
  43. data/lib/harbor/logging.rb +39 -0
  44. data/lib/harbor/logging/appenders/email.rb +84 -0
  45. data/lib/harbor/logging/request_logger.rb +34 -0
  46. data/lib/harbor/mail_servers/abstract.rb +12 -0
  47. data/lib/harbor/mail_servers/sendmail.rb +19 -0
  48. data/lib/harbor/mail_servers/smtp.rb +25 -0
  49. data/lib/harbor/mail_servers/test.rb +17 -0
  50. data/lib/harbor/mailer.rb +96 -0
  51. data/lib/harbor/messages.rb +17 -0
  52. data/lib/harbor/mime.rb +206 -0
  53. data/lib/harbor/plugin.rb +52 -0
  54. data/lib/harbor/plugin_list.rb +38 -0
  55. data/lib/harbor/request.rb +138 -0
  56. data/lib/harbor/response.rb +281 -0
  57. data/lib/harbor/router.rb +112 -0
  58. data/lib/harbor/script.rb +155 -0
  59. data/lib/harbor/session.rb +75 -0
  60. data/lib/harbor/session/abstract.rb +27 -0
  61. data/lib/harbor/session/cookie.rb +17 -0
  62. data/lib/harbor/shellwords.rb +26 -0
  63. data/lib/harbor/test/mailer.rb +10 -0
  64. data/lib/harbor/test/request.rb +28 -0
  65. data/lib/harbor/test/response.rb +17 -0
  66. data/lib/harbor/test/session.rb +11 -0
  67. data/lib/harbor/test/test.rb +22 -0
  68. data/lib/harbor/version.rb +3 -0
  69. data/lib/harbor/view.rb +89 -0
  70. data/lib/harbor/view_context.rb +134 -0
  71. data/lib/harbor/view_context/helpers.rb +7 -0
  72. data/lib/harbor/view_context/helpers/cache.rb +77 -0
  73. data/lib/harbor/view_context/helpers/form.rb +34 -0
  74. data/lib/harbor/view_context/helpers/html.rb +26 -0
  75. data/lib/harbor/view_context/helpers/text.rb +120 -0
  76. data/lib/harbor/view_context/helpers/url.rb +11 -0
  77. data/lib/harbor/xml_view.rb +57 -0
  78. data/lib/harbor/zipped_io.rb +203 -0
  79. data/lib/vendor/cloudfiles-1.3.0/cloudfiles.rb +77 -0
  80. data/lib/vendor/cloudfiles-1.3.0/cloudfiles/authentication.rb +46 -0
  81. data/lib/vendor/cloudfiles-1.3.0/cloudfiles/connection.rb +280 -0
  82. data/lib/vendor/cloudfiles-1.3.0/cloudfiles/container.rb +260 -0
  83. data/lib/vendor/cloudfiles-1.3.0/cloudfiles/storage_object.rb +253 -0
  84. metadata +155 -0
@@ -0,0 +1,11 @@
1
+ ##
2
+ # Generic URL helpers, such as merging query strings.
3
+ ##
4
+ module Harbor::ViewContext::Helpers::Url
5
+ ##
6
+ # Takes a query string and merges the provided params returning a new query string.
7
+ ##
8
+ def merge_query_string(query_string, params = {})
9
+ Rack::Utils::build_query(Rack::Utils::parse_query(query_string).merge(params))
10
+ end
11
+ end
@@ -0,0 +1,57 @@
1
+ gem "builder"
2
+ require "builder"
3
+
4
+ module Harbor
5
+ class XMLViewContext < ViewContext
6
+
7
+ def render(partial, variables=nil)
8
+ context = to_hash
9
+
10
+ result = XMLView.new(partial, merge(variables)).to_s
11
+
12
+ replace(context)
13
+
14
+ result
15
+ end
16
+
17
+ def xml
18
+ @view.xml
19
+ end
20
+
21
+ end
22
+
23
+ class XMLView < View
24
+
25
+ attr_accessor :xml, :output
26
+
27
+ def initialize(view, context = {})
28
+ super
29
+ @content_type = "text/xml"
30
+ @extension = ".rxml"
31
+ @output = ""
32
+ @filename = ::File.extname(view) == "" ? (view + @extension) : view
33
+
34
+ if context.is_a?(ViewContext)
35
+ @context = context
36
+ @xml = context.view.xml
37
+ else
38
+ @xml = Builder::XmlMarkup.new(:indent => 2, :target => @output)
39
+ @context = XMLViewContext.new(self, context)
40
+ end
41
+ end
42
+
43
+ def supports_layouts?
44
+ false
45
+ end
46
+
47
+ def to_s(layout = nil)
48
+ path = View::path.detect { |dir| ::File.exists?(dir + @filename) }
49
+ raise "Could not find '#{@filename}' in #{View::path.inspect}" if path.nil?
50
+
51
+ eval_code = ::File.read(path + @filename)
52
+ XMLViewContext.new(self, @context).instance_eval(eval_code, __FILE__, __LINE__)
53
+
54
+ @output
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,203 @@
1
+ require "zlib"
2
+
3
+ module Harbor
4
+
5
+ # An IO class for zipping files suitable for sending via rack.
6
+ class ZippedIO
7
+
8
+ CENTRAL_DIRECTORY_ENTRY_SIGNATURE = 0x02014b50
9
+ END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06054b50
10
+
11
+ def initialize(files)
12
+ @files = files
13
+ end
14
+
15
+ def each
16
+ zip_entries.each do |entry|
17
+ yield entry.read_local_entry
18
+
19
+ deflater = Deflater.new(entry.file)
20
+ deflater.read do |data|
21
+ yield data
22
+ end
23
+ end
24
+
25
+ zip_central_directory.read do |data|
26
+ yield data
27
+ end
28
+ end
29
+
30
+ def size
31
+ return @size if @size
32
+ @size = 0
33
+ zip_entries.each do |entry|
34
+ @size += entry.size
35
+ end
36
+ @files.each do |file|
37
+ @size += ZippedIO::Deflater.new(file).size
38
+ end
39
+ @size += zip_central_directory.size
40
+ @size
41
+ end
42
+
43
+ def zip_central_directory
44
+ @zip_central_directory ||= ZipCentralDirectory.new(zip_entries)
45
+ end
46
+
47
+ def zip_entries
48
+ @zip_entries ||= @files.map { |file| ZipEntry.new(file) }
49
+ end
50
+
51
+ @@block_size = 4096
52
+
53
+ def self.block_size
54
+ @@block_size
55
+ end
56
+
57
+ def self.block_size=(value)
58
+ @@block_size = value
59
+ end
60
+
61
+ class Deflater
62
+
63
+ attr_accessor :size
64
+
65
+ def initialize(file, level = 0)
66
+ @file = file
67
+ @zlibDeflater = Zlib::Deflate.new(level, -Zlib::MAX_WBITS)
68
+ end
69
+
70
+ def read
71
+ @file.rewind
72
+ while data = @file.read(Harbor::ZippedIO::block_size)
73
+ yield @zlibDeflater.deflate(data)
74
+ end
75
+ until @zlibDeflater.finished?
76
+ yield @zlibDeflater.finish
77
+ end
78
+ nil
79
+ end
80
+
81
+ def size
82
+ return @size if @size
83
+ @size = 0
84
+ @file.rewind
85
+ while data = @file.read(Harbor::ZippedIO::block_size)
86
+ @size += @zlibDeflater.deflate(data).size
87
+ end
88
+
89
+ until @zlibDeflater.finished?
90
+ @size += @zlibDeflater.finish.size
91
+ end
92
+ @size
93
+ end
94
+
95
+ end
96
+
97
+ class ZipCentralDirectory
98
+
99
+ def initialize(entries)
100
+ @entries = entries
101
+ end
102
+
103
+ def read
104
+ generate unless @io
105
+
106
+ @io.pos = 0
107
+ while data = @io.read(Harbor::ZippedIO::block_size)
108
+ yield data
109
+ end
110
+ end
111
+
112
+ def size
113
+ generate unless @io
114
+ @io.size
115
+ end
116
+
117
+ private
118
+
119
+ def generate
120
+ @io = StringIO.new
121
+ @io << [
122
+ END_OF_CENTRAL_DIRECTORY_SIGNATURE,
123
+ 0, # number of this disk
124
+ 0, # numer of disk with start of central directory
125
+ @entries.size,
126
+ @entries.size,
127
+ @entries.inject(0) { |value, entry| entry.central_directory_header_size + value },
128
+ 0, # offset
129
+ 0 # comment length
130
+ ].pack('VvvvvVVv')
131
+ @io << "" #comment
132
+ end
133
+
134
+ end
135
+
136
+ class ZipEntry
137
+
138
+ DEFLATED = 8
139
+
140
+ FSTYPE_UNIX = 3
141
+
142
+ LOCAL_ENTRY_SIGNATURE = 0x04034b50
143
+ CENTRAL_DIRECTORY_STATIC_HEADER_LENGTH = 46
144
+
145
+ attr_accessor :comment, :crc, :name, :size, :file
146
+
147
+ def initialize(file)
148
+ @file = file
149
+
150
+ @crc = 0
151
+ @compressed_size = file.size
152
+ @size = file.size
153
+ end
154
+
155
+ def binary_dos_date
156
+ (time.day) + (time.month << 5) + ((time.year - 1980) << 9)
157
+ end
158
+
159
+ def binary_dos_time
160
+ (time.sec / 2) + (time.min << 5) + (time.hour << 11)
161
+ end
162
+
163
+ def central_directory_header_size
164
+ CENTRAL_DIRECTORY_STATIC_HEADER_LENGTH + @file.name.size
165
+ end
166
+
167
+ def time
168
+ Time.now
169
+ end
170
+
171
+ def read_local_entry
172
+ generate unless @data
173
+ @data
174
+ end
175
+
176
+ def size
177
+ generate unless @data
178
+ @data.size
179
+ end
180
+
181
+ private
182
+
183
+ def generate
184
+ @data ||= [
185
+ ZipEntry::LOCAL_ENTRY_SIGNATURE,
186
+ 0,
187
+ 0,
188
+ ZipEntry::DEFLATED,
189
+ binary_dos_time,
190
+ binary_dos_date,
191
+ @crc, #crc
192
+ @compressed_size,
193
+ @size,
194
+ @file.name.length,
195
+ 0 # extra length
196
+ ].pack('VvvvvvVVVvv') << @file.name << ""
197
+ end
198
+
199
+ end
200
+
201
+ end
202
+
203
+ end
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # == Cloud Files API
4
+ # ==== Connects Ruby Applications to Rackspace's {Mosso Cloud Files service}[http://www.mosso.com/cloudfiles.jsp]
5
+ # Initial work by Major Hayden <major.hayden@rackspace.com>
6
+ #
7
+ # Subsequent work by H. Wade Minter <wade.minter@rackspace.com>
8
+ #
9
+ # Copyright (C) 2008 Rackspace US, Inc.
10
+ #
11
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16
+ #
17
+ # Except as contained in this notice, the name of Rackspace US, Inc. shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from Rackspace US, Inc.
18
+ #
19
+ # ----
20
+ #
21
+ # === Documentation & Examples
22
+ # To begin reviewing the available methods and examples, peruse the README file, or begin by looking at documentation for the
23
+ # CloudFiles::Connection class.
24
+ #
25
+ # The CloudFiles class is the base class. Not much of note happens here.
26
+ # To create a new CloudFiles connection, use the CloudFiles::Connection.new('user_name', 'api_key') method.
27
+ module CloudFiles
28
+
29
+ VERSION = '1.3.0'
30
+ require 'net/http'
31
+ require 'net/https'
32
+ require 'rexml/document'
33
+ require 'uri'
34
+ require 'digest/md5'
35
+ require 'jcode'
36
+ require 'time'
37
+ require 'rubygems'
38
+ require 'mime/types'
39
+
40
+ $KCODE = 'u'
41
+
42
+ $:.unshift(File.dirname(__FILE__))
43
+ require 'cloudfiles/authentication'
44
+ require 'cloudfiles/connection'
45
+ require 'cloudfiles/container'
46
+ require 'cloudfiles/storage_object'
47
+
48
+ end
49
+
50
+
51
+
52
+ class SyntaxException < StandardError # :nodoc:
53
+ end
54
+ class ConnectionException < StandardError # :nodoc:
55
+ end
56
+ class AuthenticationException < StandardError # :nodoc:
57
+ end
58
+ class InvalidResponseException < StandardError # :nodoc:
59
+ end
60
+ class NonEmptyContainerException < StandardError # :nodoc:
61
+ end
62
+ class NoSuchObjectException < StandardError # :nodoc:
63
+ end
64
+ class NoSuchContainerException < StandardError # :nodoc:
65
+ end
66
+ class NoSuchAccountException < StandardError # :nodoc:
67
+ end
68
+ class MisMatchedChecksumException < StandardError # :nodoc:
69
+ end
70
+ class IOException < StandardError # :nodoc:
71
+ end
72
+ class CDNNotEnabledException < StandardError # :nodoc:
73
+ end
74
+ class ObjectExistsException < StandardError # :nodoc:
75
+ end
76
+ class ExpiredAuthTokenException < StandardError # :nodoc:
77
+ end
@@ -0,0 +1,46 @@
1
+ # Copyright (C) 2008 Rackspace US, Inc.
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+ #
5
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+ #
7
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8
+ #
9
+ # Except as contained in this notice, the name of Rackspace US, Inc. shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from Rackspace US, Inc.
10
+
11
+ module CloudFiles
12
+ class Authentication
13
+
14
+ # Performs an authentication to the Cloud Files servers. Opens a new HTTP connection to the API server,
15
+ # sends the credentials, and looks for a successful authentication. If it succeeds, it sets the cdmmgmthost,
16
+ # cdmmgmtpath, storagehost, storagepath, authtoken, and authok variables on the connection. If it fails, it raises
17
+ # an AuthenticationException.
18
+ #
19
+ # Should probably never be called directly.
20
+ def initialize(connection)
21
+ path = '/auth'
22
+ hdrhash = { "X-Auth-User" => connection.authuser, "X-Auth-Key" => connection.authkey }
23
+ begin
24
+ server = Net::HTTP.new('api.mosso.com',443)
25
+ server.use_ssl = true
26
+ server.verify_mode = OpenSSL::SSL::VERIFY_NONE
27
+ server.start
28
+ rescue
29
+ raise ConnectionException, "Unable to connect to #{server}"
30
+ end
31
+ response = server.get(path,hdrhash)
32
+ if (response.code == "204")
33
+ connection.cdnmgmthost = URI.parse(response["x-cdn-management-url"]).host
34
+ connection.cdnmgmtpath = URI.parse(response["x-cdn-management-url"]).path
35
+ connection.storagehost = URI.parse(response["x-storage-url"]).host
36
+ connection.storagepath = URI.parse(response["x-storage-url"]).path
37
+ connection.authtoken = response["x-auth-token"]
38
+ connection.authok = true
39
+ else
40
+ connection.authtoken = false
41
+ raise AuthenticationException, "Authentication failed"
42
+ end
43
+ server.finish
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,280 @@
1
+ # Copyright (C) 2008 Rackspace US, Inc.
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+ #
5
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+ #
7
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8
+ #
9
+ # Except as contained in this notice, the name of Rackspace US, Inc. shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from Rackspace US, Inc.
10
+
11
+ module CloudFiles
12
+ class Connection
13
+
14
+ # Authentication key provided when the CloudFiles class was instantiated
15
+ attr_reader :authkey
16
+
17
+ # Token returned after a successful authentication
18
+ attr_accessor :authtoken
19
+
20
+ # Authentication username provided when the CloudFiles class was instantiated
21
+ attr_reader :authuser
22
+
23
+ # Hostname of the CDN management server
24
+ attr_accessor :cdnmgmthost
25
+
26
+ # Path for managing containers on the CDN management server
27
+ attr_accessor :cdnmgmtpath
28
+
29
+ # Array of requests that have been made so far
30
+ attr_reader :reqlog
31
+
32
+ # Hostname of the storage server
33
+ attr_accessor :storagehost
34
+
35
+ # Path for managing containers/objects on the storage server
36
+ attr_accessor :storagepath
37
+
38
+ # Instance variable that is set when authorization succeeds
39
+ attr_accessor :authok
40
+
41
+ # The total size in bytes under this connection
42
+ attr_reader :bytes
43
+
44
+ # The total number of containers under this connection
45
+ attr_reader :count
46
+
47
+ # Creates a new CloudFiles::Connection object. Uses CloudFiles::Authentication to perform the login for the connection.
48
+ # The authuser is the Mosso username, the authkey is the Mosso API key.
49
+ #
50
+ # Setting the optional retry_auth variable to false will cause an exception to be thrown if your authorization token expires.
51
+ # Otherwise, it will attempt to reauthenticate.
52
+ #
53
+ # This will likely be the base class for most operations.
54
+ #
55
+ # cf = CloudFiles::Connection.new(MY_USERNAME, MY_API_KEY)
56
+ def initialize(authuser,authkey,retry_auth = true)
57
+ @authuser = authuser
58
+ @authkey = authkey
59
+ @retry_auth = retry_auth
60
+ @authok = false
61
+ @http = {}
62
+ @reqlog = []
63
+ CloudFiles::Authentication.new(self)
64
+ end
65
+
66
+ # Returns true if the authentication was successful and returns false otherwise.
67
+ #
68
+ # cf.authok?
69
+ # => true
70
+ def authok?
71
+ @authok
72
+ end
73
+
74
+ # Returns an CloudFiles::Container object that can be manipulated easily. Throws a NoSuchContainerException if
75
+ # the container doesn't exist.
76
+ #
77
+ # container = cf.container('test')
78
+ # container.count
79
+ # => 2
80
+ def container(name)
81
+ CloudFiles::Container.new(self,name)
82
+ end
83
+ alias :get_container :container
84
+
85
+ # Sets instance variables for the bytes of storage used for this account/connection, as well as the number of containers
86
+ # stored under the account. Returns a hash with :bytes and :count keys, and also sets the instance variables.
87
+ #
88
+ # cf.get_info
89
+ # => {:count=>8, :bytes=>42438527}
90
+ # cf.bytes
91
+ # => 42438527
92
+ def get_info
93
+ response = cfreq("HEAD",@storagehost,@storagepath)
94
+ raise InvalidResponseException, "Unable to obtain account size" unless (response.code == "204")
95
+ @bytes = response["x-account-bytes-used"].to_i
96
+ @count = response["x-account-container-count"].to_i
97
+ {:bytes => @bytes, :count => @count}
98
+ end
99
+
100
+ # Gathers a list of the containers that exist for the account and returns the list of container names
101
+ # as an array. If no containers exist, an empty array is returned. Throws an InvalidResponseException
102
+ # if the request fails.
103
+ #
104
+ # If you supply the optional limit and marker parameters, the call will return the number of containers
105
+ # specified in limit, starting after the object named in marker.
106
+ #
107
+ # cf.containers
108
+ # => ["backup", "Books", "cftest", "test", "video", "webpics"]
109
+ #
110
+ # cf.containers(2,'cftest')
111
+ # => ["test", "video"]
112
+ def containers(limit=0,marker="")
113
+ paramarr = []
114
+ paramarr << ["limit=#{URI.encode(limit.to_s)}"] if limit.to_i > 0
115
+ paramarr << ["offset=#{URI.encode(marker.to_s)}"] unless marker.to_s.empty?
116
+ paramstr = (paramarr.size > 0)? paramarr.join("&") : "" ;
117
+ response = cfreq("GET",@storagehost,"#{@storagepath}?#{paramstr}")
118
+ return [] if (response.code == "204")
119
+ raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code == "200")
120
+ response.body.to_a.map { |x| x.chomp }
121
+ end
122
+ alias :list_containers :containers
123
+
124
+ # Retrieves a list of containers on the account along with their sizes (in bytes) and counts of the objects
125
+ # held within them. If no containers exist, an empty hash is returned. Throws an InvalidResponseException
126
+ # if the request fails.
127
+ #
128
+ # If you supply the optional limit and marker parameters, the call will return the number of containers
129
+ # specified in limit, starting after the object named in marker.
130
+ #
131
+ # cf.containers_detail
132
+ # => { "container1" => { :bytes => "36543", :count => "146" },
133
+ # "container2" => { :bytes => "105943", :count => "25" } }
134
+ def containers_detail(limit=0,marker="")
135
+ paramarr = []
136
+ paramarr << ["limit=#{URI.encode(limit.to_s)}"] if limit.to_i > 0
137
+ paramarr << ["offset=#{URI.encode(marker.to_s)}"] unless marker.to_s.empty?
138
+ paramstr = (paramarr.size > 0)? paramarr.join("&") : "" ;
139
+ response = cfreq("GET",@storagehost,"#{@storagepath}?format=xml&#{paramstr}")
140
+ return {} if (response.code == "204")
141
+ raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code == "200")
142
+ doc = REXML::Document.new(response.body)
143
+ detailhash = {}
144
+ doc.elements.each("account/container/") { |c|
145
+ detailhash[c.elements["name"].text] = { :bytes => c.elements["bytes"].text, :count => c.elements["count"].text }
146
+ }
147
+ doc = nil
148
+ return detailhash
149
+ end
150
+ alias :list_containers_info :containers_detail
151
+
152
+ # Returns true if the requested container exists and returns false otherwise.
153
+ #
154
+ # cf.container_exists?('good_container')
155
+ # => true
156
+ #
157
+ # cf.container_exists?('bad_container')
158
+ # => false
159
+ def container_exists?(containername)
160
+ response = cfreq("HEAD",@storagehost,"#{@storagepath}/#{containername}")
161
+ return (response.code == "204")? true : false ;
162
+ end
163
+
164
+ # Creates a new container and returns the CloudFiles::Container object. Throws an InvalidResponseException if the
165
+ # request fails.
166
+ #
167
+ # Slash (/) and question mark (?) are invalid characters, and will be stripped out. The container name is limited to
168
+ # 256 characters or less.
169
+ #
170
+ # container = cf.create_container('new_container')
171
+ # container.name
172
+ # => "new_container"
173
+ #
174
+ # container = cf.create_container('bad/name')
175
+ # => SyntaxException: Container name cannot contain the characters '/' or '?'
176
+ def create_container(containername)
177
+ raise SyntaxException, "Container name cannot contain the characters '/' or '?'" if containername.match(/[\/\?]/)
178
+ raise SyntaxException, "Container name is limited to 256 characters" if containername.length > 256
179
+ response = cfreq("PUT",@storagehost,"#{@storagepath}/#{containername}")
180
+ raise InvalidResponseException, "Unable to create container #{containername}" unless (response.code == "201" || response.code == "202")
181
+ CloudFiles::Container.new(self,containername)
182
+ end
183
+
184
+ # Deletes a container from the account. Throws a NonEmptyContainerException if the container still contains
185
+ # objects. Throws a NoSuchContainerException if the container doesn't exist.
186
+ #
187
+ # cf.delete_container('new_container')
188
+ # => true
189
+ #
190
+ # cf.delete_container('video')
191
+ # => NonEmptyContainerException: Container video is not empty
192
+ #
193
+ # cf.delete_container('nonexistent')
194
+ # => NoSuchContainerException: Container nonexistent does not exist
195
+ def delete_container(containername)
196
+ response = cfreq("DELETE",@storagehost,"#{@storagepath}/#{containername}")
197
+ raise NonEmptyContainerException, "Container #{containername} is not empty" if (response.code == "409")
198
+ raise NoSuchContainerException, "Container #{containername} does not exist" unless (response.code == "204")
199
+ true
200
+ end
201
+
202
+ # Gathers a list of public (CDN-enabled) containers that exist for an account and returns the list of container names
203
+ # as an array. If no containers are public, an empty array is returned. Throws a InvalidResponseException if
204
+ # the request fails.
205
+ #
206
+ # If you pass the optional argument as true, it will only show containers that are CURRENTLY being shared on the CDN,
207
+ # as opposed to the default behavior which is to show all containers that have EVER been public.
208
+ #
209
+ # cf.public_containers
210
+ # => ["video", "webpics"]
211
+ def public_containers(enabled_only = false)
212
+ paramstr = enabled_only == true ? "enabled_only=true" : ""
213
+ response = cfreq("GET",@cdnmgmthost,"#{@cdnmgmtpath}?#{paramstr}")
214
+ return [] if (response.code == "204")
215
+ raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code == "200")
216
+ response.body.to_a.map { |x| x.chomp }
217
+ end
218
+
219
+ # This method actually makes the HTTP calls out to the server
220
+ def cfreq(method,server,path,headers = {},data = nil,attempts = 0,&block) # :nodoc:
221
+ start = Time.now
222
+ hdrhash = headerprep(headers)
223
+ path = URI.escape(path)
224
+ start_http(server,path,hdrhash)
225
+ request = Net::HTTP.const_get(method.to_s.capitalize).new(path,hdrhash)
226
+ if data
227
+ if data.respond_to?(:read)
228
+ request.body_stream = data
229
+ else
230
+ request.body = data
231
+ end
232
+ request.content_length = data.respond_to?(:lstat) ? data.stat.size : data.size
233
+ else
234
+ request.content_length = 0
235
+ end
236
+ response = @http[server].request(request,&block)
237
+ raise ExpiredAuthTokenException if response.code == "401"
238
+ response
239
+ rescue Errno::EPIPE, Timeout::Error, Errno::EINVAL, EOFError
240
+ # Server closed the connection, retry
241
+ raise ConnectionException, "Unable to reconnect to #{server} after #{count} attempts" if attempts >= 5
242
+ attempts += 1
243
+ @http[server].finish
244
+ start_http(server,path,headers)
245
+ retry
246
+ rescue ExpiredAuthTokenException
247
+ raise ConnectionException, "Authentication token expired and you have requested not to retry" if @retry_auth == false
248
+ CloudFiles::Authentication.new(self)
249
+ retry
250
+ end
251
+
252
+ private
253
+
254
+ # Sets up standard HTTP headers
255
+ def headerprep(headers = {}) # :nodoc:
256
+ default_headers = {}
257
+ default_headers["X-Auth-Token"] = @authtoken if (authok? && @account.nil?)
258
+ default_headers["X-Storage-Token"] = @authtoken if (authok? && !@account.nil?)
259
+ default_headers["Connection"] = "Keep-Alive"
260
+ default_headers["User-Agent"] = "Ruby-CloudFiles/#{VERSION}"
261
+ default_headers.merge(headers)
262
+ end
263
+
264
+ # Starts (or restarts) the HTTP connection
265
+ def start_http(server,path,headers) # :nodoc:
266
+ if (@http[server].nil?)
267
+ begin
268
+ @http[server] = Net::HTTP.new(server,443)
269
+ @http[server].use_ssl = true
270
+ @http[server].verify_mode = OpenSSL::SSL::VERIFY_NONE
271
+ @http[server].start
272
+ rescue
273
+ raise ConnectionException, "Unable to connect to #{server}"
274
+ end
275
+ end
276
+ end
277
+
278
+ end
279
+
280
+ end