vagrant/plugins/synced_folders/rsync/command/rsync_auto.rb

156 lines
5.2 KiB
Ruby

require "log4r"
require 'optparse'
require "thread"
require "listen"
require "vagrant/action/builtin/mixin_synced_folders"
require "vagrant/util/busy"
require_relative "../helper"
module VagrantPlugins
module SyncedFolderRSync
module Command
class RsyncAuto < Vagrant.plugin("2", :command)
include Vagrant::Action::Builtin::MixinSyncedFolders
def self.synopsis
"syncs rsync synced folders automatically when files change"
end
def execute
@logger = Log4r::Logger.new("vagrant::commands::rsync-auto")
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant rsync-auto [vm-name]"
o.separator ""
end
# Parse the options and return if we don't have any target.
argv = parse_options(opts)
return if !argv
# Build up the paths that we need to listen to.
paths = {}
ignores = []
with_target_vms(argv) do |machine|
folders = synced_folders(machine)[:rsync]
next if !folders || folders.empty?
# Get the SSH info for this machine so we can do an initial
# sync to the VM.
ssh_info = machine.ssh_info
if ssh_info
machine.ui.info(I18n.t("vagrant.rsync_auto_initial"))
folders.each do |id, folder_opts|
RsyncHelper.rsync_single(machine, ssh_info, folder_opts)
end
end
folders.each do |id, folder_opts|
# If we marked this folder to not auto sync, then
# don't do it.
next if folder_opts.has_key?(:auto) && !folder_opts[:auto]
hostpath = folder_opts[:hostpath]
hostpath = File.expand_path(hostpath, machine.env.root_path)
paths[hostpath] ||= []
paths[hostpath] << {
id: id,
machine: machine,
opts: folder_opts,
}
if folder_opts[:exclude]
Array(folder_opts[:exclude]).each do |pattern|
ignores << RsyncHelper.exclude_to_regexp(hostpath, pattern.to_s)
end
end
end
end
# Output to the user what paths we'll be watching
paths.keys.sort.each do |path|
paths[path].each do |path_opts|
path_opts[:machine].ui.info(I18n.t(
"vagrant.rsync_auto_path",
path: path.to_s,
))
end
end
@logger.info("Listening to paths: #{paths.keys.sort.inspect}")
@logger.info("Ignoring #{ignores.length} paths:")
ignores.each do |ignore|
@logger.info(" -- #{ignore.to_s}")
end
@logger.info("Listening via: #{Listen::Adapter.select.inspect}")
callback = method(:callback).to_proc.curry[paths]
listener = Listen.to(*paths.keys, ignore: ignores, &callback)
# Create the callback that lets us know when we've been interrupted
queue = Queue.new
callback = lambda do
# This needs to execute in another thread because Thread
# synchronization can't happen in a trap context.
Thread.new { queue << true }.join
end
# Run the listener in a busy block so that we can cleanly
# exit once we receive an interrupt.
Vagrant::Util::Busy.busy(callback) do
listener.start
queue.pop
listener.stop if listener.listen?
end
0
end
# This is the callback that is called when any changes happen
def callback(paths, modified, added, removed)
@logger.info("File change callback called!")
@logger.info(" - Modified: #{modified.inspect}")
@logger.info(" - Added: #{added.inspect}")
@logger.info(" - Removed: #{removed.inspect}")
tosync = []
paths.each do |hostpath, folders|
# Find out if this path should be synced
found = catch(:done) do
[modified, added, removed].each do |changed|
changed.each do |listenpath|
throw :done, true if listenpath.start_with?(hostpath)
end
end
# Make sure to return false if all else fails so that we
# don't sync to this machine.
false
end
# If it should be synced, store it for later
tosync << folders if found
end
# Sync all the folders that need to be synced
tosync.each do |folders|
folders.each do |opts|
ssh_info = opts[:machine].ssh_info
begin
RsyncHelper.rsync_single(opts[:machine], ssh_info, opts[:opts])
rescue Vagrant::Errors::MachineGuestNotReady
# Error communicating to the machine, probably a reload or
# halt is happening. Just notify the user but don't fail out.
opts[:machine].ui.error(I18n.t(
"vagrant.rsync_communicator_not_ready_callback"))
end
end
end
end
end
end
end
end