timcharper-redis 0.0.3.4

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Ezra Zygmuntowicz
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,31 @@
1
+ # redis-rb
2
+
3
+ A ruby client library for the redis key value storage system.
4
+
5
+ ## Information about redis
6
+
7
+ Redis is a key value store with some interesting features:
8
+ 1. It's fast.
9
+ 2. Keys are strings but values can have types of "NONE", "STRING", "LIST", or "SET". List's can be atomically push'd, pop'd, lpush'd, lpop'd and indexed. This allows you to store things like lists of comments under one key while retaining the ability to append comments without reading and putting back the whole list.
10
+
11
+ See [redis on code.google.com](http://code.google.com/p/redis/wiki/README) for more information.
12
+
13
+ ## Dependencies
14
+
15
+ 1. redis -
16
+
17
+ rake redis:install
18
+
19
+ 2. dtach -
20
+
21
+ rake dtach:install
22
+
23
+ 3. svn - git is the new black, but we need it for the google codes.
24
+
25
+ ## Setup
26
+
27
+ Use the tasks mentioned above (in Dependencies) to get your machine setup.
28
+
29
+ ## Examples
30
+
31
+ Check the examples/ directory. *Note* you need to have redis-server running first.
data/Rakefile ADDED
@@ -0,0 +1,59 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rubygems/specification'
4
+ require 'date'
5
+ require 'spec/rake/spectask'
6
+ require 'tasks/redis.tasks'
7
+
8
+
9
+ GEM = 'redis'
10
+ GEM_NAME = 'redis'
11
+ GEM_VERSION = '0.0.3.3'
12
+ AUTHORS = ['Ezra Zygmuntowicz', 'Taylor Weibley', 'Matthew Clark']
13
+ EMAIL = "matt.clark@punchstock.com"
14
+ HOMEPAGE = "http://github.com/winescout/redis-rb"
15
+ SUMMARY = "Ruby client library for redis key value storage server"
16
+
17
+ spec = Gem::Specification.new do |s|
18
+ s.name = GEM
19
+ s.version = GEM_VERSION
20
+ s.platform = Gem::Platform::RUBY
21
+ s.has_rdoc = true
22
+ s.extra_rdoc_files = ["LICENSE"]
23
+ s.summary = SUMMARY
24
+ s.description = s.summary
25
+ s.authors = AUTHORS
26
+ s.email = EMAIL
27
+ s.homepage = HOMEPAGE
28
+
29
+ # Uncomment this to add a dependency
30
+ # s.add_dependency "foo"
31
+
32
+ s.require_path = 'lib'
33
+ s.autorequire = GEM
34
+ s.files = %w(LICENSE README.markdown Rakefile) + Dir.glob("{lib,spec}/**/*")
35
+ end
36
+
37
+ task :default => :spec
38
+
39
+ desc "Run specs"
40
+ Spec::Rake::SpecTask.new do |t|
41
+ t.spec_files = FileList['spec/**/*_spec.rb']
42
+ t.spec_opts = %w(-fs --color)
43
+ end
44
+
45
+ Rake::GemPackageTask.new(spec) do |pkg|
46
+ pkg.gem_spec = spec
47
+ end
48
+
49
+ desc "install the gem locally"
50
+ task :install => [:package] do
51
+ sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
52
+ end
53
+
54
+ desc "create a gemspec file"
55
+ task :make_spec do
56
+ File.open("#{GEM}.gemspec", "w") do |file|
57
+ file.puts spec.to_ruby
58
+ end
59
+ end
data/lib/dist_redis.rb ADDED
@@ -0,0 +1,111 @@
1
+ require 'redis'
2
+ require 'hash_ring'
3
+ class DistRedis
4
+ attr_reader :ring
5
+ def initialize(*servers)
6
+ srvs = []
7
+ servers.each do |s|
8
+ server, port = s.split(':')
9
+ srvs << Redis.new(:host => server, :port => port)
10
+ end
11
+ @ring = HashRing.new srvs
12
+ end
13
+
14
+ def node_for_key(key)
15
+ if key =~ /\{(.*)?\}/
16
+ key = $1
17
+ end
18
+ @ring.get_node(key)
19
+ end
20
+
21
+ def add_server(server)
22
+ server, port = server.split(':')
23
+ @ring.add_node Redis.new(:host => server, :port => port)
24
+ end
25
+
26
+ def method_missing(sym, *args, &blk)
27
+ if redis = node_for_key(args.first.to_s)
28
+ redis.send sym, *args, &blk
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ def keys(glob)
35
+ keyz = []
36
+ @ring.nodes.each do |red|
37
+ keyz.concat red.keys(glob)
38
+ end
39
+ keyz
40
+ end
41
+
42
+ def save
43
+ @ring.nodes.each do |red|
44
+ red.save
45
+ end
46
+ end
47
+
48
+ def bgsave
49
+ @ring.nodes.each do |red|
50
+ red.bgsave
51
+ end
52
+ end
53
+
54
+ def quit
55
+ @ring.nodes.each do |red|
56
+ red.quit
57
+ end
58
+ end
59
+
60
+ def delete_cloud!
61
+ @ring.nodes.each do |red|
62
+ red.keys("*").each do |key|
63
+ red.delete key
64
+ end
65
+ end
66
+ end
67
+
68
+ end
69
+
70
+
71
+ if __FILE__ == $0
72
+
73
+ r = DistRedis.new 'localhost:6379', 'localhost:6380', 'localhost:6381', 'localhost:6382'
74
+ r['urmom'] = 'urmom'
75
+ r['urdad'] = 'urdad'
76
+ r['urmom1'] = 'urmom1'
77
+ r['urdad1'] = 'urdad1'
78
+ r['urmom2'] = 'urmom2'
79
+ r['urdad2'] = 'urdad2'
80
+ r['urmom3'] = 'urmom3'
81
+ r['urdad3'] = 'urdad3'
82
+ p r['urmom']
83
+ p r['urdad']
84
+ p r['urmom1']
85
+ p r['urdad1']
86
+ p r['urmom2']
87
+ p r['urdad2']
88
+ p r['urmom3']
89
+ p r['urdad3']
90
+
91
+ r.push_tail 'listor', 'foo1'
92
+ r.push_tail 'listor', 'foo2'
93
+ r.push_tail 'listor', 'foo3'
94
+ r.push_tail 'listor', 'foo4'
95
+ r.push_tail 'listor', 'foo5'
96
+
97
+ p r.pop_tail('listor')
98
+ p r.pop_tail('listor')
99
+ p r.pop_tail('listor')
100
+ p r.pop_tail('listor')
101
+ p r.pop_tail('listor')
102
+
103
+ puts "key distribution:"
104
+
105
+ r.ring.nodes.each do |red|
106
+ p [red.port, red.keys("*")]
107
+ end
108
+ r.delete_cloud!
109
+ p r.keys('*')
110
+
111
+ end
data/lib/hash_ring.rb ADDED
@@ -0,0 +1,127 @@
1
+ require 'zlib'
2
+
3
+ class HashRing
4
+
5
+ POINTS_PER_SERVER = 160 # this is the default in libmemcached
6
+
7
+ attr_reader :ring, :sorted_keys, :replicas, :nodes
8
+
9
+ # nodes is a list of objects that have a proper to_s representation.
10
+ # replicas indicates how many virtual points should be used pr. node,
11
+ # replicas are required to improve the distribution.
12
+ def initialize(nodes=[], replicas=POINTS_PER_SERVER)
13
+ @replicas = replicas
14
+ @ring = {}
15
+ @nodes = []
16
+ @sorted_keys = []
17
+ nodes.each do |node|
18
+ add_node(node)
19
+ end
20
+ end
21
+
22
+ # Adds a `node` to the hash ring (including a number of replicas).
23
+ def add_node(node)
24
+ @nodes << node
25
+ @replicas.times do |i|
26
+ key = Zlib.crc32("#{node}:#{i}")
27
+ @ring[key] = node
28
+ @sorted_keys << key
29
+ end
30
+ @sorted_keys.sort!
31
+ end
32
+
33
+ def remove_node(node)
34
+ @replicas.times do |i|
35
+ key = Zlib.crc32("#{node}:#{count}")
36
+ @ring.delete(key)
37
+ @sorted_keys.reject! {|k| k == key}
38
+ end
39
+ end
40
+
41
+ # get the node in the hash ring for this key
42
+ def get_node(key)
43
+ get_node_pos(key)[0]
44
+ end
45
+
46
+ def get_node_pos(key)
47
+ return [nil,nil] if @ring.size == 0
48
+ crc = Zlib.crc32(key)
49
+ idx = HashRing.binary_search(@sorted_keys, crc)
50
+ return [@ring[@sorted_keys[idx]], idx]
51
+ end
52
+
53
+ def iter_nodes(key)
54
+ return [nil,nil] if @ring.size == 0
55
+ node, pos = get_node_pos(key)
56
+ @sorted_keys[pos..-1].each do |k|
57
+ yield @ring[k]
58
+ end
59
+ end
60
+
61
+ class << self
62
+
63
+ # gem install RubyInline to use this code
64
+ # Native extension to perform the binary search within the hashring.
65
+ # There's a pure ruby version below so this is purely optional
66
+ # for performance. In testing 20k gets and sets, the native
67
+ # binary search shaved about 12% off the runtime (9sec -> 8sec).
68
+ begin
69
+ require 'inline'
70
+ inline do |builder|
71
+ builder.c <<-EOM
72
+ int binary_search(VALUE ary, unsigned int r) {
73
+ int upper = RARRAY_LEN(ary) - 1;
74
+ int lower = 0;
75
+ int idx = 0;
76
+
77
+ while (lower <= upper) {
78
+ idx = (lower + upper) / 2;
79
+
80
+ VALUE continuumValue = RARRAY_PTR(ary)[idx];
81
+ unsigned int l = NUM2UINT(continuumValue);
82
+ if (l == r) {
83
+ return idx;
84
+ }
85
+ else if (l > r) {
86
+ upper = idx - 1;
87
+ }
88
+ else {
89
+ lower = idx + 1;
90
+ }
91
+ }
92
+ return upper;
93
+ }
94
+ EOM
95
+ end
96
+ rescue Exception => e
97
+ # Find the closest index in HashRing with value <= the given value
98
+ def binary_search(ary, value, &block)
99
+ upper = ary.size - 1
100
+ lower = 0
101
+ idx = 0
102
+
103
+ while(lower <= upper) do
104
+ idx = (lower + upper) / 2
105
+ comp = ary[idx] <=> value
106
+
107
+ if comp == 0
108
+ return idx
109
+ elsif comp > 0
110
+ upper = idx - 1
111
+ else
112
+ lower = idx + 1
113
+ end
114
+ end
115
+ return upper
116
+ end
117
+
118
+ end
119
+ end
120
+
121
+ end
122
+
123
+ # ring = HashRing.new ['server1', 'server2', 'server3']
124
+ # p ring
125
+ # #
126
+ # p ring.get_node "kjhjkjlkjlkkh"
127
+ #
data/lib/pipeline.rb ADDED
@@ -0,0 +1,31 @@
1
+ require "redis"
2
+
3
+ class Redis
4
+ class Pipeline < Redis
5
+ BUFFER_SIZE = 50_000
6
+
7
+ def initialize(redis)
8
+ @redis = redis
9
+ @commands = []
10
+ end
11
+
12
+ def get_response
13
+ end
14
+
15
+ def write(data)
16
+ @commands << data
17
+ write_and_read if @commands.size >= BUFFER_SIZE
18
+ end
19
+
20
+ def finish
21
+ write_and_read
22
+ end
23
+
24
+ def write_and_read
25
+ @redis.write @commands.join
26
+ @redis.read_socket
27
+ @commands.clear
28
+ end
29
+
30
+ end
31
+ end
data/lib/redis.rb ADDED
@@ -0,0 +1,504 @@
1
+ require 'socket'
2
+ require 'set'
3
+ require File.join(File.dirname(__FILE__),'server')
4
+ require File.join(File.dirname(__FILE__),'pipeline')
5
+
6
+
7
+ class RedisError < StandardError
8
+ end
9
+ class RedisRenameError < StandardError
10
+ end
11
+ class Redis
12
+ ERR = "-".freeze
13
+ OK = 'OK'.freeze
14
+ SINGLE = '+'.freeze
15
+ BULK = '$'.freeze
16
+ MULTI = '*'.freeze
17
+ INT = ':'.freeze
18
+
19
+ attr_reader :server
20
+
21
+
22
+ def initialize(opts={})
23
+ @opts = {:host => 'localhost', :port => '6379', :db => 0}.merge(opts)
24
+ $debug = @opts[:debug]
25
+ @db = @opts[:db]
26
+ @server = Server.new(@opts[:host], @opts[:port], (@opts[:timeout]||10))
27
+ end
28
+
29
+ def pipelined
30
+ pipeline = Pipeline.new(self)
31
+ yield pipeline
32
+ pipeline.finish
33
+ end
34
+
35
+ def to_s
36
+ "#{host}:#{port}"
37
+ end
38
+
39
+ def port
40
+ @opts[:port]
41
+ end
42
+
43
+ def host
44
+ @opts[:host]
45
+ end
46
+
47
+ def with_socket_management(server, &block)
48
+ begin
49
+ block.call(server.socket)
50
+ #Timeout or server down
51
+ rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNREFUSED => e
52
+ server.close
53
+ puts "Client (#{server.inspect}) disconnected from server: #{e.inspect}\n" if $debug
54
+ retry
55
+ #Server down
56
+ rescue NoMethodError => e
57
+ puts "Client (#{server.inspect}) tryin server that is down: #{e.inspect}\n Dying!" if $debug
58
+ raise Errno::ECONNREFUSED
59
+ #exit
60
+ end
61
+ end
62
+
63
+ def monitor
64
+ with_socket_management(@server) do |socket|
65
+ trap("INT") { puts "\nGot ^C! Dying!"; exit }
66
+ write "MONITOR\r\n"
67
+ puts "Now Monitoring..."
68
+ socket.read(12)
69
+ loop do
70
+ x = socket.gets
71
+ puts x unless x.nil?
72
+ end
73
+ end
74
+ end
75
+
76
+ def quit
77
+ write "QUIT\r\n"
78
+ end
79
+
80
+ def select_db(index)
81
+ @db = index
82
+ write "SELECT #{index}\r\n"
83
+ get_response
84
+ end
85
+
86
+ def flush_db
87
+ write "FLUSHDB\r\n"
88
+ get_response == OK
89
+ end
90
+
91
+ def flush_all
92
+ ensure_retry do
93
+ puts "Warning!\nFlushing *ALL* databases!\n5 Seconds to Hit ^C!"
94
+ trap('INT') {quit; return false}
95
+ sleep 5
96
+ write "FLUSHALL\r\n"
97
+ get_response == OK
98
+ end
99
+ end
100
+
101
+ def last_save
102
+ write "LASTSAVE\r\n"
103
+ get_response.to_i
104
+ end
105
+
106
+ def bgsave
107
+ write "BGSAVE\r\n"
108
+ get_response == OK
109
+ end
110
+
111
+ def info
112
+ info = {}
113
+ write("INFO\r\n")
114
+ x = get_response
115
+ x.each do |kv|
116
+ k,v = kv.split(':', 2)
117
+ k,v = k.chomp, v = v.chomp
118
+ info[k.to_sym] = v
119
+ end
120
+ info
121
+ end
122
+
123
+
124
+ def bulk_reply
125
+ begin
126
+ x = read
127
+ puts "bulk_reply read value is #{x.inspect}" if $debug
128
+ return x
129
+ rescue => e
130
+ puts "error in bulk_reply #{e}" if $debug
131
+ nil
132
+ end
133
+ end
134
+
135
+ def write(data)
136
+ with_socket_management(@server) do |socket|
137
+ puts "writing: #{data}" if $debug
138
+ socket.write(data)
139
+ end
140
+ end
141
+
142
+ def fetch(len)
143
+ with_socket_management(@server) do |socket|
144
+ len = [0, len.to_i].max
145
+ res = socket.read(len + 2)
146
+ res = res.chomp if res
147
+ res
148
+ end
149
+ end
150
+
151
+ def read(length = read_proto)
152
+ with_socket_management(@server) do |socket|
153
+ res = socket.read(length)
154
+ puts "read is #{res.inspect}" if $debug
155
+ res
156
+ end
157
+ end
158
+
159
+ def keys(glob)
160
+ write "KEYS #{glob}\r\n"
161
+ get_response.split(' ')
162
+ end
163
+
164
+ def rename!(oldkey, newkey)
165
+ write "RENAME #{oldkey} #{newkey}\r\n"
166
+ get_response
167
+ end
168
+
169
+ def rename(oldkey, newkey)
170
+ write "RENAMENX #{oldkey} #{newkey}\r\n"
171
+ case get_response
172
+ when -1
173
+ raise RedisRenameError, "source key: #{oldkey} does not exist"
174
+ when 0
175
+ raise RedisRenameError, "target key: #{oldkey} already exists"
176
+ when -3
177
+ raise RedisRenameError, "source and destination keys are the same"
178
+ when 1
179
+ true
180
+ end
181
+ end
182
+
183
+ def key?(key)
184
+ write "EXISTS #{key}\r\n"
185
+ get_response == 1
186
+ end
187
+
188
+ def delete(key)
189
+ write "DEL #{key}\r\n"
190
+ get_response == 1
191
+ end
192
+
193
+ def [](key)
194
+ get(key)
195
+ end
196
+
197
+ def get(key)
198
+ write "GET #{key}\r\n"
199
+ get_response
200
+ end
201
+
202
+ def mget(*keys)
203
+ write "MGET #{keys.join(' ')}\r\n"
204
+ get_response
205
+ end
206
+
207
+ def incr(key, increment=nil)
208
+ if increment
209
+ write "INCRBY #{key} #{increment}\r\n"
210
+ else
211
+ write "INCR #{key}\r\n"
212
+ end
213
+ get_response
214
+ end
215
+
216
+ def decr(key, decrement=nil)
217
+ if decrement
218
+ write "DECRBY #{key} #{decrement}\r\n"
219
+ else
220
+ write "DECR #{key}\r\n"
221
+ end
222
+ get_response
223
+ end
224
+
225
+ def randkey
226
+ write "RANDOMKEY\r\n"
227
+ get_response
228
+ end
229
+
230
+ def list_length(key)
231
+ write "LLEN #{key}\r\n"
232
+ case i = get_response
233
+ when -2
234
+ raise RedisError, "key: #{key} does not hold a list value"
235
+ else
236
+ i
237
+ end
238
+ end
239
+
240
+ def type?(key)
241
+ write "TYPE #{key}\r\n"
242
+ get_response
243
+ end
244
+
245
+ def push_tail(key, string)
246
+ write "RPUSH #{key} #{string.to_s.size}\r\n#{string.to_s}\r\n"
247
+ get_response
248
+ end
249
+
250
+ def push_head(key, string)
251
+ write "LPUSH #{key} #{string.to_s.size}\r\n#{string.to_s}\r\n"
252
+ get_response
253
+ end
254
+
255
+ def pop_head(key)
256
+ write "LPOP #{key}\r\n"
257
+ get_response
258
+ end
259
+
260
+ def pop_tail(key)
261
+ write "RPOP #{key}\r\n"
262
+ get_response
263
+ end
264
+
265
+ def list_set(key, index, val)
266
+ write "LSET #{key} #{index} #{val.to_s.size}\r\n#{val}\r\n"
267
+ get_response == OK
268
+ end
269
+
270
+ def list_range(key, start, ending)
271
+ write "LRANGE #{key} #{start} #{ending}\r\n"
272
+ get_response
273
+ end
274
+
275
+ def list_trim(key, start, ending)
276
+ write "LTRIM #{key} #{start} #{ending}\r\n"
277
+ get_response
278
+ end
279
+
280
+ def list_index(key, index)
281
+ write "LINDEX #{key} #{index}\r\n"
282
+ get_response
283
+ end
284
+
285
+ def list_rm(key, count, value)
286
+ write "LREM #{key} #{count} #{value.to_s.size}\r\n#{value}\r\n"
287
+ case num = get_response
288
+ when -1
289
+ raise RedisError, "key: #{key} does not exist"
290
+ when -2
291
+ raise RedisError, "key: #{key} does not hold a list value"
292
+ else
293
+ num
294
+ end
295
+ end
296
+
297
+ def set_add(key, member)
298
+ write "SADD #{key} #{member.to_s.size}\r\n#{member}\r\n"
299
+ case get_response
300
+ when 1
301
+ true
302
+ when 0
303
+ false
304
+ when -2
305
+ raise RedisError, "key: #{key} contains a non set value"
306
+ end
307
+ end
308
+
309
+ def set_delete(key, member)
310
+ write "SREM #{key} #{member.to_s.size}\r\n#{member}\r\n"
311
+ case get_response
312
+ when 1
313
+ true
314
+ when 0
315
+ false
316
+ when -2
317
+ raise RedisError, "key: #{key} contains a non set value"
318
+ end
319
+ end
320
+
321
+ def set_count(key)
322
+ write "SCARD #{key}\r\n"
323
+ case i = get_response
324
+ when -2
325
+ raise RedisError, "key: #{key} contains a non set value"
326
+ else
327
+ i
328
+ end
329
+ end
330
+
331
+ def set_member?(key, member)
332
+ write "SISMEMBER #{key} #{member.to_s.size}\r\n#{member}\r\n"
333
+ case get_response
334
+ when 1
335
+ true
336
+ when 0
337
+ false
338
+ when -2
339
+ raise RedisError, "key: #{key} contains a non set value"
340
+ end
341
+ end
342
+
343
+ def set_members(key)
344
+ write "SMEMBERS #{key}\r\n"
345
+ Set.new(get_response)
346
+ end
347
+
348
+ def set_intersect(*keys)
349
+ write "SINTER #{keys.join(' ')}\r\n"
350
+ Set.new(get_response)
351
+ end
352
+
353
+ def set_inter_store(destkey, *keys)
354
+ write "SINTERSTORE #{destkey} #{keys.join(' ')}\r\n"
355
+ get_response
356
+ end
357
+
358
+ def set_union(*keys)
359
+ write "SUNION #{keys.join(' ')}\r\n"
360
+ Set.new(get_response)
361
+ end
362
+
363
+ def set_union_store(destkey, *keys)
364
+ write "SUNIONSTORE #{destkey} #{keys.join(' ')}\r\n"
365
+ get_response
366
+ end
367
+
368
+ def set_diff(*keys)
369
+ write "SDIFF #{keys.join(' ')}\r\n"
370
+ Set.new(get_response)
371
+ end
372
+
373
+ def set_diff_store(destkey, *keys)
374
+ write "SDIFFSTORE #{destkey} #{keys.join(' ')}\r\n"
375
+ get_response
376
+ end
377
+
378
+ def sort(key, opts={})
379
+ cmd = "SORT #{key}"
380
+ cmd << " BY #{opts[:by]}" if opts[:by]
381
+ cmd << " GET #{opts[:get]}" if opts[:get]
382
+ cmd << " INCR #{opts[:incr]}" if opts[:incr]
383
+ cmd << " DEL #{opts[:del]}" if opts[:del]
384
+ cmd << " DECR #{opts[:decr]}" if opts[:decr]
385
+ cmd << " #{opts[:order]}" if opts[:order]
386
+ cmd << " LIMIT #{opts[:limit].join(' ')}" if opts[:limit]
387
+ cmd << "\r\n"
388
+ write(cmd)
389
+ get_response
390
+ end
391
+
392
+ def multi_bulk
393
+ res = read_proto
394
+ puts "mb res is #{res.inspect}" if $debug
395
+ list = []
396
+ Integer(res).times do
397
+ vf = get_response
398
+ puts "curren vf is #{vf.inspect}" if $debug
399
+ list << vf
400
+ puts "current list is #{list.inspect}" if $debug
401
+ end
402
+ list
403
+ end
404
+
405
+ def get_reply
406
+ begin
407
+ r = read(1)
408
+ raise RedisError if (r == "\r" || r == "\n")
409
+ rescue RedisError
410
+ retry
411
+ end
412
+ r
413
+ end
414
+
415
+ def []=(key, val)
416
+ set(key,val)
417
+ end
418
+
419
+
420
+ def set(key, val, expiry=nil)
421
+ write("SET #{key} #{val.to_s.size}\r\n#{val}\r\n")
422
+ s = get_response == OK
423
+ return expire(key, expiry) if s && expiry
424
+ s
425
+ end
426
+
427
+ def expire(key, expiry=nil)
428
+ write("EXPIRE #{key} #{expiry}\r\n")
429
+ get_response == 1
430
+ end
431
+
432
+ def set_unless_exists(key, val)
433
+ write "SETNX #{key} #{val.to_s.size}\r\n#{val}\r\n"
434
+ get_response == 1
435
+ end
436
+
437
+ def status_code_reply
438
+ begin
439
+ res = read_proto
440
+ if res.index('-') == 0
441
+ raise RedisError, res
442
+ else
443
+ true
444
+ end
445
+ rescue RedisError
446
+ raise RedisError
447
+ end
448
+ end
449
+
450
+ def get_response
451
+ begin
452
+ rtype = get_reply
453
+ rescue => e
454
+ raise RedisError, e.inspect
455
+ end
456
+ puts "reply_type is #{rtype.inspect}" if $debug
457
+ case rtype
458
+ when SINGLE
459
+ single_line
460
+ when BULK
461
+ bulk_reply
462
+ when MULTI
463
+ multi_bulk
464
+ when INT
465
+ integer_reply
466
+ when ERR
467
+ raise RedisError, single_line
468
+ else
469
+ raise RedisError, "Unknown response.."
470
+ end
471
+ end
472
+
473
+ def integer_reply
474
+ Integer(read_proto)
475
+ end
476
+
477
+ def single_line
478
+ buff = ""
479
+ while buff[-2..-1] != "\r\n"
480
+ buff << read(1)
481
+ end
482
+ puts "single_line value is #{buff[0..-3].inspect}" if $debug
483
+ buff[0..-3]
484
+ end
485
+
486
+ def read_socket
487
+ with_socket_management(@server) do |socket|
488
+ while res = socket.read(8096)
489
+ break if res.size != 8096
490
+ end
491
+ end
492
+ end
493
+
494
+ def read_proto
495
+ with_socket_management(@server) do |socket|
496
+ if res = socket.gets
497
+ x = res.chomp
498
+ puts "read_proto is #{x.inspect}\n\n" if $debug
499
+ x.to_i
500
+ end
501
+ end
502
+ end
503
+
504
+ end