droonga-client 0.2.0 → 0.2.1

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.
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