attachment_factory.rb 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. module Import
  2. module OTRS
  3. class Article
  4. module AttachmentFactory
  5. extend Import::Helper
  6. # rubocop:disable Style/ModuleFunction
  7. extend self
  8. def import(args)
  9. attachments = args[:attachments] || []
  10. local_article = args[:local_article]
  11. return if skip_import?(attachments, local_article)
  12. perform_import(attachments, local_article)
  13. end
  14. private
  15. def perform_import(attachments, local_article)
  16. attachments.each { |attachment| import_single(local_article, attachment) }
  17. end
  18. def import_single(local_article, attachment)
  19. decoded_filename = Base64.decode64(attachment['Filename'])
  20. decoded_content = Base64.decode64(attachment['Content'])
  21. # TODO: should be done by a/the Storage object
  22. # to handle fingerprinting
  23. sha = Digest::SHA256.hexdigest(decoded_content)
  24. retries = 3
  25. begin
  26. queueing(sha, decoded_filename)
  27. log "Ticket #{local_article.ticket_id}, Article #{local_article.id} - Starting import for fingerprint #{sha} (#{decoded_filename})... Queue: #{@sha_queue[sha]}."
  28. ActiveRecord::Base.transaction do
  29. Store.add(
  30. object: 'Ticket::Article',
  31. o_id: local_article.id,
  32. filename: decoded_filename,
  33. data: decoded_content,
  34. preferences: {
  35. 'Mime-Type' => attachment['ContentType'],
  36. 'Content-ID' => attachment['ContentID'],
  37. 'content-alternative' => attachment['ContentAlternative'],
  38. },
  39. created_by_id: 1,
  40. )
  41. end
  42. log "Ticket #{local_article.ticket_id}, Article #{local_article.id} - Finished import for fingerprint #{sha} (#{decoded_filename})... Queue: #{@sha_queue[sha]}."
  43. rescue ActiveRecord::RecordNotUnique, ActiveRecord::StatementInvalid => e
  44. log "Ticket #{local_article.ticket_id} - #{sha} - #{e.class}: #{e}"
  45. sleep rand 3
  46. retry if !(retries -= 1).zero?
  47. raise
  48. ensure
  49. queue_cleanup(sha)
  50. end
  51. end
  52. def skip_import?(attachments, local_article)
  53. local_attachments = local_article.attachments
  54. return true if local_attachments.count == attachments.count
  55. # get a common ground
  56. local_attachments.each(&:delete)
  57. return true if attachments.empty?
  58. false
  59. end
  60. def queueing(sha, decoded_filename)
  61. # this is (currently) needed for avoiding
  62. # race conditions inserting attachments with
  63. # the same fingerprint in the DB in concurrent threads
  64. @sha_queue ||= {}
  65. @sha_queue[sha] ||= []
  66. return if !queueing_active?
  67. @sha_queue[sha].push(queue_id)
  68. while @sha_queue[sha].first != queue_id
  69. sleep_time = 0.25
  70. log "Found active import for fingerprint #{sha} (#{decoded_filename})... sleeping #{sleep_time} seconds. Queue: #{@sha_queue[sha]}."
  71. sleep sleep_time
  72. end
  73. end
  74. def queue_cleanup(sha)
  75. return if !queueing_active?
  76. @sha_queue[sha].shift
  77. end
  78. def queueing_active?
  79. return if !queue_id
  80. true
  81. end
  82. def queue_id
  83. Thread.current[:thread_no]
  84. end
  85. end
  86. end
  87. end
  88. end