drndump 1.0.0 → 1.0.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: 5f360ce69bbbae294a5d10b32486910970402232
4
- data.tar.gz: b2c1f9925a35b424ce739c5d7086e7c30610a697
3
+ metadata.gz: ffe9d62833a98f43fa9911c82c56429d74af6205
4
+ data.tar.gz: 62d440b218bc4d963d3c91a7a426f6e6bd3940fa
5
5
  SHA512:
6
- metadata.gz: 96dfb02da9769cf3be082156bc27759891b8d2e68159ff041604159cd86be170fd6b59819ae494c2f0066b843a17fe4fe98ce015e61c1cc4da36943e38df2954
7
- data.tar.gz: 34fef0d416a78cdc52861b209bef9ecb0aac8e329e856dc770e9e91a6bd5d5418573d82bcfecb998293dc0b5d918367d62763e8e6d23f0744ff5f0fd8af01b97
6
+ metadata.gz: 2c32f075bcf25f1104d7331aed5f9cc55545da025ced1b0ea6ce11c102f0b6a10de2afb9fc5c160c9cbcf4c5a533595611df42d9c61666c39fc1c4ee1731d98e
7
+ data.tar.gz: 1ea16dde38b2f88bea29c058fd2d8f54cf0e90abe255597c622f3b5455de02ade8f734b50166053569bd6b9584e7586e39a37e0d40f87dc786379a525d9c2ec9
@@ -1,5 +1,12 @@
1
1
  # News
2
2
 
3
+ ## 1.0.1: 2014-04-29
4
+
5
+ * Becomes modulable. You can use internal libraries for other products.
6
+ * Supports new `@subscribe-until` directive, for subscription type commands like `dump`.
7
+ You can unsubscribe the next request following to the directive automatically with given timeout, like:
8
+ `#@subscribe-until 10s`
9
+
3
10
  ## 1.0.0: 2014-05-29
4
11
 
5
12
  The first release!!!
@@ -19,9 +19,8 @@ require "json"
19
19
 
20
20
  require "cool.io"
21
21
 
22
- require "droonga/client"
23
-
24
22
  require "drndump/version"
23
+ require "drndump/dump_client"
25
24
 
26
25
  module Drndump
27
26
  class Command
@@ -106,138 +105,28 @@ module Drndump
106
105
  parser
107
106
  end
108
107
 
109
- def client_options
108
+ def dumper_params
110
109
  {
111
110
  :host => @host,
112
111
  :port => @port,
113
112
  :tag => @tag,
114
- :protocol => :droonga,
113
+ :dataset => @dataset,
115
114
  :receiver_host => @receiver_host,
116
115
  :receiver_port => @receiver_port,
117
- :backend => :coolio,
118
- :loop => @loop,
119
116
  }
120
117
  end
121
118
 
122
119
  def dump
123
- client = Droonga::Client.new(client_options)
124
-
125
- error_message = nil
126
- n_dumpers = 0
127
-
128
- dump_message = {
129
- "type" => "dump",
130
- "dataset" => @dataset,
120
+ @dumper = DumpClient.new(dumper_params)
121
+ dump_options = {
122
+ :backend => :coolio,
123
+ :loop => @loop,
131
124
  }
132
- client.subscribe(dump_message) do |message|
133
- case message
134
- when Droonga::Client::Error
135
- client.close
136
- error_message = message.to_s
137
- else
138
- case message["type"]
139
- when "dump.result", "dump.error"
140
- if message["statusCode"] != 200
141
- client.close
142
- error = message["body"]
143
- error_message = "#{error['name']}: #{error['message']}"
144
- end
145
- when "dump.table"
146
- table_create_message = convert_to_table_create_message(message)
147
- puts(JSON.pretty_generate(table_create_message))
148
- when "dump.column"
149
- column_create_message = convert_to_column_create_message(message)
150
- puts(JSON.pretty_generate(column_create_message))
151
- when "dump.record"
152
- add_message = message.dup
153
- add_message.delete("inReplyTo")
154
- add_message["type"] = "add"
155
- puts(JSON.pretty_generate(add_message))
156
- when "dump.start"
157
- n_dumpers += 1
158
- when "dump.end"
159
- n_dumpers -= 1
160
- client.close if n_dumpers <= 0
161
- end
162
- end
125
+ @dumper.run(dump_options) do |message|
126
+ puts(JSON.pretty_generate(message))
163
127
  end
164
128
  @loop.run
165
-
166
- error_message
167
- end
168
-
169
- def convert_to_table_create_message(message)
170
- body = message["body"]
171
- flags = []
172
- case body["type"]
173
- when "Array"
174
- flags << "TABLE_NO_KEY"
175
- when "Hash"
176
- flags << "TABLE_HASH_KEY"
177
- when "PatriciaTrie"
178
- flags << "TABLE_PAT_KEY"
179
- when "DoubleArrayTrie"
180
- flags << "TABLE_DAT_KEY"
181
- end
182
- table_create_message = {
183
- "type" => "table_create",
184
- "dataset" => message["dataset"],
185
- "body" => {
186
- "name" => body["name"],
187
- "flags" => flags.join("|"),
188
- "key_type" => body["keyType"],
189
- }
190
- }
191
-
192
- if body["tokenizer"]
193
- table_create_message["body"]["default_tokenizer"] = body["tokenizer"]
194
- end
195
- if body["normalizer"]
196
- table_create_message["body"]["normalizer"] = body["normalizer"]
197
- end
198
-
199
- table_create_message
200
- end
201
-
202
- def convert_to_column_create_message(message)
203
- body = message["body"]
204
- column_create_message = {
205
- "type" => "column_create",
206
- "dataset" => message["dataset"],
207
- "body" => {
208
- "table" => body["table"],
209
- "name" => body["name"],
210
- "type" => body["valueType"],
211
- }
212
- }
213
-
214
- flags = []
215
- case body["type"]
216
- when "Scalar"
217
- flags << "COLUMN_SCALAR"
218
- when "Vector"
219
- flags << "COLUMN_VECTOR"
220
- vector_options = body["vectorOptions"] || {}
221
- flags << "WITH_WEIGHT" if vector_options["weight"]
222
- when "Index"
223
- flags << "COLUMN_INDEX"
224
- index_options = body["indexOptions"] || {}
225
- flags << "WITH_SECTION" if index_options["section"]
226
- flags << "WITH_WEIGHT" if index_options["weight"]
227
- flags << "WITH_POSITION" if index_options["position"]
228
- end
229
-
230
- column_create_message["body"]["flags"] = flags.join("|")
231
-
232
- if body["type"] == "Index"
233
- index_options = body["indexOptions"] || {}
234
- sources = index_options["sources"] || []
235
- unless sources.empty?
236
- column_create_message["body"]["source"] = sources.join(",")
237
- end
238
- end
239
-
240
- column_create_message
129
+ @dumper.error_message
241
130
  end
242
131
  end
243
132
  end
@@ -0,0 +1,298 @@
1
+ # Copyright (C) 2014-2015 Droonga Project
2
+ #
3
+ # This library is free software; you can redistribute it and/or
4
+ # modify it under the terms of the GNU Lesser General Public
5
+ # License version 2.1 as published by the Free Software Foundation.
6
+ #
7
+ # This library is distributed in the hope that it will be useful,
8
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10
+ # Lesser General Public License for more details.
11
+ #
12
+ # You should have received a copy of the GNU Lesser General Public
13
+ # License along with this library; if not, write to the Free Software
14
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
+
16
+ require "socket"
17
+
18
+ require "droonga/client"
19
+
20
+ module Drndump
21
+ class DumpClient
22
+ DEFAULT_MESSAGES_PER_SECOND = 10000 # same to droogna-engine's one
23
+ MIN_REPORTED_THROUGHPUT = 0.01
24
+
25
+ attr_reader :n_forecasted_messages, :n_received_messages
26
+ attr_reader :error_message
27
+ attr_writer :on_finish, :on_progress, :on_error
28
+
29
+ class NilMessage < StandardError
30
+ end
31
+
32
+ class InvalidMessage < StandardError
33
+ end
34
+
35
+ class ClientError < StandardError
36
+ def initialize(error)
37
+ super(error.inspect)
38
+ end
39
+ end
40
+
41
+ def initialize(params)
42
+ @host = params[:host]
43
+ @port = params[:port]
44
+ @tag = params[:tag]
45
+ @dataset = params[:dataset]
46
+
47
+ @receiver_host = params[:receiver_host]
48
+ @receiver_port = params[:receiver_port]
49
+
50
+ @n_forecasted_messages = 0
51
+ @n_received_messages = 0
52
+ @n_messages_per_second = DEFAULT_MESSAGES_PER_SECOND
53
+
54
+ @error_message = nil
55
+
56
+ @on_finish = nil
57
+ @on_progress = nil
58
+ @on_error = nil
59
+ end
60
+
61
+ def run(options={}, &block)
62
+ extra_client_options = {
63
+ :backend => options[:backend],
64
+ :loop => options[:loop],
65
+ }
66
+ @client = Droonga::Client.new(client_options.merge(extra_client_options))
67
+ @client.on_error = lambda do |error|
68
+ on_error(ClientError.new(error))
69
+ end
70
+
71
+ @n_dumpers = 0
72
+
73
+ @n_messages_per_second = options[:messages_per_second] || DEFAULT_MESSAGES_PER_SECOND
74
+ @n_messages_per_second = [@n_messages_per_second, 1].max
75
+
76
+ @measure_start_time = Time.now
77
+ @previous_measure_time = @measure_start_time
78
+ @previous_n_received_messages = 0.0
79
+
80
+ dump_message = {
81
+ "type" => "dump",
82
+ "dataset" => @dataset,
83
+ "body" => {
84
+ "messagesPerSecond" => @n_messages_per_second,
85
+ },
86
+ }
87
+ @client.subscribe(dump_message) do |message|
88
+ begin
89
+ on_progress(message)
90
+ case message
91
+ when Droonga::Client::Error
92
+ client.close
93
+ on_error(message)
94
+ @error_message = message.to_s
95
+ when Hash
96
+ handle_dump_message(message) do |restore_message|
97
+ yield(restore_message)
98
+ end
99
+ when NilClass
100
+ raise NilMessage.new("nil message in dump")
101
+ else
102
+ raise InvalidMessage.new("invalid message in dump",
103
+ :message => message.inspect)
104
+ end
105
+ rescue Exception => exception
106
+ @client.close
107
+ on_error(exception)
108
+ @error_message = exception.to_s
109
+ end
110
+ end
111
+
112
+ @error_message
113
+ end
114
+
115
+ def recent_throughput
116
+ now = Time.now
117
+ n_messages = @n_received_messages - @previous_n_received_messages
118
+
119
+ if now - @previous_measure_time < 1
120
+ now = @previous_measure_time
121
+ n_messages = @previous_n_received_messages
122
+ else
123
+ @previous_measure_time = now
124
+ @previous_n_received_messages = n_messages.to_f
125
+ end
126
+
127
+ if now == @measure_start_time
128
+ actual_throughput = 0
129
+ else
130
+ elapsed_seconds = now - @measure_start_time
131
+ actual_throughput = n_messages / elapsed_seconds
132
+ end
133
+
134
+ [actual_throughput, MIN_REPORTED_THROUGHPUT].max
135
+ end
136
+
137
+ def n_remaining_messages
138
+ [@n_forecasted_messages - @n_received_messages, 0].max
139
+ end
140
+
141
+ def remaining_seconds
142
+ throughput = [recent_throughput, @n_messages_per_second].min
143
+ n_remaining_messages.to_f / throughput
144
+ end
145
+
146
+ ONE_MINUTE_IN_SECONDS = 60
147
+ ONE_HOUR_IN_SECONDS = ONE_MINUTE_IN_SECONDS * 60
148
+
149
+ def formatted_remaining_time
150
+ seconds = remaining_seconds
151
+ hours = (seconds / ONE_HOUR_IN_SECONDS).floor
152
+ seconds -= hours * ONE_HOUR_IN_SECONDS
153
+ minutes = (seconds / ONE_MINUTE_IN_SECONDS).floor
154
+ seconds -= minutes * ONE_MINUTE_IN_SECONDS
155
+ sprintf("%02i:%02i:%02i", hours, minutes, seconds)
156
+ end
157
+
158
+ def progress_percentage
159
+ return 0 if @n_forecasted_messages.zero?
160
+ progress = @n_received_messages.to_f / @n_forecasted_messages
161
+ [(progress * 100).to_i, 100].min
162
+ end
163
+
164
+ private
165
+ def client_options
166
+ {
167
+ :host => @host,
168
+ :port => @port,
169
+ :tag => @tag,
170
+ :protocol => :droonga,
171
+ :receiver_host => @receiver_host,
172
+ :receiver_port => @receiver_port,
173
+ }
174
+ end
175
+
176
+ def handle_dump_message(message, &block)
177
+ case message["type"]
178
+ when "dump.result", "dump.error"
179
+ if message["statusCode"] != 200
180
+ @client.close
181
+ error = message["body"]
182
+ on_error(error)
183
+ @error_message = "#{error['name']}: #{error['message']}"
184
+ end
185
+ when "dump.table"
186
+ @n_received_messages += 1
187
+ table_create_message = convert_to_table_create_message(message)
188
+ yield(table_create_message)
189
+ when "dump.column"
190
+ @n_received_messages += 1
191
+ column_create_message = convert_to_column_create_message(message)
192
+ yield(column_create_message)
193
+ when "dump.record"
194
+ @n_received_messages += 1
195
+ add_message = message.dup
196
+ add_message.delete("inReplyTo")
197
+ add_message["type"] = "add"
198
+ yield(add_message)
199
+ when "dump.start"
200
+ @n_dumpers += 1
201
+ when "dump.end"
202
+ @n_dumpers -= 1
203
+ if @n_dumpers <= 0
204
+ @client.close
205
+ on_finish
206
+ end
207
+ when "dump.forecast"
208
+ @n_forecasted_messages += message["body"]["nMessages"]
209
+ end
210
+ end
211
+
212
+ def convert_to_table_create_message(message)
213
+ body = message["body"]
214
+ flags = []
215
+ case body["type"]
216
+ when "Array"
217
+ flags << "TABLE_NO_KEY"
218
+ when "Hash"
219
+ flags << "TABLE_HASH_KEY"
220
+ when "PatriciaTrie"
221
+ flags << "TABLE_PAT_KEY"
222
+ when "DoubleArrayTrie"
223
+ flags << "TABLE_DAT_KEY"
224
+ end
225
+ table_create_message = {
226
+ "type" => "table_create",
227
+ "dataset" => message["dataset"],
228
+ "body" => {
229
+ "name" => body["name"],
230
+ "flags" => flags.join("|"),
231
+ "key_type" => body["keyType"],
232
+ }
233
+ }
234
+
235
+ if body["tokenizer"]
236
+ table_create_message["body"]["default_tokenizer"] = body["tokenizer"]
237
+ end
238
+ if body["normalizer"]
239
+ table_create_message["body"]["normalizer"] = body["normalizer"]
240
+ end
241
+
242
+ table_create_message
243
+ end
244
+
245
+ def convert_to_column_create_message(message)
246
+ body = message["body"]
247
+ column_create_message = {
248
+ "type" => "column_create",
249
+ "dataset" => message["dataset"],
250
+ "body" => {
251
+ "table" => body["table"],
252
+ "name" => body["name"],
253
+ "type" => body["valueType"],
254
+ }
255
+ }
256
+
257
+ flags = []
258
+ case body["type"]
259
+ when "Scalar"
260
+ flags << "COLUMN_SCALAR"
261
+ when "Vector"
262
+ flags << "COLUMN_VECTOR"
263
+ vector_options = body["vectorOptions"] || {}
264
+ flags << "WITH_WEIGHT" if vector_options["weight"]
265
+ when "Index"
266
+ flags << "COLUMN_INDEX"
267
+ index_options = body["indexOptions"] || {}
268
+ flags << "WITH_SECTION" if index_options["section"]
269
+ flags << "WITH_WEIGHT" if index_options["weight"]
270
+ flags << "WITH_POSITION" if index_options["position"]
271
+ end
272
+
273
+ column_create_message["body"]["flags"] = flags.join("|")
274
+
275
+ if body["type"] == "Index"
276
+ index_options = body["indexOptions"] || {}
277
+ sources = index_options["sources"] || []
278
+ unless sources.empty?
279
+ column_create_message["body"]["source"] = sources.join(",")
280
+ end
281
+ end
282
+
283
+ column_create_message
284
+ end
285
+
286
+ def on_finish
287
+ @on_finish.call if @on_finish
288
+ end
289
+
290
+ def on_progress(message)
291
+ @on_progress.call(message) if @on_progress
292
+ end
293
+
294
+ def on_error(error)
295
+ @on_error.call(error) if @on_error
296
+ end
297
+ end
298
+ end
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2014 Droonga Project
1
+ # Copyright (C) 2014-2015 Droonga Project
2
2
  #
3
3
  # This program is free software: you can redistribute it and/or modify
4
4
  # it under the terms of the GNU General Public License as published by
@@ -14,5 +14,5 @@
14
14
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
15
 
16
16
  module Drndump
17
- VERSION = "1.0.0"
17
+ VERSION = "1.0.1"
18
18
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: drndump
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.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-05-29 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
14
  name: json
@@ -116,14 +116,15 @@ executables:
116
116
  extensions: []
117
117
  extra_rdoc_files: []
118
118
  files:
119
+ - Gemfile
119
120
  - README.md
120
121
  - Rakefile
121
- - Gemfile
122
+ - bin/drndump
123
+ - doc/text/news.md
122
124
  - drndump.gemspec
123
- - lib/drndump/version.rb
124
125
  - lib/drndump/command.rb
125
- - doc/text/news.md
126
- - bin/drndump
126
+ - lib/drndump/dump_client.rb
127
+ - lib/drndump/version.rb
127
128
  homepage: https://github.com/droonga/drndump
128
129
  licenses:
129
130
  - GPLv3 or later
@@ -144,7 +145,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
144
145
  version: '0'
145
146
  requirements: []
146
147
  rubyforge_project:
147
- rubygems_version: 2.0.14
148
+ rubygems_version: 2.4.1
148
149
  signing_key:
149
150
  specification_version: 4
150
151
  summary: Drndump is a tool to dump Droonga database. You can use it for backup.