Added pdf analyzer

Analyzer should be moved to ActiveStorage::Analyzer
First there are many bugs with ActiveStorage that needs to be fixed:
Files are not deleted if job is destroyed (dependent: :purge is set)
If a new file is uploaded the old file is not deleted
(has_one_attechment)
This commit is contained in:
2024-08-08 13:56:36 +02:00
parent 19cf60c9a9
commit 616bd0cbe7
7 changed files with 139 additions and 14 deletions

View File

@@ -71,3 +71,5 @@ group :test do
end
gem 'inline_svg', '~> 1.9'
gem "pdf-reader", "~> 2.12"

View File

@@ -1,6 +1,7 @@
GEM
remote: https://rubygems.org/
specs:
Ascii85 (1.1.1)
actioncable (7.1.3.4)
actionpack (= 7.1.3.4)
activesupport (= 7.1.3.4)
@@ -77,6 +78,7 @@ GEM
tzinfo (~> 2.0)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
afm (0.2.2)
base64 (0.2.0)
bigdecimal (3.1.8)
bindex (0.8.1)
@@ -105,6 +107,7 @@ GEM
i18n (>= 1.8.11, < 2)
globalid (1.2.1)
activesupport (>= 6.1)
hashery (2.1.2)
i18n (1.14.5)
concurrent-ruby (~> 1.0)
importmap-rails (2.0.1)
@@ -158,6 +161,12 @@ GEM
racc (~> 1.4)
nokogiri (1.16.7-x86_64-linux)
racc (~> 1.4)
pdf-reader (2.12.0)
Ascii85 (~> 1.0)
afm (~> 0.2.1)
hashery (~> 2.0)
ruby-rc4
ttfunk
psych (5.1.2)
stringio
public_suffix (6.0.1)
@@ -213,6 +222,7 @@ GEM
io-console (~> 0.5)
rexml (3.3.4)
strscan
ruby-rc4 (0.1.5)
rubyzip (2.3.2)
selenium-webdriver (4.23.0)
base64 (~> 0.2)
@@ -251,6 +261,8 @@ GEM
railties (>= 7.0.0)
thor (1.3.1)
timeout (0.4.1)
ttfunk (1.8.0)
bigdecimal (~> 3.1)
turbo-rails (2.0.6)
actionpack (>= 6.0.0)
activejob (>= 6.0.0)
@@ -287,6 +299,7 @@ DEPENDENCIES
importmap-rails
inline_svg (~> 1.9)
jbuilder
pdf-reader (~> 2.12)
puma (>= 5.0)
rails (~> 7.1.3, >= 7.1.3.4)
redis (>= 4.0.1)

View File

@@ -2,7 +2,7 @@ class Job < ApplicationRecord
belongs_to :operator, class_name: 'User', optional: true
belongs_to :costumer, class_name: 'User', optional: true
has_one_attached :pdf, dependent: :destroy
has_one_attached :pdf, dependent: :purge
validates_presence_of :costumer_firstname, :costumer_lastname, :privacy_policy_accepted, :pdf
validate :acceptable_pdf
@@ -11,6 +11,9 @@ class Job < ApplicationRecord
before_save :update_paid_at, if: :will_save_change_to_status?
before_save :update_status_changed_at, if: :will_save_change_to_status?
# TODO: works only when job is created. Should move analyzer to activestorage :https://discuss.rubyonrails.org/t/active-storage-in-production-lessons-learned-and-in-depth-look-at-how-it-works/83289
after_create_commit :analyze_pdf
# NOTE: Multiple status if paing before brinting?
enum status: {
open: 0,
@@ -53,12 +56,10 @@ class Job < ApplicationRecord
def acceptable_pdf
return unless pdf.attached?
errors.add(:pdf, 'is too big') unless pdf.blob.byte_size <= 100.megabyte
acceptable_types = ['application/pdf']
return if acceptable_types.include?(pdf.content_type)
errors.add(:pdf, 'must be a PDF')
errors.add(:pdf, 'is too big') unless pdf.blob.byte_size <= 100.megabyte
errors.add(:pdf, 'must be a PDF') unless acceptable_types.include?(pdf.content_type)
end
def able_to_cancel?
@@ -84,4 +85,29 @@ class Job < ApplicationRecord
def update_status_changed_at
self.status_changed_at = Time.now
end
def analyze_pdf
# return unless pdf.attached? && pdf.new_record?
# TODO: add any check if attachment has changed
# pdfs.each do |pdf|
pdf.blob.open do |file|
self.number_of_plans_a0 = 0
self.number_of_plans_a1 = 0
self.number_of_plans_a2 = 0
self.number_of_plans_a3 = 0
self.costum_qm_plan = 0
# file = ActiveStorage::Blob.service.path_for(pdf.key).to_s
pdf_analyzer = Services::PdfAnalyzer.new(file)
pdf_analyzer.analyze
self.number_of_plans_a0 += pdf_analyzer.pages_a0
self.number_of_plans_a1 += pdf_analyzer.pages_a1
self.number_of_plans_a2 += pdf_analyzer.pages_a2
self.number_of_plans_a3 += pdf_analyzer.pages_a3
self.costum_qm_plan += pdf_analyzer.costum_qm
save
end
# end
end
end

View File

@@ -5,13 +5,16 @@
<td class="p-3 text-sm text-hsrm-gray whitespace-nowrap"> <%= job.fullname %> </td>
<td class="p-3 text-sm text-hsrm-gray whitespace-nowrap">
<% if job.pdf.attached? %>
<%= job.pdf.filename %><span class="p-1.5 bg-gray-300 font-medium rounded-lg ml-2"><%=number_to_human_size job.pdf.blob.byte_size%></span>
<%#= link_to job.pdf.filename, rails_blob_path(job.pdf, disposition: "attachment") %>
<%= link_to job.pdf.filename, job.pdf, download:true %>
<span class="p-1.5 bg-gray-300 font-medium rounded-lg ml-2"><%=number_to_human_size job.pdf.blob.byte_size%></span>
<% end %>
</td>
<td class="p-3 text-sm text-left text-hsrm-gray whitespace-nowrap"><span class="p-1.5 bg-gray-300 bg-opacity-50 font-medium rounded-lg"><%= job.number_of_plans_a0 %></span></td>
<td class="p-3 text-sm text-left text-hsrm-gray whitespace-nowrap"><span class="p-1.5 bg-gray-300 bg-opacity-50 font-medium rounded-lg"><%= job.number_of_plans_a1 %></span></td>
<td class="p-3 text-sm text-left text-hsrm-gray whitespace-nowrap"><span class="p-1.5 bg-gray-300 bg-opacity-50 font-medium rounded-lg"><%= job.number_of_plans_a2 %></span></td>
<td class="p-3 text-sm text-left text-hsrm-gray whitespace-nowrap"><span class="p-1.5 bg-gray-300 bg-opacity-50 font-medium rounded-lg"><%= job.number_of_plans_a3 %></span></td>
<td class="p-3 text-sm text-left text-hsrm-gray whitespace-nowrap"><span class="p-1.5 bg-gray-300 bg-opacity-50 font-medium rounded-lg"><%= job.costum_qm_plan.round(2) %></span></td>
<td class="p-3 text-sm text-left text-hsrm-gray whitespace-nowrap">
<span class="p-1.5 text-xs font-medium uppercase tracking-wider bg-opacity-50 text-status-<%= job.status.to_sym %> bg-status-<%= job.status %>-light rounded-lg"><%= job.status %></span>
</td>

View File

@@ -17,6 +17,7 @@
<th class="p-3 text-sm font-semibold tracking-wide text-left w-1"> A1 </th>
<th class="p-3 text-sm font-semibold tracking-wide text-left w-1"> A2 </th>
<th class="p-3 text-sm font-semibold tracking-wide text-left w-1"> A3 </th>
<th class="p-3 text-sm font-semibold tracking-wide text-left w-1"> qm </th>
<th class="p-3 text-sm font-semibold tracking-wide text-left w-1"> Status </th>
<th class="p-3 text-sm font-semibold tracking-wide text-left w-1"> Actions </th>
</tr>

View File

@@ -17,18 +17,18 @@ end
['GanzWichtig.pdf', 'IchBinIn5MinDran.pdf', 'DerPlanDerImmerProblemeMacht.pdf',
'DieFarbenGefallenMirNicht.pdf', 'MachHinIchHabsEilig.pdf', 'WarumDauertDasSoLange.pdf',
'DenPlanBezahleIchNicht.pdf', 'IchWarAlsErstesDran.pdf', 'WarumIstDerPlotterDefekt.pdf', 'DasNächsteMalGeheIchWoAndersHin.pdf'].shuffle.each do |pdf|
a0 = rand(0...7)
a1 = rand(0...7)
a2 = rand(0...7)
a3 = rand(0...7)
a0.zero? || a1 = 0 && a2 = 0 && a3 = 0
a1.zero? || a2 = 0 && a3 = 0
a2.zero? || a3 = 0
# a0 = rand(0...7)
# a1 = rand(0...7)
# a2 = rand(0...7)
# a3 = rand(0...7)
# a0.zero? || a1 = 0 && a2 = 0 && a3 = 0
# a1.zero? || a2 = 0 && a3 = 0
# a2.zero? || a3 = 0
status = %i[open open open open open open open open open open open open open open
printing ready_for_pickup paid canceled].sample
job = Job.new(costumer_firstname: Faker::Name.unique.first_name, costumer_lastname: Faker::Name.unique.last_name,
number_of_plans_a0: a0, number_of_plans_a1: a1, number_of_plans_a2: a2, number_of_plans_a3: a3,
# number_of_plans_a0: a0, number_of_plans_a1: a1, number_of_plans_a2: a2, number_of_plans_a3: a3,
status:, privacy_policy_accepted: true)
job.pdf = File.open(Rails.root.join('db/pdfs/', pdf))
job.save!

View File

@@ -0,0 +1,80 @@
module Services
# Class to analyze PDF to extract number of pages from a0 to a3 and costum qm
class PdfAnalyzer
require 'pdf/reader'
require 'bigdecimal'
attr_reader :costum_qm, :pages_a0, :pages_a1, :pages_a2, :pages_a3
def initialize(file)
@reader = PDF::Reader.new(file)
@pages_a0 = 0
@pages_a1 = 0
@pages_a2 = 0
@pages_a3 = 0
@costum_qm = 0
@page_width = 0
@page_height = 0
end
def analyze
@reader.pages.each do |page|
format = extract_format(page)
case format
when 'a0'
@pages_a0 += 1
when 'a1'
@pages_a1 += 1
when 'a2'
@pages_a2 += 1
when 'a3'
@pages_a3 += 1
else
@costum_qm += calculate_qm
end
end
end
private
def calculate_qm
(@page_width / 1000) * (@page_height / 1000)
end
def extract_format(page)
# debugger
extract_size(page)
return 'a0' if @page_width.round == 841 && @page_height.round == 1189
return 'a1' if @page_width.round == 594 && @page_height.round == 841
return 'a2' if @page_width.round == 420 && @page_height.round == 594
return 'a3' if @page_width.round == 297 && @page_height.round == 420
'costum'
end
def extract_size(page)
# debugger
bbox = page.attributes[:MediaBox] # CropBox
width = bbox[2] - bbox[0]
height = bbox[3] - bbox[1]
width_in_mm = pt2mm(width)
height_in_mm = pt2mm(height)
width_in_mm, height_in_mm = height_in_mm, width_in_mm if width_in_mm > height_in_mm
@page_width = width_in_mm
@page_height = height_in_mm
nil
end
def pt2mm(pt)
(pt2in(pt) * BigDecimal('25.4')).round(2)
end
def pt2in(pt)
(pt / BigDecimal('72')).round(2)
end
end
end