-
Notifications
You must be signed in to change notification settings - Fork 34
/
install.ex
289 lines (226 loc) · 8.77 KB
/
install.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
defmodule Cldr.Install do
@moduledoc """
Provides functions for installing locales.
When installed as a package on from [hex](http://hex.pm), `Cldr` has only
the default locales `["en", "und"]` installed and configured.
When other locales are added to the configuration `Cldr` will attempt to
download the locale from [github](https://github.com/kipcole9/cldr)
during compilation.
If `Cldr` is installed from github directly then all locales are already
installed.
"""
defdelegate client_data_dir(config), to: Cldr.Config
defdelegate client_locales_dir(config), to: Cldr.Config
defdelegate locale_filename(locale), to: Cldr.Config
@doc """
Install all the configured locales.
"""
def install_known_locale_names(config) do
config
|> Cldr.Locale.Loader.known_locale_names()
|> Enum.each(&install_locale_name(&1, config))
:ok
end
@doc """
Install all available locales.
"""
def install_all_locale_names(config) do
Cldr.Config.all_locale_names()
|> Enum.each(&install_locale_name(&1, config))
:ok
end
@doc """
Download the requested locale from github into the
client application's cldr data directory.
* `locale` is any locale returned by `Cldr.known_locale_names/1`
* `options` is a keyword list. Currently the only supported
option is `:force` which defaults to `false`. If `truthy` the
locale will be installed or re-installed.
The data directory is typically `priv/cldr/locales`.
This function is intended to be invoked during application
compilation when a valid locale is configured but is not yet
installed in the application.
An https request to the master github repository for `Cldr` is made
to download the correct version of the locale file which is then
written to the configured data directory.
"""
def install_locale_name(locale_name, config, options \\ []) do
force_download? = config.force_locale_download || options[:force]
if !locale_installed?(locale_name, config) || force_download? do
ensure_client_dirs_exist!(client_locales_dir(config))
Application.ensure_started(:inets)
Application.ensure_started(:ssl)
Application.ensure_started(Cldr.Config.app_name())
do_install_locale_name(locale_name, config, locale_name in Cldr.Config.all_locale_names())
else
output_file_name = locale_output_file_name(locale_name, config)
Cldr.maybe_log("Locale already installed and found at #{inspect(output_file_name)}")
:already_installed
end
end
# Normally a library function shouldn't raise an exception (that's up
# to the client app) but we install locales only at compilation time
# and an exception then is the appropriate response.
defp do_install_locale_name(locale_name, _config, false) do
raise Cldr.UnknownLocaleError,
"Failed to install the locale named #{inspect(locale_name)}. The locale name is not known."
end
defp do_install_locale_name(locale_name, config, true) do
require Logger
output_file_name = locale_output_file_name(locale_name, config)
url = String.to_charlist("#{base_url()}#{locale_filename(locale_name)}")
case :httpc.request(:get, {url, headers()}, https_opts(), []) do
{:ok, {{_version, 200, 'OK'}, _headers, body}} ->
output_file_name
|> File.write!(body)
Logger.bare_log(:info, "Downloaded locale #{inspect(locale_name)}")
{:ok, output_file_name}
{_, {{_version, code, message}, _headers, _body}} ->
Logger.bare_log(
:error,
"Failed to download locale #{inspect(locale_name)} from #{url}. " <>
"HTTP Error: (#{code}) #{inspect(message)}"
)
{:error, code}
{:error, {:failed_connect, [{_, {host, _port}}, {_, _, sys_message}]}} ->
Logger.bare_log(
:error,
"Failed to connect to #{inspect(host)} to download " <>
"locale #{inspect(locale_name)}. Reason: #{inspect(sys_message)}"
)
{:error, sys_message}
end
end
defp locale_output_file_name(locale_name, config) do
[client_locales_dir(config), "/", locale_filename(locale_name)]
|> :erlang.iolist_to_binary()
end
defp headers do
# [{'Connection', 'close'}]
[]
end
@certificate_locations [
# Configured cacertfile
Application.get_env(Cldr.Config.app_name(), :cacertfile),
# Populated if hex package CAStore is configured
if(Code.ensure_loaded?(CAStore), do: CAStore.file_path()),
# Populated if hex package certfi is configured
if(Code.ensure_loaded?(:certifi),
do: :certifi.cacertfile() |> List.to_string()
),
# Debian/Ubuntu/Gentoo etc.
"/etc/ssl/certs/ca-certificates.crt",
# Fedora/RHEL 6
"/etc/pki/tls/certs/ca-bundle.crt",
# OpenSUSE
"/etc/ssl/ca-bundle.pem",
# OpenELEC
"/etc/pki/tls/cacert.pem",
# CentOS/RHEL 7
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem",
# Open SSL on MacOS
"/usr/local/etc/openssl/cert.pem",
# MacOS & Alpine Linux
"/etc/ssl/cert.pem"
]
|> Enum.reject(&is_nil/1)
def certificate_store do
@certificate_locations
|> Enum.find(&File.exists?/1)
|> raise_if_no_cacertfile
|> :erlang.binary_to_list()
end
defp raise_if_no_cacertfile(nil) do
raise RuntimeError, """
No certificate trust store was found.
Tried looking for: #{inspect(@certificate_locations)}
A certificate trust store is required in
order to download locales for your configuration.
Since ex_cldr could not detect a system
installed certificate trust store one of the
following actions may be taken:
1. Install the hex package `castore`. It will
be automatically detected after recompilation.
2. Install the hex package `certifi`. It will
be automatically detected after recomilation.
3. Specify the location of a certificate trust store
by configuring it in `config.exs`:
config :ex_cldr,
cacertfile: "/path/to/cacertfile",
...
"""
end
defp raise_if_no_cacertfile(file) do
file
end
defp https_opts do
[
ssl: [
verify: :verify_peer,
cacertfile: certificate_store(),
customize_hostname_check: [
match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
]
]
]
end
# Builds the base url to retrieve a locale file from github.
#
# The url is built using the version number of the `Cldr` application.
# If the version is a `-dev` version then the locale file is downloaded
# from the master branch.
#
# This requires that a branch is tagged with the version number before creating
# a release or publishing to hex.
@base_url "https://raw.githubusercontent.com/elixir-cldr/cldr/"
defp base_url do
[@base_url, branch_from_version(), "/priv/cldr/locales/"]
|> :erlang.iolist_to_binary()
end
# Returns the version of ex_cldr
defp app_version do
cond do
spec = Application.spec(Cldr.Config.app_name()) ->
Keyword.get(spec, :vsn) |> :erlang.list_to_binary()
Code.ensure_loaded?(Cldr.Mixfile) ->
module = Module.concat(Cldr, Mixfile)
Keyword.get(module.project(), :version)
true ->
:error
end
end
# Get the git branch name based upon the app version
defp branch_from_version do
version = app_version()
if String.contains?(version, "-dev") do
"master"
else
"v#{version}"
end
end
@doc """
Returns a `boolean` indicating if the requested locale is installed.
No checking of the validity of the `locale` itself is performed. The
check is based upon whether there is a locale file installed in the
client application or in `Cldr` itself.
"""
def locale_installed?(locale, config) do
case Cldr.Config.locale_path(locale, config) do
{:ok, _path} -> true
_ -> false
end
end
@doc """
Returns the full pathname of the locale's json file.
* `locale` is any locale returned by `Cldr.known_locale_names/1`
No checking of locale validity is performed.
"""
def client_locale_file(locale, config) do
Path.join(client_locales_dir(config), "#{locale}.json")
end
# Create the client app locales directory and any directories
# that don't exist above it.
defp ensure_client_dirs_exist!(dir) do
File.mkdir_p(dir)
end
end