ruby-redis-portertech 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.
@@ -0,0 +1,69 @@
1
+ require 'logger'
2
+
3
+ module Redis
4
+ class Logger < ::Logger
5
+
6
+ # Redis levels are: DEBUG < INFO < NOTICE < WARNING
7
+ # This logger inserts support for NOTICE
8
+
9
+ def initialize(logdev, *args)
10
+ super
11
+ @raw_logdev = logdev
12
+ @default_formatter = proc { |severity, datetime, progname, msg|
13
+ mark = case severity
14
+ when 'DEBUG' then '.'
15
+ when 'INFO' then '-'
16
+ when 'NOTE' then '*'
17
+ when 'WARN' then '#'
18
+ else '!'
19
+ end
20
+ "[#{Process.pid}] #{datetime.strftime '%d %b %H:%H:%S'} #{mark} #{msg}\n"
21
+ }
22
+ end
23
+
24
+ def flush
25
+ @raw_logdev.flush if @raw_logdev.respond_to? :flush
26
+ end
27
+
28
+ module Severity
29
+ # logger.rb says "max 5 char" for labels
30
+ SEV_LABEL = %w(DEBUG INFO NOTE WARN ERROR FATAL ANY)
31
+ DEBUG = 0
32
+ INFO = 1
33
+ NOTICE = 2
34
+ WARN = 3
35
+ ERROR = 4
36
+ FATAL = 5
37
+ UNKNOWN = 6
38
+ end
39
+ include Severity
40
+
41
+ def notice(progname = nil, &block)
42
+ add(NOTICE, nil, progname, &block)
43
+ end
44
+ def warn(progname = nil, &block)
45
+ add(WARN, nil, progname, &block)
46
+ end
47
+ def error(progname = nil, &block)
48
+ add(ERROR, nil, progname, &block)
49
+ end
50
+ def fatal(progname = nil, &block)
51
+ add(FATAL, nil, progname, &block)
52
+ end
53
+ def unknown(progname = nil, &block)
54
+ add(UNKNOWN, nil, progname, &block)
55
+ end
56
+
57
+ def notice?; @level <= NOTICE; end
58
+ def warn?; @level <= WARN; end
59
+ def error?; @level <= ERROR; end
60
+ def fatal?; @level <= FATAL; end
61
+
62
+ private
63
+
64
+ def format_severity(severity)
65
+ SEV_LABEL[severity] || 'ANY'
66
+ end
67
+
68
+ end
69
+ end
@@ -0,0 +1,107 @@
1
+ module Redis
2
+ module Protocol
3
+
4
+ def initialize *args
5
+ @reader = Reader.new
6
+ @multi = nil
7
+ @deferred = nil
8
+ @watcher = nil
9
+ super
10
+ end
11
+
12
+ def unbind
13
+ @deferred.unbind if @deferred
14
+ @watcher.unbind if @watcher
15
+ super
16
+ end
17
+
18
+ # Process incoming redis protocol
19
+ def receive_data data
20
+ return unless @reader
21
+ @reader.feed data
22
+ until (strings = @reader.gets) == false
23
+ if @multi and !%w{WATCH DISCARD MULTI EXEC DEBUG}.include?(strings[0].upcase)
24
+ @multi << strings
25
+ send_redis :'+QUEUED'
26
+ else
27
+ result = __send__ "redis_#{strings[0].upcase}", *strings[1..-1]
28
+ if Integer === result
29
+ send_data ":#{result}\r\n"
30
+ elsif EventMachine::Deferrable === result
31
+ @deferred.unbind if @deferred and @deferred != result
32
+ @deferred = result
33
+ elsif result == :quit
34
+ @reader = nil
35
+ close_connection_after_writing
36
+ break
37
+ elsif result != :exec
38
+ send_redis result
39
+ end
40
+ end
41
+ end
42
+ rescue StandardError, LoadError, SyntaxError => e
43
+ # This sometimes comes in handy for the TCL tests
44
+ # @logger.warn "#{e.class}:/#{e.backtrace[0]} #{e.message}"
45
+ # e.backtrace[1..-1].each {|bt| @logger.warn bt}
46
+ send_data "-ERR #{e.class.name}: #{e.message}\r\n"
47
+ end
48
+
49
+ def redis_WATCH *keys
50
+ raise 'WATCH inside MULTI is not allowed' if @multi
51
+ @watcher ||= Database::Watcher.new
52
+ @watcher.bind @database, *keys
53
+ :'+OK'
54
+ end
55
+
56
+ def redis_UNWATCH
57
+ if @watcher
58
+ @watcher.unbind
59
+ @watcher = nil
60
+ end
61
+ :'+OK'
62
+ end
63
+
64
+ def redis_MULTI
65
+ raise 'MULTI nesting not allowed' if @multi
66
+ @multi = []
67
+ :'+OK'
68
+ end
69
+
70
+ def redis_DISCARD
71
+ redis_UNWATCH
72
+ @multi = nil
73
+ :'+OK'
74
+ end
75
+
76
+ def redis_EXEC
77
+ if @watcher
78
+ still_bound = @watcher.bound
79
+ redis_UNWATCH
80
+ unless still_bound
81
+ @multi = nil
82
+ return :'*-1'
83
+ end
84
+ end
85
+ send_data "*#{@multi.size}\r\n"
86
+ response = []
87
+ @multi.each do |strings|
88
+ begin
89
+ result = __send__ "redis_#{strings[0].upcase}", *strings[1..-1]
90
+ rescue StandardError, LoadError, SyntaxError => e
91
+ result = e
92
+ end
93
+ if EventMachine::Deferrable === result
94
+ result.unbind
95
+ send_redis nil
96
+ elsif Exception === result
97
+ send_data "-ERR #{result.class.name}: #{result.message}"
98
+ else
99
+ send_redis result
100
+ end
101
+ end
102
+ @multi = nil
103
+ :exec
104
+ end
105
+
106
+ end
107
+ end
@@ -0,0 +1,150 @@
1
+ require 'eventmachine'
2
+
3
+ module Redis
4
+ module PubSub
5
+
6
+ class Subscription
7
+ include EventMachine::Deferrable
8
+
9
+ @@channels ||= {}
10
+ @@pchannels ||= {}
11
+
12
+ def self.publish channel, message
13
+ subs = @@channels[channel] || []
14
+ subs.each do |sub|
15
+ sub.call channel, message
16
+ end
17
+ sent = subs.size
18
+ (@@pchannels || []).each do |pchannel, subs|
19
+ next unless File.fnmatch(pchannel, channel)
20
+ subs.each do |sub|
21
+ sub.pcall pchannel, channel, message
22
+ end
23
+ sent += subs.size
24
+ end
25
+ sent
26
+ end
27
+
28
+ def initialize connection
29
+ @connection = connection
30
+ @subs = []
31
+ @psubs = []
32
+ end
33
+
34
+ def bound
35
+ size > 0
36
+ end
37
+
38
+ def size
39
+ @subs.size + @psubs.size
40
+ end
41
+
42
+ def unbind
43
+ unbind_channels
44
+ unbind_patterns
45
+ end
46
+
47
+ def unbind_channels
48
+ @subs.each do |channel|
49
+ (@@channels[channel] || []).delete self
50
+ end
51
+ @subs.clear
52
+ end
53
+
54
+ def unbind_patterns
55
+ @psubs.each do |channel|
56
+ (@@pchannels[channel] || []).delete self
57
+ end
58
+ @psubs.clear
59
+ end
60
+
61
+ def subscribe channel
62
+ c = (@@channels[channel] ||= [])
63
+ unless c.include? self
64
+ c << self
65
+ @subs << channel
66
+ end
67
+ @connection.send_redis ['subscribe', channel, size]
68
+ end
69
+
70
+ def psubscribe channel
71
+ c = (@@pchannels[channel] ||= [])
72
+ unless c.include? self
73
+ c << self
74
+ @psubs << channel
75
+ end
76
+ @connection.send_redis ['psubscribe', channel, size]
77
+ end
78
+
79
+ def unsubscribe channel
80
+ c = (@@channels[channel] || [])
81
+ @subs.delete channel if c.delete self
82
+ @connection.send_redis ['unsubscribe', channel, size]
83
+ end
84
+
85
+ def punsubscribe channel
86
+ c = (@@pchannels[channel] || [])
87
+ @psubs.delete channel if c.delete self
88
+ @connection.send_redis ['punsubscribe', channel, size]
89
+ end
90
+
91
+ def call channel, message
92
+ @connection.send_redis ['message', channel, message]
93
+ end
94
+
95
+ def pcall pchannel, channel, message
96
+ @connection.send_redis ['pmessage', pchannel, channel, message]
97
+ end
98
+
99
+ end
100
+
101
+ def redis_SUBSCRIBE *channels
102
+ sub = @deferred
103
+ sub = Subscription.new self unless Subscription === sub
104
+ channels.each do |channel|
105
+ sub.subscribe channel
106
+ end
107
+ sub
108
+ end
109
+
110
+ def redis_UNSUBSCRIBE *channels
111
+ sub = @deferred
112
+ sub = Subscription.new self unless Subscription === sub
113
+ if channels.empty?
114
+ sub.unbind_channels
115
+ else
116
+ channels.each do |channel|
117
+ sub.unsubscribe channel
118
+ end
119
+ end
120
+ sub
121
+ end
122
+
123
+ def redis_PSUBSCRIBE *channels
124
+ sub = @deferred
125
+ sub = Subscription.new self unless Subscription === sub
126
+ channels.each do |channel|
127
+ sub.psubscribe channel
128
+ end
129
+ sub
130
+ end
131
+
132
+ def redis_PUNSUBSCRIBE *channels
133
+ sub = @deferred
134
+ sub = Subscription.new self unless Subscription === sub
135
+ if channels.empty?
136
+ sub.unbind_patterns
137
+ else
138
+ channels.each do |channel|
139
+ sub.punsubscribe channel
140
+ end
141
+ end
142
+ sub
143
+ end
144
+
145
+ def redis_PUBLISH channel, message
146
+ Subscription.publish channel, message
147
+ end
148
+
149
+ end
150
+ end
@@ -0,0 +1,175 @@
1
+ module Redis
2
+ class Reader < Array
3
+
4
+ # Minimize the amount of memory copying. The primary
5
+ # performance trick is to String#split and work with that.
6
+ # This is almost as fast as hiredis/reader plus it supports servers
7
+
8
+ def initialize options={}
9
+ super()
10
+ @multibulk_limit = options[:multibulk_limit] || 1024*1024
11
+ @bulk_limit = options[:bulk_limit] || 512*1024*1024
12
+ @buffer_overflow = false
13
+ flush
14
+ end
15
+
16
+ def feed data
17
+ return if @buffer_overflow
18
+ if reduce(0){|t,e|t+e.bytesize} > @bulk_limit + 2
19
+ @buffer_overflow = true
20
+ flush
21
+ return
22
+ end
23
+ push data
24
+ end
25
+
26
+ def gets
27
+ raise 'buffer overflow' if @buffer_overflow
28
+ if @completed.empty?
29
+ unshift_split if @split
30
+ frame do |str|
31
+ @elements << str
32
+ if @remaining != 0
33
+ if @remaining < 0
34
+ @remaining = -@remaining
35
+ @elements = str
36
+ end
37
+ @remaining -= 1
38
+ if @remaining == 0 and !@stack.empty?
39
+ elements = @elements
40
+ @elements, @remaining = @stack.pop
41
+ @elements << elements
42
+ @remaining -= 1
43
+ end
44
+ next unless @remaining == 0
45
+ @completed << @elements
46
+ elsif !@elements.empty?
47
+ @completed << @elements[0]
48
+ end
49
+ @elements = []
50
+ @remaining = 0
51
+ end
52
+ end
53
+ return false if @completed.empty?
54
+ @completed.shift
55
+ end
56
+
57
+ private
58
+
59
+ def flush
60
+ @split = nil
61
+ @pending = nil
62
+ @binary_size = nil
63
+ @remaining = 0
64
+ @elements = []
65
+ @stack = []
66
+ @completed = []
67
+ clear
68
+ end
69
+
70
+ def unshift_split
71
+ unshift @split.join "\n"
72
+ @split = nil
73
+ end
74
+
75
+ # yields redis data until no more found in buffer
76
+ def frame
77
+ while true
78
+ if @binary_size
79
+ s = read_binary @binary_size
80
+ break unless s
81
+ @binary_size = nil
82
+ yield s
83
+ else
84
+ line = read_line
85
+ break unless line
86
+ case line[0..0]
87
+ when '-'
88
+ yield RuntimeError.new line[1..-1]
89
+ when '+'
90
+ yield line[1..-1]
91
+ when ':'
92
+ yield line[1..-1].to_i
93
+ when '*'
94
+ prev_remaining = @remaining
95
+ @remaining = line[1..-1].to_i
96
+ if @remaining == 0
97
+ @remaining = -1
98
+ yield []
99
+ elsif @remaining == -1
100
+ yield nil
101
+ elsif @remaining > @multibulk_limit
102
+ flush
103
+ raise 'invalid multibulk length'
104
+ elsif prev_remaining > 0
105
+ @stack << [@elements, prev_remaining]
106
+ @elements = []
107
+ end
108
+ when '$'
109
+ @binary_size = line[1..-1].to_i
110
+ if @binary_size == -1
111
+ @binary_size = nil
112
+ yield nil
113
+ elsif (@binary_size == 0 and line[1..1] != '0') or @binary_size < 0 or @binary_size > @bulk_limit
114
+ flush
115
+ raise 'invalid bulk length'
116
+ end
117
+ else
118
+ if @remaining > 0
119
+ flush
120
+ raise "expected \"$\", got #{line[0].chr.dump}"
121
+ end
122
+ parts = line.split(' ')
123
+ @remaining = parts.size
124
+ parts.each {|l| yield l}
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ # Read a binary redis token, nil if none available
131
+ def read_binary length
132
+ if @split
133
+ if @split.first.size >= length
134
+ result = @split.shift[0...length]
135
+ unshift_split if @split.size == 1
136
+ return result
137
+ end
138
+ unshift_split
139
+ end
140
+ unless @pending
141
+ size = reduce(0){|x,y|x+y.size}
142
+ return nil unless size >= length
143
+ @pending = dup
144
+ clear
145
+ remainder = size - length
146
+ if remainder > 0
147
+ last_string = @pending[-1]
148
+ @pending[-1] = last_string[0...-remainder]
149
+ push last_string[-remainder..-1]
150
+ end
151
+ end
152
+ # eat newline
153
+ return nil unless read_line
154
+ result = @pending.join
155
+ @pending = nil
156
+ result
157
+ end
158
+
159
+ # Read a newline terminated redis token, nil if none available
160
+ def read_line
161
+ unless @split
162
+ @split = join.split "\n", -1
163
+ clear
164
+ end
165
+ if @split.size > 1
166
+ result = @split.shift.chomp "\n"
167
+ else
168
+ result = nil
169
+ end
170
+ unshift_split if @split.size == 1
171
+ result
172
+ end
173
+
174
+ end
175
+ end