zold 0.20.1 → 0.20.2

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