diff --git a/Dockerfile b/Dockerfile index 0eb255048..e46b0864d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,11 @@ FROM ruby:2.5.5 MAINTAINER BioSistemika +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/* diff --git a/Dockerfile.production b/Dockerfile.production index 3dc59885e..ca922f441 100644 --- a/Dockerfile.production +++ b/Dockerfile.production @@ -1,8 +1,11 @@ FROM ruby:2.5.5 MAINTAINER BioSistemika +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/* diff --git a/Gemfile b/Gemfile index 0d04ba489..b82a4042d 100644 --- a/Gemfile +++ b/Gemfile @@ -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', diff --git a/Gemfile.lock b/Gemfile.lock index cc6ec962c..d54892eb6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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 diff --git a/app/models/asset.rb b/app/models/asset.rb index 9de1922f8..407a75889 100644 --- a/app/models/asset.rb +++ b/app/models/asset.rb @@ -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 diff --git a/app/views/shared/_file_preview_icon.html.erb b/app/views/shared/_file_preview_icon.html.erb index 337f8969e..a37b5e60e 100644 --- a/app/views/shared/_file_preview_icon.html.erb +++ b/app/views/shared/_file_preview_icon.html.erb @@ -1,4 +1,8 @@
- + <% if asset.file.previewable_document? %> + <%= image_tag(asset.url(:large)) %> + <% else %> + + <% end %>

diff --git a/app/views/steps/attachments/_item.html.erb b/app/views/steps/attachments/_item.html.erb index fe01bf3c3..08c1be4b3 100644 --- a/app/views/steps/attachments/_item.html.erb +++ b/app/views/steps/attachments/_item.html.erb @@ -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 %> diff --git a/app/views/steps/attachments/_placeholder.html.erb b/app/views/steps/attachments/_placeholder.html.erb index ea845e888..c5c079c52 100644 --- a/app/views/steps/attachments/_placeholder.html.erb +++ b/app/views/steps/attachments/_placeholder.html.erb @@ -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 %>
diff --git a/config/initializers/constants.rb b/config/initializers/constants.rb index a8505c532..afdf18be1 100644 --- a/config/initializers/constants.rb +++ b/config/initializers/constants.rb @@ -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 diff --git a/config/initializers/paperclip.rb b/config/initializers/paperclip.rb index f032d32ca..868ff2c55 100644 --- a/config/initializers/paperclip.rb +++ b/config/initializers/paperclip.rb @@ -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 diff --git a/lib/paperclip_processors/custom_file_preview.rb b/lib/paperclip_processors/custom_file_preview.rb new file mode 100644 index 000000000..256c4f261 --- /dev/null +++ b/lib/paperclip_processors/custom_file_preview.rb @@ -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 diff --git a/public/images/large/processing.gif b/public/images/large/processing.gif new file mode 100644 index 000000000..b58672fdf Binary files /dev/null and b/public/images/large/processing.gif differ