safeguard 0.0.2 → 0.0.3

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.
data/.gitignore CHANGED
@@ -1 +1,2 @@
1
1
  .safeguard/
2
+ *.lock
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
5
+ { acclaim: '~/projects/acclaim',
6
+ ribbon: '~/projects/ribbon',
7
+ rookie: '~/projects/rookie' }.each do |project, path|
8
+ path = File.expand_path path
9
+ gem project.to_s, path: path if Dir.exists? path
10
+ end
data/Rakefile CHANGED
@@ -1,44 +1,3 @@
1
- this_dir = File.expand_path('..', __FILE__)
2
- gem_dir = File.join this_dir, 'gem'
3
- spec_file = File.join this_dir, 'safeguard.gemspec'
1
+ require 'rookie'
4
2
 
5
- spec = Gem::Specification.load spec_file
6
-
7
- task :mkdir do
8
- FileUtils.mkdir_p gem_dir
9
- end
10
-
11
- task :gem => :mkdir do
12
- gem_file = File.join this_dir, Gem::Builder.new(spec).build
13
- FileUtils.mv gem_file, gem_dir
14
- end
15
-
16
- namespace :gem do
17
-
18
- task :build => :gem
19
-
20
- gem_file = File.join gem_dir, "#{spec.name}-#{spec.version}.gem"
21
-
22
- task :push => :gem do
23
- sh "gem push #{gem_file}"
24
- end
25
-
26
- task :install => :gem do
27
- sh "gem install #{gem_file}"
28
- end
29
-
30
- task :uninstall do
31
- sh "gem uninstall #{spec.name}"
32
- end
33
-
34
- task :clean do
35
- FileUtils.rm_rf gem_dir
36
- end
37
-
38
- end
39
-
40
- task :clean => 'gem:clean'
41
-
42
- task :setup => [ 'gem:install', :clean ]
43
-
44
- task :default => :setup
3
+ Rookie::Tasks.new 'safeguard.gemspec'
data/bin/safeguard CHANGED
@@ -1,7 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'safeguard'
3
3
 
4
- puts 'No command given' or exit if ARGV.empty?
5
-
6
- command = ARGV.shift
7
- Safeguard.run(command, *ARGV)
4
+ Safeguard::Command.run *ARGV
@@ -1,29 +1,27 @@
1
1
  require 'safeguard/command'
2
+ require 'safeguard/digest'
2
3
 
3
4
  module Safeguard
4
- module Command
5
+ class Command
5
6
 
6
7
  # Adds files to a Repository.
7
- module Add
8
+ class Add < Command
8
9
 
9
- Command.register self
10
+ opt :functions, '--use', Symbol,
11
+ "Algorithm used to calculate the file's checksum. " <<
12
+ "Currently supported: #{Digest::SUPPORTED_ALGORITHMS.join(', ')}",
13
+ arity: [1,0], on_multiple: :append
14
+
15
+ opt :force, '--force', 'Rehash files that are already in the repository'
10
16
 
11
17
  # For every argument, try to add it to the Repository in the current
12
18
  # directory.
13
- def self.execute(*args)
14
- repo = Repository.new Dir.pwd
15
- count = 0
16
- args.each do |filename|
17
- begin
18
- puts "Adding #{filename}..."
19
- repo.track filename
20
- # If an exception is raised, count will not be incremented.
21
- count += 1
22
- rescue => e
23
- puts e.message
24
- end
19
+ action do |options, args|
20
+ repo = Repository.new options.dir
21
+ repo.before_save do
22
+ repo.add_files! *args, :functions => options.functions,
23
+ :force => options.force?
25
24
  end
26
- puts "Added #{count} files to repository."
27
25
  end
28
26
 
29
27
  end
@@ -2,17 +2,31 @@ require 'safeguard/command'
2
2
  require 'safeguard/digest'
3
3
 
4
4
  module Safeguard
5
- module Command
5
+ class Command
6
6
 
7
- # Outputs the SHA1 hash of files.
8
- module Hash
7
+ # Outputs the checksum of files.
8
+ #
9
+ # $ safeguard hash *.mp3
10
+ #
11
+ # The hash function can be specified with the --hash-function option. The
12
+ # default is SHA1, but MD5 and CRC32 are also supported.
13
+ #
14
+ # $ safeguard --hash-function md5 hash *.mp3
15
+ class Hash < Command
9
16
 
10
- Command.register self
17
+ opt :func, '--hash-function', '--hash-functions', Symbol,
18
+ "Algorithm to use to calculate the file's checksum. " <<
19
+ "Currently supported: #{Digest::SUPPORTED_ALGORITHMS.join(', ')}",
20
+ default: :sha1, arity: [1,-1]
11
21
 
12
- # For every argument, outputs its SHA1 sum if it exists as a file.
13
- def self.execute(*args)
14
- args.each do |filename|
15
- puts "#{Digest.file filename} => #{filename}" if File.file? filename
22
+ # For every argument, outputs its checksum if it exists as a file.
23
+ action do |options, args|
24
+ hasher = Hasher.new *args, :functions => options.func
25
+ hasher.each do |file, hash_data|
26
+ puts "#{file}:"
27
+ hasher.functions.each do |function|
28
+ puts "\t#{function} => #{hash_data[function]}"
29
+ end
16
30
  end
17
31
  end
18
32
 
@@ -3,20 +3,20 @@ require 'safeguard/repository'
3
3
  require 'fileutils'
4
4
 
5
5
  module Safeguard
6
- module Command
6
+ class Command
7
7
 
8
8
  # Initializes a Repository in a given directory.
9
- module Init
10
-
11
- Command.register self
9
+ #
10
+ # $ safeguard init .
11
+ class Init < Command
12
12
 
13
13
  # Initializes a Safeguard Repository in a directory, which is either the
14
14
  # current directory, if +args+ is empty, or the directory designated by
15
15
  # the last element in +args+.
16
- def self.execute(*args)
17
- args << Dir.pwd if args.empty?
18
- dir = File.expand_path args.pop
16
+ action do |options, args|
17
+ dir = File.expand_path(args.pop || options.dir)
19
18
  repo = Repository.new(dir)
19
+ repo.create_directory!
20
20
  puts "Initialized safeguard repository in #{repo.dir}"
21
21
  end
22
22
 
@@ -1,33 +1,33 @@
1
1
  require 'safeguard/command'
2
2
 
3
3
  module Safeguard
4
- module Command
4
+ class Command
5
5
 
6
6
  # Verifies the files present in a Repository.
7
- module Verify
7
+ class Verify < Command
8
8
 
9
- Command.register self
9
+ opt :functions, '--use', Symbol,
10
+ "Algorithm used to calculate the file's checksum. " <<
11
+ "Currently supported: #{Digest::SUPPORTED_ALGORITHMS.join(', ')}",
12
+ arity: [1,0], on_multiple: :append
10
13
 
11
14
  # Verify the files passed as arguments using information from the
12
15
  # Repository in the current directory.
13
- def self.execute(*args)
14
- repo = Repository.new Dir.pwd
15
- if args.empty?
16
- repo.verify_all do |filename, result|
17
- display_result filename, result
18
- end
19
- else
20
- args.each do |filename|
21
- result = repo.verify filename
22
- display_result filename, result
16
+ action do |options, args|
17
+ repo = Repository.new options.dir
18
+ functions = options.functions
19
+ results = repo.verify_files *args, functions: functions
20
+ results.keys.each do |file|
21
+ puts "#{file}:"
22
+ results[file].keys.each do |function|
23
+ value = results[file][function]
24
+ value = "File not in repository" if value == :file_missing
25
+ value = "Hash not in repository" if value == :hash_missing
26
+ puts "\t#{function} => #{value}"
23
27
  end
24
28
  end
25
29
  end
26
30
 
27
- def self.display_result(filename, result)
28
- puts "#{filename} => #{result ? 'OK' : 'Mismatch'}"
29
- end
30
-
31
31
  end
32
32
 
33
33
  end
@@ -1,42 +1,25 @@
1
- module Safeguard
2
-
3
- # Manages the commands that may be given to Safeguard.
4
- module Command
5
-
6
- instance_eval do
1
+ require 'acclaim/command'
2
+ require 'acclaim/version'
7
3
 
8
- # Returns a hash that associates command modules by name.
9
- def commands
10
- @commands ||= {}
11
- end
12
-
13
- alias :all :commands
4
+ module Safeguard
14
5
 
15
- # Computes a name for the command and associates it with the command's
16
- # module.
17
- def register(command_module)
18
- name = command_module.name.gsub(/^.*::/, '').downcase
19
- commands[name] = command_module
20
- end
6
+ # Main Safeguard command. Currently just invokes the init command.
7
+ #
8
+ # $ safeguard .
9
+ class Command < Acclaim::Command
21
10
 
22
- # Looks up a command by name and returns its module, raising an exception
23
- # if there is no match.
24
- def find(command_name)
25
- name = command_name.to_s.strip.downcase
26
- commands[name].tap do |command|
27
- raise "unsupported command: #{name}" if command.nil?
28
- end
29
- end
11
+ help
12
+ version Acclaim::Version::STRING
30
13
 
31
- # Attempts to find a command by name, and, if successful, invokes it with
32
- # the given arguments.
33
- def invoke(command_name, *args)
34
- find(command_name).execute(*args)
35
- end
14
+ opt :dir, '-D', '--dir', '--directory', 'Directory in which the repository is.',
15
+ default: Dir.pwd, arity: [1,0]
36
16
 
17
+ action do |options, args|
18
+ Init.execute options, args
37
19
  end
38
20
 
39
21
  end
22
+
40
23
  end
41
24
 
42
25
  require 'safeguard/command/add'
@@ -0,0 +1,23 @@
1
+ module Safeguard
2
+ module CoreExt
3
+ module File
4
+
5
+ # Default size of chunks in bytes. A megabyte is equal to 2^20 bytes, or
6
+ # 1,048,576 bytes.
7
+ DEFAULT_CHUNK_SIZE = 2 ** 20
8
+
9
+ # Reads a file piece by piece, yielding every chunk until the end of the
10
+ # file. Will yield chunks of DEFAULT_CHUNK_SIZE bytes, unless specified.
11
+ def each_chunk(chunk_size = DEFAULT_CHUNK_SIZE)
12
+ # A chunk_size of zero will cause an infinite loop. It will keep reading
13
+ # 0 bytes, so the pointer will never change and the end of the file will
14
+ # never be reached.
15
+ bytes = case chunk_size when nil, 0 then nil else chunk_size.abs end
16
+ yield read bytes until eof?
17
+ end
18
+ end
19
+
20
+ ::File.send :include, Safeguard::CoreExt::File
21
+
22
+ end
23
+ end
@@ -0,0 +1,46 @@
1
+ require 'digest'
2
+ require 'zlib'
3
+
4
+ module Safeguard
5
+ module Digest
6
+
7
+ # Digest implementation of CRC32 using Zlib.
8
+ class CRC32 < ::Digest::Class
9
+
10
+ include ::Digest::Instance
11
+
12
+ INITIAL_VALUE = 0
13
+
14
+ attr_reader :crc32
15
+
16
+ def initialize
17
+ reset
18
+ end
19
+
20
+ def reset
21
+ @crc32 = INITIAL_VALUE
22
+ end
23
+
24
+ def update(str)
25
+ @crc32 = Zlib.crc32(str, crc32)
26
+ self
27
+ end
28
+
29
+ alias << update
30
+
31
+ def finish
32
+ [ crc32 ].pack 'N'
33
+ end
34
+
35
+ def digest_length
36
+ 1
37
+ end
38
+
39
+ def block_length
40
+ 1
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+ end
@@ -1,3 +1,5 @@
1
+ require 'safeguard/core_ext/file'
2
+ require 'safeguard/digest/crc32'
1
3
  require 'openssl'
2
4
 
3
5
  module Safeguard
@@ -5,9 +7,56 @@ module Safeguard
5
7
  # Encapsulates checksum computation for files using a set of hash functions.
6
8
  module Digest
7
9
 
8
- # Compute the SHA1 sum of a file.
9
- def self.file(filename)
10
- OpenSSL::Digest::SHA1.file(filename).hexdigest
10
+ SUPPORTED_ALGORITHMS = %w(sha1 md5 crc32).map!(&:to_sym).freeze
11
+
12
+ # Digests a file using an +algorithm+.
13
+ #
14
+ # Algorithms are expected include the Digest::Instance module.
15
+ #
16
+ # OpenSSL::Digest::SHA1.include? Digest::Instance
17
+ # => true
18
+ def self.digest_with(algorithm, filename)
19
+ digest = algorithm.new
20
+ File.open(filename, 'rb') do |file|
21
+ file.each_chunk do |chunk|
22
+ digest << chunk
23
+ end
24
+ end
25
+ digest.hexdigest!
26
+ end
27
+
28
+ private_class_method :digest_with
29
+
30
+ # Computes the SHA1 sum of the given file.
31
+ def self.sha1(*args)
32
+ digest_with OpenSSL::Digest::SHA1, *args
33
+ end
34
+
35
+ # Computes the MD5 sum of the given file.
36
+ def self.md5(*args)
37
+ digest_with OpenSSL::Digest::MD5, *args
38
+ end
39
+
40
+ # Computes the CRC32 sum of the given file.
41
+ def self.crc32(*args)
42
+ # Read file in binary mode. Doesn't make any difference in *nix, but Ruby
43
+ # will attempt to convert line endings if the file is opened in text mode
44
+ # in other platforms.
45
+ digest_with Safeguard::Digest::CRC32, *args
46
+ end
47
+
48
+ # Digests a file using a hash function, which can be the symbol of any
49
+ # Digest module method that takes a file. Uses SHA1 by default.
50
+ #
51
+ # Safeguard::Digest.file(file, :md5)
52
+ #
53
+ # Is equivalent to:
54
+ #
55
+ # Safeguard::Digest.md5(file)
56
+ def self.file(file, hash_function = :sha1)
57
+ f = hash_function.to_sym
58
+ raise ArgumentError, "Unsupported hash function: #{f}" unless respond_to? f
59
+ send hash_function, file
11
60
  end
12
61
 
13
62
  end
@@ -0,0 +1,60 @@
1
+ require 'safeguard/digest'
2
+ require 'ribbon'
3
+ require 'ribbon/core_ext/array'
4
+
5
+ module Safeguard
6
+
7
+ # Hashes a set of files.
8
+ class Hasher
9
+
10
+ # The files which will be hashed.
11
+ attr_accessor :files
12
+
13
+ # The hash functions to use.
14
+ attr_accessor :functions
15
+
16
+ # Initializes a new hasher with the given files. Last argument can be an
17
+ # options hash or ribbon which specifies the hash functions to use:
18
+ #
19
+ # files = %w(file1 file2 fil3)
20
+ # Hasher.new *files, :functions => :sha1
21
+ # Hasher.new *files, :functions => [ :sha1, :md5, :crc32 ]
22
+ def initialize(*args)
23
+ ribbon = args.extract_options_as_ribbon!
24
+ funcs = ribbon.functions? do
25
+ raise ArgumentError, 'No hash functions specified'
26
+ end
27
+ self.functions = [*funcs]
28
+ self.files = args
29
+ end
30
+
31
+ # Calculates the hash of each file. Updates the cached hash results.
32
+ def hash_files!
33
+ results = Ribbon.new
34
+ files.each do |file|
35
+ functions.each do |function|
36
+ results[file][function] = Safeguard::Digest.file file, function
37
+ end
38
+ end
39
+ results = Ribbon[results]
40
+ results.wrap_all!
41
+ @results = results
42
+ end
43
+
44
+ # Returns the cached results containing the hashes calculated for each file.
45
+ #
46
+ # In order to force hash recalculation, call #hash_files!.
47
+ def results
48
+ @results ||= hash_files!
49
+ end
50
+
51
+ # Yields file => hash data pairs or returns an enumerator.
52
+ def each(&block)
53
+ Ribbon[results].each &block
54
+ end
55
+
56
+ include Enumerable
57
+
58
+ end
59
+
60
+ end
@@ -0,0 +1,54 @@
1
+ require 'safeguard/digest'
2
+ require 'ribbon'
3
+ require 'ribbon/core_ext'
4
+ require 'yaml'
5
+
6
+ module Safeguard
7
+ class Repository
8
+
9
+ # Holds filename => checksum pairs.
10
+ class HashTable
11
+
12
+ # Initializes this hash table with the contents of the given Ribbon.
13
+ def initialize(ribbon = nil, &block)
14
+ merge! ribbon, &block if ribbon
15
+ end
16
+
17
+ # Saves the HashTable to a YAML file.
18
+ def save(filename)
19
+ File.open(filename, 'w') do |file|
20
+ file.puts ribbon.to_yaml
21
+ end
22
+ end
23
+
24
+ # Loads the HashTable from a YAML file.
25
+ def self.load(filename)
26
+ new Ribbon::Wrapper.from_yaml File.read(filename)
27
+ end
28
+
29
+ # Merges this hash table's data with the other's.
30
+ def merge!(other)
31
+ other = other.ribbon if other.is_a? HashTable
32
+ Ribbon.deep_merge! ribbon, other
33
+ end
34
+
35
+ # Looks up the checksum data for the given +filename+.
36
+ def [](filename)
37
+ ribbon[filename]
38
+ end
39
+
40
+ # Returns a list of files present in this hash table.
41
+ def files
42
+ ribbon.keys
43
+ end
44
+
45
+ # The underlying wrapped ribbon used to store filenames and their
46
+ # associated hashes.
47
+ def ribbon
48
+ @ribbon ||= Ribbon::Wrapper.new
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+ end
@@ -1,4 +1,9 @@
1
- require 'safeguard/hash_table'
1
+ require 'safeguard/hasher'
2
+ require 'safeguard/repository/hash_table'
3
+ require 'safeguard/verifier'
4
+ require 'fileutils'
5
+ require 'ribbon'
6
+ require 'ribbon/core_ext'
2
7
 
3
8
  module Safeguard
4
9
 
@@ -15,17 +20,19 @@ module Safeguard
15
20
  alias :dir :directory
16
21
 
17
22
  # Initializes a Safeguard repository in or from the given directory.
18
- #
19
- # If the given directory does not contain a repository directory, whose name
20
- # is defined by the DIRECTORY_NAME constant, it will be created.
21
23
  def initialize(dir)
22
24
  self.directory = Repository.directory_in(dir)
25
+ end
26
+
27
+ # Creates a directory for this repository, if one doesn't exist. The
28
+ # directory will have the name defined in the DIRECTORY_NAME constant.
29
+ def create_directory!
23
30
  FileUtils.mkdir_p directory
24
31
  end
25
32
 
26
- # Loads this repository's HashTable. Creates a new one if unable to do so.
33
+ # This repository's HashTable. Lazily loaded from disk.
27
34
  def hash_table
28
- HashTable.load hash_table_file_name rescue HashTable.new
35
+ @table ||= load_hash_table!
29
36
  end
30
37
 
31
38
  # Returns the name of the HashTable file relative to this repository.
@@ -33,30 +40,46 @@ module Safeguard
33
40
  File.join directory, HASH_TABLE_FILE_NAME
34
41
  end
35
42
 
36
- # Adds a file to this repository's HashTable, and saves it.
37
- def track(filename)
38
- table = hash_table_file_name
39
- file = File.expand_path filename
40
- hash_table.instance_eval do
41
- add file
42
- save table
43
- end
43
+ # Saves this Repository's HashTable with the filename returned by
44
+ # #hash_table_file_name.
45
+ def save_hash_table
46
+ hash_table.save hash_table_file_name
47
+ end
48
+
49
+ # Calls the block and ensures that all data is persisted afterwards.
50
+ def before_save(&block)
51
+ if block.arity == 1 then block.call self else instance_eval &block end
52
+ ensure
53
+ save_hash_table
44
54
  end
45
55
 
46
- # Verifies whether or not the file still matches the original version.
47
- #
48
- # An exception will be raised if the given file isn't in the repository.
49
- def verify(filename)
50
- hash_table.verify File.expand_path(filename)
56
+ # Calculates the checksum of the given files and stores the results. +args+
57
+ # will be used to instantiate a new Hasher.
58
+ def add_files!(*args)
59
+ ribbon = args.extract_ribbon!
60
+ hasher = Hasher.new *args, ribbon
61
+ hasher.files.delete_if do |file|
62
+ hash_table.files.include? file
63
+ end unless ribbon.force?
64
+ results = hasher.results
65
+ hash_table.merge! results
51
66
  end
52
67
 
53
- # Verifies all files present in this repository, and returns a hash of
54
- # results associating a filename with either +true+, when the file is the
55
- # same as the original version, or +false+, when otherwise.
56
- #
57
- # If a block is given, the filename and the result will be yielded instead.
58
- def verify_all(&block)
59
- hash_table.verify_all &block
68
+ # Creates a Hasher for the files present in this repository.
69
+ def create_hasher_with(functions)
70
+ Hasher.new *hash_table.files, :functions => functions
71
+ end
72
+
73
+ # Creates a verifier using this repository's hash table.
74
+ def create_verifier_with(*args)
75
+ ribbon = args.extract_wrapped_ribbon!
76
+ Verifier.new *args, ribbon.merge!(hash_table: hash_table)
77
+ end
78
+
79
+ # Recalculates the checksum of the given files and compares them to the
80
+ # stored values. +args+ will be used to instantiate a new Verifier.
81
+ def verify_files(*args)
82
+ create_verifier_with(*args).results
60
83
  end
61
84
 
62
85
  # Returns the path to the repository relative to the given +dir+.
@@ -64,10 +87,27 @@ module Safeguard
64
87
  File.join File.expand_path(dir), DIRECTORY_NAME
65
88
  end
66
89
 
90
+ # Checks whether or not this repository's directory has been created and
91
+ # initialized.
92
+ def initialized?
93
+ File.directory? dir
94
+ end
95
+
67
96
  # Checks whether or not a repository has been created in the given +dir+.
68
97
  def self.initialized?(dir)
69
98
  File.directory? directory_in(dir)
70
99
  end
71
100
 
101
+ private
102
+
103
+ # Loads the hash table from file. Creates a new one if unable to do so.
104
+ def load_hash_table!
105
+ HashTable.load hash_table_file_name
106
+ rescue
107
+ # TODO: handle all error cases properly. This can overwrite a perfectly
108
+ # good hash table for incredibly sily reasons.
109
+ HashTable.new
110
+ end
111
+
72
112
  end
73
113
  end
@@ -0,0 +1,88 @@
1
+ require 'safeguard/hasher'
2
+ require 'safeguard/repository/hash_table'
3
+ require 'ribbon'
4
+ require 'ribbon/core_ext/array'
5
+
6
+ module Safeguard
7
+
8
+ # Rehashes a set of files and compares the results to the stored hashes.
9
+ class Verifier
10
+
11
+ # The hasher used to calculate the checksum of the files.
12
+ attr_accessor :hasher
13
+
14
+ # Hash table to compare results against.
15
+ attr_accessor :hash_table
16
+
17
+ # Initializes a new verifier with the given files. Last argument can be an
18
+ # options hash or ribbon which specifies the hash table to verify against
19
+ # and the Hasher to use. If a hasher isn't specified, a new one will be
20
+ # created using the files and options given. If no files were given, the
21
+ # hash table's list of files will be used.
22
+ #
23
+ # files = %w(file1 file2 file3)
24
+ # hasher = Hasher.new *files, :functions => :crc32
25
+ # table = hasher.results
26
+ #
27
+ # Verifier.new :hasher => hasher, :hash_table => table
28
+ # Verifier.new :functions => [ :crc32, :md5 ], :hash_table => table
29
+ # Verifier.new *files, :functions => :crc32, :hash_table => table
30
+ def initialize(*args)
31
+ ribbon = args.extract_ribbon!
32
+ table = ribbon.hash_table? do
33
+ raise ArgumentError, 'No hash table to verify against'
34
+ end
35
+ table = table.ribbon if Repository::HashTable === table
36
+ self.hash_table = Ribbon[table]
37
+ hash_table.wrap_all!
38
+ args = hash_table.keys if args.empty?
39
+ self.hasher = ribbon.hasher? do
40
+ Hasher.new *args, ribbon
41
+ end
42
+ end
43
+
44
+ # Uses the hasher to recalculate the hashes of the files using the specified
45
+ # functions and compares the results with the data on the hash table.
46
+ #
47
+ # If a given file is not present in the hash table, the result of all
48
+ # comparisons will be the <tt>:file_missing</tt> symbol. If the value of a
49
+ # given hash for a given file is not present in the hash table, the result
50
+ # of the comparison will be the <tt>:hash_missing</tt> symbol.
51
+ def verify!
52
+ results = Ribbon.new
53
+ hasher.each do |file, hash_data|
54
+ hasher.functions.each do |function|
55
+ results[file][function] = if hash_table.has_key? file
56
+ if hash_table[file].has_key? function
57
+ hash_data[function] == hash_table[file][function]
58
+ else
59
+ :hash_missing
60
+ end
61
+ else
62
+ :file_missing
63
+ end
64
+ end
65
+ end
66
+ results = Ribbon[results]
67
+ results.wrap_all!
68
+ @results = results
69
+ end
70
+
71
+ # Returns the cached data containing the results of the comparison betweeb
72
+ # the recalculated hashes of the files with the data in the hash table.
73
+ #
74
+ # To force recomputation, call the #verify! method.
75
+ def results
76
+ @results ||= verify!
77
+ end
78
+
79
+ # Yields file => comparison data pairs, or returns an enumerator.
80
+ def each(&block)
81
+ Ribbon[results].each &block
82
+ end
83
+
84
+ include Enumerable
85
+
86
+ end
87
+
88
+ end
@@ -16,7 +16,7 @@ module Safeguard
16
16
  # Patch version.
17
17
  #
18
18
  # Increments denote changes in implementation.
19
- PATCH = 2
19
+ PATCH = 3
20
20
 
21
21
  # Build version.
22
22
  #
data/lib/safeguard.rb CHANGED
@@ -1,4 +1,9 @@
1
1
  require 'safeguard/command'
2
+ require 'safeguard/digest'
3
+ require 'safeguard/hasher'
4
+ require 'safeguard/repository'
5
+ require 'safeguard/verifier'
6
+ require 'safeguard/version'
2
7
 
3
8
  # Safeguard module.
4
9
  module Safeguard
data/safeguard.gemspec CHANGED
@@ -17,4 +17,9 @@ Gem::Specification.new('safeguard') do |gem|
17
17
  gem.files = `git ls-files`.split "\n"
18
18
  gem.executables = `git ls-files -- bin/*`.split("\n").map &File.method(:basename)
19
19
 
20
+ gem.add_runtime_dependency 'acclaim'
21
+ gem.add_runtime_dependency 'ribbon'
22
+
23
+ gem.add_development_dependency 'rookie'
24
+
20
25
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: safeguard
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,8 +9,41 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-12-15 00:00:00.000000000 Z
13
- dependencies: []
12
+ date: 2012-01-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: acclaim
16
+ requirement: &6175680 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *6175680
25
+ - !ruby/object:Gem::Dependency
26
+ name: ribbon
27
+ requirement: &6174640 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *6174640
36
+ - !ruby/object:Gem::Dependency
37
+ name: rookie
38
+ requirement: &6174080 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *6174080
14
47
  description: Hash-based file integrity verification utility
15
48
  email: matheus.a.m.moreira@gmail.com
16
49
  executables:
@@ -20,6 +53,7 @@ extra_rdoc_files: []
20
53
  files:
21
54
  - .gitignore
22
55
  - .rvmrc
56
+ - Gemfile
23
57
  - LICENSE.GPLv3
24
58
  - README.markdown
25
59
  - Rakefile
@@ -30,9 +64,13 @@ files:
30
64
  - lib/safeguard/command/hash.rb
31
65
  - lib/safeguard/command/init.rb
32
66
  - lib/safeguard/command/verify.rb
67
+ - lib/safeguard/core_ext/file.rb
33
68
  - lib/safeguard/digest.rb
34
- - lib/safeguard/hash_table.rb
69
+ - lib/safeguard/digest/crc32.rb
70
+ - lib/safeguard/hasher.rb
35
71
  - lib/safeguard/repository.rb
72
+ - lib/safeguard/repository/hash_table.rb
73
+ - lib/safeguard/verifier.rb
36
74
  - lib/safeguard/version.rb
37
75
  - safeguard.gemspec
38
76
  homepage: https://github.com/matheusmoreira/safeguard
@@ -47,12 +85,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
47
85
  - - ! '>='
48
86
  - !ruby/object:Gem::Version
49
87
  version: '0'
88
+ segments:
89
+ - 0
90
+ hash: -397877248667330061
50
91
  required_rubygems_version: !ruby/object:Gem::Requirement
51
92
  none: false
52
93
  requirements:
53
94
  - - ! '>='
54
95
  - !ruby/object:Gem::Version
55
96
  version: '0'
97
+ segments:
98
+ - 0
99
+ hash: -397877248667330061
56
100
  requirements: []
57
101
  rubyforge_project:
58
102
  rubygems_version: 1.8.10
@@ -1,71 +0,0 @@
1
- require 'safeguard/digest'
2
- require 'yaml'
3
-
4
- module Safeguard
5
-
6
- # Holds filename => checksum pairs.
7
- class HashTable
8
-
9
- # Initializes an empty HashTable.
10
- def initialize
11
- @table = {}
12
- end
13
-
14
- # Saves the HashTable to a YAML file.
15
- def save(filename)
16
- File.open("#{filename}.yaml", 'w') do |file|
17
- file.puts to_yaml
18
- end
19
- end
20
-
21
- # Loads the HashTable from a YAML file.
22
- def self.load(filename)
23
- YAML::load_file "#{filename}.yaml"
24
- end
25
-
26
- # Associates the given +filename+ to the computed checksum of the file it
27
- # refers to.
28
- def <<(filename)
29
- @table[filename] = Digest.file filename
30
- end
31
-
32
- alias :add :<<
33
-
34
- # Looks up the checksum for the given +filename+.
35
- def [](filename)
36
- @table[filename]
37
- end
38
-
39
- # Recalculates the hash and compares it to the original hash associated with
40
- # the given filename.
41
- #
42
- # If a hash for the given file isn't stored in this table, an exception will
43
- # be raised.
44
- def verify(filename)
45
- hash = @table[filename]
46
- raise "File not in repository: #{filename}" unless hash
47
- Digest.file(filename) == hash
48
- end
49
-
50
- # Verifies all files stored in this table and returns a hash of results
51
- # associating a filename with either +true+, when the file's recalculated
52
- # hash is equal to the hash stored in this table, or +false+, when
53
- # otherwise.
54
- #
55
- # If a block is given, the filename and the result will be yielded instead.
56
- def verify_all
57
- files = @table.keys
58
- if block_given?
59
- files.each { |file| yield file, verify(file) }
60
- else
61
- {}.tap do |results|
62
- files.each do |file|
63
- results[file] = verify file
64
- end
65
- end
66
- end
67
- end
68
-
69
- end
70
-
71
- end