File: file_cache_map.rb

package info (click to toggle)
ruby-rgen 0.10.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,428 kB
  • sloc: ruby: 11,344; xml: 1,368; yacc: 72; makefile: 10
file content (124 lines) | stat: -rw-r--r-- 3,630 bytes parent folder | download | duplicates (11)
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
require 'digest'
require 'fileutils'

module RGen

module Util

# Implements a cache for storing and loading data associated with arbitrary files.
# The data is stored in cache files within a subfolder of the folder where
# the associated files exists.
# The cache files are protected with a checksum and loaded data will be
# invalid in case either the associated file are the cache file has changed.
#
class FileCacheMap
  # optional program version info to be associated with the cache files
  # if the program version changes, cached data will also be invalid
  attr_accessor :version_info

  # +cache_dir+ is the name of the subfolder where cache files are created
  # +postfix+ is an extension appended to the original file name for 
  # creating the name of the cache file
  def initialize(cache_dir, postfix)
    @postfix = postfix
    @cache_dir = cache_dir
  end

  # load data associated with file +key_path+
  # returns :invalid in case either the associated file or the cache file has changed
  #
  # options:
  #  :invalidation_reasons:
  #    an array which will receive symbols indicating why the cache is invalid:
  #    :no_cachefile, :cachefile_corrupted, :keyfile_changed
  #
  def load_data(key_path, options={})
    reasons = options[:invalidation_reasons] || []
    cf = cache_file(key_path)
    result = nil
    begin
      File.open(cf, "rb") do |f|
        header = f.read(41)
        if !header
          reasons << :cachefile_corrupted
          return :invalid
        end
        checksum = header[0..39]
        data = f.read
        if calc_sha1(data) == checksum
          if calc_sha1_keydata(key_path) == data[0..39]
            result = data[41..-1]
          else
            reasons << :keyfile_changed
            result = :invalid
          end
        else
          reasons << :cachefile_corrupted
          result = :invalid
        end
      end 
    rescue Errno::ENOENT
      reasons << :no_cachefile
      result = :invalid 
    end
    result
  end

  # store data +value_data+ associated with file +key_path+
  def store_data(key_path, value_data)
    data = calc_sha1_keydata(key_path) + "\n" + value_data
    data = calc_sha1(data) + "\n" + data
    cf = cache_file(key_path)
    FileUtils.mkdir(File.dirname(cf)) rescue Errno::EEXIST
    File.open(cf, "wb") do |f|
      f.write(data)
    end 
  end

  # remove cache files which are not associated with any file in +key_paths+
  # will only remove files within +root_path+
  def clean_unused(root_path, key_paths)
    raise "key paths must be within root path" unless key_paths.all?{|p| p.index(root_path) == 0}
    used_files = key_paths.collect{|p| cache_file(p)}
    files = Dir[root_path+"/**/"+@cache_dir+"/*"+@postfix] 
    files.each do |f|
      FileUtils.rm(f) unless used_files.include?(f)
    end
  end

private
  
  def cache_file(path)
    File.dirname(path) + "/"+@cache_dir+"/" + File.basename(path) + @postfix 
  end

  def calc_sha1(data)
    sha1 = Digest::SHA1.new
    sha1.update(data)
    sha1.hexdigest
  end

  def keyData(path)
    File.read(path)+@version_info.to_s
  end

  # this method is much faster than calling +keyData+ and putting the result in +calc_sha1+
  # reason is probably that there are not so many big strings being created
  def calc_sha1_keydata(path)
    begin
      sha1 = Digest::SHA1.new
      sha1.file(path)
      sha1.update(@version_info.to_s)
      sha1.hexdigest
    rescue Errno::ENOENT
      "<missing_key_file>"
    end
  end
   
end

end

end