seomoz-riak-client 1.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. data/Gemfile +27 -0
  2. data/Guardfile +14 -0
  3. data/Rakefile +76 -0
  4. data/erl_src/riak_kv_test_backend.beam +0 -0
  5. data/erl_src/riak_kv_test_backend.erl +174 -0
  6. data/erl_src/riak_search_test_backend.beam +0 -0
  7. data/erl_src/riak_search_test_backend.erl +175 -0
  8. data/lib/active_support/cache/riak_store.rb +2 -0
  9. data/lib/riak.rb +21 -0
  10. data/lib/riak/bucket.rb +215 -0
  11. data/lib/riak/cache_store.rb +84 -0
  12. data/lib/riak/client.rb +415 -0
  13. data/lib/riak/client/beefcake/messages.rb +147 -0
  14. data/lib/riak/client/beefcake/object_methods.rb +92 -0
  15. data/lib/riak/client/beefcake_protobuffs_backend.rb +176 -0
  16. data/lib/riak/client/excon_backend.rb +65 -0
  17. data/lib/riak/client/http_backend.rb +203 -0
  18. data/lib/riak/client/http_backend/configuration.rb +46 -0
  19. data/lib/riak/client/http_backend/key_streamer.rb +43 -0
  20. data/lib/riak/client/http_backend/object_methods.rb +94 -0
  21. data/lib/riak/client/http_backend/request_headers.rb +34 -0
  22. data/lib/riak/client/http_backend/transport_methods.rb +218 -0
  23. data/lib/riak/client/net_http_backend.rb +79 -0
  24. data/lib/riak/client/protobuffs_backend.rb +97 -0
  25. data/lib/riak/client/pump.rb +30 -0
  26. data/lib/riak/client/search.rb +94 -0
  27. data/lib/riak/core_ext.rb +6 -0
  28. data/lib/riak/core_ext/blank.rb +53 -0
  29. data/lib/riak/core_ext/extract_options.rb +7 -0
  30. data/lib/riak/core_ext/json.rb +15 -0
  31. data/lib/riak/core_ext/slice.rb +18 -0
  32. data/lib/riak/core_ext/stringify_keys.rb +10 -0
  33. data/lib/riak/core_ext/symbolize_keys.rb +10 -0
  34. data/lib/riak/core_ext/to_param.rb +31 -0
  35. data/lib/riak/encoding.rb +6 -0
  36. data/lib/riak/failed_request.rb +81 -0
  37. data/lib/riak/i18n.rb +3 -0
  38. data/lib/riak/json.rb +28 -0
  39. data/lib/riak/link.rb +85 -0
  40. data/lib/riak/locale/en.yml +48 -0
  41. data/lib/riak/map_reduce.rb +206 -0
  42. data/lib/riak/map_reduce/filter_builder.rb +94 -0
  43. data/lib/riak/map_reduce/phase.rb +98 -0
  44. data/lib/riak/map_reduce_error.rb +7 -0
  45. data/lib/riak/robject.rb +290 -0
  46. data/lib/riak/search.rb +3 -0
  47. data/lib/riak/serializers.rb +74 -0
  48. data/lib/riak/stamp.rb +77 -0
  49. data/lib/riak/test_server.rb +252 -0
  50. data/lib/riak/util/escape.rb +45 -0
  51. data/lib/riak/util/fiber1.8.rb +48 -0
  52. data/lib/riak/util/headers.rb +53 -0
  53. data/lib/riak/util/multipart.rb +52 -0
  54. data/lib/riak/util/multipart/stream_parser.rb +62 -0
  55. data/lib/riak/util/tcp_socket_extensions.rb +58 -0
  56. data/lib/riak/util/translation.rb +19 -0
  57. data/lib/riak/walk_spec.rb +105 -0
  58. data/riak-client.gemspec +55 -0
  59. data/seomoz-riak-client.gemspec +55 -0
  60. data/spec/fixtures/cat.jpg +0 -0
  61. data/spec/fixtures/multipart-blank.txt +7 -0
  62. data/spec/fixtures/multipart-mapreduce.txt +10 -0
  63. data/spec/fixtures/multipart-with-body.txt +16 -0
  64. data/spec/fixtures/server.cert.crt +15 -0
  65. data/spec/fixtures/server.cert.key +15 -0
  66. data/spec/fixtures/test.pem +1 -0
  67. data/spec/integration/riak/cache_store_spec.rb +154 -0
  68. data/spec/integration/riak/http_backends_spec.rb +58 -0
  69. data/spec/integration/riak/protobuffs_backends_spec.rb +32 -0
  70. data/spec/integration/riak/test_server_spec.rb +161 -0
  71. data/spec/riak/beefcake_protobuffs_backend_spec.rb +59 -0
  72. data/spec/riak/bucket_spec.rb +205 -0
  73. data/spec/riak/client_spec.rb +517 -0
  74. data/spec/riak/core_ext/to_param_spec.rb +15 -0
  75. data/spec/riak/escape_spec.rb +69 -0
  76. data/spec/riak/excon_backend_spec.rb +64 -0
  77. data/spec/riak/headers_spec.rb +38 -0
  78. data/spec/riak/http_backend/configuration_spec.rb +51 -0
  79. data/spec/riak/http_backend/object_methods_spec.rb +217 -0
  80. data/spec/riak/http_backend/transport_methods_spec.rb +117 -0
  81. data/spec/riak/http_backend_spec.rb +269 -0
  82. data/spec/riak/link_spec.rb +71 -0
  83. data/spec/riak/map_reduce/filter_builder_spec.rb +32 -0
  84. data/spec/riak/map_reduce/phase_spec.rb +136 -0
  85. data/spec/riak/map_reduce_spec.rb +310 -0
  86. data/spec/riak/multipart_spec.rb +23 -0
  87. data/spec/riak/net_http_backend_spec.rb +16 -0
  88. data/spec/riak/robject_spec.rb +427 -0
  89. data/spec/riak/search_spec.rb +178 -0
  90. data/spec/riak/serializers_spec.rb +93 -0
  91. data/spec/riak/stamp_spec.rb +54 -0
  92. data/spec/riak/stream_parser_spec.rb +53 -0
  93. data/spec/riak/walk_spec_spec.rb +195 -0
  94. data/spec/spec_helper.rb +39 -0
  95. data/spec/support/drb_mock_server.rb +39 -0
  96. data/spec/support/http_backend_implementation_examples.rb +266 -0
  97. data/spec/support/integration_setup.rb +10 -0
  98. data/spec/support/mock_server.rb +81 -0
  99. data/spec/support/mocks.rb +4 -0
  100. data/spec/support/test_server.yml.example +2 -0
  101. data/spec/support/unified_backend_examples.rb +255 -0
  102. metadata +271 -0
@@ -0,0 +1,2 @@
1
+ require 'riak'
2
+ require 'riak/cache_store'
@@ -0,0 +1,21 @@
1
+ require 'riak/encoding'
2
+ require 'riak/core_ext'
3
+ require 'riak/client'
4
+ require 'riak/map_reduce'
5
+ require 'riak/util/translation'
6
+
7
+ # The Riak module contains all aspects of the client interface to
8
+ # Riak.
9
+ module Riak
10
+ # Utility classes and mixins
11
+ module Util; end
12
+ extend Util::Translation
13
+
14
+ class << self
15
+ # Only change this if you really know what you're doing. Better to
16
+ # err on the side of caution and assume you don't.
17
+ # @private
18
+ attr_accessor :disable_list_keys_warnings
19
+ end
20
+ self.disable_list_keys_warnings = false
21
+ end
@@ -0,0 +1,215 @@
1
+
2
+ require 'riak/util/translation'
3
+ require 'riak/util/escape'
4
+ require 'riak/client'
5
+ require 'riak/robject'
6
+ require 'riak/failed_request'
7
+
8
+ module Riak
9
+ # Represents and encapsulates operations on a Riak bucket. You may retrieve a bucket
10
+ # using {Client#bucket}, or create it manually and retrieve its meta-information later.
11
+ class Bucket
12
+ include Util::Translation
13
+ include Util::Escape
14
+
15
+ # (Riak Search) The precommit specification for kv/search integration
16
+ SEARCH_PRECOMMIT_HOOK = {"mod" => "riak_search_kv_hook", "fun" => "precommit"}
17
+
18
+ # @return [Riak::Client] the associated client
19
+ attr_reader :client
20
+
21
+ # @return [String] the bucket name
22
+ attr_reader :name
23
+
24
+ # Create a Riak bucket manually.
25
+ # @param [Client] client the {Riak::Client} for this bucket
26
+ # @param [String] name the name of the bucket
27
+ def initialize(client, name)
28
+ raise ArgumentError, t("client_type", :client => client.inspect) unless Client === client
29
+ raise ArgumentError, t("string_type", :string => name.inspect) unless String === name
30
+ @client, @name = client, name
31
+ end
32
+
33
+ # Retrieves a list of keys in this bucket.
34
+ # If a block is given, keys will be streamed through
35
+ # the block (useful for large buckets). When streaming,
36
+ # results of the operation will not be returned to the caller.
37
+ # @yield [Array<String>] a list of keys from the current chunk
38
+ # @return [Array<String>] Keys in this bucket
39
+ # @note This operation has serious performance implications and
40
+ # should not be used in production applications.
41
+ def keys(&block)
42
+ warn(t('list_keys', :backtrace => caller.join("\n "))) unless Riak.disable_list_keys_warnings
43
+ if block_given?
44
+ @client.backend.list_keys(self, &block)
45
+ else
46
+ @client.backend.list_keys(self)
47
+ end
48
+ end
49
+
50
+ # Sets internal properties on the bucket
51
+ # Note: this results in a request to the Riak server!
52
+ # @param [Hash] properties new properties for the bucket
53
+ # @option properties [Fixnum] :n_val (3) The N value (replication factor)
54
+ # @option properties [true,false] :allow_mult (false) Whether to permit object siblings
55
+ # @option properties [true,false] :last_write_wins (false) Whether to ignore vclocks
56
+ # @option properties [Array<Hash>] :precommit ([]) precommit hooks
57
+ # @option properties [Array<Hash>] :postcommit ([])postcommit hooks
58
+ # @option properties [Fixnum,String] :r ("quorum") read quorum (numeric or
59
+ # symbolic)
60
+ # @option properties [Fixnum,String] :w ("quorum") write quorum (numeric or
61
+ # symbolic)
62
+ # @option properties [Fixnum,String] :dw ("quorum") durable write quorum
63
+ # (numeric or symbolic)
64
+ # @option properties [Fixnum,String] :rw ("quorum") delete quorum (numeric or
65
+ # symbolic)
66
+ # @return [Hash] the merged bucket properties
67
+ # @raise [FailedRequest] if the new properties were not accepted by the Riakserver
68
+ # @see #n_value, #allow_mult, #r, #w, #dw, #rw
69
+ def props=(properties)
70
+ raise ArgumentError, t("hash_type", :hash => properties.inspect) unless Hash === properties
71
+ props.merge!(properties)
72
+ @client.backend.set_bucket_props(self, properties)
73
+ props
74
+ end
75
+ alias :'properties=' :'props='
76
+
77
+ # @return [Hash] Internal Riak bucket properties.
78
+ # @see #props=
79
+ def props
80
+ @props ||= @client.backend.get_bucket_props(self)
81
+ end
82
+ alias :properties :props
83
+
84
+ # Retrieve an object from within the bucket.
85
+ # @param [String] key the key of the object to retrieve
86
+ # @param [Hash] options query parameters for the request
87
+ # @option options [Fixnum] :r - the read quorum for the request - how many nodes should concur on the read
88
+ # @return [Riak::RObject] the object
89
+ # @raise [FailedRequest] if the object is not found or some other error occurs
90
+ def get(key, options={})
91
+ @client.backend.fetch_object(self, key, options[:r])
92
+ end
93
+ alias :[] :get
94
+
95
+ # Create a new blank object
96
+ # @param [String] key the key of the new object
97
+ # @return [RObject] the new, unsaved object
98
+ def new(key=nil)
99
+ RObject.new(self, key).tap do |obj|
100
+ obj.content_type = "application/json"
101
+ end
102
+ end
103
+
104
+ # Fetches an object if it exists, otherwise creates a new one with the given key
105
+ # @param [String] key the key to fetch or create
106
+ # @return [RObject] the new or existing object
107
+ def get_or_new(key, options={})
108
+ begin
109
+ get(key, options)
110
+ rescue Riak::FailedRequest => fr
111
+ if fr.not_found?
112
+ new(key)
113
+ else
114
+ raise fr
115
+ end
116
+ end
117
+ end
118
+
119
+ # Checks whether an object exists in Riak.
120
+ # @param [String] key the key to check
121
+ # @param [Hash] options quorum options
122
+ # @option options [Fixnum] :r - the read quorum value for the request (R)
123
+ # @return [true, false] whether the key exists in this bucket
124
+ def exists?(key, options={})
125
+ begin
126
+ get(key, options)
127
+ true
128
+ rescue Riak::FailedRequest
129
+ false
130
+ end
131
+ end
132
+ alias :exist? :exists?
133
+
134
+ # Deletes a key from the bucket
135
+ # @param [String] key the key to delete
136
+ # @param [Hash] options quorum options
137
+ # @option options [Fixnum] :rw - the read/write quorum for the delete
138
+ def delete(key, options={})
139
+ client.backend.delete_object(self, key, options[:rw])
140
+ end
141
+
142
+ # @return [true, false] whether the bucket allows divergent siblings
143
+ def allow_mult
144
+ props['allow_mult']
145
+ end
146
+
147
+ # Set the allow_mult property. *NOTE* This will result in a PUT request to Riak.
148
+ # @param [true, false] value whether the bucket should allow siblings
149
+ def allow_mult=(value)
150
+ self.props = {'allow_mult' => value}
151
+ value
152
+ end
153
+
154
+ # @return [Fixnum] the N value, or number of replicas for this bucket
155
+ def n_value
156
+ props['n_val']
157
+ end
158
+ alias :n_val :n_value
159
+
160
+ # Set the N value (number of replicas). *NOTE* This will result in a PUT request to Riak.
161
+ # Setting this value after the bucket has objects stored in it may have unpredictable results.
162
+ # @param [Fixnum] value the number of replicas the bucket should keep of each object
163
+ def n_value=(value)
164
+ self.props = {'n_val' => value}
165
+ value
166
+ end
167
+ alias :'n_val=' :'n_value='
168
+
169
+ [:r,:w,:dw,:rw].each do |q|
170
+ class_eval <<-CODE
171
+ def #{q}
172
+ props["#{q}"]
173
+ end
174
+
175
+ def #{q}=(value)
176
+ self.props = {"#{q}" => value}
177
+ value
178
+ end
179
+ CODE
180
+ end
181
+
182
+ # (Riak Search) Installs a precommit hook that automatically indexes objects
183
+ # into riak_search.
184
+ def enable_index!
185
+ unless is_indexed?
186
+ self.props = {"precommit" => (props['precommit'] + [SEARCH_PRECOMMIT_HOOK])}
187
+ end
188
+ end
189
+
190
+ # (Riak Search) Removes the precommit hook that automatically indexes objects
191
+ # into riak_search.
192
+ def disable_index!
193
+ if is_indexed?
194
+ self.props = {"precommit" => (props['precommit'] - [SEARCH_PRECOMMIT_HOOK])}
195
+ end
196
+ end
197
+
198
+ # (Riak Search) Detects whether the bucket is automatically indexed into
199
+ # riak_search.
200
+ # @return [true,false] whether the bucket includes the search indexing hook
201
+ def is_indexed?
202
+ props['precommit'].include?(SEARCH_PRECOMMIT_HOOK)
203
+ end
204
+
205
+ # @return [String] a representation suitable for IRB and debugging output
206
+ def inspect
207
+ "#<Riak::Bucket {#{name}}#{" keys=[#{keys.join(',')}]" if defined?(@keys)}>"
208
+ end
209
+
210
+ # @return [true,false] whether the other is equivalent
211
+ def ==(other)
212
+ Bucket === other && other.client == client && other.name == name
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,84 @@
1
+
2
+ require 'yaml'
3
+ require 'riak/client'
4
+ require 'riak/bucket'
5
+ require 'riak/robject'
6
+ require 'riak/failed_request'
7
+ require 'active_support/version'
8
+
9
+ if ActiveSupport::VERSION::STRING < "3.0.0"
10
+ raise LoadError, "ActiveSupport 3.0.0 or greater is required to use Riak::CacheStore."
11
+ else
12
+ require 'active_support/cache'
13
+ end
14
+
15
+ module Riak
16
+ # An ActiveSupport::Cache::Store implementation that uses Riak.
17
+ # Compatible only with ActiveSupport version 3 or greater.
18
+ class CacheStore < ActiveSupport::Cache::Store
19
+ attr_accessor :client
20
+
21
+ # Creates a Riak-backed cache store.
22
+ def initialize(options = {})
23
+ super
24
+ @bucket_name = options.delete(:bucket) || '_cache'
25
+ @n_value = options.delete(:n_value) || 2
26
+ @r = options.delete(:r) || 1
27
+ @w = options.delete(:w) || 1
28
+ @dw = options.delete(:dw) || 0
29
+ @rw = options.delete(:rw) || "quorum"
30
+ @client = Riak::Client.new(options)
31
+ set_bucket_defaults
32
+ end
33
+
34
+ def bucket
35
+ @bucket ||= @client.bucket(@bucket_name)
36
+ end
37
+
38
+ def delete_matched(matcher, options={})
39
+ instrument(:delete_matched, matcher) do
40
+ bucket.keys do |keys|
41
+ keys.grep(matcher).each do |k|
42
+ bucket.delete(k)
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ protected
49
+ def set_bucket_defaults
50
+ begin
51
+ new_values = {}
52
+ new_values['n_val'] = @n_value unless bucket.n_value == @n_value
53
+ new_values['r'] = @r unless bucket.r == @r
54
+ new_values['w'] = @w unless bucket.w == @w
55
+ new_values['dw'] = @dw unless bucket.dw == @dw
56
+ new_values['rw'] = @rw unless bucket.rw == @rw
57
+ bucket.props = new_values unless new_values.empty?
58
+ rescue
59
+ end
60
+ end
61
+
62
+ def write_entry(key, value, options={})
63
+ object = bucket.get_or_new(key)
64
+ object.content_type = 'application/yaml'
65
+ object.data = value
66
+ object.store
67
+ end
68
+
69
+ def read_entry(key, options={})
70
+ begin
71
+ bucket.get(key).data
72
+ rescue Riak::FailedRequest => fr
73
+ raise fr unless fr.not_found?
74
+ nil
75
+ end
76
+ end
77
+
78
+ def delete_entry(key, options={})
79
+ bucket.delete(key)
80
+ end
81
+ end
82
+ end
83
+
84
+ ActiveSupport::Cache::RiakStore = Riak::CacheStore unless defined?(ActiveSupport::Cache::RiakStore)
@@ -0,0 +1,415 @@
1
+ require 'tempfile'
2
+ require 'delegate'
3
+ require 'riak'
4
+ require 'riak/util/translation'
5
+ require 'riak/util/escape'
6
+ require 'riak/failed_request'
7
+ require 'riak/client/search'
8
+ require 'riak/client/http_backend'
9
+ require 'riak/client/net_http_backend'
10
+ require 'riak/client/excon_backend'
11
+ require 'riak/client/protobuffs_backend'
12
+ require 'riak/client/beefcake_protobuffs_backend'
13
+ require 'riak/bucket'
14
+ require 'riak/stamp'
15
+
16
+ module Riak
17
+ # A client connection to Riak.
18
+ class Client
19
+ include Util::Translation
20
+ include Util::Escape
21
+
22
+ # When using integer client IDs, the exclusive upper-bound of valid values.
23
+ MAX_CLIENT_ID = 4294967296
24
+
25
+ # Array of valid protocols
26
+ PROTOCOLS = %w[http https pbc]
27
+
28
+ # Regexp for validating hostnames, lifted from uri.rb in Ruby 1.8.6
29
+ HOST_REGEX = /^(?:(?:(?:[a-zA-Z\d](?:[-a-zA-Z\d]*[a-zA-Z\d])?)\.)*(?:[a-zA-Z](?:[-a-zA-Z\d]*[a-zA-Z\d])?)\.?|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[(?:(?:[a-fA-F\d]{1,4}:)*(?:[a-fA-F\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|(?:(?:[a-fA-F\d]{1,4}:)*[a-fA-F\d]{1,4})?::(?:(?:[a-fA-F\d]{1,4}:)*(?:[a-fA-F\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))?)\])$/n
30
+
31
+ VALID_OPTIONS = [:protocol, :host, :port, :http_port, :pb_port, :prefix, :client_id, :mapred, :luwak, :solr, :http_backend, :protobuffs_backend, :ssl, :basic_auth]
32
+
33
+ # @return [String] The protocol to use for the Riak endpoint
34
+ attr_reader :protocol
35
+
36
+ # @return [String] The host or IP address for the Riak endpoint
37
+ attr_reader :host
38
+
39
+ # @return [Fixnum] The HTTP(S) port for the Riak endpoint
40
+ attr_reader :http_port
41
+
42
+ # @return [Fixnum] The Protocol Buffers port for the Riak endpoint
43
+ attr_reader :pb_port
44
+
45
+ # @return [String] The user:pass for http basic authentication
46
+ attr_reader :basic_auth
47
+
48
+ # @return [Hash|nil] The SSL options that get built when using SSL
49
+ attr_reader :ssl_options
50
+
51
+ # @return [String] The internal client ID used by Riak to route responses
52
+ attr_reader :client_id
53
+
54
+ # @attr_writer [Hash|nil] The writer that will build valid SSL options
55
+ # from the provided config
56
+ attr_writer :ssl
57
+
58
+ # @return [String] The URL path prefix to the "raw" HTTP endpoint
59
+ attr_accessor :prefix
60
+
61
+ # @return [String] The URL path to the map-reduce HTTP endpoint
62
+ attr_accessor :mapred
63
+
64
+ # @return [String] The URL path to the luwak HTTP endpoint
65
+ attr_accessor :luwak
66
+
67
+ # @return [String] The URL path prefix to the Solr HTTP endpoint
68
+ attr_accessor :solr
69
+
70
+ # @return [Symbol] The HTTP backend/client to use
71
+ attr_accessor :http_backend
72
+
73
+ # @return [Symbol] The Protocol Buffers backend/client to use
74
+ attr_accessor :protobuffs_backend
75
+
76
+ # Creates a client connection to Riak
77
+ # @param [Hash] options configuration options for the client
78
+ # @option options [String] :host ('127.0.0.1') The host or IP address for the Riak endpoint
79
+ # @option options [Fixnum] :port (8098) The port of the Riak HTTP endpoint
80
+ # @option options [String] :prefix ('/riak/') The URL path prefix to the main HTTP endpoint
81
+ # @option options [String] :mapred ('/mapred') The path to the map-reduce HTTP endpoint
82
+ # @option options [Fixnum, String] :client_id (rand(MAX_CLIENT_ID)) The internal client ID used by Riak to route responses
83
+ # @option options [String, Symbol] :http_backend (:NetHTTP) which HTTP backend to use
84
+ # @option options [String, Symbol] :protobuffs_backend (:Beefcake) which Protocol Buffers backend to use
85
+ # @raise [ArgumentError] raised if any invalid options are given
86
+ def initialize(options={})
87
+ unless (options.keys - VALID_OPTIONS).empty?
88
+ raise ArgumentError, t("invalid_options")
89
+ end
90
+ self.protocol = options[:protocol] || "http"
91
+ self.ssl = options[:ssl] if options[:ssl]
92
+ self.host = options[:host] || "127.0.0.1"
93
+ self.http_port = options[:http_port] || 8098
94
+ self.pb_port = options[:pb_port] || 8087
95
+ self.port = options[:port] if options[:port]
96
+ self.prefix = options[:prefix] || "/riak/"
97
+ self.mapred = options[:mapred] || "/mapred"
98
+ self.luwak = options[:luwak] || "/luwak"
99
+ self.solr = options[:solr] || "/solr"
100
+ self.http_backend = options[:http_backend] || :NetHTTP
101
+ self.protobuffs_backend = options[:protobuffs_backend] || :Beefcake
102
+ self.basic_auth = options[:basic_auth] if options[:basic_auth]
103
+ self.client_id = options[:client_id] if options[:client_id]
104
+ end
105
+
106
+ # Set the client ID for this client. Must be a string or Fixnum value 0 =< value < MAX_CLIENT_ID.
107
+ # @param [String, Fixnum] value The internal client ID used by Riak to route responses
108
+ # @raise [ArgumentError] when an invalid client ID is given
109
+ # @return [String] the assigned client ID
110
+ def client_id=(value)
111
+ value = case value
112
+ when 0...MAX_CLIENT_ID, String
113
+ value
114
+ else
115
+ raise ArgumentError, t("invalid_client_id", :max_id => MAX_CLIENT_ID)
116
+ end
117
+ backend.set_client_id value if backend.respond_to?(:set_client_id)
118
+ @client_id = value
119
+ end
120
+
121
+ def client_id
122
+ @client_id ||= backend.respond_to?(:get_client_id) ? backend.get_client_id : make_client_id
123
+ end
124
+
125
+ # Set the protocol of the Riak endpoint. Value must be in the
126
+ # Riak::Client::PROTOCOLS array.
127
+ # @raise [ArgumentError] if the protocol is not in PROTOCOLS
128
+ # @return [String] the protocol being assigned
129
+ def protocol=(value)
130
+ unless PROTOCOLS.include?(value.to_s)
131
+ raise ArgumentError, t("protocol_invalid", :invalid => value, :valid => PROTOCOLS.join(', '))
132
+ end
133
+ @ssl_options ||= {} if value == 'https'
134
+ @ssl_options = nil if value == 'http'
135
+ @backend = nil
136
+ @protocol = value
137
+ end
138
+
139
+ # Set the hostname of the Riak endpoint. Must be an IPv4, IPv6, or valid hostname
140
+ # @param [String] value The host or IP address for the Riak endpoint
141
+ # @raise [ArgumentError] if an invalid hostname is given
142
+ # @return [String] the assigned hostname
143
+ def host=(value)
144
+ raise ArgumentError, t("hostname_invalid") unless String === value && value.present? && value =~ HOST_REGEX
145
+ @host = value
146
+ end
147
+
148
+ # @return [Fixnum] The port of the Riak endpoint
149
+ # @deprecated Ports for HTTP(S) and Protocol Buffers are
150
+ # segregated. Use {#http_port} or {#pb_port}.
151
+ def port
152
+ warn(t('deprecated.port', :backtrace => caller.join("\n")))
153
+ case protocol
154
+ when /http/i
155
+ http_port
156
+ when /pbc/i
157
+ pb_port
158
+ end
159
+ end
160
+
161
+ # Set the port number of the Riak endpoint. This must be an
162
+ # integer between 0 and 65535.
163
+ # @deprecated Ports for HTTP(S) and Protocol Buffers are
164
+ # segregated. Use {#http_port=} or {#pb_port=}.
165
+ # @param [Fixnum] value The port number of the Riak endpoint
166
+ # @raise [ArgumentError] if an invalid port number is given
167
+ # @return [Fixnum] the assigned port number
168
+ def port=(value)
169
+ warn(t('deprecated.port', :backtrace => caller[0..2].join("\n ")))
170
+ raise ArgumentError, t("port_invalid") unless (0..65535).include?(value)
171
+ case protocol
172
+ when /http/i
173
+ self.http_port = value
174
+ when /pbc/i
175
+ self.pb_port = value
176
+ end
177
+ end
178
+
179
+ # Set the HTTP(S) port for the Riak endpoint
180
+ # @param [Fixnum] value The HTTP port number of the Riak endpoint
181
+ # @raise [ArgumentError] if an invalid port number is given
182
+ # @return [Fixnum] the assigned port number
183
+ def http_port=(value)
184
+ raise ArgumentError, t("port_invalid") unless (0..65535).include?(value)
185
+ @http_port = value
186
+ end
187
+
188
+ # Set the Protocol Buffers port for the Riak endpoint
189
+ # @param [Fixnum] value The Protocol Buffers port number of the Riak endpoint
190
+ # @raise [ArgumentError] if an invalid port number is given
191
+ # @return [Fixnum] the assigned port number
192
+ def pb_port=(value)
193
+ raise ArgumentError, t("port_invalid") unless (0..65535).include?(value)
194
+ @pb_port = value
195
+ end
196
+
197
+
198
+ # Sets the HTTP Basic Authentication credentials.
199
+ # @param [String] value an auth string in the form "user:password"
200
+ def basic_auth=(value)
201
+ raise ArgumentError, t("invalid_basic_auth") unless value.to_s.split(':').length === 2
202
+ @basic_auth = value
203
+ end
204
+
205
+ # Sets the desired HTTP backend
206
+ def http_backend=(value)
207
+ @http, @backend = nil, nil
208
+ @http_backend = value
209
+ end
210
+
211
+ # Sets the desired Protocol Buffers backend
212
+ def protobuffs_backend=(value)
213
+ @protobuffs, @backend = nil, nil
214
+ @protobuffs_backend = value
215
+ end
216
+
217
+ # Enables or disables SSL on the client to be utilized by the HTTP Backends
218
+ def ssl=(value)
219
+ @ssl_options = Hash === value ? value : {}
220
+ value ? ssl_enable : ssl_disable
221
+ end
222
+
223
+ # Checks if SSL is enabled for HTTP
224
+ def ssl_enabled?
225
+ protocol == 'https' || @ssl_options.present?
226
+ end
227
+
228
+ # Automatically detects and returns an appropriate HTTP backend.
229
+ # The HTTP backend is used internally by the Riak client, but can also
230
+ # be used to access the server directly.
231
+ # @return [HTTPBackend] the HTTP backend for this client
232
+ def http
233
+ @http ||= begin
234
+ klass = self.class.const_get("#{@http_backend}Backend")
235
+ if klass.configured?
236
+ klass.new(self)
237
+ else
238
+ raise t('http_configuration', :backend => @http_backend)
239
+ end
240
+ end
241
+ end
242
+
243
+ # Automatically detects and returns an appropriate Protocol
244
+ # Buffers backend. The Protocol Buffers backend is used
245
+ # internally by the Riak client but can also be used to access the
246
+ # server directly.
247
+ # @return [ProtobuffsBackend] the Protocol Buffers backend for
248
+ # this client
249
+ def protobuffs
250
+ @protobuffs ||= begin
251
+ klass = self.class.const_get("#{@protobuffs_backend}ProtobuffsBackend")
252
+ if klass.configured?
253
+ klass.new(self)
254
+ else
255
+ raise t('protobuffs_configuration', :backend => @protobuffs_backend)
256
+ end
257
+ end
258
+ end
259
+
260
+ # Returns a backend for operations that are protocol-independent.
261
+ # You can change which type of backend is used by setting the
262
+ # {#protocol}.
263
+ # @return [HTTPBackend,ProtobuffsBackend] an appropriate client backend
264
+ def backend
265
+ @backend ||= case @protocol.to_s
266
+ when /https?/i
267
+ http
268
+ when /pbc/i
269
+ protobuffs
270
+ end
271
+ end
272
+
273
+ # Pings the Riak server to check for liveness.
274
+ # @return [true,false] whether the Riak server is alive and reachable
275
+ def ping
276
+ backend.ping
277
+ end
278
+
279
+ # Retrieves a bucket from Riak.
280
+ # @param [String] bucket the bucket to retrieve
281
+ # @param [Hash] options options for retrieving the bucket
282
+ # @option options [Boolean] :props (false) whether to retreive the bucket properties
283
+ # @return [Bucket] the requested bucket
284
+ def bucket(name, options={})
285
+ unless (options.keys - [:props]).empty?
286
+ raise ArgumentError, "invalid options"
287
+ end
288
+ @bucket_cache ||= {}
289
+ (@bucket_cache[name] ||= Bucket.new(self, name)).tap do |b|
290
+ b.props if options[:props]
291
+ end
292
+ end
293
+ alias :[] :bucket
294
+
295
+ # Lists buckets which have keys stored in them.
296
+ # @note This is an expensive operation and should be used only
297
+ # in development.
298
+ # @return [Array<Bucket>] a list of buckets
299
+ def buckets
300
+ warn(t('list_buckets', :backtrace => caller.join("\n "))) unless Riak.disable_list_keys_warnings
301
+ backend.list_buckets.map {|name| Bucket.new(self, name) }
302
+ end
303
+ alias :list_buckets :buckets
304
+
305
+ # Exposes a {Stamp} object for use in generating unique
306
+ # identifiers.
307
+ # @return [Stamp] an ID generator
308
+ # @see Stamp#next
309
+ def stamp
310
+ @stamp ||= Riak::Stamp.new(self)
311
+ end
312
+
313
+ # Stores a large file/IO-like object in Riak via the "Luwak" interface.
314
+ # @overload store_file(filename, content_type, data)
315
+ # Stores the file at the given key/filename
316
+ # @param [String] filename the key/filename for the object
317
+ # @param [String] content_type the MIME Content-Type for the data
318
+ # @param [IO, String] data the contents of the file
319
+ # @overload store_file(content_type, data)
320
+ # Stores the file with a server-determined key/filename
321
+ # @param [String] content_type the MIME Content-Type for the data
322
+ # @param [String, #read] data the contents of the file
323
+ # @return [String] the key/filename where the object was stored
324
+ def store_file(*args)
325
+ data, content_type, filename = args.reverse
326
+ if filename
327
+ http.put(204, luwak, escape(filename), data, {"Content-Type" => content_type})
328
+ filename
329
+ else
330
+ response = http.post(201, luwak, data, {"Content-Type" => content_type})
331
+ response[:headers]["location"].first.split("/").last
332
+ end
333
+ end
334
+
335
+ # Retrieves a large file/IO object from Riak via the "Luwak"
336
+ # interface. Streams the data to a temporary file unless a block
337
+ # is given.
338
+ # @param [String] filename the key/filename for the object
339
+ # @return [IO, nil] the file (also having content_type and
340
+ # original_filename accessors). The file will need to be
341
+ # reopened to be read. nil will be returned if a block is given.
342
+ # @yield [chunk] stream contents of the file through the
343
+ # block. Passing the block will result in nil being returned
344
+ # from the method.
345
+ # @yieldparam [String] chunk a single chunk of the object's data
346
+ def get_file(filename, &block)
347
+ if block_given?
348
+ http.get(200, luwak, escape(filename), &block)
349
+ nil
350
+ else
351
+ tmpfile = LuwakFile.new(escape(filename))
352
+ begin
353
+ response = http.get(200, luwak, escape(filename)) do |chunk|
354
+ tmpfile.write chunk
355
+ end
356
+ tmpfile.content_type = response[:headers]['content-type'].first
357
+ tmpfile
358
+ ensure
359
+ tmpfile.close
360
+ end
361
+ end
362
+ end
363
+
364
+ # Deletes a file stored via the "Luwak" interface
365
+ # @param [String] filename the key/filename to delete
366
+ def delete_file(filename)
367
+ http.delete([204,404], luwak, escape(filename))
368
+ true
369
+ end
370
+
371
+ # Checks whether a file exists in "Luwak".
372
+ # @param [String] key the key to check
373
+ # @return [true, false] whether the key exists in "Luwak"
374
+ def file_exists?(key)
375
+ result = http.head([200,404], luwak, escape(key))
376
+ result[:code] == 200
377
+ end
378
+ alias :file_exist? :file_exists?
379
+
380
+ # @return [String] A representation suitable for IRB and debugging output.
381
+ def inspect
382
+ "#<Riak::Client #{protocol}://#{host}:#{protocol == 'pbc' ? pb_port : http_port}>"
383
+ end
384
+
385
+ private
386
+ def make_client_id
387
+ rand(MAX_CLIENT_ID)
388
+ end
389
+
390
+ def ssl_enable
391
+ self.protocol = 'https'
392
+ @ssl_options[:pem] = File.read(@ssl_options[:pem_file]) if @ssl_options[:pem_file]
393
+ @ssl_options[:verify_mode] ||= "peer" if @ssl_options.stringify_keys.any? {|k,v| %w[pem ca_file ca_path].include?(k)}
394
+ @ssl_options[:verify_mode] ||= "none"
395
+ raise ArgumentError.new(t('invalid_ssl_verify_mode', :invalid => @ssl_options[:verify_mode])) unless %w[none peer].include?(@ssl_options[:verify_mode])
396
+
397
+ @ssl_options
398
+ end
399
+
400
+ def ssl_disable
401
+ self.protocol = 'http'
402
+ @ssl_options = nil
403
+ end
404
+
405
+ # @private
406
+ class LuwakFile < DelegateClass(Tempfile)
407
+ attr_accessor :original_filename, :content_type
408
+ alias :key :original_filename
409
+ def initialize(fn)
410
+ super(Tempfile.new(fn))
411
+ @original_filename = fn
412
+ end
413
+ end
414
+ end
415
+ end