hiredis 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +10 -0
- data/Rakefile +77 -0
- data/ext/hiredis_ext/connection.c +215 -0
- data/ext/hiredis_ext/extconf.rb +21 -0
- data/ext/hiredis_ext/hiredis_ext.c +11 -0
- data/ext/hiredis_ext/hiredis_ext.h +38 -0
- data/ext/hiredis_ext/reader.c +140 -0
- data/lib/hiredis.rb +11 -0
- data/lib/hiredis/connection.rb +11 -0
- data/lib/hiredis/version.rb +3 -0
- data/vendor/hiredis/COPYING +10 -0
- data/vendor/hiredis/Makefile +102 -0
- data/vendor/hiredis/async.c +284 -0
- data/vendor/hiredis/async.h +94 -0
- data/vendor/hiredis/fmacros.h +15 -0
- data/vendor/hiredis/hiredis.c +926 -0
- data/vendor/hiredis/hiredis.h +157 -0
- data/vendor/hiredis/net.c +167 -0
- data/vendor/hiredis/net.h +37 -0
- data/vendor/hiredis/sds.c +479 -0
- data/vendor/hiredis/sds.h +77 -0
- data/vendor/hiredis/test.c +400 -0
- data/vendor/hiredis/util.h +40 -0
- metadata +115 -0
data/COPYING
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
Copyright (c) 2010, Pieter Noordhuis
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
5
|
+
|
6
|
+
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
7
|
+
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
8
|
+
* Neither the name of Redis nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
9
|
+
|
10
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/Rakefile
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
gem 'rake-compiler', '~> 0.7.1'
|
6
|
+
require "rake/extensiontask"
|
7
|
+
|
8
|
+
$:.unshift File.join(File.dirname(__FILE__), 'lib')
|
9
|
+
require 'hiredis/version'
|
10
|
+
|
11
|
+
GEM = 'hiredis'
|
12
|
+
GEM_VERSION = Hiredis::VERSION
|
13
|
+
AUTHORS = ['Pieter Noordhuis']
|
14
|
+
EMAIL = "pcnoordhuis@gmail.com"
|
15
|
+
HOMEPAGE = "http://github.com/pietern/hiredis-rb"
|
16
|
+
SUMMARY = "Ruby extension that wraps Hiredis (blocking connection and reply parsing)"
|
17
|
+
|
18
|
+
spec = Gem::Specification.new do |s|
|
19
|
+
s.name = GEM
|
20
|
+
s.version = GEM_VERSION
|
21
|
+
s.platform = Gem::Platform::RUBY
|
22
|
+
s.has_rdoc = true
|
23
|
+
s.extra_rdoc_files = ["COPYING"]
|
24
|
+
s.summary = SUMMARY
|
25
|
+
s.description = s.summary
|
26
|
+
s.authors = AUTHORS
|
27
|
+
s.email = EMAIL
|
28
|
+
s.homepage = HOMEPAGE
|
29
|
+
s.require_path = 'lib'
|
30
|
+
s.extensions = FileList["ext/**/extconf.rb"]
|
31
|
+
|
32
|
+
ext_files = Dir.glob("ext/**/*.{rb,c,h}")
|
33
|
+
lib_files = Dir.glob("lib/**/*.rb")
|
34
|
+
hiredis_files = Dir.glob("vendor/hiredis/*.{c,h}") -
|
35
|
+
Dir.glob("vendor/hiredis/example*") +
|
36
|
+
Dir.glob("vendor/hiredis/COPYING") +
|
37
|
+
Dir.glob("vendor/hiredis/Makefile")
|
38
|
+
s.files = %w(COPYING Rakefile) + ext_files + lib_files + hiredis_files
|
39
|
+
|
40
|
+
s.add_runtime_dependency "rake-compiler", "~> 0.7.1"
|
41
|
+
s.add_runtime_dependency "redis", "~> 2.1.0"
|
42
|
+
end
|
43
|
+
|
44
|
+
desc "create a gemspec file"
|
45
|
+
task :gemspec do
|
46
|
+
File.open("#{GEM}.gemspec", "w") do |file|
|
47
|
+
file.puts spec.to_ruby
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
Rake::ExtensionTask.new('hiredis_ext') do |task|
|
52
|
+
# Pass --with-foo-config args to extconf.rb
|
53
|
+
task.config_options = ARGV[1..-1]
|
54
|
+
task.lib_dir = File.join(*['lib', 'hiredis'])
|
55
|
+
end
|
56
|
+
|
57
|
+
namespace :hiredis do
|
58
|
+
task :clean do
|
59
|
+
# Fetch hiredis if not present
|
60
|
+
if !File.directory?("vendor/hiredis/.git")
|
61
|
+
system("git submodule update --init")
|
62
|
+
end
|
63
|
+
system("cd vendor/hiredis && make clean")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# "rake clean" should also clean bundled hiredis
|
68
|
+
Rake::Task[:clean].enhance(['hiredis:clean'])
|
69
|
+
|
70
|
+
# Build from scratch
|
71
|
+
task :build => [:clean, :compile]
|
72
|
+
|
73
|
+
desc "Run tests"
|
74
|
+
Rake::TestTask.new(:test) do |t|
|
75
|
+
t.libs << "test"
|
76
|
+
t.pattern = 'test/**/*_test.rb'
|
77
|
+
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
#include <sys/socket.h>
|
2
|
+
#include <errno.h>
|
3
|
+
#include "hiredis_ext.h"
|
4
|
+
|
5
|
+
typedef struct redisParentContext {
|
6
|
+
redisContext *context;
|
7
|
+
} redisParentContext;
|
8
|
+
|
9
|
+
static void parent_context_try_free(redisParentContext *pc) {
|
10
|
+
if (pc->context) {
|
11
|
+
redisFree(pc->context);
|
12
|
+
pc->context = NULL;
|
13
|
+
}
|
14
|
+
}
|
15
|
+
|
16
|
+
static void parent_context_mark(redisParentContext *pc) {
|
17
|
+
VALUE root;
|
18
|
+
fflush(stdout);
|
19
|
+
if (pc->context && pc->context->reader) {
|
20
|
+
root = (VALUE)redisReplyReaderGetObject(pc->context->reader);
|
21
|
+
if (root != 0 && TYPE(root) == T_ARRAY) {
|
22
|
+
rb_gc_mark(root);
|
23
|
+
}
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
static void parent_context_free(redisParentContext *pc) {
|
28
|
+
parent_context_try_free(pc);
|
29
|
+
free(pc);
|
30
|
+
}
|
31
|
+
|
32
|
+
static VALUE connection_parent_context_alloc(VALUE klass) {
|
33
|
+
redisParentContext *pc = malloc(sizeof(*pc));
|
34
|
+
pc->context = NULL;
|
35
|
+
return Data_Wrap_Struct(klass, parent_context_mark, parent_context_free, pc);
|
36
|
+
}
|
37
|
+
|
38
|
+
static VALUE connection_connect(VALUE self, VALUE _host, VALUE _port) {
|
39
|
+
redisParentContext *pc;
|
40
|
+
redisContext *c;
|
41
|
+
char *host = StringValuePtr(_host);
|
42
|
+
int port = NUM2INT(_port);
|
43
|
+
int err;
|
44
|
+
char errstr[1024];
|
45
|
+
|
46
|
+
Data_Get_Struct(self,redisParentContext,pc);
|
47
|
+
parent_context_try_free(pc);
|
48
|
+
|
49
|
+
c = redisConnect(host,port);
|
50
|
+
if (c->err) {
|
51
|
+
/* Copy error and free context */
|
52
|
+
err = c->err;
|
53
|
+
snprintf(errstr,sizeof(errstr),"%s",c->errstr);
|
54
|
+
redisFree(c);
|
55
|
+
|
56
|
+
if (err == REDIS_ERR_IO) {
|
57
|
+
/* Raise native Ruby I/O error */
|
58
|
+
rb_sys_fail(0);
|
59
|
+
} else {
|
60
|
+
/* Raise something else */
|
61
|
+
rb_raise(rb_eRuntimeError,"%s",errstr);
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
redisSetReplyObjectFunctions(c,&redisExtReplyObjectFunctions);
|
66
|
+
pc->context = c;
|
67
|
+
return Qnil;
|
68
|
+
}
|
69
|
+
|
70
|
+
static VALUE connection_is_connected(VALUE self) {
|
71
|
+
redisParentContext *pc;
|
72
|
+
Data_Get_Struct(self,redisParentContext,pc);
|
73
|
+
if (pc->context && !pc->context->err)
|
74
|
+
return Qtrue;
|
75
|
+
else
|
76
|
+
return Qfalse;
|
77
|
+
}
|
78
|
+
|
79
|
+
static VALUE connection_disconnect(VALUE self) {
|
80
|
+
redisParentContext *pc;
|
81
|
+
Data_Get_Struct(self,redisParentContext,pc);
|
82
|
+
if (!pc->context)
|
83
|
+
rb_raise(rb_eRuntimeError,"%s","not connected");
|
84
|
+
parent_context_try_free(pc);
|
85
|
+
return Qnil;
|
86
|
+
}
|
87
|
+
|
88
|
+
static VALUE connection_write(VALUE self, VALUE command) {
|
89
|
+
redisParentContext *pc;
|
90
|
+
int argc;
|
91
|
+
char **argv = NULL;
|
92
|
+
size_t *alen = NULL;
|
93
|
+
int i;
|
94
|
+
|
95
|
+
/* Commands should be an array of commands, where each command
|
96
|
+
* is an array of string arguments. */
|
97
|
+
if (TYPE(command) != T_ARRAY)
|
98
|
+
rb_raise(rb_eArgError,"%s","not an array");
|
99
|
+
|
100
|
+
Data_Get_Struct(self,redisParentContext,pc);
|
101
|
+
if (!pc->context)
|
102
|
+
rb_raise(rb_eRuntimeError,"%s","not connected");
|
103
|
+
|
104
|
+
argc = (int)RARRAY_LEN(command);
|
105
|
+
argv = malloc(argc*sizeof(char*));
|
106
|
+
alen = malloc(argc*sizeof(size_t));
|
107
|
+
for (i = 0; i < argc; i++) {
|
108
|
+
VALUE arg = rb_obj_as_string(RARRAY_PTR(command)[i]);
|
109
|
+
argv[i] = RSTRING_PTR(arg);
|
110
|
+
alen[i] = RSTRING_LEN(arg);
|
111
|
+
}
|
112
|
+
redisAppendCommandArgv(pc->context,argc,(const char**)argv,alen);
|
113
|
+
free(argv);
|
114
|
+
free(alen);
|
115
|
+
return Qnil;
|
116
|
+
}
|
117
|
+
|
118
|
+
static int __get_reply(redisParentContext *pc, VALUE *reply) {
|
119
|
+
redisContext *c = pc->context;
|
120
|
+
int wdone = 0;
|
121
|
+
void *aux = NULL;
|
122
|
+
|
123
|
+
/* Try to read pending replies */
|
124
|
+
if (redisGetReplyFromReader(c,&aux) == REDIS_ERR)
|
125
|
+
return -1;
|
126
|
+
|
127
|
+
if (aux == NULL) {
|
128
|
+
do { /* Write until done */
|
129
|
+
if (redisBufferWrite(c,&wdone) == REDIS_ERR)
|
130
|
+
return -1;
|
131
|
+
} while (!wdone);
|
132
|
+
do { /* Read until there is a reply */
|
133
|
+
rb_thread_wait_fd(c->fd);
|
134
|
+
if (redisBufferRead(c) == REDIS_ERR)
|
135
|
+
return -1;
|
136
|
+
if (redisGetReplyFromReader(c,&aux) == REDIS_ERR)
|
137
|
+
return -1;
|
138
|
+
} while (aux == NULL);
|
139
|
+
}
|
140
|
+
|
141
|
+
/* Set reply object */
|
142
|
+
if (reply != NULL) *reply = (VALUE)aux;
|
143
|
+
return 0;
|
144
|
+
}
|
145
|
+
|
146
|
+
static VALUE connection_read(VALUE self) {
|
147
|
+
redisParentContext *pc;
|
148
|
+
VALUE reply;
|
149
|
+
int err;
|
150
|
+
char errstr[1024];
|
151
|
+
|
152
|
+
Data_Get_Struct(self,redisParentContext,pc);
|
153
|
+
if (!pc->context)
|
154
|
+
rb_raise(rb_eRuntimeError, "not connected");
|
155
|
+
|
156
|
+
if (__get_reply(pc,&reply) == -1) {
|
157
|
+
/* Copy error and free context */
|
158
|
+
err = pc->context->err;
|
159
|
+
snprintf(errstr,sizeof(errstr),"%s",pc->context->errstr);
|
160
|
+
parent_context_try_free(pc);
|
161
|
+
|
162
|
+
switch(err) {
|
163
|
+
case REDIS_ERR_IO:
|
164
|
+
/* Raise native Ruby I/O error */
|
165
|
+
rb_sys_fail(0);
|
166
|
+
break;
|
167
|
+
case REDIS_ERR_EOF:
|
168
|
+
/* Raise our own EOF error */
|
169
|
+
rb_raise(error_eof,"%s",errstr);
|
170
|
+
break;
|
171
|
+
default:
|
172
|
+
/* Raise something else */
|
173
|
+
rb_raise(rb_eRuntimeError,"%s",errstr);
|
174
|
+
}
|
175
|
+
}
|
176
|
+
|
177
|
+
if (rb_ivar_defined(reply,ivar_hiredis_error)) {
|
178
|
+
VALUE error = rb_ivar_get(reply,ivar_hiredis_error);
|
179
|
+
rb_raise(rb_eRuntimeError,"%s",RSTRING_PTR(error));
|
180
|
+
}
|
181
|
+
|
182
|
+
return reply;
|
183
|
+
}
|
184
|
+
|
185
|
+
static VALUE connection_set_timeout(VALUE self, VALUE usecs) {
|
186
|
+
redisParentContext *pc;
|
187
|
+
int s = NUM2INT(usecs)/1000000;
|
188
|
+
int us = NUM2INT(usecs)-(s*1000000);
|
189
|
+
struct timeval timeout = { s, us };
|
190
|
+
|
191
|
+
Data_Get_Struct(self,redisParentContext,pc);
|
192
|
+
if (!pc->context)
|
193
|
+
rb_raise(rb_eRuntimeError, "not connected");
|
194
|
+
|
195
|
+
if (setsockopt(pc->context->fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) == -1)
|
196
|
+
rb_sys_fail(0);
|
197
|
+
if (setsockopt(pc->context->fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) == -1)
|
198
|
+
rb_sys_fail(0);
|
199
|
+
return usecs;
|
200
|
+
}
|
201
|
+
|
202
|
+
|
203
|
+
VALUE klass_connection;
|
204
|
+
VALUE error_eof;
|
205
|
+
void InitConnection(VALUE mod) {
|
206
|
+
klass_connection = rb_define_class_under(mod, "Connection", rb_cObject);
|
207
|
+
rb_define_alloc_func(klass_connection, connection_parent_context_alloc);
|
208
|
+
rb_define_method(klass_connection, "connect", connection_connect, 2);
|
209
|
+
rb_define_method(klass_connection, "connected?", connection_is_connected, 0);
|
210
|
+
rb_define_method(klass_connection, "disconnect", connection_disconnect, 0);
|
211
|
+
rb_define_method(klass_connection, "timeout=", connection_set_timeout, 1);
|
212
|
+
rb_define_method(klass_connection, "write", connection_write, 1);
|
213
|
+
rb_define_method(klass_connection, "read", connection_read, 0);
|
214
|
+
error_eof = rb_define_class_under(klass_connection, "EOFError", rb_eStandardError);
|
215
|
+
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
RbConfig::MAKEFILE_CONFIG['CC'] = ENV['CC'] if ENV['CC']
|
4
|
+
|
5
|
+
def need_header(*args)
|
6
|
+
abort "\n--- #{args.first} is missing\n\n" if !find_header(*args)
|
7
|
+
end
|
8
|
+
|
9
|
+
def need_library(*args)
|
10
|
+
abort "\n--- lib#{args.first} is missing\n\n" if !find_library(*args)
|
11
|
+
end
|
12
|
+
|
13
|
+
bundled_hiredis_dir = File.join(File.dirname(__FILE__), %w{.. .. vendor hiredis})
|
14
|
+
dir_config('hiredis', bundled_hiredis_dir, bundled_hiredis_dir)
|
15
|
+
|
16
|
+
# Compile hiredis when the bundled version can be found
|
17
|
+
system("cd #{bundled_hiredis_dir} && make static") if File.directory?(bundled_hiredis_dir)
|
18
|
+
|
19
|
+
need_header('hiredis.h')
|
20
|
+
need_library('hiredis', 'redisReplyReaderCreate')
|
21
|
+
create_makefile('hiredis_ext/hiredis_ext')
|
@@ -0,0 +1,38 @@
|
|
1
|
+
#ifndef __HIREDIS_EXT_H
|
2
|
+
#define __HIREDIS_EXT_H
|
3
|
+
|
4
|
+
#include "hiredis.h"
|
5
|
+
#include "ruby.h"
|
6
|
+
|
7
|
+
/* Defined in hiredis_ext.c */
|
8
|
+
extern VALUE mod_hiredis;
|
9
|
+
|
10
|
+
/* Defined in reader.c */
|
11
|
+
extern redisReplyObjectFunctions redisExtReplyObjectFunctions;
|
12
|
+
extern VALUE klass_reader;
|
13
|
+
extern ID ivar_hiredis_error; /* ivar used to store error reply ("-ERR message") */
|
14
|
+
extern void InitReader(VALUE module);
|
15
|
+
|
16
|
+
/* Defined in connection.c */
|
17
|
+
extern VALUE klass_connection;
|
18
|
+
extern VALUE error_eof;
|
19
|
+
extern void InitConnection(VALUE module);
|
20
|
+
|
21
|
+
/* Borrowed from Nokogiri */
|
22
|
+
#ifndef RSTRING_PTR
|
23
|
+
#define RSTRING_PTR(s) (RSTRING(s)->ptr)
|
24
|
+
#endif
|
25
|
+
|
26
|
+
#ifndef RSTRING_LEN
|
27
|
+
#define RSTRING_LEN(s) (RSTRING(s)->len)
|
28
|
+
#endif
|
29
|
+
|
30
|
+
#ifndef RARRAY_PTR
|
31
|
+
#define RARRAY_PTR(a) RARRAY(a)->ptr
|
32
|
+
#endif
|
33
|
+
|
34
|
+
#ifndef RARRAY_LEN
|
35
|
+
#define RARRAY_LEN(a) RARRAY(a)->len
|
36
|
+
#endif
|
37
|
+
|
38
|
+
#endif
|
@@ -0,0 +1,140 @@
|
|
1
|
+
#include <assert.h>
|
2
|
+
#include "hiredis_ext.h"
|
3
|
+
|
4
|
+
/* Force encoding on new strings? */
|
5
|
+
static VALUE enc_klass;
|
6
|
+
static ID enc_default_external = 0;
|
7
|
+
static ID str_force_encoding = 0;
|
8
|
+
|
9
|
+
/* Singleton method to test if the reply contains an error. */
|
10
|
+
ID ivar_hiredis_error;
|
11
|
+
|
12
|
+
/* Add VALUE to parent when the redisReadTask has a parent.
|
13
|
+
* Note that the parent should always be of type T_ARRAY. */
|
14
|
+
static void *tryParentize(const redisReadTask *task, VALUE v) {
|
15
|
+
if (task && task->parent != NULL) {
|
16
|
+
VALUE parent = (VALUE)task->parent;
|
17
|
+
assert(TYPE(parent) == T_ARRAY);
|
18
|
+
rb_ary_store(parent,task->idx,v);
|
19
|
+
}
|
20
|
+
return (void*)v;
|
21
|
+
}
|
22
|
+
|
23
|
+
static VALUE object_contains_error(VALUE self) {
|
24
|
+
return Qtrue;
|
25
|
+
}
|
26
|
+
|
27
|
+
static void *createStringObject(const redisReadTask *task, char *str, size_t len) {
|
28
|
+
VALUE v, enc;
|
29
|
+
v = rb_str_new(str,len);
|
30
|
+
|
31
|
+
/* Force default external encoding if possible. */
|
32
|
+
if (enc_default_external) {
|
33
|
+
enc = rb_funcall(enc_klass,enc_default_external,0);
|
34
|
+
v = rb_funcall(v,str_force_encoding,1,enc);
|
35
|
+
}
|
36
|
+
|
37
|
+
if (task->type == REDIS_REPLY_ERROR) {
|
38
|
+
rb_ivar_set(v,ivar_hiredis_error,v);
|
39
|
+
if (task && task->parent != NULL) {
|
40
|
+
/* Also make the parent respond to this method. Redis currently
|
41
|
+
* only emits nested multi bulks of depth 2, so we don't need
|
42
|
+
* to cascade setting this ivar. Make sure to only set the first
|
43
|
+
* error reply on the parent. */
|
44
|
+
VALUE parent = (VALUE)task->parent;
|
45
|
+
if (!rb_ivar_defined(parent,ivar_hiredis_error))
|
46
|
+
rb_ivar_set(parent,ivar_hiredis_error,v);
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
return tryParentize(task,v);
|
51
|
+
}
|
52
|
+
|
53
|
+
static void *createArrayObject(const redisReadTask *task, int elements) {
|
54
|
+
VALUE v = rb_ary_new2(elements);
|
55
|
+
return tryParentize(task,v);
|
56
|
+
}
|
57
|
+
|
58
|
+
static void *createIntegerObject(const redisReadTask *task, long long value) {
|
59
|
+
VALUE v = LL2NUM(value);
|
60
|
+
return tryParentize(task,v);
|
61
|
+
}
|
62
|
+
|
63
|
+
static void *createNilObject(const redisReadTask *task) {
|
64
|
+
return tryParentize(task,Qnil);
|
65
|
+
}
|
66
|
+
|
67
|
+
static void freeObject(void *ptr) {
|
68
|
+
/* Garbage collection will clean things up. */
|
69
|
+
}
|
70
|
+
|
71
|
+
/* Declare our set of reply object functions only once. */
|
72
|
+
redisReplyObjectFunctions redisExtReplyObjectFunctions = {
|
73
|
+
createStringObject,
|
74
|
+
createArrayObject,
|
75
|
+
createIntegerObject,
|
76
|
+
createNilObject,
|
77
|
+
freeObject
|
78
|
+
};
|
79
|
+
|
80
|
+
static void reader_mark(void *reader) {
|
81
|
+
VALUE root;
|
82
|
+
root = (VALUE)redisReplyReaderGetObject(reader);
|
83
|
+
if (root != 0 && TYPE(root) == T_ARRAY) rb_gc_mark(root);
|
84
|
+
}
|
85
|
+
|
86
|
+
static VALUE reader_allocate(VALUE klass) {
|
87
|
+
void *reader = redisReplyReaderCreate();
|
88
|
+
redisReplyReaderSetReplyObjectFunctions(reader,&redisExtReplyObjectFunctions);
|
89
|
+
return Data_Wrap_Struct(klass, reader_mark, redisReplyReaderFree, reader);
|
90
|
+
}
|
91
|
+
|
92
|
+
static VALUE reader_feed(VALUE klass, VALUE str) {
|
93
|
+
void *reader;
|
94
|
+
unsigned int size;
|
95
|
+
|
96
|
+
if (TYPE(str) != T_STRING)
|
97
|
+
rb_raise(rb_eTypeError, "not a string");
|
98
|
+
|
99
|
+
Data_Get_Struct(klass, void, reader);
|
100
|
+
redisReplyReaderFeed(reader,RSTRING_PTR(str),(size_t)RSTRING_LEN(str));
|
101
|
+
return INT2NUM(0);
|
102
|
+
}
|
103
|
+
|
104
|
+
static VALUE reader_gets(VALUE klass) {
|
105
|
+
void *reader;
|
106
|
+
VALUE reply;
|
107
|
+
|
108
|
+
Data_Get_Struct(klass, void, reader);
|
109
|
+
if (redisReplyReaderGetReply(reader,(void**)&reply) != REDIS_OK) {
|
110
|
+
char *errstr = redisReplyReaderGetError(reader);
|
111
|
+
rb_raise(rb_eRuntimeError,"%s",errstr);
|
112
|
+
}
|
113
|
+
|
114
|
+
if (rb_ivar_defined(reply,ivar_hiredis_error)) {
|
115
|
+
VALUE error = rb_ivar_get(reply,ivar_hiredis_error);
|
116
|
+
rb_raise(rb_eRuntimeError,"%s",RSTRING_PTR(error));
|
117
|
+
}
|
118
|
+
|
119
|
+
return reply;
|
120
|
+
}
|
121
|
+
|
122
|
+
VALUE klass_reader;
|
123
|
+
void InitReader(VALUE mod) {
|
124
|
+
klass_reader = rb_define_class_under(mod, "Reader", rb_cObject);
|
125
|
+
rb_define_alloc_func(klass_reader, reader_allocate);
|
126
|
+
rb_define_method(klass_reader, "feed", reader_feed, 1);
|
127
|
+
rb_define_method(klass_reader, "gets", reader_gets, 0);
|
128
|
+
ivar_hiredis_error = rb_intern("@__hiredis_error");
|
129
|
+
|
130
|
+
/* If the Encoding class is present, #default_external should be used to
|
131
|
+
* determine the encoding for new strings. The "enc_default_external"
|
132
|
+
* ID is non-zero when encoding should be set on new strings. */
|
133
|
+
if (rb_const_defined(rb_cObject, rb_intern("Encoding"))) {
|
134
|
+
enc_klass = rb_const_get(rb_cObject, rb_intern("Encoding"));
|
135
|
+
enc_default_external = rb_intern("default_external");
|
136
|
+
str_force_encoding = rb_intern("force_encoding");
|
137
|
+
} else {
|
138
|
+
enc_default_external = 0;
|
139
|
+
}
|
140
|
+
}
|