winescout-redis 0.0.3

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.
data/lib/redis.rb ADDED
@@ -0,0 +1,462 @@
1
+ require 'socket'
2
+ require 'set'
3
+ require File.join(File.dirname(__FILE__),'server')
4
+
5
+
6
+ class RedisError < StandardError
7
+ end
8
+ class RedisRenameError < StandardError
9
+ end
10
+ class Redis
11
+ ERR = "-".freeze
12
+ OK = 'OK'.freeze
13
+ SINGLE = '+'.freeze
14
+ BULK = '$'.freeze
15
+ MULTI = '*'.freeze
16
+ INT = ':'.freeze
17
+
18
+ attr_reader :server
19
+
20
+
21
+ def initialize(opts={})
22
+ @opts = {:host => 'localhost', :port => '6379'}.merge(opts)
23
+ $debug = @opts[:debug]
24
+ @server = Server.new(@opts[:host], @opts[:port])
25
+ end
26
+
27
+ def to_s
28
+ "#{host}:#{port}"
29
+ end
30
+
31
+ def port
32
+ @opts[:port]
33
+ end
34
+
35
+ def host
36
+ @opts[:host]
37
+ end
38
+
39
+ def with_socket_management(server, &block)
40
+ begin
41
+ block.call(server.socket)
42
+ #Timeout or server down
43
+ rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNREFUSED => e
44
+ server.close
45
+ puts "Client (#{server.inspect}) disconnected from server: #{e.inspect}\n" if $debug
46
+ retry
47
+ #Server down
48
+ rescue NoMethodError => e
49
+ puts "Client (#{server.inspect}) tryin server that is down: #{e.inspect}\n Dying!" if $debug
50
+ raise Errno::ECONNREFUSED
51
+ #exit
52
+ end
53
+ end
54
+
55
+ def monitor
56
+ ensure_retry do
57
+ with_socket_management(@server) do |socket|
58
+ trap("INT") { puts "\nGot ^C! Dying!"; exit }
59
+ write "MONITOR\r\n"
60
+ puts "Now Monitoring..."
61
+ socket.read(12)
62
+ loop do
63
+ x = socket.gets
64
+ puts x unless x.nil?
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ def quit
71
+ write "QUIT\r\n"
72
+ end
73
+
74
+ def select_db(index)
75
+ write "SELECT #{index}\r\n"
76
+ get_response
77
+ end
78
+
79
+ def flush_db
80
+ write "FLUSHDB\r\n"
81
+ get_response == OK
82
+ end
83
+
84
+ def last_save
85
+ write "LASTSAVE\r\n"
86
+ get_response.to_i
87
+ end
88
+
89
+ def bgsave
90
+ write "BGSAVE\r\n"
91
+ get_response == OK
92
+ end
93
+
94
+ def info
95
+ info = {}
96
+ write("INFO\r\n")
97
+ x = get_response
98
+ x.each do |kv|
99
+ k,v = kv.split(':', 2)
100
+ k,v = k.chomp, v = v.chomp
101
+ info[k.to_sym] = v
102
+ end
103
+ info
104
+ end
105
+
106
+
107
+ def bulk_reply
108
+ begin
109
+ x = read.chomp
110
+ puts "bulk_reply read value is #{x.inspect}" if $debug
111
+ return x
112
+ rescue => e
113
+ puts "error in bulk_reply #{e}" if $debug
114
+ nil
115
+ end
116
+ end
117
+
118
+ def write(data)
119
+ with_socket_management(@server) do |socket|
120
+ puts "writing: #{data}" if $debug
121
+ socket.write(data)
122
+ end
123
+ end
124
+
125
+ def fetch(len)
126
+ with_socket_management(@server) do |socket|
127
+ len = [0, len.to_i].max
128
+ res = socket.read(len + 2)
129
+ res = res.chomp if res
130
+ res
131
+ end
132
+ end
133
+
134
+ def read(length = read_proto)
135
+ with_socket_management(@server) do |socket|
136
+ res = socket.read(length)
137
+ puts "read is #{res.inspect}" if $debug
138
+ res
139
+ end
140
+ end
141
+
142
+ def keys(glob)
143
+ write "KEYS #{glob}\r\n"
144
+ get_response.split(' ')
145
+ end
146
+
147
+ def rename!(oldkey, newkey)
148
+ write "RENAME #{oldkey} #{newkey}\r\n"
149
+ get_response
150
+ end
151
+
152
+ def rename(oldkey, newkey)
153
+ write "RENAMENX #{oldkey} #{newkey}\r\n"
154
+ case get_response
155
+ when -1
156
+ raise RedisRenameError, "source key: #{oldkey} does not exist"
157
+ when 0
158
+ raise RedisRenameError, "target key: #{oldkey} already exists"
159
+ when -3
160
+ raise RedisRenameError, "source and destination keys are the same"
161
+ when 1
162
+ true
163
+ end
164
+ end
165
+
166
+ def key?(key)
167
+ write "EXISTS #{key}\r\n"
168
+ get_response == 1
169
+ end
170
+
171
+ def delete(key)
172
+ write "DEL #{key}\r\n"
173
+ get_response == 1
174
+ end
175
+
176
+ def [](key)
177
+ get(key)
178
+ end
179
+
180
+ def get(key)
181
+ write "GET #{key}\r\n"
182
+ get_response
183
+ end
184
+
185
+ def mget(*keys)
186
+ write "MGET #{keys.join(' ')}\r\n"
187
+ get_response
188
+ end
189
+
190
+ def incr(key, increment=nil)
191
+ if increment
192
+ write "INCRBY #{key} #{increment}\r\n"
193
+ else
194
+ write "INCR #{key}\r\n"
195
+ end
196
+ get_response
197
+ end
198
+
199
+ def decr(key, decrement=nil)
200
+ if decrement
201
+ write "DECRRBY #{key} #{decrement}\r\n"
202
+ else
203
+ write "DECR #{key}\r\n"
204
+ end
205
+ get_response
206
+ end
207
+
208
+ def randkey
209
+ write "RANDOMKEY\r\n"
210
+ get_response
211
+ end
212
+
213
+ def list_length(key)
214
+ write "LLEN #{key}\r\n"
215
+ case i = get_response
216
+ when -2
217
+ raise RedisError, "key: #{key} does not hold a list value"
218
+ else
219
+ i
220
+ end
221
+ end
222
+
223
+ def type?(key)
224
+ write "TYPE #{key}\r\n"
225
+ get_response
226
+ end
227
+
228
+ def push_tail(key, string)
229
+ write "RPUSH #{key} #{string.to_s.size}\r\n#{string.to_s}\r\n"
230
+ get_response
231
+ end
232
+
233
+ def push_head(key, string)
234
+ write "LPUSH #{key} #{string.to_s.size}\r\n#{string.to_s}\r\n"
235
+ get_response
236
+ end
237
+
238
+ def pop_head(key)
239
+ write "LPOP #{key}\r\n"
240
+ get_response
241
+ end
242
+
243
+ def pop_tail(key)
244
+ write "RPOP #{key}\r\n"
245
+ get_response
246
+ end
247
+
248
+ def list_set(key, index, val)
249
+ write "LSET #{key} #{index} #{val.to_s.size}\r\n#{val}\r\n"
250
+ get_response == OK
251
+ end
252
+
253
+ def list_length(key)
254
+ write "LLEN #{key}\r\n"
255
+ case i = get_response
256
+ when -2
257
+ raise RedisError, "key: #{key} does not hold a list value"
258
+ else
259
+ i
260
+ end
261
+ end
262
+
263
+ def list_range(key, start, ending)
264
+ write "LRANGE #{key} #{start} #{ending}\r\n"
265
+ get_response
266
+ end
267
+
268
+ def list_trim(key, start, ending)
269
+ write "LTRIM #{key} #{start} #{ending}\r\n"
270
+ get_response
271
+ end
272
+
273
+ def list_index(key, index)
274
+ write "LINDEX #{key} #{index}\r\n"
275
+ get_response
276
+ end
277
+
278
+ def list_rm(key, count, value)
279
+ write "LREM #{key} #{count} #{value.to_s.size}\r\n#{value}\r\n"
280
+ case num = get_response
281
+ when -1
282
+ raise RedisError, "key: #{key} does not exist"
283
+ when -2
284
+ raise RedisError, "key: #{key} does not hold a list value"
285
+ else
286
+ num
287
+ end
288
+ end
289
+
290
+ def set_add(key, member)
291
+ write "SADD #{key} #{member.to_s.size}\r\n#{member}\r\n"
292
+ case get_response
293
+ when 1
294
+ true
295
+ when 0
296
+ false
297
+ when -2
298
+ raise RedisError, "key: #{key} contains a non set value"
299
+ end
300
+ end
301
+
302
+ def set_delete(key, member)
303
+ write "SREM #{key} #{member.to_s.size}\r\n#{member}\r\n"
304
+ case get_response
305
+ when 1
306
+ true
307
+ when 0
308
+ false
309
+ when -2
310
+ raise RedisError, "key: #{key} contains a non set value"
311
+ end
312
+ end
313
+
314
+ def set_count(key)
315
+ write "SCARD #{key}\r\n"
316
+ case i = get_response
317
+ when -2
318
+ raise RedisError, "key: #{key} contains a non set value"
319
+ else
320
+ i
321
+ end
322
+ end
323
+
324
+ def set_member?(key, member)
325
+ write "SISMEMBER #{key} #{member.to_s.size}\r\n#{member}\r\n"
326
+ case get_response
327
+ when 1
328
+ true
329
+ when 0
330
+ false
331
+ when -2
332
+ raise RedisError, "key: #{key} contains a non set value"
333
+ end
334
+ end
335
+
336
+ def set_members(key)
337
+ write "SMEMBERS #{key}\r\n"
338
+ Set.new(get_response)
339
+ end
340
+
341
+ def set_intersect(*keys)
342
+ write "SINTER #{keys.join(' ')}\r\n"
343
+ Set.new(get_response)
344
+ end
345
+
346
+ def set_inter_store(destkey, *keys)
347
+ write "SINTERSTORE #{destkey} #{keys.join(' ')}\r\n"
348
+ get_response
349
+ end
350
+
351
+ def sort(key, opts={})
352
+ cmd = "SORT #{key}"
353
+ cmd << " BY #{opts[:by]}" if opts[:by]
354
+ cmd << " GET #{opts[:get]}" if opts[:get]
355
+ cmd << " INCR #{opts[:incr]}" if opts[:incr]
356
+ cmd << " DEL #{opts[:del]}" if opts[:del]
357
+ cmd << " DECR #{opts[:decr]}" if opts[:decr]
358
+ cmd << " #{opts[:order]}" if opts[:order]
359
+ cmd << " LIMIT #{opts[:limit].join(' ')}" if opts[:limit]
360
+ cmd << "\r\n"
361
+ write(cmd)
362
+ get_response
363
+ end
364
+
365
+ def multi_bulk
366
+ res = read_proto
367
+ puts "mb res is #{res.inspect}" if $debug
368
+ list = []
369
+ Integer(res).times do
370
+ vf = get_response
371
+ puts "curren vf is #{vf.inspect}" if $debug
372
+ list << vf
373
+ puts "current list is #{list.inspect}" if $debug
374
+ end
375
+ list
376
+ end
377
+
378
+ def get_reply
379
+ begin
380
+ r = read(1)
381
+ raise RedisError if (r == "\r" || r == "\n")
382
+ rescue RedisError
383
+ retry
384
+ end
385
+ r
386
+ end
387
+
388
+ def []=(key, val)
389
+ set(key,val)
390
+ end
391
+
392
+
393
+ def set(key, val, expiry=nil)
394
+ write("SET #{key} #{val.to_s.size}\r\n#{val}\r\n")
395
+ get_response == OK
396
+ end
397
+
398
+ def set_unless_exists(key, val)
399
+ write "SETNX #{key} #{val.to_s.size}\r\n#{val}\r\n"
400
+ get_response == 1
401
+ end
402
+
403
+ def status_code_reply
404
+ begin
405
+ res = read_proto
406
+ if res.index('-') == 0
407
+ raise RedisError, res
408
+ else
409
+ true
410
+ end
411
+ rescue RedisError
412
+ raise RedisError
413
+ end
414
+ end
415
+
416
+ def get_response
417
+ begin
418
+ rtype = get_reply
419
+ rescue => e
420
+ raise RedisError, e.inspect
421
+ end
422
+ puts "reply_type is #{rtype.inspect}" if $debug
423
+ case rtype
424
+ when SINGLE
425
+ single_line
426
+ when BULK
427
+ bulk_reply
428
+ when MULTI
429
+ multi_bulk
430
+ when INT
431
+ integer_reply
432
+ when ERR
433
+ raise RedisError, single_line
434
+ else
435
+ raise RedisError, "Unknown response.."
436
+ end
437
+ end
438
+
439
+ def integer_reply
440
+ Integer(read_proto)
441
+ end
442
+
443
+ def single_line
444
+ buff = ""
445
+ while buff[-2..-1] != "\r\n"
446
+ buff << read(1)
447
+ end
448
+ puts "single_line value is #{buff[0..-3].inspect}" if $debug
449
+ buff[0..-3]
450
+ end
451
+
452
+ def read_proto
453
+ with_socket_management(@server) do |socket|
454
+ if res = socket.gets
455
+ x = res.chomp
456
+ puts "read_proto is #{x.inspect}\n\n" if $debug
457
+ x.to_i
458
+ end
459
+ end
460
+ end
461
+
462
+ end
data/lib/server.rb ADDED
@@ -0,0 +1,131 @@
1
+ ##
2
+ # This class represents a redis server instance.
3
+
4
+ class Server
5
+
6
+ ##
7
+ # The amount of time to wait before attempting to re-establish a
8
+ # connection with a server that is marked dead.
9
+
10
+ RETRY_DELAY = 30.0
11
+
12
+ ##
13
+ # The host the redis server is running on.
14
+
15
+ attr_reader :host
16
+
17
+ ##
18
+ # The port the redis server is listening on.
19
+
20
+ attr_reader :port
21
+
22
+ ##
23
+ #
24
+
25
+ attr_reader :replica
26
+
27
+ ##
28
+ # The time of next retry if the connection is dead.
29
+
30
+ attr_reader :retry
31
+
32
+ ##
33
+ # A text status string describing the state of the server.
34
+
35
+ attr_reader :status
36
+
37
+ ##
38
+ # Create a new Redis::Server object for the redis instance
39
+ # listening on the given host and port.
40
+
41
+ def initialize(host, port = DEFAULT_PORT)
42
+ raise ArgumentError, "No host specified" if host.nil? or host.empty?
43
+ raise ArgumentError, "No port specified" if port.nil? or port.to_i.zero?
44
+
45
+ @host = host
46
+ @port = port.to_i
47
+
48
+ @sock = nil
49
+ @retry = nil
50
+ @status = 'NOT CONNECTED'
51
+ @timeout = 1
52
+ end
53
+
54
+ ##
55
+ # Return a string representation of the server object.
56
+ def inspect
57
+ "<Redis::Server: %s:%d (%s)>" % [@host, @port, @status]
58
+ end
59
+
60
+ ##
61
+ # Try to connect to the redis server targeted by this object.
62
+ # Returns the connected socket object on success or nil on failure.
63
+
64
+ def socket
65
+ return @sock if @sock and not @sock.closed?
66
+
67
+ @sock = nil
68
+
69
+ # If the host was dead, don't retry for a while.
70
+ return if @retry and @retry > Time.now
71
+
72
+ # Attempt to connect if not already connected.
73
+ begin
74
+ @sock = connect_to(@host, @port, @timeout)
75
+ @sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
76
+ @retry = nil
77
+ @status = 'CONNECTED'
78
+ rescue Errno::EPIPE, Errno::ECONNREFUSED => e
79
+ puts "Socket died... socket: #{@sock.inspect}\n" if $debug
80
+ @sock.close
81
+ retry
82
+ rescue SocketError, SystemCallError, IOError => err
83
+ puts "Unable to open socket: #{err.class.name}, #{err.message}" if $debug
84
+ mark_dead err
85
+ end
86
+
87
+ return @sock
88
+ end
89
+
90
+ def connect_to(host, port, timeout=nil)
91
+ addrs = Socket.getaddrinfo(host, nil)
92
+ addr = addrs.detect { |ad| ad[0] == 'AF_INET' }
93
+ sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
94
+ #addr = Socket.getaddrinfo(host, nil)
95
+ #sock = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
96
+
97
+ if timeout
98
+ secs = Integer(timeout)
99
+ usecs = Integer((timeout - secs) * 1_000_000)
100
+ optval = [secs, usecs].pack("l_2")
101
+ sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
102
+ sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
103
+ end
104
+ sock.connect(Socket.pack_sockaddr_in(port, addr[3]))
105
+ sock
106
+ end
107
+
108
+ ##
109
+ # Close the connection to the redis server targeted by this
110
+ # object. The server is not considered dead.
111
+
112
+ def close
113
+ @sock.close if @sock && !@sock.closed?
114
+ @sock = nil
115
+ @retry = nil
116
+ @status = "NOT CONNECTED"
117
+ end
118
+
119
+ ##
120
+ # Mark the server as dead and close its socket.
121
+ def mark_dead(error)
122
+ @sock.close if @sock && !@sock.closed?
123
+ @sock = nil
124
+ @retry = Time.now #+ RETRY_DELAY
125
+
126
+ reason = "#{error.class.name}: #{error.message}"
127
+ @status = sprintf "%s:%s DEAD (%s), will retry at %s", @host, @port, reason, @retry
128
+ puts @status
129
+ end
130
+
131
+ end