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
@@ -0,0 +1,46 @@
1
+ require 'longleaf/services/metadata_serializer'
2
+ require 'longleaf/errors'
3
+
4
+ module Longleaf
5
+ # Handles the persistence of metadata records
6
+ class MetadataPersistenceManager
7
+ # Initialize the MetadataPersistenceManager
8
+ # @param index_manager [IndexManager] system config manager
9
+ def initialize(index_manager)
10
+ @index_manager = index_manager
11
+ end
12
+
13
+ # Persist the metadata for the provided file record to all configured destinations.
14
+ # This may include to disk as well as to an index.
15
+ # @param file_rec [FileRecord] file record
16
+ def persist(file_rec)
17
+ if file_rec.metadata_record.nil?
18
+ raise MetadataError.new("No metadata record provided, cannot persist metadata for #{file_rec.path}")
19
+ end
20
+
21
+ MetadataSerializer::write(metadata: file_rec.metadata_record,
22
+ file_path: file_rec.metadata_path,
23
+ digest_algs: file_rec.storage_location.metadata_digests)
24
+
25
+ index(file_rec)
26
+ end
27
+
28
+ # Index metadata for the provided file record
29
+ # @param file_rec [FileRecord] file record
30
+ def index(file_rec)
31
+ if @index_manager.using_index?
32
+ @index_manager.index(file_rec)
33
+ end
34
+ end
35
+
36
+ # Load the metadata record for the provided file record
37
+ # @param file_rec [FileRecord] file record
38
+ # @return [MetadataRecord] the metadata record for the file record
39
+ def load(file_rec)
40
+ md_rec = MetadataDeserializer.deserialize(file_path: file_rec.metadata_path,
41
+ digest_algs: file_rec.storage_location.metadata_digests)
42
+ file_rec.metadata_record = md_rec
43
+ md_rec
44
+ end
45
+ end
46
+ end
@@ -11,7 +11,7 @@ module Longleaf
11
11
  class MetadataSerializer
12
12
  extend Longleaf::Logging
13
13
  MDF ||= MDFields
14
-
14
+
15
15
  # Serialize the contents of the provided metadata record to the specified path
16
16
  #
17
17
  # @param metadata [MetadataRecord] metadata record to serialize. Required.
@@ -22,57 +22,57 @@ module Longleaf
22
22
  def self.write(metadata:, file_path:, format: 'yaml', digest_algs: [])
23
23
  raise ArgumentError.new('metadata parameter must be a MetadataRecord') \
24
24
  unless metadata.class == MetadataRecord
25
-
25
+
26
26
  case format
27
27
  when 'yaml'
28
28
  content = to_yaml(metadata)
29
29
  else
30
- raise ArgumentError.new('Invalid serialization format #{format} specified')
30
+ raise ArgumentError.new("Invalid serialization format #{format} specified")
31
31
  end
32
-
32
+
33
33
  # Fill in parent directories if they do not exist
34
34
  parent_dir = Pathname(file_path).parent
35
35
  parent_dir.mkpath unless parent_dir.exist?
36
-
36
+
37
37
  File.write(file_path, content)
38
38
  write_digests(file_path, content, digest_algs)
39
39
  end
40
-
40
+
41
41
  # @param metadata [MetadataRecord] metadata record to transform
42
42
  # @return [String] a yaml representation of the provided MetadataRecord
43
43
  def self.to_yaml(metadata)
44
44
  props = to_hash(metadata)
45
45
  props.to_yaml
46
46
  end
47
-
47
+
48
48
  # Create a hash representation of the given MetadataRecord file
49
49
  # @param metadata [MetadataRecord] metadata record to transform into a hash
50
50
  def self.to_hash(metadata)
51
51
  props = Hash.new
52
-
52
+
53
53
  data = Hash.new.merge(metadata.properties)
54
54
  data[MDF::REGISTERED_TIMESTAMP] = metadata.registered if metadata.registered
55
55
  data[MDF::DEREGISTERED_TIMESTAMP] = metadata.deregistered if metadata.deregistered
56
- data[MDF::CHECKSUMS] = metadata.checksums unless metadata.checksums&.empty?
56
+ data[MDF::CHECKSUMS] = metadata.checksums unless metadata.checksums && metadata.checksums.empty?
57
57
  data[MDF::FILE_SIZE] = metadata.file_size unless metadata.file_size.nil?
58
58
  data[MDF::LAST_MODIFIED] = metadata.last_modified if metadata.last_modified
59
-
59
+
60
60
  props[MDF::DATA] = data
61
-
61
+
62
62
  services = Hash.new
63
63
  metadata.list_services.each do |name|
64
64
  service = metadata.service(name)
65
65
  service[MDF::STALE_REPLICAS] = service.stale_replicas if service.stale_replicas
66
66
  service[MDF::SERVICE_TIMESTAMP] = service.timestamp unless service.timestamp.nil?
67
67
  service[MDF::RUN_NEEDED] = service.run_needed if service.run_needed
68
- services[name] = service.properties
68
+ services[name] = service.properties unless service.properties.empty?
69
69
  end
70
-
70
+
71
71
  props[MDF::SERVICES] = services
72
-
72
+
73
73
  props
74
74
  end
75
-
75
+
76
76
  # @param format [String] encoding format used for metadata file
77
77
  # @return [String] the suffix used to indicate that a file is a metadata file in the provided encoding
78
78
  # @raise [ArgumentError] raised if the provided format is not a supported metadata encoding format
@@ -81,14 +81,13 @@ module Longleaf
81
81
  when 'yaml'
82
82
  '-llmd.yaml'
83
83
  else
84
- raise ArgumentError.new('Invalid serialization format #{format} specified')
84
+ raise ArgumentError.new("Invalid serialization format #{format} specified")
85
85
  end
86
86
  end
87
-
88
- private
87
+
89
88
  def self.write_digests(file_path, content, digests)
90
89
  return if digests.nil? || digests.empty?
91
-
90
+
92
91
  digests.each do |alg|
93
92
  digest_class = DigestHelper::start_digest(alg)
94
93
  result = digest_class.hexdigest(content)
@@ -97,11 +96,13 @@ module Longleaf
97
96
  else
98
97
  digest_path = "#{file_path}.#{alg}"
99
98
  end
100
-
99
+
101
100
  File.write(digest_path, result)
102
-
101
+
103
102
  self.logger.debug("Generated #{alg} digest for metadata file #{file_path}: #{result}")
104
103
  end
105
104
  end
105
+
106
+ private_class_method :write_digests
106
107
  end
107
- end
108
+ end
@@ -4,7 +4,7 @@ module Longleaf
4
4
  # Cache for loading and retrieving preservation service classes
5
5
  class ServiceClassCache
6
6
  STD_PRESERVATION_SERVICE_PATH = 'longleaf/preservation_services/'
7
-
7
+
8
8
  def initialize(app_manager)
9
9
  @app_manager = app_manager
10
10
  # Cache storing per service definition instances of service classes
@@ -12,7 +12,7 @@ module Longleaf
12
12
  # Cache storing per script path class of service
13
13
  @class_cache = Hash.new
14
14
  end
15
-
15
+
16
16
  # Returns an instance of the preversation service defined for the provided service definition,
17
17
  # based on the work_script and work_class properties provided.
18
18
  #
@@ -24,12 +24,12 @@ module Longleaf
24
24
  if @service_instance_cache.key?(service_name)
25
25
  return @service_instance_cache[service_name]
26
26
  end
27
-
27
+
28
28
  clazz = service_class(service_def)
29
29
  # Cache and return the class instance
30
30
  @service_instance_cache[service_name] = clazz.new(service_def, @app_manager)
31
31
  end
32
-
32
+
33
33
  # Load and return the PreservationService class assigned to the provided service definition,
34
34
  # based on the work_script and work_class properties provided.
35
35
  #
@@ -38,13 +38,13 @@ module Longleaf
38
38
  def service_class(service_def)
39
39
  service_name = service_def.name
40
40
  work_script = service_def.work_script
41
-
41
+
42
42
  if work_script.include?('/')
43
43
  expanded_path = Pathname.new(work_script).expand_path.to_s
44
44
  if !from_permitted_path?(expanded_path)
45
45
  raise ConfigurationError.new("Unable to load work_script for service #{service_name}, #{work_script} is not in a known library path.")
46
46
  end
47
-
47
+
48
48
  last_slash_index = work_script.rindex('/')
49
49
  script_path = work_script[0..last_slash_index]
50
50
  script_name = work_script[(last_slash_index + 1)..-1]
@@ -52,23 +52,23 @@ module Longleaf
52
52
  script_path = STD_PRESERVATION_SERVICE_PATH
53
53
  script_name = work_script
54
54
  end
55
-
55
+
56
56
  # Strip off the extension
57
57
  script_name.sub!('.rb', '')
58
-
58
+
59
59
  require_path = File.join(script_path, script_name)
60
60
  # Return the cached Class if this path has been encountered before
61
61
  if @class_cache.key?(require_path)
62
62
  return @class_cache[require_path]
63
63
  end
64
-
64
+
65
65
  # Load the script
66
66
  begin
67
67
  require require_path
68
- rescue LoadError => e
68
+ rescue LoadError
69
69
  raise ConfigurationError.new("Failed to load work_script '#{script_name}' for service #{service_name}")
70
70
  end
71
-
71
+
72
72
  # Generate the class name, either configured or from file naming convention if possible
73
73
  if service_def.work_class
74
74
  class_name = service_def.work_class
@@ -77,7 +77,7 @@ module Longleaf
77
77
  # Assume the longleaf module for classes in the standard path
78
78
  class_name = 'Longleaf::' + class_name if script_path == STD_PRESERVATION_SERVICE_PATH
79
79
  end
80
-
80
+
81
81
  begin
82
82
  class_constant = constantize(class_name)
83
83
  # cache the class for this work_script and return it
@@ -86,7 +86,7 @@ module Longleaf
86
86
  raise ConfigurationError.new("Failed to load work_script '#{script_name}' for service #{service_name}, class name #{class_name} was not found.")
87
87
  end
88
88
  end
89
-
89
+
90
90
  private
91
91
  # Borrowed from sidekiq implementation
92
92
  def constantize(str)
@@ -99,7 +99,7 @@ module Longleaf
99
99
  constant.const_defined?(name, false) ? constant.const_get(name, false) : constant.const_missing(name)
100
100
  end
101
101
  end
102
-
102
+
103
103
  def from_permitted_path?(script_path)
104
104
  $LOAD_PATH.each do |lib_path|
105
105
  if script_path.start_with?(lib_path)
@@ -109,4 +109,4 @@ module Longleaf
109
109
  false
110
110
  end
111
111
  end
112
- end
112
+ end
@@ -6,17 +6,17 @@ module Longleaf
6
6
  class ServiceDefinitionManager
7
7
  SF ||= Longleaf::ServiceFields
8
8
  AF ||= Longleaf::AppFields
9
-
9
+
10
10
  # Hash containing the set of configured services, represented as {ServiceDefinition} objects
11
11
  attr_reader :services
12
-
12
+
13
13
  # @param config [Hash] hash representation of the application configuration
14
14
  def initialize(config)
15
15
  raise ArgumentError.new("Configuration must be provided") if config.nil? || config.empty?
16
16
 
17
17
  services_config = config[AF::SERVICES]
18
18
  raise ArgumentError.new("Services configuration must be provided") if services_config.nil?
19
-
19
+
20
20
  @services = Hash.new
21
21
  config[AF::SERVICES].each do |name, properties|
22
22
  work_script = properties.delete(SF::WORK_SCRIPT)
@@ -30,11 +30,10 @@ module Longleaf
30
30
  frequency: frequency,
31
31
  delay: delay,
32
32
  properties: properties)
33
-
33
+
34
34
  @services[name] = service
35
35
  end
36
36
  @services.freeze
37
37
  end
38
-
39
38
  end
40
- end
39
+ end
@@ -9,8 +9,8 @@ module Longleaf
9
9
  class ServiceDefinitionValidator < ConfigurationValidator
10
10
  SF ||= Longleaf::ServiceFields
11
11
  AF ||= Longleaf::AppFields
12
-
13
- # Validates configuration to ensure that it is syntactically correct and does not violate
12
+
13
+ # Validates configuration to ensure that it is syntactically correct and does not violate
14
14
  # schema requirements.
15
15
  # @param config [Hash] hash containing the application configuration
16
16
  def self.validate_config(config)
@@ -18,15 +18,14 @@ module Longleaf
18
18
  assert("Configuration must contain a root '#{AF::SERVICES}' key", config.key?(AF::SERVICES))
19
19
  services = config[AF::SERVICES]
20
20
  assert("'#{AF::SERVICES}' must be a hash of services", services.class == Hash)
21
-
22
- existing_paths = Array.new
21
+
23
22
  services.each do |name, properties|
24
23
  assert("Name of service definition must be a string, but was of type #{name.class}", name.instance_of?(String))
25
24
  assert("Service definition '#{name}' must be a hash, but a #{properties.class} was provided", properties.is_a?(Hash))
26
-
25
+
27
26
  work_script = properties[SF::WORK_SCRIPT]
28
27
  assert("Service definition '#{name}' must specify a '#{SF::WORK_SCRIPT}' property", !work_script.nil? && !work_script.empty?)
29
28
  end
30
29
  end
31
30
  end
32
- end
31
+ end
@@ -4,6 +4,9 @@ require 'longleaf/services/service_class_cache'
4
4
  module Longleaf
5
5
  # Manager which provides preservation service definitions based on their mappings
6
6
  class ServiceManager
7
+ attr_reader :definition_manager
8
+ attr_reader :mapping_manager
9
+
7
10
  # @param definition_manager [ServiceDefinitionManager] the service definition manager
8
11
  # @param mapping_manager [ServiceMappingManager] the mapping of services to locations
9
12
  # @param app_manager [ApplicationConfigManager] manager for storage locations
@@ -16,7 +19,18 @@ module Longleaf
16
19
  @app_manager = app_manager
17
20
  @service_class_cache = ServiceClassCache.new(app_manager)
18
21
  end
19
-
22
+
23
+ # Return a service instance instance for provided service name.
24
+ # @param service_name [String] name of the service
25
+ # @return Preservation service class for the provided name
26
+ # @raise ArgumentError if service_name does not reference an existing service
27
+ def service(service_name)
28
+ raise ArgumentError.new('Service name is required') if service_name.nil? || service_name.empty?
29
+ raise ArgumentError.new("No service with name #{service_name}") unless @definition_manager.services.key?(service_name)
30
+ definition = @definition_manager.services[service_name]
31
+ @service_class_cache.service_instance(definition)
32
+ end
33
+
20
34
  # List the names of services which are applicable to the given criteria
21
35
  # @param location [String] name of the locations to lookup
22
36
  # @param event [String] name of the preservation event taking place
@@ -25,23 +39,29 @@ module Longleaf
25
39
  service_names = @mapping_manager.list_services(location)
26
40
  if !event.nil?
27
41
  # Filter service names down by event
28
- service_names.select{ |name| applicable_for_event?(name, event) }
42
+ service_names.select { |name| applicable_for_event?(name, event) }
29
43
  else
30
44
  service_names
31
45
  end
32
46
  end
33
-
47
+
48
+ # List definitions for services which are applicable to the given criteria
49
+ # @param location [String] name of the locations to lookup
50
+ # @param event [String] name of the preservation event taking place
51
+ # @return [Array] List of service definitions which match the provided criteria
52
+ def list_service_definitions(location: nil, event: nil)
53
+ names = list_services(location: location, event: event)
54
+ names.map { |name| @definition_manager.services[name] }
55
+ end
56
+
34
57
  # Determines if a service is applicable for a specific preservation event
35
58
  # @param service_name [String] name of the service being evaluated
36
59
  # @param event [String] name of the event to check against
37
60
  # @return [Boolean] true if the service is applicable for the event
38
61
  def applicable_for_event?(service_name, event)
39
- definition = @definition_manager.services[service_name]
40
- service = @service_class_cache.service_instance(definition)
41
-
42
- service.is_applicable?(event)
62
+ service(service_name).is_applicable?(event)
43
63
  end
44
-
64
+
45
65
  # Determine if a service should run for a particular file based on the service's definition and
46
66
  # the file's service related metadata.
47
67
  # @param service_name [String] name of the service being evaluated
@@ -51,34 +71,34 @@ module Longleaf
51
71
  # If service not recorded for file, then it is needed
52
72
  present_services = md_rec.list_services
53
73
  return true unless present_services.include?(service_name)
54
-
74
+
55
75
  service_rec = md_rec.service(service_name)
56
-
76
+
57
77
  return true if service_rec.run_needed
58
78
  return true if service_rec.timestamp.nil?
59
-
79
+
60
80
  definition = @definition_manager.services[service_name]
61
-
81
+
62
82
  # Check if the amount of time defined in frequency has passed since the service timestamp
63
83
  frequency = definition.frequency
64
84
  unless frequency.nil?
65
85
  service_timestamp = service_rec.timestamp
66
86
  now = ServiceDateHelper.formatted_timestamp
67
-
87
+
68
88
  return true if now > ServiceDateHelper.add_to_timestamp(service_timestamp, frequency)
69
89
  end
70
90
  false
71
91
  end
72
-
92
+
73
93
  # Perform the specified service on the file record, in the context of the specified event
74
94
  # @param service_name [String] name of the service
75
95
  # @param file_rec [FileRecord] file record to perform service upon
76
- # @param event_name [String] name of the event service is being performed within.
96
+ # @param event [String] name of the event service is being performed within.
77
97
  def perform_service(service_name, file_rec, event)
78
98
  definition = @definition_manager.services[service_name]
79
-
99
+
80
100
  service = @service_class_cache.service_instance(definition)
81
101
  service.perform(file_rec, event)
82
102
  end
83
103
  end
84
- end
104
+ end
@@ -5,26 +5,26 @@ module Longleaf
5
5
  # Manager which loads and provides access to location to service mappings
6
6
  class ServiceMappingManager
7
7
  AF ||= Longleaf::AppFields
8
-
8
+
9
9
  # @param config [Hash] has representation of the application configuration
10
10
  def initialize(config)
11
11
  raise ArgumentError.new("Configuration must be provided") if config.nil? || config.empty?
12
12
 
13
13
  mappings_config = config[AF::SERVICE_MAPPINGS]
14
14
  raise ArgumentError.new("Service mappings configuration must be provided") if mappings_config.nil?
15
-
15
+
16
16
  @loc_to_services = Hash.new
17
-
17
+
18
18
  mappings_config.each do |mapping|
19
19
  locations = mapping[AF::LOCATIONS]
20
20
  services = mapping[AF::SERVICES]
21
-
21
+
22
22
  locations = [locations] if locations.is_a?(String)
23
23
  services = [services] if services.is_a?(String)
24
-
24
+
25
25
  locations.each do |loc_name|
26
26
  @loc_to_services[loc_name] = Array.new unless @loc_to_services.key?(loc_name)
27
-
27
+
28
28
  service_set = @loc_to_services[loc_name]
29
29
  if services.is_a?(String)
30
30
  service_set.push(services)
@@ -33,11 +33,11 @@ module Longleaf
33
33
  end
34
34
  end
35
35
  end
36
-
36
+
37
37
  @loc_to_services.each { |loc, services| services.uniq! }
38
38
  @loc_to_services.freeze
39
39
  end
40
-
40
+
41
41
  # Gets a list of service names associated with the given location
42
42
  # @param loc_name [String] name of the location to lookup
43
43
  # @return [Array] a list of service names associated with the location
@@ -45,4 +45,4 @@ module Longleaf
45
45
  @loc_to_services[loc_name] || []
46
46
  end
47
47
  end
48
- end
48
+ end