Skip to content

sandbox type id classifier

tora edited this page Feb 1, 2012 · 11 revisions
################################################################################
# castoro-common/type_id_classifier.rb
################################################################################
module Castoro
  class TypeIdClassifier
    def initialize args
      default = args.delete :default  # assign the default value
      entries = parse args
      check_if_overwrap entries.keys

      @cache = Hash.new do |cache, type|  # if the cache misses, examine the type and assign it
        range, converter = entries.find { |range, converter| range.cover? type }
        cache[ type ] = converter || default
      end
    end

    def converter_module type
      @cache[ type ]
    end

    private

    def parse args
      Hash.new.tap do |h|
        args.each do |key, value|
          value.split(',').each do |portion|
            if portion =~ /\A\s*(\d+)(?:\s*-\s*(\d+))?\s*\Z/
              min, max = [$1, ($2 || $1)].map { |x| x.to_i }
              min <= max or raise ArgumentError, "starting value exceeds ending value in the Type ID range: #{portion}"
              h[ min..max ] = key
            else
              raise ArgumentError, "Invalid expression in the Type ID range: #{portion}"
            end
          end
        end
      end
    end

    def check_if_overwrap ranges
      loop do
        a = ranges.shift
        break if ranges.empty?
        ranges.each do |b|
          if a.cover? b.min or a.cover? b.max
            raise ArgumentError, "Two ranges overwrap each other: #{a} and #{b}"
          end
        end
      end
    end
  end
end

################################################################################
# castoro-common/basket_key_converter.rb
################################################################################
# require 'castoro-common/type_id_classifier'
module Castoro
  class BasketKeyConverter
    def initialize configuration
      args = parse configuration 
      @classifier = TypeIdClassifier.new args
    end

    def path base_dir, basket
      @classifier.converter_module(basket.type).path base_dir, basket
    end

    def string basket
      @classifier.converter_module(basket.type).string basket
    end

    def converter_module type
      @classifier.converter_module type
    end

    private

    def parse configuration
      Hash.new.tap do |h|
        configuration.map do |key, value|
          if key == "Default"
            h[ :default ] = find_module value
          else
            h[ find_module( key ) ] = value
          end
        end
      end
    end

    def find_module name
      BasketKeyConverterModule.const_get(name)
    rescue
      raise ArgumentError, "Invalid basket key converter module name: #{name}"
    end
  end

  module BasketKeyConverterModule
    module Dec40Seq
      def path base_dir, basket
        "#{base_dir}/#{basket.type}/baskets/a/#{mid(basket)}/#{dir(basket)}"
      end

      def string basket
        sprintf '%d.%d.%d', basket.content, basket.type, basket.revision
      end

      #        654321 =>    0/000/654
      # 3210987654321 => 3210/987/654
      def mid basket
        n = basket.content / 1000
        n, c = n.divmod 1000
        a, b = n.divmod 1000
        sprintf '%d/%03d/%03d', a, b, c
      end

      alias dir string

      module_function :path, :string, :mid, :dir
    end

    module Hex64Seq
      def path base_dir, basket
        "#{base_dir}/#{basket.type}/baskets/a/#{mid(basket)}/#{dir(basket)}"
      end

      def string basket
        sprintf '0x%016x.%d.%d', basket.content, basket.type, basket.revision
      end

      # 0x0123456789abcdef => 0123/456/789/abc
      def mid basket
        e = basket.content >> 12
        d = e >> 12
        c = d >> 12
        b = c >> 12
        a = b >> 12
        sprintf '%01x/%03x/%03x/%03x/%03x', (a & 0xf), (b & 0xfff), (c & 0xfff), (d & 0xfff), (e & 0xfff)
      end

      def dir basket
        sprintf '%016x.%d.%d', basket.content, basket.type, basket.revision
      end

      module_function :path, :string, :mid, :dir
    end
  end
end

################################################################################
# castoro-common/basket_key.rb
################################################################################
module Castoro
  class BasketKey
    attr_reader :content, :type, :revision

    def initialize content, type, revision
      @content, @type, @revision = content, type, revision
    end
  end
end

################################################################################
# castoro-peer/basket.rb
################################################################################
module Castoro
  module Peer
    class Basket
      attr_reader :content, :type, :revision

      def self.base_dir= base_dir
        @@base_dir = base_dir
      end

      def self.converter= converter
        @@converter = converter
      end

      def initialize content, type, revision
        @content, @type, @revision = content, type, revision
        @converter = @@converter.converter_module type
      end

      def to_s
        defined?(@s) ? @s : @s = @converter.string(self)
      end

      def path_w
        defined?(@w) ? @w : @w = create_temp_path("baskets/w")
      end

      def path_r
        defined?(@r) ? @r : @r = create_temp_path("baskets/r")
      end

      def path_a
        defined?(@a) ? @a : @a = @converter.path(@@base_dir, self)
      end

      def path_d
        defined?(@d) ? @d : @d = create_temp_path("baskets/d")
      end

      def path_c
        defined?(@c) ? @c : @c = create_temp_path("offline/canceled")
      end

      def path_c_with_hint path
        path.match( /\/([^\/]+)$/ )
        @c = create_full_path("offline/canceled", $1)
        if ( File.exist? @c )
          @c = mktemp( @c )
        end
        @c
      end

     private

      def create_full_path part, dir
        unless defined? @time
          @time = Time.now.strftime("%Y%m%dT%H")
        end
        "#{@@base_dir}/#{@type}/#{part}/#{@time}/#{dir}"
      end

      def create_temp_path part
        unless defined? @dir
          @dir = @converter.dir self
        end
        mktemp create_full_path(part, @dir)
      end

      def mktemp path
        t = Time.new
        body = "#{path}.#{t.strftime('%Y%m%dT%H%M%S')}.#{'%03d' % (t.usec / 1000)}"
        offset = 1
        big_number = Process.pid * Thread.current.object_id
        begin
          number = big_number / offset % 1000000
          candidate = "#{body}.#{'%06d' % number}"
          return candidate unless File.exist? candidate
          offset = offset * 10
        end until 1000000 < offset
        raise InternalServerError, "mktemp failed: #{path} for #{to_s}"
      end
    end
  end
end

################################################################################
# examples
################################################################################
# require 'castoro-common/basket_key_converter'

module Castoro

  configuration = {
    "Dec40Seq" => "1-999, 2000, 3000 - 3999",
    "Hex64Seq" => "1000-1999",
    "Default"  => "Dec40Seq"
  }

  converter = BasketKeyConverter.new configuration

  samples = [
             [ 654321, 1, 1 ],
             [ 3210987654321, 2000, 1 ],
             [ 1234567890, 3333, 1 ],
             [ 78901234, 3333, 1 ],
             [ 0xaaa, 1000, 1 ],
             [ 0x0123456789ABCDEF, 1000, 1 ],
             [ 0x00fedcba98765432, 1234, 5 ],
             [ 0x6789abcdef, 9999, 4 ],
            ]

  # require 'castoro-common/basket_key'
  puts "Gateway:"
  samples.each do |x|
    basket = BasketKey.new x[0], x[1], x[2]
    printf "%-30s => %s\n", converter.string(basket), converter.path("/expdsk", basket)
  end

  puts ""

  # require 'castoro-peer/basket'
  puts "Peer:"
  Peer::Basket.base_dir = "/expdsk"
  Peer::Basket.converter = converter
  samples.each do |x|
    basket = Peer::Basket.new x[0], x[1], x[2]
    printf "%-30s => %s\n", basket.to_s, basket.path_a
    if false
      printf "%-30s => %s\n", "", basket.path_w
      printf "%-30s => %s\n", "", basket.path_r
      printf "%-30s => %s\n", "", basket.path_d
      # printf "%-30s => %s\n", "", basket.path_c
      printf "%-30s => %s\n", "", basket.path_c_with_hint("/expdsk/1234/baskets/w/20120201T10/00fedcba98765432.1234.5.20120201T101337.648.011000")
      printf "%-30s => %s\n", "", basket.path_c
    end
  end

end

__END__

$ ruby type_id-40.rb
Gateway:
654321.1.1                     => /expdsk/1/baskets/a/0/000/654/654321.1.1
3210987654321.2000.1           => /expdsk/2000/baskets/a/3210/987/654/3210987654321.2000.1
1234567890.3333.1              => /expdsk/3333/baskets/a/1/234/567/1234567890.3333.1
78901234.3333.1                => /expdsk/3333/baskets/a/0/078/901/78901234.3333.1
0x0000000000000aaa.1000.1      => /expdsk/1000/baskets/a/0/000/000/000/000/0000000000000aaa.1000.1
0x0123456789abcdef.1000.1      => /expdsk/1000/baskets/a/0/123/456/789/abc/0123456789abcdef.1000.1
0x00fedcba98765432.1234.5      => /expdsk/1234/baskets/a/0/0fe/dcb/a98/765/00fedcba98765432.1234.5
444691369455.9999.4            => /expdsk/9999/baskets/a/444/691/369/444691369455.9999.4

Peer:
654321.1.1                     => /expdsk/1/baskets/a/0/000/654/654321.1.1
3210987654321.2000.1           => /expdsk/2000/baskets/a/3210/987/654/3210987654321.2000.1
1234567890.3333.1              => /expdsk/3333/baskets/a/1/234/567/1234567890.3333.1
78901234.3333.1                => /expdsk/3333/baskets/a/0/078/901/78901234.3333.1
0x0000000000000aaa.1000.1      => /expdsk/1000/baskets/a/0/000/000/000/000/0000000000000aaa.1000.1
0x0123456789abcdef.1000.1      => /expdsk/1000/baskets/a/0/123/456/789/abc/0123456789abcdef.1000.1
0x00fedcba98765432.1234.5      => /expdsk/1234/baskets/a/0/0fe/dcb/a98/765/00fedcba98765432.1234.5
444691369455.9999.4            => /expdsk/9999/baskets/a/444/691/369/444691369455.9999.4

To convert .rb into html for github wiki:
$ perl -pe 'BEGIN{print"<pre>\n"} s/</</g; s/>/>/g; END{print"</pre>\n"}' xxx.rb > xxx.html