require 'stockboy/provider'
module Stockboy::Providers
class FTP < Stockboy::Provider
require_relative 'ftp/ftp_adapter'
require_relative 'ftp/sftp_adapter'
dsl_attr :host
dsl_attr :passive
dsl_attr :username
dsl_attr :password
dsl_attr :binary
dsl_attr :secure
dsl_attr :file_name
dsl_attr :file_dir
dsl_attr :file_newer, alias: :since
dsl_attr :file_smaller
dsl_attr :file_larger
dsl_attr :pick
def initialize(opts={}, &block)
super(opts, &block)
@host = opts[:host]
@passive = opts[:passive]
@username = opts[:username]
@password = opts[:password]
@secure = opts[:secure]
@binary = opts[:binary]
@file_dir = opts[:file_dir]
@file_name = opts[:file_name]
@file_newer = opts[:file_newer]
@file_smaller = opts[:file_smaller]
@file_larger = opts[:file_larger]
@pick = opts[:pick] || :last
DSL.new(self).instance_eval(&block) if block_given?
@open_adapter = nil
end
def adapter_class
secure ? SFTPAdapter : FTPAdapter
end
def adapter
return yield @open_adapter if @open_adapter
adapter_class.new(self).open do |ftp|
@open_adapter = ftp
ftp.chdir file_dir if file_dir
response = yield ftp
@open_adapter = nil
response
end
rescue adapter_class.exception_class => e
errors << e.message
logger.warn e.message
nil
end
def client
adapter { |ftp| yield ftp.client }
end
def matching_file
return @matching_file if @matching_file
adapter do |ftp|
file_listing = ftp.list_files
@matching_file = pick_from file_listing.select(&file_name_matcher)
end
end
def delete_data
raise Stockboy::OutOfSequence, "must confirm #matching_file or calling #data" unless picked_matching_file?
adapter do |ftp|
logger.info "FTP deleting file #{host} #{file_dir}/#{matching_file}"
ftp.delete matching_file
matching_file
end
end
def clear
super
@matching_file = nil
@data_time = nil
@data_size = nil
end
private
def fetch_data
adapter do |ftp|
validate_file(matching_file)
if valid?
logger.debug "FTP getting file #{inspect_matching_file}"
@data = ftp.download(matching_file)
logger.debug "FTP got file #{inspect_matching_file} (#{data_size} bytes)"
end
end
!@data.nil?
end
def picked_matching_file?
!!@matching_file
end
def validate
errors << "host must be specified" if host.blank?
errors << "file_name must be specified" if file_name.blank?
errors.empty?
end
def file_name_matcher
case file_name
when Regexp
->(i) { i =~ file_name }
when String
->(i) { ::File.fnmatch(file_name, i) }
end
end
def validate_file(data_file)
return errors << "No matching files" unless data_file
validate_file_newer(data_file)
validate_file_smaller(data_file)
validate_file_larger(data_file)
end
def validate_file_newer(data_file)
@data_time ||= adapter { |ftp| ftp.modification_time(data_file) }
if file_newer and @data_time < file_newer
errors << "No new files since #{file_newer}"
end
end
def validate_file_smaller(data_file)
@data_size ||= adapter { |ftp| ftp.size(data_file) }
if file_smaller and @data_size > file_smaller
errors << "File size larger than #{file_smaller}"
end
end
def validate_file_larger(data_file)
@data_size ||= adapter { |ftp| ftp.size(data_file) }
if file_larger and @data_size < file_larger
errors << "File size smaller than #{file_larger}"
end
end
def inspect_matching_file
"#{host} #{file_dir}/#{matching_file}"
end
end
end