ThibautAdd Store#size456c4cb11 years agoHistory
207 lines · 3.8 KB · Ruby
RawDownload
require 'pathname' module Docs  class AbstractStore    class InvalidPathError < StandardError; end    class LockError < StandardError; end     include Instrumentable     def initialize(path)      path = Pathname.new(path).cleanpath      raise ArgumentError if path.relative?      @root_path = @working_path = path.freeze    end     def root_path      @root_path.to_s    end     def working_path      @working_path.to_s    end     def expand_path(path)      join_paths @working_path, path    end     def open(path, &block)      if block_given?        open_yield_close(path, &block)      else        set_working_path join_paths(@root_path, path)      end    end     def close      set_working_path @root_path    end     def read(path)      path = expand_path(path)      read_file(path) if file_exist?(path)    end     def write(path, value)      path = expand_path(path)      touch(path)       if file_exist?(path)        update(path, value)      else        create(path, value)      end    end     def delete(path)      path = expand_path(path)       if file_exist?(path)        destroy(path)        true      end    end     def exist?(path)      file_exist? expand_path(path)    end     def mtime(path)      path = expand_path(path)      file_mtime(path) if file_exist?(path)    end     def size(path)      path = expand_path(path)      file_size(path) if file_exist?(path)    end     def each(&block)      list_files(working_path, &block)    end     def replace(path = nil, &block)      if path        return open(path) { replace(&block) }      else        lock { track_touched { yield.tap { delete_untouched } } }      end    end     private     def read_file(path)      raise NotImplementedError    end     def create_file(path, value)      raise NotImplementedError    end     def update_file(path, value)      raise NotImplementedError    end     def delete_file(path)      raise NotImplementedError    end     def file_exist?(path)      raise NotImplementedError    end     def file_mtime(path)      raise NotImplementedError    end     def file_size(path)      raise NotImplementedError    end     def list_files(path, &block)      raise NotImplementedError    end     def set_working_path(path)      @working_path = Pathname.new(path).freeze if assert_unlocked    end     def join_paths(base, path)      base = Pathname.new(base).cleanpath      path = Pathname.new(path).cleanpath      path = base + path unless path.absolute?       unless File.join(path, '').start_with? File.join(base, '')        raise InvalidPathError, "Tried accessing #{path} outside #{base}"      end       path.to_s    end     def open_yield_close(path)      working_path_was = working_path      open(path)      yield    ensure      set_working_path working_path_was    end     def create(path, value)      instrument 'create.store', path: path do        create_file(path, value)      end    end     def update(path, value)      instrument 'update.store', path: path do        update_file(path, value)      end    end     def destroy(path)      instrument 'destroy.store', path: path do        delete_file(path)      end    end     def lock      assert_unlocked      @locked = true      yield    ensure      @locked = false    end     def assert_unlocked      raise LockError if @locked      true    end     def track_touched      @touched = []      yield    ensure      @touched = nil    end     def touch(path)      @touched << path if @touched    end     def touched?(path)      dir = File.join(path, '')       @touched.any? do |touched_path|        touched_path == path || touched_path.start_with?(dir)      end    end     def delete_untouched      return if @touched.empty?       each do |path|        destroy(path) unless touched?(path)      end    end  endend