adjust preview cache

This commit is contained in:
Pete Matsyburka
2026-08-07 21:45:56 +03:00
parent 485b7f2c8f
commit eeec852b3b
3 changed files with 90 additions and 19 deletions
@@ -5,6 +5,10 @@ class PreviewDocumentPageController < ActionController::API
FORMAT = Templates::ProcessDocument::FORMAT
TMPFILE_PREFIX = 'attachment-'
TMPFILE_TTL = 5.minutes
TMPFILE_MAX_TOTAL_SIZE = Docuseal.multitenant? ? 400.megabytes : 1.gigabyte
def show
result_data =
ApplicationRecord.signed_id_verifier.verified(params[:signed_key], purpose: :attachment)
@@ -30,34 +34,63 @@ class PreviewDocumentPageController < ActionController::API
allow_other_host: true
end
file_path =
if attachment.service.name == :disk
ActiveStorage::Blob.service.path_for(attachment.key)
else
find_or_create_document_tempfile_path(attachment)
end
preview_image =
Templates::ProcessDocument.generate_pdf_preview_from_file(attachment, file_path, params[:id].to_i)
open_attachment_io(attachment) do |io|
Templates::ProcessDocument.generate_pdf_preview_from_io(attachment, io, params[:id].to_i)
end
redirect_to preview_image.url(time: ActiveStorage::Attachment.service_url_time), allow_other_host: true
end
def find_or_create_document_tempfile_path(attachment)
file_path = "#{Dir.tmpdir}/attachment-#{Digest::SHA1.hexdigest("#{attachment.id}-#{attachment.uuid}")}"
def open_attachment_io(attachment, &)
return File.open(ActiveStorage::Blob.service.path_for(attachment.key), 'rb', &) if attachment.service.name == :disk
File.open(file_path, File::RDWR | File::CREAT, 0o644) do |f|
f.flock(File::LOCK_EX)
file_name = "#{TMPFILE_PREFIX}#{Digest::SHA1.hexdigest("#{attachment.id}-#{attachment.uuid}")}"
file_path = File.join(Dir.tmpdir, file_name)
File.open(file_path, File::RDWR | File::CREAT, 0o644) do |file|
file.flock(File::LOCK_EX)
# rubocop:disable Style/ZeroLengthPredicate
if f.size.zero?
f.binmode
if file.size.zero?
cleanup_stale_tempfiles
f.write(attachment.download)
file.binmode
file.write(attachment.download)
else
FileUtils.touch(file_path)
end
# rubocop:enable Style/ZeroLengthPredicate
end
file_path
file.flock(File::LOCK_UN)
yield file
end
end
def cleanup_stale_tempfiles
entries =
Dir.glob(File.join(Dir.tmpdir, "#{TMPFILE_PREFIX}*")).filter_map do |path|
stat = File.stat(path)
[path, stat.mtime, stat.size]
rescue Errno::ENOENT
nil
end
total_size = entries.sum(&:last)
stale_time = TMPFILE_TTL.ago
entries.sort_by(&:second).each do |path, mtime, size|
break if mtime > stale_time && total_size <= TMPFILE_MAX_TOTAL_SIZE
File.unlink(path)
total_size -= size
rescue Errno::ENOENT
nil
end
end
end
+38
View File
@@ -98,6 +98,7 @@ class Pdfium
attach_function :FPDF_LoadDocument, %i[string FPDF_STRING], :FPDF_DOCUMENT
attach_function :FPDF_LoadMemDocument, %i[pointer int FPDF_STRING], :FPDF_DOCUMENT
attach_function :FPDF_LoadCustomDocument, %i[pointer FPDF_STRING], :FPDF_DOCUMENT
attach_function :FPDF_CloseDocument, [:FPDF_DOCUMENT], :void
attach_function :FPDF_GetPageCount, [:FPDF_DOCUMENT], :int
attach_function :FPDF_GetLastError, [], :ulong
@@ -463,6 +464,43 @@ class Pdfium
end
end
def self.open_io(io, password = nil)
io.binmode
get_block = FFI::Function.new(:int, %i[pointer ulong pointer ulong]) do |_param, position, out, size|
io.seek(position)
bytes = io.read(size).to_s
out.put_bytes(0, bytes)
bytes.bytesize == size ? 1 : 0
end
file_access = Pdfium::FPDF_FILEACCESS.new
file_access[:m_FileLen] = io.size
file_access[:m_GetBlock] = get_block
file_access[:m_Param] = FFI::Pointer::NULL
doc_ptr = Pdfium.FPDF_LoadCustomDocument(file_access, password)
if doc_ptr.null?
Pdfium.check_last_error('Failed to load document from IO')
raise PdfiumError, 'Failed to load document from IO, pointer is NULL.'
end
doc = new(doc_ptr, [file_access, get_block, io])
return doc unless block_given?
begin
yield doc
ensure
doc.close
end
end
def closed?
@closed
end
+2 -2
View File
@@ -192,8 +192,8 @@ module Templates
end
end
def generate_pdf_preview_from_file(attachment, file_path, page_number)
doc = Pdfium::Document.open_file(file_path)
def generate_pdf_preview_from_io(attachment, io, page_number)
doc = Pdfium::Document.open_io(io)
doc_page = doc.get_page(page_number)