droonga-client 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ee9432ca6044b36092942ac41aa627930b7b9d9a
4
- data.tar.gz: a6f8e194ccad61f14f5f270e57994a69fdfc5fe5
3
+ metadata.gz: c506f2bd7e3f43ef73c8ed5463a678fb6a281cda
4
+ data.tar.gz: b6f6a5fec2421d4b625060d9b4d2f61b1d229cdc
5
5
  SHA512:
6
- metadata.gz: 11b805404b3bffee885633090fdde5cee4966e4cf27d0ec781770d1e6c50a488715500c6cc2c0a6cab657eef3d86563b8bbf7ea0eece59abbc650567b645c90e
7
- data.tar.gz: 2966e8a79f03671b924d2d1214ec5ea1f990e3ccf9fa7138a78b768036c78e1f799258c19234ca3eb587211971a39272553ca83cd9a64a81b5ddf24720e14bf6
6
+ metadata.gz: 8c644cdee887e5733d8349eda8ea6aa5f316d73357ea311e0af419a439d13d998260d7443ae7f8062b1aba1e021c6a64f2d7360f4acc0225c5b630cd9c67ea53
7
+ data.tar.gz: 3cc489e4cb5de93c5705df1a3c961fd6d320f4e97b20fd0529fd4ce63509374e02af892c48dad4bf7c9d52f0cc98310744f31ee2b538a066ecb7194dae6cc91d
data/bin/droonga-add ADDED
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (C) 2015 Droonga Project
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License version 2.1 as published by the Free Software Foundation.
8
+ #
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
+
18
+ require "json"
19
+
20
+ require "droonga/command/base"
21
+
22
+ module Droonga
23
+ module Command
24
+ class Add < Base
25
+ def run
26
+ parse_options do |option|
27
+ option.banner("Usage: droonga-add [options]\n" +
28
+ " ex: droonga-add --table User --name Adam --age 10\n" +
29
+ " droonga-add --table Server --value:host example.com\n" +
30
+ " (You can specify column value expressly with the prefix \"value:\".)")
31
+
32
+ option.on("table=",
33
+ "Name of the target table.",
34
+ :required => true)
35
+ option.on("key=",
36
+ "A unique key for the added record.",
37
+ :default => nil)
38
+ end
39
+
40
+ add_params = {
41
+ "table" => @options[:table],
42
+ "key" => @options[:key],
43
+ "values" => build_values(ARGV),
44
+ }
45
+ add_message = {
46
+ "dataset" => @options[:dataset],
47
+ "type" => "add",
48
+ "body" => add_params,
49
+ }
50
+
51
+ puts "Adding new record..."
52
+ puts(JSON.pretty_generate(add_params))
53
+
54
+ response = request(add_message)
55
+ raise NoResponse.new unless response
56
+
57
+ if response["body"]
58
+ puts "Done."
59
+ true
60
+ else
61
+ false
62
+ end
63
+ rescue MissingRequiredParameter
64
+ puts(@options)
65
+ false
66
+ rescue NoResponse
67
+ puts("Error: request timed out.")
68
+ false
69
+ end
70
+
71
+ private
72
+ def build_values(argv)
73
+ values = {}
74
+ column_name = nil
75
+ argv.each do |arg|
76
+ case arg
77
+ when /\A--value:([^\s=]+)=(.+)\z/
78
+ values[$1] = $2
79
+ when /\A--value:([^\s=]+)\z/
80
+ column_name = $1
81
+ when /\A--([^\s=]+)=(.+)\z/
82
+ values[$1] = $2
83
+ when /\A--([^\s=]+)\z/
84
+ column_name = $1
85
+ else
86
+ if column_name
87
+ values[column_name] = arg
88
+ column_name = nil
89
+ end
90
+ end
91
+ end
92
+ values
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ exit(Droonga::Command::Add.new.run)
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (C) 2015 Droonga Project
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License version 2.1 as published by the Free Software Foundation.
8
+ #
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
+
18
+ require "json"
19
+
20
+ require "droonga/command/base"
21
+
22
+ module Droonga
23
+ module Command
24
+ class Groonga < Base
25
+ class InvalidCommandName < StandardError
26
+ end
27
+
28
+ class MissingCommandName < StandardError
29
+ end
30
+
31
+ def run
32
+ parse_options do |option|
33
+ option.banner("Usage: droonga-groonga [groonga-command-name] [options]\n" +
34
+ " ex: droonga-groonga select --table=Users")
35
+
36
+ option.separator("Formatting:")
37
+ option.on(:pretty,
38
+ "Output result as a pretty print JSON.",
39
+ :default => false)
40
+ end
41
+
42
+ @command = ARGV.shift
43
+ assert_valid_command
44
+
45
+ groonga_message = {
46
+ "dataset" => @options[:dataset],
47
+ "type" => @command,
48
+ "body" => build_params(ARGV),
49
+ }
50
+
51
+ response = request(groonga_message)
52
+ raise NoResponse.new unless response
53
+ body = response["body"]
54
+
55
+ if @options[:pretty]
56
+ puts(JSON.pretty_generate(body))
57
+ else
58
+ puts(JSON.generate(body))
59
+ end
60
+
61
+ true
62
+ rescue MissingRequiredParameter, InvalidCommandName, MissingCommandName
63
+ puts(@options)
64
+ false
65
+ rescue NoResponse
66
+ puts("Error: request timed out.")
67
+ false
68
+ end
69
+
70
+ private
71
+ def assert_valid_command
72
+ raise InvalidCommandName.new(@command) if /\A--.+\z/ =~ @command
73
+ raise MissingCommandName.new if @command.nil?
74
+ end
75
+
76
+ def build_params(argv)
77
+ params = {}
78
+ option_name = nil
79
+ argv.each do |arg|
80
+ case arg
81
+ when /\A--([^\s=]+)=(.+)\z/
82
+ params[$1] = $2
83
+ when /\A--([^\s=]+)\z/
84
+ option_name = $1
85
+ else
86
+ if option_name
87
+ params[option_name] = arg
88
+ option_name = nil
89
+ end
90
+ end
91
+ end
92
+ params
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ exit(Droonga::Command::Groonga.new.run)
data/bin/droonga-request CHANGED
@@ -22,16 +22,20 @@ require "json"
22
22
  require "droonga/client"
23
23
 
24
24
  options = {
25
- :host => "localhost",
26
- :port => 10031,
27
- :tag => "droonga",
25
+ :host => Droonga::Client::DEFAULT_HOST,
26
+ :port => Droonga::Client::DEFAULT_PORT,
27
+ :tag => Droonga::Client::DEFAULT_TAG,
28
+ :default_dataset => Droonga::Client::DEFAULT_DATASET,
28
29
  :protocol => :droonga,
30
+ :default_target_role => Droonga::Client::DEFAULT_TARGET_ROLE,
29
31
  :timeout => 1,
30
32
  :exit_on_response => true,
31
- :receiver_host => "localhost",
33
+ :receiver_host => Droonga::Client::DEFAULT_HOST,
32
34
  :receiver_port => 0,
33
35
  :report_request => false,
34
36
  :report_elapsed_time => true,
37
+ :completion => true,
38
+ :validation => true,
35
39
  }
36
40
 
37
41
  parser = OptionParser.new
@@ -97,11 +101,34 @@ parser.on("--[no-]report-elapsed-time",
97
101
  "(#{options[:report_elapsed_time]})") do |report_elapsed_time|
98
102
  options[:report_elapsed_time] = report_elapsed_time
99
103
  end
104
+ parser.separator("")
105
+ parser.separator("Messages:")
106
+ parser.on("--detault-dataset=NAME",
107
+ "Default dataset name for sending messages.",
108
+ "(#{options[:default_dataset]})") do |name|
109
+ options[:default_dataset] = name
110
+ end
111
+ parser.on("--detault-target-role=ROLE",
112
+ "Default target role of engine nodes for sending messages.",
113
+ "(#{options[:default_target_role]})") do |role|
114
+ options[:default_target_role] = role
115
+ end
116
+ parser.on("--[no-]completion",
117
+ "Do completion of required fields for input message or not.",
118
+ "(#{options[:completion]})") do |completion|
119
+ options[:completion] = completion
120
+ end
121
+ parser.on("--[no-]validation",
122
+ "Do validation for input message or not.",
123
+ "(#{options[:validation]})") do |validation|
124
+ options[:validation] = validation
125
+ end
100
126
  request_json_files = parser.parse!(ARGV)
101
127
 
102
128
  client = Droonga::Client.new(options)
103
129
  json_parser = Yajl::Parser.new
104
130
  json_parser.on_parse_complete = lambda do |request_message|
131
+ request_message["dataset"] ||= options[:default_dataset]
105
132
  if options[:report_request]
106
133
  message = "Request: "
107
134
  begin
data/bin/droonga-send CHANGED
@@ -26,11 +26,16 @@ options = OpenStruct.new
26
26
  options.report_request = false
27
27
  options.report_throughput = false
28
28
  options.default_protocol = "droonga"
29
- options.default_port = 10031
30
- options.default_tag = "droonga"
29
+ options.default_host = Droonga::Client::DEFAULT_HOST
30
+ options.default_port = Droonga::Client::DEFAULT_PORT
31
+ options.default_tag = Droonga::Client::DEFAULT_TAG
32
+ options.default_dataset = Droonga::Client::DEFAULT_DATASET
33
+ options.default_target_role = Droonga::Client::DEFAULT_TARGET_ROLE
34
+ options.completion = true
35
+ options.validation = true
31
36
 
32
37
  servers = []
33
- default_server = "droonga:localhost:10031/droonga"
38
+ default_server = "#{options.default_protocol}:#{options.default_host}:#{options.default_port}/#{options.default_tag}"
34
39
  messages_per_second = Droonga::Client::RateLimiter::DEFAULT_LIMIT
35
40
 
36
41
  def parse_server(server, options)
@@ -57,6 +62,9 @@ def parse_server(server, options)
57
62
  :port => Integer(port),
58
63
  :protocol => protocol.to_sym,
59
64
  :tag => tag,
65
+ :default_target_role => options.default_target_role,
66
+ :completion => options.completion,
67
+ :validation => options.validation,
60
68
  }
61
69
  end
62
70
 
@@ -138,6 +146,28 @@ parser.on("--report-throughput",
138
146
  "(no)") do
139
147
  options.report_throughput = true
140
148
  end
149
+ parser.separator("")
150
+ parser.separator("Messages:")
151
+ parser.on("--default-dataset=NAME",
152
+ "Default dataset name for sending messages.",
153
+ "(#{options.default_dataset})") do |name|
154
+ options.default_dataset = name
155
+ end
156
+ parser.on("--default-target-role=ROLE",
157
+ "Default target role of engine nodes for sending messages.",
158
+ "(#{options.default_target_role})") do |role|
159
+ options.default_target_role = role
160
+ end
161
+ parser.on("--[no-]completion",
162
+ "Do completion of required fields for input message or not.",
163
+ "(#{options.completion})") do |completion|
164
+ options.completion = completion
165
+ end
166
+ parser.on("--[no-]validation",
167
+ "Do validation for input message or not.",
168
+ "(#{options.validation})") do |validation|
169
+ options.validation = validation
170
+ end
141
171
  request_json_files = parser.parse!(ARGV)
142
172
 
143
173
  servers << default_server if servers.empty?
@@ -157,6 +187,7 @@ end
157
187
  client_index = 0
158
188
  json_parser = Yajl::Parser.new
159
189
  json_parser.on_parse_complete = lambda do |request_message|
190
+ request_message["dataset"] ||= options.default_dataset
160
191
  if options.report_request
161
192
  message = "Request: "
162
193
  begin
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (C) 2015 Droonga Project
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License version 2.1 as published by the Free Software Foundation.
8
+ #
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
+
18
+ require "json"
19
+
20
+ require "droonga/command/base"
21
+
22
+ module Droonga
23
+ module Command
24
+ class SystemStatus < Base
25
+ def run
26
+ parse_options do |option|
27
+ option.on(:pretty,
28
+ "Output result as a pretty print JSON.",
29
+ :default => false)
30
+ end
31
+
32
+ response = request("dataset" => @options[:dataset],
33
+ "type" => "system.status")
34
+ raise NoResponse.new unless response
35
+ body = response["body"]
36
+
37
+ if @options[:pretty]
38
+ puts(JSON.pretty_generate(body))
39
+ else
40
+ puts(JSON.generate(body))
41
+ end
42
+
43
+ true
44
+ rescue MissingRequiredParameter
45
+ puts(@options)
46
+ false
47
+ rescue NoResponse
48
+ puts("Error: request timed out.")
49
+ false
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ exit(Droonga::Command::SystemStatus.new.run)
data/doc/text/news.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # News
2
2
 
3
+ ## 0.2.1: 2005-04-29
4
+
5
+ * Required fields of input messages are automatically completed by default.
6
+ * Input messages are automatically validated by default.
7
+ * Timeout option for subscription is available.
8
+ You can unsubscribe subscription automatically with specified timeout, like:
9
+ `client.subscribe(request, :subscription_timeout => 10)`
10
+ * New utility commands are available as shorthand.
11
+ * `droonga-system-status`: allows you to send a `system.status` request to the cluster easily.
12
+ * `droonga-add`: allows you to send an `add` request to the cluster easily.
13
+ * `droonga-groonga`: works like the `groonga` command.
14
+ * droonga-send, droonga-request:
15
+ * A new option `--[no-]completion` is introduced.
16
+ You should specify `--no-completion` to send incomplete messages intentionally.
17
+ * A new option `--[no-]validation` is introduced.
18
+ You should specify `--no-validation` to send invalid messages intentionally.
19
+ * A new option `--default-dataset` is introduced.
20
+ It is used for sending messages if they have no `dataset` field.
21
+ * A new option `--default-target-role` is introduced.
22
+ It is used for sending messages if they have no `targetRole` field.
23
+ * The "date" field is filled with the format same to droonga-engine's internal one
24
+ like "2015-04-08T06:16:20.571303Z".
25
+
3
26
  ## 0.2.0: 2014-11-29
4
27
 
5
28
  * droonga-send:
@@ -35,13 +35,14 @@ Gem::Specification.new do |spec|
35
35
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
36
36
  spec.require_paths = ["lib"]
37
37
 
38
- spec.add_runtime_dependency "msgpack"
38
+ spec.add_runtime_dependency "droonga-message-pack-packer"
39
39
  spec.add_runtime_dependency "fluent-logger"
40
+ spec.add_runtime_dependency "msgpack"
40
41
  spec.add_runtime_dependency "rack"
42
+ spec.add_runtime_dependency "slop", "<= 3.6.0"
41
43
  spec.add_runtime_dependency "yajl-ruby"
42
- spec.add_runtime_dependency "droonga-message-pack-packer"
43
44
 
44
45
  spec.add_development_dependency "bundler", "~> 1.3"
45
- spec.add_development_dependency "rake"
46
46
  spec.add_development_dependency "packnga"
47
+ spec.add_development_dependency "rake"
47
48
  end
@@ -20,9 +20,27 @@ require "droonga/client/error"
20
20
  require "droonga/client/connection/http"
21
21
  require "droonga/client/connection/droonga-protocol"
22
22
  require "droonga/client/rate-limiter"
23
+ require "droonga/client/message_completer"
24
+ require "droonga/client/message_validator"
23
25
 
24
26
  module Droonga
25
27
  class Client
28
+ DEFAULT_HOST = Socket.gethostname
29
+ DEFAULT_HOST.force_encoding("US-ASCII") if DEFAULT_HOST.ascii_only?
30
+ DEFAULT_PORT = 10031
31
+ DEFAULT_TAG = "droonga"
32
+ DEFAULT_DATASET = "Default"
33
+ DEFAULT_TARGET_ROLE = "any"
34
+ DEFAULT_TIMEOUT_SECONDS = 3
35
+
36
+ attr_writer :on_error
37
+
38
+ class ConnectionError < StandardError
39
+ def initialize(error)
40
+ super(error.inspect)
41
+ end
42
+ end
43
+
26
44
  class << self
27
45
  # Opens a new connection and yields a {Client} object to use the
28
46
  # connection. The client is closed after the given block is
@@ -60,30 +78,39 @@ module Droonga
60
78
  # @option options [Integer] :timeout (5)
61
79
  # The timeout value for connecting to, writing to and reading
62
80
  # from Droonga Engine.
81
+ # @option options [Boolean] :completion (true)
82
+ # Do or do not complete required fields of input messages.
83
+ # @option options [Boolean] :validation (true)
84
+ # Do or do not validate input messages.
63
85
  def initialize(options={})
64
86
  @connection = create_connection(options)
87
+ @connection.on_error = lambda do |error|
88
+ on_error(ConnectionError.new(error))
89
+ end
90
+
91
+ @completion = options[:completion] != false
92
+ @validation = options[:validation] != false
93
+
94
+ @completer = MessageCompleter.new(:default_timeout => options[:default_timeout],
95
+ :default_target_role => options[:default_target_role])
96
+ @validator = MessageValidator.new
65
97
  end
66
98
 
67
99
  def send(message, options={}, &block)
68
- if message["id"].nil? or message["date"].nil?
69
- id = message["id"] || generate_id
70
- date = message["date"] || Time.now
71
- message = message.merge("id" => id, "date" => date)
72
- end
100
+ message = do_completion(message, options)
101
+ do_validation(message, options)
73
102
  @connection.send(message, options, &block)
74
103
  end
75
104
 
76
105
  def request(message, options={}, &block)
77
- if message["id"].nil?
78
- message = message.merge("id" => generate_id)
79
- end
106
+ message = do_completion(message, options)
107
+ do_validation(message, options)
80
108
  @connection.request(message, options, &block)
81
109
  end
82
110
 
83
111
  def subscribe(message, options={}, &block)
84
- if message["id"].nil?
85
- message = message.merge("id" => generate_id)
86
- end
112
+ message = do_completion(message, options)
113
+ do_validation(message, options)
87
114
  @connection.subscribe(message, options, &block)
88
115
  end
89
116
 
@@ -105,8 +132,26 @@ module Droonga
105
132
  end
106
133
  end
107
134
 
108
- def generate_id
109
- Time.now.to_f.to_s
135
+ def do_completion(message, options={})
136
+ if options[:completion].nil?
137
+ return message unless @completion
138
+ else
139
+ return message if options[:completion] == false
140
+ end
141
+ @completer.complete(message)
142
+ end
143
+
144
+ def do_validation(message, options={})
145
+ if options[:validation].nil?
146
+ return unless @validation
147
+ else
148
+ return if options[:validation] == false
149
+ end
150
+ @validator.validate(message)
151
+ end
152
+
153
+ def on_error(error)
154
+ @on_error.call(error) if @on_error
110
155
  end
111
156
  end
112
157
  end
@@ -21,12 +21,23 @@ module Droonga
21
21
  class Client
22
22
  module Connection
23
23
  class DroongaProtocol
24
+ class BackendError < StandardError
25
+ def initialize(error)
26
+ super(error.inspect)
27
+ end
28
+ end
29
+
30
+ attr_writer :on_error
31
+
24
32
  def initialize(options={})
25
33
  @host = options[:host] || "127.0.0.1"
26
34
  @port = options[:port] || 24224
27
35
  @tag = options[:tag] || "droonga"
28
36
  @options = options
29
37
  @backend = create_backend
38
+ @backend.on_error = lambda do |error|
39
+ on_error(BackendError.new(error))
40
+ end
30
41
  end
31
42
 
32
43
  # Sends a request message and receives one or more response
@@ -121,6 +132,10 @@ module Droonga
121
132
  backend_class = self.class.const_get(backend_name)
122
133
  backend_class.new(@host, @port, @tag, @options)
123
134
  end
135
+
136
+ def on_error(error)
137
+ @on_error.call(error) if @on_error
138
+ end
124
139
  end
125
140
  end
126
141
  end
@@ -21,6 +21,17 @@ module Droonga
21
21
  module Connection
22
22
  class DroongaProtocol
23
23
  class Coolio
24
+ attr_writer :on_error
25
+
26
+ class ReceiverError < StandardError
27
+ def initialize(error)
28
+ super(error.inspect)
29
+ end
30
+ end
31
+
32
+ class NilMessage < StandardError
33
+ end
34
+
24
35
  class Request
25
36
  def initialize(receiver, id, loop)
26
37
  @receiver = receiver
@@ -37,11 +48,22 @@ module Droonga
37
48
  end
38
49
 
39
50
  class InfiniteRequest
40
- def initialize(loop)
51
+ attr_writer :on_timeout
52
+
53
+ def initialize(loop, options={})
41
54
  @loop = loop
55
+ @subscription_timeout = options[:subscription_timeout]
42
56
  end
43
57
 
44
58
  def wait
59
+ if @subscription_timeout
60
+ @timer = Coolio::TimerWatcher.new(@subscription_timeout)
61
+ @timer.on_timer do
62
+ @timer.detach
63
+ @on_timeout.call if @on_timeout
64
+ end
65
+ @loop.attach(@timer)
66
+ end
45
67
  @loop.run
46
68
  end
47
69
  end
@@ -91,6 +113,9 @@ module Droonga
91
113
  end
92
114
 
93
115
  class Receiver < ::Coolio::TCPServer
116
+ attr_accessor :max_messages
117
+ attr_writer :on_error
118
+
94
119
  def initialize(*args)
95
120
  super(*args) do |engine|
96
121
  @engines << engine
@@ -98,6 +123,7 @@ module Droonga
98
123
  end
99
124
  @requests = {}
100
125
  @engines = []
126
+ @max_messages = nil
101
127
  end
102
128
 
103
129
  def close
@@ -142,14 +168,28 @@ module Droonga
142
168
  private
143
169
  def handle_engine(engine)
144
170
  unpacker = MessagePack::Unpacker.new
171
+ n_messages = 0
145
172
  on_read = lambda do |data|
146
173
  unpacker.feed_each(data) do |fluent_message|
174
+ unless fluent_message
175
+ on_error(NilMessage.new("coolio / unpacker.feed_each"))
176
+ end
147
177
  tag, time, droonga_message = fluent_message
178
+ unless droonga_message
179
+ on_error(NilMessage.new("coolio / unpacker.feed_each",
180
+ :fluent_message => fluent_message.inspect))
181
+ end
148
182
  id = droonga_message["inReplyTo"]
149
183
  request = @requests[id]
150
- next if request.nil?
151
- request[:received] = true
152
- request[:callback].call(droonga_message)
184
+ n_messages += 1
185
+ if request
186
+ request[:received] = true
187
+ request[:callback].call(droonga_message)
188
+ end
189
+ if @max_messages and
190
+ n_messages >= @max_messages
191
+ unregister(id)
192
+ end
153
193
  end
154
194
  end
155
195
  engine.on_read do |data|
@@ -163,6 +203,10 @@ module Droonga
163
203
  on_close.call
164
204
  end
165
205
  end
206
+
207
+ def on_error(error)
208
+ @on_error.call(error) if @on_error
209
+ end
166
210
  end
167
211
 
168
212
  def initialize(host, port, tag, options={})
@@ -179,6 +223,9 @@ module Droonga
179
223
  @receiver_host = @options[:receiver_host] || Socket.gethostname
180
224
  @receiver_port = @options[:receiver_port] || 0
181
225
  @receiver = Receiver.new(@receiver_host, @receiver_port)
226
+ @receiver.on_error = lambda do |error|
227
+ on_error(ReceiverError.new(error))
228
+ end
182
229
  @receiver.attach(@loop)
183
230
  end
184
231
 
@@ -217,7 +264,14 @@ module Droonga
217
264
  end
218
265
 
219
266
  id = message["id"]
220
- request = InfiniteRequest.new(@loop)
267
+ request_options = {
268
+ :subscription_timeout => options[:subscription_timeout],
269
+ }
270
+ @receiver.max_messages = options[:max_messages]
271
+ request = InfiniteRequest.new(@loop, request_options)
272
+ request.on_timeout = lambda do
273
+ @receiver.unregister(id)
274
+ end
221
275
  sync = block.nil?
222
276
  if sync
223
277
  yielder = nil
@@ -256,6 +310,10 @@ module Droonga
256
310
  @sender.close
257
311
  @receiver.close
258
312
  end
313
+
314
+ def on_error(error)
315
+ @on_error.call(error) if @on_error
316
+ end
259
317
  end
260
318
  end
261
319
  end
@@ -23,6 +23,19 @@ module Droonga
23
23
  module Connection
24
24
  class DroongaProtocol
25
25
  class Thread
26
+ DEFAULT_TIMEOUT_SECONDS = 10
27
+
28
+ attr_writer :on_error
29
+
30
+ class ReceiverError < StandardError
31
+ def initialize(error)
32
+ super(error.inspect)
33
+ end
34
+ end
35
+
36
+ class NilMessage < StandardError
37
+ end
38
+
26
39
  class Request
27
40
  def initialize(thread)
28
41
  @thread = thread
@@ -47,6 +60,9 @@ module Droonga
47
60
 
48
61
  def request(message, options={}, &block)
49
62
  receiver = create_receiver
63
+ receiver.on_error = lambda do |error|
64
+ on_error(ReceiverError.new(error))
65
+ end
50
66
  message = message.dup
51
67
  message["replyTo"] = "#{receiver.host}:#{receiver.port}/droonga"
52
68
  send(message, options)
@@ -78,23 +94,48 @@ module Droonga
78
94
  message["from"] = receive_end_point
79
95
  send(message, options)
80
96
 
97
+ subscription_timeout = options[:subscription_timeout]
98
+ max_messages = options[:max_messages]
99
+ start = Time.now
81
100
  receive_options = {
82
- :timeout => nil,
101
+ :timeout => options[:timeout] || DEFAULT_TIMEOUT_SECONDS,
83
102
  }
103
+ n_messages = 0
84
104
  sync = block.nil?
85
105
  if sync
86
106
  Enumerator.new do |yielder|
87
107
  loop do
88
108
  receiver.receive(receive_options) do |object|
89
109
  yielder << object
110
+ n_messages += 1
111
+ end
112
+ if max_messages and
113
+ n_messages >= max_messages
114
+ break
115
+ end
116
+ if subscription_timeout
117
+ elapsed_seconds = Time.now - start
118
+ break if elapsed_seconds >= subscription_timeout
90
119
  end
91
120
  end
121
+ receiver.close
92
122
  end
93
123
  else
94
124
  thread = ::Thread.new do
95
125
  begin
96
126
  loop do
97
- receiver.receive(receive_options, &block)
127
+ receiver.receive(receive_options) do |message|
128
+ block.call(message)
129
+ n_messages += 1
130
+ end
131
+ if max_messages and
132
+ n_messages >= max_messages
133
+ break
134
+ end
135
+ if subscription_timeout
136
+ elapsed_seconds = Time.now - start
137
+ break if elapsed_seconds >= subscription_timeout
138
+ end
98
139
  end
99
140
  ensure
100
141
  receiver.close
@@ -133,7 +174,13 @@ module Droonga
133
174
  end
134
175
  end
135
176
 
177
+ def on_error(error)
178
+ @on_error.call(error) if @on_error
179
+ end
180
+
136
181
  class Receiver
182
+ attr_writer :on_error
183
+
137
184
  def initialize(options={})
138
185
  host = options[:host] || Socket.gethostname
139
186
  port = options[:port] || 0
@@ -162,7 +209,7 @@ module Droonga
162
209
  timeout = options[:timeout]
163
210
  catch do |tag|
164
211
  loop do
165
- start = Time.new
212
+ start = Time.now
166
213
  readable_ios, = IO.select(@read_ios, nil, nil, timeout)
167
214
  break if readable_ios.nil?
168
215
  if timeout
@@ -198,7 +245,14 @@ module Droonga
198
245
  @client_handlers.delete(client)
199
246
  else
200
247
  unpacker.feed_each(data) do |fluent_message|
248
+ unless fluent_message
249
+ on_error(NilMessage.new("thread / unpacker.feed_each"))
250
+ end
201
251
  tag, time, droonga_message = fluent_message
252
+ unless droonga_message
253
+ on_error(NilMessage.new("thread / unpacker.feed_each",
254
+ :fluent_message => fluent_message))
255
+ end
202
256
  yield(droonga_message)
203
257
  end
204
258
  end
@@ -207,6 +261,10 @@ module Droonga
207
261
  @client_handlers[io].call
208
262
  end
209
263
  end
264
+
265
+ def on_error(error)
266
+ @on_error.call(error) if @on_error
267
+ end
210
268
  end
211
269
  end
212
270
  end
@@ -26,6 +26,8 @@ module Droonga
26
26
  class Client
27
27
  module Connection
28
28
  class HTTP
29
+ attr_writer :on_error
30
+
29
31
  class InvalidHTTPMethodError < Error
30
32
  attr_reader :http_method
31
33
  attr_reader :request_message
@@ -179,6 +181,10 @@ module Droonga
179
181
  "#{base_path}?#{Rack::Utils.build_nested_query(parameters)}"
180
182
  end
181
183
  end
184
+
185
+ def on_error(error)
186
+ @on_error.call(error) if @on_error
187
+ end
182
188
  end
183
189
  end
184
190
  end
@@ -0,0 +1,60 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2015 Droonga Project
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License version 2.1 as published by the Free Software Foundation.
8
+ #
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
+
18
+ require "time"
19
+
20
+ module Droonga
21
+ class Client
22
+ class MessageCompleter
23
+ def initialize(options={})
24
+ @options = options
25
+ @fixed_date = @options[:fixed_date]
26
+ @default_timeout = @options[:default_timeout]
27
+ @default_target_role = @options[:default_target_role]
28
+ end
29
+
30
+ def complete(message)
31
+ id = message["id"] || generate_id
32
+ date = message["date"] || @fixed_date || new_date
33
+ if not have_timeout?(message) and @default_timeout
34
+ message["timeout"] = @default_timeout
35
+ end
36
+ if not message["targetRole"].nil? and @default_target_role
37
+ message["targetRole"] = @default_target_role
38
+ end
39
+ message.merge("id" => id, "date" => date)
40
+ end
41
+
42
+ private
43
+ def generate_id
44
+ Time.now.to_f.to_s
45
+ end
46
+
47
+ MICRO_SECONDS_DECIMAL_PLACE = 6
48
+
49
+ def new_date
50
+ Time.now.utc.iso8601(MICRO_SECONDS_DECIMAL_PLACE)
51
+ end
52
+
53
+ def have_timeout?(message)
54
+ return true if message["timeout"]
55
+ return false unless message["body"].is_a?(Hash)
56
+ not message["body"]["timeout"].nil?
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,52 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2015 Droonga Project
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License version 2.1 as published by the Free Software Foundation.
8
+ #
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
+
18
+ require "time"
19
+
20
+ module Droonga
21
+ class Client
22
+ class MessageValidator
23
+ class MissingDataset < ArgumentError
24
+ end
25
+
26
+ class InvalidDate < ArgumentError
27
+ end
28
+
29
+ def initialize(options={})
30
+ @options = options
31
+ end
32
+
33
+ def validate(message)
34
+ validate_dataset(message)
35
+ validate_date(message)
36
+ end
37
+
38
+ private
39
+ def validate_dataset(message)
40
+ unless message["dataset"]
41
+ raise MissingDataset.new(message)
42
+ end
43
+ end
44
+
45
+ def validate_date(message)
46
+ Time.parse(message["date"])
47
+ rescue ArgumentError
48
+ raise InvalidDate.new(message)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -17,6 +17,6 @@
17
17
 
18
18
  module Droonga
19
19
  class Client
20
- VERSION = "0.2.0"
20
+ VERSION = "0.2.1"
21
21
  end
22
22
  end
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (C) 2015 Droonga Project
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License version 2.1 as published by the Free Software Foundation.
8
+ #
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
+
18
+ require "slop"
19
+
20
+ require "droonga/client"
21
+
22
+ module Droonga
23
+ module Command
24
+ class MissingRequiredParameter < StandardError
25
+ end
26
+
27
+ class NoResponse < StandardError
28
+ end
29
+
30
+ class Base
31
+ private
32
+ def parse_options(&block)
33
+ options = Slop.parse(:help => true) do |option|
34
+ yield(option) if block_given?
35
+
36
+ option.separator("Connections:")
37
+ option.on(:host=,
38
+ "Host name of the engine node.",
39
+ :default => Client::DEFAULT_HOST)
40
+ option.on(:port=,
41
+ "Port number to communicate with the engine.",
42
+ :as => Integer,
43
+ :default => Client::DEFAULT_PORT)
44
+ option.on(:tag=,
45
+ "Tag name to communicate with the engine.",
46
+ :default => Client::DEFAULT_TAG)
47
+ option.on(:dataset=,
48
+ "Dataset name for the sending message.",
49
+ :default => Client::DEFAULT_DATASET)
50
+ option.on("receiver-host=",
51
+ "Host name of this host.",
52
+ :default => Client::DEFAULT_HOST)
53
+ option.on("target-role=",
54
+ "Role of engine nodes which should receive the message.",
55
+ :default => Client::DEFAULT_TARGET_ROLE)
56
+ option.on("timeout=",
57
+ "Time to terminate unresponsive connections (in seconds).",
58
+ :default => Client::DEFAULT_TIMEOUT_SECONDS)
59
+ end
60
+ @options = options
61
+ rescue Slop::MissingOptionError => error
62
+ $stderr.puts(error)
63
+ raise MissingRequiredParameter.new
64
+ end
65
+
66
+ def request(message)
67
+ response = nil
68
+ open do |client|
69
+ response = client.request(message)
70
+ end
71
+ response
72
+ end
73
+
74
+ def send(message)
75
+ open do |client|
76
+ client.send(message)
77
+ end
78
+ end
79
+
80
+ def open(&block)
81
+ options = {
82
+ :host => @options[:host],
83
+ :port => @options[:port],
84
+ :tag => @options[:tag],
85
+ :protocol => :droonga,
86
+ :receiver_host => @options["receiver-host"],
87
+ :receiver_port => 0,
88
+ :default_timeout => @options[:timeout],
89
+ :default_target_role => @options[:target_role],
90
+ }
91
+ Droonga::Client.open(options) do |client|
92
+ yield(client)
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: droonga-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Droonga Project
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-28 00:00:00.000000000 Z
11
+ date: 2015-04-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: msgpack
14
+ name: droonga-message-pack-packer
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - '>='
@@ -39,7 +39,7 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: rack
42
+ name: msgpack
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - '>='
@@ -53,7 +53,7 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: yajl-ruby
56
+ name: rack
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - '>='
@@ -67,7 +67,21 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: droonga-message-pack-packer
70
+ name: slop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - <=
74
+ - !ruby/object:Gem::Version
75
+ version: 3.6.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - <=
81
+ - !ruby/object:Gem::Version
82
+ version: 3.6.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: yajl-ruby
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
87
  - - '>='
@@ -95,7 +109,7 @@ dependencies:
95
109
  - !ruby/object:Gem::Version
96
110
  version: '1.3'
97
111
  - !ruby/object:Gem::Dependency
98
- name: rake
112
+ name: packnga
99
113
  requirement: !ruby/object:Gem::Requirement
100
114
  requirements:
101
115
  - - '>='
@@ -109,7 +123,7 @@ dependencies:
109
123
  - !ruby/object:Gem::Version
110
124
  version: '0'
111
125
  - !ruby/object:Gem::Dependency
112
- name: packnga
126
+ name: rake
113
127
  requirement: !ruby/object:Gem::Requirement
114
128
  requirements:
115
129
  - - '>='
@@ -126,8 +140,11 @@ description: Droonga client for Ruby
126
140
  email:
127
141
  - droonga@groonga.org
128
142
  executables:
143
+ - droonga-add
144
+ - droonga-groonga
129
145
  - droonga-request
130
146
  - droonga-send
147
+ - droonga-system-status
131
148
  extensions: []
132
149
  extra_rdoc_files: []
133
150
  files:
@@ -137,8 +154,11 @@ files:
137
154
  - LICENSE.txt
138
155
  - README.md
139
156
  - Rakefile
157
+ - bin/droonga-add
158
+ - bin/droonga-groonga
140
159
  - bin/droonga-request
141
160
  - bin/droonga-send
161
+ - bin/droonga-system-status
142
162
  - doc/text/news.md
143
163
  - droonga-client.gemspec
144
164
  - lib/droonga/client.rb
@@ -149,8 +169,11 @@ files:
149
169
  - lib/droonga/client/connection/error.rb
150
170
  - lib/droonga/client/connection/http.rb
151
171
  - lib/droonga/client/error.rb
172
+ - lib/droonga/client/message_completer.rb
173
+ - lib/droonga/client/message_validator.rb
152
174
  - lib/droonga/client/rate-limiter.rb
153
175
  - lib/droonga/client/version.rb
176
+ - lib/droonga/command/base.rb
154
177
  homepage: https://github.com/droonga/droonga-client-ruby
155
178
  licenses:
156
179
  - LGPL-2.1