ccp 0.3.5 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/ccp.gemspec +10 -9
- data/lib/ccp/kvs.rb +9 -3
- data/lib/ccp/kvs/core.rb +0 -6
- data/lib/ccp/kvs/hash.rb +2 -0
- data/lib/ccp/kvs/kch.rb +33 -0
- data/lib/ccp/kvs/kyoto.rb +17 -0
- data/lib/ccp/kvs/kyoto/base.rb +60 -0
- data/lib/ccp/kvs/kyoto/cabinet.rb +137 -0
- data/lib/ccp/kvs/kyoto/state_machine.rb +142 -0
- data/lib/ccp/kvs/tch.rb +29 -13
- data/lib/ccp/kvs/tokyo/cabinet.rb +0 -5
- data/lib/ccp/storage.rb +0 -6
- data/lib/ccp/version.rb +1 -1
- data/spec/kvs/enum_spec.rb +131 -0
- data/spec/kvs/kvs_spec.rb +1 -0
- data/spec/kvs/kyoto/cabinet_spec.rb +282 -0
- data/spec/kvs/load_spec.rb +43 -0
- data/spec/kvs/usecase_spec.rb +33 -0
- data/spec/models.rb +1 -1
- data/spec/storage/loadable_spec.rb +7 -0
- data/spec/storage/usecase_spec.rb +36 -0
- metadata +160 -145
    
        data/ccp.gemspec
    CHANGED
    
    | @@ -20,18 +20,19 @@ Gem::Specification.new do |s| | |
| 20 20 | 
             
              s.require_paths = ["lib"]
         | 
| 21 21 |  | 
| 22 22 | 
             
              if RUBY_VERSION >= "1.9"
         | 
| 23 | 
            -
                s. | 
| 23 | 
            +
                s.add_runtime_dependency "activesupport"
         | 
| 24 24 | 
             
              else
         | 
| 25 | 
            -
                s. | 
| 25 | 
            +
                s.add_runtime_dependency "activesupport", "~> 3.2.0"
         | 
| 26 26 | 
             
              end
         | 
| 27 27 |  | 
| 28 | 
            -
              s. | 
| 29 | 
            -
              s. | 
| 30 | 
            -
              s. | 
| 31 | 
            -
              s. | 
| 32 | 
            -
              s. | 
| 33 | 
            -
              s. | 
| 34 | 
            -
              s.add_dependency "tokyocabinet", "~> 1.29.1"
         | 
| 28 | 
            +
              s.add_runtime_dependency "typed", ">= 0.2.2"
         | 
| 29 | 
            +
              s.add_runtime_dependency "must", ">= 0.3.0"
         | 
| 30 | 
            +
              s.add_runtime_dependency "dsl_accessor", ">= 0.4.1"
         | 
| 31 | 
            +
              s.add_runtime_dependency "json"
         | 
| 32 | 
            +
              s.add_runtime_dependency "yajl-ruby"
         | 
| 33 | 
            +
              s.add_runtime_dependency "msgpack", "> 0.4"
         | 
| 35 34 |  | 
| 36 35 | 
             
              s.add_development_dependency "rspec"
         | 
| 36 | 
            +
              s.add_development_dependency "tokyocabinet", "~> 1.29.1"
         | 
| 37 | 
            +
              s.add_development_dependency "kyotocabinet-ruby", "~> 1.27.1"
         | 
| 37 38 | 
             
            end
         | 
    
        data/lib/ccp/kvs.rb
    CHANGED
    
    | @@ -10,6 +10,14 @@ module Ccp | |
| 10 10 |  | 
| 11 11 | 
             
                DICTIONARY = {}             # cache for (extname -> Kvs)
         | 
| 12 12 |  | 
| 13 | 
            +
                def self.load(path)
         | 
| 14 | 
            +
                  array = path.to_s.split(".")
         | 
| 15 | 
            +
                  kvs   = Ccp::Kvs[array.pop].new(path)
         | 
| 16 | 
            +
                  codec = Ccp::Serializers[array.pop]
         | 
| 17 | 
            +
                  kvs.codec!(codec)
         | 
| 18 | 
            +
                  return kvs
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 13 21 | 
             
                include Enumerable
         | 
| 14 22 | 
             
                delegate :delete, :to=>"DICTIONARY"
         | 
| 15 23 |  | 
| @@ -39,8 +47,6 @@ module Ccp | |
| 39 47 | 
             
            end
         | 
| 40 48 |  | 
| 41 49 | 
             
            require 'ccp/kvs/hash'
         | 
| 42 | 
            -
            require 'ccp/kvs/tokyo'
         | 
| 43 50 | 
             
            require 'ccp/kvs/tch'
         | 
| 51 | 
            +
            require 'ccp/kvs/kch'
         | 
| 44 52 |  | 
| 45 | 
            -
            Ccp::Kvs << Ccp::Kvs::Hash
         | 
| 46 | 
            -
            Ccp::Kvs << Ccp::Kvs::Tch
         | 
    
        data/lib/ccp/kvs/core.rb
    CHANGED
    
    | @@ -35,12 +35,6 @@ module Ccp | |
| 35 35 | 
             
                  # bulk operation
         | 
| 36 36 | 
             
                  def read     ; keys.inject({}){|h,k| h[k] = get(k); h }           ; end
         | 
| 37 37 | 
             
                  def write(h) ; h.each_pair{|k,v| set(k,v)}                        ; end
         | 
| 38 | 
            -
             | 
| 39 | 
            -
                  # backward compat (until 0.3.6)
         | 
| 40 | 
            -
                  def read!
         | 
| 41 | 
            -
                    STDERR.puts "DEPRECATION WARNING: #{self.class}#read! will be removed in 0.3.6, use read instead"
         | 
| 42 | 
            -
                    read
         | 
| 43 | 
            -
                  end
         | 
| 44 38 | 
             
                end
         | 
| 45 39 | 
             
              end
         | 
| 46 40 | 
             
            end
         | 
    
        data/lib/ccp/kvs/hash.rb
    CHANGED
    
    
    
        data/lib/ccp/kvs/kch.rb
    ADDED
    
    | @@ -0,0 +1,33 @@ | |
| 1 | 
            +
            begin
         | 
| 2 | 
            +
              require 'kyotocabinet'
         | 
| 3 | 
            +
            rescue LoadError
         | 
| 4 | 
            +
              load_error = true
         | 
| 5 | 
            +
            end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            unless load_error
         | 
| 8 | 
            +
              require 'ccp/kvs/kyoto'
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              module Ccp
         | 
| 11 | 
            +
                module Kvs
         | 
| 12 | 
            +
                  class Kch < Kyoto::Cabinet
         | 
| 13 | 
            +
                    # core
         | 
| 14 | 
            +
                    def get(k)        ; R{ super }; end
         | 
| 15 | 
            +
                    def set(k,v)      ; W{ super }; end
         | 
| 16 | 
            +
                    def del(k)        ; W{ super }; end
         | 
| 17 | 
            +
                    def count         ; R{ super }; end
         | 
| 18 | 
            +
                    def read          ; R{ super }; end
         | 
| 19 | 
            +
                    def write(h)      ; W{ super }; end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    # enum
         | 
| 22 | 
            +
                    def each(&b)      ; R{ super }; end
         | 
| 23 | 
            +
                    def each_pair(&b) ; R{ super }; end
         | 
| 24 | 
            +
                    def each_key(&b)  ; R{ super }; end
         | 
| 25 | 
            +
                    def keys          ; R{ super }; end
         | 
| 26 | 
            +
                    def first_key     ; R{ super }; end
         | 
| 27 | 
            +
                    def first         ; R{ super }; end
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              Ccp::Kvs << Ccp::Kvs::Kch
         | 
| 33 | 
            +
            end
         | 
| @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            require 'kyotocabinet'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Ccp
         | 
| 4 | 
            +
              module Kvs
         | 
| 5 | 
            +
                module Kyoto
         | 
| 6 | 
            +
                  Error  = Class.new(Ccp::Kvs::Error)
         | 
| 7 | 
            +
                  Locked = Class.new(Error)
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  CONNECTIONS = {}
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
            end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            require 'ccp/kvs/kyoto/base'
         | 
| 15 | 
            +
            require 'ccp/kvs/kyoto/state_machine'
         | 
| 16 | 
            +
            require 'ccp/kvs/kyoto/cabinet'
         | 
| 17 | 
            +
             | 
| @@ -0,0 +1,60 @@ | |
| 1 | 
            +
            module Ccp
         | 
| 2 | 
            +
              module Kvs
         | 
| 3 | 
            +
                module Kyoto
         | 
| 4 | 
            +
                  class Base
         | 
| 5 | 
            +
                    include Ccp::Kvs::Core
         | 
| 6 | 
            +
                    include KyotoCabinet
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                    ######################################################################
         | 
| 9 | 
            +
                    ### info
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    def info
         | 
| 12 | 
            +
                      raise "not implemented yet"
         | 
| 13 | 
            +
                    end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    ######################################################################
         | 
| 16 | 
            +
                    ### kvs
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    def path
         | 
| 19 | 
            +
                      file = @source.to_s.sub(/#.*$/, '') # parse "foo.tch#mode=r"
         | 
| 20 | 
            +
                      Pathname(file)
         | 
| 21 | 
            +
                    end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    private
         | 
| 24 | 
            +
                      # Check ecode and then raise. too boring... The library should implement this as atomic operation!
         | 
| 25 | 
            +
                      def atomic(&block)
         | 
| 26 | 
            +
                        raise NotImplementedError, "tc keep ecode until new erros occured"
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                        if kyoto_error?
         | 
| 29 | 
            +
                          raise "tc already error before atomic: #{@db.ecode}"
         | 
| 30 | 
            +
                        end
         | 
| 31 | 
            +
                        v = block.call
         | 
| 32 | 
            +
                        kyoto_error! if kyoto_error?
         | 
| 33 | 
            +
                        return v
         | 
| 34 | 
            +
                      end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                      def kyoto_error!(label = nil)
         | 
| 37 | 
            +
                        raise Ccp::Kvs::Kyoto::Error, "%s%s (%s)" % [label, error_message, @source]
         | 
| 38 | 
            +
                      end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                      def kyoto_error?
         | 
| 41 | 
            +
                        @db.error.is_a?(KyotoCabinet::Error::XSUCCESS)
         | 
| 42 | 
            +
                      end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                      def threading_error?
         | 
| 45 | 
            +
                        false
         | 
| 46 | 
            +
                      end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                      def error_message
         | 
| 49 | 
            +
                        if @db
         | 
| 50 | 
            +
                          @db.error
         | 
| 51 | 
            +
                        else
         | 
| 52 | 
            +
                          '[Not Initialized]'
         | 
| 53 | 
            +
                        end
         | 
| 54 | 
            +
                      rescue Exception => e
         | 
| 55 | 
            +
                        "[BUG] #{e}"
         | 
| 56 | 
            +
                      end
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
            end
         | 
| @@ -0,0 +1,137 @@ | |
| 1 | 
            +
            module Ccp
         | 
| 2 | 
            +
              module Kvs
         | 
| 3 | 
            +
                module Kyoto
         | 
| 4 | 
            +
                  class Cabinet < Base
         | 
| 5 | 
            +
                    include StateMachine
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                    def initialize(source)
         | 
| 8 | 
            +
                      @source = source
         | 
| 9 | 
            +
                      @db     = DB.new
         | 
| 10 | 
            +
                    end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                    ######################################################################
         | 
| 13 | 
            +
                    ### kvs
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    def get(k)
         | 
| 16 | 
            +
                      tryR("get")
         | 
| 17 | 
            +
                      v = @db[k.to_s]
         | 
| 18 | 
            +
                      if v
         | 
| 19 | 
            +
                        return decode(v)
         | 
| 20 | 
            +
                      else
         | 
| 21 | 
            +
                        if @db.error.is_a?(KyotoCabinet::Error::XNOREC)
         | 
| 22 | 
            +
                          return nil
         | 
| 23 | 
            +
                        else
         | 
| 24 | 
            +
                          kyoto_error!("get(%s): " % k)
         | 
| 25 | 
            +
                        end
         | 
| 26 | 
            +
                      end
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    def set(k,v)
         | 
| 30 | 
            +
                      tryW("set")
         | 
| 31 | 
            +
                      val = encode(v)
         | 
| 32 | 
            +
                      @db[k.to_s] = val or
         | 
| 33 | 
            +
                        kyoto_error!("set(%s): " % k)
         | 
| 34 | 
            +
                    end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    def del(k)
         | 
| 37 | 
            +
                      tryW("del")
         | 
| 38 | 
            +
                      v = @db[k.to_s]
         | 
| 39 | 
            +
                      if v
         | 
| 40 | 
            +
                        if @db.delete(k.to_s)
         | 
| 41 | 
            +
                          return decode(v)
         | 
| 42 | 
            +
                        else
         | 
| 43 | 
            +
                          kyoto_error!("del(%s): " % k)
         | 
| 44 | 
            +
                        end
         | 
| 45 | 
            +
                      else
         | 
| 46 | 
            +
                        return nil
         | 
| 47 | 
            +
                      end
         | 
| 48 | 
            +
                    end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                    def exist?(k)
         | 
| 51 | 
            +
                      tryR("exist?")
         | 
| 52 | 
            +
                      return !! @db[k.to_s] # TODO: fast access
         | 
| 53 | 
            +
                    end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                    def count
         | 
| 56 | 
            +
                      tryR("count")
         | 
| 57 | 
            +
                      return @db.count
         | 
| 58 | 
            +
                    end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                    ######################################################################
         | 
| 61 | 
            +
                    ### bulk operations (not DRY but fast)
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                    def read
         | 
| 64 | 
            +
                      tryR("read")
         | 
| 65 | 
            +
                      hash = {}
         | 
| 66 | 
            +
                      @db.each do |k, v|
         | 
| 67 | 
            +
                        v or kyoto_error!("each(%s): " % k)
         | 
| 68 | 
            +
                        hash[k] = decode(v)
         | 
| 69 | 
            +
                      end
         | 
| 70 | 
            +
                      return hash
         | 
| 71 | 
            +
                    end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                    def write(h)
         | 
| 74 | 
            +
                      tryW("write")
         | 
| 75 | 
            +
                      h.each_pair do |k,v|
         | 
| 76 | 
            +
                        val = encode(v)
         | 
| 77 | 
            +
                        @db[k.to_s] = val or kyoto_error!("write(%s): " % k)
         | 
| 78 | 
            +
                      end
         | 
| 79 | 
            +
                      return h
         | 
| 80 | 
            +
                    end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                    ######################################################################
         | 
| 83 | 
            +
                    ### iterator
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                    def each(&block)
         | 
| 86 | 
            +
                      each_pair(&block)
         | 
| 87 | 
            +
                    end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                    def each_pair(&block)
         | 
| 90 | 
            +
                      tryR("each_pair")
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                      # TODO: Waste memory! But kc ignores exceptions in his each block.
         | 
| 93 | 
            +
                      array = []
         | 
| 94 | 
            +
                      @db.each{|k, v| array << [k, decode(v)]} or kyoto_error!("each_pair: ")
         | 
| 95 | 
            +
                      array.each do |a|
         | 
| 96 | 
            +
                        block.call(a[0], a[1])
         | 
| 97 | 
            +
                      end
         | 
| 98 | 
            +
                    end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                    def each_key(&block)
         | 
| 101 | 
            +
                      tryR("each_key")
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                      # TODO: Waste memory! But kc ignores exceptions in his each block.
         | 
| 104 | 
            +
                      array = []
         | 
| 105 | 
            +
                      @db.each_key{|k| array << k.first} or kyoto_error!("each_key: ")
         | 
| 106 | 
            +
                      array.each do |k|
         | 
| 107 | 
            +
                        block.call(k)
         | 
| 108 | 
            +
                      end
         | 
| 109 | 
            +
                    end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                    def keys
         | 
| 112 | 
            +
                      tryR("keys")
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                      array = []
         | 
| 115 | 
            +
                      each_key do |key|
         | 
| 116 | 
            +
                        array << key
         | 
| 117 | 
            +
                      end
         | 
| 118 | 
            +
                      return array
         | 
| 119 | 
            +
                    end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                    def first_key
         | 
| 122 | 
            +
                      first.first
         | 
| 123 | 
            +
                    end
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                    def first
         | 
| 126 | 
            +
                      tryR("first")
         | 
| 127 | 
            +
                      @db.cursor_process {|cur|
         | 
| 128 | 
            +
                        cur.jump
         | 
| 129 | 
            +
                        k, v = cur.get(true)
         | 
| 130 | 
            +
                        return [k, decode(v)]
         | 
| 131 | 
            +
                      }
         | 
| 132 | 
            +
                    end
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                  end
         | 
| 135 | 
            +
                end
         | 
| 136 | 
            +
              end
         | 
| 137 | 
            +
            end
         | 
| @@ -0,0 +1,142 @@ | |
| 1 | 
            +
            module Ccp
         | 
| 2 | 
            +
              module Kvs
         | 
| 3 | 
            +
                module Kyoto
         | 
| 4 | 
            +
                  module StateMachine
         | 
| 5 | 
            +
                    include KyotoCabinet
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                    ######################################################################
         | 
| 8 | 
            +
                    ### state machine
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                    CLOSED   = 1
         | 
| 11 | 
            +
                    READABLE = 2
         | 
| 12 | 
            +
                    WRITABLE = 3
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                    LOCKED_BY = proc{|c| Array(c).select{|i| i !~ %r{/ruby/[^/]+/gems/}}[0,5].join("\n") || c rescue c}
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                    def state
         | 
| 17 | 
            +
                      @state || CLOSED
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    def locker_info
         | 
| 21 | 
            +
                      if CONNECTIONS[@source]
         | 
| 22 | 
            +
                        return LOCKED_BY[CONNECTIONS[@source]]
         | 
| 23 | 
            +
                      end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                      target = File.basename(@source)
         | 
| 26 | 
            +
                      CONNECTIONS.each_pair do |file, reason|
         | 
| 27 | 
            +
                        return LOCKED_BY[reason] if File.basename(file) == target
         | 
| 28 | 
            +
                      end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                      if CONNECTIONS.any?
         | 
| 31 | 
            +
                        return CONNECTIONS.inspect
         | 
| 32 | 
            +
                      else
         | 
| 33 | 
            +
                        return 'no brockers. maybe locked by other systems?'
         | 
| 34 | 
            +
                      end
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    def open(mode, locker = nil)
         | 
| 38 | 
            +
                      Pathname(@source.to_s).parent.mkpath
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                      # open and mark filename for threading error
         | 
| 41 | 
            +
                      if @db.open(@source.to_s, mode)
         | 
| 42 | 
            +
                        locker ||= (caller rescue "???")
         | 
| 43 | 
            +
                        STDERR.puts "LOCK: #{@source} by [#{LOCKED_BY[locker]}]" if @debug
         | 
| 44 | 
            +
                        CONNECTIONS[@db.path.to_s] = locker
         | 
| 45 | 
            +
                      elsif threading_error?
         | 
| 46 | 
            +
                        raise Kyoto::Locked, "%s is locked by [%s]" % [@source, locker_info]
         | 
| 47 | 
            +
                      else
         | 
| 48 | 
            +
                        kyoto_error!("%s#open(%s,%s): " % [self.class, @source, mode])
         | 
| 49 | 
            +
                      end
         | 
| 50 | 
            +
                    end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                    def __close__(locker = nil)
         | 
| 53 | 
            +
                      @db.close
         | 
| 54 | 
            +
                      CONNECTIONS[@db.path] = nil
         | 
| 55 | 
            +
                      STDERR.puts "UNLOCK: #{@source} by [#{LOCKED_BY[locker || caller]}]" if @debug
         | 
| 56 | 
            +
                    end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                    def close(locker = nil)
         | 
| 59 | 
            +
                      C!(locker)
         | 
| 60 | 
            +
                    end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                    def C!(locker = nil)
         | 
| 63 | 
            +
                      case state
         | 
| 64 | 
            +
                      when CLOSED   ; # NOP
         | 
| 65 | 
            +
                      when READABLE,
         | 
| 66 | 
            +
                           WRITABLE ; __close__(locker); @state = CLOSED
         | 
| 67 | 
            +
                      else          ; raise "unknown state: #{state}"
         | 
| 68 | 
            +
                      end
         | 
| 69 | 
            +
                    end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                    def R!(locker = nil)
         | 
| 72 | 
            +
                      case state
         | 
| 73 | 
            +
                      when CLOSED   ; open(DB::OREADER, locker); @state = READABLE
         | 
| 74 | 
            +
                      when READABLE ; # NOP
         | 
| 75 | 
            +
                      when WRITABLE ; # NOP
         | 
| 76 | 
            +
                      else          ; raise "unknown state: #{state}"
         | 
| 77 | 
            +
                      end
         | 
| 78 | 
            +
                    end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                    def W!(locker = nil)
         | 
| 81 | 
            +
                      case state
         | 
| 82 | 
            +
                      when CLOSED   ; open(DB::OCREATE | DB::OWRITER, locker); @state = WRITABLE
         | 
| 83 | 
            +
                      when READABLE ; C!(locker); W!(locker)
         | 
| 84 | 
            +
                      when WRITABLE ; # NOP
         | 
| 85 | 
            +
                      else          ; raise "unknown state: #{state}"
         | 
| 86 | 
            +
                      end
         | 
| 87 | 
            +
                    end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                    def R(locker = nil, &block)
         | 
| 90 | 
            +
                      case state
         | 
| 91 | 
            +
                      when CLOSED   ; begin; R!(locker); yield; ensure; close(locker); end
         | 
| 92 | 
            +
                      when READABLE ; yield
         | 
| 93 | 
            +
                      when WRITABLE ; yield
         | 
| 94 | 
            +
                      else          ; raise "unknown state: #{state}"
         | 
| 95 | 
            +
                      end
         | 
| 96 | 
            +
                    end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                    def W(locker = nil, &block)
         | 
| 99 | 
            +
                      case state
         | 
| 100 | 
            +
                      when CLOSED   ; begin; W!(locker); yield; ensure; close(locker); end
         | 
| 101 | 
            +
                      when READABLE ; raise "reopen from read to write is not permitted"
         | 
| 102 | 
            +
                        # TODO: close -> W -> close -> R ???
         | 
| 103 | 
            +
                      when WRITABLE ; yield
         | 
| 104 | 
            +
                      else          ; raise "unknown state: #{state}"
         | 
| 105 | 
            +
                      end
         | 
| 106 | 
            +
                    end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                    def touch
         | 
| 109 | 
            +
                      W() {}
         | 
| 110 | 
            +
                    end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                    private
         | 
| 113 | 
            +
                      def isReadable
         | 
| 114 | 
            +
                        case state
         | 
| 115 | 
            +
                        when CLOSED   ; false
         | 
| 116 | 
            +
                        when READABLE ; true
         | 
| 117 | 
            +
                        when WRITABLE ; true
         | 
| 118 | 
            +
                        else          ; raise "unknown state: #{state}"
         | 
| 119 | 
            +
                        end
         | 
| 120 | 
            +
                      end
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                      def isWritable
         | 
| 123 | 
            +
                        case state
         | 
| 124 | 
            +
                        when CLOSED   ; false
         | 
| 125 | 
            +
                        when READABLE ; false
         | 
| 126 | 
            +
                        when WRITABLE ; true
         | 
| 127 | 
            +
                        else          ; raise "unknown state: #{state}"
         | 
| 128 | 
            +
                        end
         | 
| 129 | 
            +
                      end
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                      def tryR(op)
         | 
| 132 | 
            +
                        isReadable or raise NotAllowed, "use R! or R{tch.%s} (%s)" % [op, source]
         | 
| 133 | 
            +
                      end
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                      def tryW(op)
         | 
| 136 | 
            +
                        isWritable or raise NotAllowed, "use W! or W{tch.%s} (%s)" % [op, source]
         | 
| 137 | 
            +
                      end
         | 
| 138 | 
            +
                    
         | 
| 139 | 
            +
                  end
         | 
| 140 | 
            +
                end
         | 
| 141 | 
            +
              end
         | 
| 142 | 
            +
            end
         |