Class: InstagramConnect::FetchMediaJob

Inherits:
ApplicationJob
  • Object
show all
Defined in:
app/jobs/instagram_connect/fetch_media_job.rb

Overview

Copies one inbound attachment's bytes out of Meta's CDN and into the host's own storage.

This runs against a clock nobody controls: Instagram's media URLs expire and there is no endpoint to ask for a fresh one. So a failure here is permanent for that file, which is why the retry ladder ends in a terminal state rather than leaving the row pending — a pending attachment renders as a loading placeholder, and one that never resolves is worse than an honest "this file is no longer available".

Constant Summary collapse

TIMEOUT =
30
TRANSPORT_ERRORS =
[
  Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ETIMEDOUT,
  Net::OpenTimeout, Net::ReadTimeout, SocketError, IOError
].freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.exhausted(attachment_id, error) ⇒ Object

Retries are spent. Without this the job would land in the dead set and leave the attachment pending for ever — a loading placeholder in the thread that nothing will ever resolve.



29
30
31
# File 'app/jobs/instagram_connect/fetch_media_job.rb', line 29

def self.exhausted(attachment_id, error)
  MessageAttachment.find_by(id: attachment_id)&.mark_unavailable!("transport:#{error.class}")
end

Instance Method Details

#perform(attachment_id) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'app/jobs/instagram_connect/fetch_media_job.rb', line 33

def perform(attachment_id)
  attachment = MessageAttachment.find_by(id: attachment_id)
  return if attachment.nil?
  return unless storage_available?

  # The guard and the write have to be one atomic step: two deliveries of
  # the same webhook would otherwise both see "pending" and both fetch.
  attachment.with_lock do
    next unless attachment.fetchable?

    fetch_into(attachment)
  end
end