Skip to content

Commit

Permalink
extract Cacher class from Sparkline logic. It's now easy to replace t…
Browse files Browse the repository at this point in the history
…he Filesystem cacher with something more interesting
  • Loading branch information
technoweenie committed Oct 10, 2009
1 parent 3b3f770 commit 7dc1cd4
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 30 deletions.
31 changes: 12 additions & 19 deletions lib/rack-sparklines.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
require 'spark_pr'
require 'time'

module Rack
# Render sparkline graphs dynamically from datapoints in a matching CSV file
Expand All @@ -8,11 +7,11 @@ class Sparklines
DEFAULT_SPARK_OPTIONS = {:has_min => true, :has_max => true, 'has_last' => 'true', 'height' => '40', :step => 10, :normalize => 'logarithmic'}

# Options:
# :spark - Hash of sparkline options. See spark_pr.rb
# :prefix - URL prefix for handled requests. Setting it to "/sparks"
# :spark - Hash of sparkline options. See spark_pr.rb
# :prefix - URL prefix for handled requests. Setting it to "/sparks"
# treats requests like "/sparks/stats.csv" as dynamic sparklines.
# :directory - local directory that cached PNG files are stored.
# :handler - Handler instances know how to fetch data and pass them
# :cacher - Cachers know how to store and stream sparkline PNG data.
# :handler - Handler instances know how to fetch data and pass them
# to the Sparklines library.
def initialize(app, options = {})
@app, @options = app, options
Expand All @@ -27,35 +26,29 @@ def _call(env)
if env['PATH_INFO'][@options[:prefix]] == @options[:prefix]
@data_path = env['PATH_INFO'][@options[:prefix].size+1..-1]
@data_path.sub! /\.png$/, ''
@png_path = @data_path + ".png"
@cache_file = ::File.join(@options[:directory], @png_path)
@handler = @options[:handler].set(@data_path)
@png_path = @data_path + ".png"
@cacher = @options[:cacher].set(@png_path)
@handler = @options[:handler].set(@data_path)
if !@handler.exists?
return @app.call(env)
end
if !@handler.already_cached?(@cache_file)
if !@handler.already_cached?(@cacher)
@handler.fetch do |data|
::File.open(@cache_file, 'wb') do |png|
png << Spark.plot(data, @options[:spark])
end
@cacher.save(data, @options[:spark])
end
end
[200, {
"Last-Modified" => ::File.mtime(@cache_file).rfc822,
"Last-Modified" => @cacher.updated_at.rfc822,
"Content-Type" => "image/png",
"Content-Length" => ::File.size(@cache_file).to_s
"Content-Length" => @cacher.size.to_s
}, self]
else
@app.call(env)
end
end

def each
::File.open(@cache_file, "rb") do |file|
while part = file.read(8192)
yield part
end
end
@cacher.stream { |part| yield part }
end
end
end
57 changes: 57 additions & 0 deletions lib/rack-sparklines/cachers/filesystem.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
require 'rack-sparklines'
require 'time'

class Rack::Sparklines
module Cachers
# Reads sparkline data from CSV files. Only the first line of numbers are
# read. Requests for "/sparks/stats.csv" will pass a data_path of "stats.csv"
class Filesystem
attr_accessor :png_path
attr_accessor :directory

def initialize(directory)
@directory = directory
@size, @updated_at = nil
end

# Setting the png_path returns a duplicate of this object that has any
# custom instance variables (configuration settings, for example).
def set(png_path)
cacher = dup
cacher.png_path = png_path
cacher
end

def png_path=(s)
@cache_file = File.join(@directory, s)
@png_path = s
end

def size
@size ||= File.size(@cache_file)
end

def exists?
File.file?(@cache_file)
end

def updated_at
@updated_at ||= File.mtime(@cache_file)
end

def save(data, options)
File.open(@cache_file, 'wb') do |png|
png << Spark.plot(data, options)
end
end

def stream
::File.open(@cache_file, "rb") do |file|
while part = file.read(8192)
yield part
end
end
end
end
end
end
4 changes: 2 additions & 2 deletions lib/rack-sparklines/handlers/abstract_data.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ def set(data_path)
data
end

def already_cached?(cache_file)
if cache_time = File.file?(cache_file) && File.mtime(cache_file)
def already_cached?(cacher)
if cache_time = cacher.exists? && cacher.updated_at
cache_time > updated_at
end
end
Expand Down
20 changes: 11 additions & 9 deletions test/rack-sparklines_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,27 @@
require 'rack-sparklines'
require 'rack-sparklines/handlers/stubbed_data'
require 'rack-sparklines/handlers/csv_data'
require 'rack-sparklines/cachers/filesystem'

class SparklinesTest < Test::Unit::TestCase
include Rack::Test::Methods

$data_dir = File.join(File.dirname(__FILE__), 'data')
$data_dir = File.join(File.dirname(__FILE__), 'data')
$stubbed_data = {:updated => Time.utc(2009, 1, 1), :contents => [47, 43, 24, 47, 16, 28, 38, 57, 50, 76, 42, 20, 98, 34, 53, 1, 55, 74, 63, 38, 31, 98, 89]}
FileUtils.rm_rf $data_dir
FileUtils.mkdir_p $data_dir
File.open File.join($data_dir, 'stats.csv'), 'wb' do |csv|
csv << "47,43,24,47,16,28,38,57,50,76,42,20,98,34,53,1,55,74,63,38,31,98,89"
csv << $stubbed_data[:contents].join(",")
end
sleep 1

def app
stubbed_data = {:updated => Time.utc(2009, 1, 1), :contents => [47, 43, 24, 47, 16, 28, 38, 57, 50, 76, 42, 20, 98, 34, 53, 1, 55, 74, 63, 38, 31, 98, 89]}

Rack::Sparklines.new \
Proc.new {|env| [200, {"Content-Type" => "text/html"}, "booya"] },
:handler => Rack::Sparklines::Handlers::StubbedData.new('stats.csv' => stubbed_data),
:prefix => '/sparks',
:directory => $data_dir
:handler => Rack::Sparklines::Handlers::StubbedData.new('stats.csv' => $stubbed_data),
:cacher => Rack::Sparklines::Cachers::Filesystem.new($data_dir),
:prefix => '/sparks'
end

def setup
Expand Down Expand Up @@ -66,8 +68,8 @@ class SparklinesCSVTest < SparklinesTest
def app
Rack::Sparklines.new \
Proc.new {|env| [200, {"Content-Type" => "text/html"}, "booya"] },
:handler => Rack::Sparklines::Handlers::CsvData.new($data_dir),
:prefix => '/sparks',
:directory => $data_dir
:handler => Rack::Sparklines::Handlers::CsvData.new($data_dir),
:cacher => Rack::Sparklines::Cachers::Filesystem.new($data_dir),
:prefix => '/sparks'
end
end

0 comments on commit 7dc1cd4

Please sign in to comment.