Skip to content

Commit

Permalink
optimize for large files
Browse files Browse the repository at this point in the history
  • Loading branch information
omohokcoj committed Oct 22, 2023
1 parent c639a3f commit ce39494
Show file tree
Hide file tree
Showing 19 changed files with 206 additions and 32 deletions.
3 changes: 3 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ RSpec/MultipleMemoizedHelpers:
Rails/I18nLocaleTexts:
Enabled: false

Rails/FindEach:
Enabled: false

Rails/SkipsModelValidations:
Enabled: false

Expand Down
1 change: 1 addition & 0 deletions app/controllers/api/templates_documents_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def create
render json: {
schema:,
documents: documents.as_json(
methods: [:metadata],
include: {
preview_images: { methods: %i[url metadata filename] }
}
Expand Down
52 changes: 52 additions & 0 deletions app/controllers/preview_document_page_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# frozen_string_literal: true

class PreviewDocumentPageController < ActionController::API
include ActiveStorage::SetCurrent

FORMAT = Templates::ProcessDocument::FORMAT

def show
if Docuseal.multitenant?
Rollbar.warning('load page')

return head :not_found
end

attachment = ActiveStorage::Attachment.find_by(uuid: params[:attachment_uuid])

return head :not_found unless attachment

preview_image = attachment.preview_images.joins(:blob).find_by(blob: { filename: "#{params[:id]}#{FORMAT}" })

return redirect_to preview_image.url, allow_other_host: true if preview_image

file_path =
if attachment.service.name == :disk
ActiveStorage::Blob.service.path_for(attachment.key)
else
find_or_create_document_tempfile_path(attachment)
end

io = Templates::ProcessDocument.generate_pdf_preview_from_file(attachment, file_path, params[:id].to_i)

render plain: io.tap(&:rewind).read
end

def find_or_create_document_tempfile_path(attachment)
file_path = "#{Dir.tmpdir}/#{attachment.uuid}"

File.open(file_path, File::RDWR | File::CREAT, 0o644) do |f|
f.flock(File::LOCK_EX)

# rubocop:disable Style/ZeroLengthPredicate
if f.size.zero?
f.binmode

f.write(attachment.download)
end
# rubocop:enable Style/ZeroLengthPredicate
end

file_path
end
end
14 changes: 13 additions & 1 deletion app/controllers/submissions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,24 @@ class SubmissionsController < ApplicationController

load_and_authorize_resource :submission, only: %i[show destroy]

PRELOAD_ALL_PAGES_AMOUNT = 200

def show
ActiveRecord::Associations::Preloader.new(
records: [@submission],
associations: [:template, { template_schema_documents: [:blob, { preview_images_attachments: :blob }] }]
associations: [:template, { template_schema_documents: :blob }]
).call

total_pages =
@submission.template_schema_documents.sum { |e| e.metadata.dig('pdf', 'number_of_pages').to_i }

if total_pages < PRELOAD_ALL_PAGES_AMOUNT
ActiveRecord::Associations::Preloader.new(
records: @submission.template_schema_documents,
associations: [:blob, { preview_images_attachments: :blob }]
).call
end

render :show, layout: 'plain'
end

Expand Down
23 changes: 18 additions & 5 deletions app/controllers/submit_form_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,28 @@ class SubmitFormController < ApplicationController
skip_before_action :authenticate_user!
skip_authorization_check

PRELOAD_ALL_PAGES_AMOUNT = 200

def show
@submitter =
Submitter.preload(submission: [
:template, { template_schema_documents: [:blob, { preview_images_attachments: :blob }] }
])
.find_by!(slug: params[:slug])
@submitter = Submitter.find_by!(slug: params[:slug])

return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at?

ActiveRecord::Associations::Preloader.new(
records: [@submitter],
associations: [submission: [:template, { template_schema_documents: :blob }]]
).call

total_pages =
@submitter.submission.template_schema_documents.sum { |e| e.metadata.dig('pdf', 'number_of_pages').to_i }

if total_pages < PRELOAD_ALL_PAGES_AMOUNT
ActiveRecord::Associations::Preloader.new(
records: @submitter.submission.template_schema_documents,
associations: [:blob, { preview_images_attachments: :blob }]
).call
end

Submitters::MaybeUpdateDefaultValues.call(@submitter, current_user)

cookies[:submitter_sid] = @submitter.signed_id
Expand Down
8 changes: 8 additions & 0 deletions app/controllers/templates_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ def edit
associations: [schema_documents: { preview_images_attachments: :blob }]
).call

@template_data =
@template.as_json.merge(
documents: @template.schema_documents.as_json(
methods: [:metadata],
include: { preview_images: { methods: %i[url metadata filename] } }
)
).to_json

render :edit, layout: 'plain'
end

Expand Down
20 changes: 19 additions & 1 deletion app/javascript/template_builder/document.vue
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,26 @@ export default {
}
},
computed: {
numberOfPages () {
return this.document.metadata?.pdf?.number_of_pages || this.document.preview_images.length
},
sortedPreviewImages () {
return [...this.document.preview_images].sort((a, b) => parseInt(a.filename) - parseInt(b.filename))
const lazyloadMetadata = this.document.preview_images[this.document.preview_images.length - 1].metadata
return [...Array(this.numberOfPages).keys()].map((i) => {
return this.previewImagesIndex[i] || {
metadata: lazyloadMetadata,
id: Math.random().toString(),
url: `/preview/${this.document.uuid}/${i}.jpg`
}
})
},
previewImagesIndex () {
return this.document.preview_images.reduce((acc, e) => {
acc[parseInt(e.filename)] = e
return acc
}, {})
}
},
beforeUpdate () {
Expand Down
9 changes: 7 additions & 2 deletions app/javascript/template_builder/page.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
>
<img
ref="image"
loading="lazy"
:src="image.url"
:width="width"
class="border rounded mb-4"
:height="height"
loading="lazy"
class="border rounded mb-4"
@load="onImageLoad"
>
<div
class="top-0 bottom-0 left-0 right-0 absolute"
Expand Down Expand Up @@ -117,6 +118,10 @@ export default {
this.areaRefs = []
},
methods: {
onImageLoad (e) {
e.target.setAttribute('width', e.target.naturalWidth)
e.target.setAttribute('height', e.target.naturalHeight)
},
setAreaRefs (el) {
if (el) {
this.areaRefs.push(el)
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/template_builder/preview.vue
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export default {
emits: ['scroll-to', 'change', 'remove', 'up', 'down', 'replace'],
computed: {
previewImage () {
return this.document.preview_images[0]
return [...this.document.preview_images].sort((a, b) => parseInt(a.filename) - parseInt(b.filename))[0]
}
},
mounted () {
Expand Down
22 changes: 18 additions & 4 deletions app/mailers/submitter_mailer.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# frozen_string_literal: true

class SubmitterMailer < ApplicationMailer
MAX_ATTACHMENTS_SIZE = 10.megabytes

def invitation_email(submitter, body: nil, subject: nil)
@current_account = submitter.submission.template.account
@submitter = submitter
Expand Down Expand Up @@ -75,14 +77,26 @@ def documents_copy_email(submitter, to: nil)
def add_completed_email_attachments!(submitter)
documents = Submitters.select_attachments_for_download(submitter)

documents.each do |attachment|
attachments[attachment.filename.to_s] = attachment.download
end
total_size = 0
audit_trail_data = nil

if submitter.submission.audit_trail.present?
attachments[submitter.submission.audit_trail.filename.to_s] = submitter.submission.audit_trail.download
audit_trail_data = submitter.submission.audit_trail.download

total_size = audit_trail_data.size
end

documents.each do |attachment|
data = attachment.download
total_size += data.size

break if total_size >= MAX_ATTACHMENTS_SIZE

attachments[attachment.filename.to_s] = data
end

attachments[submitter.submission.audit_trail.filename.to_s] = audit_trail_data if audit_trail_data

documents
end

Expand Down
2 changes: 1 addition & 1 deletion app/views/start_form/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
</div>
</div>
<% unless @template.deleted_at? %>
<%= form_for @submitter, url: start_form_path(@template.slug), data: { turbo_frame: :_top }, method: :put, html: { class: 'space-y-4' } do |f| %>
<%= form_for @submitter, url: start_form_path(@template.slug), data: { turbo_frame: :_top }, method: :put, html: { class: 'space-y-4', onsubmit: 'event.submitter.disabled = true' } do |f| %>
<div class="form-control !mt-0">
<%= f.label :email, class: 'label' %>
<%= f.email_field :email, value: current_user&.email, required: true, class: 'base-input', placeholder: 'Provide your email to start' %>
Expand Down
8 changes: 6 additions & 2 deletions app/views/submissions/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,16 @@
<% fields_index = Templates.build_field_areas_index(@submission.template_fields || @submission.template.fields) %>
<% values = @submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %>
<% attachments_index = ActiveStorage::Attachment.where(record: @submission.submitters, name: :attachments).preload(:blob).index_by(&:uuid) %>
<% page_blob_struct = Struct.new(:url, :metadata, keyword_init: true) %>
<% (@submission.template_schema || @submission.template.schema).each do |item| %>
<% document = @submission.template_schema_documents.find { |e| e.uuid == item['attachment_uuid'] } %>
<% document_annots_index = document.metadata.dig('pdf', 'annotations')&.group_by { |e| e['page'] } || {} %>
<% document.preview_images.sort_by { |a| a.filename.base.to_i }.each_with_index do |page, index| %>
<% preview_images_index = document.preview_images.loaded? ? document.preview_images.index_by { |e| e.filename.base.to_i } : {} %>
<% lazyload_metadata = document.preview_images.first.metadata %>
<% (document.metadata.dig('pdf', 'number_of_pages') || (document.preview_images.loaded? ? preview_images_index.size : document.preview_images.size)).times do |index| %>
<% page = preview_images_index[index] || page_blob_struct.new(metadata: lazyload_metadata, url: preview_document_page_path(document.uuid, "#{index}.jpg")) %>
<div id="<%= "page-#{document.uuid}-#{index}" %>" class="relative">
<img src="<%= page.url %>" width="<%= page.metadata['width'] %>" class="shadow-md mb-4" height="<%= page.metadata['height'] %>" loading="lazy">
<img loading="lazy" src="<%= page.url %>" width="<%= page.metadata['width'] %>" class="shadow-md mb-4" height="<%= page.metadata['height'] %>">
<div class="top-0 bottom-0 left-0 right-0 absolute">
<% document_annots_index[index]&.each do |annot| %>
<%= render 'submissions/annotation', annot: %>
Expand Down
8 changes: 6 additions & 2 deletions app/views/submit_form/show.html.erb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<% fields_index = Templates.build_field_areas_index(@submitter.submission.template_fields || @submitter.submission.template.fields) %>
<% values = @submitter.submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %>
<% attachments_index = ActiveStorage::Attachment.where(record: @submitter.submission.submitters, name: :attachments).preload(:blob).index_by(&:uuid) %>
<% page_blob_struct = Struct.new(:url, :metadata, keyword_init: true) %>
<div style="max-height: -webkit-fill-available;">
<div id="scrollbox">
<div class="mx-auto block pb-72" style="max-width: 1000px">
Expand All @@ -10,9 +11,12 @@
<% (@submitter.submission.template_schema || @submitter.submission.template.schema).each do |item| %>
<% document = @submitter.submission.template_schema_documents.find { |a| a.uuid == item['attachment_uuid'] } %>
<% document_annots_index = document.metadata.dig('pdf', 'annotations')&.group_by { |e| e['page'] } || {} %>
<% document.preview_images.sort_by { |a| a.filename.base.to_i }.each_with_index do |page, index| %>
<% preview_images_index = document.preview_images.loaded? ? document.preview_images.index_by { |e| e.filename.base.to_i } : {} %>
<% lazyload_metadata = document.preview_images.last.metadata %>
<% (document.metadata.dig('pdf', 'number_of_pages') || (document.preview_images.loaded? ? preview_images_index.size : document.preview_images.size)).times do |index| %>
<% page = preview_images_index[index] || page_blob_struct.new(metadata: lazyload_metadata, url: preview_document_page_path(document.uuid, "#{index}.jpg")) %>
<div class="relative my-4 shadow-md">
<img src="<%= page.url %>" width="<%= page.metadata['width'] %>" height="<%= page.metadata['height'] %>" loading="lazy">
<img loading="lazy" src="<%= page.url %>" width="<%= page.metadata['width'] %>" height="<%= page.metadata['height'] %>">
<div id="page-<%= [document.uuid, index].join('-') %>" class="top-0 bottom-0 left-0 right-0 absolute">
<% document_annots_index[index]&.each do |annot| %>
<%= render 'submissions/annotation', annot: %>
Expand Down
2 changes: 1 addition & 1 deletion app/views/templates/edit.html.erb
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<template-builder class="grid" data-is-direct-upload="<%= Docuseal.active_storage_public? %>" data-template="<%= @template.as_json.merge(documents: @template.schema_documents.as_json(include: { preview_images: { methods: %i[url metadata filename] } })).to_json %>"></template-builder>
<template-builder class="grid" data-is-direct-upload="<%= Docuseal.active_storage_public? %>" data-template="<%= @template_data %>"></template-builder>
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
resource :folder, only: %i[edit update], controller: 'templates_folders'
resources :submissions_export, only: %i[index new]
end
resources :preview_document_page, only: %i[show], path: '/preview/:attachment_uuid'

resources :start_form, only: %i[show update], path: 'd', param: 'slug' do
get :completed
Expand Down
2 changes: 1 addition & 1 deletion lib/submissions/ensure_result_generated.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Submissions
module EnsureResultGenerated
WAIT_FOR_RETRY = 2.seconds
CHECK_EVENT_INTERVAL = 1.second
CHECK_COMPLETE_TIMEOUT = 20.seconds
CHECK_COMPLETE_TIMEOUT = 90.seconds

WaitForCompleteTimeout = Class.new(StandardError)

Expand Down
16 changes: 9 additions & 7 deletions lib/templates/clone_attachments.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ def call(template:, original_template:)
record: template
)

document.preview_images_attachments.each do |preview_image|
ActiveStorage::Attachment.create!(
uuid: preview_image.uuid,
blob_id: preview_image.blob_id,
name: 'preview_images',
record: new_document
)
ApplicationRecord.no_touching do
document.preview_images_attachments.each do |preview_image|
ActiveStorage::Attachment.create!(
uuid: preview_image.uuid,
blob_id: preview_image.blob_id,
name: 'preview_images',
record: new_document
)
end
end
end
end
Expand Down
9 changes: 7 additions & 2 deletions lib/templates/create_attachments.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
module Templates
module CreateAttachments
PDF_CONTENT_TYPE = 'application/pdf'
ANNOTATIONS_SIZE_LIMIT = 6.megabytes
InvalidFileType = Class.new(StandardError)

module_function
Expand All @@ -18,7 +19,10 @@ def call(template, params)
document = template.documents.create!(blob:)

if blob.content_type == PDF_CONTENT_TYPE && blob.metadata['pdf'].nil?
blob.metadata['pdf'] = { 'annotations' => Templates::BuildAnnotations.call(document_data) }
annotations =
document_data.size > ANNOTATIONS_SIZE_LIMIT ? [] : Templates::BuildAnnotations.call(document_data)

blob.metadata['pdf'] = { 'annotations' => annotations }
blob.metadata['sha256'] = Base64.urlsafe_encode64(Digest::SHA256.digest(document_data))
end

Expand All @@ -37,9 +41,10 @@ def find_or_create_blobs(params)
data = file.read

if file.content_type == PDF_CONTENT_TYPE
annotations = data.size > ANNOTATIONS_SIZE_LIMIT ? [] : Templates::BuildAnnotations.call(data)
metadata = { 'identified' => true, 'analyzed' => true,
'sha256' => Base64.urlsafe_encode64(Digest::SHA256.digest(data)),
'pdf' => { 'annotations' => Templates::BuildAnnotations.call(data) } }
'pdf' => { 'annotations' => annotations } }
end

ActiveStorage::Blob.create_and_upload!(
Expand Down
Loading

0 comments on commit ce39494

Please sign in to comment.