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,110 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>
7
+ Top Level Namespace
8
+
9
+ &mdash; Documentation by YARD 0.9.19
10
+
11
+ </title>
12
+
13
+ <link rel="stylesheet" href="css/style.css" type="text/css" charset="utf-8" />
14
+
15
+ <link rel="stylesheet" href="css/common.css" type="text/css" charset="utf-8" />
16
+
17
+ <script type="text/javascript" charset="utf-8">
18
+ pathId = "";
19
+ relpath = '';
20
+ </script>
21
+
22
+
23
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
24
+
25
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
26
+
27
+
28
+ </head>
29
+ <body>
30
+ <div class="nav_wrap">
31
+ <iframe id="nav" src="class_list.html?1"></iframe>
32
+ <div id="resizer"></div>
33
+ </div>
34
+
35
+ <div id="main" tabindex="-1">
36
+ <div id="header">
37
+ <div id="menu">
38
+
39
+ <a href="_index.html">Index</a> &raquo;
40
+
41
+
42
+ <span class="title">Top Level Namespace</span>
43
+
44
+ </div>
45
+
46
+ <div id="search">
47
+
48
+ <a class="full_list_link" id="class_list_link"
49
+ href="class_list.html">
50
+
51
+ <svg width="24" height="24">
52
+ <rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
53
+ <rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
54
+ <rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
55
+ </svg>
56
+ </a>
57
+
58
+ </div>
59
+ <div class="clear"></div>
60
+ </div>
61
+
62
+ <div id="content"><h1>Top Level Namespace
63
+
64
+
65
+
66
+ </h1>
67
+ <div class="box_info">
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+
78
+
79
+ </div>
80
+
81
+ <h2>Defined Under Namespace</h2>
82
+ <p class="children">
83
+
84
+
85
+ <strong class="modules">Modules:</strong> <span class='object_link'><a href="Longleaf.html" title="Longleaf (module)">Longleaf</a></span>
86
+
87
+
88
+
89
+
90
+ </p>
91
+
92
+
93
+
94
+
95
+
96
+
97
+
98
+
99
+
100
+ </div>
101
+
102
+ <div id="footer">
103
+ Generated on Tue May 28 15:48:00 2019 by
104
+ <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
105
+ 0.9.19 (ruby-2.6.3).
106
+ </div>
107
+
108
+ </div>
109
+ </body>
110
+ </html>
@@ -4,7 +4,11 @@ module Longleaf
4
4
  # Selects and allows for iteration over files which match a provided set of selection criteria
5
5
  class FileSelector
6
6
  include Longleaf::Logging
7
-
7
+ SPECIFICITY_PATH = 'path'
8
+ SPECIFICITY_STORAGE_LOCATION = 'storage_location'
9
+
10
+ attr_reader :specificity
11
+
8
12
  # May only provide either file_paths or storage_locations
9
13
  def initialize(file_paths: nil, storage_locations: nil, app_config:)
10
14
  if nil_or_empty?(file_paths) && nil_or_empty?(storage_locations)
@@ -15,11 +19,28 @@ module Longleaf
15
19
  end
16
20
  @app_config = app_config
17
21
  # The top level paths targeted by this selector
18
- @target_paths = file_paths
22
+ @target_paths = file_paths&.map do |path|
23
+ # Resolve relative paths against pwd
24
+ pathname = Pathname.new(path)
25
+ if !pathname.absolute?
26
+ path = File.join(Dir.pwd, path)
27
+ end
28
+ path = File.expand_path(path)
29
+
30
+ # adding trailing /'s to directories
31
+ if Dir.exist?(path) && !path.end_with?('/')
32
+ path + '/'
33
+ else
34
+ path
35
+ end
36
+ end
19
37
  # The set of storage locations to select file paths from
20
38
  @storage_locations = storage_locations
21
39
  # Validate that the selected storage locations are known
22
- unless @storage_locations.nil?
40
+ if @storage_locations.nil?
41
+ @specificity = SPECIFICITY_PATH
42
+ else
43
+ @specificity = SPECIFICITY_STORAGE_LOCATION
23
44
  locations = @app_config.location_manager.locations
24
45
  @storage_locations.each do |loc_name|
25
46
  unless locations.key?(loc_name)
@@ -28,7 +49,7 @@ module Longleaf
28
49
  end
29
50
  end
30
51
  end
31
-
52
+
32
53
  # @return [Array] a list of top level paths from which files will be selected
33
54
  def target_paths
34
55
  # If starting from locations, initialize by expanding locations out to their actual paths
@@ -38,10 +59,10 @@ module Longleaf
38
59
  @target_paths << @app_config.location_manager.locations[loc_name].path
39
60
  end
40
61
  end
41
-
62
+
42
63
  @target_paths
43
64
  end
44
-
65
+
45
66
  # Get the next file path for this selector.
46
67
  # @return [String] an absolute path to the next file targeted by this selector,
47
68
  # or nil if no more files selected
@@ -54,17 +75,17 @@ module Longleaf
54
75
 
55
76
  # No more paths to return
56
77
  return nil if @paths&.empty?
57
-
78
+
58
79
  # Get the most recently added path for depth first traversal of selected paths
59
80
  path = @paths.pop
60
81
  until path.nil? do
61
82
  @app_config.location_manager.verify_path_in_location(path)
62
-
83
+
63
84
  if File.exist?(path)
64
85
  if File.directory?(path)
65
86
  logger.debug("Expanding directory #{path}")
66
87
  # For a directory, add all children to file_paths
67
- Dir.entries(path).sort.reverse.each do |child|
88
+ Dir.entries(path).sort.reverse_each do |child|
68
89
  @paths << File.join(path, child) unless child == '.' or child == '..'
69
90
  end
70
91
  else
@@ -74,12 +95,23 @@ module Longleaf
74
95
  else
75
96
  raise InvalidStoragePathError.new("File #{path} does not exist.")
76
97
  end
77
-
98
+
78
99
  # Returned path was not a suitable file, try the next path
79
100
  path = @paths.pop
80
101
  end
81
102
  end
82
-
103
+
104
+ # Iterate through the file paths for this selector and execute the provided block with each.
105
+ # A block is required.
106
+ def each
107
+ file_path = next_path
108
+ until file_path.nil?
109
+ yield file_path
110
+
111
+ file_path = next_path
112
+ end
113
+ end
114
+
83
115
  # return [Array] a list of all storage locations being targeted by this selector
84
116
  def storage_locations
85
117
  # Determine what storage_locations are represented by the given file paths
@@ -91,17 +123,17 @@ module Longleaf
91
123
  end
92
124
  @storage_locations = loc_set.to_a
93
125
  end
94
-
126
+
95
127
  if @storage_locations.nil?
96
128
  @storage_locations = Array.new
97
129
  end
98
-
130
+
99
131
  @storage_locations
100
132
  end
101
-
133
+
102
134
  private
103
135
  def nil_or_empty?(value)
104
136
  value.nil? || value.empty?
105
137
  end
106
138
  end
107
- end
139
+ end
@@ -0,0 +1,67 @@
1
+ require 'longleaf/candidates/file_selector'
2
+ require 'longleaf/logging'
3
+
4
+ module Longleaf
5
+ # Selects and allows for iteration over files which are registered and match a provided
6
+ # set of selection criteria
7
+ class RegisteredFileSelector < FileSelector
8
+ include Longleaf::Logging
9
+
10
+ # Get the next file path for this selector.
11
+ # @raise [InvalidStoragePathError] if any of the selected files do not exist
12
+ # @raise [StorageLocationUnavailableError] if any of the selected paths are not
13
+ # in a registered storage location.
14
+ # @return [String] an absolute path to the next file targeted by this selector,
15
+ # or nil if no more files selected
16
+ def next_path
17
+ if @md_paths.nil?
18
+ # Compute the starting paths by looking up the metadata paths for the provided targets,
19
+ # in reverse order since @md_paths is a LIFO stack structure.
20
+ @md_paths = target_paths.reverse_each.map do |file_path|
21
+ storage_loc = @app_config.location_manager.verify_path_in_location(file_path)
22
+ storage_loc.get_metadata_path_for(file_path)
23
+ end
24
+ end
25
+
26
+ # No more paths to return
27
+ return nil if @md_paths&.empty?
28
+
29
+ # Get the most recently added path for depth first traversal of selected paths
30
+ md_path = @md_paths.pop
31
+ until md_path.nil? do
32
+ if File.exist?(md_path)
33
+ if File.directory?(md_path)
34
+ logger.debug("Expanding metadata directory #{md_path}")
35
+ # For a directory, add all children to file_paths
36
+ Dir.entries(md_path).sort.reverse_each do |child|
37
+ @md_paths << File.join(md_path, child) unless child == '.' or child == '..'
38
+ end
39
+ elsif md_path.end_with?(MetadataSerializer::metadata_suffix)
40
+ # Convert metadata path to file path before returning
41
+ return file_path_for_metadata(md_path)
42
+ else
43
+ logger.debug("Skipping non-metadata file in metadata directory #{md_path}")
44
+ end
45
+ else
46
+ file_path = file_path_for_metadata(md_path)
47
+ if File.exist?(file_path)
48
+ raise RegistrationError.new("File #{file_path} is not registered.")
49
+ else
50
+ raise InvalidStoragePathError.new("File #{file_path} does not exist.")
51
+ end
52
+ end
53
+
54
+ # Returned path was not a suitable file, try the next path
55
+ md_path = @md_paths.pop
56
+ end
57
+ end
58
+
59
+ private
60
+ def file_path_for_metadata(md_path)
61
+ storage_loc = @app_config.location_manager.get_location_by_metadata_path(md_path)
62
+ file_path = storage_loc.get_path_from_metadata_path(md_path)
63
+ logger.debug("Returning next file #{file_path} for metadata path #{md_path}")
64
+ return file_path
65
+ end
66
+ end
67
+ end
@@ -1,5 +1,4 @@
1
1
  require 'longleaf/services/service_manager'
2
- require 'longleaf/services/metadata_deserializer'
3
2
  require 'longleaf/events/event_names'
4
3
  require 'longleaf/errors'
5
4
  require 'longleaf/logging'
@@ -11,89 +10,84 @@ module Longleaf
11
10
  # about service status.
12
11
  class ServiceCandidateFilesystemIterator
13
12
  include Longleaf::Logging
14
-
13
+
15
14
  def initialize(file_selector, event, app_config, force = false)
16
15
  @file_selector = file_selector
17
16
  @event = event
18
17
  @app_config = app_config
19
18
  @force = force
20
19
  end
21
-
22
- # Get the file record for the next candidate which needs services run which match the
20
+
21
+ # Get the file record for the next candidate which needs services run which match the
23
22
  # provided file_selector
24
23
  # @return [FileRecord] file record of the next candidate with services needing to be run,
25
24
  # or nil if there are no more candidates.
26
25
  def next_candidate
27
26
  loop do
28
- begin
29
- next_path = @file_selector.next_path
30
- return nil if next_path.nil?
31
-
32
- logger.debug("Evaluating candidate #{next_path}")
33
- storage_loc = @app_config.location_manager.get_location_by_path(next_path)
34
- file_rec = FileRecord.new(next_path, storage_loc)
35
-
36
- # Skip over unregistered files
37
- if !file_rec.metadata_present?
38
- logger.debug("Ignoring unregistered file #{next_path}")
39
- next
40
- end
41
-
42
- file_rec.metadata_record = MetadataDeserializer.deserialize(file_path: file_rec.metadata_path,
43
- digest_algs: storage_loc.metadata_digests)
44
-
45
- # Return the file record if it needs any services run
46
- return file_rec if needs_run?(file_rec)
47
- rescue InvalidStoragePathError => e
48
- logger.warn("Skipping candidate file: #{e.message}")
27
+ next_path = @file_selector.next_path
28
+ return nil if next_path.nil?
29
+
30
+ logger.debug("Evaluating candidate #{next_path}")
31
+ storage_loc = @app_config.location_manager.get_location_by_path(next_path)
32
+ file_rec = FileRecord.new(next_path, storage_loc)
33
+
34
+ # Skip over unregistered files
35
+ if !file_rec.metadata_present?
36
+ logger.debug("Ignoring unregistered file #{next_path}")
37
+ next
49
38
  end
39
+
40
+ @app_config.md_manager.load(file_rec)
41
+
42
+ # Return the file record if it needs any services run
43
+ return file_rec if needs_run?(file_rec)
50
44
  end
51
45
  end
52
-
46
+
53
47
  # Iterate through the candidates in this object and execute the provided block with each
54
48
  # candidate. A block is required.
55
49
  def each
56
50
  file_rec = next_candidate
57
51
  until file_rec.nil?
58
52
  yield file_rec
59
-
53
+
60
54
  file_rec = next_candidate
61
55
  end
62
56
  end
63
-
57
+
64
58
  private
65
59
  # Returns true if the file record contains any services which need to be run
66
60
  def needs_run?(file_rec)
67
61
  md_rec = file_rec.metadata_record
68
62
  storage_loc = file_rec.storage_location
69
63
  service_manager = @app_config.service_manager
70
-
64
+
71
65
  # File is not a valid candidate for services if it is deregistered, unless performing cleanup
72
66
  if @event != EventNames::CLEANUP && md_rec.deregistered?
73
67
  logger.debug("Skipping deregistered file: #{file_rec.path}")
74
68
  return false
75
69
  end
76
-
70
+
77
71
  expected_services = service_manager.list_services(
78
72
  location: storage_loc.name,
79
73
  event: @event)
80
-
74
+
81
75
  # When in force mode, candidate needs a run as long as there are any services configured for it.
82
- if @force && expected_services.length > 0
76
+ if @force && !expected_services.empty?
83
77
  logger.debug("Forcing run needed for file: #{file_rec.path}")
84
78
  return true
85
79
  end
86
-
80
+
87
81
  expected_services.each do |service_name|
88
82
  if service_manager.service_needed?(service_name, md_rec)
89
83
  logger.debug("Service #{service_name} needed for file: #{file_rec.path}")
90
84
  return true
91
85
  end
92
86
  end
93
-
87
+
94
88
  logger.debug("Run not needed for file: #{file_rec.path}")
95
89
  # No services needed to be run for this file
96
90
  false
97
91
  end
98
92
  end
99
- end
93
+ end
@@ -0,0 +1,84 @@
1
+ require 'longleaf/events/event_names'
2
+ require 'longleaf/errors'
3
+ require 'longleaf/logging'
4
+ require 'time'
5
+
6
+ module Longleaf
7
+ # Iterator for getting file candidates which have services which need to be run.
8
+ # Implementation uses an index of file metadata to determine if the file needs any
9
+ # services run.
10
+ class ServiceCandidateIndexIterator
11
+ include Longleaf::Logging
12
+
13
+ def initialize(file_selector, event, app_config, force = false)
14
+ @file_selector = file_selector
15
+ @event = event
16
+ @app_config = app_config
17
+ @force = force
18
+ @index_manager = @app_config.index_manager
19
+ @stale_datetime = Time.now.utc
20
+ @result_set = nil
21
+ end
22
+
23
+ # Get the file record for the next candidate which needs services run which match the
24
+ # provided file_selector
25
+ # @return [FileRecord] file record of the next candidate with services needing to be run,
26
+ # or nil if there are no more candidates.
27
+ def next_candidate
28
+ file_rec = nil
29
+ # loop until a candidate with metadata is retrieved
30
+ loop do
31
+ # Get the next page of results if the previous page has been processed
32
+ fetch_next_page if @result_set.nil? || @result_set.empty?
33
+
34
+ # Retrieve the next possible candidate path from the page
35
+ next_path = @result_set.shift
36
+ # given that the page was just retrieved, getting a nil path indicates that the retrieved page of
37
+ # candidates is empty and there are no more candidates to iterate at this time.
38
+ return nil if next_path.nil?
39
+
40
+ logger.debug("Retrieved candidate #{next_path}")
41
+ storage_loc = @app_config.location_manager.get_location_by_path(next_path)
42
+ file_rec = FileRecord.new(next_path, storage_loc)
43
+
44
+ # Keep seeking until a registered candidate is found, according to the file system.
45
+ if file_rec.metadata_present?
46
+ break
47
+ else
48
+ logger.warn("Encountered #{next_path} in index, but path is not registered. Clearing out of synch entry.")
49
+ @index_manager.remove(file_rec)
50
+ end
51
+ end
52
+
53
+ @app_config.md_manager.load(file_rec)
54
+
55
+ file_rec
56
+ end
57
+
58
+ # Iterate through the candidates in this object and execute the provided block with each
59
+ # candidate. A block is required.
60
+ def each
61
+ file_rec = next_candidate
62
+ until file_rec.nil?
63
+ yield file_rec
64
+
65
+ file_rec = next_candidate
66
+ end
67
+ end
68
+
69
+ private
70
+ def fetch_next_page
71
+ if @force
72
+ @result_set = @index_manager.registered_paths(@file_selector)
73
+ else
74
+ case @event
75
+ when EventNames::PRESERVE
76
+ @result_set = @index_manager.paths_with_stale_services(@file_selector, @stale_datetime)
77
+ when EventNames::CLEANUP
78
+ # TODO
79
+ end
80
+ end
81
+ logger.debug("Retrieve result set with #{@result_set&.length} entries")
82
+ end
83
+ end
84
+ end