longleaf 0.2.0.pre.1 → 0.3.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.
Files changed (165) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +84 -0
  3. data/.gitignore +4 -2
  4. data/.rubocop.yml +42 -2
  5. data/.rubocop_todo.yml +390 -311
  6. data/.yardopts +1 -0
  7. data/Gemfile +16 -1
  8. data/README.md +67 -13
  9. data/Rakefile +6 -0
  10. data/bin/setup +16 -1
  11. data/docs/aboutlongleaf.md +28 -0
  12. data/docs/extra.css +32 -0
  13. data/docs/img/change-file.png +0 -0
  14. data/docs/img/ll-example-preserved.png +0 -0
  15. data/docs/index.md +19 -0
  16. data/docs/install.md +66 -0
  17. data/docs/ll-example/config-example-relative.yml +33 -0
  18. data/docs/ll-example/files-dir/LLexample-PDF.pdf +0 -0
  19. data/docs/ll-example/files-dir/LLexample-TOCHANGE.txt +15 -0
  20. data/docs/ll-example/files-dir/LLexample-tokeep.txt +10 -0
  21. data/docs/ll-example/metadata-dir/.gitkeep +0 -0
  22. data/docs/ll-example/replica-files/.gitkeep +0 -0
  23. data/docs/ll-example/replica-metadata/.gitkeep +0 -0
  24. data/docs/quickstart.md +270 -0
  25. data/docs/rdocs/Longleaf.html +135 -0
  26. data/docs/rdocs/Longleaf/AppFields.html +178 -0
  27. data/docs/rdocs/Longleaf/ApplicationConfigDeserializer.html +631 -0
  28. data/docs/rdocs/Longleaf/ApplicationConfigManager.html +610 -0
  29. data/docs/rdocs/Longleaf/ApplicationConfigValidator.html +238 -0
  30. data/docs/rdocs/Longleaf/CLI.html +909 -0
  31. data/docs/rdocs/Longleaf/ChecksumMismatchError.html +151 -0
  32. data/docs/rdocs/Longleaf/ConfigBuilder.html +1339 -0
  33. data/docs/rdocs/Longleaf/ConfigurationError.html +143 -0
  34. data/docs/rdocs/Longleaf/ConfigurationValidator.html +227 -0
  35. data/docs/rdocs/Longleaf/DeregisterCommand.html +420 -0
  36. data/docs/rdocs/Longleaf/DeregisterEvent.html +453 -0
  37. data/docs/rdocs/Longleaf/DeregistrationError.html +151 -0
  38. data/docs/rdocs/Longleaf/DigestHelper.html +419 -0
  39. data/docs/rdocs/Longleaf/EventError.html +147 -0
  40. data/docs/rdocs/Longleaf/EventNames.html +163 -0
  41. data/docs/rdocs/Longleaf/EventStatusTracking.html +656 -0
  42. data/docs/rdocs/Longleaf/FileCheckService.html +540 -0
  43. data/docs/rdocs/Longleaf/FileHelpers.html +520 -0
  44. data/docs/rdocs/Longleaf/FileRecord.html +716 -0
  45. data/docs/rdocs/Longleaf/FileSelector.html +901 -0
  46. data/docs/rdocs/Longleaf/FixityCheckService.html +691 -0
  47. data/docs/rdocs/Longleaf/IndexManager.html +1155 -0
  48. data/docs/rdocs/Longleaf/InvalidDigestAlgorithmError.html +143 -0
  49. data/docs/rdocs/Longleaf/InvalidStoragePathError.html +143 -0
  50. data/docs/rdocs/Longleaf/Logging.html +405 -0
  51. data/docs/rdocs/Longleaf/Logging/RedirectingLogger.html +1213 -0
  52. data/docs/rdocs/Longleaf/LongleafError.html +139 -0
  53. data/docs/rdocs/Longleaf/MDFields.html +193 -0
  54. data/docs/rdocs/Longleaf/MetadataBuilder.html +787 -0
  55. data/docs/rdocs/Longleaf/MetadataDeserializer.html +537 -0
  56. data/docs/rdocs/Longleaf/MetadataError.html +143 -0
  57. data/docs/rdocs/Longleaf/MetadataPersistenceManager.html +539 -0
  58. data/docs/rdocs/Longleaf/MetadataRecord.html +1411 -0
  59. data/docs/rdocs/Longleaf/MetadataSerializer.html +786 -0
  60. data/docs/rdocs/Longleaf/PreservationServiceError.html +147 -0
  61. data/docs/rdocs/Longleaf/PreserveCommand.html +410 -0
  62. data/docs/rdocs/Longleaf/PreserveEvent.html +491 -0
  63. data/docs/rdocs/Longleaf/RegisterCommand.html +428 -0
  64. data/docs/rdocs/Longleaf/RegisterEvent.html +628 -0
  65. data/docs/rdocs/Longleaf/RegisteredFileSelector.html +446 -0
  66. data/docs/rdocs/Longleaf/RegistrationError.html +151 -0
  67. data/docs/rdocs/Longleaf/ReindexCommand.html +576 -0
  68. data/docs/rdocs/Longleaf/RsyncReplicationService.html +1180 -0
  69. data/docs/rdocs/Longleaf/SequelIndexDriver.html +1978 -0
  70. data/docs/rdocs/Longleaf/ServiceCandidateFilesystemIterator.html +572 -0
  71. data/docs/rdocs/Longleaf/ServiceCandidateIndexIterator.html +532 -0
  72. data/docs/rdocs/Longleaf/ServiceCandidateLocator.html +333 -0
  73. data/docs/rdocs/Longleaf/ServiceClassCache.html +725 -0
  74. data/docs/rdocs/Longleaf/ServiceDateHelper.html +425 -0
  75. data/docs/rdocs/Longleaf/ServiceDefinition.html +683 -0
  76. data/docs/rdocs/Longleaf/ServiceDefinitionManager.html +371 -0
  77. data/docs/rdocs/Longleaf/ServiceDefinitionValidator.html +269 -0
  78. data/docs/rdocs/Longleaf/ServiceFields.html +173 -0
  79. data/docs/rdocs/Longleaf/ServiceManager.html +1229 -0
  80. data/docs/rdocs/Longleaf/ServiceMappingManager.html +410 -0
  81. data/docs/rdocs/Longleaf/ServiceMappingValidator.html +347 -0
  82. data/docs/rdocs/Longleaf/ServiceRecord.html +821 -0
  83. data/docs/rdocs/Longleaf/StorageLocation.html +985 -0
  84. data/docs/rdocs/Longleaf/StorageLocationManager.html +729 -0
  85. data/docs/rdocs/Longleaf/StorageLocationUnavailableError.html +143 -0
  86. data/docs/rdocs/Longleaf/StorageLocationValidator.html +373 -0
  87. data/docs/rdocs/Longleaf/StoragePathValidator.html +253 -0
  88. data/docs/rdocs/Longleaf/SystemConfigBuilder.html +441 -0
  89. data/docs/rdocs/Longleaf/SystemConfigFields.html +163 -0
  90. data/docs/rdocs/Longleaf/ValidateConfigCommand.html +451 -0
  91. data/docs/rdocs/Longleaf/ValidateMetadataCommand.html +408 -0
  92. data/docs/rdocs/_index.html +660 -0
  93. data/docs/rdocs/class_list.html +51 -0
  94. data/docs/rdocs/css/common.css +1 -0
  95. data/docs/rdocs/css/full_list.css +58 -0
  96. data/docs/rdocs/css/style.css +496 -0
  97. data/docs/rdocs/file.README.html +165 -0
  98. data/docs/rdocs/file_list.html +56 -0
  99. data/docs/rdocs/frames.html +17 -0
  100. data/docs/rdocs/index.html +165 -0
  101. data/docs/rdocs/js/app.js +303 -0
  102. data/docs/rdocs/js/full_list.js +216 -0
  103. data/docs/rdocs/js/jquery.js +4 -0
  104. data/docs/rdocs/method_list.html +2051 -0
  105. data/docs/rdocs/top-level-namespace.html +110 -0
  106. data/lib/longleaf/candidates/file_selector.rb +47 -15
  107. data/lib/longleaf/candidates/registered_file_selector.rb +67 -0
  108. data/lib/longleaf/candidates/service_candidate_filesystem_iterator.rb +29 -35
  109. data/lib/longleaf/candidates/service_candidate_index_iterator.rb +84 -0
  110. data/lib/longleaf/candidates/service_candidate_locator.rb +9 -4
  111. data/lib/longleaf/cli.rb +162 -80
  112. data/lib/longleaf/commands/deregister_command.rb +12 -11
  113. data/lib/longleaf/commands/preserve_command.rb +13 -8
  114. data/lib/longleaf/commands/register_command.rb +9 -6
  115. data/lib/longleaf/commands/reindex_command.rb +92 -0
  116. data/lib/longleaf/commands/validate_config_command.rb +27 -6
  117. data/lib/longleaf/commands/validate_metadata_command.rb +11 -9
  118. data/lib/longleaf/errors.rb +12 -12
  119. data/lib/longleaf/events/deregister_event.rb +13 -15
  120. data/lib/longleaf/events/event_status_tracking.rb +7 -7
  121. data/lib/longleaf/events/preserve_event.rb +24 -14
  122. data/lib/longleaf/events/register_event.rb +21 -35
  123. data/lib/longleaf/helpers/digest_helper.rb +4 -4
  124. data/lib/longleaf/helpers/service_date_helper.rb +5 -6
  125. data/lib/longleaf/indexing/index_manager.rb +101 -0
  126. data/lib/longleaf/indexing/sequel_index_driver.rb +324 -0
  127. data/lib/longleaf/logging.rb +4 -4
  128. data/lib/longleaf/logging/redirecting_logger.rb +20 -20
  129. data/lib/longleaf/models/app_fields.rb +2 -1
  130. data/lib/longleaf/models/file_record.rb +10 -6
  131. data/lib/longleaf/models/md_fields.rb +1 -1
  132. data/lib/longleaf/models/metadata_record.rb +22 -12
  133. data/lib/longleaf/models/service_definition.rb +3 -3
  134. data/lib/longleaf/models/service_fields.rb +1 -1
  135. data/lib/longleaf/models/service_record.rb +6 -5
  136. data/lib/longleaf/models/storage_location.rb +26 -7
  137. data/lib/longleaf/models/system_config_fields.rb +9 -0
  138. data/lib/longleaf/preservation_services/file_check_service.rb +58 -0
  139. data/lib/longleaf/preservation_services/fixity_check_service.rb +16 -14
  140. data/lib/longleaf/preservation_services/rsync_replication_service.rb +32 -31
  141. data/lib/longleaf/services/application_config_deserializer.rb +55 -18
  142. data/lib/longleaf/services/application_config_manager.rb +16 -4
  143. data/lib/longleaf/services/application_config_validator.rb +1 -2
  144. data/lib/longleaf/services/configuration_validator.rb +6 -4
  145. data/lib/longleaf/services/metadata_deserializer.rb +40 -38
  146. data/lib/longleaf/services/metadata_persistence_manager.rb +46 -0
  147. data/lib/longleaf/services/metadata_serializer.rb +23 -22
  148. data/lib/longleaf/services/service_class_cache.rb +15 -15
  149. data/lib/longleaf/services/service_definition_manager.rb +5 -6
  150. data/lib/longleaf/services/service_definition_validator.rb +5 -6
  151. data/lib/longleaf/services/service_manager.rb +37 -17
  152. data/lib/longleaf/services/service_mapping_manager.rb +9 -9
  153. data/lib/longleaf/services/service_mapping_validator.rb +9 -10
  154. data/lib/longleaf/services/storage_location_manager.rb +22 -8
  155. data/lib/longleaf/services/storage_location_validator.rb +11 -8
  156. data/lib/longleaf/services/storage_path_validator.rb +1 -1
  157. data/lib/longleaf/specs/config_builder.rb +30 -17
  158. data/lib/longleaf/specs/custom_matchers.rb +1 -1
  159. data/lib/longleaf/specs/file_helpers.rb +15 -14
  160. data/lib/longleaf/specs/metadata_builder.rb +91 -0
  161. data/lib/longleaf/specs/system_config_builder.rb +27 -0
  162. data/lib/longleaf/version.rb +1 -1
  163. data/longleaf.gemspec +17 -7
  164. data/mkdocs.yml +20 -0
  165. metadata +233 -22
@@ -7,16 +7,16 @@ module Longleaf
7
7
  def logger
8
8
  Logging.logger
9
9
  end
10
-
10
+
11
11
  # Get the main logger for longleaf
12
12
  def self.logger
13
13
  @logger ||= RedirectingLogger.new
14
14
  end
15
-
15
+
16
16
  def initialize_logger(failure_only, log_level, log_format, datetime_format)
17
17
  Logging.initialize_logger(failure_only, log_level, log_format, datetime_format)
18
18
  end
19
-
19
+
20
20
  def self.initialize_logger(failure_only, log_level, log_format, datetime_format)
21
21
  @logger = RedirectingLogger.new(failure_only: failure_only,
22
22
  log_level: log_level,
@@ -24,4 +24,4 @@ module Longleaf
24
24
  datetime_format: datetime_format)
25
25
  end
26
26
  end
27
- end
27
+ end
@@ -25,12 +25,12 @@ module Longleaf
25
25
  @stderr_log.formatter = proc do |severity, datetime, progname, msg|
26
26
  # Make sure the format ends with a newline
27
27
  @log_format = @log_format + "\n" unless @log_format.end_with?("\n")
28
-
28
+
29
29
  formatted_date = @stderr_log.datetime_format.nil? ? datetime : datetime.strftime(datetime_format)
30
30
  @log_format % { :severity => severity, :datetime => formatted_date, :progname => progname, :msg => msg }
31
31
  end
32
32
  end
33
-
33
+
34
34
  @stdout_log = Logger.new($stdout)
35
35
  @stdout_log.formatter = proc do |severity, datetime, progname, msg|
36
36
  "#{msg}\n"
@@ -41,34 +41,34 @@ module Longleaf
41
41
  @stdout_log.level = 'info'
42
42
  end
43
43
  end
44
-
44
+
45
45
  def debug(progname = nil, &block)
46
46
  @stderr_log.debug(progname, &block)
47
47
  end
48
-
48
+
49
49
  def info(progname = nil, &block)
50
50
  @stderr_log.info(progname, &block)
51
51
  end
52
-
52
+
53
53
  def warn(progname = nil, &block)
54
54
  @stderr_log.warn(progname, &block)
55
55
  end
56
-
56
+
57
57
  def error(progname = nil, &block)
58
58
  @stderr_log.error(progname, &block)
59
59
  end
60
-
60
+
61
61
  def fatal(progname = nil, &block)
62
62
  @stderr_log.fatal(progname, &block)
63
63
  end
64
-
64
+
65
65
  def unknown(progname = nil, &block)
66
66
  @stderr_log.unknown(progname, &block)
67
67
  end
68
-
68
+
69
69
  # Logs a success message to STDOUT, as well as STDERR at info level.
70
- #
71
- # @param [String] eventOrMessage name of the preservation event which succeeded,
70
+ #
71
+ # @param [String] eventOrMessage name of the preservation event which succeeded,
72
72
  # or the message to output if it is the only parameter. Required.
73
73
  # @param file_name [String] file name which is the subject of this message.
74
74
  # @param message [String] descriptive message to accompany this output
@@ -76,10 +76,10 @@ module Longleaf
76
76
  def success(eventOrMessage, file_name = nil, message = nil, service = nil)
77
77
  outcome('SUCCESS', eventOrMessage, file_name, message, service)
78
78
  end
79
-
79
+
80
80
  # Logs a failure message to STDOUT, as well as STDERR at info level.
81
81
  # If an error was provided, it is logged to STDERR at error level.
82
- # @param eventOrMessage [String] name of the preservation event which failed,
82
+ # @param eventOrMessage [String] name of the preservation event which failed,
83
83
  # or the message to output if it is the only parameter.
84
84
  # @param file_name [String] file name which is the subject of this message.
85
85
  # @param message [String] descriptive message to accompany this output
@@ -88,17 +88,17 @@ module Longleaf
88
88
  def failure(eventOrMessage, file_name = nil, message = nil, service = nil, error: nil)
89
89
  text = outcome_text('FAILURE', eventOrMessage, file_name, message, service, error)
90
90
  @stdout_log.warn(text)
91
-
91
+
92
92
  @stderr_log.info(text)
93
93
  @stderr_log.error("#{error.message}") unless error.nil?
94
- @stderr_log.error("#{error.backtrace.to_s}") unless error.nil? || error.backtrace.nil?
94
+ @stderr_log.error("#{error.backtrace}") unless error.nil? || error.backtrace.nil?
95
95
  end
96
-
96
+
97
97
  # Logs an outcome message to STDOUT, as well as STDERR at info level.
98
98
  # If file_name and message are nil, eventOrMessage will be used as the message.
99
99
  #
100
100
  # @param outcome [String] The status of the outcome. Required.
101
- # @param eventOrMessage [String] name of the preservation event which was successful,
101
+ # @param eventOrMessage [String] name of the preservation event which was successful,
102
102
  # or the message to output if it is the only parameter. Required.
103
103
  # @param file_name [String] file name which is the subject of this message.
104
104
  # @param message [String] descriptive message to accompany this output
@@ -109,13 +109,13 @@ module Longleaf
109
109
  @stdout_log.info(text)
110
110
  @stderr_log.info(text)
111
111
  end
112
-
112
+
113
113
  private
114
114
  def outcome_text(outcome, eventOrMessage, file_name = nil, message = nil, service = nil, error = nil)
115
115
  message_only = file_name.nil? && message.nil? && error.nil?
116
-
116
+
117
117
  text = "#{outcome}"
118
-
118
+
119
119
  if message_only
120
120
  text << ": #{eventOrMessage}"
121
121
  else
@@ -4,7 +4,8 @@ module Longleaf
4
4
  LOCATIONS = 'locations'
5
5
  SERVICES = 'services'
6
6
  SERVICE_MAPPINGS = 'service_mappings'
7
-
7
+ SYSTEM = 'system'
8
+
8
9
  LOCATION_PATH = 'path'
9
10
  METADATA_PATH = 'metadata_path'
10
11
  METADATA_DIGESTS = 'metadata_digests'
@@ -1,30 +1,34 @@
1
1
  module Longleaf
2
2
  # Record for an individual file and its associated information
3
3
  class FileRecord
4
-
5
4
  attr_accessor :metadata_record
6
5
  attr_reader :storage_location
7
6
  attr_reader :path
8
-
7
+
9
8
  # @param file_path [String] path to the file
10
9
  # @param storage_location [StorageLocation] storage location containing the file
11
10
  def initialize(file_path, storage_location, metadata_record = nil)
12
11
  raise ArgumentError.new("FileRecord requires a path") if file_path.nil?
13
12
  raise ArgumentError.new("FileRecord requires a storage_location") if storage_location.nil?
14
-
13
+
15
14
  @path = file_path
16
15
  @storage_location = storage_location
17
16
  @metadata_record = metadata_record
18
17
  end
19
-
18
+
20
19
  # @return [String] path for the metadata file for this file
21
20
  def metadata_path
22
21
  @metadata_path = @storage_location.get_metadata_path_for(path) if @metadata_path.nil?
23
22
  @metadata_path
24
23
  end
25
-
24
+
26
25
  def metadata_present?
27
26
  File.exist?(metadata_path)
28
27
  end
28
+
29
+ def ==(other_obj)
30
+ return false unless other_obj.is_a?(FileRecord)
31
+ path == other_obj.path
32
+ end
29
33
  end
30
- end
34
+ end
@@ -3,7 +3,7 @@ module Longleaf
3
3
  class MDFields
4
4
  DATA = 'data'
5
5
  SERVICES = 'services'
6
-
6
+
7
7
  REGISTERED_TIMESTAMP = 'registered'
8
8
  DEREGISTERED_TIMESTAMP = 'deregistered'
9
9
 
@@ -9,7 +9,7 @@ module Longleaf
9
9
  attr_reader :checksums
10
10
  attr_reader :properties
11
11
  attr_accessor :file_size, :last_modified
12
-
12
+
13
13
  # @param properties [Hash] initial data properties for this record
14
14
  # @param services [Hash] initial service property tree
15
15
  # @param deregistered [String] deregistered timestamp
@@ -17,22 +17,22 @@ module Longleaf
17
17
  # @param checksums [Hash] hash of checksum values
18
18
  # @param file_size [Integer] size of file in bytes
19
19
  # @param last_modified [String] iso8601 representation of the last modified date of file
20
- def initialize(properties: Hash.new, services: Hash.new, deregistered: nil, registered: nil, checksums: Hash.new,
20
+ def initialize(properties: nil, services: nil, deregistered: nil, registered: nil, checksums: nil,
21
21
  file_size: nil, last_modified: nil)
22
- @properties = properties
22
+ @properties = properties || Hash.new
23
23
  @registered = registered
24
24
  @deregistered = deregistered
25
- @checksums = checksums
26
- @services = services
25
+ @checksums = checksums || Hash.new
26
+ @services = services || Hash.new
27
27
  @file_size = file_size
28
28
  @last_modified = last_modified
29
29
  end
30
-
30
+
31
31
  # @return [Boolean] true if the record is deregistered
32
32
  def deregistered?
33
33
  !@deregistered.nil?
34
34
  end
35
-
35
+
36
36
  # Adds a service to this record
37
37
  #
38
38
  # @param name [String] identifier for the service being added
@@ -41,10 +41,10 @@ module Longleaf
41
41
  def add_service(name, service = ServiceRecord.new)
42
42
  raise ArgumentError.new("Value must be a ServiceRecord object when adding a service") unless service.class == Longleaf::ServiceRecord
43
43
  raise IndexError.new("Service with name '#{name}' already exists") if @services.key?(name)
44
-
44
+
45
45
  @services[name] = service
46
46
  end
47
-
47
+
48
48
  # Updates details of service record as if the service had been executed.
49
49
  # @param service_name [String] name of the service run
50
50
  # @return [ServiceRecord] the service record updated
@@ -54,16 +54,26 @@ module Longleaf
54
54
  service_rec.timestamp = ServiceDateHelper.formatted_timestamp
55
55
  service_rec
56
56
  end
57
-
57
+
58
+ # Updates details of service record as if the service had encountered a
59
+ # failure during execution.
60
+ # @param service_name [String] name of the service run
61
+ # @return [ServiceRecord] the service record updated
62
+ def update_service_as_failed(service_name)
63
+ service_rec = service(service_name) || add_service(service_name)
64
+ service_rec.failure_timestamp = ServiceDateHelper.formatted_timestamp
65
+ service_rec
66
+ end
67
+
58
68
  # @param name [String] name identifier of the service to retrieve
59
69
  # @return [ServiceRecord] the ServiceRecord for the service identified by name, or nil
60
70
  def service(name)
61
71
  @services[name]
62
72
  end
63
-
73
+
64
74
  # @return [Array<String>] a list of name identifiers for services registered to this record
65
75
  def list_services
66
76
  @services.keys
67
77
  end
68
78
  end
69
- end
79
+ end
@@ -7,10 +7,10 @@ module Longleaf
7
7
  attr_reader :work_script, :work_class
8
8
  attr_reader :frequency, :delay
9
9
  attr_reader :properties
10
-
10
+
11
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
@@ -19,4 +19,4 @@ module Longleaf
19
19
  @delay = delay
20
20
  end
21
21
  end
22
- end
22
+ end
@@ -5,7 +5,7 @@ module Longleaf
5
5
  WORK_CLASS = 'work_class'
6
6
  FREQUENCY = 'frequency'
7
7
  DELAY = 'delay'
8
-
8
+
9
9
  REPLICATE_TO = 'to'
10
10
  DIGEST_ALGORITHMS = 'algorithms'
11
11
  end
@@ -3,28 +3,29 @@ module Longleaf
3
3
  class ServiceRecord
4
4
  attr_reader :properties
5
5
  attr_accessor :stale_replicas, :timestamp, :run_needed
6
-
6
+ attr_accessor :failure_timestamp
7
+
7
8
  # @param properties [Hash] initial properties for this service record
8
9
  # @param stale_replicas [Boolean] whether there are any stale replicas from this service
9
10
  # @param timestamp [String] timestamp when this service last ran or was initialized
10
11
  # @param run_needed [Boolean] flag indicating that this service should be run at the next available opportunity
11
12
  def initialize(properties: Hash.new, stale_replicas: false, timestamp: nil, run_needed: false)
12
13
  raise ArgumentError.new("Service properties must be a hash") if properties.class != Hash
13
-
14
+
14
15
  @properties = properties
15
16
  @timestamp = timestamp
16
17
  @stale_replicas = stale_replicas
17
18
  @run_needed = run_needed
18
19
  end
19
-
20
+
20
21
  # @return the value of a service property identified by key
21
22
  def [](key)
22
23
  @properties[key]
23
24
  end
24
-
25
+
25
26
  # set the value of a service property identified by key
26
27
  def []=(key, value)
27
28
  @properties[key] = value
28
29
  end
29
30
  end
30
- end
31
+ end
@@ -7,20 +7,20 @@ module Longleaf
7
7
  attr_reader :path
8
8
  attr_reader :metadata_path
9
9
  attr_reader :metadata_digests
10
-
10
+
11
11
  # @param name [String] the name of this storage location
12
12
  # @param path [String] absolute path where the storage location is located
13
13
  # @param metadata_path [String] absolute path where the metadata for files in this location will be stored.
14
14
  # @param metadata_digests list of digest algorithms to use for metadata file digests in this location.
15
15
  def initialize(name:, path:, metadata_path:, metadata_digests: [])
16
16
  raise ArgumentError.new("Parameters name, path and metadata_path are required") unless name && path && metadata_path
17
-
17
+
18
18
  @path = path
19
19
  @path += '/' unless @path.end_with?('/')
20
20
  @name = name
21
21
  @metadata_path = metadata_path
22
22
  @metadata_path += '/' unless @metadata_path.end_with?('/')
23
-
23
+
24
24
  if metadata_digests.nil?
25
25
  @metadata_digests = []
26
26
  elsif metadata_digests.is_a?(String)
@@ -30,7 +30,7 @@ module Longleaf
30
30
  end
31
31
  DigestHelper::validate_algorithms(@metadata_digests)
32
32
  end
33
-
33
+
34
34
  # Get the path for the metadata file for the given file path located in this storage location.
35
35
  # @param file_path [String] path of the file
36
36
  # @raise [ArgumentError] if the file_path is not provided or is not in this storage location.
@@ -39,9 +39,28 @@ module Longleaf
39
39
  raise ArgumentError.new("Provided file path is not contained by storage location #{@name}: #{file_path}") \
40
40
  unless file_path.start_with?(@path)
41
41
 
42
- file_path.sub(/^#{@path}/, metadata_path) + MetadataSerializer::metadata_suffix
42
+ md_path = file_path.sub(/^#{@path}/, @metadata_path)
43
+ # If the file_path is to a file, then add metadata suffix.
44
+ if md_path.end_with?('/')
45
+ md_path
46
+ else
47
+ md_path + MetadataSerializer::metadata_suffix
48
+ end
49
+ end
50
+
51
+ # Get the metadata path for the provided file path located in this storage location.
52
+ # @param md_path [String] metadata file path
53
+ # @raise [ArgumentError] if the md_path is not in this storage location
54
+ # @return [String] the path for the file associated with this metadata
55
+ def get_path_from_metadata_path(md_path)
56
+ raise ArgumentError.new("A file_path parameter is required") if md_path.nil? || md_path.empty?
57
+ raise ArgumentError.new("Provided metadata path is not contained by storage location #{@name}: #{md_path}") \
58
+ unless md_path&.start_with?(@metadata_path)
59
+
60
+ file_path = md_path.sub(/^#{@metadata_path}/, @path)
61
+ file_path.sub(/#{MetadataSerializer::metadata_suffix}$/, '')
43
62
  end
44
-
63
+
45
64
  # Checks that the path and metadata path defined in this location are available
46
65
  # @raise [StorageLocationUnavailableError] if the storage location is not available
47
66
  def available?
@@ -51,4 +70,4 @@ module Longleaf
51
70
  unless Dir.exist?(@metadata_path)
52
71
  end
53
72
  end
54
- end
73
+ end
@@ -0,0 +1,9 @@
1
+ module Longleaf
2
+ # System configuration field names
3
+ class SystemConfigFields
4
+ MD_INDEX = 'index'
5
+ MD_INDEX_ADAPTER = 'adapter'
6
+ MD_INDEX_CONNECTION = 'connection'
7
+ MD_INDEX_PAGE_SIZE = 'page_size'
8
+ end
9
+ end
@@ -0,0 +1,58 @@
1
+ require 'longleaf/events/event_names'
2
+ require 'longleaf/logging'
3
+
4
+ module Longleaf
5
+ # Preservation service which validates a file using current filesystem information compared against the
6
+ # last registered details for that file. Checks using file name, size and last modified timestamp.
7
+ class FileCheckService
8
+ include Longleaf::Logging
9
+
10
+ # Initialize a FileCheckService from the given service definition
11
+ #
12
+ # @param service_def [ServiceDefinition] the configuration for this service
13
+ # @param app_manager [ApplicationConfigManager] manager for configured storage locations
14
+ def initialize(service_def, app_manager)
15
+ @service_def = service_def
16
+ @app_manager = app_manager
17
+ end
18
+
19
+ # Perform file information check.
20
+ #
21
+ # @param file_rec [FileRecord] record representing the file to perform the service on.
22
+ # @param event [String] name of the event this service is being invoked by.
23
+ # @raise [PreservationServiceError] if the file system information does not match the stored details
24
+ def perform(file_rec, event)
25
+ file_path = file_rec.path
26
+ md_rec = file_rec.metadata_record
27
+
28
+ logger.debug("Performing file information check of #{file_path}")
29
+
30
+ if !File.exist?(file_path)
31
+ raise PreservationServiceError.new("File does not exist: #{file_path}")
32
+ end
33
+
34
+ file_size = File.size(file_rec.path)
35
+ if file_size != md_rec.file_size
36
+ raise PreservationServiceError.new("File size for #{file_path} does not match the expected value: registered = #{md_rec.file_size} bytes, actual = #{file_size} bytes")
37
+ end
38
+
39
+ last_modified = File.mtime(file_rec.path).utc.iso8601(3)
40
+ if last_modified != md_rec.last_modified
41
+ raise PreservationServiceError.new("Last modified timestamp for #{file_path} does not match the expected value: registered = #{md_rec.last_modified}, actual = #{last_modified}")
42
+ end
43
+ end
44
+
45
+ # Determine if this service is applicable for the provided event, given the configured service definition
46
+ #
47
+ # @param event [String] name of the event
48
+ # @return [Boolean] returns true if this service is applicable for the provided event
49
+ def is_applicable?(event)
50
+ case event
51
+ when EventNames::PRESERVE
52
+ true
53
+ else
54
+ false
55
+ end
56
+ end
57
+ end
58
+ end