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.
- checksums.yaml +4 -4
- data/.circleci/config.yml +84 -0
- data/.gitignore +4 -2
- data/.rubocop.yml +42 -2
- data/.rubocop_todo.yml +390 -311
- data/.yardopts +1 -0
- data/Gemfile +16 -1
- data/README.md +67 -13
- data/Rakefile +6 -0
- data/bin/setup +16 -1
- data/docs/aboutlongleaf.md +28 -0
- data/docs/extra.css +32 -0
- data/docs/img/change-file.png +0 -0
- data/docs/img/ll-example-preserved.png +0 -0
- data/docs/index.md +19 -0
- data/docs/install.md +66 -0
- data/docs/ll-example/config-example-relative.yml +33 -0
- data/docs/ll-example/files-dir/LLexample-PDF.pdf +0 -0
- data/docs/ll-example/files-dir/LLexample-TOCHANGE.txt +15 -0
- data/docs/ll-example/files-dir/LLexample-tokeep.txt +10 -0
- data/docs/ll-example/metadata-dir/.gitkeep +0 -0
- data/docs/ll-example/replica-files/.gitkeep +0 -0
- data/docs/ll-example/replica-metadata/.gitkeep +0 -0
- data/docs/quickstart.md +270 -0
- data/docs/rdocs/Longleaf.html +135 -0
- data/docs/rdocs/Longleaf/AppFields.html +178 -0
- data/docs/rdocs/Longleaf/ApplicationConfigDeserializer.html +631 -0
- data/docs/rdocs/Longleaf/ApplicationConfigManager.html +610 -0
- data/docs/rdocs/Longleaf/ApplicationConfigValidator.html +238 -0
- data/docs/rdocs/Longleaf/CLI.html +909 -0
- data/docs/rdocs/Longleaf/ChecksumMismatchError.html +151 -0
- data/docs/rdocs/Longleaf/ConfigBuilder.html +1339 -0
- data/docs/rdocs/Longleaf/ConfigurationError.html +143 -0
- data/docs/rdocs/Longleaf/ConfigurationValidator.html +227 -0
- data/docs/rdocs/Longleaf/DeregisterCommand.html +420 -0
- data/docs/rdocs/Longleaf/DeregisterEvent.html +453 -0
- data/docs/rdocs/Longleaf/DeregistrationError.html +151 -0
- data/docs/rdocs/Longleaf/DigestHelper.html +419 -0
- data/docs/rdocs/Longleaf/EventError.html +147 -0
- data/docs/rdocs/Longleaf/EventNames.html +163 -0
- data/docs/rdocs/Longleaf/EventStatusTracking.html +656 -0
- data/docs/rdocs/Longleaf/FileCheckService.html +540 -0
- data/docs/rdocs/Longleaf/FileHelpers.html +520 -0
- data/docs/rdocs/Longleaf/FileRecord.html +716 -0
- data/docs/rdocs/Longleaf/FileSelector.html +901 -0
- data/docs/rdocs/Longleaf/FixityCheckService.html +691 -0
- data/docs/rdocs/Longleaf/IndexManager.html +1155 -0
- data/docs/rdocs/Longleaf/InvalidDigestAlgorithmError.html +143 -0
- data/docs/rdocs/Longleaf/InvalidStoragePathError.html +143 -0
- data/docs/rdocs/Longleaf/Logging.html +405 -0
- data/docs/rdocs/Longleaf/Logging/RedirectingLogger.html +1213 -0
- data/docs/rdocs/Longleaf/LongleafError.html +139 -0
- data/docs/rdocs/Longleaf/MDFields.html +193 -0
- data/docs/rdocs/Longleaf/MetadataBuilder.html +787 -0
- data/docs/rdocs/Longleaf/MetadataDeserializer.html +537 -0
- data/docs/rdocs/Longleaf/MetadataError.html +143 -0
- data/docs/rdocs/Longleaf/MetadataPersistenceManager.html +539 -0
- data/docs/rdocs/Longleaf/MetadataRecord.html +1411 -0
- data/docs/rdocs/Longleaf/MetadataSerializer.html +786 -0
- data/docs/rdocs/Longleaf/PreservationServiceError.html +147 -0
- data/docs/rdocs/Longleaf/PreserveCommand.html +410 -0
- data/docs/rdocs/Longleaf/PreserveEvent.html +491 -0
- data/docs/rdocs/Longleaf/RegisterCommand.html +428 -0
- data/docs/rdocs/Longleaf/RegisterEvent.html +628 -0
- data/docs/rdocs/Longleaf/RegisteredFileSelector.html +446 -0
- data/docs/rdocs/Longleaf/RegistrationError.html +151 -0
- data/docs/rdocs/Longleaf/ReindexCommand.html +576 -0
- data/docs/rdocs/Longleaf/RsyncReplicationService.html +1180 -0
- data/docs/rdocs/Longleaf/SequelIndexDriver.html +1978 -0
- data/docs/rdocs/Longleaf/ServiceCandidateFilesystemIterator.html +572 -0
- data/docs/rdocs/Longleaf/ServiceCandidateIndexIterator.html +532 -0
- data/docs/rdocs/Longleaf/ServiceCandidateLocator.html +333 -0
- data/docs/rdocs/Longleaf/ServiceClassCache.html +725 -0
- data/docs/rdocs/Longleaf/ServiceDateHelper.html +425 -0
- data/docs/rdocs/Longleaf/ServiceDefinition.html +683 -0
- data/docs/rdocs/Longleaf/ServiceDefinitionManager.html +371 -0
- data/docs/rdocs/Longleaf/ServiceDefinitionValidator.html +269 -0
- data/docs/rdocs/Longleaf/ServiceFields.html +173 -0
- data/docs/rdocs/Longleaf/ServiceManager.html +1229 -0
- data/docs/rdocs/Longleaf/ServiceMappingManager.html +410 -0
- data/docs/rdocs/Longleaf/ServiceMappingValidator.html +347 -0
- data/docs/rdocs/Longleaf/ServiceRecord.html +821 -0
- data/docs/rdocs/Longleaf/StorageLocation.html +985 -0
- data/docs/rdocs/Longleaf/StorageLocationManager.html +729 -0
- data/docs/rdocs/Longleaf/StorageLocationUnavailableError.html +143 -0
- data/docs/rdocs/Longleaf/StorageLocationValidator.html +373 -0
- data/docs/rdocs/Longleaf/StoragePathValidator.html +253 -0
- data/docs/rdocs/Longleaf/SystemConfigBuilder.html +441 -0
- data/docs/rdocs/Longleaf/SystemConfigFields.html +163 -0
- data/docs/rdocs/Longleaf/ValidateConfigCommand.html +451 -0
- data/docs/rdocs/Longleaf/ValidateMetadataCommand.html +408 -0
- data/docs/rdocs/_index.html +660 -0
- data/docs/rdocs/class_list.html +51 -0
- data/docs/rdocs/css/common.css +1 -0
- data/docs/rdocs/css/full_list.css +58 -0
- data/docs/rdocs/css/style.css +496 -0
- data/docs/rdocs/file.README.html +165 -0
- data/docs/rdocs/file_list.html +56 -0
- data/docs/rdocs/frames.html +17 -0
- data/docs/rdocs/index.html +165 -0
- data/docs/rdocs/js/app.js +303 -0
- data/docs/rdocs/js/full_list.js +216 -0
- data/docs/rdocs/js/jquery.js +4 -0
- data/docs/rdocs/method_list.html +2051 -0
- data/docs/rdocs/top-level-namespace.html +110 -0
- data/lib/longleaf/candidates/file_selector.rb +47 -15
- data/lib/longleaf/candidates/registered_file_selector.rb +67 -0
- data/lib/longleaf/candidates/service_candidate_filesystem_iterator.rb +29 -35
- data/lib/longleaf/candidates/service_candidate_index_iterator.rb +84 -0
- data/lib/longleaf/candidates/service_candidate_locator.rb +9 -4
- data/lib/longleaf/cli.rb +162 -80
- data/lib/longleaf/commands/deregister_command.rb +12 -11
- data/lib/longleaf/commands/preserve_command.rb +13 -8
- data/lib/longleaf/commands/register_command.rb +9 -6
- data/lib/longleaf/commands/reindex_command.rb +92 -0
- data/lib/longleaf/commands/validate_config_command.rb +27 -6
- data/lib/longleaf/commands/validate_metadata_command.rb +11 -9
- data/lib/longleaf/errors.rb +12 -12
- data/lib/longleaf/events/deregister_event.rb +13 -15
- data/lib/longleaf/events/event_status_tracking.rb +7 -7
- data/lib/longleaf/events/preserve_event.rb +24 -14
- data/lib/longleaf/events/register_event.rb +21 -35
- data/lib/longleaf/helpers/digest_helper.rb +4 -4
- data/lib/longleaf/helpers/service_date_helper.rb +5 -6
- data/lib/longleaf/indexing/index_manager.rb +101 -0
- data/lib/longleaf/indexing/sequel_index_driver.rb +324 -0
- data/lib/longleaf/logging.rb +4 -4
- data/lib/longleaf/logging/redirecting_logger.rb +20 -20
- data/lib/longleaf/models/app_fields.rb +2 -1
- data/lib/longleaf/models/file_record.rb +10 -6
- data/lib/longleaf/models/md_fields.rb +1 -1
- data/lib/longleaf/models/metadata_record.rb +22 -12
- data/lib/longleaf/models/service_definition.rb +3 -3
- data/lib/longleaf/models/service_fields.rb +1 -1
- data/lib/longleaf/models/service_record.rb +6 -5
- data/lib/longleaf/models/storage_location.rb +26 -7
- data/lib/longleaf/models/system_config_fields.rb +9 -0
- data/lib/longleaf/preservation_services/file_check_service.rb +58 -0
- data/lib/longleaf/preservation_services/fixity_check_service.rb +16 -14
- data/lib/longleaf/preservation_services/rsync_replication_service.rb +32 -31
- data/lib/longleaf/services/application_config_deserializer.rb +55 -18
- data/lib/longleaf/services/application_config_manager.rb +16 -4
- data/lib/longleaf/services/application_config_validator.rb +1 -2
- data/lib/longleaf/services/configuration_validator.rb +6 -4
- data/lib/longleaf/services/metadata_deserializer.rb +40 -38
- data/lib/longleaf/services/metadata_persistence_manager.rb +46 -0
- data/lib/longleaf/services/metadata_serializer.rb +23 -22
- data/lib/longleaf/services/service_class_cache.rb +15 -15
- data/lib/longleaf/services/service_definition_manager.rb +5 -6
- data/lib/longleaf/services/service_definition_validator.rb +5 -6
- data/lib/longleaf/services/service_manager.rb +37 -17
- data/lib/longleaf/services/service_mapping_manager.rb +9 -9
- data/lib/longleaf/services/service_mapping_validator.rb +9 -10
- data/lib/longleaf/services/storage_location_manager.rb +22 -8
- data/lib/longleaf/services/storage_location_validator.rb +11 -8
- data/lib/longleaf/services/storage_path_validator.rb +1 -1
- data/lib/longleaf/specs/config_builder.rb +30 -17
- data/lib/longleaf/specs/custom_matchers.rb +1 -1
- data/lib/longleaf/specs/file_helpers.rb +15 -14
- data/lib/longleaf/specs/metadata_builder.rb +91 -0
- data/lib/longleaf/specs/system_config_builder.rb +27 -0
- data/lib/longleaf/version.rb +1 -1
- data/longleaf.gemspec +17 -7
- data/mkdocs.yml +20 -0
- 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(
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|