module Nanoc::Int # An abstract superclass for classes that need to store data to the # filesystem, such as checksums, cached compiled content and dependency # graphs. # # Each store has a version number. When attempting to load data from a store # that has an incompatible version number, no data will be loaded, but # {#version_mismatch_detected} will be called. # # @abstract Subclasses must implement {#data} and {#data=}, and may # implement {#no_data_found} and {#version_mismatch_detected}. # # @api private class Store # @return [String] The name of the file where data will be loaded from and # stored to. attr_reader :filename # @return [Numeric] The version number corresponding to the file format # the data is in. When the file format changes, the version number # should be incremented. attr_reader :version # Creates a new store for the given filename. # # @param [String] filename The name of the file where data will be loaded # from and stored to. # # @param [Numeric] version The version number corresponding to the file # format the data is in. When the file format changes, the version # number should be incremented. def initialize(filename, version) @filename = filename @version = version end # @group Loading and storing data # @return The data that should be written to the disk # # @abstract This method must be implemented by the subclass. def data raise NotImplementedError.new('Nanoc::Int::Store subclasses must implement #data and #data=') end # @param new_data The data that has been loaded from the disk # # @abstract This method must be implemented by the subclass. # # @return [void] def data=(new_data) # rubocop:disable Lint/UnusedMethodArgument raise NotImplementedError.new('Nanoc::Int::Store subclasses must implement #data and #data=') end # Loads the data from the filesystem into memory. This method will set the # loaded data using the {#data=} method. # # @return [void] def load # Don’t load twice if @loaded return end # Check file existance unless File.file?(filename) no_data_found @loaded = true return end begin pstore.transaction do # Check version if pstore[:version] != version version_mismatch_detected @loaded = true return end # Load self.data = pstore[:data] @loaded = true end rescue FileUtils.rm_f(filename) load end end # Undoes the effects of {#load}. Used when {#load} raises an exception. def unload end # Stores the data contained in memory to the filesystem. This method will # use the {#data} method to fetch the data that should be written. # # @return [void] def store FileUtils.mkdir_p(File.dirname(filename)) pstore.transaction do pstore[:data] = data pstore[:version] = version end end # @group Callback methods # Callback method that is called when no data file was found. By default, # this implementation does nothing, but it should probably be overridden # by the subclass. # # @return [void] def no_data_found end # Callback method that is called when a version mismatch is detected. By # default, this implementation does nothing, but it should probably be # overridden by the subclass. # # @return [void] def version_mismatch_detected end private def pstore @pstore ||= PStore.new(filename) end end end