Add document file preview generation with LibreOffice [SCI-3145]

This commit is contained in:
Oleksii Kriuchykhin 2019-06-05 12:52:43 +02:00
parent b94cab6e37
commit e543a849d4
12 changed files with 113 additions and 32 deletions

View file

@ -1,8 +1,11 @@
FROM ruby:2.5.5
MAINTAINER BioSistemika <info@biosistemika.com>
RUN echo deb "http://http.debian.net/debian stretch-backports main" >> /etc/apt/sources.list
# additional dependecies
# libSSL-1.0 is required by wkhtmltopdf binary
# libreoffice for file preview generation
RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - && \
apt-get update -qq && \
apt-get install -y \
@ -14,6 +17,7 @@ RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - && \
unison \
sudo graphviz --no-install-recommends \
libfile-mimeinfo-perl && \
apt-get install -y --no-install-recommends -t stretch-backports libreoffice && \
npm install -g yarn && \
rm -rf /var/lib/apt/lists/*

View file

@ -1,8 +1,11 @@
FROM ruby:2.5.5
MAINTAINER BioSistemika <info@biosistemika.com>
RUN echo deb "http://http.debian.net/debian stretch-backports main" >> /etc/apt/sources.list
# additional dependecies
# libSSL-1.0 is required by wkhtmltopdf binary
# libreoffice for file preview generation
RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - && \
apt-get update -qq && \
apt-get install -y \
@ -16,6 +19,7 @@ RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - && \
default-jre-headless \
sudo graphviz --no-install-recommends \
libfile-mimeinfo-perl && \
apt-get install -y --no-install-recommends -t stretch-backports libreoffice && \
npm install -g yarn && \
rm -rf /var/lib/apt/lists/*

View file

@ -79,8 +79,8 @@ gem 'underscore-rails'
gem 'wicked_pdf', '~> 1.1.0'
gem 'wkhtmltopdf-heroku'
gem 'aws-sdk', '~> 2'
gem 'paperclip', '~> 5.3' # File attachment, image attachment library
gem 'aws-sdk-s3'
gem 'paperclip', '~> 6.1' # File attachment, image attachment library
gem 'delayed_job_active_record'
gem 'devise-async',
git: 'https://github.com/mhfs/devise-async.git',

View file

@ -102,13 +102,19 @@ GEM
rails (>= 3.1)
awesome_print (1.8.0)
aws-eventstream (1.0.3)
aws-sdk (2.11.264)
aws-sdk-resources (= 2.11.264)
aws-sdk-core (2.11.264)
aws-sigv4 (~> 1.0)
aws-partitions (1.172.0)
aws-sdk-core (3.54.2)
aws-eventstream (~> 1.0, >= 1.0.2)
aws-partitions (~> 1.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-resources (2.11.264)
aws-sdk-core (= 2.11.264)
aws-sdk-kms (1.21.0)
aws-sdk-core (~> 3, >= 3.53.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.42.0)
aws-sdk-core (~> 3, >= 3.53.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
aws-sigv4 (1.1.0)
aws-eventstream (~> 1.0, >= 1.0.2)
backports (3.14.0)
@ -334,7 +340,7 @@ GEM
overcommit (0.47.0)
childprocess (~> 0.6, >= 0.6.3)
iniparse (~> 1.4)
paperclip (5.3.0)
paperclip (6.1.0)
activemodel (>= 4.2.0)
activesupport (>= 4.2.0)
mime-types
@ -550,7 +556,7 @@ DEPENDENCIES
auto_strip_attributes (~> 2.1)
autosize-rails
awesome_print
aws-sdk (~> 2)
aws-sdk-s3
base62
bcrypt (~> 3.1.10)
better_errors
@ -599,7 +605,7 @@ DEPENDENCIES
omniauth
omniauth-linkedin-oauth2
overcommit
paperclip (~> 5.3)
paperclip (~> 6.1)
pg (~> 0.18)
pg_search
phantomjs

View file

@ -10,16 +10,34 @@ class Asset < ApplicationRecord
# Paperclip validation
has_attached_file :file,
styles: {
large: [Constants::LARGE_PIC_FORMAT, :jpg],
medium: [Constants::MEDIUM_PIC_FORMAT, :jpg],
original: { processors: [:image_quality_calculate] }
styles: lambda { |a|
if a.previewable_document?
{
large: { processors: [:custom_file_preview],
geometry: Constants::LARGE_PIC_FORMAT,
format: :jpg },
medium: { processors: [:custom_file_preview],
geometry: Constants::MEDIUM_PIC_FORMAT,
format: :jpg }
}
else
{
large: [Constants::LARGE_PIC_FORMAT, :jpg],
medium: [Constants::MEDIUM_PIC_FORMAT, :jpg],
original: { processors: [:image_quality_calculate] }
}
end
},
convert_options: {
medium: '-quality 70 -strip',
all: '-background "#d2d2d2" -flatten +matte'
}
before_post_process :previewable?
# adds image processing in background job
process_in_background :file, processing_image_url: '/images/:style/processing.gif'
validates_attachment :file,
presence: true,
size: {
@ -31,20 +49,6 @@ class Asset < ApplicationRecord
# Should be checked for any security leaks
do_not_validate_attachment_file_type :file
# adds image processing in background job
process_in_background :file,
only_process: lambda { |a|
if a.content_type ==
%r{^image/#{ Regexp.union(
Constants::WHITELISTED_IMAGE_TYPES
) }}
%i(large medium original)
else
{}
end
},
processing_image_url: '/images/:style/processing.gif'
# Asset validation
# This could cause some problems if you create empty asset and want to
# assign it to result
@ -196,6 +200,10 @@ class Asset < ApplicationRecord
end
end
def previewable?
file.previewable_image? || file.previewable_document?
end
def is_image?
%r{^image/#{Regexp.union(Constants::WHITELISTED_IMAGE_TYPES)}} ===
file.content_type

View file

@ -1,4 +1,8 @@
<div>
<i class="fas fa-10x <%= file_fa_icon_class(asset) %>"></i>
<% if asset.file.previewable_document? %>
<%= image_tag(asset.url(:large)) %>
<% else %>
<i class="fas fa-10x <%= file_fa_icon_class(asset) %>"></i>
<% end %>
<h3 class="file-name"></h3>
</div>

View file

@ -1,4 +1,4 @@
<% if asset.file.processing? && asset.is_image? %>
<% if asset.file.processing? && (asset.is_image? || asset.file.previewable_document?) %>
<% asset_status = 'asset-loading' %>
<% present_url = step_file_present_asset_path(asset.id) %>
<% else %>

View file

@ -1,4 +1,4 @@
<% image_preview = asset.is_image? ? asset.url(:medium) : nil %>
<% image_preview = (asset.is_image? || asset.file.previewable_document?) ? asset.url(:medium) : nil %>
<div class="attachment-placeholder pull-left">
<div class="attachment-thumbnail <%= image_preview ? '' : 'no-shadow' %>">

View file

@ -218,6 +218,8 @@ class Constants
'text/plain'
].freeze
PREVIEWABLE_FILE_TYPES = TEXT_EXTRACT_FILE_TYPES
WHITELISTED_IMAGE_TYPES = [
'gif', 'jpeg', 'pjpeg', 'png', 'x-png', 'svg+xml', 'bmp', 'tiff'
].freeze

View file

@ -61,6 +61,27 @@ Paperclip::Attachment.class_eval do
def fetch
Paperclip.io_adapters.for self
end
def previewable_document?
previewable = Constants::PREVIEWABLE_FILE_TYPES.include?(content_type)
extensions = %w(.xlsx .docx .pptx .xls .doc .ppt)
# Mimetype sometimes recognizes Office files as zip files
# In this case we also check the extension of the given file
# Otherwise the conversion should fail if the file is being something else
previewable ||= (content_type == 'application/zip' && extensions.include?(File.extname(original_filename)))
# Mimetype also sometimes recognizes '.xls' and '.ppt' files as
# application/x-ole-storage (https://github.com/minad/mimemagic/issues/50)
previewable ||=
(content_type == 'application/x-ole-storage' && %w(.xls .ppt).include?(File.extname(original_filename)))
previewable
end
def previewable_image?
content_type == %r{^image/#{Regexp.union(Constants::WHITELISTED_IMAGE_TYPES)}}
end
end
module Paperclip

View file

@ -0,0 +1,32 @@
# frozen_string_literal: true
module Paperclip
class CustomFilePreview < Processor
def make
libreoffice_path = ENV['LIBREOFFICE_PATH'] || 'libreoffice'
directory = File.dirname(@file.path)
basename = File.basename(@file.path, '.*')
original_preview_file = File.join(directory, "#{basename}.png")
dst = TempfileFactory.new.generate("#{basename}.#{options[:format]}")
begin
Paperclip.run(
libreoffice_path,
"--headless --invisible --convert-to png --outdir #{directory} #{@file.path}"
)
convert(
":source -resize '#{options[:geometry]}' -format #{options[:format]} #{options[:convert_options]} :dest",
source: File.expand_path(original_preview_file),
dest: File.expand_path(dst.path)
)
ensure
File.delete(original_preview_file) if File.file?(original_preview_file)
end
dst
rescue StandardError => e
raise Paperclip::Error, "There was an error generating document preview - #{e}"
end
end
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB