require 'digest' require 'fileutils' module Concrete # 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 :versionInfo # +cacheDir+ is the name of the subfolder where cache files are created # +postfix+ is an extension appended to the original file name in order to # create the name of the cache file def initialize(cacheDir, postfix) @postfix = postfix @cacheDir = cacheDir end # load data associated with file +keyPath+ # returns :invalid in case either the associated file or the cache file has changed def loadData(keyPath) cf = cacheFile(keyPath) return :invalid unless File.exist?(cf) result = nil File.open(cf, "rb") do |f| checksum = f.read(41)[0..39] data = f.read if calcSha1(data) == checksum if calcSha1(keyData(keyPath)) == data[0..39] result = data[41..-1] else result = :invalid end else result = :invalid end end result end # store data +valueData+ associated with file +keyPath+ def storeData(keyPath, valueData) data = calcSha1(keyData(keyPath)) + "\n" + valueData data = calcSha1(data) + "\n" + data cf = cacheFile(keyPath) FileUtils.mkdir(File.dirname(cf)) unless File.exist?(File.dirname(cf)) File.open(cf, "wb") do |f| f.write(data) end end # remove cache files which are not associated with any file in +keyPaths+ # will only remove files within +rootPath+ def cleanUnused(rootPath, keyPaths) raise "key paths must be within root path" unless keyPaths.all?{|p| p.index(rootPath) == 0} usedFiles = keyPaths.collect{|p| cacheFile(p)} files = Dir[rootPath+"/**/"+@cacheDir+"/*"+@postfix] files.each do |f| FileUtils.rm(f) unless usedFiles.include?(f) end end private def keyData(path) File.read(path)+@versionInfo.to_s end def cacheFile(path) File.dirname(path) + "/"+@cacheDir+"/" + File.basename(path) + @postfix end def calcSha1(data) sha1 = Digest::SHA1.new sha1.update(data) sha1.hexdigest end end end