fingerprint 1.3.3 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|