fingerprint 1.3.1 → 3.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b8e4286765727a4efa88c5ecf4f84ab5d736606a0adae710c32073da69746465
4
+ data.tar.gz: be66585855747916dd4294736a68cc04d2ff3e1aa49df83d08ec98b4e21bcfee
5
+ SHA512:
6
+ metadata.gz: 8772554e075f2b29016fe859c85d0d7082fc66a77f068b02d33ebc084c15efa20a83baa52d48fad3860b18316d2084f515f28933065fd7f0a7f7c9743c27bd39
7
+ data.tar.gz: 8757e6f7cc91db4a7f80be3485ac61184ed0c8fa9ecaad58f2ba257ff74a2b6d7384888439263d7faafa99d4238712b96ab11ba7e6585d4fe43968a8a6fd7672
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
3
+ # Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
4
  #
5
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  # of this software and associated documentation files (the "Software"), to deal
@@ -20,219 +20,10 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
21
  # THE SOFTWARE.
22
22
 
23
- require 'optparse'
24
- require 'pathname'
25
- require 'fingerprint'
26
- require 'lockfile'
27
- require 'fileutils'
23
+ # This script takes a given path, and renames it with the given format.
24
+ # It then ensures that there is a symlink called "latest" that points
25
+ # to the renamed directory.
28
26
 
29
- OPTIONS = {
30
- :root => "./",
31
- :mode => :scan,
32
- :output => $stdout,
33
- :verbose => false,
34
- :force => false,
35
- :name => "index.fingerprint",
36
- :extended => false,
37
- :checksums => Fingerprint::DEFAULT_CHECKSUMS,
38
- :lockfile => true
39
- }
27
+ require_relative '../lib/fingerprint/command'
40
28
 
41
- ARGV.options do |o|
42
- script_name = File.basename($0)
43
-
44
- o.banner = "Usage: #{script_name} [options] [path]"
45
- o.define_head "This script is used to create and compare file system fingerprints."
46
-
47
- o.separator ""
48
- o.separator "Directory analysis and verification:"
49
-
50
- o.on("-a", "--analyze [path]", String, "Generage a fingerprint of the given path and save it for later verification.") do |path|
51
- OPTIONS[:mode] = :analyze
52
- OPTIONS[:root] = path if path
53
- end
54
-
55
- o.on("-v", "--verify [path]", String, "Verify a given path based on a previously saved fingerprint.") do |path|
56
- OPTIONS[:mode] = :verify
57
- OPTIONS[:root] = path if path
58
- end
59
-
60
- o.on("-n name", String, "Specify the name of the fingerprint file.", "Default: #{OPTIONS[:name]}") do |name|
61
- OPTIONS[:name] = name
62
- end
63
-
64
- o.on("-f", "Force any operation to complete despite warnings.") do
65
- OPTIONS[:force] = true
66
- end
67
-
68
- o.separator ""
69
- o.separator "Low level operations:"
70
-
71
- o.on("-c", "Compare the given fingerprints. Check that the second fingerprint is a superset of the first.") do
72
- OPTIONS[:mode] = :check
73
- end
74
-
75
- o.on("-o [output-path]", String, "Write the fingerprint output to the given file.") do |path|
76
- OPTIONS[:output] = File.open(path, "w")
77
- end
78
-
79
- o.on("--verbose", "Verbose output, include additional details in the file transcript.") do
80
- OPTIONS[:verbose] = true
81
- end
82
-
83
- o.on("--progress", "Print percentage progress to standard error.") do
84
- OPTIONS[:progress] = true
85
- end
86
-
87
- o.on("--no-lockfile", "Don't use a lockfile to validate access to the fingerprint.") do
88
- OPTIONS[:lockfile] = false
89
- end
90
-
91
- o.separator ""
92
-
93
- o.on("-x", "Include additional extended information about files and directories.") do
94
- OPTIONS[:extended] = true
95
- end
96
-
97
- o.on("-s [checksum1,checksum2]", "Provide a list of the checksum algorithms to use.", "Available: #{Fingerprint::CHECKSUMS.keys.join(', ')}; Default: #{OPTIONS[:checksums].join(', ')}") do |checksums|
98
- OPTIONS[:checksums] = checksums.split(/[\s,]+/)
99
- end
100
-
101
- o.separator ""
102
- o.separator "Help and Copyright information:"
103
-
104
- o.on_tail("--copy", "Display copyright and warranty information") do
105
- $stderr.puts "#{script_name} v#{Fingerprint::VERSION::STRING}. Copyright (c) 2011 Samuel Williams."
106
- $stderr.puts "This software is released under the MIT license and comes with ABSOLUTELY NO WARRANTY."
107
- $stderr.puts "See http://www.oriontransfer.co.nz/ for more information."
108
- exit
109
- end
110
-
111
- o.on_tail("-h", "--help", "Show this help message.") do
112
- $stderr.puts o
113
- exit
114
- end
115
- end.parse!
116
-
117
- unless File.directory? OPTIONS[:root]
118
- $stderr.puts "Path #{OPTIONS[:root]} doesn't exist!"
119
- exit(255)
120
- end
121
-
122
- if OPTIONS[:checksums].size == 0
123
- OPTIONS[:checksums] = ['MD5', 'SHA2.256']
124
- end
125
-
126
- # Run some block with the given lock, if requested.
127
- def with_lock
128
- lockfile_path = nil
129
- if OPTIONS[:lockfile] == true
130
- lockfile_path = Pathname.new(OPTIONS[:root]) + (OPTIONS[:name] + ".lock")
131
- elsif OPTION[:lockfile] != false
132
- lockfile_path = OPTIONS[:lockfile]
133
- end
134
-
135
- if lockfile_path
136
- begin
137
- Lockfile.new(lockfile_path, :retries => 0) do
138
- yield
139
- end
140
- rescue Lockfile::MaxTriesLockError
141
- $stderr.puts "Could not acquire lock #{lockfile_path}."
142
-
143
- return false
144
- end
145
- else
146
- yield
147
- end
148
-
149
- return true
150
- end
151
-
152
- case (OPTIONS[:mode])
153
- when :analyze
154
- result = with_lock do
155
- output_file = Pathname.new(OPTIONS[:root]) + OPTIONS[:name]
156
-
157
- if output_file.exist? && !OPTIONS[:force]
158
- $stderr.puts "Output file #{output_file} already exists. Aborting."
159
- exit(2)
160
- end
161
-
162
- options = OPTIONS.dup
163
- options[:excludes] = [OPTIONS[:name]]
164
-
165
- finished = false
166
- begin
167
- File.open(output_file, "w") do |io|
168
- options[:output] = io
169
-
170
- Fingerprint::Scanner.scan_paths([OPTIONS[:root]], options)
171
- end
172
- finished = true
173
- ensure
174
- FileUtils.rm(output_file) unless finished
175
- end
176
- end
177
-
178
- unless result
179
- # Lockfile failure
180
- exit(4)
181
- end
182
- when :verify
183
- error_count = 0
184
-
185
- input_file = Pathname.new(OPTIONS[:root]) + OPTIONS[:name]
186
-
187
- unless File.exist? input_file
188
- $stderr.puts "Can't find index #{input_file}. Aborting."
189
- exit(3)
190
- end
191
-
192
- options = OPTIONS.dup
193
-
194
- master = Fingerprint::Recordset.new
195
-
196
- File.open(input_file, "r") do |io|
197
- master.parse(io)
198
- end
199
-
200
- if master.configuration
201
- options.merge!(master.configuration.options)
202
- end
203
-
204
- scanner = Fingerprint::Scanner.new([OPTIONS[:root]], options)
205
- copy = Fingerprint::SparseRecordset.new(scanner)
206
-
207
- error_count += Fingerprint::Checker::verify(master, copy, options)
208
-
209
- if error_count == 0
210
- $stderr.puts "Data verified, 0 errors found."
211
- exit(0)
212
- else
213
- $stderr.puts "Data inconsistent, #{error_count} errors found!"
214
- exit(1)
215
- end
216
- when :scan
217
- roots = ARGV
218
- roots << Dir.pwd if roots.size == 0
219
-
220
- # Check that all supplied paths exist
221
- roots.delete_if do |root|
222
- if File.exist? root
223
- false
224
- else
225
- $stderr.puts "Path #{root} doesn't exist, skipping!"
226
- true
227
- end
228
- end
229
-
230
- options = OPTIONS.dup
231
-
232
- Fingerprint::Scanner.scan_paths(roots, options)
233
- when :check
234
- options = OPTIONS.dup
235
-
236
- error_count = Fingerprint::Checker.check_files(ARGV[0], ARGV[1], options)
237
- exit(error_count > 0 ? 1 : 0)
238
- end
29
+ Fingerprint::Command.call
Binary file
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
1
+ # Copyright, 2011, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the "Software"), to deal
@@ -18,25 +18,39 @@
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'
24
25
 
25
26
  module Fingerprint
26
27
  # A helper function to check two paths for consistency. Provides callback from +Fingerprint::Checker+.
27
- def self.check_paths(master_path, copy_path, &block)
28
+ def self.check_paths(master_path, copy_path, **options, &block)
28
29
  master = Scanner.new([master_path])
29
30
  copy = Scanner.new([copy_path])
30
31
 
31
- master_recordset = Recordset.new
32
- copy_recordset = SparseRecordset.new(copy)
32
+ master_recordset = RecordSet.new
33
+ copy_recordset = SparseRecordSet.new(copy)
33
34
 
34
35
  master.scan(master_recordset)
35
36
 
36
- checker = Checker.new(master_recordset, copy_recordset)
37
+ checker = Checker.new(master_recordset, copy_recordset, **options)
37
38
 
38
39
  checker.check(&block)
39
40
 
40
41
  return checker
41
42
  end
43
+
44
+ # Returns true if the given paths contain identical files. Useful for expectations, e.g. `expect(Fingerprint).to be_identical(source, destination)`
45
+ def self.identical?(source, destination, &block)
46
+ failures = 0
47
+
48
+ check_paths(source, destination) do |record, name, message|
49
+ failures += 1
50
+
51
+ yield(record) if block_given?
52
+ end
53
+
54
+ return failures == 0
55
+ end
42
56
  end
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
1
+ # Copyright, 2011, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the "Software"), to deal
@@ -31,7 +31,7 @@ module Fingerprint
31
31
  #
32
32
  # Master and copy are +IO+ objects corresponding to the output produced by +Fingerprint::Scanner+.
33
33
  class Checker
34
- def initialize(master, copy, options = {})
34
+ def initialize(master, copy, **options)
35
35
  @master = master
36
36
  @copy = copy
37
37
 
@@ -42,15 +42,26 @@ module Fingerprint
42
42
  attr :copy
43
43
 
44
44
  # Run the checking process.
45
- def check (options = {}, &block)
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 }
52
51
 
52
+ if @options[:additions]
53
+ copy_paths = @copy.paths.dup
54
+ else
55
+ copy_paths = {}
56
+ end
57
+
53
58
  @master.records.each_with_index do |record, processed_count|
59
+ copy_paths.delete(record.path)
60
+
61
+ if @options[:progress]
62
+ $stderr.puts "# Checking: #{record.path}"
63
+ end
64
+
54
65
  next if record.mode != :file
55
66
 
56
67
  result, message = @copy.compare(record)
@@ -66,53 +77,64 @@ module Fingerprint
66
77
  end
67
78
 
68
79
  if @options[:progress]
69
- $stderr.puts "# Progress: File #{processed_count} / #{total_count} = #{sprintf('%0.2f%', processed_count.to_f / total_count.to_f * 100.0)}; Byte #{processed_size} / #{total_size} = #{sprintf('%0.2f%', processed_size.to_f / total_size.to_f * 100.0)}"
80
+ $stderr.puts "# Progress: File #{processed_count} / #{total_count}; Byte #{processed_size} / #{total_size} = #{sprintf('%0.2f%', processed_size.to_f / total_size.to_f * 100.0)}"
70
81
 
71
82
  processed_size += (record['file.size'] || 0).to_i
72
83
  end
73
84
  end
85
+
86
+ if @options[:additions]
87
+ copy_paths.each do |path, record|
88
+ next unless record.mode == :file || record.mode == :directory
89
+
90
+ yield record, :addition, "File added"
91
+ end
92
+ end
74
93
  end
75
94
 
76
95
  # A list of files which either did not exist in the copy, or had the wrong checksum.
77
96
  attr :failures
78
97
 
79
- def self.check_files(master, copy, options = {}, &block)
80
- # New API that takes two Recordsets...
98
+ def self.check_files(master, copy, **options, &block)
99
+ # New API that takes two RecordSets...
81
100
 
82
101
  File.open(master) do |master_file|
83
102
  File.open(copy) do |copy_file|
84
- master_recordset = Recordset.new
103
+ master_recordset = RecordSet.new
85
104
  master_recordset.parse(master_file)
86
105
 
87
- copy_recordset = Recordset.new
106
+ copy_recordset = RecordSet.new
88
107
  copy_recordset.parse(copy_file)
89
108
 
90
- verify(master_recordset, copy_recordset, options, &block)
109
+ verify(master_recordset, copy_recordset, **options, &block)
91
110
  end
92
111
  end
93
112
  end
94
113
 
95
114
  # Helper function to check two fingerprint files.
96
- def self.verify(master, copy, options = {}, &block)
115
+ def self.verify(master, copy, **options, &block)
97
116
  error_count = 0
98
117
 
99
- errors = options.delete(:recordset) || Recordset.new
118
+ errors = options.delete(:recordset) || RecordSet.new
100
119
  if options[:output]
101
- errors = RecordsetPrinter.new(errors, options[:output])
120
+ errors = RecordSetPrinter.new(errors, options[:output])
102
121
  end
103
122
 
104
- checker = Checker.new(master, copy, options)
123
+ checker = Checker.new(master, copy, **options)
105
124
 
106
125
  checker.check do |record, result, message|
107
126
  error_count += 1
108
- copy = checker.copy.paths[record.path]
109
127
 
110
128
  metadata = {
111
129
  'error.code' => result,
112
130
  'error.message' => message
113
131
  }
114
132
 
115
- if copy
133
+ if result == :addition
134
+ metadata.merge!(record.metadata)
135
+
136
+ errors << Record.new(:warning, record.path, metadata)
137
+ elsif (copy = checker.copy.paths[record.path])
116
138
  changes = record.diff(copy)
117
139
 
118
140
  changes.each do |name|
@@ -126,7 +148,13 @@ module Fingerprint
126
148
  end
127
149
  end
128
150
 
129
- errors << Record.new(:summary, nil, {
151
+ if error_count
152
+ summary_message = "#{error_count} error(s) detected."
153
+ else
154
+ summary_message = "No errors detected"
155
+ end
156
+
157
+ errors << Record.new(:summary, summary_message, {
130
158
  'error.count' => error_count
131
159
  })
132
160
 
@@ -0,0 +1,33 @@
1
+ # Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ # This script takes a given path, and renames it with the given format.
22
+ # It then ensures that there is a symlink called "latest" that points
23
+ # to the renamed directory.
24
+
25
+ require 'samovar'
26
+
27
+ module Fingerprint
28
+ module Checksums
29
+ def self.call(result)
30
+ result.split(/\s*,\s*/)
31
+ end
32
+ end
33
+ end