require 'rack' require 'rack/request' require 'rack/response' # ref) NewRelic gem https://github.com/newrelic/rpm/blob/master/lib/new_relic/rack/developer_mode.rb module CopyTunerClient # Rack middleware that synchronizes with CopyTuner during each request. # # This is injected into the Rails middleware stack in development environments. class RequestSync VIEW_PATH = File.expand_path('../../../ui/views/', __FILE__) # @param app [Rack] the upstream app into whose responses to inject the editor # @param options [Hash] # @option options [Cache] :cache agent that should be flushed after each request def initialize(app, options) @app = app @poller = options[:poller] @cache = options[:cache] @interval = options[:interval] @ignore_regex = options[:ignore_regex] @last_synced = options[:last_synced] @first = true end attr_accessor :last_synced # Invokes the upstream Rack application and flushes the cache after each # request. def call(env) if /^\/copytuner/ =~ ::Rack::Request.new(env).path_info dup._call(env) else first_request = @first if first_request @first = false @cache.download end cancel_sync = cancel_sync?(env) response = @app.call(env) @poller.start_sync unless first_request || cancel_sync update_last_synced unless in_interval? response end end protected def _call(env) @req = ::Rack::Request.new(env) case @req.path_info when /^\/copytuner\/?$/ index when /sync/ sync else @app.call(env) end end private def index @next_sync_at = next_sync_at render :index end def sync @cache.sync ::Rack::Response.new{|r| r.redirect('/copytuner/')}.finish end def render(view, layout=true) add_rack_array = true if view.is_a? Hash layout = false if view[:object] object = view[:object] end if view[:collection] return view[:collection].map do |object| render({:partial => view[:partial], :object => object}) end.join(' ') end if view[:partial] add_rack_array = false view = "_#{view[:partial]}" end end binding = Proc.new {}.binding if layout body = render_with_layout(view) do render_without_layout(view, binding) end else body = render_without_layout(view, binding) end if add_rack_array ::Rack::Response.new( body, 200, ::Rack::CONTENT_TYPE => 'text/html; charset=utf-8' ).finish else body end end # You have to call this with a block - the contents returned from # that block are interpolated into the layout def render_with_layout(view) body = ERB.new(File.read(File.join(VIEW_PATH, 'layouts/copytuner_default.html.erb'))) body.result(Proc.new {}.binding) end # you have to pass a binding to this (a proc) so that ERB can have # access to helper functions and local variables def render_without_layout(view, binding) ERB.new(File.read(File.join(VIEW_PATH, 'copytuner', view.to_s + '.html.erb')), nil, nil, 'frobnitz').result(binding) end def cancel_sync?(env) asset_request?(env) or ignore_regex_request?(env) or in_interval? end def ignore_regex_request?(env) env['PATH_INFO'] =~ @ignore_regex end def asset_request?(env) env['PATH_INFO'] =~ /^\/assets/ end def in_interval? return false if @last_synced.nil? return false if @interval <= 0 next_sync_at > Time.now.utc end def next_sync_at @last_synced + @interval if @last_synced and @interval end def update_last_synced @last_synced = Time.now.utc end def logger CopyTunerClient.configuration.logger end end end