From 88fa90b7f039f62962cc8d11031446412b951be2 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 6 Dec 2010 15:43:46 -0800 Subject: [PATCH] allow easier, single-file options for TC and TDB Most (other) users only need a single file, even though my primary use of this is for multiple files. --- lib/metropolis.rb | 30 ++++++++++++------------------ lib/metropolis/common.rb | 15 +++++++++++++-- lib/metropolis/hash.rb | 2 +- lib/metropolis/tc.rb | 11 +++++++++++ lib/metropolis/tc/hdb.rb | 19 ++++++++----------- lib/metropolis/tdb.rb | 29 ++++++++--------------------- lib/metropolis/tdb/multi.rb | 19 +++++++++++++++++++ lib/metropolis/tdb/single.rb | 16 ++++++++++++++++ test/test_tc_hdb.rb | 13 ++----------- test/test_tc_hdb_single.rb | 22 ++++++++++++++++++++++ test/test_tdb_single.rb | 21 +++++++++++++++++++++ 11 files changed, 133 insertions(+), 64 deletions(-) create mode 100644 lib/metropolis/tdb/multi.rb create mode 100644 lib/metropolis/tdb/single.rb create mode 100644 test/test_tc_hdb_single.rb create mode 100644 test/test_tdb_single.rb diff --git a/lib/metropolis.rb b/lib/metropolis.rb index 425ce1c..c38d4af 100644 --- a/lib/metropolis.rb +++ b/lib/metropolis.rb @@ -13,28 +13,22 @@ module Metropolis def self.new(opts = {}) opts = opts.dup rv = Object.new - uri = opts[:uri] = URI.parse(opts[:uri]) - if uri.path != '/' && opts[:path_pattern] - raise ArgumentError, ":path_pattern may only be used if path is '/'" + uri = URI.parse(opts[:uri]) + rv.instance_eval do + @uri = uri + @query = @uri.query ? Rack::Utils.parse_query(@uri.query) : nil + @path_pattern = opts[:path_pattern] + @path = @uri.path if @uri.path != '/' end - case uri.scheme - when 'hash' - opts[:path] = uri.path if uri.path != '/' - rv.extend Metropolis::Hash - when 'tdb' - opts[:query] = Rack::Utils.parse_query(uri.query) if uri.query - rv.extend Metropolis::TDB - when 'tc' - opts[:query] = Rack::Utils.parse_query(uri.query) if uri.query - case ext = File.extname(opts[:path_pattern] || uri.path) - when '.tch' - rv.extend Metropolis::TC::HDB - else - raise ArgumentError, "unsupported suffix: #{ext}" - end + + base = case uri.scheme + when 'hash' then Metropolis::Hash + when 'tdb' then Metropolis::TDB + when 'tc' then Metropolis::TC else raise ArgumentError, "unsupported URI scheme: #{uri.scheme}" end + rv.extend(base) rv.setup(opts) rv end diff --git a/lib/metropolis/common.rb b/lib/metropolis/common.rb index 971accd..d9cadba 100644 --- a/lib/metropolis/common.rb +++ b/lib/metropolis/common.rb @@ -4,10 +4,21 @@ module Metropolis::Common autoload :RO, 'metropolis/common/ro' def setup(opts) - @uri = opts[:uri] @headers = { 'Content-Type' => 'application/octet-stream' } @headers.merge!(opts[:response_headers] || {}) - @nr_slots = opts[:nr_slots] || 3 + @nr_slots = opts[:nr_slots] + + if @path_pattern + @nr_slots ||= 3 + @uri.path == '/' or + raise ArgumentError, ":path_pattern may only be used if path is '/'" + @path_pattern.scan(/%\d*x/).size == 1 or + raise ArgumentError, "only one '/%\d*x/' may appear in #@path_pattern" + else + @nr_slots and + raise ArgumentError, ":nr_slots may be used with :path_pattern" + end + @readonly = !!opts[:readonly] @exclusive = !!opts[:exclusive] if @readonly && @exclusive diff --git a/lib/metropolis/hash.rb b/lib/metropolis/hash.rb index fb4d272..d5c70cb 100644 --- a/lib/metropolis/hash.rb +++ b/lib/metropolis/hash.rb @@ -8,7 +8,7 @@ module Metropolis::Hash def setup(opts) super - if @path = opts[:path] + if @path begin @db = Marshal.load(File.open(@path, "rb") { |fp| fp.read }) Hash === @db or raise ArgumentError, "#@path is not a marshaled Hash" diff --git a/lib/metropolis/tc.rb b/lib/metropolis/tc.rb index 84dfaff..1f15e7b 100644 --- a/lib/metropolis/tc.rb +++ b/lib/metropolis/tc.rb @@ -3,4 +3,15 @@ require 'tokyocabinet' module Metropolis::TC autoload :HDB, 'metropolis/tc/hdb' + + def self.extended(obj) + obj.instance_eval do + case ext = File.extname(@path_pattern || @path) + when '.tch' + extend Metropolis::TC::HDB + else + raise ArgumentError, "unsupported suffix: #{ext}" + end + end + end end diff --git a/lib/metropolis/tc/hdb.rb b/lib/metropolis/tc/hdb.rb index 97e9050..d0833f8 100644 --- a/lib/metropolis/tc/hdb.rb +++ b/lib/metropolis/tc/hdb.rb @@ -11,16 +11,12 @@ module Metropolis::TC::HDB def setup(opts) super - path_pattern = opts[:path_pattern] - path_pattern.scan(/%\d*x/).size == 1 or - raise ArgumentError, "only one '/%\d*x/' may appear in #{path_pattern}" - @rd_flags = TCHDB::OREADER @wr_flags = TCHDB::OWRITER @optimize = nil - if query = opts[:query] - case query['rdlock'] + if @query + case @query['rdlock'] when 'true', nil when 'false' @rd_flags |= TCHDB::ONOLCK @@ -28,7 +24,7 @@ module Metropolis::TC::HDB raise ArgumentError, "'rdlock' must be 'true' or 'false'" end - case query['wrlock'] + case @query['wrlock'] when 'true', nil when 'false' @wr_flags |= TCHDB::ONOLCK @@ -38,11 +34,11 @@ module Metropolis::TC::HDB flags = 0 @optimize = %w(bnum apow fpow).map do |x| - v = query[x] + v = @query[x] v ? v.to_i : nil end - case large = query['large'] + case large = @query['large'] when 'false', nil when 'true' flags |= TCHDB::TLARGE @@ -50,7 +46,7 @@ module Metropolis::TC::HDB raise ArgumentError, "invalid 'large' value: #{large}" end - case compress = query['compress'] + case compress = @query['compress'] when nil when 'deflate', 'bzip', 'tcbs' flags |= TCHDB.const_get("T#{compress.upcase}") @@ -59,8 +55,9 @@ module Metropolis::TC::HDB end @optimize << flags end + @nr_slots = 1 unless @path_pattern @dbv = (0...@nr_slots).to_a.map do |slot| - path = sprintf(path_pattern, slot) + path = @path_pattern ? sprintf(@path_pattern, slot) : @uri.path hdb = TCHDB.new unless @readonly hdb.open(path, TCHDB::OWRITER | TCHDB::OCREAT) or ex!(:open, hdb) diff --git a/lib/metropolis/tdb.rb b/lib/metropolis/tdb.rb index 14aa3aa..d734443 100644 --- a/lib/metropolis/tdb.rb +++ b/lib/metropolis/tdb.rb @@ -4,44 +4,35 @@ require 'tdb' module Metropolis::TDB include Metropolis::Common + autoload :Single, 'metropolis/tdb/single' + autoload :Multi, 'metropolis/tdb/multi' def setup(opts) super - path_pattern = opts[:path_pattern] - path_pattern.scan(/%\d*x/).size == 1 or - raise ArgumentError, "only one '/%\d*x/' may appear in #{path_pattern}" @tdb_opts = { :tdb_flags => 0 } if @readonly @tdb_opts[:open_flags] = IO::RDONLY extend Metropolis::Common::RO end - if query = opts[:query] - size = query['hash_size'] and @tdb_opts[:hash_size] = size.to_i - hash = query['hash'] and @tdb_opts[:hash] = hash.to_sym + if @query + size = @query['hash_size'] and @tdb_opts[:hash_size] = size.to_i + hash = @query['hash'] and @tdb_opts[:hash] = hash.to_sym - case query['volatile'] + case @query['volatile'] when 'true'; @tdb_opts[:tdb_flags] |= TDB::VOLATILE when 'false', nil else raise ArgumentError, "'volatile' must be 'true' or 'false'" end - case query['sync'] + case @query['sync'] when 'true', nil when 'false'; @tdb_opts[:tdb_flags] |= TDB::NOSYNC else raise ArgumentError, "'sync' must be 'true' or 'false'" end end - - @dbv = (0...@nr_slots).to_a.map do |slot| - path = sprintf(path_pattern, slot) - ::TDB.new(path, @tdb_opts) - end - end - - def db(key, &block) - yield @dbv[key.hash % @nr_slots] + extend(@path_pattern ? Metropolis::TDB::Multi : Metropolis::TDB::Single) end def put(key, env) @@ -67,8 +58,4 @@ module Metropolis::TDB value = db(key) { |tdb| tdb.fetch(key) } or return r(404) [ 200, { 'Content-Length' => value.size.to_s }.merge!(@headers), [ value ] ] end - - def close! - @dbv.each { |tdb| tdb.close } - end end diff --git a/lib/metropolis/tdb/multi.rb b/lib/metropolis/tdb/multi.rb new file mode 100644 index 0000000..b22e63b --- /dev/null +++ b/lib/metropolis/tdb/multi.rb @@ -0,0 +1,19 @@ +# -*- encoding: binary -*- +module Metropolis::TDB::Multi + def self.extended(obj) + obj.instance_eval do + @dbv = (0...@nr_slots).to_a.map do |slot| + path = sprintf(@path_pattern, slot) + ::TDB.new(path, @tdb_opts) + end + end + end + + def db(key, &block) + yield @dbv[key.hash % @nr_slots] + end + + def close! + @dbv.each { |tdb| tdb.close } + end +end diff --git a/lib/metropolis/tdb/single.rb b/lib/metropolis/tdb/single.rb new file mode 100644 index 0000000..840b57a --- /dev/null +++ b/lib/metropolis/tdb/single.rb @@ -0,0 +1,16 @@ +# -*- encoding: binary -*- +module Metropolis::TDB::Single + def self.extended(obj) + obj.instance_eval do + @db = ::TDB.new(@uri.path, @tdb_opts) + end + end + + def db(key, &block) + yield @db + end + + def close! + @db.close + end +end diff --git a/test/test_tc_hdb.rb b/test/test_tc_hdb.rb index a5d27b8..690fb82 100644 --- a/test/test_tc_hdb.rb +++ b/test/test_tc_hdb.rb @@ -21,12 +21,7 @@ class Test_TC_HDB < Test::Unit::TestCase end def osetup - o = Object.new - o.extend Metropolis::TC::HDB - assert_nothing_raised do - o.setup :path_pattern => @path_pattern - end - o + Metropolis.new(@app_opts) end def test_create_put_get_delete @@ -135,11 +130,7 @@ class Test_TC_HDB < Test::Unit::TestCase key = "x" wr = osetup wr.put(key, { "rack.input" => StringIO.new("OK") }) - o = Object.new - o.extend Metropolis::TC::HDB - assert_nothing_raised do - o.setup :path_pattern => @path_pattern, :readonly => true - end + o = Metropolis.new(@app_opts.merge(:readonly => true)) %w(PUT DELETE).each do |rm| env = { "rack.input" => StringIO.new("FAIL"), diff --git a/test/test_tc_hdb_single.rb b/test/test_tc_hdb_single.rb new file mode 100644 index 0000000..c8e33a9 --- /dev/null +++ b/test/test_tc_hdb_single.rb @@ -0,0 +1,22 @@ +# -*- encoding: binary -*- +require './test/rack_read_write.rb' +require 'tokyocabinet' # FIXME: emits warning with 1.29 gem +$-w = true +require 'metropolis' + +class Test_TC_HDB_Single < Test::Unit::TestCase + attr_reader :tmp, :o, :uri + include TestRackReadWrite + + def setup + @tmp = Tempfile.new('tchdb') + @path = @tmp.path + '.tch' + @uri = "tc://#{@path}" + @app_opts = { :uri => @uri } + end + + def teardown + @tmp.close! + File.unlink(@path) + end +end diff --git a/test/test_tdb_single.rb b/test/test_tdb_single.rb new file mode 100644 index 0000000..e04fe2f --- /dev/null +++ b/test/test_tdb_single.rb @@ -0,0 +1,21 @@ +# -*- encoding: binary -*- +require './test/rack_read_write.rb' +$-w = true +require 'metropolis' + +class Test_TDB_Single < Test::Unit::TestCase + attr_reader :tmp, :o, :uri + include TestRackReadWrite + + def setup + @tmp = Tempfile.new('tdb') + @path = @tmp.path + '.tdb' + @uri = "tdb://#{@path}" + @app_opts = { :uri => @uri } + end + + def teardown + @tmp.close! + File.unlink(@path) + end +end -- 2.11.4.GIT