longleaf 0.1.0.pre.2 → 1.0.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 (184) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +94 -0
  3. data/.editorconfig +13 -0
  4. data/.gitignore +4 -1
  5. data/.rubocop.yml +44 -0
  6. data/.rubocop_todo.yml +834 -0
  7. data/.yardopts +1 -0
  8. data/Gemfile +16 -1
  9. data/README.md +98 -12
  10. data/Rakefile +6 -0
  11. data/bin/setup +16 -1
  12. data/docs/aboutlongleaf.md +28 -0
  13. data/docs/extra.css +32 -0
  14. data/docs/img/change-file.png +0 -0
  15. data/docs/img/ll-example-preserved.png +0 -0
  16. data/docs/index.md +19 -0
  17. data/docs/install.md +66 -0
  18. data/docs/ll-example/config-example-relative.yml +33 -0
  19. data/docs/ll-example/files-dir/LLexample-PDF.pdf +0 -0
  20. data/docs/ll-example/files-dir/LLexample-TOCHANGE.txt +15 -0
  21. data/docs/ll-example/files-dir/LLexample-tokeep.txt +10 -0
  22. data/docs/ll-example/metadata-dir/.gitkeep +0 -0
  23. data/docs/ll-example/replica-files/.gitkeep +0 -0
  24. data/docs/ll-example/replica-metadata/.gitkeep +0 -0
  25. data/docs/quickstart.md +270 -0
  26. data/docs/rdocs/Longleaf.html +135 -0
  27. data/docs/rdocs/Longleaf/AppFields.html +178 -0
  28. data/docs/rdocs/Longleaf/ApplicationConfigDeserializer.html +631 -0
  29. data/docs/rdocs/Longleaf/ApplicationConfigManager.html +610 -0
  30. data/docs/rdocs/Longleaf/ApplicationConfigValidator.html +238 -0
  31. data/docs/rdocs/Longleaf/CLI.html +909 -0
  32. data/docs/rdocs/Longleaf/ChecksumMismatchError.html +151 -0
  33. data/docs/rdocs/Longleaf/ConfigBuilder.html +1339 -0
  34. data/docs/rdocs/Longleaf/ConfigurationError.html +143 -0
  35. data/docs/rdocs/Longleaf/ConfigurationValidator.html +227 -0
  36. data/docs/rdocs/Longleaf/DeregisterCommand.html +420 -0
  37. data/docs/rdocs/Longleaf/DeregisterEvent.html +453 -0
  38. data/docs/rdocs/Longleaf/DeregistrationError.html +151 -0
  39. data/docs/rdocs/Longleaf/DigestHelper.html +419 -0
  40. data/docs/rdocs/Longleaf/EventError.html +147 -0
  41. data/docs/rdocs/Longleaf/EventNames.html +163 -0
  42. data/docs/rdocs/Longleaf/EventStatusTracking.html +656 -0
  43. data/docs/rdocs/Longleaf/FileCheckService.html +540 -0
  44. data/docs/rdocs/Longleaf/FileHelpers.html +520 -0
  45. data/docs/rdocs/Longleaf/FileRecord.html +716 -0
  46. data/docs/rdocs/Longleaf/FileSelector.html +901 -0
  47. data/docs/rdocs/Longleaf/FixityCheckService.html +691 -0
  48. data/docs/rdocs/Longleaf/IndexManager.html +1155 -0
  49. data/docs/rdocs/Longleaf/InvalidDigestAlgorithmError.html +143 -0
  50. data/docs/rdocs/Longleaf/InvalidStoragePathError.html +143 -0
  51. data/docs/rdocs/Longleaf/Logging.html +405 -0
  52. data/docs/rdocs/Longleaf/Logging/RedirectingLogger.html +1213 -0
  53. data/docs/rdocs/Longleaf/LongleafError.html +139 -0
  54. data/docs/rdocs/Longleaf/MDFields.html +193 -0
  55. data/docs/rdocs/Longleaf/MetadataBuilder.html +787 -0
  56. data/docs/rdocs/Longleaf/MetadataDeserializer.html +537 -0
  57. data/docs/rdocs/Longleaf/MetadataError.html +143 -0
  58. data/docs/rdocs/Longleaf/MetadataPersistenceManager.html +539 -0
  59. data/docs/rdocs/Longleaf/MetadataRecord.html +1411 -0
  60. data/docs/rdocs/Longleaf/MetadataSerializer.html +786 -0
  61. data/docs/rdocs/Longleaf/PreservationServiceError.html +147 -0
  62. data/docs/rdocs/Longleaf/PreserveCommand.html +410 -0
  63. data/docs/rdocs/Longleaf/PreserveEvent.html +491 -0
  64. data/docs/rdocs/Longleaf/RegisterCommand.html +428 -0
  65. data/docs/rdocs/Longleaf/RegisterEvent.html +628 -0
  66. data/docs/rdocs/Longleaf/RegisteredFileSelector.html +446 -0
  67. data/docs/rdocs/Longleaf/RegistrationError.html +151 -0
  68. data/docs/rdocs/Longleaf/ReindexCommand.html +576 -0
  69. data/docs/rdocs/Longleaf/RsyncReplicationService.html +1180 -0
  70. data/docs/rdocs/Longleaf/SequelIndexDriver.html +1978 -0
  71. data/docs/rdocs/Longleaf/ServiceCandidateFilesystemIterator.html +572 -0
  72. data/docs/rdocs/Longleaf/ServiceCandidateIndexIterator.html +532 -0
  73. data/docs/rdocs/Longleaf/ServiceCandidateLocator.html +333 -0
  74. data/docs/rdocs/Longleaf/ServiceClassCache.html +725 -0
  75. data/docs/rdocs/Longleaf/ServiceDateHelper.html +425 -0
  76. data/docs/rdocs/Longleaf/ServiceDefinition.html +683 -0
  77. data/docs/rdocs/Longleaf/ServiceDefinitionManager.html +371 -0
  78. data/docs/rdocs/Longleaf/ServiceDefinitionValidator.html +269 -0
  79. data/docs/rdocs/Longleaf/ServiceFields.html +173 -0
  80. data/docs/rdocs/Longleaf/ServiceManager.html +1229 -0
  81. data/docs/rdocs/Longleaf/ServiceMappingManager.html +410 -0
  82. data/docs/rdocs/Longleaf/ServiceMappingValidator.html +347 -0
  83. data/docs/rdocs/Longleaf/ServiceRecord.html +821 -0
  84. data/docs/rdocs/Longleaf/StorageLocation.html +985 -0
  85. data/docs/rdocs/Longleaf/StorageLocationManager.html +729 -0
  86. data/docs/rdocs/Longleaf/StorageLocationUnavailableError.html +143 -0
  87. data/docs/rdocs/Longleaf/StorageLocationValidator.html +373 -0
  88. data/docs/rdocs/Longleaf/StoragePathValidator.html +253 -0
  89. data/docs/rdocs/Longleaf/SystemConfigBuilder.html +441 -0
  90. data/docs/rdocs/Longleaf/SystemConfigFields.html +163 -0
  91. data/docs/rdocs/Longleaf/ValidateConfigCommand.html +451 -0
  92. data/docs/rdocs/Longleaf/ValidateMetadataCommand.html +408 -0
  93. data/docs/rdocs/_index.html +660 -0
  94. data/docs/rdocs/class_list.html +51 -0
  95. data/docs/rdocs/css/common.css +1 -0
  96. data/docs/rdocs/css/full_list.css +58 -0
  97. data/docs/rdocs/css/style.css +496 -0
  98. data/docs/rdocs/file.README.html +165 -0
  99. data/docs/rdocs/file_list.html +56 -0
  100. data/docs/rdocs/frames.html +17 -0
  101. data/docs/rdocs/index.html +165 -0
  102. data/docs/rdocs/js/app.js +303 -0
  103. data/docs/rdocs/js/full_list.js +216 -0
  104. data/docs/rdocs/js/jquery.js +4 -0
  105. data/docs/rdocs/method_list.html +2051 -0
  106. data/docs/rdocs/top-level-namespace.html +110 -0
  107. data/lib/longleaf/candidates/file_selector.rb +139 -0
  108. data/lib/longleaf/candidates/manifest_digest_provider.rb +17 -0
  109. data/lib/longleaf/candidates/registered_file_selector.rb +67 -0
  110. data/lib/longleaf/candidates/service_candidate_filesystem_iterator.rb +93 -0
  111. data/lib/longleaf/candidates/service_candidate_index_iterator.rb +84 -0
  112. data/lib/longleaf/candidates/service_candidate_locator.rb +23 -0
  113. data/lib/longleaf/candidates/single_digest_provider.rb +13 -0
  114. data/lib/longleaf/cli.rb +237 -46
  115. data/lib/longleaf/commands/deregister_command.rb +51 -0
  116. data/lib/longleaf/commands/preserve_command.rb +50 -0
  117. data/lib/longleaf/commands/register_command.rb +32 -43
  118. data/lib/longleaf/commands/reindex_command.rb +92 -0
  119. data/lib/longleaf/commands/validate_config_command.rb +33 -8
  120. data/lib/longleaf/commands/validate_metadata_command.rb +51 -0
  121. data/lib/longleaf/errors.rb +26 -7
  122. data/lib/longleaf/events/deregister_event.rb +53 -0
  123. data/lib/longleaf/events/event_names.rb +9 -0
  124. data/lib/longleaf/events/event_status_tracking.rb +59 -0
  125. data/lib/longleaf/events/preserve_event.rb +81 -0
  126. data/lib/longleaf/events/register_event.rb +52 -51
  127. data/lib/longleaf/helpers/case_insensitive_hash.rb +38 -0
  128. data/lib/longleaf/helpers/digest_helper.rb +56 -0
  129. data/lib/longleaf/helpers/s3_uri_helper.rb +86 -0
  130. data/lib/longleaf/helpers/selection_options_parser.rb +189 -0
  131. data/lib/longleaf/helpers/service_date_helper.rb +78 -0
  132. data/lib/longleaf/indexing/index_manager.rb +101 -0
  133. data/lib/longleaf/indexing/sequel_index_driver.rb +306 -0
  134. data/lib/longleaf/logging.rb +5 -4
  135. data/lib/longleaf/logging/redirecting_logger.rb +26 -25
  136. data/lib/longleaf/models/app_fields.rb +7 -2
  137. data/lib/longleaf/models/file_record.rb +17 -8
  138. data/lib/longleaf/models/filesystem_metadata_location.rb +56 -0
  139. data/lib/longleaf/models/filesystem_storage_location.rb +52 -0
  140. data/lib/longleaf/models/md_fields.rb +2 -1
  141. data/lib/longleaf/models/metadata_location.rb +47 -0
  142. data/lib/longleaf/models/metadata_record.rb +39 -15
  143. data/lib/longleaf/models/s3_storage_location.rb +133 -0
  144. data/lib/longleaf/models/service_definition.rb +7 -6
  145. data/lib/longleaf/models/service_fields.rb +7 -1
  146. data/lib/longleaf/models/service_record.rb +10 -6
  147. data/lib/longleaf/models/storage_location.rb +24 -19
  148. data/lib/longleaf/models/storage_types.rb +9 -0
  149. data/lib/longleaf/models/system_config_fields.rb +9 -0
  150. data/lib/longleaf/preservation_services/file_check_service.rb +58 -0
  151. data/lib/longleaf/preservation_services/fixity_check_service.rb +123 -0
  152. data/lib/longleaf/preservation_services/rsync_replication_service.rb +182 -0
  153. data/lib/longleaf/preservation_services/s3_replication_service.rb +143 -0
  154. data/lib/longleaf/services/application_config_deserializer.rb +81 -24
  155. data/lib/longleaf/services/application_config_manager.rb +20 -6
  156. data/lib/longleaf/services/application_config_validator.rb +19 -9
  157. data/lib/longleaf/services/configuration_validator.rb +67 -4
  158. data/lib/longleaf/services/filesystem_location_validator.rb +16 -0
  159. data/lib/longleaf/services/metadata_deserializer.rb +113 -42
  160. data/lib/longleaf/services/metadata_persistence_manager.rb +47 -0
  161. data/lib/longleaf/services/metadata_serializer.rb +138 -25
  162. data/lib/longleaf/services/metadata_validator.rb +76 -0
  163. data/lib/longleaf/services/s3_location_validator.rb +19 -0
  164. data/lib/longleaf/services/service_class_cache.rb +112 -0
  165. data/lib/longleaf/services/service_definition_manager.rb +10 -7
  166. data/lib/longleaf/services/service_definition_validator.rb +25 -18
  167. data/lib/longleaf/services/service_manager.rb +86 -11
  168. data/lib/longleaf/services/service_mapping_manager.rb +13 -12
  169. data/lib/longleaf/services/service_mapping_validator.rb +36 -26
  170. data/lib/longleaf/services/storage_location_manager.rb +76 -15
  171. data/lib/longleaf/services/storage_location_validator.rb +49 -35
  172. data/lib/longleaf/specs/config_builder.rb +47 -23
  173. data/lib/longleaf/specs/config_validator_helpers.rb +16 -0
  174. data/lib/longleaf/specs/custom_matchers.rb +9 -0
  175. data/lib/longleaf/specs/file_helpers.rb +61 -0
  176. data/lib/longleaf/specs/metadata_builder.rb +92 -0
  177. data/lib/longleaf/specs/system_config_builder.rb +27 -0
  178. data/lib/longleaf/version.rb +1 -1
  179. data/longleaf.gemspec +20 -7
  180. data/mkdocs.yml +21 -0
  181. metadata +306 -23
  182. data/.travis.yml +0 -4
  183. data/lib/longleaf/commands/abstract_command.rb +0 -37
  184. data/lib/longleaf/services/storage_path_validator.rb +0 -16
@@ -1,21 +1,22 @@
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
7
8
  Logging.logger
8
9
  end
9
-
10
+
10
11
  # Get the main logger for longleaf
11
12
  def self.logger
12
13
  @logger ||= RedirectingLogger.new
13
14
  end
14
-
15
+
15
16
  def initialize_logger(failure_only, log_level, log_format, datetime_format)
16
17
  Logging.initialize_logger(failure_only, log_level, log_format, datetime_format)
17
18
  end
18
-
19
+
19
20
  def self.initialize_logger(failure_only, log_level, log_format, datetime_format)
20
21
  @logger = RedirectingLogger.new(failure_only: failure_only,
21
22
  log_level: log_level,
@@ -23,4 +24,4 @@ module Longleaf
23
24
  datetime_format: datetime_format)
24
25
  end
25
26
  end
26
- end
27
+ end
@@ -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)
@@ -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,33 +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
- # @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
@@ -75,10 +76,10 @@ module Longleaf
75
76
  def success(eventOrMessage, file_name = nil, message = nil, service = nil)
76
77
  outcome('SUCCESS', eventOrMessage, file_name, message, service)
77
78
  end
78
-
79
+
79
80
  # Logs a failure message to STDOUT, as well as STDERR at info level.
80
81
  # If an error was provided, it is logged to STDERR at error level.
81
- # @param eventOrMessage [String] name of the preservation event which failed,
82
+ # @param eventOrMessage [String] name of the preservation event which failed,
82
83
  # or the message to output if it is the only parameter.
83
84
  # @param file_name [String] file name which is the subject of this message.
84
85
  # @param message [String] descriptive message to accompany this output
@@ -87,17 +88,18 @@ module Longleaf
87
88
  def failure(eventOrMessage, file_name = nil, message = nil, service = nil, error: nil)
88
89
  text = outcome_text('FAILURE', eventOrMessage, file_name, message, service, error)
89
90
  @stdout_log.warn(text)
90
-
91
+
91
92
  @stderr_log.info(text)
92
93
  @stderr_log.error("#{error.message}") unless error.nil?
94
+ @stderr_log.error("#{error.backtrace}") 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.
96
98
  # If file_name and message are nil, eventOrMessage will be used as the message.
97
99
  #
98
100
  # @param outcome [String] The status of the outcome. Required.
99
- # @param eventOrMessage [String] name of the preservation event which was successful,
100
- # or the message to output if it is the only parameter. Required.
101
+ # @param eventOrMessage [String] name of the preservation event which was successful,
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.
@@ -107,14 +109,13 @@ module Longleaf
107
109
  @stdout_log.info(text)
108
110
  @stderr_log.info(text)
109
111
  end
110
-
111
- # FAILURE verify[cdr_fixity_check] /path/to/file: Something terrible
112
+
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?
115
-
116
+
116
117
  text = "#{outcome}"
117
-
118
+
118
119
  if message_only
119
120
  text << ": #{eventOrMessage}"
120
121
  else
@@ -1,10 +1,15 @@
1
1
  module Longleaf
2
+ # Application configuration field names
2
3
  class AppFields
3
4
  LOCATIONS = 'locations'
4
5
  SERVICES = 'services'
5
6
  SERVICE_MAPPINGS = 'service_mappings'
6
-
7
+ SYSTEM = 'system'
8
+
7
9
  LOCATION_PATH = 'path'
8
- METADATA_PATH = 'metadata_path'
10
+ METADATA_CONFIG = 'metadata'
11
+ METADATA_DIGESTS = 'digests'
12
+
13
+ STORAGE_TYPE = 'type'
9
14
  end
10
15
  end
@@ -1,25 +1,34 @@
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
-
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
- # @param storage_location [Longleaf::StorageLocation] storage location containing the file
11
- def initialize(file_path, storage_location)
9
+ # @param storage_location [StorageLocation] storage location containing the file
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
16
+ @metadata_record = metadata_record
17
17
  end
18
-
18
+
19
19
  # @return [String] path for the metadata file for this file
20
20
  def metadata_path
21
21
  @metadata_path = @storage_location.get_metadata_path_for(path) if @metadata_path.nil?
22
22
  @metadata_path
23
23
  end
24
+
25
+ def metadata_present?
26
+ File.exist?(metadata_path)
27
+ end
28
+
29
+ def ==(other_obj)
30
+ return false unless other_obj.is_a?(FileRecord)
31
+ path == other_obj.path
32
+ end
24
33
  end
25
- end
34
+ end
@@ -0,0 +1,56 @@
1
+ require 'longleaf/services/metadata_serializer'
2
+ require 'longleaf/models/metadata_location'
3
+ require 'longleaf/models/storage_types'
4
+
5
+ module Longleaf
6
+ # A filesystem based location in which metadata associated with registered files is stored.
7
+ class FilesystemMetadataLocation < MetadataLocation
8
+ AF ||= Longleaf::AppFields
9
+
10
+ def initialize(config)
11
+ super(config)
12
+ end
13
+
14
+ # @return the storage type for this location
15
+ def type
16
+ StorageTypes::FILESYSTEM_STORAGE_TYPE
17
+ end
18
+
19
+ # Get the absolute path for the metadata file for the given file path located in this storage location.
20
+ # @param file_path [String] path of the file relative its storage location
21
+ # @return absolute path to the metadata
22
+ # @raise [ArgumentError] if the file_path is not provided.
23
+ def metadata_path_for(file_path)
24
+ raise ArgumentError.new("A file_path parameter is required") if file_path.nil?
25
+ raise ArgumentError.new("File path must be relative") if Pathname.new(file_path).absolute?
26
+
27
+ md_path = File.join(@path, file_path)
28
+ # If the file_path is to a file, then add metadata suffix.
29
+ if md_path.end_with?('/')
30
+ md_path
31
+ else
32
+ md_path + MetadataSerializer::metadata_suffix
33
+ end
34
+ end
35
+
36
+ # Get the metadata path relative to this location
37
+ # @param md_path [String] metadata file path
38
+ # @return the metadata path relative to this location
39
+ # @raise [ArgumentError] if the metadata path is not contained by this location
40
+ def relativize(md_path)
41
+ return md_path if Pathname.new(md_path).relative?
42
+
43
+ raise ArgumentError.new("Metadata path must be contained by this location") if !md_path.start_with?(@path)
44
+
45
+ md_path.sub(@path, "")
46
+ end
47
+
48
+
49
+ # Checks that the path defined in this metadata location are available
50
+ # @raise [StorageLocationUnavailableError] if the metadata location is not available
51
+ def available?
52
+ raise StorageLocationUnavailableError.new("Metadata path does not exist or is not a directory: #{@path}")\
53
+ unless Dir.exist?(@path)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,52 @@
1
+ require 'longleaf/models/storage_location'
2
+ require 'longleaf/models/storage_types'
3
+
4
+ module Longleaf
5
+ # A storage location in a local filesystem
6
+ class FilesystemStorageLocation < StorageLocation
7
+ # @param name [String] the name of this storage location
8
+ # @param config [Hash] hash containing the configuration options for this location
9
+ # @param md_loc [MetadataLocation] metadata location associated with this storage location
10
+ def initialize(name, config, md_loc)
11
+ super(name, config, md_loc)
12
+ @path += File::SEPARATOR unless @path.end_with?(File::SEPARATOR)
13
+ end
14
+
15
+ # @return the storage type for this location
16
+ def type
17
+ StorageTypes::FILESYSTEM_STORAGE_TYPE
18
+ end
19
+
20
+ # Get that absolute path to the file associated with the provided metadata path
21
+ # @param md_path [String] metadata file path
22
+ # @raise [ArgumentError] if the md_path is not in this storage location
23
+ # @return [String] the path for the file associated with this metadata
24
+ def get_path_from_metadata_path(md_path)
25
+ raise ArgumentError.new("A file_path parameter is required") if md_path.nil? || md_path.empty?
26
+
27
+ rel_path = @metadata_location.relative_file_path_for(md_path)
28
+
29
+ File.join(@path, rel_path)
30
+ end
31
+
32
+ # Checks that the path and metadata path defined in this location are available
33
+ # @raise [StorageLocationUnavailableError] if the storage location is not available
34
+ def available?
35
+ raise StorageLocationUnavailableError.new("Path does not exist or is not a directory: #{@path}")\
36
+ unless Dir.exist?(@path)
37
+ @metadata_location.available?
38
+ end
39
+
40
+ # Get the file path relative to this location
41
+ # @param file_path [String] file path
42
+ # @return the file path relative to this location
43
+ # @raise [ArgumentError] if the file path is not contained by this location
44
+ def relativize(file_path)
45
+ return file_path if Pathname.new(file_path).relative?
46
+
47
+ raise ArgumentError.new("Metadata path must be contained by this location") if !file_path.start_with?(@path)
48
+
49
+ file_path.sub(@path, "")
50
+ end
51
+ end
52
+ end
@@ -1,8 +1,9 @@
1
1
  module Longleaf
2
+ # File metadata fields
2
3
  class MDFields
3
4
  DATA = 'data'
4
5
  SERVICES = 'services'
5
-
6
+
6
7
  REGISTERED_TIMESTAMP = 'registered'
7
8
  DEREGISTERED_TIMESTAMP = 'deregistered'
8
9
 
@@ -0,0 +1,47 @@
1
+ require 'longleaf/models/app_fields'
2
+
3
+ module Longleaf
4
+ # A location in which metadata associated with registered files is stored.
5
+ class MetadataLocation
6
+ AF ||= Longleaf::AppFields
7
+
8
+ attr_reader :path
9
+ attr_reader :digests
10
+
11
+ def initialize(config)
12
+ raise ArgumentError.new("Config parameter is required") unless config
13
+ @path = config[AF::LOCATION_PATH]
14
+ raise ArgumentError.new("Parameter path is required") unless @path
15
+ @path += '/' unless @path.end_with?('/')
16
+
17
+ digests = config[AF::METADATA_DIGESTS]
18
+ if digests.nil?
19
+ @digests = []
20
+ elsif digests.is_a?(String)
21
+ @digests = [digests.downcase]
22
+ else
23
+ @digests = digests.map(&:downcase)
24
+ end
25
+ DigestHelper::validate_algorithms(@digests)
26
+ end
27
+
28
+ # Transforms the given metadata path into a relative storage location path
29
+ # @param md_path [String] path of the metadata file or directory to compute file path for.
30
+ # @return
31
+ def relative_file_path_for(md_path)
32
+ rel_md_path = relativize(md_path)
33
+
34
+ if rel_md_path.end_with?(MetadataSerializer::metadata_suffix)
35
+ rel_md_path[0..-MetadataSerializer::metadata_suffix.length - 1]
36
+ else
37
+ rel_md_path
38
+ end
39
+ end
40
+
41
+ # @param [String] metadata path to check
42
+ # @return true if the metadata path is contained by the path for this location
43
+ def contains?(md_path)
44
+ md_path.start_with?(@path)
45
+ end
46
+ end
47
+ end
@@ -1,14 +1,16 @@
1
1
  require_relative 'md_fields'
2
2
  require_relative 'service_record'
3
+ require 'longleaf/helpers/case_insensitive_hash'
3
4
 
4
- # Metadata record for a single file
5
5
  module Longleaf
6
+ # Metadata record for a single file
6
7
  class MetadataRecord
7
- attr_reader :deregistered, :registered
8
+ attr_reader :registered
9
+ attr_accessor :deregistered
8
10
  attr_reader :checksums
9
11
  attr_reader :properties
10
12
  attr_accessor :file_size, :last_modified
11
-
13
+
12
14
  # @param properties [Hash] initial data properties for this record
13
15
  # @param services [Hash] initial service property tree
14
16
  # @param deregistered [String] deregistered timestamp
@@ -16,42 +18,64 @@ module Longleaf
16
18
  # @param checksums [Hash] hash of checksum values
17
19
  # @param file_size [Integer] size of file in bytes
18
20
  # @param last_modified [String] iso8601 representation of the last modified date of file
19
- def initialize(properties: Hash.new, services: Hash.new, deregistered: nil, registered: nil, checksums: Hash.new,
21
+ def initialize(properties: nil, services: nil, deregistered: nil, registered: nil, checksums: nil,
20
22
  file_size: nil, last_modified: nil)
21
- @properties = properties
23
+ @properties = properties || Hash.new
22
24
  @registered = registered
23
25
  @deregistered = deregistered
24
- @checksums = checksums
25
- @services = services
26
+ @checksums = CaseInsensitiveHash.new
27
+ @checksums.merge!(checksums) unless checksums.nil?
28
+ @services = services || Hash.new
26
29
  @file_size = file_size
27
30
  @last_modified = last_modified
28
31
  end
29
-
32
+
30
33
  # @return [Boolean] true if the record is deregistered
31
34
  def deregistered?
32
35
  !@deregistered.nil?
33
36
  end
34
-
37
+
35
38
  # Adds a service to this record
36
39
  #
37
40
  # @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)
41
+ # @param service [ServiceRecord] properties for populating the new service
42
+ # @return [ServiceRecord] the service added
43
+ def add_service(name, service = ServiceRecord.new)
40
44
  raise ArgumentError.new("Value must be a ServiceRecord object when adding a service") unless service.class == Longleaf::ServiceRecord
41
45
  raise IndexError.new("Service with name '#{name}' already exists") if @services.key?(name)
42
-
46
+
43
47
  @services[name] = service
44
48
  end
45
-
49
+
50
+ # Updates details of service record as if the service had been executed.
51
+ # @param service_name [String] name of the service run
52
+ # @return [ServiceRecord] the service record updated
53
+ def update_service_as_performed(service_name)
54
+ service_rec = service(service_name) || add_service(service_name)
55
+ service_rec.run_needed = false
56
+ service_rec.timestamp = ServiceDateHelper.formatted_timestamp
57
+ service_rec
58
+ end
59
+
60
+ # Updates details of service record as if the service had encountered a
61
+ # failure during execution.
62
+ # @param service_name [String] name of the service run
63
+ # @return [ServiceRecord] the service record updated
64
+ def update_service_as_failed(service_name)
65
+ service_rec = service(service_name) || add_service(service_name)
66
+ service_rec.failure_timestamp = ServiceDateHelper.formatted_timestamp
67
+ service_rec
68
+ end
69
+
46
70
  # @param name [String] name identifier of the service to retrieve
47
71
  # @return [ServiceRecord] the ServiceRecord for the service identified by name, or nil
48
72
  def service(name)
49
73
  @services[name]
50
74
  end
51
-
75
+
52
76
  # @return [Array<String>] a list of name identifiers for services registered to this record
53
77
  def list_services
54
78
  @services.keys
55
79
  end
56
80
  end
57
- end
81
+ end