hiredis 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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.
@@ -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,11 @@
1
+ #include <stdlib.h>
2
+ #include <string.h>
3
+ #include <assert.h>
4
+ #include "hiredis_ext.h"
5
+
6
+ VALUE mod_hiredis;
7
+ void Init_hiredis_ext() {
8
+ mod_hiredis = rb_define_module("Hiredis");
9
+ InitReader(mod_hiredis);
10
+ InitConnection(mod_hiredis);
11
+ }
@@ -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
+ }