Add tasks files

new_user
1resu 10 months ago
parent 2133be2436
commit 1ef6964dd7
  1. 2
      lib/tasks/bnn_sync.rake
  2. 7
      lib/tasks/ftp_sync.rake
  3. 31
      lib/tasks/import.rake
  4. 116
      lib/tasks/mail.rake

@ -0,0 +1,2 @@
desc "Sync files via FTP -- legacy task name. Update articles."
task sync_bnn_files: :sync_ftp_files

@ -0,0 +1,7 @@
desc "Sync files via FTP. Update articles."
task sync_ftp_files: :environment do
Supplier.ftp_sync.all.each do |supplier|
puts "FTP-sync files for #{supplier.name}..."
supplier.sync_ftp_files
end
end

@ -0,0 +1,31 @@
# encoding:utf-8
desc "Import articles from file (options: FILE=f, TYPE=t, SUPPLIER=id_or_name)"
task 'import:articles': :environment do |t, args|
filename = ENV['FILE']
filename.present? or raise 'Please set FILE to the file to import'
type = ENV['TYPE']
types_avail = ArticleImport.file_formats.keys
types_avail.include?(type) or raise "Please set TYPE to one of: #{types_avail.join(', ')}"
supplier = Supplier.where('id = ? OR name = ?', ENV['SUPPLIER'], ENV['SUPPLIER']).first
supplier.present? or raise 'Please set SUPPLIER to a supplier id or name'
outlisted_counter, new_counter, updated_counter, invalid_articles = begin
Article.transaction do
supplier.update_articles_from_file(File.new(filename), type: type)
end
end
# show result
# @todo remove code duplication with mail_sync
puts "* imported: #{new_counter} new, #{updated_counter} updated, #{outlisted_counter} outlisted, #{invalid_articles.size} invalid"
invalid_articles.each do |article|
puts "- invalid article '#{article.name}'"
article.errors.each do |attr, msg|
msg.split("\n").each do |l|
puts " · #{attr.blank? ? '' : "#{attr}: "} #{l}"
end
end
end
end

@ -0,0 +1,116 @@
#
# Rake tasks for receiving article updates by email.
#
# Mail setup is heavily inspired by Foodsoft's reply-by-mail feature for messages
# @see https://github.com/foodcoops/foodsoft/blob/master/lib/foodsoft_mail_receiver.rb
#
require "mail"
require "midi-smtp-server"
class ReplyEmailSmtpServer < MidiSmtpServer::Smtpd
def start
super
@from = nil
@supplier = nil
end
private
def on_mail_from_event(ctx, from)
@from = from
end
def on_rcpt_to_event(ctx, rcpt_to)
recipient = rcpt_to.gsub(/\A\s*<\s*(.*)\s*>\s*\z/, '\1')
@supplier = find_supplier(recipient)
rcpt_to
rescue => error
logger.info("Can not accept mail for '#{rcpt_to}': #{error}")
raise MidiSmtpServer::Smtpd550Exception
end
def on_message_data_event(ctx)
handle_mail(ctx[:message][:data])
end
def find_supplier(recipient)
m = /\A#{Regexp.escape(ENV['MAILER_PREFIX'])}(?<supplier_id>\d+)\.(?<hash>\w+)(@(?<hostname>.*))?/.match(recipient)
raise "recipient is missing or has an invalid format" if m.nil?
supplier = Supplier.mail_sync.find_by_id(m[:supplier_id])
raise "supplier id #{m[:supplier_id]} could not be found" if supplier.nil?
hash = supplier.articles_mail_hash
hash.casecmp(m[:hash]) == 0 or raise "hash '#{m[:hash]}' does not match expectations for supplier '#{supplier.name}'"
supplier
end
def handle_mail(data)
message = Mail.read_from_string(data)
# message checks
if @supplier.mail_from.present?
m, s = message.from, @supplier.mail_from
m.any? {|n| n.include?(s) } or raise "Expected to find '#{s}' in from address '#{m.join(', ')}' for supplier #{@supplier.name}"
end
if @supplier.mail_subject.present?
m, s = message.subject, @supplier.mail_subject
m.downcase.include?(s.downcase) or raise "Expected to find '#{s}' in subject '#{m}' for supplier #{@supplier.name}"
end
# get attachment
filename = nil
message.attachments.each do |part|
# @todo perhaps get heuristic from article import filters?
if part.filename.match(/\.(xls|xlsx|ods|sxc|csv|tsv|xml)$/i)
FileUtils.mkdir_p(@supplier.mail_path)
filename = "#{message.date.strftime '%Y%m%d'}_#{part.filename.gsub(/[^-a-z0-9_\.]+/i, '_')}"
filename = @supplier.mail_path.join(filename)
File.open(filename, 'w+b') { |f| f.write part.body.decoded }
end
end
raise "No spreadsheet attachment found" unless filename.present?
# import!
outlisted_counter, new_counter, updated_counter, invalid_articles =
@supplier.update_articles_from_file(File.new(filename), type: @supplier.mail_type)
msg = "Handled articles update email for #{@supplier.name}: "
msg += "#{new_counter} new, #{updated_counter} updated, #{outlisted_counter} outlisted, #{invalid_articles.size} invalid"
invalid_articles.map do |article|
msg += "\n* invalid article '#{article.name}'"
article.errors.each do |attr, errmsg|
errmsg.split("\n").each do |l|
msg += "\n - #{': ' unless attr.blank?}" + l
end
end
end
logger.info(msg)
end
end
namespace :mail do
desc "Parse incoming email on stdin (options: RECIPIENT=1.a1b2c3d3e5)"
task parse_reply_email: :environment do
hande_mail(ENV['RECIPIENT'], STDIN.read)
end
desc "Start STMP server for incoming email (options: SMTP_SERVER_PORT=2525, SMTP_SERVER_HOST=127.0.0.1)"
task :smtp_server => :environment do
port = (ENV['SMTP_SERVER_PORT'] || 2525).to_i
host = ENV['SMTP_SERVER_HOST'] || '127.0.0.1'
rake_say "Started SMTP server for incoming email on #{host}:#{port}."
server = ReplyEmailSmtpServer.new(port, host)
server.start
server.join
end
end
# Helper
def rake_say(message)
puts message unless Rake.application.options.silent
end
Loading…
Cancel
Save