cgminer_api_client 0.2.6

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.
@@ -0,0 +1,201 @@
1
+ module CgminerApiClient
2
+ class Miner
3
+ module Commands
4
+ module ReadOnly
5
+ def asc(number)
6
+ query(:asc, number)
7
+ end
8
+
9
+ def asccount
10
+ query(:asccount)
11
+ end
12
+
13
+ def check(command)
14
+ query(:check, command)[0]
15
+ end
16
+
17
+ def coin
18
+ query(:coin)[0]
19
+ end
20
+
21
+ def config
22
+ query(:config)[0]
23
+ end
24
+
25
+ def devdetails
26
+ query(:devdetails)
27
+ end
28
+
29
+ def devs
30
+ query(:devs)
31
+ end
32
+
33
+ def pga(number)
34
+ query(:pga, number)
35
+ end
36
+
37
+ def pgacount
38
+ query(:pgacount)
39
+ end
40
+
41
+ def pools
42
+ query(:pools)
43
+ end
44
+
45
+ def privileged
46
+ query(:privileged)
47
+ true
48
+ rescue
49
+ false
50
+
51
+ end
52
+
53
+ def notify
54
+ query(:notify)
55
+ end
56
+
57
+ def stats
58
+ query(:stats)
59
+ end
60
+
61
+ def summary
62
+ query(:summary)[0]
63
+ end
64
+
65
+ def usbstats
66
+ query(:usbstats)
67
+ end
68
+
69
+ def version
70
+ query(:version)[0]
71
+ end
72
+ end
73
+
74
+ module Privileged
75
+ module Asc
76
+ def ascdisable(number)
77
+ query(:ascdisable, number) unless access_denied?
78
+ end
79
+
80
+ def ascenable(number)
81
+ query(:ascenable, number) unless access_denied?
82
+ end
83
+
84
+ def ascidentify(number)
85
+ query(:ascidentify, number) unless access_denied?
86
+ end
87
+
88
+ def ascset(number, option, value = nil)
89
+ unless access_denied?
90
+ value ? query(:ascset, number, option, value) : query(:ascset, number, option)
91
+ end
92
+ end
93
+ end
94
+
95
+ module Pga
96
+ def pgadisable(number)
97
+ query(:pgadisable, number) unless access_denied?
98
+ end
99
+
100
+ def pgaenable(number)
101
+ query(:pgaenable, number) unless access_denied?
102
+ end
103
+
104
+ def pgaidentify(number)
105
+ query(:pgaidentify, number) unless access_denied?
106
+ end
107
+
108
+ def pgaset(number, option, value = nil)
109
+ unless access_denied?
110
+ value ? query(:pgaset, number, option, value) : query(:pgaset, number, option)
111
+ end
112
+ end
113
+ end
114
+
115
+ module Pool
116
+ def addpool(url, user, pass)
117
+ query(:addpool, url, user, pass) unless access_denied?
118
+ end
119
+
120
+ def disablepool(number)
121
+ query(:disablepool, number) unless access_denied?
122
+ end
123
+
124
+ def enablepool(number)
125
+ query(:enablepool, number) unless access_denied?
126
+ end
127
+
128
+ def poolpriority(*id_order)
129
+ query(:poolpriority, *id_order) unless access_denied?
130
+ end
131
+
132
+ def poolquota(number, value)
133
+ query(:poolquota, number, value) unless access_denied?
134
+ end
135
+
136
+ def removepool(number)
137
+ query(:removepool, number) unless access_denied?
138
+ end
139
+
140
+ def switchpool(number)
141
+ query(:switchpool, number) unless access_denied?
142
+ end
143
+ end
144
+
145
+ module System
146
+ def debug(setting = 'D')
147
+ query(:debug, setting) unless access_denied?
148
+ end
149
+
150
+ def failover_only(value)
151
+ query(:'failover-only', value) unless access_denied?
152
+ end
153
+
154
+ def hotplug(seconds)
155
+ query(:hotplug, seconds) unless access_denied?
156
+ end
157
+
158
+ def quit
159
+ query(:quit) unless access_denied?
160
+ end
161
+
162
+ def restart
163
+ query(:restart) unless access_denied?
164
+ end
165
+
166
+ def save(filename = nil)
167
+ unless access_denied?
168
+ filename ? query(:save, filename) : query(:save)
169
+ end
170
+ end
171
+
172
+ def setconfig(name, value)
173
+ query(:setconfig, name, value) unless access_denied?
174
+ end
175
+
176
+ def zero(which = 'All', full_summary = false)
177
+ query(:zero, which, full_summary) unless access_denied?
178
+ end
179
+ end
180
+
181
+ private
182
+
183
+ def access_denied?
184
+ if !privileged
185
+ raise 'access_denied'
186
+ else
187
+ return false
188
+ end
189
+ end
190
+
191
+ include Miner::Commands::Privileged::Asc
192
+ include Miner::Commands::Privileged::Pga
193
+ include Miner::Commands::Privileged::Pool
194
+ include Miner::Commands::Privileged::System
195
+ end
196
+
197
+ include Miner::Commands::ReadOnly
198
+ include Miner::Commands::Privileged
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,68 @@
1
+ module CgminerApiClient
2
+ class MinerPool
3
+ include Miner::Commands
4
+
5
+ attr_accessor :miners
6
+
7
+ def initialize
8
+ load_miners!
9
+ end
10
+
11
+ def reload_miners!
12
+ @miners = nil
13
+ load_miners!
14
+ end
15
+
16
+ def query(method, *params)
17
+ threads = @miners.collect do |miner|
18
+ Thread.new do
19
+ begin
20
+ miner.query(method, *params)
21
+ rescue => e
22
+ $stderr.puts "#{e.class}: #{e}"
23
+ []
24
+ end
25
+ end
26
+ end
27
+ threads.each { |thr| thr.join }
28
+ threads.collect(&:value)
29
+ end
30
+
31
+ def available_miners(force_reload = false)
32
+ threads = @miners.collect do |miner|
33
+ Thread.new do
34
+ begin
35
+ miner if miner.available?(force_reload)
36
+ rescue
37
+ nil
38
+ end
39
+ end
40
+ end
41
+ threads.each { |thr| thr.join }
42
+ threads.collect(&:value).compact
43
+ end
44
+
45
+ def unavailable_miners(force_reload = false)
46
+ @miners - available_miners(force_reload)
47
+ end
48
+
49
+ def method_missing(name, *args)
50
+ query(name, *args)
51
+ end
52
+
53
+ private
54
+
55
+ def load_miners!
56
+ raise 'Please create config/miners.yml' unless File.exist?('config/miners.yml')
57
+
58
+ miners_config = YAML.load_file('config/miners.yml')
59
+ @miners = miners_config.collect{|miner|
60
+ CgminerApiClient::Miner.new(
61
+ miner['host'],
62
+ miner['port'],
63
+ miner['timeout']
64
+ )
65
+ }
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,30 @@
1
+ module CgminerApiClient
2
+ module SocketWithTimeout
3
+ def open_socket(host, port, timeout)
4
+ addr = Socket.getaddrinfo(host, nil)
5
+ sockaddr = Socket.pack_sockaddr_in(port, addr[0][3])
6
+
7
+ Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0).tap do |socket|
8
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
9
+
10
+ begin
11
+ socket.connect_nonblock(sockaddr)
12
+ rescue IO::WaitWritable
13
+ if IO.select(nil, [socket], nil, timeout)
14
+ begin
15
+ socket.connect_nonblock(sockaddr)
16
+ rescue Errno::EISCONN
17
+ # the socket is connected
18
+ rescue
19
+ socket.close
20
+ raise
21
+ end
22
+ else
23
+ socket.close
24
+ raise "Connection timeout"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ module CgminerApiClient
2
+ VERSION = "0.2.6"
3
+ end
@@ -0,0 +1,501 @@
1
+ require 'spec_helper'
2
+
3
+ describe CgminerApiClient::Miner::Commands do
4
+ let(:host) { '127.0.0.1' }
5
+ let(:port) { 4028 }
6
+ let(:instance) { CgminerApiClient::Miner.new(host, port) }
7
+
8
+ describe CgminerApiClient::Miner::Commands::ReadOnly do
9
+ context '#asc' do
10
+ it 'should require one argument' do
11
+ expect {
12
+ instance.asc
13
+ }.to raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 1)')
14
+ end
15
+
16
+ it 'should query the miner' do
17
+ expect(instance).to receive(:query).with(:asc, 0).and_return('asc' => {})
18
+ instance.asc(0)
19
+ end
20
+ end
21
+
22
+ context '#asccount' do
23
+ it 'should query the miner' do
24
+ expect(instance).to receive(:query).with(:asccount).and_return('asccount' => {})
25
+ instance.asccount
26
+ end
27
+ end
28
+
29
+ context '#check' do
30
+ it 'should require one argument' do
31
+ expect {
32
+ instance.check
33
+ }.to raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 1)')
34
+ end
35
+
36
+ it 'should query the miner' do
37
+ expect(instance).to receive(:query).with(:check, :foo).and_return('foo' => [{}])
38
+ instance.check(:foo)
39
+ end
40
+ end
41
+
42
+ context '#coin' do
43
+ it 'should query the miner' do
44
+ expect(instance).to receive(:query).with(:coin).and_return('coin' => [{}])
45
+ instance.coin
46
+ end
47
+ end
48
+
49
+ context '#config' do
50
+ it 'should query the miner' do
51
+ expect(instance).to receive(:query).with(:config).and_return('config' => [{}])
52
+ instance.config
53
+ end
54
+ end
55
+
56
+ context '#devdetails' do
57
+ it 'should query the miner' do
58
+ expect(instance).to receive(:query).with(:devdetails).and_return('devdetails' => [{}])
59
+ instance.devdetails
60
+ end
61
+ end
62
+
63
+ context '#devs' do
64
+ it 'should query the miner' do
65
+ expect(instance).to receive(:query).with(:devs).and_return('devs' => [{}])
66
+ instance.devs
67
+ end
68
+ end
69
+
70
+ context '#pga' do
71
+ it 'should require one argument' do
72
+ expect {
73
+ instance.pga
74
+ }.to raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 1)')
75
+ end
76
+
77
+ it 'should query the miner' do
78
+ expect(instance).to receive(:query).with(:pga, 0).and_return('pga' => [{}])
79
+ instance.pga(0)
80
+ end
81
+ end
82
+
83
+ context '#pgacount' do
84
+ it 'should query the miner' do
85
+ expect(instance).to receive(:query).with(:pgacount).and_return('pgacount' => [{}])
86
+ instance.pgacount
87
+ end
88
+ end
89
+
90
+ context '#pools' do
91
+ it 'should query the miner' do
92
+ expect(instance).to receive(:query).with(:pools).and_return('pools' => [{}])
93
+ instance.pools
94
+ end
95
+ end
96
+
97
+ context '#privileged' do
98
+ it 'should query the miner' do
99
+ expect(instance).to receive(:query).with(:privileged).and_return(nil)
100
+ instance.privileged
101
+ end
102
+
103
+ context 'an exception' do
104
+ before do
105
+ expect(instance).to receive(:query).with(:privileged).and_raise('something')
106
+ end
107
+
108
+ it 'should return false' do
109
+ expect(instance.privileged).to eq false
110
+ end
111
+ end
112
+
113
+ context 'success' do
114
+ before do
115
+ expect(instance).to receive(:query).with(:privileged).and_return(nil)
116
+ end
117
+
118
+ it 'should return true' do
119
+ expect(instance.privileged).to eq true
120
+ end
121
+ end
122
+ end
123
+
124
+ context '#notify' do
125
+ it 'should query the miner' do
126
+ expect(instance).to receive(:query).with(:notify).and_return('notify' => [{}])
127
+ instance.notify
128
+ end
129
+ end
130
+
131
+ context '#stats' do
132
+ it 'should query the miner' do
133
+ expect(instance).to receive(:query).with(:stats).and_return('stats' => [{}])
134
+ instance.stats
135
+ end
136
+ end
137
+
138
+ context '#summary' do
139
+ it 'should query the miner' do
140
+ expect(instance).to receive(:query).with(:summary).and_return('summary' => [{}])
141
+ instance.summary
142
+ end
143
+ end
144
+
145
+ context '#usbstats' do
146
+ it 'should query the miner' do
147
+ expect(instance).to receive(:query).with(:usbstats).and_return('usbstats' => [{}])
148
+ instance.usbstats
149
+ end
150
+ end
151
+
152
+ context '#version' do
153
+ it 'should query the miner' do
154
+ expect(instance).to receive(:query).with(:version).and_return('version' => [{}])
155
+ instance.version
156
+ end
157
+ end
158
+ end
159
+
160
+ describe CgminerApiClient::Miner::Commands::Privileged do
161
+ context 'access_denied?' do
162
+ context 'when not privileged' do
163
+ before do
164
+ allow(instance).to receive(:privileged).and_return(false)
165
+ end
166
+
167
+ it 'should raise an error' do
168
+ expect {
169
+ instance.send(:access_denied?)
170
+ }.to raise_error('access_denied')
171
+ end
172
+ end
173
+
174
+ context 'when privileged' do
175
+ before do
176
+ allow(instance).to receive(:privileged).and_return(true)
177
+ end
178
+
179
+ it 'should return false' do
180
+ expect(instance.send(:access_denied?)).to eq false
181
+ end
182
+ end
183
+ end
184
+
185
+ describe CgminerApiClient::Miner::Commands::Privileged::Asc do
186
+ before do
187
+ allow(instance).to receive(:access_denied?).and_return(false)
188
+ end
189
+
190
+ context 'ascdisable' do
191
+ it 'should require one argument' do
192
+ expect {
193
+ instance.ascdisable
194
+ }.to raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 1)')
195
+ end
196
+
197
+ it 'should query the miner with arguments' do
198
+ expect(instance).to receive(:query).with(:ascdisable, :number)
199
+ instance.ascdisable(:number)
200
+ end
201
+ end
202
+
203
+ context 'ascenable' do
204
+ it 'should require one argument' do
205
+ expect {
206
+ instance.ascenable
207
+ }.to raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 1)')
208
+ end
209
+
210
+ it 'should query the miner with arguments' do
211
+ expect(instance).to receive(:query).with(:ascenable, :number)
212
+ instance.ascenable(:number)
213
+ end
214
+ end
215
+
216
+ context 'ascidentify' do
217
+ it 'should require one argument' do
218
+ expect {
219
+ instance.ascidentify
220
+ }.to raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 1)')
221
+ end
222
+
223
+ it 'should query the miner with arguments' do
224
+ expect(instance).to receive(:query).with(:ascidentify, :number)
225
+ instance.ascidentify(:number)
226
+ end
227
+ end
228
+
229
+ context 'ascset' do
230
+ it 'should require 2-3 arguments' do
231
+ expect {
232
+ instance.ascset
233
+ }.to raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 2..3)')
234
+ end
235
+
236
+ it 'should query the miner with 2 arguments' do
237
+ expect(instance).to receive(:query).with(:ascset, :number, :foo)
238
+ instance.ascset(:number, :foo)
239
+ end
240
+
241
+ it 'should query the miner with 3 arguments' do
242
+ expect(instance).to receive(:query).with(:ascset, :number, :foo, :bar)
243
+ instance.ascset(:number, :foo, :bar)
244
+ end
245
+ end
246
+ end
247
+
248
+ describe CgminerApiClient::Miner::Commands::Privileged::Pga do
249
+ before do
250
+ allow(instance).to receive(:access_denied?).and_return(false)
251
+ end
252
+
253
+ context 'pgadisable' do
254
+ it 'should require one argument' do
255
+ expect {
256
+ instance.pgadisable
257
+ }.to raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 1)')
258
+ end
259
+
260
+ it 'should query the miner with arguments' do
261
+ expect(instance).to receive(:query).with(:pgadisable, :number)
262
+ instance.pgadisable(:number)
263
+ end
264
+ end
265
+
266
+ context 'pgaenable' do
267
+ it 'should require one argument' do
268
+ expect {
269
+ instance.pgaenable
270
+ }.to raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 1)')
271
+ end
272
+
273
+ it 'should query the miner with arguments' do
274
+ expect(instance).to receive(:query).with(:pgaenable, :number)
275
+ instance.pgaenable(:number)
276
+ end
277
+ end
278
+
279
+ context 'pgaidentify' do
280
+ it 'should require one argument' do
281
+ expect {
282
+ instance.pgaidentify
283
+ }.to raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 1)')
284
+ end
285
+
286
+ it 'should query the miner with arguments' do
287
+ expect(instance).to receive(:query).with(:pgaidentify, :number)
288
+ instance.pgaidentify(:number)
289
+ end
290
+ end
291
+
292
+ context 'pgaset' do
293
+ it 'should require 2-3 arguments' do
294
+ expect {
295
+ instance.pgaset
296
+ }.to raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 2..3)')
297
+ end
298
+
299
+ it 'should query the miner with 2 arguments' do
300
+ expect(instance).to receive(:query).with(:pgaset, :number, :foo)
301
+ instance.pgaset(:number, :foo)
302
+ end
303
+
304
+ it 'should query the miner with 3 arguments' do
305
+ expect(instance).to receive(:query).with(:pgaset, :number, :foo, :bar)
306
+ instance.pgaset(:number, :foo, :bar)
307
+ end
308
+ end
309
+ end
310
+
311
+ describe CgminerApiClient::Miner::Commands::Privileged::Pool do
312
+ before do
313
+ allow(instance).to receive(:access_denied?).and_return(false)
314
+ end
315
+
316
+ context 'addpool' do
317
+ it 'should require three arguments' do
318
+ expect {
319
+ instance.addpool
320
+ }.to raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 3)')
321
+ end
322
+
323
+ it 'should query the miner with arguments' do
324
+ expect(instance).to receive(:query).with(:addpool, :url, :user, :pass)
325
+ instance.addpool(:url, :user, :pass)
326
+ end
327
+ end
328
+
329
+ context 'disablepool' do
330
+ it 'should require one argument' do
331
+ expect {
332
+ instance.disablepool
333
+ }.to raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 1)')
334
+ end
335
+
336
+ it 'should query the miner with arguments' do
337
+ expect(instance).to receive(:query).with(:disablepool, :pool_number)
338
+ instance.disablepool(:pool_number)
339
+ end
340
+ end
341
+
342
+ context 'enablepool' do
343
+ it 'should require one argument' do
344
+ expect {
345
+ instance.enablepool
346
+ }.to raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 1)')
347
+ end
348
+
349
+ it 'should query the miner with arguments' do
350
+ expect(instance).to receive(:query).with(:enablepool, :pool_number)
351
+ instance.enablepool(:pool_number)
352
+ end
353
+ end
354
+
355
+ context 'poolpriority' do
356
+ it 'should query the miner with arguments' do
357
+ expect(instance).to receive(:query).with(:poolpriority, :pool_number_1, :pool_number_2, :pool_number_3)
358
+ instance.poolpriority(:pool_number_1, :pool_number_2, :pool_number_3)
359
+ end
360
+ end
361
+
362
+ context 'poolquota' do
363
+ it 'should require two arguments' do
364
+ expect {
365
+ instance.poolquota
366
+ }.to raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 2)')
367
+ end
368
+
369
+ it 'should query the miner with arguments' do
370
+ expect(instance).to receive(:query).with(:poolquota, :pool_number, :quota)
371
+ instance.poolquota(:pool_number, :quota)
372
+ end
373
+ end
374
+
375
+ context 'removepool' do
376
+ it 'should require one argument' do
377
+ expect {
378
+ instance.removepool
379
+ }.to raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 1)')
380
+ end
381
+
382
+ it 'should query the miner with arguments' do
383
+ expect(instance).to receive(:query).with(:removepool, :pool_number)
384
+ instance.removepool(:pool_number)
385
+ end
386
+ end
387
+
388
+ context 'switchpool' do
389
+ it 'should require one argument' do
390
+ expect {
391
+ instance.switchpool
392
+ }.to raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 1)')
393
+ end
394
+
395
+ it 'should query the miner with arguments' do
396
+ expect(instance).to receive(:query).with(:switchpool, :pool_number)
397
+ instance.switchpool(:pool_number)
398
+ end
399
+ end
400
+ end
401
+
402
+ describe CgminerApiClient::Miner::Commands::Privileged::System do
403
+ before do
404
+ allow(instance).to receive(:access_denied?).and_return(false)
405
+ end
406
+
407
+ context 'debug' do
408
+ it 'should query the miner with defaults' do
409
+ expect(instance).to receive(:query).with(:debug, 'D')
410
+ instance.debug
411
+ end
412
+
413
+ it 'should query the miner with arguments' do
414
+ expect(instance).to receive(:query).with(:debug, :setting)
415
+ instance.debug(:setting)
416
+ end
417
+ end
418
+
419
+ context 'failover_only' do
420
+ it 'should require one argument' do
421
+ expect {
422
+ instance.failover_only
423
+ }.to raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 1)')
424
+ end
425
+
426
+ it 'should query the miner with arguments' do
427
+ expect(instance).to receive(:query).with(:'failover-only', :value)
428
+ instance.failover_only(:value)
429
+ end
430
+ end
431
+
432
+ context 'hotplug' do
433
+ it 'should require one argument' do
434
+ expect {
435
+ instance.hotplug
436
+ }.to raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 1)')
437
+ end
438
+
439
+ it 'should query the miner with arguments' do
440
+ expect(instance).to receive(:query).with(:hotplug, :value)
441
+ instance.hotplug(:value)
442
+ end
443
+ end
444
+
445
+ context 'quit' do
446
+ it 'should query the miner' do
447
+ expect(instance).to receive(:query).with(:quit)
448
+ instance.quit
449
+ end
450
+ end
451
+
452
+ context 'restart' do
453
+ it 'should query the miner' do
454
+ expect(instance).to receive(:query).with(:restart)
455
+ instance.restart
456
+ end
457
+ end
458
+
459
+ context 'save' do
460
+ context 'without filename' do
461
+ it 'should query the miner' do
462
+ expect(instance).to receive(:query).with(:save)
463
+ instance.save
464
+ end
465
+ end
466
+
467
+ context 'with filename' do
468
+ it 'should query the miner with arguments' do
469
+ expect(instance).to receive(:query).with(:save, :filename)
470
+ instance.save(:filename)
471
+ end
472
+ end
473
+ end
474
+
475
+ context 'setconfig' do
476
+ it 'should require two arguments' do
477
+ expect {
478
+ instance.setconfig
479
+ }.to raise_error(ArgumentError, 'wrong number of arguments (given 0, expected 2)')
480
+ end
481
+
482
+ it 'should query the miner with arguments' do
483
+ expect(instance).to receive(:query).with(:setconfig, :name, :value)
484
+ instance.setconfig(:name, :value)
485
+ end
486
+ end
487
+
488
+ context 'zero' do
489
+ it 'should query the miner with defaults' do
490
+ expect(instance).to receive(:query).with(:zero, 'All', false)
491
+ instance.zero
492
+ end
493
+
494
+ it 'should query the miner with arguments' do
495
+ expect(instance).to receive(:query).with(:zero, :which, :full_summary)
496
+ instance.zero(:which, :full_summary)
497
+ end
498
+ end
499
+ end
500
+ end
501
+ end