fingerprint 1.3.3 → 1.4.0
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.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/README.md +33 -12
- data/bin/fingerprint +76 -112
- data/bin/fingerprint-diff +145 -0
- data/fingerprint.gemspec +27 -0
- data/lib/fingerprint.rb +3 -2
- data/lib/fingerprint/checker.rb +5 -6
- data/lib/fingerprint/record.rb +22 -14
- data/lib/fingerprint/scanner.rb +8 -2
- data/lib/fingerprint/version.rb +1 -7
- data/rakefile.rb +9 -0
- data/test/test_fingerprint.rb +61 -0
- metadata +85 -62
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2c6867e9566430e12dc2d913a013c469e3b2bc47
|
4
|
+
data.tar.gz: 58d9a1812dcd041743816328ac3e71fc79496014
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ac0bfcca4b51a31797849aa039c795626f78d7a5d0b5278aadbb9872b635d7ca3cd369b3cd08de77c48545ca58af4aad83ede79e99749b56e896a03b8e238e5e
|
7
|
+
data.tar.gz: 76f51cf43136114a35cafb99958da8e7099c98ee68a931efa7a1b853e76dc9025a2bedd6df697ebf69b7c7f47004f3616865514beea9c980d8a9e4798f7eec58
|
data/Gemfile
ADDED
data/README.md
CHANGED
@@ -1,9 +1,4 @@
|
|
1
|
-
Fingerprint
|
2
|
-
===========
|
3
|
-
|
4
|
-
* Author: Samuel G. D. Williams (<http://www.oriontransfer.co.nz>)
|
5
|
-
* Copyright (C) 2010, 2011 Samuel G. D. Williams.
|
6
|
-
* Released under the MIT license.
|
1
|
+
# Fingerprint
|
7
2
|
|
8
3
|
Fingerprint is primarily a command line tool to compare directory structures on
|
9
4
|
disk. It also provides a programmatic interface for this procedure.
|
@@ -14,10 +9,27 @@ used to check the integrity of a remote backup.
|
|
14
9
|
|
15
10
|
For examples and documentation please see the main [project page][1].
|
16
11
|
|
17
|
-
[1]: http://www.
|
12
|
+
[1]: http://www.codeotaku.com/projects/fingerprint/index
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
Add this line to your application's Gemfile:
|
17
|
+
|
18
|
+
gem 'fingerprint'
|
19
|
+
|
20
|
+
And then execute:
|
21
|
+
|
22
|
+
$ bundle
|
23
|
+
|
24
|
+
Or install it yourself as:
|
18
25
|
|
19
|
-
|
20
|
-
|
26
|
+
$ gem install fingerprint
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
Please refer to the [online documentation][http://www.codeotaku.com/projects/fingerprint/documentation/introduction].
|
31
|
+
|
32
|
+
## Todo
|
21
33
|
|
22
34
|
* Command line option to show files that have been created (e.g. don't exist in master fingerprint).
|
23
35
|
* Command line option to show files that have changed but have the same modified time (hardware corrutpion).
|
@@ -28,10 +40,19 @@ Todo
|
|
28
40
|
* Support general filenames for `--archive`, e.g. along with `-n`, maybe support a file called `index.fingerprint` by default: improved visibility for end user.
|
29
41
|
* Because fingerprint is currently IO bound in terms of performance, single-threaded checksumming is fine, but for SSD and other fast storage, it might be possible to improve speed somewhat by using a map-reduce style approach.
|
30
42
|
|
31
|
-
|
32
|
-
|
43
|
+
## Contributing
|
44
|
+
|
45
|
+
1. Fork it
|
46
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
47
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
48
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
49
|
+
5. Create new Pull Request
|
50
|
+
|
51
|
+
## License
|
52
|
+
|
53
|
+
Released under the MIT license.
|
33
54
|
|
34
|
-
Copyright
|
55
|
+
Copyright, 2012, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
|
35
56
|
|
36
57
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
37
58
|
of this software and associated documentation files (the "Software"), to deal
|
data/bin/fingerprint
CHANGED
@@ -23,7 +23,6 @@
|
|
23
23
|
require 'optparse'
|
24
24
|
require 'pathname'
|
25
25
|
require 'fingerprint'
|
26
|
-
require 'lockfile'
|
27
26
|
require 'fileutils'
|
28
27
|
|
29
28
|
OPTIONS = {
|
@@ -35,9 +34,8 @@ OPTIONS = {
|
|
35
34
|
:name => "index.fingerprint",
|
36
35
|
:extended => false,
|
37
36
|
:checksums => Fingerprint::DEFAULT_CHECKSUMS,
|
38
|
-
:lockfile => true,
|
39
37
|
:additions => false,
|
40
|
-
:
|
38
|
+
:failures => :ignore,
|
41
39
|
}
|
42
40
|
|
43
41
|
ARGV.options do |o|
|
@@ -103,19 +101,15 @@ ARGV.options do |o|
|
|
103
101
|
OPTIONS[:progress] = true
|
104
102
|
end
|
105
103
|
|
106
|
-
o.on("--
|
107
|
-
OPTIONS[:
|
108
|
-
end
|
109
|
-
|
110
|
-
o.on("-X", "Give a non-zero exit code if errors are detected by check or verify.") do
|
111
|
-
OPTIONS[:failures_exit_status] = 1
|
104
|
+
o.on("--die", "Give a non-zero exit code if errors are detected by check or verify.") do
|
105
|
+
OPTIONS[:failures] = :die
|
112
106
|
end
|
113
107
|
|
114
108
|
o.separator ""
|
115
109
|
o.separator "Help and Copyright information:"
|
116
110
|
|
117
111
|
o.on_tail("--copy", "Display copyright and warranty information") do
|
118
|
-
$stderr.puts "#{script_name} v#{Fingerprint::VERSION
|
112
|
+
$stderr.puts "#{script_name} v#{Fingerprint::VERSION}. Copyright (c) 2011 Samuel Williams."
|
119
113
|
$stderr.puts "This software is released under the MIT license and comes with ABSOLUTELY NO WARRANTY."
|
120
114
|
$stderr.puts "See http://www.oriontransfer.co.nz/ for more information."
|
121
115
|
exit
|
@@ -136,121 +130,91 @@ if OPTIONS[:checksums].size == 0
|
|
136
130
|
OPTIONS[:checksums] = ['MD5', 'SHA2.256']
|
137
131
|
end
|
138
132
|
|
139
|
-
# Run some block with the given lock, if requested.
|
140
|
-
def with_lock
|
141
|
-
lockfile_path = nil
|
142
|
-
if OPTIONS[:lockfile] == true
|
143
|
-
lockfile_path = Pathname.new(OPTIONS[:root]) + (OPTIONS[:name] + ".lock")
|
144
|
-
elsif OPTION[:lockfile] != false
|
145
|
-
lockfile_path = OPTIONS[:lockfile]
|
146
|
-
end
|
147
|
-
|
148
|
-
if lockfile_path
|
149
|
-
begin
|
150
|
-
Lockfile.new(lockfile_path, :retries => 0) do
|
151
|
-
yield
|
152
|
-
end
|
153
|
-
rescue Lockfile::MaxTriesLockError
|
154
|
-
$stderr.puts "Could not acquire lock #{lockfile_path}."
|
155
|
-
|
156
|
-
return false
|
157
|
-
end
|
158
|
-
else
|
159
|
-
yield
|
160
|
-
end
|
161
|
-
|
162
|
-
return true
|
163
|
-
end
|
164
|
-
|
165
133
|
def finish_check(error_count)
|
166
134
|
if error_count == 0
|
167
135
|
$stderr.puts "Data verified, 0 errors found."
|
168
136
|
exit(0)
|
169
137
|
else
|
170
138
|
$stderr.puts "Data inconsistent, #{error_count} errors found!"
|
171
|
-
exit(OPTIONS[:
|
139
|
+
exit(OPTIONS[:failures] == :die ? 1 : 0)
|
172
140
|
end
|
173
141
|
end
|
174
142
|
|
175
143
|
case (OPTIONS[:mode])
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
Fingerprint::Scanner.scan_paths([OPTIONS[:root]], options)
|
194
|
-
end
|
195
|
-
finished = true
|
196
|
-
ensure
|
197
|
-
FileUtils.rm(output_file) unless finished
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
unless result
|
202
|
-
# Lockfile failure
|
203
|
-
exit(4)
|
204
|
-
end
|
205
|
-
when :verify
|
206
|
-
error_count = 0
|
207
|
-
|
208
|
-
input_file = Pathname.new(OPTIONS[:root]) + OPTIONS[:name]
|
209
|
-
|
210
|
-
unless File.exist? input_file
|
211
|
-
$stderr.puts "Can't find index #{input_file}. Aborting."
|
212
|
-
exit(3)
|
213
|
-
end
|
214
|
-
|
215
|
-
options = OPTIONS.dup
|
216
|
-
|
217
|
-
master = Fingerprint::Recordset.new
|
218
|
-
|
219
|
-
File.open(input_file, "r") do |io|
|
220
|
-
master.parse(io)
|
221
|
-
end
|
222
|
-
|
223
|
-
if master.configuration
|
224
|
-
options.merge!(master.configuration.options)
|
144
|
+
when :analyze
|
145
|
+
output_file = Pathname.new(OPTIONS[:root]) + OPTIONS[:name]
|
146
|
+
|
147
|
+
if output_file.exist? && !OPTIONS[:force]
|
148
|
+
$stderr.puts "Output file #{output_file} already exists. Aborting."
|
149
|
+
exit(2)
|
150
|
+
end
|
151
|
+
|
152
|
+
options = OPTIONS.dup
|
153
|
+
options[:excludes] = [OPTIONS[:name]]
|
154
|
+
|
155
|
+
finished = false
|
156
|
+
begin
|
157
|
+
File.open(output_file, "w") do |io|
|
158
|
+
options[:output] = io
|
159
|
+
|
160
|
+
Fingerprint::Scanner.scan_paths([OPTIONS[:root]], options)
|
225
161
|
end
|
162
|
+
finished = true
|
163
|
+
ensure
|
164
|
+
FileUtils.rm(output_file) unless finished
|
165
|
+
end
|
166
|
+
when :verify
|
167
|
+
error_count = 0
|
168
|
+
|
169
|
+
input_file = Pathname.new(OPTIONS[:root]) + OPTIONS[:name]
|
170
|
+
|
171
|
+
unless File.exist? input_file
|
172
|
+
$stderr.puts "Can't find index #{input_file}. Aborting."
|
173
|
+
exit(3)
|
174
|
+
end
|
175
|
+
|
176
|
+
options = OPTIONS.dup
|
226
177
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
178
|
+
master = Fingerprint::RecordSet.new
|
179
|
+
|
180
|
+
File.open(input_file, "r") do |io|
|
181
|
+
master.parse(io)
|
182
|
+
end
|
183
|
+
|
184
|
+
if master.configuration
|
185
|
+
options.merge!(master.configuration.options)
|
186
|
+
end
|
187
|
+
|
188
|
+
scanner = Fingerprint::Scanner.new([OPTIONS[:root]], options)
|
189
|
+
copy = Fingerprint::SparseRecordSet.new(scanner)
|
190
|
+
|
191
|
+
error_count += Fingerprint::Checker::verify(master, copy, options)
|
192
|
+
|
193
|
+
finish_check(error_count)
|
194
|
+
when :scan
|
195
|
+
roots = ARGV
|
196
|
+
roots << Dir.pwd if roots.size == 0
|
197
|
+
|
198
|
+
# Check that all supplied paths exist
|
199
|
+
roots.delete_if do |root|
|
200
|
+
if File.exist? root
|
201
|
+
false
|
202
|
+
else
|
203
|
+
$stderr.puts "Path #{root} doesn't exist, skipping!"
|
204
|
+
true
|
245
205
|
end
|
206
|
+
end
|
207
|
+
|
208
|
+
options = OPTIONS.dup
|
209
|
+
|
210
|
+
# Discard output once it has been written to disk:
|
211
|
+
options[:recordset] = nil
|
212
|
+
|
213
|
+
Fingerprint::Scanner.scan_paths(roots, options)
|
214
|
+
when :check
|
215
|
+
options = OPTIONS.dup
|
246
216
|
|
247
|
-
|
217
|
+
error_count = Fingerprint::Checker.check_files(ARGV[0], ARGV[1], options)
|
248
218
|
|
249
|
-
|
250
|
-
when :check
|
251
|
-
options = OPTIONS.dup
|
252
|
-
|
253
|
-
error_count = Fingerprint::Checker.check_files(ARGV[0], ARGV[1], options)
|
254
|
-
|
255
|
-
finish_check(error_count)
|
219
|
+
finish_check(error_count)
|
256
220
|
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
|
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
|
13
|
+
# all 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 NONINFRINGEMENT. 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
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'optparse'
|
24
|
+
require 'pathname'
|
25
|
+
require 'fingerprint'
|
26
|
+
require 'lockfile'
|
27
|
+
require 'fileutils'
|
28
|
+
|
29
|
+
OPTIONS = {
|
30
|
+
:mode => nil,
|
31
|
+
:output => $stdout,
|
32
|
+
:verbose => false,
|
33
|
+
:force => false,
|
34
|
+
:extended => false,
|
35
|
+
:inverse => false
|
36
|
+
}
|
37
|
+
|
38
|
+
$options = nil
|
39
|
+
|
40
|
+
ARGV.options do |o|
|
41
|
+
$options = o
|
42
|
+
|
43
|
+
script_name = File.basename($0)
|
44
|
+
|
45
|
+
o.banner = "Usage: #{script_name} [options] [path]"
|
46
|
+
o.define_head "This script is used to calculate the differences between two fingerprints."
|
47
|
+
|
48
|
+
o.separator ""
|
49
|
+
o.separator "Set operations between fingerprints:"
|
50
|
+
|
51
|
+
o.on("--duplicates", "Calculate files in copy that already exist in master") do |master, copy|
|
52
|
+
OPTIONS[:mode] = :duplicates
|
53
|
+
end
|
54
|
+
|
55
|
+
o.separator ""
|
56
|
+
o.separator "Output manipulation:"
|
57
|
+
|
58
|
+
o.on("-o [output-path]", String, "Write the fingerprint output to the given file.") do |path|
|
59
|
+
OPTIONS[:output] = File.open(path, "w")
|
60
|
+
end
|
61
|
+
|
62
|
+
o.on("--verbose", "Verbose output, include additional details in the file transcript.") do
|
63
|
+
OPTIONS[:verbose] = true
|
64
|
+
end
|
65
|
+
|
66
|
+
o.on("--progress", "Print percentage progress to standard error.") do
|
67
|
+
OPTIONS[:progress] = true
|
68
|
+
end
|
69
|
+
|
70
|
+
o.on("--inverse", "Show the inverse of the selected operation.") do
|
71
|
+
OPTIONS[:inverse] = true
|
72
|
+
end
|
73
|
+
|
74
|
+
o.separator ""
|
75
|
+
o.separator "Help and Copyright information:"
|
76
|
+
|
77
|
+
o.on_tail("--copy", "Display copyright and warranty information") do
|
78
|
+
$stderr.puts "#{script_name} v#{Fingerprint::VERSION::STRING}. Copyright (c) 2011 Samuel Williams."
|
79
|
+
$stderr.puts "This software is released under the MIT license and comes with ABSOLUTELY NO WARRANTY."
|
80
|
+
$stderr.puts "See http://www.oriontransfer.co.nz/ for more information."
|
81
|
+
exit
|
82
|
+
end
|
83
|
+
|
84
|
+
o.on_tail("-h", "--help", "Show this help message.") do
|
85
|
+
$stderr.puts o
|
86
|
+
exit
|
87
|
+
end
|
88
|
+
end.parse!
|
89
|
+
|
90
|
+
case (OPTIONS[:mode])
|
91
|
+
when :duplicates
|
92
|
+
options = OPTIONS.dup
|
93
|
+
|
94
|
+
include Fingerprint
|
95
|
+
|
96
|
+
duplicates_recordset = RecordSet.new
|
97
|
+
results = RecordSetPrinter.new(duplicates_recordset, OPTIONS[:output])
|
98
|
+
|
99
|
+
master_file_path = ARGV.shift
|
100
|
+
File.open(master_file_path) do |master_file|
|
101
|
+
master_recordset = RecordSet.new
|
102
|
+
master_recordset.parse(master_file)
|
103
|
+
|
104
|
+
ignore_similar = false
|
105
|
+
|
106
|
+
copy_file_paths = ARGV
|
107
|
+
if copy_file_paths.size == 0
|
108
|
+
copy_file_paths = [master_file_path]
|
109
|
+
ignore_similar = true
|
110
|
+
end
|
111
|
+
|
112
|
+
copy_file_paths.each do |copy_file_path|
|
113
|
+
File.open(copy_file_path) do |copy_file|
|
114
|
+
copy_recordset = RecordSet.new
|
115
|
+
copy_recordset.parse(copy_file)
|
116
|
+
|
117
|
+
copy_recordset.records.each do |record|
|
118
|
+
record.metadata['fingerprint'] = copy_file_path
|
119
|
+
# We need to see if the record exists in the master
|
120
|
+
|
121
|
+
if OPTIONS[:verbose]
|
122
|
+
$stderr.puts "Checking #{record.inspect}"
|
123
|
+
end
|
124
|
+
|
125
|
+
main_record = master_recordset.find_by_key(record)
|
126
|
+
|
127
|
+
# If we are scanning the same index, don't print out every file, just those that are duplicates within the single file.
|
128
|
+
if ignore_similar && main_record && (main_record.path == record.path)
|
129
|
+
main_record = nil
|
130
|
+
end
|
131
|
+
|
132
|
+
if main_record
|
133
|
+
record.metadata['original.path'] = main_record.path
|
134
|
+
record.metadata['original.fingerprint'] = master_file_path
|
135
|
+
results << record if !OPTIONS[:inverse]
|
136
|
+
else
|
137
|
+
results << record if OPTIONS[:inverse]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
else
|
144
|
+
puts $options
|
145
|
+
end
|
data/fingerprint.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'fingerprint/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "fingerprint"
|
8
|
+
spec.version = Fingerprint::VERSION
|
9
|
+
spec.authors = ["Samuel Williams"]
|
10
|
+
spec.email = ["samuel.williams@oriontransfer.co.nz"]
|
11
|
+
spec.description = <<-EOF
|
12
|
+
Fingerprint is a general purpose data integrity tool that uses cryptographic hashes to detect changes in files and directory trees. The fingerprint command scans a directory tree and generates a fingerprint file containing the names and cryptographic hashes of the files in the tree. This snapshot can be later used to generate a list of files that have been created, deleted or modified. If so much as a single bit in the file data has changed, Fingerprint will detect it.
|
13
|
+
EOF
|
14
|
+
spec.summary = "Fingerprint is a tool for creating checksums of entire directory structures, and comparing them for inconsistencies."
|
15
|
+
spec.homepage = "http://www.codeotaku.com/projects/fingerprint"
|
16
|
+
spec.license = "MIT"
|
17
|
+
|
18
|
+
spec.files = `git ls-files`.split($/)
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
|
26
|
+
spec.add_dependency "lockfile"
|
27
|
+
end
|
data/lib/fingerprint.rb
CHANGED
@@ -18,6 +18,7 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
+
require 'fingerprint/record'
|
21
22
|
require 'fingerprint/version'
|
22
23
|
require 'fingerprint/scanner'
|
23
24
|
require 'fingerprint/checker'
|
@@ -28,8 +29,8 @@ module Fingerprint
|
|
28
29
|
master = Scanner.new([master_path])
|
29
30
|
copy = Scanner.new([copy_path])
|
30
31
|
|
31
|
-
master_recordset =
|
32
|
-
copy_recordset =
|
32
|
+
master_recordset = RecordSet.new
|
33
|
+
copy_recordset = SparseRecordSet.new(copy)
|
33
34
|
|
34
35
|
master.scan(master_recordset)
|
35
36
|
|
data/lib/fingerprint/checker.rb
CHANGED
@@ -45,7 +45,6 @@ module Fingerprint
|
|
45
45
|
def check (&block)
|
46
46
|
# For every file in the src, we check that it exists
|
47
47
|
# in the destination:
|
48
|
-
|
49
48
|
total_count = @master.records.count
|
50
49
|
processed_size = 0
|
51
50
|
total_size = @master.records.inject(0) { |count, record| count + (record['file.size'] || 0).to_i }
|
@@ -97,14 +96,14 @@ module Fingerprint
|
|
97
96
|
attr :failures
|
98
97
|
|
99
98
|
def self.check_files(master, copy, options = {}, &block)
|
100
|
-
# New API that takes two
|
99
|
+
# New API that takes two RecordSets...
|
101
100
|
|
102
101
|
File.open(master) do |master_file|
|
103
102
|
File.open(copy) do |copy_file|
|
104
|
-
master_recordset =
|
103
|
+
master_recordset = RecordSet.new
|
105
104
|
master_recordset.parse(master_file)
|
106
105
|
|
107
|
-
copy_recordset =
|
106
|
+
copy_recordset = RecordSet.new
|
108
107
|
copy_recordset.parse(copy_file)
|
109
108
|
|
110
109
|
verify(master_recordset, copy_recordset, options, &block)
|
@@ -116,9 +115,9 @@ module Fingerprint
|
|
116
115
|
def self.verify(master, copy, options = {}, &block)
|
117
116
|
error_count = 0
|
118
117
|
|
119
|
-
errors = options.delete(:recordset) ||
|
118
|
+
errors = options.delete(:recordset) || RecordSet.new
|
120
119
|
if options[:output]
|
121
|
-
errors =
|
120
|
+
errors = RecordSetPrinter.new(errors, options[:output])
|
122
121
|
end
|
123
122
|
|
124
123
|
checker = Checker.new(master, copy, options)
|
data/lib/fingerprint/record.rb
CHANGED
@@ -83,12 +83,12 @@ module Fingerprint
|
|
83
83
|
end
|
84
84
|
end
|
85
85
|
|
86
|
-
class
|
86
|
+
class RecordSet
|
87
87
|
def initialize
|
88
88
|
@records = []
|
89
89
|
@paths = {}
|
90
90
|
@keys = {}
|
91
|
-
|
91
|
+
|
92
92
|
@configuration = nil
|
93
93
|
|
94
94
|
@callback = nil
|
@@ -107,8 +107,10 @@ module Fingerprint
|
|
107
107
|
@configuration = record
|
108
108
|
else
|
109
109
|
@paths[record.path] = record
|
110
|
-
record.keys.each do |key
|
111
|
-
@keys[key]
|
110
|
+
record.keys.each do |key|
|
111
|
+
@keys[key] ||= {}
|
112
|
+
|
113
|
+
@keys[key][record[key]] = record
|
112
114
|
end
|
113
115
|
end
|
114
116
|
end
|
@@ -117,12 +119,10 @@ module Fingerprint
|
|
117
119
|
return @paths[path]
|
118
120
|
end
|
119
121
|
|
120
|
-
def
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
@record.keys.each do |key, value|
|
122
|
+
def find_by_key(record)
|
123
|
+
record.keys.each do |key|
|
124
|
+
value = record[key]
|
125
|
+
|
126
126
|
result = @keys[key][value]
|
127
127
|
|
128
128
|
return result if result
|
@@ -131,6 +131,14 @@ module Fingerprint
|
|
131
131
|
return nil
|
132
132
|
end
|
133
133
|
|
134
|
+
def find(record)
|
135
|
+
result = lookup(record.path)
|
136
|
+
|
137
|
+
return result if result
|
138
|
+
|
139
|
+
return find_by_key(record)
|
140
|
+
end
|
141
|
+
|
134
142
|
def compare(other)
|
135
143
|
main = lookup(other.path)
|
136
144
|
|
@@ -218,7 +226,7 @@ module Fingerprint
|
|
218
226
|
end
|
219
227
|
|
220
228
|
# This record set dynamically computes data from the disk as required.
|
221
|
-
class
|
229
|
+
class SparseRecordSet < RecordSet
|
222
230
|
def initialize(scanner)
|
223
231
|
super()
|
224
232
|
|
@@ -234,7 +242,7 @@ module Fingerprint
|
|
234
242
|
end
|
235
243
|
end
|
236
244
|
|
237
|
-
class
|
245
|
+
class RecordSetWrapper
|
238
246
|
def initialize(recordset)
|
239
247
|
@recordset = recordset
|
240
248
|
end
|
@@ -248,7 +256,7 @@ module Fingerprint
|
|
248
256
|
end
|
249
257
|
end
|
250
258
|
|
251
|
-
class
|
259
|
+
class RecordSetPrinter < RecordSetWrapper
|
252
260
|
def initialize(recordset, output)
|
253
261
|
super(recordset)
|
254
262
|
@output = output
|
@@ -256,7 +264,7 @@ module Fingerprint
|
|
256
264
|
|
257
265
|
def <<(record)
|
258
266
|
record.write(@output)
|
259
|
-
@recordset << record
|
267
|
+
@recordset << record if @recordset
|
260
268
|
end
|
261
269
|
end
|
262
270
|
end
|
data/lib/fingerprint/scanner.rb
CHANGED
@@ -71,7 +71,7 @@ module Fingerprint
|
|
71
71
|
'options.extended' => @options[:extended] == true,
|
72
72
|
'options.checksums' => @options[:checksums].join(', '),
|
73
73
|
'summary.time.start' => Time.now,
|
74
|
-
'fingerprint.version' => Fingerprint::VERSION
|
74
|
+
'fingerprint.version' => Fingerprint::VERSION
|
75
75
|
})
|
76
76
|
end
|
77
77
|
|
@@ -282,7 +282,13 @@ module Fingerprint
|
|
282
282
|
# A helper function to scan a set of directories.
|
283
283
|
def self.scan_paths(paths, options = {})
|
284
284
|
if options[:output]
|
285
|
-
options
|
285
|
+
if options.key? :recordset
|
286
|
+
recordset = options[:recordset]
|
287
|
+
else
|
288
|
+
recordset = RecordSet.new
|
289
|
+
end
|
290
|
+
|
291
|
+
options[:recordset] = RecordSetPrinter.new(recordset, options[:output])
|
286
292
|
end
|
287
293
|
|
288
294
|
scanner = Scanner.new(paths, options)
|
data/lib/fingerprint/version.rb
CHANGED
data/rakefile.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Copyright (c) 2007, 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
|
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
|
13
|
+
# all 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 NONINFRINGEMENT. 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
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'rubygems'
|
24
|
+
|
25
|
+
require 'test/unit'
|
26
|
+
require 'fileutils'
|
27
|
+
require 'pathname'
|
28
|
+
require 'fingerprint'
|
29
|
+
|
30
|
+
require 'timeout'
|
31
|
+
|
32
|
+
class TestFingerprint < Test::Unit::TestCase
|
33
|
+
def test_analyze_verify
|
34
|
+
File.open("junk.txt", "w") { |fp| fp.write("foobar") }
|
35
|
+
|
36
|
+
result = system("fingerprint --analyze ./ -f")
|
37
|
+
|
38
|
+
File.open("junk.txt", "w") { |fp| fp.write("foobar") }
|
39
|
+
|
40
|
+
result = system("fingerprint --verify ./")
|
41
|
+
|
42
|
+
assert_equal true, result
|
43
|
+
|
44
|
+
File.open("junk.txt", "w") { |fp| fp.write("foobaz") }
|
45
|
+
|
46
|
+
result = system("fingerprint -X --verify ./")
|
47
|
+
|
48
|
+
assert_equal false, result
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_check_paths
|
52
|
+
errors = 0
|
53
|
+
test_path = File.dirname(__FILE__)
|
54
|
+
|
55
|
+
Fingerprint::check_paths(test_path, test_path) do |record, result, message|
|
56
|
+
errors += 1
|
57
|
+
end
|
58
|
+
|
59
|
+
assert_equal errors, 0
|
60
|
+
end
|
61
|
+
end
|
metadata
CHANGED
@@ -1,84 +1,107 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: fingerprint
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
6
|
-
segments:
|
7
|
-
- 1
|
8
|
-
- 3
|
9
|
-
- 3
|
10
|
-
version: 1.3.3
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.4.0
|
11
5
|
platform: ruby
|
12
|
-
authors:
|
6
|
+
authors:
|
13
7
|
- Samuel Williams
|
14
8
|
autorequire:
|
15
9
|
bindir: bin
|
16
10
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
11
|
+
date: 2013-09-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
22
35
|
prerelease: false
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: lockfile
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
32
48
|
type: :runtime
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: "\t\tFingerprint is a general purpose data integrity tool that uses cryptographic
|
56
|
+
hashes to detect changes in files and directory trees. The fingerprint command scans
|
57
|
+
a directory tree and generates a fingerprint file containing the names and cryptographic
|
58
|
+
hashes of the files in the tree. This snapshot can be later used to generate a list
|
59
|
+
of files that have been created, deleted or modified. If so much as a single bit
|
60
|
+
in the file data has changed, Fingerprint will detect it.\n"
|
61
|
+
email:
|
62
|
+
- samuel.williams@oriontransfer.co.nz
|
63
|
+
executables:
|
37
64
|
- fingerprint
|
65
|
+
- fingerprint-diff
|
38
66
|
extensions: []
|
39
|
-
|
40
67
|
extra_rdoc_files: []
|
41
|
-
|
42
|
-
|
68
|
+
files:
|
69
|
+
- Gemfile
|
70
|
+
- README.md
|
43
71
|
- bin/fingerprint
|
72
|
+
- bin/fingerprint-diff
|
73
|
+
- fingerprint.gemspec
|
74
|
+
- lib/fingerprint.rb
|
44
75
|
- lib/fingerprint/checker.rb
|
45
76
|
- lib/fingerprint/record.rb
|
46
77
|
- lib/fingerprint/scanner.rb
|
47
78
|
- lib/fingerprint/version.rb
|
48
|
-
-
|
49
|
-
-
|
50
|
-
homepage: http://www.
|
51
|
-
licenses:
|
52
|
-
|
79
|
+
- rakefile.rb
|
80
|
+
- test/test_fingerprint.rb
|
81
|
+
homepage: http://www.codeotaku.com/projects/fingerprint
|
82
|
+
licenses:
|
83
|
+
- MIT
|
84
|
+
metadata: {}
|
53
85
|
post_install_message:
|
54
86
|
rdoc_options: []
|
55
|
-
|
56
|
-
require_paths:
|
87
|
+
require_paths:
|
57
88
|
- lib
|
58
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
none: false
|
69
|
-
requirements:
|
70
|
-
- - ">="
|
71
|
-
- !ruby/object:Gem::Version
|
72
|
-
hash: 3
|
73
|
-
segments:
|
74
|
-
- 0
|
75
|
-
version: "0"
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
76
99
|
requirements: []
|
77
|
-
|
78
100
|
rubyforge_project:
|
79
|
-
rubygems_version:
|
101
|
+
rubygems_version: 2.0.6
|
80
102
|
signing_key:
|
81
|
-
specification_version:
|
82
|
-
summary: Fingerprint is a tool for creating checksums of entire directory structures,
|
83
|
-
|
84
|
-
|
103
|
+
specification_version: 4
|
104
|
+
summary: Fingerprint is a tool for creating checksums of entire directory structures,
|
105
|
+
and comparing them for inconsistencies.
|
106
|
+
test_files:
|
107
|
+
- test/test_fingerprint.rb
|