file-digests 0.0.32 → 0.0.37
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/file-digests +14 -0
- data/lib/file-digests.rb +161 -97
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 27e992f2a4849569d6c87e53807ebdc53676b3e47ca2c1efd2799927fd16d0c7
|
4
|
+
data.tar.gz: 9e292709b7978d906b0423a980cc72b23f8a44c665f070d71f30953ccdc59256
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c80f844d16255d9437c8dd012eff010df23f5a14d87336bf987b34e913a12fa0932fd85e18760884c63d10f882041afecf77b0b354555cef62aadc98de5ba091
|
7
|
+
data.tar.gz: f5f5c8309b8921f034edeb36643cab34dff52e4091cd5508462294a48e79002dfdbdf09a126988449d1775d207651579ca19ffb7cd169038fe71067dabb5b8af
|
data/bin/file-digests
CHANGED
@@ -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
|
data/lib/file-digests.rb
CHANGED
@@ -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
|
-
|
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)
|
@@ -75,7 +90,12 @@ class FileDigests
|
|
75
90
|
options[:quiet] = true
|
76
91
|
end
|
77
92
|
|
78
|
-
opts.on(
|
93
|
+
opts.on(
|
94
|
+
"-t", "--test",
|
95
|
+
"Perform a test to verify directory contents.",
|
96
|
+
"Compare actual files with the stored digests, check if any files are missing.",
|
97
|
+
"Digest database will not be modified."
|
98
|
+
) do
|
79
99
|
options[:test_only] = true
|
80
100
|
end
|
81
101
|
|
@@ -96,6 +116,7 @@ class FileDigests
|
|
96
116
|
|
97
117
|
def initialize files_path, digest_database_path, options = {}
|
98
118
|
@options = options
|
119
|
+
@user_input_wait_time = 0
|
99
120
|
|
100
121
|
initialize_paths files_path, digest_database_path
|
101
122
|
initialize_database
|
@@ -139,8 +160,6 @@ class FileDigests
|
|
139
160
|
@db.results_as_hash = true
|
140
161
|
@db.busy_timeout = 5000
|
141
162
|
|
142
|
-
file_digests_gem_version = Gem.loaded_specs["file-digests"]&.version&.to_s
|
143
|
-
|
144
163
|
execute "PRAGMA encoding = 'UTF-8'"
|
145
164
|
execute "PRAGMA locking_mode = 'EXCLUSIVE'"
|
146
165
|
execute "PRAGMA journal_mode = 'WAL'"
|
@@ -155,14 +174,13 @@ class FileDigests
|
|
155
174
|
execute "CREATE TABLE metadata (
|
156
175
|
key TEXT NOT NULL PRIMARY KEY,
|
157
176
|
value TEXT)"
|
158
|
-
execute "CREATE UNIQUE INDEX metadata_key ON metadata(key)"
|
159
177
|
metadata_table_was_created = true
|
160
178
|
end
|
161
179
|
|
162
180
|
prepare_method :set_metadata_query, "INSERT INTO metadata (key, value) VALUES (?, ?) ON CONFLICT (key) DO UPDATE SET value=excluded.value"
|
163
181
|
prepare_method :get_metadata_query, "SELECT value FROM metadata WHERE key = ?"
|
164
182
|
|
165
|
-
set_metadata("metadata_table_created_by_gem_version",
|
183
|
+
set_metadata("metadata_table_created_by_gem_version", FileDigests::VERSION) if FileDigests::VERSION && metadata_table_was_created
|
166
184
|
|
167
185
|
# Heuristic to detect database version 1 (metadata was not stored back then)
|
168
186
|
unless get_metadata("database_version")
|
@@ -179,20 +197,19 @@ class FileDigests
|
|
179
197
|
digest TEXT NOT NULL,
|
180
198
|
digest_check_time TEXT NOT NULL)"
|
181
199
|
execute "CREATE UNIQUE INDEX digests_filename ON digests(filename)"
|
182
|
-
|
200
|
+
execute "CREATE INDEX digests_digest ON digests(digest)"
|
201
|
+
set_metadata("digests_table_created_by_gem_version", FileDigests::VERSION) if FileDigests::VERSION
|
183
202
|
end
|
184
203
|
|
185
|
-
prepare_method :
|
186
|
-
prepare_method :
|
187
|
-
prepare_method :
|
188
|
-
prepare_method :
|
189
|
-
prepare_method :
|
190
|
-
prepare_method :
|
191
|
-
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;"
|
192
|
-
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;"
|
193
210
|
|
194
211
|
unless get_metadata("database_version")
|
195
|
-
set_metadata "database_version", "
|
212
|
+
set_metadata "database_version", "3"
|
196
213
|
end
|
197
214
|
|
198
215
|
# Convert database from 1st to 2nd version
|
@@ -207,79 +224,110 @@ class FileDigests
|
|
207
224
|
end
|
208
225
|
end
|
209
226
|
|
210
|
-
if get_metadata("database_version")
|
211
|
-
|
212
|
-
|
227
|
+
if get_metadata("database_version") == "2"
|
228
|
+
execute "CREATE INDEX digests_digest ON digests(digest)"
|
229
|
+
set_metadata "database_version", "3"
|
213
230
|
end
|
231
|
+
|
232
|
+
check_if_database_is_at_certain_version "3"
|
233
|
+
|
234
|
+
create_temporary_tables
|
214
235
|
end
|
215
236
|
end
|
216
237
|
|
217
|
-
def
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
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)"
|
251
|
+
|
252
|
+
execute "INSERT INTO missing_files (filename, digest) SELECT filename, digest FROM digests"
|
253
|
+
|
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
|
222
270
|
|
223
|
-
|
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}
|
224
275
|
|
225
|
-
measure_time do
|
226
276
|
walk_files do |filename|
|
227
277
|
process_file filename
|
228
278
|
end
|
229
|
-
end
|
230
279
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
280
|
+
nested_transaction do
|
281
|
+
puts "Tracking renames..." if @options[:verbose]
|
282
|
+
track_renames
|
283
|
+
end
|
235
284
|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
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
|
245
295
|
end
|
246
296
|
end
|
247
297
|
end
|
248
|
-
end
|
249
298
|
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
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}"
|
257
307
|
end
|
258
|
-
set_metadata "digest_algorithm", @new_digest_algorithm
|
259
|
-
puts "Transition to a new digest algorithm complete: #{@new_digest_algorithm}"
|
260
308
|
end
|
261
|
-
end
|
262
309
|
|
263
|
-
|
264
|
-
|
265
|
-
|
310
|
+
if any_likely_damaged? || any_exceptions?
|
311
|
+
STDERR.puts "PLEASE REVIEW ERRORS THAT WERE OCCURRED!"
|
312
|
+
end
|
266
313
|
|
267
|
-
|
314
|
+
set_metadata(@options[:test_only] ? "latest_test_only_check_time" : "latest_complete_check_time", time_to_database(Time.now))
|
268
315
|
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
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)"
|
276
323
|
|
277
|
-
|
324
|
+
hide_database_files
|
325
|
+
end
|
278
326
|
end
|
279
327
|
|
280
328
|
def show_duplicates
|
281
329
|
current_digest = nil
|
282
|
-
|
330
|
+
digests_select_duplicates.each do |found|
|
283
331
|
if current_digest != found["digest"]
|
284
332
|
puts "" if current_digest
|
285
333
|
current_digest = found["digest"]
|
@@ -311,9 +359,10 @@ class FileDigests
|
|
311
359
|
|
312
360
|
normalized_filename = filename.delete_prefix("#{@files_path.to_s}/").encode("utf-8", universal_newline: true).unicode_normalize(:nfkc)
|
313
361
|
mtime_string = time_to_database stat.mtime
|
314
|
-
digest = get_file_digest(filename)
|
362
|
+
digest, new_digest = get_file_digest(filename)
|
315
363
|
|
316
364
|
nested_transaction do
|
365
|
+
new_digests_insert(normalized_filename, new_digest) if new_digest
|
317
366
|
process_file_indeed normalized_filename, mtime_string, digest
|
318
367
|
end
|
319
368
|
|
@@ -331,15 +380,15 @@ class FileDigests
|
|
331
380
|
end
|
332
381
|
|
333
382
|
def process_previously_seen_file found, filename, mtime, digest
|
334
|
-
|
383
|
+
missing_files_delete filename
|
335
384
|
if found["digest"] == digest
|
336
385
|
@counters[:good] += 1
|
337
386
|
puts "GOOD: #{filename}" if @options[:verbose]
|
338
387
|
unless @options[:test_only]
|
339
388
|
if found["mtime"] == mtime
|
340
|
-
|
389
|
+
digests_touch_check_time found["id"]
|
341
390
|
else
|
342
|
-
|
391
|
+
digests_update_mtime mtime, found["id"]
|
343
392
|
end
|
344
393
|
end
|
345
394
|
else
|
@@ -350,18 +399,17 @@ class FileDigests
|
|
350
399
|
@counters[:updated] += 1
|
351
400
|
puts "UPDATED#{" (FATE ACCEPTED)" if found["mtime"] == mtime && @options[:accept_fate]}: #{filename}" unless @options[:quiet]
|
352
401
|
unless @options[:test_only]
|
353
|
-
|
402
|
+
digests_update_mtime_and_digest mtime, digest, found["id"]
|
354
403
|
end
|
355
404
|
end
|
356
405
|
end
|
357
406
|
end
|
358
407
|
|
359
408
|
def process_new_file filename, mtime, digest
|
360
|
-
@counters[:new] += 1
|
361
409
|
puts "NEW: #{filename}" unless @options[:quiet]
|
410
|
+
new_files_insert filename, digest
|
362
411
|
unless @options[:test_only]
|
363
|
-
|
364
|
-
insert filename, mtime, digest
|
412
|
+
digests_insert filename, mtime, digest
|
365
413
|
end
|
366
414
|
end
|
367
415
|
|
@@ -369,29 +417,31 @@ class FileDigests
|
|
369
417
|
# Renames and missing files
|
370
418
|
|
371
419
|
def track_renames
|
372
|
-
@
|
373
|
-
|
374
|
-
@counters[:renamed] += 1
|
375
|
-
unless @options[:test_only]
|
376
|
-
delete_by_filename filename
|
377
|
-
end
|
378
|
-
true
|
379
|
-
end
|
420
|
+
unless @options[:test_only]
|
421
|
+
digests_delete_renamed_files
|
380
422
|
end
|
423
|
+
missing_files_delete_renamed_files
|
424
|
+
@counters[:renamed] = @db.changes
|
381
425
|
end
|
382
426
|
|
383
427
|
def print_missing_files
|
384
428
|
puts "\nMISSING FILES:"
|
385
|
-
|
386
|
-
puts filename
|
429
|
+
missing_files_select_all_filenames.each do |record|
|
430
|
+
puts record["filename"]
|
387
431
|
end
|
388
432
|
end
|
389
433
|
|
390
434
|
def remove_missing_files
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
435
|
+
digests_delete_all_missing_files
|
436
|
+
missing_files_delete_all
|
437
|
+
end
|
438
|
+
|
439
|
+
def missing_files_count
|
440
|
+
missing_files_count_query!&.first&.first
|
441
|
+
end
|
442
|
+
|
443
|
+
def any_missing_files?
|
444
|
+
missing_files_count > 0
|
395
445
|
end
|
396
446
|
|
397
447
|
|
@@ -402,6 +452,7 @@ class FileDigests
|
|
402
452
|
end
|
403
453
|
|
404
454
|
def integrity_check
|
455
|
+
puts "Checking database integrity..." if @options[:verbose]
|
405
456
|
if execute("PRAGMA integrity_check")&.first&.fetch("integrity_check") != "ok"
|
406
457
|
raise "Database integrity check failed"
|
407
458
|
end
|
@@ -456,7 +507,7 @@ class FileDigests
|
|
456
507
|
end
|
457
508
|
|
458
509
|
def find_by_filename filename
|
459
|
-
result =
|
510
|
+
result = digests_find_by_filename_query filename
|
460
511
|
found = result.next
|
461
512
|
raise "Multiple records found" if result.next
|
462
513
|
found
|
@@ -476,6 +527,18 @@ class FileDigests
|
|
476
527
|
end
|
477
528
|
end
|
478
529
|
|
530
|
+
def check_if_database_is_at_certain_version target_version
|
531
|
+
current_version = get_metadata("database_version")
|
532
|
+
if current_version != target_version
|
533
|
+
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."
|
534
|
+
raise "Incompatible database version"
|
535
|
+
end
|
536
|
+
end
|
537
|
+
|
538
|
+
def new_files_count
|
539
|
+
new_files_count_query!&.first&.first
|
540
|
+
end
|
541
|
+
|
479
542
|
|
480
543
|
# Filesystem-related helpers
|
481
544
|
|
@@ -514,18 +577,13 @@ class FileDigests
|
|
514
577
|
digest.update(buffer)
|
515
578
|
new_digest.update(buffer) if @new_digest_algorithm
|
516
579
|
end
|
517
|
-
|
518
|
-
return digest.hexdigest
|
580
|
+
return [digest.hexdigest, (new_digest.hexdigest if @new_digest_algorithm)]
|
519
581
|
end
|
520
582
|
end
|
521
583
|
|
522
584
|
|
523
585
|
# Runtime state helpers
|
524
586
|
|
525
|
-
def any_missing_files?
|
526
|
-
@missing_files.length > 0
|
527
|
-
end
|
528
|
-
|
529
587
|
def any_exceptions?
|
530
588
|
@counters[:exceptions] > 0
|
531
589
|
end
|
@@ -539,14 +597,17 @@ class FileDigests
|
|
539
597
|
def confirm text
|
540
598
|
if STDIN.tty? && STDOUT.tty?
|
541
599
|
puts "#{text} (y/n)?"
|
542
|
-
|
600
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
601
|
+
result = (STDIN.gets.strip.downcase == "y")
|
602
|
+
@user_input_wait_time += (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start)
|
603
|
+
result
|
543
604
|
end
|
544
605
|
end
|
545
606
|
|
546
607
|
def measure_time
|
547
608
|
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
548
609
|
yield
|
549
|
-
elapsed = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start)
|
610
|
+
elapsed = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) - @user_input_wait_time
|
550
611
|
puts "Elapsed time: #{elapsed.to_i / 3600}h #{(elapsed.to_i % 3600) / 60}m #{"%.3f" % (elapsed % 60)}s" unless @options[:quiet]
|
551
612
|
end
|
552
613
|
|
@@ -564,11 +625,14 @@ class FileDigests
|
|
564
625
|
end
|
565
626
|
|
566
627
|
def print_counters
|
628
|
+
missing_files_count_result = missing_files_count
|
629
|
+
new_files_count_result = new_files_count - @counters[:renamed]
|
630
|
+
|
567
631
|
puts "#{@counters[:good]} file(s) passes digest check" if @counters[:good] > 0
|
568
632
|
puts "#{@counters[:updated]} file(s) are updated" if @counters[:updated] > 0
|
569
|
-
puts "#{
|
633
|
+
puts "#{new_files_count_result} file(s) are new" if new_files_count_result > 0
|
570
634
|
puts "#{@counters[:renamed]} file(s) are renamed" if @counters[:renamed] > 0
|
571
|
-
puts "#{
|
635
|
+
puts "#{missing_files_count_result} file(s) are missing" if missing_files_count_result > 0
|
572
636
|
puts "#{@counters[:likely_damaged]} file(s) are likely damaged (!)" if @counters[:likely_damaged] > 0
|
573
637
|
puts "#{@counters[:exceptions]} file(s) had exceptions occured during processing (!)" if @counters[:exceptions] > 0
|
574
638
|
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.
|
4
|
+
version: 0.0.37
|
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-
|
11
|
+
date: 2020-10-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: openssl
|