Class: JekyllImgFlow::ManifestManager
- Inherits:
-
Object
- Object
- JekyllImgFlow::ManifestManager
- Defined in:
- lib/jekyll-imgflow/manifest_manager.rb
Overview
Manages the manifest of generated images Tracks default vs specialized versions and page usage for cleanup
Constant Summary collapse
- VERSION_TYPES =
%w[default specialized].freeze
Instance Attribute Summary collapse
-
#manifest_path ⇒ Object
readonly
Returns the value of attribute manifest_path.
Instance Method Summary collapse
-
#cleanup_deleted_originals(current_originals) ⇒ Object
Clean up manifest entries for deleted original images.
- #cleanup_obsolete_defaults(expected_operations) ⇒ Object
-
#cleanup_orphans ⇒ Object
Cleanup orphaned specialized images.
-
#current_provider ⇒ Object
Get current provider from site config.
-
#default_version?(original_name, operations) ⇒ Boolean
Check if image version is default type.
- #empty_manifest ⇒ Object
-
#find_orphans ⇒ Object
Find orphaned specialized images (not used on any page).
-
#get_version_output(original_name, operations, type = :specialized) ⇒ Object
Get output path for a specific version.
-
#get_versions(original_name) ⇒ Object
Get all versions for an original image.
-
#handle_provider_change ⇒ Object
Handle provider change by invalidating cache and cleaning optimized directory.
-
#initialize(site) ⇒ ManifestManager
constructor
A new instance of ManifestManager.
-
#load_manifest(path = @manifest_path) ⇒ Object
Load existing manifest or create new one.
- #load_manifest_file(path) ⇒ Object
- #manifest_image(original_name, file_digest) ⇒ Object
- #manifest_images(data) ⇒ Object
-
#migrate_legacy_manifest(images) ⇒ Object
Detect and migrate legacy manifest entries (pre-0.1.11 format).
- #new_version(output_path, operations, type, page_paths, file_digest, provider) ⇒ Object
-
#normalize_operations(operations) ⇒ Object
Normalize operations for consistent comparison after JSON persistence.
- #normalize_page_paths(page_path) ⇒ Object
-
#provider_changed? ⇒ Boolean
Check if the provider has changed since the manifest was created.
-
#register_version(original_name, output_path, operations, type, page_path, file_digest = nil, provider = nil) ⇒ Object
Register a new image version.
-
#remove_page_usage(page_path) ⇒ Object
Remove page from all image version usage tracking.
- #reset_page_usage ⇒ Object
- #same_operations?(left, right) ⇒ Boolean
-
#save ⇒ Object
Save manifest to disk.
-
#update_page_usage(original_name, operations, type, page_path) ⇒ Object
Update page usage for an existing version This is a convenience method that calls register_version.
- #update_version(version, output_path, page_paths, file_digest, provider) ⇒ Object
-
#version_exists?(original_name, operations, type = :specialized, file_digest = nil) ⇒ Boolean
Check if a specific image version exists and is up-to-date.
-
#versions?(original_name) ⇒ Boolean
Check if original image has any versions.
Constructor Details
#initialize(site) ⇒ ManifestManager
Returns a new instance of ManifestManager.
13 14 15 16 17 18 19 20 21 22 23 24 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 13 def initialize(site) @site = site @config = JekyllImgFlow::Config.new(site) @file_cleaner = GeneratedFileCleaner.new(site, @config) @manifest_path = File.join(@site.source, @config.cache_dir, "imgflow-manifest.json") @legacy_manifest_paths = legacy_manifest_paths @manifest = load_manifest(manifest_load_path) @current_provider = current_provider # Check if provider changed and invalidate cache if needed handle_provider_change if provider_changed? end |
Instance Attribute Details
#manifest_path ⇒ Object (readonly)
Returns the value of attribute manifest_path.
11 12 13 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 11 def manifest_path @manifest_path end |
Instance Method Details
#cleanup_deleted_originals(current_originals) ⇒ Object
Clean up manifest entries for deleted original images
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 106 def cleanup_deleted_originals(current_originals) current_set = current_originals.to_set @manifest.each_key do |original_name| next if current_set.include?(original_name) data = @manifest.delete(original_name) VERSION_TYPES.each do |type| (data.dig("versions", type) || []).each do |version| @file_cleaner.delete(version["output"]) end end Jekyll.logger.debug "🗑️ Removed manifest entry for deleted original: #{original_name}" end end |
#cleanup_obsolete_defaults(expected_operations) ⇒ Object
122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 122 def cleanup_obsolete_defaults(expected_operations) @manifest.each_value do |data| versions = data.dig("versions", "default") || [] versions.reject! do |version| obsolete = expected_operations.none? do |operations| same_operations?(version["operations"], operations) end @file_cleaner.delete(version["output"]) if obsolete obsolete end end end |
#cleanup_orphans ⇒ Object
Cleanup orphaned specialized images
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 328 def cleanup_orphans orphans = find_orphans cleaned = [] orphans.each do |orphan| if @file_cleaner.delete(orphan["output"]) cleaned << orphan["output"] Jekyll.logger.info "🗑️ Cleaned orphaned image: #{orphan['output']}" end # Remove from manifest versions = @manifest.dig(orphan["original"], "versions", "specialized") || [] versions.reject! { |v| same_operations?(v["operations"], orphan["operations"]) } end cleaned end |
#current_provider ⇒ Object
Get current provider from site config
102 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 102 def current_provider = @config.backend_priority&.first || "unknown" |
#default_version?(original_name, operations) ⇒ Boolean
Check if image version is default type
288 289 290 291 292 293 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 288 def default_version?(original_name, operations) return false unless @manifest[original_name] versions = @manifest.dig(original_name, "versions", "default") || [] versions.any? { |v| same_operations?(v["operations"], operations) } end |
#empty_manifest ⇒ Object
52 53 54 55 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 52 def empty_manifest @cached_provider = nil {} end |
#find_orphans ⇒ Object
Find orphaned specialized images (not used on any page)
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 296 def find_orphans orphans = [] @manifest.each do |original_name, data| specialized = data.dig("versions", "specialized") || [] specialized.each do |version| next unless version["used_on"].nil? || version["used_on"].empty? orphans << { "original" => original_name, "output" => version["output"], "operations" => version["operations"] } end end orphans end |
#get_version_output(original_name, operations, type = :specialized) ⇒ Object
Get output path for a specific version
206 207 208 209 210 211 212 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 206 def get_version_output(original_name, operations, type = :specialized) return unless @manifest[original_name] versions = @manifest.dig(original_name, "versions", type.to_s) || [] version = versions.find { |v| same_operations?(v["operations"], operations) } version&.dig("output") end |
#get_versions(original_name) ⇒ Object
Get all versions for an original image
347 348 349 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 347 def get_versions(original_name) @manifest.dig(original_name, "versions") || { "default" => [], "specialized" => [] } end |
#handle_provider_change ⇒ Object
Handle provider change by invalidating cache and cleaning optimized directory
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 187 def handle_provider_change Jekyll.logger.warn "ImgFlow:", "Provider changed from '#{@cached_provider}' to '#{@current_provider}'" Jekyll.logger.warn "ImgFlow:", "Invalidating cache and cleaning optimized directory..." deleted_count = @file_cleaner.clear Jekyll.logger.warn "ImgFlow:", "Deleted #{deleted_count} cached images from optimized directory" # Clear manifest cache @manifest.clear Jekyll.logger.warn "ImgFlow:", "Manifest cache cleared - all images will be regenerated with '#{@current_provider}'" # Update cached provider to current @cached_provider = @current_provider end |
#load_manifest(path = @manifest_path) ⇒ Object
Load existing manifest or create new one
27 28 29 30 31 32 33 34 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 27 def load_manifest(path = @manifest_path) return empty_manifest unless path && File.exist?(path) load_manifest_file(path) rescue JSON::ParserError => e Jekyll.logger.warn "ImgFlow: Corrupt manifest file, starting fresh: #{e.}" empty_manifest end |
#load_manifest_file(path) ⇒ Object
36 37 38 39 40 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 36 def load_manifest_file(path) data = JSON.parse(File.read(path)) images = manifest_images(data) migrate_legacy_manifest(images) end |
#manifest_image(original_name, file_digest) ⇒ Object
252 253 254 255 256 257 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 252 def manifest_image(original_name, file_digest) @manifest[original_name] ||= { "versions" => { "default" => [], "specialized" => [] }, "file_digest" => file_digest } end |
#manifest_images(data) ⇒ Object
42 43 44 45 46 47 48 49 50 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 42 def manifest_images(data) if data.is_a?(Hash) && data.key?("images") @cached_provider = data["provider"] data["images"] || {} else @cached_provider = nil data end end |
#migrate_legacy_manifest(images) ⇒ Object
Detect and migrate legacy manifest entries (pre-0.1.11 format). Old entries have operations like 400 without format/quality and no file_digest. Clearing them lets the build re-register existing output files without reprocessing (output_up_to_date? check handles this).
61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 61 def migrate_legacy_manifest(images) legacy = images.any? do |_name, data| versions = data.dig("versions", "default") || [] versions.any? { |v| v["file_digest"].nil? || !v["operations"].key?("format") } end if legacy Jekyll.logger.info "ImgFlow: Detected legacy manifest format, " \ "clearing entries for re-registration." images.clear end images end |
#new_version(output_path, operations, type, page_paths, file_digest, provider) ⇒ Object
275 276 277 278 279 280 281 282 283 284 285 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 275 def new_version(output_path, operations, type, page_paths, file_digest, provider) { "output" => output_path, "operations" => operations, "type" => type.to_s, "used_on" => page_paths, "created_at" => Time.now.to_i, "file_digest" => file_digest, "provider" => provider } end |
#normalize_operations(operations) ⇒ Object
Normalize operations for consistent comparison after JSON persistence.
144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 144 def normalize_operations(operations) case operations when Hash operations.to_h do |key, value| [key.to_s, normalize_operations(value)] end when Array operations.map { |value| normalize_operations(value) } when Symbol operations.to_s else operations end end |
#normalize_page_paths(page_path) ⇒ Object
259 260 261 262 263 264 265 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 259 def normalize_page_paths(page_path) if page_path.is_a?(Array) page_path else (page_path ? [page_path] : []) end end |
#provider_changed? ⇒ Boolean
Check if the provider has changed since the manifest was created
178 179 180 181 182 183 184 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 178 def provider_changed? # If no cached provider, this is a new manifest return false if @cached_provider.nil? # Provider changed if current doesn't match cached @cached_provider != @current_provider end |
#register_version(original_name, output_path, operations, type, page_path, file_digest = nil, provider = nil) ⇒ Object
Register a new image version
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 236 def register_version(original_name, output_path, operations, type, page_path, file_digest = nil, provider = nil) image = manifest_image(original_name, file_digest) image["file_digest"] = file_digest if file_digest versions = image["versions"][type.to_s] ||= [] normalized = normalize_operations(operations) page_paths = normalize_page_paths(page_path) version = versions.find { |entry| same_operations?(entry["operations"], normalized) } if version update_version(version, output_path, page_paths, file_digest, provider) else versions << new_version(output_path, normalized, type, page_paths, file_digest, provider) end end |
#remove_page_usage(page_path) ⇒ Object
Remove page from all image version usage tracking
316 317 318 319 320 321 322 323 324 325 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 316 def remove_page_usage(page_path) @manifest.each_value do |data| VERSION_TYPES.each do |type| versions = data.dig("versions", type) || [] versions.each do |version| version["used_on"]&.delete(page_path) end end end end |
#reset_page_usage ⇒ Object
135 136 137 138 139 140 141 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 135 def reset_page_usage @manifest.each_value do |data| (data.dig("versions", "specialized") || []).each do |version| version["used_on"] = [] end end end |
#same_operations?(left, right) ⇒ Boolean
159 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 159 def same_operations?(left, right) = normalize_operations(left) == normalize_operations(right) |
#save ⇒ Object
Save manifest to disk
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 75 def save # Store current provider at top level of manifest manifest_data = { "provider" => @current_provider, "images" => @manifest } content = JSON.pretty_generate(manifest_data) if File.exist?(@manifest_path) && File.binread(@manifest_path) == content cleanup_legacy_manifests return end directory = File.dirname(@manifest_path) temporary_path = "#{@manifest_path}.tmp-#{Process.pid}-#{Thread.current.object_id}" FileUtils.mkdir_p(directory) File.open(temporary_path, "wb") do |file| file.write(content) file.flush file.fsync end File.rename(temporary_path, @manifest_path) cleanup_legacy_manifests ensure FileUtils.rm_f(temporary_path) if temporary_path end |
#update_page_usage(original_name, operations, type, page_path) ⇒ Object
Update page usage for an existing version This is a convenience method that calls register_version
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 216 def update_page_usage(original_name, operations, type, page_path) Jekyll.logger.debug "🔍 update_page_usage: original_name=#{original_name}, operations=#{operations.inspect}, type=#{type}, page_path=#{page_path}" # Get the existing version's output path (already relative) output_path = get_version_output(original_name, operations, type) Jekyll.logger.debug "🔍 update_page_usage: output_path=#{output_path}" return unless output_path # Get the existing version to preserve provider versions = @manifest.dig(original_name, "versions", type.to_s) || [] version = versions.find { |v| same_operations?(v["operations"], operations) } provider = version&.dig("provider") Jekyll.logger.debug "🔍 update_page_usage: provider=#{provider}, current used_on=#{version&.dig('used_on')&.inspect}" # Call register_version to update page usage (output_path is already relative) register_version(original_name, output_path, operations, type, page_path, nil, provider) Jekyll.logger.debug "🔍 update_page_usage: completed register_version call" end |
#update_version(version, output_path, page_paths, file_digest, provider) ⇒ Object
267 268 269 270 271 272 273 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 267 def update_version(version, output_path, page_paths, file_digest, provider) version["used_on"] ||= [] page_paths.each { |path| version["used_on"] << path if path && !version["used_on"].include?(path) } version["output"] = output_path version["file_digest"] = file_digest if file_digest version["provider"] = provider if provider end |
#version_exists?(original_name, operations, type = :specialized, file_digest = nil) ⇒ Boolean
Check if a specific image version exists and is up-to-date
162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 162 def version_exists?(original_name, operations, type = :specialized, file_digest = nil) return false unless @manifest[original_name] # Provider change is already handled in initialize, so just check operations versions = @manifest.dig(original_name, "versions", type.to_s) || [] Jekyll.logger.debug "🔍 ManifestManager: Looking for operations: #{operations.inspect} in #{versions.length} #{type} versions" result = versions.any? do |version| same_operations?(version["operations"], operations) && (!file_digest || version["file_digest"] == file_digest) end Jekyll.logger.debug "🔍 ManifestManager: Final result: #{result}" result end |
#versions?(original_name) ⇒ Boolean
Check if original image has any versions
352 353 354 355 356 357 |
# File 'lib/jekyll-imgflow/manifest_manager.rb', line 352 def versions?(original_name) return false unless @manifest[original_name] versions = @manifest[original_name]["versions"] (versions["default"]&.any? || false) || (versions["specialized"]&.any? || false) end |