lib/roda/plugins/recheck_precompiled_assets.rb
# frozen-string-literal: true # class Roda module RodaPlugins # The recheck_precompiled_assets plugin enables checking for the precompiled asset metadata file. # You need to have already loaded the assets plugin with the +:precompiled+ option and the file # specified by the +:precompiled+ option must already exist in order to use the # recheck_precompiled_assets plugin. # # Any time you want to check whether the precompiled asset metadata file has changed and should be # reloaded, you can call the +recheck_precompiled_assets+ class method. This method will check # whether the file has changed, and reload it if it has. If you want to check for modifications on # every request, you can use +self.class.recheck_precompiled_assets+ inside your route block. module RecheckPrecompiledAssets # Thread safe wrapper for the compiled asset metadata hash. Does not wrap all # hash methods, only a few that are used. class CompiledAssetsHash include Enumerable def initialize @hash = {} @mutex = Mutex.new end def [](key) @mutex.synchronize{@hash[key]} end def []=(key, value) @mutex.synchronize{@hash[key] = value} end def replace(hash) hash = hash.instance_variable_get(:@hash) if (CompiledAssetsHash === hash) @mutex.synchronize{@hash.replace(hash)} self end def each(&block) @mutex.synchronize{@hash.dup}.each(&block) self end def to_json(*args) @mutex.synchronize{@hash.dup}.to_json(*args) end end def self.load_dependencies(app) unless app.respond_to?(:assets_opts) && app.assets_opts[:precompiled] raise RodaError, "must load assets plugin with precompiled option before loading recheck_precompiled_assets plugin" end end def self.configure(app) precompiled_file = app.assets_opts[:precompiled] prev_mtime = ::File.mtime(precompiled_file) app.instance_exec do opts[:assets] = opts[:assets].merge(:compiled=>_compiled_assets_initial_hash.replace(assets_opts[:compiled])).freeze define_singleton_method(:recheck_precompiled_assets) do new_mtime = ::File.mtime(precompiled_file) if new_mtime != prev_mtime prev_mtime = new_mtime assets_opts[:compiled].replace(_precompiled_asset_metadata(precompiled_file)) # Unset the cached asset matchers, so new ones will be generated. # This is needed in case the new precompiled metadata uses # different files. app::RodaRequest.instance_variable_set(:@assets_matchers, nil) end end singleton_class.send(:alias_method, :recheck_precompiled_assets, :recheck_precompiled_assets) end end module ClassMethods private # Wrap the precompiled asset metadata in a thread-safe hash. def _precompiled_asset_metadata(file) CompiledAssetsHash.new.replace(super) end # Use a thread-safe wrapper of a hash for the :compiled assets option, since # the recheck_precompiled_asset_metadata can modify it at runtime. def _compiled_assets_initial_hash CompiledAssetsHash.new end end module RequestClassMethods private # Use a regexp that matches any digest. When the precompiled asset metadata # file is updated, this allows requests for a previous precompiled asset to # still work. def _asset_regexp(type, key, _) /#{Regexp.escape(key.sub(/\A#{type}/, ''))}\.[0-9a-fA-F]+\.#{type}/ end end end register_plugin(:recheck_precompiled_assets, RecheckPrecompiledAssets) end end