Skip to content

Instantly share code, notes, and snippets.

@rab
Created May 28, 2011 21:35
Show Gist options
  • Save rab/997254 to your computer and use it in GitHub Desktop.
Save rab/997254 to your computer and use it in GitHub Desktop.
Ruby script to xfer a set of directories to an sftp location
#!/usr/bin/env ruby -w
# Expected to be found at ~/code/ruby/xferlist.rb
# Move all files newer than the 'sentinel' to the web site
# skip all .doc files (MS-Word)
# skip all files containing a tilde (~) (local backup or archival copies)
# skip anything in 'old-notices'
#
# Deals with spaces in file/directory names by using the '?' shell wildcard
require 'optparse'
require 'ostruct'
require 'rubygems'
require 'net/sftp'
require 'fileutils'
options = OpenStruct.new(:user => ENV['USER'] || ENV['LOGNAME'],
:exclusions => [ /\A\./, /~/, /\.cache\z/,
/\.psd\z/, /\.docx?\z/, /\.xls\z/, /\.pages\z/,
/old-notices/,
],
:dryrun => false,
:debug => false)
OptionParser.new do |opts|
opts.banner = "Usage: #{$0} [options] connection-spec [simple-dir [...]]"
opts.separator " where a connection-spec is for sftp like user@host:directory"
opts.on("-d", '--debug',
"Include debugging comments in the output") do |val|
puts "debug: #{val}"
options.debug = true
end
opts.on("-n", '--dry-run',
"Don't change the sentinel or run the commands through sftp") do |val|
puts "dryrun: #{val}" if options.debug
options.dryrun = val
options.debug = true
end
opts.on("--dir FROM,TO", String,
"A list of directory pairs local,remote[,local2,remote2,...]",
"in which to look for new files to transfer. This option",
"may be repeated.") do |val|
val.scan %r{([^,]+),([^,]+)} do |f,t|
puts "dir: #{f} => #{t}" if options.debug
(options.dirs ||= []) << [f,t]
end
end
opts.on("-x", '--exclude',
"") do |val|
end
opts.on_tail("-h", "--help", "Show this message") do
puts opts
exit
end
end.parse!
target = ARGV.shift
puts "sftp #{target}" if options.dryrun
user, host, start_dir = target.match(/\A(?:([^@]+)@)?([^:]+)(?::(.*))?\z/).captures
ARGV.each {|dir| (options.dirs ||= []) << [dir,dir] }
class ProgressHandler
def initialize(debug)
@debug = debug
end
def on_open(uploader, file)
puts "#{uploader.object_id.to_s(16)}: starting upload: #{file.local} -> #{file.remote} (#{file.size} bytes)"
end
def on_put(uploader, file, offset, data)
puts "#{uploader.object_id.to_s(16)}: writing #{data.length} bytes to #{file.remote} starting at #{offset}" if @debug
end
def on_close(uploader, file)
puts "#{uploader.object_id.to_s(16)}: finished with #{file.remote}"
end
def on_mkdir(uploader, path)
puts "#{uploader.object_id.to_s(16)}: creating directory #{path}"
end
def on_finish(uploader)
puts "#{uploader.object_id.to_s(16)}: all done!" if @debug
end
end
puts "Starts in #{Dir.pwd}" if options.debug
sentinel = File.mtime('sentinel') rescue Time.at(0)
uploads = []
Net::SFTP.start(host, user) do |sftp|
(options.dirs || [['.','.']]).each do |from,to|
Dir.glob("#{from}/**/*") do |localfile|
next if sentinel > File.mtime(localfile)
next if File.directory?(localfile) # I really should handle directory creation!
next if options.exclusions.any? {|exclude| localfile =~ exclude }
localfile, remotefile = localfile, localfile.sub(/\A#{from}/, to)
remotefile = File.join(start_dir, remotefile) if start_dir
puts "upload #{localfile} => #{remotefile}" if options.debug
uploads << sftp.upload(localfile, remotefile,
:progress => ProgressHandler.new(options.debug)) unless options.dryrun
end
end
uploads.each { |u| u.wait }
end
unless options.dryrun
File.rename 'sentinel', 'sentinel.prev' if File.exist? 'sentinel'
FileUtils.touch 'sentinel'
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment