require 'fileutils'
module Memoria
# Reads and writes snapshots to and from the file system.
class SnapshotSaver
# Creates a new SnapshotSaver.
#
# @example
# snapshot_saver = SnapshotSaver.new(Configuration.new)
#
# @param [Configuration] configuration The gem's configuration.
#
# @return [SnapshotSaver] An instance of +SnapshotSaver+.
#
# @api public
#
def initialize(configuration)
@configuration = configuration
end
# Whether a snapshot for the given storage key (file name) is persisted in the file system.
#
# @example
# snapshot_saver = SnapshotSaver.new(Configuration.new)
# snapshot_saver.snapshot_exists?(Snapshot.new('does-not')) # => false
#
# @param [String] file_name The storage key (file name) of the snapshot.
#
# @return [SnapshotSaver] An instance of +SnapshotSaver+.
#
# @api public
#
def snapshot_exists?(file_name)
location = absolute_path_to_file(file_name)
return false if location.nil?
File.exist?(location)
end
# Retrieves the snapshot contents for the given storage key (file name).
#
# @example When there is no snapshot for a given storage key (file name).
# snapshot_saver = SnapshotSaver.new(Configuration.new)
# snapshot_saver.read('/404') # => nil
#
# @example When there is a snapshot for a given storage key (file name).
# snapshot_saver = SnapshotSaver.new(Configuration.new)
# snapshot_saver.read('index_page') # => '
Hello World
'
#
# @param [String] file_name The storage key (file name) of the snapshot.
#
# @return [String] The snapshot content.
#
# @api public
#
def read(file_name)
path = absolute_path_to_file(file_name)
return nil unless File.exist?(path)
File.read(path)
end
# Persists the snapshot contents for the given storage key (file name).
#
# @example
# snapshot_saver = SnapshotSaver.new(Configuration.new)
# snapshot_saver.write('index_page', 'Homepage')
# snapshot_saver.read('index_page') # => 'Homepage'
#
# @param [String] file_name The storage key (file name) of the snapshot.
#
# @param [String] content The snapshot content.
#
# @return [Fixnum] The length written.
#
# @api public
#
def write(file_name, content)
path = absolute_path_to_file(file_name)
directory = File.dirname(path)
FileUtils.mkdir_p(directory) unless File.exist?(directory)
File.binwrite(path, content)
end
private
# Returns the gem's configuration.
#
# @return [Configuration] The gem's configuration.
#
attr_reader :configuration
# Returns the absolute path to a given file, including the snapshot extension.
#
# @param [String] file_name The storage key (file name) of the snapshot.
#
# @return [String]
#
def absolute_path_to_file(file_name)
return nil unless storage_location
absolute_file_name = File.join(storage_location, sanitized_file_name_from(file_name) + file_extension)
File.expand_path(absolute_file_name)
end
# Sanitizes a given storage key (file name) to be a valid file name.
#
# @param [String] file_name The storage key (file name) of the snapshot.
#
# @return [String] A sanitized file name.
#
def sanitized_file_name_from(file_name)
parts = file_name.to_s.split('.')
file_extension = '.' + parts.pop if parts.size > 1 && !parts.last.include?(File::SEPARATOR)
parts.join('.').gsub(%r{[^[:word:]\-\/]+}, '_') + file_extension.to_s
end
# Returns the file extension for snapshots.
#
# @return [String] Extension for snapshots.
#
def file_extension
'.' + configuration.snapshot_extension
end
# Returns the path to the directory where snapshots are saved.
#
# @return [String] Path to the directory where snapshots are saved.
#
def storage_location
configuration.snapshot_directory
end
end
end