longleaf 0.1.0 → 0.2.0.pre.1

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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +13 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +4 -0
  5. data/.rubocop_todo.yml +755 -0
  6. data/README.md +29 -7
  7. data/lib/longleaf/candidates/file_selector.rb +107 -0
  8. data/lib/longleaf/candidates/service_candidate_filesystem_iterator.rb +99 -0
  9. data/lib/longleaf/candidates/service_candidate_locator.rb +18 -0
  10. data/lib/longleaf/cli.rb +102 -6
  11. data/lib/longleaf/commands/deregister_command.rb +50 -0
  12. data/lib/longleaf/commands/preserve_command.rb +45 -0
  13. data/lib/longleaf/commands/register_command.rb +24 -38
  14. data/lib/longleaf/commands/validate_config_command.rb +6 -2
  15. data/lib/longleaf/commands/validate_metadata_command.rb +49 -0
  16. data/lib/longleaf/errors.rb +19 -0
  17. data/lib/longleaf/events/deregister_event.rb +55 -0
  18. data/lib/longleaf/events/event_names.rb +9 -0
  19. data/lib/longleaf/events/event_status_tracking.rb +59 -0
  20. data/lib/longleaf/events/preserve_event.rb +71 -0
  21. data/lib/longleaf/events/register_event.rb +37 -26
  22. data/lib/longleaf/helpers/digest_helper.rb +50 -0
  23. data/lib/longleaf/helpers/service_date_helper.rb +51 -0
  24. data/lib/longleaf/logging.rb +1 -0
  25. data/lib/longleaf/logging/redirecting_logger.rb +9 -8
  26. data/lib/longleaf/models/app_fields.rb +2 -0
  27. data/lib/longleaf/models/file_record.rb +8 -3
  28. data/lib/longleaf/models/md_fields.rb +1 -0
  29. data/lib/longleaf/models/metadata_record.rb +16 -4
  30. data/lib/longleaf/models/service_definition.rb +4 -3
  31. data/lib/longleaf/models/service_fields.rb +2 -0
  32. data/lib/longleaf/models/service_record.rb +4 -1
  33. data/lib/longleaf/models/storage_location.rb +18 -1
  34. data/lib/longleaf/preservation_services/fixity_check_service.rb +121 -0
  35. data/lib/longleaf/preservation_services/rsync_replication_service.rb +183 -0
  36. data/lib/longleaf/services/application_config_deserializer.rb +4 -6
  37. data/lib/longleaf/services/application_config_manager.rb +4 -2
  38. data/lib/longleaf/services/application_config_validator.rb +1 -1
  39. data/lib/longleaf/services/configuration_validator.rb +1 -0
  40. data/lib/longleaf/services/metadata_deserializer.rb +47 -10
  41. data/lib/longleaf/services/metadata_serializer.rb +42 -6
  42. data/lib/longleaf/services/service_class_cache.rb +112 -0
  43. data/lib/longleaf/services/service_definition_manager.rb +5 -1
  44. data/lib/longleaf/services/service_definition_validator.rb +4 -4
  45. data/lib/longleaf/services/service_manager.rb +72 -9
  46. data/lib/longleaf/services/service_mapping_manager.rb +4 -3
  47. data/lib/longleaf/services/service_mapping_validator.rb +4 -4
  48. data/lib/longleaf/services/storage_location_manager.rb +26 -5
  49. data/lib/longleaf/services/storage_location_validator.rb +1 -1
  50. data/lib/longleaf/services/storage_path_validator.rb +2 -2
  51. data/lib/longleaf/specs/config_builder.rb +9 -5
  52. data/lib/longleaf/specs/custom_matchers.rb +9 -0
  53. data/lib/longleaf/specs/file_helpers.rb +60 -0
  54. data/lib/longleaf/version.rb +1 -1
  55. data/longleaf.gemspec +1 -0
  56. metadata +39 -7
  57. data/lib/longleaf/commands/abstract_command.rb +0 -37
@@ -0,0 +1,50 @@
1
+ require 'longleaf/errors'
2
+ require 'digest'
3
+
4
+ module Longleaf
5
+ # Helper methods for generating digests
6
+ class DigestHelper
7
+ KNOWN_DIGESTS ||= ['md5', 'sha1', 'sha2', 'sha256', 'sha384', 'sha512', 'rmd160']
8
+
9
+ # @param algs Either a string containing one or an array containing zero or more digest
10
+ # algorithm names.
11
+ # @raise [InvalidDigestAlgorithmError] thrown if any of the digest algorithms listed are not
12
+ # known to the system.
13
+ def self.validate_algorithms(algs)
14
+ return if algs.nil?
15
+ if algs.is_a?(String)
16
+ unless KNOWN_DIGESTS.include?(algs)
17
+ raise InvalidDigestAlgorithmError.new("Unknown digest algorithm #{algs}")
18
+ end
19
+ else
20
+ unknown = algs.select { |alg| !KNOWN_DIGESTS.include?(alg) }
21
+ unless unknown.empty?
22
+ raise InvalidDigestAlgorithmError.new("Unknown digest algorithm(s): #{unknown.to_s}")
23
+ end
24
+ end
25
+ end
26
+
27
+ # Get a Digest class for the specified algorithm
28
+ # @param alg [String] name of the digest algorithm
29
+ # @return [Digest] A digest class for the requested algorithm
30
+ # @raise [InvalidDigestAlgorithmError] if an unknown digest algorithm is requested
31
+ def self.start_digest(alg)
32
+ case alg
33
+ when 'md5'
34
+ return Digest::MD5.new
35
+ when 'sha1'
36
+ return Digest::SHA1.new
37
+ when 'sha2', 'sha256'
38
+ return Digest::SHA2.new
39
+ when 'sha384'
40
+ return Digest::SHA2.new(384)
41
+ when 'sha512'
42
+ return Digest::SHA2.new(512)
43
+ when 'rmd160'
44
+ return Digest::RMD160.new
45
+ else
46
+ raise InvalidDigestAlgorithmError.new("Cannot produce digest for unknown algorithm '#{alg}'.")
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,51 @@
1
+ require 'time'
2
+
3
+ module Longleaf
4
+ # Helper methods for interacting with dates/timestamps on services
5
+ class ServiceDateHelper
6
+
7
+ # Adds the amount of time from modifier to the provided timestamp
8
+ # @param timestamp [String] ISO-8601 timestamp string
9
+ # @param modifier [String] amount of time to add to the timestamp. It must follow the syntax
10
+ # "<quantity> <time unit>", where quantity must be a positive whole number and time unit
11
+ # must be second, minute, hour, day, week, month or year (unit may be plural).
12
+ # Any info after a comma will be ignored.
13
+ # @return [String] the original timestamp in ISO-8601 format with the provided amount of time added.
14
+ def self.add_to_timestamp(timestamp, modifier)
15
+ if modifier =~ /^(\d+) *(second|minute|hour|day|week|month|year)s?(,.*)?/
16
+ value = $1.to_i
17
+ unit = $2
18
+ else
19
+ raise ArgumentError.new("Cannot parse time modifier #{modifier}")
20
+ end
21
+
22
+ datetime = Time.iso8601(timestamp)
23
+ case unit
24
+ when 'second'
25
+ unit_modifier = 1
26
+ when 'minute'
27
+ unit_modifier = 60
28
+ when 'hour'
29
+ unit_modifier = 3600
30
+ when 'day'
31
+ unit_modifier = 24 * 3600
32
+ when 'week'
33
+ unit_modifier = 7 * 24 * 3600
34
+ when 'month'
35
+ unit_modifier = 30 * 24 * 3600
36
+ when 'year'
37
+ unit_modifier = 365 * 24 * 3600
38
+ end
39
+
40
+ modified_time = datetime + (value * unit_modifier)
41
+ modified_time.iso8601
42
+ end
43
+
44
+ # Get a timestamp in the format expected for service timestamps.
45
+ # @param timestamp [Time] the time to format. Defaults to now.
46
+ # @return [String] the time formatted as iso8601
47
+ def self.formatted_timestamp(timestamp = Time.now)
48
+ timestamp.iso8601.to_s
49
+ end
50
+ end
51
+ end
@@ -1,6 +1,7 @@
1
1
  require 'longleaf/logging/redirecting_logger'
2
2
 
3
3
  module Longleaf
4
+ # Module for access logging within longleaf
4
5
  module Logging
5
6
  # Get the main logger for longleaf
6
7
  def logger
@@ -1,14 +1,14 @@
1
1
  require 'logger'
2
2
 
3
- # Logger which directs messages to stdout and/or stderr, depending on the nature of the message.
4
- # Status logging, which includes standard logger methods, goes to STDERR.
5
- # Operation success and failure messages go to STDOUT, and to STDERR at info level.
6
3
  module Longleaf
7
4
  module Logging
5
+ # Logger which directs messages to stdout and/or stderr, depending on the nature of the message.
6
+ # Status logging, which includes standard logger methods, goes to STDERR.
7
+ # Operation success and failure messages go to STDOUT, and to STDERR at info level.
8
8
  class RedirectingLogger
9
- # @param failure_only [Boolean] If set to true, only failure messages will be output to STDOUT
9
+ # @param [Boolean] failure_only If set to true, only failure messages will be output to STDOUT
10
10
  # @param log_level [String] logger level used for output to STDERR
11
- # @param log_format [Strfailure_onlying] format string for log entries to STDERR. There are 4 variables available
11
+ # @param log_format [String] format string for log entries to STDERR. There are 4 variables available
12
12
  # for inclusion in the output: severity, datetime, progname, msg. Variables must be wrapped in %{}.
13
13
  # @param datetime_format [String] datetime formatting string used for logger dates appearing in STDERR.
14
14
  def initialize(failure_only: false, log_level: 'WARN', log_format: nil, datetime_format: nil)
@@ -67,7 +67,8 @@ module Longleaf
67
67
  end
68
68
 
69
69
  # Logs a success message to STDOUT, as well as STDERR at info level.
70
- # @param eventOrMessage [String] name of the preservation event which succeeded,
70
+ #
71
+ # @param [String] eventOrMessage name of the preservation event which succeeded,
71
72
  # or the message to output if it is the only parameter. Required.
72
73
  # @param file_name [String] file name which is the subject of this message.
73
74
  # @param message [String] descriptive message to accompany this output
@@ -90,6 +91,7 @@ module Longleaf
90
91
 
91
92
  @stderr_log.info(text)
92
93
  @stderr_log.error("#{error.message}") unless error.nil?
94
+ @stderr_log.error("#{error.backtrace.to_s}") unless error.nil? || error.backtrace.nil?
93
95
  end
94
96
 
95
97
  # Logs an outcome message to STDOUT, as well as STDERR at info level.
@@ -97,7 +99,7 @@ module Longleaf
97
99
  #
98
100
  # @param outcome [String] The status of the outcome. Required.
99
101
  # @param eventOrMessage [String] name of the preservation event which was successful,
100
- # or the message to output if it is the only parameter. Required.
102
+ # or the message to output if it is the only parameter. Required.
101
103
  # @param file_name [String] file name which is the subject of this message.
102
104
  # @param message [String] descriptive message to accompany this output
103
105
  # @param service [String] name of the service which executed.
@@ -108,7 +110,6 @@ module Longleaf
108
110
  @stderr_log.info(text)
109
111
  end
110
112
 
111
- # FAILURE verify[cdr_fixity_check] /path/to/file: Something terrible
112
113
  private
113
114
  def outcome_text(outcome, eventOrMessage, file_name = nil, message = nil, service = nil, error = nil)
114
115
  message_only = file_name.nil? && message.nil? && error.nil?
@@ -1,4 +1,5 @@
1
1
  module Longleaf
2
+ # Application configuration field names
2
3
  class AppFields
3
4
  LOCATIONS = 'locations'
4
5
  SERVICES = 'services'
@@ -6,5 +7,6 @@ module Longleaf
6
7
 
7
8
  LOCATION_PATH = 'path'
8
9
  METADATA_PATH = 'metadata_path'
10
+ METADATA_DIGESTS = 'metadata_digests'
9
11
  end
10
12
  end
@@ -1,5 +1,5 @@
1
- # Record for an individual file and its associated information
2
1
  module Longleaf
2
+ # Record for an individual file and its associated information
3
3
  class FileRecord
4
4
 
5
5
  attr_accessor :metadata_record
@@ -7,13 +7,14 @@ module Longleaf
7
7
  attr_reader :path
8
8
 
9
9
  # @param file_path [String] path to the file
10
- # @param storage_location [Longleaf::StorageLocation] storage location containing the file
11
- def initialize(file_path, storage_location)
10
+ # @param storage_location [StorageLocation] storage location containing the file
11
+ def initialize(file_path, storage_location, metadata_record = nil)
12
12
  raise ArgumentError.new("FileRecord requires a path") if file_path.nil?
13
13
  raise ArgumentError.new("FileRecord requires a storage_location") if storage_location.nil?
14
14
 
15
15
  @path = file_path
16
16
  @storage_location = storage_location
17
+ @metadata_record = metadata_record
17
18
  end
18
19
 
19
20
  # @return [String] path for the metadata file for this file
@@ -21,5 +22,9 @@ module Longleaf
21
22
  @metadata_path = @storage_location.get_metadata_path_for(path) if @metadata_path.nil?
22
23
  @metadata_path
23
24
  end
25
+
26
+ def metadata_present?
27
+ File.exist?(metadata_path)
28
+ end
24
29
  end
25
30
  end
@@ -1,4 +1,5 @@
1
1
  module Longleaf
2
+ # File metadata fields
2
3
  class MDFields
3
4
  DATA = 'data'
4
5
  SERVICES = 'services'
@@ -1,10 +1,11 @@
1
1
  require_relative 'md_fields'
2
2
  require_relative 'service_record'
3
3
 
4
- # Metadata record for a single file
5
4
  module Longleaf
5
+ # Metadata record for a single file
6
6
  class MetadataRecord
7
- attr_reader :deregistered, :registered
7
+ attr_reader :registered
8
+ attr_accessor :deregistered
8
9
  attr_reader :checksums
9
10
  attr_reader :properties
10
11
  attr_accessor :file_size, :last_modified
@@ -35,14 +36,25 @@ module Longleaf
35
36
  # Adds a service to this record
36
37
  #
37
38
  # @param name [String] identifier for the service being added
38
- # @param service_properties [ServiceRecord] properties for populating the new service
39
- def add_service(name, service = Longleaf::ServiceRecord.new)
39
+ # @param service [ServiceRecord] properties for populating the new service
40
+ # @return [ServiceRecord] the service added
41
+ def add_service(name, service = ServiceRecord.new)
40
42
  raise ArgumentError.new("Value must be a ServiceRecord object when adding a service") unless service.class == Longleaf::ServiceRecord
41
43
  raise IndexError.new("Service with name '#{name}' already exists") if @services.key?(name)
42
44
 
43
45
  @services[name] = service
44
46
  end
45
47
 
48
+ # Updates details of service record as if the service had been executed.
49
+ # @param service_name [String] name of the service run
50
+ # @return [ServiceRecord] the service record updated
51
+ def update_service_as_performed(service_name)
52
+ service_rec = service(service_name) || add_service(service_name)
53
+ service_rec.run_needed = false
54
+ service_rec.timestamp = ServiceDateHelper.formatted_timestamp
55
+ service_rec
56
+ end
57
+
46
58
  # @param name [String] name identifier of the service to retrieve
47
59
  # @return [ServiceRecord] the ServiceRecord for the service identified by name, or nil
48
60
  def service(name)
@@ -1,19 +1,20 @@
1
1
  require_relative 'service_fields'
2
2
 
3
- # Definition of a preservation service
4
3
  module Longleaf
4
+ # Definition of a configured preservation service
5
5
  class ServiceDefinition
6
6
  attr_reader :name
7
- attr_reader :work_script
7
+ attr_reader :work_script, :work_class
8
8
  attr_reader :frequency, :delay
9
9
  attr_reader :properties
10
10
 
11
- def initialize(name:, work_script:, frequency: nil, delay: nil, properties: Hash.new)
11
+ def initialize(name:, work_script:, work_class: nil, frequency: nil, delay: nil, properties: Hash.new)
12
12
  raise ArgumentError.new("Parameters name and work_script are required") unless name && work_script
13
13
 
14
14
  @properties = properties
15
15
  @name = name
16
16
  @work_script = work_script
17
+ @work_class = work_class
17
18
  @frequency = frequency
18
19
  @delay = delay
19
20
  end
@@ -1,6 +1,8 @@
1
1
  module Longleaf
2
+ # Constants for common configuration fields for preservation service definitions
2
3
  class ServiceFields
3
4
  WORK_SCRIPT = 'work_script'
5
+ WORK_CLASS = 'work_class'
4
6
  FREQUENCY = 'frequency'
5
7
  DELAY = 'delay'
6
8
 
@@ -1,10 +1,13 @@
1
- # Record for an individual service in a file's metadata record.
2
1
  module Longleaf
2
+ # Record for an individual service in a file's metadata record.
3
3
  class ServiceRecord
4
4
  attr_reader :properties
5
5
  attr_accessor :stale_replicas, :timestamp, :run_needed
6
6
 
7
7
  # @param properties [Hash] initial properties for this service record
8
+ # @param stale_replicas [Boolean] whether there are any stale replicas from this service
9
+ # @param timestamp [String] timestamp when this service last ran or was initialized
10
+ # @param run_needed [Boolean] flag indicating that this service should be run at the next available opportunity
8
11
  def initialize(properties: Hash.new, stale_replicas: false, timestamp: nil, run_needed: false)
9
12
  raise ArgumentError.new("Service properties must be a hash") if properties.class != Hash
10
13
 
@@ -1,17 +1,34 @@
1
1
  require 'longleaf/services/metadata_serializer'
2
2
 
3
3
  module Longleaf
4
+ # Representation of a configured storage location
4
5
  class StorageLocation
5
6
  attr_reader :name
6
7
  attr_reader :path
7
8
  attr_reader :metadata_path
9
+ attr_reader :metadata_digests
8
10
 
9
- def initialize(name:, path:, metadata_path:)
11
+ # @param name [String] the name of this storage location
12
+ # @param path [String] absolute path where the storage location is located
13
+ # @param metadata_path [String] absolute path where the metadata for files in this location will be stored.
14
+ # @param metadata_digests list of digest algorithms to use for metadata file digests in this location.
15
+ def initialize(name:, path:, metadata_path:, metadata_digests: [])
10
16
  raise ArgumentError.new("Parameters name, path and metadata_path are required") unless name && path && metadata_path
11
17
 
12
18
  @path = path
19
+ @path += '/' unless @path.end_with?('/')
13
20
  @name = name
14
21
  @metadata_path = metadata_path
22
+ @metadata_path += '/' unless @metadata_path.end_with?('/')
23
+
24
+ if metadata_digests.nil?
25
+ @metadata_digests = []
26
+ elsif metadata_digests.is_a?(String)
27
+ @metadata_digests = [metadata_digests.downcase]
28
+ else
29
+ @metadata_digests = metadata_digests.map(&:downcase)
30
+ end
31
+ DigestHelper::validate_algorithms(@metadata_digests)
15
32
  end
16
33
 
17
34
  # Get the path for the metadata file for the given file path located in this storage location.
@@ -0,0 +1,121 @@
1
+ require 'longleaf/events/event_names'
2
+ require 'longleaf/models/service_fields'
3
+ require 'longleaf/logging'
4
+ require 'longleaf/helpers/digest_helper'
5
+ require 'set'
6
+
7
+ module Longleaf
8
+ # Preservation service which performs one or more fixity checks on a file based on the configured list
9
+ # of digest algorithms. It currently supports 'md5', 'sha1', 'sha2', 'sha256', 'sha384', 'sha512' and 'rmd160'.
10
+ #
11
+ # If the service encounters a file which is missing any of the digest algorithms the service is configured
12
+ # to check, the outcome may be controlled with the 'absent_digest' property via the following values:
13
+ # * 'fail' - the service will raise a ChecksumMismatchError for the missing algorithm. This is the default.
14
+ # * 'ignore' - the service will skip calculating any algorithms not already present for the file.
15
+ # * 'generate' - the service will generate and store any missing digests from the set of configured algorithms.
16
+ class FixityCheckService
17
+ include Longleaf::Logging
18
+
19
+ SUPPORTED_ALGORITHMS = ['md5', 'sha1', 'sha2', 'sha256', 'sha384', 'sha512', 'rmd160']
20
+
21
+ # service configuration property indicating how to handle situations where a file does not
22
+ # have a digest for one of the expected algorithms on record.
23
+ ABSENT_DIGEST_PROPERTY = 'absent_digest'
24
+ FAIL_IF_ABSENT = 'fail'
25
+ GENERATE_IF_ABSENT = 'generate'
26
+ IGNORE_IF_ABSENT = 'ignore'
27
+ ABSENT_DIGEST_OPTIONS = [FAIL_IF_ABSENT, GENERATE_IF_ABSENT, IGNORE_IF_ABSENT]
28
+
29
+ # Initialize a FixityCheckService from the given service definition
30
+ #
31
+ # @param service_def [ServiceDefinition] the configuration for this service
32
+ # @param app_manager [ApplicationConfigManager] manager for configured storage locations
33
+ def initialize(service_def, app_manager)
34
+ @service_def = service_def
35
+ @absent_digest_behavior = @service_def.properties[ABSENT_DIGEST_PROPERTY] || FAIL_IF_ABSENT
36
+ unless ABSENT_DIGEST_OPTIONS.include?(@absent_digest_behavior)
37
+ raise ArgumentError.new("Invalid option '#{@absent_digest_behavior}' for property #{ABSENT_DIGEST_PROPERTY} in service #{service_def.name}")
38
+ end
39
+
40
+ service_algs = service_def.properties[ServiceFields::DIGEST_ALGORITHMS]
41
+ if service_algs.nil? || service_algs.empty?
42
+ raise ArgumentError.new("FixityCheckService from definition #{service_def.name} requires a list of one or more digest algorithms")
43
+ end
44
+
45
+ # Store the list of digest algorithms to verify, using normalized algorithm names.
46
+ @digest_algs = Set.new
47
+ service_algs.each do |alg|
48
+ normalized_alg = alg.downcase.delete('-')
49
+ if SUPPORTED_ALGORITHMS.include?(normalized_alg)
50
+ @digest_algs << normalized_alg
51
+ else
52
+ raise ArgumentError.new("Unsupported checksum algorithm '#{alg}' in definition #{service_def.name}. Supported algorithms are: #{SUPPORTED_ALGORITHMS.to_s}")
53
+ end
54
+ end
55
+ end
56
+
57
+ # Perform all configured fixity checks on the provided file
58
+ #
59
+ # @param file_rec [FileRecord] record representing the file to perform the service on.
60
+ # @param event [String] name of the event this service is being invoked by.
61
+ # @raise [ChecksumMismatchError] if the checksum on record does not match the generated checksum
62
+ def perform(file_rec, event)
63
+ path = file_rec.path
64
+ md_rec = file_rec.metadata_record
65
+
66
+ # Get the list of existing checksums for the file and normalize algorithm names
67
+ file_digests = Hash.new
68
+ md_rec.checksums&.each do |alg, digest|
69
+ normalized_alg = alg.downcase.delete('-')
70
+ if @digest_algs.include?(normalized_alg)
71
+ file_digests[normalized_alg] = digest
72
+ else
73
+ logger.debug("Metadata for file #{path} contains unexpected '#{alg}' digest, it will be ignored.")
74
+ end
75
+ end
76
+
77
+ @digest_algs.each do |alg|
78
+ existing_digest = file_digests[alg]
79
+
80
+ if existing_digest.nil?
81
+ if @absent_digest_behavior == FAIL_IF_ABSENT
82
+ raise ChecksumMismatchError.new("Fixity check using algorithm '#{alg}' failed for file #{path}: no existing digest of type '#{alg}' on record.")
83
+ elsif @absent_digest_behavior == IGNORE_IF_ABSENT
84
+ logger.debug("Skipping check of algorithm '#{alg}' for file #{path}: no digest on record.")
85
+ next
86
+ end
87
+ end
88
+
89
+ digest = DigestHelper::start_digest(alg)
90
+ digest.file(path)
91
+ generated_digest = digest.hexdigest
92
+
93
+ # Store the missing checksum if using the 'generate' behavior
94
+ if existing_digest.nil? && @absent_digest_behavior == GENERATE_IF_ABSENT
95
+ md_rec.checksums[alg] = generated_digest
96
+ logger.info("Generated and stored digest using algorithm '#{alg}' for file #{path}")
97
+ else
98
+ # Compare the new digest to the one on record
99
+ if existing_digest == generated_digest
100
+ logger.info("Fixity check using algorithm '#{alg}' succeeded for file #{path}")
101
+ else
102
+ raise ChecksumMismatchError.new("Fixity check using algorithm '#{alg}' failed for file #{path}: expected '#{existing_digest}', calculated '#{generated_digest}.'")
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ # Determine if this service is applicable for the provided event, given the configured service definition
109
+ #
110
+ # @param event [String] name of the event
111
+ # @return [Boolean] returns true if this service is applicable for the provided event
112
+ def is_applicable?(event)
113
+ case event
114
+ when EventNames::PRESERVE
115
+ true
116
+ else
117
+ false
118
+ end
119
+ end
120
+ end
121
+ end