zold 0.20.1 → 0.20.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/bin/zold +23 -7
  3. data/fixtures/merge/into-no-wallet/copies/0123456789abcdef/scores.zc +1 -1
  4. data/fixtures/merge/random-expenses/copies/0123456789abcdef/scores.zc +5 -5
  5. data/fixtures/merge/simple-case/copies/0123456789abcdef/scores.zc +1 -1
  6. data/lib/zold/age.rb +2 -1
  7. data/lib/zold/amount.rb +3 -0
  8. data/lib/zold/cached_wallets.rb +2 -2
  9. data/lib/zold/commands/calculate.rb +1 -1
  10. data/lib/zold/commands/clean.rb +6 -1
  11. data/lib/zold/commands/fetch.rb +5 -4
  12. data/lib/zold/commands/merge.rb +2 -1
  13. data/lib/zold/commands/node.rb +46 -10
  14. data/lib/zold/commands/push.rb +4 -2
  15. data/lib/zold/commands/routines/audit.rb +53 -0
  16. data/lib/zold/commands/routines/gc.rb +1 -1
  17. data/lib/zold/copies.rb +15 -8
  18. data/lib/zold/head.rb +15 -14
  19. data/lib/zold/hungry_wallets.rb +2 -2
  20. data/lib/zold/id.rb +4 -8
  21. data/lib/zold/metronome.rb +4 -4
  22. data/lib/zold/node/async_entrance.rb +7 -3
  23. data/lib/zold/node/farm.rb +1 -1
  24. data/lib/zold/node/front.rb +8 -5
  25. data/lib/zold/node/sync_entrance.rb +4 -0
  26. data/lib/zold/remotes.rb +57 -55
  27. data/lib/zold/tax.rb +2 -2
  28. data/lib/zold/thread_pool.rb +18 -14
  29. data/lib/zold/tree_wallets.rb +1 -1
  30. data/lib/zold/txn.rb +32 -3
  31. data/lib/zold/txns.rb +14 -14
  32. data/lib/zold/verbose_thread.rb +7 -0
  33. data/lib/zold/version.rb +1 -1
  34. data/lib/zold/wallet.rb +2 -2
  35. data/test/commands/routines/test_audit.rb +41 -0
  36. data/test/commands/test_clean.rb +3 -3
  37. data/test/node/fake_node.rb +2 -1
  38. data/test/node/test_async_entrance.rb +3 -1
  39. data/test/node/test_front.rb +1 -0
  40. data/test/node/test_sync_entrance.rb +2 -2
  41. data/test/test_copies.rb +14 -5
  42. data/test/test_tree_wallets.rb +17 -7
  43. data/test/test_txn.rb +8 -0
  44. data/test/test_wallet.rb +17 -0
  45. data/zold.gemspec +1 -1
  46. metadata +8 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dec6ef3362deffe905c48db392a63691b2af28e0e1d7935705a7ccee03f72630
4
- data.tar.gz: d8bc9621312050b27e2d25897585f6f8a9b5d2b0f9d4681c9a08e52ad04990d3
3
+ metadata.gz: d066e22fce978f4fdee5ebf56c935c3f05a09a1c7ad57c6da8ed6388db63eb84
4
+ data.tar.gz: e505ab03aafc00413ba0f71513fb4bd0f5e06794da6704a2ea335275285222cb
5
5
  SHA512:
6
- metadata.gz: 50bf436dc8d8323b83c7fae037e9de22818921e67ede7d1a2fce49c29dcffe515e1f6695199bbd36ec64668aba138782cdb82725f07ae13c2db74f78f1210b2f
7
- data.tar.gz: 603a47739743f9c77c3a4f6208802534d1ad7c59b9a15d5759324c78ddba22939ae7b4b1181181e26b0de2445ac747eef31aab14e45ffbc640d9791ec8b4e7f1
6
+ metadata.gz: 4313b16d11fb3226c35a0db9b3e7367b9550b008c2be560c3281ae05c8bae9f5249f53b5a7fbd8cd429f3f8207db098e6dada0ed3af68cb1bff927a1f45ce804
7
+ data.tar.gz: e2a483f2eff040a1429e96c0a515a00c8a7566ab200ed23ab18b6d6d5dc89c6fecda4d107ef0c21f3481df4759cf0ae6f70ca0c94b5f9270f798ee211854f737
data/bin/zold CHANGED
@@ -29,9 +29,11 @@ require 'slop'
29
29
  require 'rainbow'
30
30
  require 'backtrace'
31
31
  require 'memory_profiler'
32
+ require 'get_process_mem'
32
33
  require_relative '../lib/zold'
33
34
  require_relative '../lib/zold/version'
34
35
  require_relative '../lib/zold/wallet'
36
+ require_relative '../lib/zold/dir_items'
35
37
  require_relative '../lib/zold/wallets'
36
38
  require_relative '../lib/zold/tree_wallets'
37
39
  require_relative '../lib/zold/sync_wallets'
@@ -40,6 +42,7 @@ require_relative '../lib/zold/hungry_wallets'
40
42
  require_relative '../lib/zold/log'
41
43
  require_relative '../lib/zold/key'
42
44
  require_relative '../lib/zold/age'
45
+ require_relative '../lib/zold/size'
43
46
  require_relative '../lib/zold/amount'
44
47
  require_relative '../lib/zold/copies'
45
48
  require_relative '../lib/zold/remotes'
@@ -159,20 +162,27 @@ FileUtils.mkdir_p(home)
159
162
  Dir.chdir(home)
160
163
  log.debug("Home directory: #{home}")
161
164
 
162
- zoldata = File.join(home, '.zoldata')
165
+ zdata = File.join(home, '.zoldata')
163
166
 
164
167
  unless opts['skip-upgrades']
165
- Zold::Upgrades.new(Zold::VersionFile.new(File.join(zoldata, 'version')), 'upgrades', { command: command, network: opts['network']}).run
168
+ Zold::Upgrades.new(Zold::VersionFile.new(File.join(zdata, 'version')), 'upgrades', { command: command, network: opts['network']}).run
166
169
  end
167
170
 
171
+ locks = File.join(zdata, 'locks')
172
+ Zold::DirItems.new(locks).fetch.each do |f|
173
+ file = File.join(locks, f)
174
+ if File.mtime(file) < Time.now - 60
175
+ File.delete(file)
176
+ end
177
+ end
168
178
  wallets = Zold::SyncWallets.new(
169
179
  Zold::CachedWallets.new(
170
180
  command == 'node' ? Zold::TreeWallets.new(home) : Zold::Wallets.new(home)
171
181
  ),
172
182
  log: log,
173
- dir: File.join(zoldata, 'locks')
183
+ dir: locks
174
184
  )
175
- fremotes = File.join(zoldata, 'remotes')
185
+ fremotes = File.join(zdata, 'remotes')
176
186
  remotes = Zold::Remotes.new(file: fremotes, network: opts['network'])
177
187
  if File.exist?(fremotes)
178
188
  log.debug("Remote nodes: #{remotes.all.count} total")
@@ -180,9 +190,10 @@ else
180
190
  remotes.masters
181
191
  log.debug("Default remotes have been set: #{remotes.all.count} total")
182
192
  end
183
- copies = File.join(zoldata, 'copies')
193
+ copies = File.join(zdata, 'copies')
184
194
 
185
195
  log.debug("Network: #{opts['network']} (#{opts['network'] == Zold::Wallet::MAINET ? 'main' : 'test'} net)")
196
+ log.debug("Memory footprint at start is #{Zold::Size.new(GetProcessMem.new.bytes.to_i)}")
186
197
 
187
198
  cmd = lambda do
188
199
  begin
@@ -262,6 +273,11 @@ else
262
273
  code = cmd.call
263
274
  end
264
275
 
265
- exit(code) unless code.zero?
276
+ log.debug("Memory footprint at the end is #{Zold::Size.new(GetProcessMem.new.bytes.to_i)}")
277
+ if code.zero?
278
+ log.debug("Failed in in #{Zold::Age.new(start)}")
279
+ exit(code)
280
+ else
281
+ log.debug("Successfully finished in #{Zold::Age.new(start)}")
282
+ end
266
283
 
267
- log.debug("Successfully finished in #{Zold::Age.new(start)}")
@@ -1 +1 @@
1
- 1,0.0.0.0,4096,50,NOW
1
+ 1,0.0.0.0,4096,50,NOW,M
@@ -1,5 +1,5 @@
1
- 1,0.0.0.0,4096,10,NOW
2
- 2,0.0.0.0,4096,20,NOW
3
- 3,0.0.0.0,4096,30,NOW
4
- 4,0.0.0.0,4096,40,NOW
5
- 5,0.0.0.0,4096,50,NOW
1
+ 1,0.0.0.0,4096,10,NOW,M
2
+ 2,0.0.0.0,4096,20,NOW,E
3
+ 3,0.0.0.0,4096,30,NOW,E
4
+ 4,0.0.0.0,4096,40,NOW,E
5
+ 5,0.0.0.0,4096,50,NOW,E
@@ -1 +1 @@
1
- 1,0.0.0.0,4096,50,NOW
1
+ 1,0.0.0.0,4096,50,NOW,M
data/lib/zold/age.rb CHANGED
@@ -22,6 +22,7 @@
22
22
 
23
23
  require 'time'
24
24
  require 'rainbow'
25
+ require_relative 'txn'
25
26
 
26
27
  # Age in seconds.
27
28
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -31,7 +32,7 @@ module Zold
31
32
  # Age
32
33
  class Age
33
34
  def initialize(time, limit: nil)
34
- @time = time.nil? || time.is_a?(Time) ? time : Time.parse(time)
35
+ @time = time.nil? || time.is_a?(Time) ? time : Txn.parse_time(time)
35
36
  @limit = limit
36
37
  end
37
38
 
data/lib/zold/amount.rb CHANGED
@@ -50,12 +50,15 @@ module Zold
50
50
  raise "The amount is too small: #{@zents}" if @zents < -MAX
51
51
  end
52
52
 
53
+ # Just zero, for convenience.
53
54
  ZERO = Amount.new(zents: 0)
54
55
 
56
+ # Convert it to zents and return as an integer.
55
57
  def to_i
56
58
  @zents
57
59
  end
58
60
 
61
+ # Convert to ZLD and return as a string. If you need float, you should use <tt>to_f()</tt> later.
59
62
  def to_zld(digits = 2)
60
63
  format("%0.#{digits}f", @zents.to_f / 2**FRACTION)
61
64
  end
@@ -38,7 +38,7 @@ module Zold
38
38
  @clean = ThreadPool.new('cached-wallets')
39
39
  @clean.add do
40
40
  Endless.new('cached_wallets').run do
41
- sleep 60
41
+ sleep 5
42
42
  @zache.clean
43
43
  end
44
44
  end
@@ -47,7 +47,7 @@ module Zold
47
47
 
48
48
  def acq(id, exclusive: false)
49
49
  @wallets.acq(id, exclusive: exclusive) do |wallet|
50
- c = @zache.get(id.to_s, lifetime: 5 * 60) { wallet }
50
+ c = @zache.get(id.to_s, lifetime: 15) { wallet }
51
51
  res = yield c
52
52
  c.flush if exclusive
53
53
  res
@@ -79,7 +79,7 @@ Available options:"
79
79
  strength = opts[:strength]
80
80
  raise "Invalid strength: #{strength}" if strength <= 0 || strength > 8
81
81
  score = Zold::Score.new(
82
- time: Time.parse(opts[:time]), host: opts[:host], port: opts[:port].to_i,
82
+ time: Txn.parse_time(opts[:time]), host: opts[:host], port: opts[:port].to_i,
83
83
  invoice: opts[:invoice], strength: strength
84
84
  )
85
85
  loop do
@@ -33,6 +33,7 @@ require_relative '../size'
33
33
  require_relative '../log'
34
34
  require_relative '../http'
35
35
  require_relative '../copies'
36
+ require_relative '../thread_pool'
36
37
 
37
38
  # CLEAN command.
38
39
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -51,10 +52,14 @@ module Zold
51
52
  opts = Slop.parse(args, help: true, suppress_errors: true) do |o|
52
53
  o.banner = "Usage: zold clean [ID...] [options]
53
54
  Available options:"
55
+ o.integer '--threads',
56
+ "How many threads to use for cleaning copies (default: #{[Concurrent.processor_count / 2, 2].max})",
57
+ default: [Concurrent.processor_count / 2, 2].max
54
58
  o.bool '--help', 'Print instructions'
55
59
  end
56
60
  mine = Args.new(opts, @log).take || return
57
- (mine.empty? ? @wallets.all : mine.map { |i| Id.new(i) }).each do |id|
61
+ list = mine.empty? ? @wallets.all : mine.map { |i| Id.new(i) }
62
+ ThreadPool.new('clean', log: @log).run(opts['threads'], list.uniq) do |id|
58
63
  clean(Copies.new(File.join(@copies, id), log: @log), opts)
59
64
  end
60
65
  end
@@ -91,7 +91,7 @@ Available options:"
91
91
  end
92
92
  mine = Args.new(opts, @log).take || return
93
93
  list = mine.empty? ? @wallets.all : mine.map { |i| Id.new(i) }
94
- ThreadPool.new('fetch', log: @log).run(opts['threads'], list) do |id|
94
+ ThreadPool.new('fetch', log: @log).run(opts['threads'], list.uniq) do |id|
95
95
  fetch(id, Copies.new(File.join(@copies, id)), opts)
96
96
  end
97
97
  end
@@ -112,7 +112,9 @@ Available options:"
112
112
  done.increment
113
113
  end
114
114
  unless opts['quiet-if-absent']
115
- raise "No nodes out of #{nodes.value} have the wallet #{id}" if done.value.zero?
115
+ if done.value.zero?
116
+ raise "No nodes out of #{nodes.value} have the wallet #{id}; run 'zold remote update' and try again"
117
+ end
116
118
  if masters.value.zero? && !opts['tolerate-edges']
117
119
  raise EdgesOnly, "There are only edge nodes, run 'zold remote update' or use --tolerate-edges"
118
120
  end
@@ -140,7 +142,6 @@ run 'zold remote update' or use --tolerate-quorum=1"
140
142
  size = r.http(uri + '/size').get
141
143
  r.assert_code(200, size)
142
144
  res = r.http(uri).get(timeout: 2 + size.body.to_i * 0.01 / 1024)
143
- raise "Wallet #{id} not found" if res.status == '404'
144
145
  r.assert_code(200, res)
145
146
  json = JsonPage.new(res.body, uri).to_hash
146
147
  score = Score.parse_json(json['score'])
@@ -161,7 +162,7 @@ run 'zold remote update' or use --tolerate-quorum=1"
161
162
  if wallet.balance.negative? && !wallet.root?
162
163
  raise "The balance of #{id} is #{wallet.balance} and it's not a root wallet"
163
164
  end
164
- copy = cps.add(IO.read(f), score.host, score.port, score.value)
165
+ copy = cps.add(IO.read(f), score.host, score.port, score.value, master: r.master?)
165
166
  @log.info("#{r} returned #{wallet.mnemo} #{Age.new(json['mtime'])}/#{json['copies']}c \
166
167
  as copy ##{copy}/#{cps.all.count} in #{Age.new(start, limit: 4)}: \
167
168
  #{Rainbow(score.value).green} (#{json['version']})")
@@ -72,7 +72,8 @@ Available options:"
72
72
  end
73
73
  mine = Args.new(opts, @log).take || return
74
74
  modified = []
75
- (mine.empty? ? @wallets.all : mine.map { |i| Id.new(i) }).each do |id|
75
+ list = mine.empty? ? @wallets.all : mine.map { |i| Id.new(i) }
76
+ list.uniq.each do |id|
76
77
  next unless merge(id, Copies.new(File.join(@copies, id)), opts)
77
78
  modified << id
78
79
  next if opts['skip-propagate']
@@ -23,6 +23,7 @@
23
23
  require 'open3'
24
24
  require 'slop'
25
25
  require 'backtrace'
26
+ require 'fileutils'
26
27
  require 'zache'
27
28
  require 'concurrent'
28
29
  require 'zold/score'
@@ -132,6 +133,15 @@ module Zold
132
133
  o.bool '--no-cache',
133
134
  'Skip caching of front JSON pages (will seriously slow down, mostly useful for testing)',
134
135
  default: false
136
+ o.boolean '--skip-audit',
137
+ 'Don\'t report audit information to the console every minute',
138
+ default: false
139
+ o.boolean '--skip-reconnect',
140
+ 'Don\'t reconnect to the network every minute (for testing)',
141
+ default: false
142
+ o.boolean '--not-hungry',
143
+ 'Don\'t do hugry pulling of missed nodes (mostly for testing)',
144
+ default: false
135
145
  o.bool '--allow-spam',
136
146
  'Don\'t filter the incoming spam via PUT requests (duplicate wallets)',
137
147
  default: false
@@ -139,11 +149,14 @@ module Zold
139
149
  'Skip Out Of Memory check and never exit, no matter how much RAM is consumed',
140
150
  default: false
141
151
  o.integer '--oom-limit',
142
- 'Maximum amount of memory we can consume, quit if we take more than that, in Mb (default: 256)',
143
- default: 256
152
+ 'Maximum amount of memory we can consume, quit if we take more than that, in Mb (default: 512)',
153
+ default: 512
144
154
  o.integer '--queue-limit',
145
155
  'The maximum number of wallets to be accepted via PUSH and stored in the queue (default: 256)',
146
156
  default: 256
157
+ o.bool '--skip-gc',
158
+ 'Don\'t run garbage collector and never remove any wallets from the disk',
159
+ default: false
147
160
  o.integer '--gc-age',
148
161
  'Maximum time in seconds to keep an empty and unused wallet on the disk',
149
162
  default: 60 * 60 * 24 * 10
@@ -221,8 +234,17 @@ module Zold
221
234
  Zold::Remote.new(remotes: @remotes).run(['remote', 'remove', host, port.to_s])
222
235
  @log.info("Removed current node (#{address}) from list of remotes")
223
236
  end
224
- hungry = Zold::ThreadPool.new('hungry', log: @log)
225
- wts = Zold::HungryWallets.new(@wallets, @remotes, @copies, hungry, log: @log, network: opts['network'])
237
+ if File.exist?(@copies)
238
+ FileUtils.rm_rf(@copies)
239
+ @log.info("Directory #{@copies} deleted")
240
+ end
241
+ wts = @wallets
242
+ if opts['not-hungry']
243
+ @log.info('Hungry pulling disabled because of --not-hungry')
244
+ else
245
+ hungry = Zold::ThreadPool.new('hungry', log: @log)
246
+ wts = Zold::HungryWallets.new(@wallets, @remotes, @copies, hungry, log: @log, network: opts['network'])
247
+ end
226
248
  Front.set(:zache, Zache.new(dirty: true))
227
249
  Front.set(:wallets, wts)
228
250
  Front.set(:remotes, @remotes)
@@ -279,7 +301,7 @@ module Zold
279
301
  end
280
302
  end
281
303
  end
282
- hungry.kill
304
+ hungry.kill unless opts['not-hungry']
283
305
  @log.info('Thanks for helping Zold network!')
284
306
  end
285
307
 
@@ -383,14 +405,28 @@ module Zold
383
405
  def metronome(farm, opts)
384
406
  metronome = Metronome.new(@log)
385
407
  if opts['no-metronome']
386
- @log.info('Metronome hasn\'t been started because of --no-metronome')
408
+ @log.info("Metronome hasn't been started because of --no-metronome")
387
409
  return metronome
388
410
  end
389
- require_relative 'routines/gc'
390
- metronome.add(Routines::Gc.new(opts, @wallets, log: @log))
411
+ if opts['skip-gc']
412
+ @log.info('Garbage collection is disabled because of --skip-gc')
413
+ else
414
+ require_relative 'routines/gc'
415
+ metronome.add(Routines::Gc.new(opts, @wallets, log: @log))
416
+ end
417
+ if opts['skip-audit']
418
+ @log.info('Audit is disabled because of --skip-audit')
419
+ else
420
+ require_relative 'routines/audit'
421
+ metronome.add(Routines::Audit.new(opts, @wallets, log: @log))
422
+ end
391
423
  unless opts['standalone']
392
- require_relative 'routines/reconnect'
393
- metronome.add(Routines::Reconnect.new(opts, @remotes, farm, network: opts['network'], log: @log))
424
+ if opts['skip-reconnect']
425
+ @log.info('Reconnect is disabled because of --skip-reconnect')
426
+ else
427
+ require_relative 'routines/reconnect'
428
+ metronome.add(Routines::Reconnect.new(opts, @remotes, farm, network: opts['network'], log: @log))
429
+ end
394
430
  end
395
431
  @log.info('Metronome started (use --no-metronome to disable it)')
396
432
  metronome
@@ -83,7 +83,7 @@ Available options:"
83
83
  end
84
84
  mine = Args.new(opts, @log).take || return
85
85
  list = mine.empty? ? @wallets.all : mine.map { |i| Id.new(i) }
86
- ThreadPool.new('push', log: @log).run(opts['threads'], list) do |id|
86
+ ThreadPool.new('push', log: @log).run(opts['threads'], list.uniq) do |id|
87
87
  push(id, opts)
88
88
  end
89
89
  end
@@ -104,7 +104,9 @@ Available options:"
104
104
  done.increment
105
105
  end
106
106
  unless opts['quiet-if-missed']
107
- raise "No nodes out of #{nodes} accepted the wallet #{id}" if done.value.zero?
107
+ if done.value.zero?
108
+ raise "No nodes out of #{nodes} accepted the wallet #{id}; run 'zold remote update' and try again"
109
+ end
108
110
  if masters.value.zero? && !opts['tolerate-edges']
109
111
  raise EdgesOnly, "There are only edge nodes, run 'zold remote update' or use --tolerate-edges"
110
112
  end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2018 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require 'get_process_mem'
24
+ require_relative '../../size'
25
+
26
+ # Audit and report as much as we can to the command line.
27
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
28
+ # Copyright:: Copyright (c) 2018 Yegor Bugayenko
29
+ # License:: MIT
30
+ module Zold
31
+ # Routines module
32
+ module Routines
33
+ # Audit the system
34
+ class Audit
35
+ def initialize(opts, wallets, log: Log::NULL)
36
+ @opts = opts
37
+ @wallets = wallets
38
+ @log = log
39
+ end
40
+
41
+ def exec(_ = 0)
42
+ sleep(60) unless @opts['routine-immediately']
43
+ @log.info(
44
+ 'Audit: ' + [
45
+ "memory used: #{Size.new(GetProcessMem.new.bytes.to_i)}",
46
+ "threads total: #{Thread.list.count}",
47
+ "wallets: #{@wallets.count}"
48
+ ].join('; ')
49
+ )
50
+ end
51
+ end
52
+ end
53
+ end
@@ -47,7 +47,7 @@ module Zold
47
47
  removed = 0
48
48
  @wallets.all.each do |id|
49
49
  seen += 1
50
- next unless @wallets.acq(id) { |w| w.exists? && w.txns.empty? && w.mtime < Time.now - @opts['gc-age'] }
50
+ next unless @wallets.acq(id) { |w| w.exists? && w.mtime < Time.now - @opts['gc-age'] && w.txns.empty? }
51
51
  cmd.run(args + [id.to_s])
52
52
  removed += 1
53
53
  end
data/lib/zold/copies.rb CHANGED
@@ -89,7 +89,7 @@ module Zold
89
89
  end
90
90
 
91
91
  # Returns the name of the copy
92
- def add(content, host, port, score, time = Time.now)
92
+ def add(content, host, port, score, time: Time.now, master: false)
93
93
  raise "Content can't be empty" if content.empty?
94
94
  raise 'TCP port must be of type Integer' unless port.is_a?(Integer)
95
95
  raise "TCP port can't be negative: #{port}" if port.negative?
@@ -121,7 +121,8 @@ module Zold
121
121
  host: host,
122
122
  port: port,
123
123
  score: score,
124
- time: time
124
+ time: time,
125
+ master: master
125
126
  }
126
127
  save(list)
127
128
  name
@@ -134,24 +135,27 @@ module Zold
134
135
  {
135
136
  name: name,
136
137
  path: File.join(@dir, "#{name}#{Copies::EXT}"),
138
+ total: scores.count,
139
+ master: scores.any? { |s| s[:master] },
137
140
  score: scores.select { |s| s[:time] > Time.now - 24 * 60 * 60 }
138
141
  .map { |s| s[:score] }
139
142
  .inject(&:+) || 0
140
143
  }
141
- end.select { |c| File.exist?(c[:path]) }.sort_by { |c| c[:score] }.reverse
144
+ end.select { |c| File.exist?(c[:path]) }.sort_by { |c| [c[:master] ? 1 : 0, c[:score].to_s] }.reverse
142
145
  end
143
146
  end
144
147
 
145
148
  def load
146
149
  FileUtils.mkdir_p(File.dirname(file))
147
150
  FileUtils.touch(file)
148
- CSV.read(file).map do |s|
151
+ CSV.read(file).select { |s| s.count == 6 }.map do |s|
149
152
  {
150
153
  name: s[0],
151
154
  host: s[1],
152
155
  port: s[2].to_i,
153
156
  score: s[3].to_i,
154
- time: Time.parse(s[4])
157
+ time: Txn.parse_time(s[4]),
158
+ master: s[5] == 'M'
155
159
  }
156
160
  end
157
161
  end
@@ -163,9 +167,12 @@ module Zold
163
167
  file,
164
168
  list.map do |r|
165
169
  [
166
- r[:name], r[:host],
167
- r[:port], r[:score],
168
- r[:time].utc.iso8601
170
+ r[:name],
171
+ r[:host],
172
+ r[:port],
173
+ r[:score],
174
+ r[:time].utc.iso8601,
175
+ r[:master] ? 'M' : 'E'
169
176
  ].join(',')
170
177
  end.join("\n")
171
178
  )