require 'fileutils' require 'nitro/helper/url' module Nitro # Adds support for caching. module Caching # The directory where cached pages are generated. #-- # TODO: use Server.public_root as default. #++ setting :output_cache_root, :default => 'public', :doc => 'The directory where cached pages are generated' # The Output caching subsystem stores whole pages in the # filesystem to be served directly be the front web server # (Lighttpd, Apache, etc) for optimal performance. # require 'nitro/helper/url' # Nitro promotes coding your application in such a way as to # allow for output caching to the greatest extend. Output # caching *is your friend*. #-- # gmosx, FIXME: Don't create excessive directories, use better # rewrite rules to handle xxx.html files. #++ module Output # TODO: something more elegant here. class << self # The collection of cached actions. attr_accessor :cached_actions end Nitro::Caching::Output.cached_actions = [] def self.included(base) # :nodoc: base.extend(ClassMethods) end module ClassMethods def do_cache_output(path, content) filepath = output_cache_path(path) FileUtils.makedirs(File.dirname(filepath)) File.open(filepath, 'w+') { |f| f.write(content) } Logger.debug "Cached page '#{filepath}'" if $DBG end # Enable output caching for the given actions. def cache_output(*actions) return unless caching_enabled? # keep track of cached actions. for a in actions Nitro::Caching::Output.cached_actions << [self, a] end str = actions.collect { |a| ":#{a}" }.join(', ') module_eval %{ after "do_cache_output", :on => [ #{str} ] } end private =begin def output_cache_path(path) filename = ((path.empty? || path == '/') ? 'index.html' : path.dup) filename << '/index.html' unless (filename.split('/').last || filename).include? '.' return File.join(Caching.output_cache_root, filename).squeeze('/') end =end def output_cache_path(path) if path.empty? or path == '/' filename = 'index.html' else filename = "#{path}.html" end return File.join(Caching.output_cache_root, filename).squeeze('/') end end private def do_cache_output if caching_enabled? and caching_allowed? self.class.do_cache_output(@request.uri, @out) end end # Explicitly expire the output cached under the given # cache key. The cache key is typically the name of the # top level action responsible for generating the page. #-- # WARNING: If you change this method, don't forget the CacheSweeper # expire method. # FIXME: make the above unneeded through refactoring. #++ def expire_output(name) filename = "#{Server.public_root}/#{name}".squeeze('/') Logger.debug "Expiring cache file '#{filename}'" if $DBG FileUtils.rm_rf(filename) rescue Object => ex # ignore any error. end alias delete_output expire_output # Is caching allowed for this action (page)? The default # implementation does not cache post request or request # with query parameters. You can work arround the second # 'limitation' by cleverly using Nitro's implicit support # for 'nice' urls. def caching_allowed? not (request.post? or request.uri =~ /\?/) end end end end