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.
- checksums.yaml +4 -4
- data/bin/file-digests +14 -0
- data/lib/file-digests.rb +200 -105
- 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: 1a7193a9795eb785cd22a7663fa4dae908d3349b854be8d3613750f24ce6b711
|
4
|
+
data.tar.gz: 32a8172cc7115240f5954b7e4775f841ddce10102c7e48e184690d2643e7a476
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 584a6fc4c10e6a33d9e55d9647abcfce913fa18b046eddf7193807408d7ac4167b1b7114aa300531426e767668841f483ad66775bf7f492ff552f290d19f6e2a
|
7
|
+
data.tar.gz: 877dea6a6ba8f3ab091ed6d5b0379090a60cff4477ad9d4409fa9fef9674bd39b9134682ac534d49535a21ed58382b02088fa00f83524d31add7c23919e9c0c4
|
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)
|
@@ -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",
|
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
|
-
|
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 :
|
191
|
-
prepare_method :
|
192
|
-
prepare_method :
|
193
|
-
prepare_method :
|
194
|
-
prepare_method :
|
195
|
-
prepare_method :
|
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", "
|
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")
|
216
|
-
|
217
|
-
|
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
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
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
|
-
|
252
|
+
execute "INSERT INTO missing_files (filename, digest) SELECT filename, digest FROM digests"
|
229
253
|
|
230
|
-
|
231
|
-
|
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
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
280
|
+
nested_transaction do
|
281
|
+
puts "Tracking renames..." if @options[:verbose]
|
282
|
+
track_renames
|
283
|
+
end
|
240
284
|
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
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
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
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
|
-
|
269
|
-
|
270
|
-
|
310
|
+
if any_likely_damaged? || any_exceptions?
|
311
|
+
STDERR.puts "PLEASE REVIEW ERRORS THAT WERE OCCURRED!"
|
312
|
+
end
|
271
313
|
|
272
|
-
|
314
|
+
set_metadata(@options[:test_only] ? "latest_test_only_check_time" : "latest_complete_check_time", time_to_database(Time.now))
|
273
315
|
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
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
|
-
|
324
|
+
hide_database_files
|
325
|
+
end
|
283
326
|
end
|
284
327
|
|
285
328
|
def show_duplicates
|
286
329
|
current_digest = nil
|
287
|
-
|
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
|
-
|
343
|
+
perhaps_nt_filename = perhaps_nt_path filename
|
344
|
+
|
345
|
+
return if File.symlink? perhaps_nt_filename
|
301
346
|
|
302
|
-
stat = File.stat
|
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(
|
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
|
-
|
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
|
-
|
391
|
+
digests_touch_check_time found["id"]
|
346
392
|
else
|
347
|
-
|
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
|
-
|
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
|
-
|
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
|
-
@
|
378
|
-
|
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
|
-
|
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
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
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 =
|
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
|
-
|
507
|
-
|
508
|
-
|
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
|
-
|
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
|
-
|
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 "#{
|
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 "#{
|
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.
|
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-
|
11
|
+
date: 2020-10-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: openssl
|