# `TzWorld.Downloader`
[🔗](https://github.com/kipcole9/tz_world/blob/v2.5.0/lib/tz_world/downloader.ex#L1)

Downloads the time zone boundary data from the
[timezone-boundary-builder](https://github.com/evansiroky/timezone-boundary-builder)
releases on GitHub.

`mix tz_world.update` is the usual way to install the data, and
`update_release/1` does the same from code.

### Configuration

Downloads are made with `:httpc` over HTTPS, verifying the server's
certificate. They can be configured with:

* `config :tz_world, cacertfile: path` to name the certificate trust
  store. Without it, the store from the `castore` or `certifi` package
  is used if either is installed, and otherwise the first of several
  well-known system locations that exists.

* `config :tz_world, https_proxy: url`, or the `HTTPS_PROXY` or
  `https_proxy` environment variable, to download through a proxy.

* the `TZWORLD_HTTP_TIMEOUT` and `TZWORLD_HTTP_CONNECTION_TIMEOUT`
  environment variables, in milliseconds, to change the request timeout
  from 120,000 and the connection timeout from 60,000.

* the `TZWORLD_UNSAFE_HTTPS` environment variable, which turns off
  certificate verification when it is set to anything other than an
  empty string, `false` or `nil`. This is not recommended.

# `current_release`

Returns the release of the installed time zone boundary data.

The release is read from the installed data file, so no backend needs
to be running.

### Returns

* `{:ok, release}` where `release` is the name of the release.

* `{:error, reason}` if no data is installed, when `reason` is
  `:enoent`, or the installed file cannot be read.

### Examples

    iex> TzWorld.Downloader.current_release()
    {:ok, "2026d"}

# `get`

```elixir
@spec get(String.t() | {String.t(), list()}, options :: Keyword.t()) ::
  {:ok, binary()} | {:not_modified, any()} | {:error, any()}
```

Downloads the body of an HTTPS URL.

The request is made with `:httpc`, verifying the server's certificate as
the [EEF security guidelines](https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/ssl)
recommend. The trust store, proxy and timeouts are configured as the
module documentation describes.

### Arguments

* `url` is the URL as a string, or a `{url, headers}` tuple where
  `headers` is a list of `{name, value}` tuples of charlists.

* `options` is a keyword list of options.

### Options

* `:verify_peer` is a boolean. When `false` the server's certificate is
  not verified. The default is `true`.

* `:timeout` is the number of milliseconds the request may take. The
  default is the value of `TZWORLD_HTTP_TIMEOUT`, or 120,000.

* `:connection_timeout` is the number of milliseconds allowed to
  connect to the server. The default is the value of
  `TZWORLD_HTTP_CONNECTION_TIMEOUT`, or 60,000.

* `:https_proxy` is the URL of a proxy to download through. The default
  is the configured `:https_proxy`, or the `HTTPS_PROXY` or
  `https_proxy` environment variable.

### Returns

* `{:ok, body}` if the server responds with status 200.

* `{:not_modified, headers}` if the server responds with status 304,
  which needs a conditional request header such as `If-None-Match`.

* `{:error, reason}` otherwise, where `reason` is the HTTP status code
  or the `:httpc` error. The failure is also logged.

### Examples

    iex> {:ok, body} = TzWorld.Downloader.get("https://example.com")
    iex> String.starts_with?(body, "<!doctype html>")
    true

# `get_with_headers`

```elixir
@spec get_with_headers(String.t() | {String.t(), list()}, options :: Keyword.t()) ::
  {:ok, list(), binary()} | {:not_modified, any()} | {:error, any()}
```

Downloads the headers and body of an HTTPS URL.

The request is made as `get/2` describes.

### Arguments

* `url` is the URL as a string, or a `{url, headers}` tuple where
  `headers` is a list of `{name, value}` tuples of charlists.

* `options` is a keyword list of options.

### Options

* The options are those of `get/2`.

### Returns

* `{:ok, headers, body}` if the server responds with status 200, where
  `headers` is a list of `{name, value}` tuples of charlists.

* `{:not_modified, headers}` if the server responds with status 304.

* `{:error, reason}` otherwise, where `reason` is the HTTP status code
  or the `:httpc` error. The failure is also logged.

### Examples

    iex> {:ok, headers, _body} = TzWorld.Downloader.get_with_headers("https://example.com")
    iex> List.keymember?(headers, ~c"content-type", 0)
    true

# `latest_release`

Returns the latest release of the time zone boundary data.

### Arguments

* `include_oceans?` is a boolean. When `true` the download URL is for
  the data that also covers the oceans. The default is `false`.

* `trace?` is a boolean. When `true` progress is logged at the debug
  level. The default is `false`.

### Returns

* `{release, url}` where `release` is the name of the release and `url`
  is the download URL of its data.

A `RuntimeError` is raised if the list of releases cannot be fetched.

### Examples

    iex> TzWorld.Downloader.latest_release()
    {"2026d", "https://github.com/evansiroky/timezone-boundary-builder/releases/download/2026d/timezones.geojson.zip"}

# `stream_get_url`

Downloads a URL to a file without holding the response in memory.

### Arguments

* `url` is the URL to download.

* `path` is the path of the file the response body is written to.

* `trace?` is a boolean. When `true` the download is logged at the
  debug level. The default is `false`.

### Returns

* `{:ok, path}` once the response body has been written to `path`.

* `{:error, reason}` if the download fails, where `reason` is the HTTP
  status code or the `:httpc` error. The failure is also logged.

### Examples

    iex> path = Path.join(System.tmp_dir!(), "example.html")
    iex> {:ok, ^path} = TzWorld.Downloader.stream_get_url("https://example.com", path)
    iex> File.exists?(path)
    true

# `update_release`

Installs the latest time zone boundary data if it is newer than the
installed data.

The data is downloaded, compressed into the data directory and then
loaded into every running backend with `TzWorld.reload_timezone_data/0`.

### Arguments

* `options` is a keyword list of options.

### Options

* `:include_oceans` is a boolean. When `true` the data that also covers
  the oceans is installed. The default is `false`.

* `:force` is a boolean. When `true` the latest data is installed even
  if it is already installed, which is how to switch between the data
  with and without the oceans. The default is `false`.

* `:trace` is a boolean. When `true` progress is logged at the debug
  level. The default is `false`.

### Returns

* `{:ok, release}` if the installed data is already the latest release.

* The result of `TzWorld.reload_timezone_data/0` once the latest data
  has been installed.

* `{:error, reason}` if the download fails.

A `RuntimeError` is raised if the list of releases cannot be fetched.

### Examples

    iex> TzWorld.Downloader.update_release()
    {:ok, "2026d"}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
