github.com/nycdavid/zeus@v0.0.0-20201208104106-9ba439429e03/vagrant/lib/vagrant-zeus/commands/zeus-file-monitor.rb (about)

     1  module VagrantPlugins::Zeus
     2    module Commands
     3      class FileMonitor < Vagrant.plugin(2, :command)
     4        # these are pointing to ext/ and buid/ in the gem root
     5        if /linux/ =~ RUBY_PLATFORM
     6          FILE_MONITOR_EXECUTABLE = File.join(
     7            File.dirname(__FILE__),
     8            '../../../ext/inotify-wrapper/inotify-wrapper'
     9          )
    10        else
    11          FILE_MONITOR_EXECUTABLE = File.join(
    12            File.dirname(__FILE__),
    13            '../../../build/fsevents-wrapper'
    14          )
    15        end
    16  
    17        class FileWatcher
    18          def initialize(machine, env)
    19            @machine = machine
    20            @env = env
    21          end
    22  
    23          def run
    24            @zeus_connection = spawn_zeus_connection
    25            @file_monitor = spawn_file_monitor
    26  
    27            ui.info("Connected to zeus, watching for changes...")
    28  
    29            modified_files_buf = ''
    30            watched_files_buf = ''
    31            ended = false
    32  
    33            while !ended
    34              ready = IO.select([@file_monitor, @zeus_connection])
    35              ready[0].each do |fh|
    36                if fh == @file_monitor
    37                  begin
    38                    modified_files_buf += @file_monitor.readpartial(4096)
    39                  rescue EOFError
    40                    ui.error("Lost connection to the file monitor process, exiting!")
    41                    ended = true
    42                  end
    43                  modified_files_buf = process_modified_files(modified_files_buf)
    44                elsif fh == @zeus_connection
    45                  begin
    46                    watched_files_buf += @zeus_connection.readpartial(4096)
    47                  rescue EOFError
    48                    ui.error("Lost connection to the zeus socket, exiting!")
    49                    ended = true
    50                  end
    51                  watched_files_buf = process_watched_files(watched_files_buf)
    52                end
    53              end
    54            end
    55          ensure
    56            if @file_monitor
    57              begin
    58                Process.kill("KILL", @file_monitor.pid)
    59                Process.waitpid(@file_monitor.pid)
    60              rescue => e
    61                $stderr.puts(e)
    62              end
    63            end
    64          end
    65  
    66          private
    67  
    68          def spawn_file_monitor
    69            IO.popen(FILE_MONITOR_EXECUTABLE, 'r+')
    70          end
    71  
    72          def spawn_zeus_connection
    73            TCPSocket.new('localhost', @machine.config.zeus.file_monitor_port)
    74          end
    75  
    76          def process_modified_files(buf)
    77            lines = buf.sub(/.*\z/, '').split(/\n/)
    78            remaining = buf.sub(/\A.*\n/m, '')
    79  
    80            lines.each do |line|
    81              file = host_to_guest_path(line)
    82              if file
    83                @zeus_connection.write("#{file}\n")
    84              end
    85            end
    86  
    87            remaining
    88          end
    89  
    90          def process_watched_files(buf)
    91            lines = buf.sub(/.*\z/, '').split(/\n/)
    92            remaining = buf.sub(/\A.*\n/m, '')
    93  
    94            lines.each do |line|
    95              file = guest_to_host_path(line)
    96              if file
    97                @file_monitor.write("#{file}\n")
    98              end
    99            end
   100  
   101            remaining
   102          end
   103  
   104          def guest_to_host_path(path)
   105            guest_to_host_map.each do |guest, host|
   106              if path.start_with?(guest)
   107                return path.sub(/\A#{guest}/, host)
   108              end
   109            end
   110  
   111            # NOTE: we are explicitly not passing through things that don't live
   112            # on a synced folder, because they won't exist in a useful form on
   113            # the other side (and the inotify watcher has a slow poll for files
   114            # that don't exist)
   115            nil
   116          end
   117  
   118          def host_to_guest_path(path)
   119            host_to_guest_map.each do |host, guest|
   120              if path.start_with?(host)
   121                return path.sub(/\A#{host}/, guest)
   122              end
   123            end
   124  
   125            # NOTE: see above - this is less important because it doesn't trigger
   126            # the slow poll path, but still unnecessary
   127            nil
   128          end
   129  
   130          def guest_to_host_map
   131            paths = []
   132            @machine.config.vm.synced_folders.map do |_, options|
   133              if !options[:disabled]
   134                paths.push([
   135                  options[:guestpath],
   136                  File.absolute_path(options[:hostpath])
   137                ])
   138              end
   139            end
   140            paths.sort_by { |guest, host| guest.length }.reverse
   141          end
   142  
   143          def host_to_guest_map
   144            paths = []
   145            @machine.config.vm.synced_folders.map do |_, options|
   146              if !options[:disabled]
   147                paths.push([
   148                  File.absolute_path(options[:hostpath]),
   149                  options[:guestpath]
   150                ])
   151              end
   152            end
   153            paths.sort_by { |host, guest| host.length }.reverse
   154          end
   155  
   156          def ui
   157            @env.ui
   158          end
   159        end
   160  
   161        def execute
   162          with_target_vms(nil, :single_target => true) do |machine|
   163            watcher = FileWatcher.new(machine, @env)
   164            while true
   165              begin
   166                watcher.run
   167              rescue => e
   168                @env.ui.error(e.message)
   169              end
   170              sleep 1
   171            end
   172          end
   173          0
   174        end
   175      end
   176    end
   177  end