module Listen class QueueOptimizer class Config def initialize(adapter_class, silencer) @adapter_class = adapter_class @silencer = silencer end def exist?(path) Pathname(path).exist? end def silenced?(path, type) @silencer.silenced?(path, type) end def debug(*args, &block) Listen.logger.debug(*args, &block) end end def smoosh_changes(changes) # TODO: adapter could be nil at this point (shutdown) cookies = changes.group_by do |_, _, _, _, options| (options || {})[:cookie] end _squash_changes(_reinterpret_related_changes(cookies)) end def initialize(config) @config = config end private attr_reader :config # groups changes into the expected structure expected by # clients def _squash_changes(changes) # We combine here for backward compatibility # Newer clients should receive dir and path separately changes = changes.map { |change, dir, path| [change, dir + path] } actions = changes.group_by(&:last).map do |path, action_list| [_logical_action_for(path, action_list.map(&:first)), path.to_s] end config.debug("listen: raw changes: #{actions.inspect}") { modified: [], added: [], removed: [] }.tap do |squashed| actions.each do |type, path| squashed[type] << path unless type.nil? end config.debug("listen: final changes: #{squashed.inspect}") end end def _logical_action_for(path, actions) actions << :added if actions.delete(:moved_to) actions << :removed if actions.delete(:moved_from) modified = actions.detect { |x| x == :modified } _calculate_add_remove_difference(actions, path, modified) end def _calculate_add_remove_difference(actions, path, default_if_exists) added = actions.count { |x| x == :added } removed = actions.count { |x| x == :removed } diff = added - removed # TODO: avoid checking if path exists and instead assume the events are # in order (if last is :removed, it doesn't exist, etc.) if config.exist?(path) if diff > 0 :added elsif diff.zero? && added > 0 :modified else default_if_exists end else diff < 0 ? :removed : nil end end # remove extraneous rb-inotify events, keeping them only if it's a possible # editor rename() call (e.g. Kate and Sublime) def _reinterpret_related_changes(cookies) table = { moved_to: :added, moved_from: :removed } cookies.flat_map do |_, changes| data = _detect_possible_editor_save(changes) if data to_dir, to_file = data [[:modified, to_dir, to_file]] else not_silenced = changes.reject do |type, _, _, path, _| config.silenced?(Pathname(path), type) end not_silenced.map do |_, change, dir, path, _| [table.fetch(change, change), dir, path] end end end end def _detect_possible_editor_save(changes) return unless changes.size == 2 from_type = from_change = from = nil to_type = to_change = to_dir = to = nil changes.each do |data| case data[1] when :moved_from from_type, from_change, _, from, = data when :moved_to to_type, to_change, to_dir, to, = data else return nil end end return unless from && to # Expect an ignored moved_from and non-ignored moved_to # to qualify as an "editor modify" return unless config.silenced?(Pathname(from), from_type) config.silenced?(Pathname(to), to_type) ? nil : [to_dir, to] end end end