Class: Fontisan::Tasks::FixtureDownloader

Inherits:
Object
  • Object
show all
Defined in:
lib/fontisan/tasks/fixture_downloader.rb

Overview

Downloads a single fixture file with retry on transient network failures. Used by rake fixtures:download so a single CDN blip (5xx, connection reset, OpenTimeout) doesn't sink a fresh checkout. Permanent failures (404, malformed URL) surface immediately.

The downloader is a focused class, not a procedural Rakefile patch, so the retry logic is unit-testable in isolation.

Examples:

Fontisan::Tasks::FixtureDownloader.new(
  url: "https://github.com/.../font.ttf",
  destination: "spec/fixtures/font.ttf",
).call

Defined Under Namespace

Classes: Error

Constant Summary collapse

RETRIABLE_ERRORS =
[
  Net::OpenTimeout,
  Net::ReadTimeout,
  Errno::ECONNRESET,
  Errno::ECONNREFUSED,
  Errno::EHOSTUNREACH,
  Errno::ETIMEDOUT,
  EOFError,
  IOError,
].freeze
RETRIABLE_HTTP_STATUSES =

5xx HTTP responses are transient server errors worth retrying. 4xx are permanent (404, 403) and must fail fast.

(500..599)
DEFAULT_MAX_RETRIES =
3
DEFAULT_BASE_BACKOFF =

seconds; doubles per attempt

0.5

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(url:, destination:, max_retries: DEFAULT_MAX_RETRIES, base_backoff: DEFAULT_BASE_BACKOFF, sleep_method: method(:sleep)) ⇒ FixtureDownloader

Returns a new instance of FixtureDownloader.

Parameters:

  • url (String)

    source URL.

  • destination (String)

    path to write bytes to. Parent dir is auto-created.

  • max_retries (Integer) (defaults to: DEFAULT_MAX_RETRIES)

    total attempts including the first. 3 means: try, retry, retry.

  • base_backoff (Float) (defaults to: DEFAULT_BASE_BACKOFF)

    seconds to sleep before the first retry. Doubles per attempt.

  • sleep_method (#call) (defaults to: method(:sleep))

    injectable sleep (for tests). Defaults to Kernel.sleep.



69
70
71
72
73
74
75
76
# File 'lib/fontisan/tasks/fixture_downloader.rb', line 69

def initialize(url:, destination:, max_retries: DEFAULT_MAX_RETRIES,
               base_backoff: DEFAULT_BASE_BACKOFF, sleep_method: method(:sleep))
  @url = url
  @destination = destination
  @max_retries = max_retries
  @base_backoff = base_backoff
  @sleep_method = sleep_method
end

Instance Attribute Details

#base_backoffObject (readonly)

Returns the value of attribute base_backoff.



58
59
60
# File 'lib/fontisan/tasks/fixture_downloader.rb', line 58

def base_backoff
  @base_backoff
end

#destinationObject (readonly)

Returns the value of attribute destination.



58
59
60
# File 'lib/fontisan/tasks/fixture_downloader.rb', line 58

def destination
  @destination
end

#max_retriesObject (readonly)

Returns the value of attribute max_retries.



58
59
60
# File 'lib/fontisan/tasks/fixture_downloader.rb', line 58

def max_retries
  @max_retries
end

#sleep_methodObject (readonly)

Returns the value of attribute sleep_method.



58
59
60
# File 'lib/fontisan/tasks/fixture_downloader.rb', line 58

def sleep_method
  @sleep_method
end

#urlObject (readonly)

Returns the value of attribute url.



58
59
60
# File 'lib/fontisan/tasks/fixture_downloader.rb', line 58

def url
  @url
end

Instance Method Details

#callString

Performs the download. Returns the destination path on success. Raises Error after exhausting retries.

Returns:

  • (String)

    destination path

Raises:



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/fontisan/tasks/fixture_downloader.rb', line 83

def call
  attempts = 0
  nil

  begin
    attempts += 1
    fetch_to_destination
    destination
  rescue StandardError => e
    e
    raise if permanent_failure?(e)
    raise Error.new(url: url, attempts: attempts, last_error: e) if attempts >= max_retries

    backoff = base_backoff * (2**(attempts - 1))
    sleep_method.call(backoff)
    retry
  end
end