file-digests 0.0.33 → 0.0.38

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/bin/file-digests +14 -0
  3. data/lib/file-digests.rb +200 -105
  4. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 68dea8d33894b138d5908dcf27fef7800a17af8f1441fd043876248414ad9525
4
- data.tar.gz: da5c89c26fc95ddeaa6b8443193293490e71f41bc4ed21e00415c5937661833e
3
+ metadata.gz: 1a7193a9795eb785cd22a7663fa4dae908d3349b854be8d3613750f24ce6b711
4
+ data.tar.gz: 32a8172cc7115240f5954b7e4775f841ddce10102c7e48e184690d2643e7a476
5
5
  SHA512:
6
- metadata.gz: 05e700dedeebde8e472e8adf6b0eedb6c3692b919b3d478a041c8e8110375daae7dd926cd9c33c46379976730f0fb0610880a57524aa44e0764fa169a4020fad
7
- data.tar.gz: c0e954b6250223e53ec0823f328bd808886f9566ecf373ceaaba5da7a079ae76e18dfba3cf81bfad0741ea8dfdb87c1715c81f8af72094b77abe014fc59727ef
6
+ metadata.gz: 584a6fc4c10e6a33d9e55d9647abcfce913fa18b046eddf7193807408d7ac4167b1b7114aa300531426e767668841f483ad66775bf7f492ff552f290d19f6e2a
7
+ data.tar.gz: 877dea6a6ba8f3ab091ed6d5b0379090a60cff4477ad9d4409fa9fef9674bd39b9134682ac534d49535a21ed58382b02088fa00f83524d31add7c23919e9c0c4
@@ -1,5 +1,19 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ # Copyright 2020 Stanislav Senotrusov <stan@senotrusov.com>
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
3
17
  require 'file-digests'
4
18
 
5
19
  FileDigests.run_cli_utility
@@ -1,3 +1,17 @@
1
+ # Copyright 2020 Stanislav Senotrusov <stan@senotrusov.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
1
15
  require "date"
2
16
  require "digest"
3
17
  require "fileutils"
@@ -8,7 +22,8 @@ require "set"
8
22
  require "sqlite3"
9
23
 
10
24
  class FileDigests
11
- DIGEST_ALGORITHMS=["BLAKE2b512", "SHA3-256", "SHA512-256"]
25
+ VERSION = Gem.loaded_specs["file-digests"]&.version&.to_s
26
+ DIGEST_ALGORITHMS = ["BLAKE2b512", "SHA3-256", "SHA512-256"]
12
27
  LEGACY_DIGEST_ALGORITHMS = ["SHA512", "SHA256"]
13
28
 
14
29
  def self.canonical_digest_algorithm_name(string)
@@ -101,6 +116,7 @@ class FileDigests
101
116
 
102
117
  def initialize files_path, digest_database_path, options = {}
103
118
  @options = options
119
+ @user_input_wait_time = 0
104
120
 
105
121
  initialize_paths files_path, digest_database_path
106
122
  initialize_database
@@ -124,13 +140,13 @@ class FileDigests
124
140
 
125
141
  def initialize_paths files_path, digest_database_path
126
142
  @files_path = cleanup_path(files_path || ".")
127
-
128
143
  raise "Files path must be a readable directory" unless (File.directory?(@files_path) && File.readable?(@files_path))
144
+ @files_path = realpath_with_disk @files_path
129
145
 
130
146
  @digest_database_path = digest_database_path ? cleanup_path(digest_database_path) : @files_path
131
147
  @digest_database_path += ".file-digests.sqlite" if File.directory?(@digest_database_path)
132
148
  ensure_dir_exist @digest_database_path.dirname
133
-
149
+ @digest_database_path = realdirpath_with_disk @digest_database_path
134
150
  @digest_database_files = ["#{@digest_database_path}", "#{@digest_database_path}-wal", "#{@digest_database_path}-shm"]
135
151
 
136
152
  if @options[:verbose]
@@ -144,8 +160,6 @@ class FileDigests
144
160
  @db.results_as_hash = true
145
161
  @db.busy_timeout = 5000
146
162
 
147
- file_digests_gem_version = Gem.loaded_specs["file-digests"]&.version&.to_s
148
-
149
163
  execute "PRAGMA encoding = 'UTF-8'"
150
164
  execute "PRAGMA locking_mode = 'EXCLUSIVE'"
151
165
  execute "PRAGMA journal_mode = 'WAL'"
@@ -160,14 +174,13 @@ class FileDigests
160
174
  execute "CREATE TABLE metadata (
161
175
  key TEXT NOT NULL PRIMARY KEY,
162
176
  value TEXT)"
163
- execute "CREATE UNIQUE INDEX metadata_key ON metadata(key)"
164
177
  metadata_table_was_created = true
165
178
  end
166
179
 
167
180
  prepare_method :set_metadata_query, "INSERT INTO metadata (key, value) VALUES (?, ?) ON CONFLICT (key) DO UPDATE SET value=excluded.value"
168
181
  prepare_method :get_metadata_query, "SELECT value FROM metadata WHERE key = ?"
169
182
 
170
- set_metadata("metadata_table_created_by_gem_version", file_digests_gem_version) if file_digests_gem_version && metadata_table_was_created
183
+ set_metadata("metadata_table_created_by_gem_version", FileDigests::VERSION) if FileDigests::VERSION && metadata_table_was_created
171
184
 
172
185
  # Heuristic to detect database version 1 (metadata was not stored back then)
173
186
  unless get_metadata("database_version")
@@ -184,20 +197,19 @@ class FileDigests
184
197
  digest TEXT NOT NULL,
185
198
  digest_check_time TEXT NOT NULL)"
186
199
  execute "CREATE UNIQUE INDEX digests_filename ON digests(filename)"
187
- set_metadata("digests_table_created_by_gem_version", file_digests_gem_version) if file_digests_gem_version
200
+ execute "CREATE INDEX digests_digest ON digests(digest)"
201
+ set_metadata("digests_table_created_by_gem_version", FileDigests::VERSION) if FileDigests::VERSION
188
202
  end
189
203
 
190
- prepare_method :insert, "INSERT INTO digests (filename, mtime, digest, digest_check_time) VALUES (?, ?, ?, datetime('now'))"
191
- prepare_method :find_by_filename_query, "SELECT id, mtime, digest FROM digests WHERE filename = ?"
192
- prepare_method :touch_digest_check_time, "UPDATE digests SET digest_check_time = datetime('now') WHERE id = ?"
193
- prepare_method :update_mtime_and_digest, "UPDATE digests SET mtime = ?, digest = ?, digest_check_time = datetime('now') WHERE id = ?"
194
- prepare_method :update_mtime, "UPDATE digests SET mtime = ?, digest_check_time = datetime('now') WHERE id = ?"
195
- prepare_method :delete_by_filename, "DELETE FROM digests WHERE filename = ?"
196
- prepare_method :query_duplicates, "SELECT digest, filename FROM digests WHERE digest IN (SELECT digest FROM digests GROUP BY digest HAVING count(*) > 1) ORDER BY digest, filename;"
197
- prepare_method :update_digest_to_new_digest, "UPDATE digests SET digest = ? WHERE digest = ?"
204
+ prepare_method :digests_insert, "INSERT INTO digests (filename, mtime, digest, digest_check_time) VALUES (?, ?, ?, datetime('now'))"
205
+ prepare_method :digests_find_by_filename_query, "SELECT id, mtime, digest FROM digests WHERE filename = ?"
206
+ prepare_method :digests_touch_check_time, "UPDATE digests SET digest_check_time = datetime('now') WHERE id = ?"
207
+ prepare_method :digests_update_mtime_and_digest, "UPDATE digests SET mtime = ?, digest = ?, digest_check_time = datetime('now') WHERE id = ?"
208
+ prepare_method :digests_update_mtime, "UPDATE digests SET mtime = ?, digest_check_time = datetime('now') WHERE id = ?"
209
+ prepare_method :digests_select_duplicates, "SELECT digest, filename FROM digests WHERE digest IN (SELECT digest FROM digests GROUP BY digest HAVING count(*) > 1) ORDER BY digest, filename;"
198
210
 
199
211
  unless get_metadata("database_version")
200
- set_metadata "database_version", "2"
212
+ set_metadata "database_version", "3"
201
213
  end
202
214
 
203
215
  # Convert database from 1st to 2nd version
@@ -212,79 +224,110 @@ class FileDigests
212
224
  end
213
225
  end
214
226
 
215
- if get_metadata("database_version") != "2"
216
- STDERR.puts "This version of file-digests (#{file_digests_gem_version || "unknown"}) is only compartible with the database version 2. Current database version is #{get_metadata("database_version")}. To use this database, please install appropriate version if file-digest."
217
- raise "Incompatible database version"
227
+ if get_metadata("database_version") == "2"
228
+ execute "CREATE INDEX digests_digest ON digests(digest)"
229
+ set_metadata "database_version", "3"
218
230
  end
231
+
232
+ check_if_database_is_at_certain_version "3"
233
+
234
+ create_temporary_tables
219
235
  end
220
236
  end
221
237
 
222
- def perform_check
223
- perhaps_transaction(@new_digest_algorithm, :exclusive) do
224
- @counters = {good: 0, updated: 0, new: 0, renamed: 0, likely_damaged: 0, exceptions: 0}
225
- @new_files = {}
226
- @new_digests = {}
238
+ def create_temporary_tables
239
+ execute "CREATE TEMPORARY TABLE new_files (
240
+ filename TEXT NOT NULL PRIMARY KEY,
241
+ digest TEXT NOT NULL)"
242
+ execute "CREATE INDEX new_files_digest ON new_files(digest)"
243
+
244
+ prepare_method :new_files_insert, "INSERT INTO new_files (filename, digest) VALUES (?, ?)"
245
+ prepare_method :new_files_count_query, "SELECT count(*) FROM new_files"
246
+
247
+ execute "CREATE TEMPORARY TABLE missing_files (
248
+ filename TEXT NOT NULL PRIMARY KEY,
249
+ digest TEXT NOT NULL)"
250
+ execute "CREATE INDEX missing_files_digest ON missing_files(digest)"
227
251
 
228
- @missing_files = Hash[@db.prepare("SELECT filename, digest FROM digests").execute!]
252
+ execute "INSERT INTO missing_files (filename, digest) SELECT filename, digest FROM digests"
229
253
 
230
- measure_time do
231
- walk_files do |filename|
254
+ prepare_method :missing_files_delete, "DELETE FROM missing_files WHERE filename = ?"
255
+ prepare_method :missing_files_delete_renamed_files, "DELETE FROM missing_files WHERE digest IN (SELECT digest FROM new_files)"
256
+ prepare_method :missing_files_select_all_filenames, "SELECT filename FROM missing_files ORDER BY filename"
257
+ prepare_method :missing_files_delete_all, "DELETE FROM missing_files"
258
+ prepare_method :missing_files_count_query, "SELECT count(*) FROM missing_files"
259
+
260
+ prepare_method :digests_delete_renamed_files, "DELETE FROM digests WHERE filename IN (SELECT filename FROM missing_files WHERE digest IN (SELECT digest FROM new_files))"
261
+ prepare_method :digests_delete_all_missing_files, "DELETE FROM digests WHERE filename IN (SELECT filename FROM missing_files)"
262
+
263
+ execute "CREATE TEMPORARY TABLE new_digests (
264
+ filename TEXT NOT NULL PRIMARY KEY,
265
+ digest TEXT NOT NULL)"
266
+
267
+ prepare_method :new_digests_insert, "INSERT INTO new_digests (filename, digest) VALUES (?, ?)"
268
+ prepare_method :digests_update_digests_to_new_digests, "INSERT INTO digests (filename, digest, digest_check_time) SELECT filename, digest, false FROM new_digests WHERE true ON CONFLICT (filename) DO UPDATE SET digest=excluded.digest"
269
+ end
270
+
271
+ def perform_check
272
+ measure_time do
273
+ perhaps_transaction(@new_digest_algorithm, :exclusive) do
274
+ @counters = {good: 0, updated: 0, renamed: 0, likely_damaged: 0, exceptions: 0}
275
+
276
+ walk_files(@files_path.to_s) do |filename|
232
277
  process_file filename
233
278
  end
234
- end
235
279
 
236
- nested_transaction do
237
- puts "Tracking renames..." if @options[:verbose]
238
- track_renames
239
- end
280
+ nested_transaction do
281
+ puts "Tracking renames..." if @options[:verbose]
282
+ track_renames
283
+ end
240
284
 
241
- if any_missing_files?
242
- if any_exceptions?
243
- STDERR.puts "Due to previously occurred errors, database cleanup from missing files will be skipped this time."
244
- else
245
- print_missing_files
246
- if !@options[:test_only] && (@options[:auto] || confirm("Remove missing files from the database"))
247
- nested_transaction do
248
- puts "Removing missing files..." if @options[:verbose]
249
- remove_missing_files
285
+ if any_missing_files?
286
+ if any_exceptions?
287
+ STDERR.puts "Due to previously occurred errors, missing files will not removed from the database."
288
+ else
289
+ print_missing_files
290
+ if !@options[:test_only] && (@options[:auto] || confirm("Remove missing files from the database"))
291
+ nested_transaction do
292
+ puts "Removing missing files..." if @options[:verbose]
293
+ remove_missing_files
294
+ end
250
295
  end
251
296
  end
252
297
  end
253
- end
254
298
 
255
- if @new_digest_algorithm && !@options[:test_only]
256
- if any_missing_files? || any_likely_damaged? || any_exceptions?
257
- STDERR.puts "ERROR: New digest algorithm will not be in effect until there are files that are missing, likely damaged, or processed with an exception."
258
- else
259
- puts "Updating database to a new digest algorithm..." if @options[:verbose]
260
- @new_digests.each do |old_digest, new_digest|
261
- update_digest_to_new_digest new_digest, old_digest
299
+ if @new_digest_algorithm && !@options[:test_only]
300
+ if any_missing_files? || any_likely_damaged? || any_exceptions?
301
+ STDERR.puts "ERROR: New digest algorithm will not be in effect until there are files that are missing, likely damaged, or processed with an exception."
302
+ else
303
+ puts "Updating database to a new digest algorithm..." if @options[:verbose]
304
+ digests_update_digests_to_new_digests
305
+ set_metadata "digest_algorithm", @new_digest_algorithm
306
+ puts "Transition to a new digest algorithm complete: #{@new_digest_algorithm}"
262
307
  end
263
- set_metadata "digest_algorithm", @new_digest_algorithm
264
- puts "Transition to a new digest algorithm complete: #{@new_digest_algorithm}"
265
308
  end
266
- end
267
309
 
268
- if any_likely_damaged? || any_exceptions?
269
- STDERR.puts "PLEASE REVIEW ERRORS THAT WERE OCCURRED!"
270
- end
310
+ if any_likely_damaged? || any_exceptions?
311
+ STDERR.puts "PLEASE REVIEW ERRORS THAT WERE OCCURRED!"
312
+ end
271
313
 
272
- set_metadata(@options[:test_only] ? "latest_test_only_check_time" : "latest_complete_check_time", time_to_database(Time.now))
314
+ set_metadata(@options[:test_only] ? "latest_test_only_check_time" : "latest_complete_check_time", time_to_database(Time.now))
273
315
 
274
- print_counters
275
- end
276
-
277
- puts "Performing database maintenance..." if @options[:verbose]
278
- execute "PRAGMA optimize"
279
- execute "VACUUM"
280
- execute "PRAGMA wal_checkpoint(TRUNCATE)"
316
+ print_counters
317
+ end
318
+
319
+ puts "Performing database maintenance..." if @options[:verbose]
320
+ execute "PRAGMA optimize"
321
+ execute "VACUUM"
322
+ execute "PRAGMA wal_checkpoint(TRUNCATE)"
281
323
 
282
- hide_database_files
324
+ hide_database_files
325
+ end
283
326
  end
284
327
 
285
328
  def show_duplicates
286
329
  current_digest = nil
287
- query_duplicates.each do |found|
330
+ digests_select_duplicates.each do |found|
288
331
  if current_digest != found["digest"]
289
332
  puts "" if current_digest
290
333
  current_digest = found["digest"]
@@ -297,9 +340,11 @@ class FileDigests
297
340
  private
298
341
 
299
342
  def process_file filename
300
- return if File.symlink? filename
343
+ perhaps_nt_filename = perhaps_nt_path filename
344
+
345
+ return if File.symlink? perhaps_nt_filename
301
346
 
302
- stat = File.stat filename
347
+ stat = File.stat perhaps_nt_filename
303
348
 
304
349
  return if stat.blockdev?
305
350
  return if stat.chardev?
@@ -316,9 +361,10 @@ class FileDigests
316
361
 
317
362
  normalized_filename = filename.delete_prefix("#{@files_path.to_s}/").encode("utf-8", universal_newline: true).unicode_normalize(:nfkc)
318
363
  mtime_string = time_to_database stat.mtime
319
- digest = get_file_digest(filename)
364
+ digest, new_digest = get_file_digest(perhaps_nt_filename)
320
365
 
321
366
  nested_transaction do
367
+ new_digests_insert(normalized_filename, new_digest) if new_digest
322
368
  process_file_indeed normalized_filename, mtime_string, digest
323
369
  end
324
370
 
@@ -336,15 +382,15 @@ class FileDigests
336
382
  end
337
383
 
338
384
  def process_previously_seen_file found, filename, mtime, digest
339
- @missing_files.delete(filename)
385
+ missing_files_delete filename
340
386
  if found["digest"] == digest
341
387
  @counters[:good] += 1
342
388
  puts "GOOD: #{filename}" if @options[:verbose]
343
389
  unless @options[:test_only]
344
390
  if found["mtime"] == mtime
345
- touch_digest_check_time found["id"]
391
+ digests_touch_check_time found["id"]
346
392
  else
347
- update_mtime mtime, found["id"]
393
+ digests_update_mtime mtime, found["id"]
348
394
  end
349
395
  end
350
396
  else
@@ -355,18 +401,17 @@ class FileDigests
355
401
  @counters[:updated] += 1
356
402
  puts "UPDATED#{" (FATE ACCEPTED)" if found["mtime"] == mtime && @options[:accept_fate]}: #{filename}" unless @options[:quiet]
357
403
  unless @options[:test_only]
358
- update_mtime_and_digest mtime, digest, found["id"]
404
+ digests_update_mtime_and_digest mtime, digest, found["id"]
359
405
  end
360
406
  end
361
407
  end
362
408
  end
363
409
 
364
410
  def process_new_file filename, mtime, digest
365
- @counters[:new] += 1
366
411
  puts "NEW: #{filename}" unless @options[:quiet]
412
+ new_files_insert filename, digest
367
413
  unless @options[:test_only]
368
- @new_files[filename] = digest
369
- insert filename, mtime, digest
414
+ digests_insert filename, mtime, digest
370
415
  end
371
416
  end
372
417
 
@@ -374,29 +419,31 @@ class FileDigests
374
419
  # Renames and missing files
375
420
 
376
421
  def track_renames
377
- @missing_files.delete_if do |filename, digest|
378
- if @new_files.value?(digest)
379
- @counters[:renamed] += 1
380
- unless @options[:test_only]
381
- delete_by_filename filename
382
- end
383
- true
384
- end
422
+ unless @options[:test_only]
423
+ digests_delete_renamed_files
385
424
  end
425
+ missing_files_delete_renamed_files
426
+ @counters[:renamed] = @db.changes
386
427
  end
387
428
 
388
429
  def print_missing_files
389
430
  puts "\nMISSING FILES:"
390
- @missing_files.sort.to_h.each do |filename, digest|
391
- puts filename
431
+ missing_files_select_all_filenames.each do |record|
432
+ puts record["filename"]
392
433
  end
393
434
  end
394
435
 
395
436
  def remove_missing_files
396
- @missing_files.each do |filename, digest|
397
- delete_by_filename filename
398
- end
399
- @missing_files = {}
437
+ digests_delete_all_missing_files
438
+ missing_files_delete_all
439
+ end
440
+
441
+ def missing_files_count
442
+ missing_files_count_query!&.first&.first
443
+ end
444
+
445
+ def any_missing_files?
446
+ missing_files_count > 0
400
447
  end
401
448
 
402
449
 
@@ -407,6 +454,7 @@ class FileDigests
407
454
  end
408
455
 
409
456
  def integrity_check
457
+ puts "Checking database integrity..." if @options[:verbose]
410
458
  if execute("PRAGMA integrity_check")&.first&.fetch("integrity_check") != "ok"
411
459
  raise "Database integrity check failed"
412
460
  end
@@ -461,7 +509,7 @@ class FileDigests
461
509
  end
462
510
 
463
511
  def find_by_filename filename
464
- result = find_by_filename_query filename
512
+ result = digests_find_by_filename_query filename
465
513
  found = result.next
466
514
  raise "Multiple records found" if result.next
467
515
  found
@@ -481,9 +529,37 @@ class FileDigests
481
529
  end
482
530
  end
483
531
 
532
+ def check_if_database_is_at_certain_version target_version
533
+ current_version = get_metadata("database_version")
534
+ if current_version != target_version
535
+ STDERR.puts "This version of file-digests (#{FileDigests::VERSION || "unknown"}) is only compartible with the database version #{target_version}. Current database version is #{current_version}. To use this database, please install appropriate version if file-digest."
536
+ raise "Incompatible database version"
537
+ end
538
+ end
539
+
540
+ def new_files_count
541
+ new_files_count_query!&.first&.first
542
+ end
543
+
484
544
 
485
545
  # Filesystem-related helpers
486
546
 
547
+ def realpath_with_disk path
548
+ path = path.realpath
549
+ if Gem.win_platform? && path.to_s[0] == "/"
550
+ return Pathname(Dir.pwd[0, 2] + path.to_s)
551
+ end
552
+ path
553
+ end
554
+
555
+ def realdirpath_with_disk path
556
+ path = path.realdirpath
557
+ if Gem.win_platform? && path.to_s[0] == "/"
558
+ return Pathname(Dir.pwd[0, 2] + path.to_s)
559
+ end
560
+ path
561
+ end
562
+
487
563
  def patch_path_string path
488
564
  Gem.win_platform? ? path.gsub(/\\/, "/") : path
489
565
  end
@@ -502,10 +578,28 @@ class FileDigests
502
578
  end
503
579
  end
504
580
 
505
- def walk_files
506
- puts "Gathering the list of files..." if @options[:verbose]
507
- Dir.glob(@files_path + "**" + "*", File::FNM_DOTMATCH) do |filename|
508
- yield filename
581
+ def walk_files(path, &block)
582
+ Dir.each_child(path, encoding: "UTF-8") do |item|
583
+ item = "#{path}#{File::SEPARATOR}#{item}"
584
+ test_item = perhaps_nt_path item
585
+ if File.readable?(test_item)
586
+ if File.directory?(test_item)
587
+ walk_files(item, &block)
588
+ else
589
+ yield item
590
+ end
591
+ else
592
+ STDERR.puts "ERROR: Directory entry is not readable: #{item}"
593
+ @counters[:exceptions] += 1
594
+ end
595
+ end
596
+ end
597
+
598
+ def perhaps_nt_path path
599
+ if Gem.win_platform?
600
+ "\\??\\#{path.gsub(/\//,"\\")}"
601
+ else
602
+ path
509
603
  end
510
604
  end
511
605
 
@@ -519,18 +613,13 @@ class FileDigests
519
613
  digest.update(buffer)
520
614
  new_digest.update(buffer) if @new_digest_algorithm
521
615
  end
522
- @new_digests[digest.hexdigest] = new_digest.hexdigest if @new_digest_algorithm
523
- return digest.hexdigest
616
+ return [digest.hexdigest, (new_digest.hexdigest if @new_digest_algorithm)]
524
617
  end
525
618
  end
526
619
 
527
620
 
528
621
  # Runtime state helpers
529
622
 
530
- def any_missing_files?
531
- @missing_files.length > 0
532
- end
533
-
534
623
  def any_exceptions?
535
624
  @counters[:exceptions] > 0
536
625
  end
@@ -544,14 +633,17 @@ class FileDigests
544
633
  def confirm text
545
634
  if STDIN.tty? && STDOUT.tty?
546
635
  puts "#{text} (y/n)?"
547
- STDIN.gets.strip.downcase == "y"
636
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
637
+ result = (STDIN.gets.strip.downcase == "y")
638
+ @user_input_wait_time += (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start)
639
+ result
548
640
  end
549
641
  end
550
642
 
551
643
  def measure_time
552
644
  start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
553
645
  yield
554
- elapsed = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start)
646
+ elapsed = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) - @user_input_wait_time
555
647
  puts "Elapsed time: #{elapsed.to_i / 3600}h #{(elapsed.to_i % 3600) / 60}m #{"%.3f" % (elapsed % 60)}s" unless @options[:quiet]
556
648
  end
557
649
 
@@ -569,11 +661,14 @@ class FileDigests
569
661
  end
570
662
 
571
663
  def print_counters
664
+ missing_files_count_result = missing_files_count
665
+ new_files_count_result = new_files_count - @counters[:renamed]
666
+
572
667
  puts "#{@counters[:good]} file(s) passes digest check" if @counters[:good] > 0
573
668
  puts "#{@counters[:updated]} file(s) are updated" if @counters[:updated] > 0
574
- puts "#{@counters[:new]} file(s) are new" if @counters[:new] > 0
669
+ puts "#{new_files_count_result} file(s) are new" if new_files_count_result > 0
575
670
  puts "#{@counters[:renamed]} file(s) are renamed" if @counters[:renamed] > 0
576
- puts "#{@missing_files.length} file(s) are missing" if @missing_files.length > 0
671
+ puts "#{missing_files_count_result} file(s) are missing" if missing_files_count_result > 0
577
672
  puts "#{@counters[:likely_damaged]} file(s) are likely damaged (!)" if @counters[:likely_damaged] > 0
578
673
  puts "#{@counters[:exceptions]} file(s) had exceptions occured during processing (!)" if @counters[:exceptions] > 0
579
674
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: file-digests
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.33
4
+ version: 0.0.38
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stanislav Senotrusov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-15 00:00:00.000000000 Z
11
+ date: 2020-10-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: openssl