File: datastore.rb

package info (click to toggle)
mhc 1.1.1-2
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 2,320 kB
  • ctags: 3,529
  • sloc: ruby: 12,404; lisp: 7,448; makefile: 70; sh: 69
file content (165 lines) | stat: -rw-r--r-- 3,952 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
require "fileutils"
require "pathname"

module Mhc
  class DataStore
    def initialize(basedir)
      unless basedir and File.directory?(File.expand_path(basedir.to_s))
        raise Mhc::ConfigurationError, "datastore directory '#{basedir}' not found"
      end
      @basedir = Pathname.new(File.expand_path(basedir))
      @cache   = Cache.new(File.expand_path("status/cache/events.pstore", @basedir))
    end

    def entries(date_range = nil)
      if date_range
        int_range = date_range.min.absolute_from_epoch .. date_range.max.absolute_from_epoch
      end

      Enumerator.new do |yielder|
        ["inbox", "spool", "presets"].each do |slot|
          dir = File.expand_path(slot, @basedir)
          next unless File.directory?(dir)

          Dir.chdir(dir) do
            Dir.foreach(".") do |ent|
              parse_mhcc(ent).each {|ev| yielder << ev} if /\.mhcc$/ =~ ent
              next unless /\.mhc$/ =~ ent
              uid = $`
              cache_entry = @cache.lookup(uid, ent)
              if !date_range || cache_entry.involved?(int_range)
                yielder << Event.parse_file(File.expand_path(ent))
              end
            end
          end
        end
        @cache.save
      end
    end

    def logger
      @logger ||= Mhc::Logger.new(@logfile)
    end

    def find_by_uid(uid)
      path = find_path(uid)
      return nil unless path
      return Event.parse_file(path)
    end

    def create(event)
      if find_by_uid(event.uid)
        raise "Already exist uid:#{uid} in #{@basedir}"
      end
      File.open(path, "w") do |f|
        f.write(event.dump)
      end
    end

    def update(event)
      unless path = uid_to_path(event.uid)
        raise "Not found uid:#{uid} in #{@basedir}"
      end
      File.open(path, "w") do |f|
        f.write(event.dump)
      end
    end

    def delete(uid_or_event)
      uid = if uid_or_event.respond_to?(:uid)
              uid_or_event.uid
            else
              uid_or_event
            end
      if path = find_path(uid)
        File.delete(path)
      else
        raise "Not found uid:#{uid} in #{@basedir}"
      end
    end

    ################################################################
    private

    def parse_mhcc(filename)
      string = File.open(filename).read.scrub.gsub(/^\s*#.*$/, "").strip
      string.split(/\n\n\n*/).map do |header|
        Event.parse(header)
      end
    end

    def find_path(uid)
      glob = @basedir + ('**/' + uid + '.mhc')
      return Dir.glob(glob).first
    end

    def uid_to_path(uid)
      return @basedir + ('spool/' + uid + '.mhc')
    end

  end # class DataStore
end # module Mhc

module Mhc
  class DataStore
    class Cache
      require 'pstore'

      def initialize(cache_filename)
        @pstore = PStore.new(cache_filename)
        load
      end

      def lookup(uid, filename)
        unless c = get(uid) and File.mtime(filename).to_i <= c.mtime
          c = CacheEntry.new(filename)
          put(uid, c)
        end
        return c
      end

      def save
        return self unless @dirty
        @pstore.transaction do
          @pstore["root"] = @db
        end
        @dirty = false
      end

      private

      def get(uid)
        @db[uid]
      end

      def put(uid, value)
        @db[uid] = value
        @dirty = true
      end

      def load
        @pstore.transaction do
          @db = @pstore["root"] || {}
        end
        @dirty = false
      end

    end # class Cache

    class CacheEntry
      attr_reader :mtime, :range

      def initialize(filename)
        @mtime, event = File.mtime(filename).to_i, Event.parse_file(filename)
        @range = event.range.min.absolute_from_epoch ..
                 event.range.max.absolute_from_epoch
      end

      def involved?(range)
        range.min <= @range.max && @range.min <= range.max
      end

    end # class CacheEntry

  end # class DataStore
end # module Mhc