Skip to content

Instantly share code, notes, and snippets.

@alabamaair
Last active June 17, 2016 16:28
Show Gist options
  • Save alabamaair/8abd7a233268d141396b28aafdcba9f3 to your computer and use it in GitHub Desktop.
Save alabamaair/8abd7a233268d141396b28aafdcba9f3 to your computer and use it in GitHub Desktop.
module Spree
class PropertiesImport < ActiveRecord::Base
include XxHashUsage
require 'translit'
require 'rubygems'
require 'zip'
require 'fileutils'
has_attached_file :property_file,
:path => ':rails_root/public/properties_imports/:id_:basename.:extension',
:url => '/attachment/:id/download'
do_not_validate_attachment_file_type :property_file
def start_import
return if self.state != 'pending' and self.state != 'processing'
self.message = 'Распаковка архива...'
self.state = :processing
save
errors_list = []
products_total = 0
products_updated = 0
products_not_found = 0
content = []
logger.properties_import.info '[IMPORT] extract files started'
dest_dir = File.dirname(property_file.path) #папка, куда всё сохраняется
file_name_xml = File.basename(property_file.path, File.extname(property_file.path)) #имя архива без расширения
dest_file = "#{dest_dir}/#{file_name_xml}.xml" #полный путь до xml-файла вместе с именем
Zip::File.open(property_file.path) do |zip_file|
zip_file.each do |entry|
entry.extract("#{dest_dir}/#{entry.name}"){true}
#xml-файл сливается в новый dest_file
if entry.name.downcase =~ /.xml/
content << entry.get_input_stream.read.force_encoding('utf-8')
File.open(dest_file, "a"){ |somefile| somefile.puts content}
end
end
end
if File.exist?(dest_file)
self.message = 'Обрабатывается...'
self.state = :processing
save
logger.properties_import.info "[IMPORT] files extracted success."
logger.properties_import.info '[IMPORT] started'
file = File.open(dest_file) #открываем полученный xml-файл
node_set = Nokogiri::XML.parse(file)
#если xml валиден начинаем импорт
if node_set.errors.empty?
node_set.remove_namespaces!
#парсим вендор-код из файла
node_set.xpath('//offer').each do |product_set|
products_total +=1
vc = product_set.xpath('vendorCode').text
#выбираем товар из БД
unless vc.nil?
products = Spree::Product.joins(:properties).where("#{Spree::ProductProperty.table_name}.value LIKE ? OR #{Spree::ProductProperty.table_name}.value LIKE ?", vc, "#{vc},%").where(Spree::Product.property_conditions('partnomer'))
#если по одному коду найдено несколько товаров, то не обновляем их свойства
if products.count > 1
#errors_list << {:error => "По вендорКоду #{vc} найдено несколько товаров", :id => vc} #просто пишем в лог, слишком много ошибок
logger.properties_import.info "[WARNING] Under one #{vc} found multiple products"
else
#выбираем товар по партномеру
product = products.first
if product.nil?
products_not_found += 1
#errors_list << {:error => "По вендорКоду #{vc} не найдено товаров", :id => vc} # просто пишем в лог, слишком много ошибок
logger.properties_import.info "[WARNING] Product not found: #{vc}"
else
#парсим и обновляем свойства выбранного товара
product_set.xpath('param').each do |param_element|
param_presentation = param_element['name']
param_name = string_transliterate2(param_element['name'])
param_value = param_element.text
unless param_value.blank?
product.set_property(param_name,param_value,param_presentation)
if product.save
products_updated +=1
self.updated_product_ids_will_change!
self.updated_product_ids << product.id
logger.properties_import.info "[INFO] Product properties saved: #{vc}"
else
errors_list << {:error => "Товар не сохранен: \n#{product.errors_list.messages.to_a.join("\n")}", :id => vc}
end
end
end
#парсим и добавляем картинку
product_set.xpath('picture').each do |picture_element|
localpath = picture_element.text.sub(/^https?:\/\/(www.)?[a-zA-Z0-9_-]*\.(\w*)/,'')
fullpath = "#{dest_dir}#{localpath}"
#проверяем наличие файла
unless File.exists?(fullpath) && File.readable?(fullpath)
logger.properties_import.info "[WARNING] Image not found: #{fullpath}" #нужно ли это в логе?
else
#если файл найден, добавляем его к продукту (если такой же уже не добавлен)
fileimage = File.open(fullpath, 'rb')
newimage = product.images.new(:attachment => fileimage, :alt => product.name)
newname = newimage.attachment_file_name
newsize = newimage.attachment_file_size
checkimage = product.images.find_by(attachment_file_name: newname, attachment_file_size: newsize)
if checkimage.nil?
newimage.save
products_updated +=1
self.updated_product_ids_will_change!
self.updated_product_ids << product.id
logger.properties_import.info "[INFO] Product image saved: #{vc}"
else
logger.properties_import.info "[INFO] Product image already uploaded: #{vc}"
end
end
end
#конец парсинга и добавления картинки
end
end
end
end
if errors_list.any?
self.message = "Ошибки: #{errors_list.join("\n")}"
self.state = :failure
save
logger.properties_import.error "[ERROR] #{self.message}"
else
self.message = "Обработано успешно. Всего продуктов в файле: #{products_total}. Обновлено свойств: #{products_updated}."
self.state = :success
self.endtime = DateTime.now
save
logger.properties_import.info "[SUCCESS] #{self.message}"
end
else
self.message = 'Неверный формат файла xml, импорт невозможен'
self.state = :failure
save
logger.properties_import.error "[ERROR] #{self.message}"
end
#уборка распакованного (в dest_dir удаляем images и все xml)
Dir.chdir(dest_dir)
FileUtils.rm_rf('images')
Dir.glob('*.xml').each { |filename| File.delete(filename) }
logger.properties_import.info "[INFO] unzipped files has been deleted"
else
self.message = 'Не получен xml-файл, импорт невозможен'
self.state = :failure
save
logger.properties_import.error "[ERROR] #{self.message}"
end
end
def string_transliterate2(str)
Translit.convert(str, :english)
.parameterize('_')
.to_s
end
end
end
module Spree
module Admin
class PropertiesImportsController < BaseController
require 'nokogiri'
def show
@properties_import = Spree::PropertiesImport.find(params[:id])
if params[:filter]
case params[:filter]
when 'updated_products'
@header = 'Список обновленных товаров'
@collection = Spree::Product.where id: @properties_import.updated_product_ids
else
set_header_and_collection_by_default
end
else
set_header_and_collection_by_default
end
@collection = Kaminari.paginate_array(@collection).page(params[:page]).per(100)
render 'spree/admin/shared/processed_products'
end
def index
redirect_to :action => :new
end
def new
@properties_import = Spree::PropertiesImport.new
@properties_list = Spree::PropertiesImport.all.order(updated_at: :desc, created_at: :desc)
end
def create
begin
@properties_import = Spree::PropertiesImport.create(properties_import_params)
@properties_import.message = 'Ожидает обработки'
@properties_import.state = :pending
@properties_import.save
unless @properties_import.blank?
if @properties_import.property_file.blank?
@properties_import.destroy
flash[:notice] = 'No file'
else
content_type = @properties_import.property_file.content_type
if content_type == 'application/zip'
# удаление старых файлов
file_count = Spree::PropertiesImport.all.where(:state => :success).count
if file_count > 5
products_list = Spree::PropertiesImport.all.where(:state => :success).order(updated_at: :desc, created_at: :desc).offset(5)
products_list.each do |file|
file.destroy
end
end
flash[:notice] = 'File saved'
@properties_import.delay(:queue => 'properties_import').start_import
else
@properties_import.destroy
flash[:notice] = 'File must be in zip format'
end
end
end
rescue
flash[:notice] = 'No file. Add file and try again'
end
redirect_to :action => :new
end
private
def properties_import_params
params.require(:properties_import).permit(:property_file, :product_ids, :state, :endtime)
end
def set_header_and_collection_by_default
@header = 'Список обновленных товаров'
@collection = Spree::Product.where id: @properties_import.updated_product_ids
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment