safeguard 0.0.2 → 0.0.3

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