Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 115 additions & 76 deletions lib/sinatra/cache/helpers.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

module Sinatra
module Sinatra

# = Sinatra::Cache
#
# A Sinatra Extension that makes Page and Fragment Caching easy wthin your Sinatra apps.
Expand Down Expand Up @@ -325,9 +325,9 @@ module Sinatra
#
#
module Cache
module Helpers

module Helpers

##
# This method either caches the code fragment and then renders it,
# or locates the cached fragement and renders that.
Expand Down Expand Up @@ -387,10 +387,10 @@ module Helpers
# ... would use the same cached fragment.
#
# @api public
def cache_fragment(fragment_name, shared = nil, &block)
def cache_fragment(fragment_name, shared = nil, &block)
# 1. check for a block, there must always be a block
raise ArgumentError, "Missing block" unless block_given?

# 2. get the fragment path, by combining the PATH_INFO of the request, and the fragment_name
dir_structure = request.path_info.empty? ? '' : request.path_info.gsub(/^\//,'').gsub(/\/$/,'')
# if we are sharing this fragment with other URLs (as in the all the articles in a category of a blog)
Expand All @@ -402,7 +402,7 @@ def cache_fragment(fragment_name, shared = nil, &block)
cf = "#{settings.cache_fragments_output_dir}/#{dir_structure}/#{fragment_name}.html"
# 3. ensure the fragment cache directory exists for this fragment
FileUtils.mkdir_p(File.dirname(cf)) rescue "ERROR: could NOT create the cache directory: [ #{File.dirname(cf)} ]"

# 3. check if the fragment is already cached ?
if test(?f, cf)
# 4. yes. cached, so load it up into the ERB buffer . Sorry, don't know how to do this for Haml or any others.
Expand All @@ -426,19 +426,41 @@ def cache_fragment(fragment_name, shared = nil, &block)
end
# for future versions once old habits are gone
# alias_method :cache, :cache_fragment



##
# Cache some raw data
# @api public
def cache_raw(options = {}, &block)

# 1. check for a block, there must always be a block
raise ArgumentError, "Missing block" unless block_given?

if cache_enabled?(options)
file_path = cache_file_path
if test(?f, file_path)
IO.read(file_path)
else
content = block.call
cache_write_file(file_path, content)
content
end
else
block.call
end
end


##
# <b>NB!! Deprecated method.</b>
#
# Just returns the content after throwing out a warning.
#
def cache(content, opts={})
def cache(content, opts={})
warn("Deprecated method, caching is now happening by default if the :cache_enabled option is true")
content
end


##
# Expires the cached file (page) or fragment.
#
Expand All @@ -455,27 +477,27 @@ def cache(content, opts={})
#
#
# @api public
def cache_expire(path, options={})
def cache_expire(path, options={})
# 1. bail quickly if we don't have caching enabled
return unless settings.cache_enabled
options = { :fragment => false }.merge(options)

if options[:fragment] # dealing with a fragment
dir_structure = path.gsub(/^\//,'').gsub(/\/$/,'')
file_path = "#{settings.cache_fragments_output_dir}/#{dir_structure}/#{options[:fragment]}.html"
else
file_path = cache_file_path(path)
end

if test(?f, file_path)
File.delete(file_path)
log(:info,"Expired [#{file_path.sub(settings.root,'')}] successfully")
else
log(:warn,"The cached file [#{file_path}] could NOT be expired as it was NOT found")
end
end


##
# Prints a basic HTML comment with a timestamp in it, so that you can see when a file was cached last.
#
Expand All @@ -487,126 +509,143 @@ def cache_expire(path, options={})
# <%= cache_timestamp %> # => <!-- page cached: 2009-12-21 12:00:00 -->
#
# @api public
def cache_timestamp
def cache_timestamp
if settings.cache_enabled && settings.cache_environment == settings.environment
"<!-- page cached: #{Time.now.strftime("%Y-%m-%d %H:%M:%S")} -->\n"
end
end
# backwards compat and syntactic sugar for others
alias_method :cache_page_timestamp, :cache_timestamp
alias_method :page_cached_at, :cache_timestamp


## PRIVATE METHODS
private

##
# Converts the PATH_INFO path into the full cached file path.
#
#
# ==== GOTCHA:
#
# <b>NB!</b> completely ignores the URL query params passed such as
#
# <b>NB!</b> completely ignores the URL query params passed such as
# in this example:
#
# /products?page=2
#
#
# /products?page=2
#
# To capture and cache those query strings, please do as follows:
#
#
# get 'products/page/:page' { ... } # in your Sinatra app
#
#
# /products/page/2 => .../public/cache/products/page/2.html
#
#
#
#
# ==== Examples
#
#
# / => .../public/cache/index.html
#
#
# /contact => .../public/cache/contact.html
#
#
# /contact/ => .../public/cache/contact/index.html
#
#
#
#
# @api public
def cache_file_path(in_path = nil)
def cache_file_path(in_path = nil)
path = settings.send(:cache_output_dir).dup



path_info = in_path.nil? ? request.path_info : in_path
if (path_info.empty? || path_info == "/" )
if (path_info.empty? || path_info == "/" )
path << "/index"
elsif ( path_info[-1, 1] == '/' )
path << ::Rack::Utils.unescape(path_info.chomp('/') << '/index')
else
path << ::Rack::Utils.unescape(path_info.chomp('/'))
end
path << settings.cache_page_extension if File.extname(path) == ''
return path
path
end

##
# Writes the cached file to disk, only during GET requests,
# Writes the cached file to disk, only during GET requests,
# and then returns the content.
#
#
# ==== Examples
#
#
#
#
# @api private
def cache_write_file(cache_file, content)
def cache_write_file(cache_file, content)
# only cache GET request [http://rack.rubyforge.org/doc/classes/Rack/Request.html#M000239]
if request.get?
FileUtils.mkdir_p(File.dirname(cache_file)) rescue "ERROR: could NOT create the cache directory: [ #{File.dirname(cache_file)} ]"
File.open(cache_file, 'wb'){ |f| f << content}
end
return content
content
end

##
# Establishes the file name of the cached file from the path given
#
#
# @api private
def cache_file_name(path, options={})
def cache_file_name(path, options={})
name = (path.empty? || path == "/") ? "index" : Rack::Utils.unescape(path.sub(/^(\/)/,'').chomp('/'))
name << settings.cache_page_extension unless (name.split('/').last || name).include? '.'
return name
name
end

##
# Sets the full path to the cached page/file
# Dependent upon Sinatra.options .public and .cache_dir variables being present and set.
#
#
#
#
# @api private
def cache_page_path(path, options={})
# test if given a full path rather than relative path, otherwise join the public path to cache_dir
def cache_page_path(path, options={})
# test if given a full path rather than relative path, otherwise join the public path to cache_dir
# and ensure it is a full path
cache_dir = (settings.cache_output_dir == File.expand_path(settings.cache_output_dir)) ?
cache_dir = (settings.cache_output_dir == File.expand_path(settings.cache_output_dir)) ?
settings.cache_output_dir : File.expand_path("#{settings.public}/#{settings.cache_output_dir}")
cache_dir = cache_output_dir[0..-2] if cache_dir[-1,1] == '/'
"#{cache_dir}/#{cache_file_name(path, options)}"
end


##
# Calculate if the cache is enabled
#
# @api private
def cache_enabled? options
(settings.respond_to?(:cache_enabled) ? settings.send(:cache_enabled) : false) &&
(options[:cache] || true) &&
settings.send(:environment) == settings.cache_environment
end

##
# Extract content type from options
#
# @api private
def extract_content_type options
options.delete(:content_type) || options.delete(:default_content_type)
end

##
# Convenience method that handles logging of Cache related stuff.
#
#
# Uses Sinatra::Logger's #logger method if available, otherwise just
# puts out the log message.
#
def log(scope, msg)
#
def log(scope, msg)
if settings.cache_logging
if scope.to_sym == settings.cache_logging_level.to_sym
if self.respond_to?(:logger)
logger.send(scope, msg)
logger.send(scope, msg)
else
puts "#{scope.to_s.upcase}: #{msg}"
end
end
end
end


end #/ Helpers


##
# The default options:
#
Expand Down Expand Up @@ -635,19 +674,19 @@ def log(scope, msg)
def self.registered(app)
app.register(Sinatra::OutputBuffer)
app.helpers Cache::Helpers

## CONFIGURATIONS::
app.set :cache_enabled, false
app.set :cache_environment, :production
app.set :cache_page_extension, '.html'
app.set :cache_output_dir, lambda { app.public }
app.set :cache_fragments_output_dir, lambda { "#{app.root}/tmp/cache_fragments" }
app.set :cache_fragments_wrap_with_html_comments, true

app.set :cache_logging, true
app.set :cache_logging_level, :info


## add the extension specific options to those inspectable by :settings_inspect method
if app.respond_to?(:sinatra_settings_for_inspection)
%w( cache_enabled cache_environment cache_page_extension cache_output_dir
Expand All @@ -657,11 +696,11 @@ def self.registered(app)
app.sinatra_settings_for_inspection << m
end
end

end #/ self.registered

end #/ Cache

register(Sinatra::Cache) # support classic apps

end #/ Sinatra
21 changes: 6 additions & 15 deletions lib/sinatra/templates.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,7 @@ def render(engine, data, options={}, locals={}, &block)
@default_layout = :layout if @default_layout.nil?
layout = options.delete(:layout)
layout = @default_layout if layout.nil? or layout == true
content_type = options.delete(:content_type) || options.delete(:default_content_type)

# set the cache related options
cache_enabled = settings.respond_to?(:cache_enabled) ? settings.send(:cache_enabled) : false
cache_output_dir = settings.send(:cache_output_dir) if settings.respond_to?(:cache_output_dir)
# raise Exception, "The Sinatra::Cache cache_output_dir variable is pointing to a non-existant directory cache_output_dir=[#{cache_output_dir}]" unless test(?d, cache_output_dir)
cache_option = options[:cache]
cache_option = true if cache_option.nil?


# compile and render template
layout_was = @default_layout
@default_layout = false
Expand All @@ -45,17 +37,16 @@ def render(engine, data, options={}, locals={}, &block)
options = options.merge(:views => views, :layout => false)
output = render(engine, layout, options, locals) { output }
# Cache the content or just return it
(cache_enabled && cache_option && settings.send(:environment) == settings.cache_environment) ?
cache_write_file(cache_file_path, output.gsub(/\n\r?$/,"")) : output
cache_enabled?(options) ? cache_write_file(cache_file_path, output.gsub(/\n\r?$/,"")) : output
rescue Errno::ENOENT
end
end

# rendering without a layout
(cache_enabled && cache_option && settings.send(:environment) == settings.cache_environment) ?
cache_write_file(cache_file_path, output.gsub(/\n\r?$/,"") ) : output
output.extend(ContentTyped).content_type = content_type if content_type
cache_enabled?(options) ? cache_write_file(cache_file_path, output.gsub(/\n\r?$/,"") ) : output
if content_type = extract_content_type(options)
output.extend(ContentTyped).content_type = content_type
end
output
end

Expand Down