module HaveAPI::Fs # All built-in components are stored in this module. module Components ; end # The basic building block of the file system. Every directory and file is # represented by a subclass of this class. class Component # An encapsulation of a Hash to store child components. class Children attr_accessor :context # @param [HaveAPI::Fs::Context] ctx def initialize(ctx) @context = ctx @store = {} end def [](k) @store[k] end # Replace a child named `k` by a new child represented by `v`. The old # child, if present, is invalidated and dropped from the cache. # {Factory} is used to create an instance of `v`. # # @param [Symbol] k # @param [Array] v def []=(k, v) if @store.has_key?(k) @store[k].invalidate @store[k].context.cache.drop_below(@store[k].path) end @store[k] = Factory.create(@context, k, *v) end def set(k, v) @store[k] = v end %i(has_key? clear each select detect delete_if).each do |m| define_method(m) do |*args, &block| @store.send(m, *args, &block) end end end class << self # Define reader methods for child components. def children_reader(*args) args.each do |arg| define_method(arg) { children[arg] } end end # Set or get a component name. Component name is used for finding # components within a {Context}. # # @param [Symbol] name # @return [nil] if name is set # @return [Symbol] if name is nil def component(name = nil) if name @component = name else @component end end # Pass component name to the subclass. def inherited(subclass) subclass.component(@component) end end attr_accessor :context, :atime, :mtime, :ctime # @param [Boolean] bound def initialize(bound: false) @bound = bound @atime = @mtime = @ctime = Time.now end # Called by {Factory} when the instance is prepared. Subclasses must call # this method. def setup @children = Children.new(context) end # Attempt to find a child component with `name`. # # @return [HaveAPI::Fs::Component] if found # @return [nil] if not found def find(name) return @children[name] if @children.has_key?(name) c = new_child(name) @children.set(name, Factory.create(context, name, *c)) if c end # Attempt to find and use nested components with `names`. Each name is for # the next descendant. If the target component is found, it and all # components in its path will be bound. Bound components are not # automatically deleted when not in use. def use(*names) ret = self path = [] names.each do |n| ret = ret.find(n) return if ret.nil? path << ret end path.each { |c| c.bound = true } ret end def bound? @bound end def bound=(b) @bound = b end def directory? !file? end def file? !directory? end def readable? true end def writable? false end def executable? false end def contents raise NotImplementedError end def times [@atime, @mtime, @ctime] end # Shortcut for {#drop_children} and {#setup}. def reset drop_children setup end def title self.class.name end # @return [String] path of this component in the tree without the leading / def path context.file_path.join('/') end # @return [String] absolute path of this component from the system root def abspath File.join( context.mountpoint, path ) end def parent context.object_path[-2][1] end # A component is unsaved if it or any of its descendants has been modified # and not saved. # # @param [Integer] n used to determine the result just once per the same `n` # @return [Boolean] def unsaved?(n = nil) return @is_unsaved if n && @last_unsaved == n child = @children.detect { |_, c| c.unsaved? } @last_unsaved = n @is_unsaved = !child.nil? end # Mark the component and all its descendats as invalid. Invalid components # can still be in the cache and are dropped on hit. def invalidate @invalid = true children.each { |_, c| c.invalidate } end def invalid? @invalid end protected attr_accessor :children # Called to create a component for a child with `name` if this child is not # yet or not anymore in memory. All subclasses should extend this method to # add their own custom contents. # # @param [Symbol] name # @return [Array] the array describes the new child to be created by # {Factory}. The first item is a class name and # the rest are arguments to its constructor. # @return [nil] if the child does not exist def new_child(name) raise NotImplementedError end # Drop all children from the memory and clear them from the cache. def drop_children @children.clear context.cache.drop_below(path) end # Update the time of last modification. def changed self.mtime = Time.now end end end