Packages
locus
2.3.13
2.3.15
2.3.14
2.3.13
2.3.12
2.3.11
2.3.10
2.3.9
2.3.8
2.3.7
2.3.6
2.3.5
2.3.4
2.3.3
retired
2.3.2
2.3.1
2.3.0
2.2.2
2.2.1
2.2.0
2.1.0
2.0.0
1.16.1
1.16.0
1.15.0
1.14.1
1.14.0
1.13.2
1.13.1
1.13.0
1.12.2
1.12.1
1.12.0
1.11.0
1.11.0-beta
1.10.2
1.10.1
1.10.0
1.9.0
1.9.0-beta
1.8.0
1.7.0
1.6.2
1.6.1
1.6.0
1.5.1
1.5.0
1.4.0
1.3.1
1.3.0
1.2.3
1.2.2
1.2.1
1.2.0
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.2
1.0.1
1.0.0
MMDB reader for geolocation and ASN lookup of IP addresses, supporting MaxMind GeoLite2/GeoIP2 and other providers
Current section
Files
Jump to
Current section
Files
src/locus.erl
%% Copyright (c) 2017-2024 Guilherme Andrade
%%
%% Permission is hereby granted, free of charge, to any person obtaining a
%% copy of this software and associated documentation files (the "Software"),
%% to deal in the Software without restriction, including without limitation
%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
%% and/or sell copies of the Software, and to permit persons to whom the
%% Software is furnished to do so, subject to the following conditions:
%%
%% The above copyright notice and this permission notice shall be included in
%% all copies or substantial portions of the Software.
%%
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
%% DEALINGS IN THE SOFTWARE.
%%
%% locus is an independent project and has not been authorized, sponsored,
%% or otherwise approved by MaxMind.
%% @doc The main API
-module(locus).
%% ------------------------------------------------------------------
%% API Function Exports
%% ------------------------------------------------------------------
-export([start_loader/2]). -ignore_xref(start_loader/2).
-export([start_loader/3]). -ignore_xref(start_loader/3).
-export([stop_loader/1]). -ignore_xref(stop_loader/1).
-export([loader_child_spec/2]). -ignore_xref(loader_child_spec/2).
-export([loader_child_spec/3]). -ignore_xref(loader_child_spec/3).
-export([loader_child_spec/4]). -ignore_xref(loader_child_spec/4).
-export([await_loader/1]). -ignore_xref(await_loader/1).
-export([await_loader/2]). -ignore_xref(await_loader/2).
-export([await_loaders/2]). -ignore_xref(await_loaders/2).
-export([lookup/2]). -ignore_xref(lookup/2).
-export([get_info/1]). -ignore_xref(get_info/1).
-export([get_info/2]). -ignore_xref(get_info/2).
-export([check/1]). -ignore_xref(check/1).
-ifdef(TEST).
-export([parse_database_edition/1]).
-endif.
%% ------------------------------------------------------------------
%% CLI-only Function Exports
%% ------------------------------------------------------------------
-ifdef(ESCRIPTIZING).
-export([main/1]). -ignore_xref(main/1).
-endif.
%% ------------------------------------------------------------------
%% Macro Definitions
%% ------------------------------------------------------------------
-define(might_be_chardata(V), (is_binary((V)) orelse ?is_proper_list((V)))).
-define(is_proper_list(V), (length((V)) >= 0)).
%% ------------------------------------------------------------------
%% Type Definitions
%% ------------------------------------------------------------------
-type database_edition() :: maxmind_database_edition().
-export_type([database_edition/0]).
-type maxmind_database_edition() ::
{maxmind, atom() | unicode:chardata()} |
legacy_maxmind_database_edition().
-export_type([maxmind_database_edition/0]).
-type legacy_maxmind_database_edition() :: atom().
-export_type([legacy_maxmind_database_edition/0]).
-type database_url() :: unicode:chardata().
-export_type([database_url/0]).
-type custom_fetcher() :: {custom_fetcher, module(), Args :: term()}.
-export_type([custom_fetcher/0]).
-type database_error() :: database_unknown | database_not_loaded.
-export_type([database_error/0]).
-type database_entry() :: locus_mmdb_data:value().
-export_type([database_entry/0]).
-type ip_address_prefix() :: locus_mmdb_tree:ip_address_prefix().
-export_type([ip_address_prefix/0]).
-type database_info() ::
#{ metadata := database_metadata(),
source := database_source(),
version := database_version()
}.
-export_type([database_info/0]).
-type database_metadata() :: locus_mmdb_metadata:t().
-export_type([database_metadata/0]).
-type database_source() :: locus_loader:source().
-export_type([database_source/0]).
-type database_version() :: calendar:datetime().
-export_type([database_version/0]).
%% ------------------------------------------------------------------
%% API Function Definitions
%% ------------------------------------------------------------------
%% @doc Like `:start_loader/3' but with default options
%%
%% <ul>
%% <li>`DatabaseId' must be an atom.</li>
%% <li>`LoadFrom' must be either:
%% <ul>
%% <li>a `database_edition()' tuple, or</li>
%% <li>a `DatabaseURL' containing either a string or binary representation
%% of a HTTP(s) URL or local path, or</li>
%% <li>a `{custom_fetcher, Module, Args}' tuple, with `Module' implementing
%% the `locus_custom_fetcher' behaviour</li>
%% </ul>
%% </li>
%% </ul>
%%
%% Returns:
%% <ul>
%% <li>`ok' in case of success.</li>
%% <li>`{error, invalid_url}' if the source is invalid.</li>
%% <li>`{error, already_started}' if the loader under `DatabaseId' has already been started.</li>
%% </ul>
%% @see await_loader/1
%% @see await_loader/2
%% @see start_loader/3
-spec start_loader(DatabaseId, LoadFrom) -> ok | {error, Error}
when DatabaseId :: atom(),
LoadFrom :: DatabaseEdition | DatabaseURL | CustomFetcher,
DatabaseEdition :: database_edition(),
DatabaseURL :: database_url(),
CustomFetcher :: custom_fetcher(),
Error :: invalid_url | already_started | application_not_running.
start_loader(DatabaseId, LoadFrom) ->
start_loader(DatabaseId, LoadFrom, []).
%% @doc Starts a database loader under id `DatabaseId' with options `Opts'.
%%
%% <ul>
%% <li>`DatabaseId' must be an atom.</li>
%% <li>`LoadFrom' must be either:
%% <ul>
%% <li>a `database_edition()' tuple, or</li>
%% <li>a `DatabaseURL' containing either a string or binary representation
%% of a HTTP(s) URL or local path, or</li>
%% <li>a `{custom_fetcher, Module, Args}' tuple, with `Module' implementing
%% the `locus_custom_fetcher' behaviour</li>
%% </ul>
%% </li>
%% <li>`Opts' must be a list of `locus_database:opt()' values</li>
%% </ul>
%%
%% Returns:
%% <ul>
%% <li>`ok' in case of success.</li>
%% <li>`{error, invalid_url}' if the source is invalid.</li>
%% <li>`{error, already_started}' if the loader under `DatabaseId' has already been started.</li>
%% </ul>
%% @see await_loader/1
%% @see await_loader/2
%% @see start_loader/2
-spec start_loader(DatabaseId, LoadFrom, Opts) -> ok | {error, Error}
when DatabaseId :: atom(),
LoadFrom :: DatabaseEdition | DatabaseURL | CustomFetcher,
DatabaseEdition :: database_edition(),
DatabaseURL :: database_url(),
CustomFetcher :: custom_fetcher(),
Opts :: [locus_database:opt()],
Error :: (invalid_url | already_started |
{invalid_opt, term()} | application_not_running).
start_loader(DatabaseId, {maxmind, _} = DatabaseEdition, Opts) ->
Origin = parse_database_edition(DatabaseEdition),
OptsWithDefaults = opts_with_defaults(Opts),
locus_database:start(DatabaseId, Origin, OptsWithDefaults);
start_loader(DatabaseId, DatabaseEdition, Opts)
when is_atom(DatabaseEdition) ->
% Deprecated edition format
Origin = parse_database_edition(DatabaseEdition),
OptsWithDefaults = opts_with_defaults(Opts),
locus_database:start(DatabaseId, Origin, OptsWithDefaults);
start_loader(DatabaseId, DatabaseURL, Opts)
when ?might_be_chardata(DatabaseURL) ->
case parse_url(DatabaseURL) of
false ->
{error, invalid_url};
Origin ->
OptsWithDefaults = opts_with_defaults(Opts),
locus_database:start(DatabaseId, Origin, OptsWithDefaults)
end;
start_loader(DatabaseId, {custom_fetcher, Module, _Args} = CustomFetcher, Opts)
when is_atom(Module) ->
Origin = CustomFetcher,
OptsWithDefaults = opts_with_defaults(Opts),
locus_database:start(DatabaseId, Origin, OptsWithDefaults).
%% @doc Stops the database loader under id `DatabaseId'.
%%
%% <ul>
%% <li>`DatabaseId' must be an atom and refer to a database loader.</li>
%% </ul>
%%
%% Returns `ok' in case of success, `{error, not_found}' otherwise.
-spec stop_loader(DatabaseId) -> ok | {error, Error}
when DatabaseId :: atom(),
Error :: not_found.
stop_loader(DatabaseId) ->
locus_database:stop(DatabaseId, _Reason = normal).
%% @doc Like `:loader_child_spec/2' but with default options
%%
%% <ul>
%% <li>`DatabaseId' must be an atom.</li>
%% <li>`LoadFrom' must be either:
%% <ul>
%% <li>a `database_edition()' tuple, or</li>
%% <li>a `DatabaseURL' containing either a string or binary representation
%% of a HTTP(s) URL or local path, or</li>
%% <li>a `{custom_fetcher, Module, Args}' tuple, with `Module' implementing
%% the `locus_custom_fetcher' behaviour</li>
%% </ul>
%% </li>
%% </ul>
%%
%% Returns:
%% <ul>
%% <li>A `supervisor:child_spec()'.</li>
%% </ul>
%% @see loader_child_spec/3
%% @see await_loader/1
%% @see await_loader/2
%% @see start_loader/2
-spec loader_child_spec(DatabaseId, LoadFrom) -> ChildSpec | no_return()
when DatabaseId :: atom(),
LoadFrom :: DatabaseEdition | DatabaseURL | CustomFetcher,
DatabaseEdition :: database_edition(),
DatabaseURL :: database_url(),
CustomFetcher :: custom_fetcher(),
ChildSpec :: locus_database:static_child_spec().
loader_child_spec(DatabaseId, LoadFrom) ->
loader_child_spec(DatabaseId, LoadFrom, []).
%% @doc Like `:loader_child_spec/3' but with default child id
%%
%% <ul>
%% <li>`DatabaseId' must be an atom.</li>
%% <li>`LoadFrom' must be either:
%% <ul>
%% <li>a `database_edition()' tuple, or</li>
%% <li>a `DatabaseURL' containing either a string or binary representation
%% of a HTTP(s) URL or local path, or</li>
%% <li>a `{custom_fetcher, Module, Args}' tuple, with `Module' implementing
%% the `locus_custom_fetcher' behaviour</li>
%% </ul>
%% </li>
%% <li>`Opts' must be a list of `locus_database:opt()' values</li>
%% </ul>
%%
%% Returns:
%% <ul>
%% <li>A `supervisor:child_spec()'.</li>
%% </ul>
%% @see loader_child_spec/2
%% @see loader_child_spec/4
%% @see await_loader/1
%% @see await_loader/2
%% @see start_loader/3
-spec loader_child_spec(DatabaseId, LoadFrom, Opts) -> ChildSpec | no_return()
when DatabaseId :: atom(),
LoadFrom :: DatabaseEdition | DatabaseURL | CustomFetcher,
DatabaseEdition :: database_edition(),
DatabaseURL :: database_url(),
CustomFetcher :: custom_fetcher(),
Opts :: [locus_database:opt()],
ChildSpec :: locus_database:static_child_spec().
loader_child_spec(DatabaseId, LoadFrom, Opts) ->
loader_child_spec({locus_database, DatabaseId}, DatabaseId, LoadFrom, Opts).
%% @doc Returns a supervisor child spec for a database loader
%% under id `DatabaseId' with options `Opts'.
%%
%% <ul>
%% <li>`DatabaseId' must be an atom.</li>
%% <li>`LoadFrom' must be either:
%% <ul>
%% <li>a `database_edition()' tuple, or</li>
%% <li>a `DatabaseURL' containing either a string or binary representation
%% of a HTTP(s) URL or local path, or</li>
%% <li>a `{custom_fetcher, Module, Args}' tuple, with `Module' implementing
%% the `locus_custom_fetcher' behaviour</li>
%% </ul>
%% </li>
%% <li>`Opts' must be a list of `locus_database:opt()' values</li>
%% </ul>
%%
%% Returns:
%% <ul>
%% <li>A `supervisor:child_spec()'.</li>
%% </ul>
%% @see loader_child_spec/3
%% @see await_loader/1
%% @see await_loader/2
%% @see start_loader/3
-spec loader_child_spec(ChildId, DatabaseId, LoadFrom, Opts)
-> ChildSpec | no_return()
when ChildId :: term(),
DatabaseId :: atom(),
LoadFrom :: DatabaseEdition | DatabaseURL | CustomFetcher,
DatabaseEdition :: database_edition(),
DatabaseURL :: database_url(),
CustomFetcher :: custom_fetcher(),
Opts :: [locus_database:opt()],
ChildSpec :: locus_database:static_child_spec().
loader_child_spec(ChildId, DatabaseId, {maxmind, _} = DatabaseEdition, Opts) ->
Origin = parse_database_edition(DatabaseEdition),
OptsWithDefaults = opts_with_defaults(Opts),
locus_database:static_child_spec(ChildId, DatabaseId, Origin, OptsWithDefaults);
loader_child_spec(ChildId, DatabaseId, DatabaseEdition, Opts)
when is_atom(DatabaseEdition) ->
% Deprecated edition format
Origin = parse_database_edition(DatabaseEdition),
OptsWithDefaults = opts_with_defaults(Opts),
locus_database:static_child_spec(ChildId, DatabaseId, Origin, OptsWithDefaults);
loader_child_spec(ChildId, DatabaseId, DatabaseURL, Opts)
when ?might_be_chardata(DatabaseURL) ->
case parse_url(DatabaseURL) of
false ->
error(invalid_url);
Origin ->
OptsWithDefaults = opts_with_defaults(Opts),
locus_database:static_child_spec(ChildId, DatabaseId, Origin, OptsWithDefaults)
end;
loader_child_spec(ChildId, DatabaseId, {custom_fetcher, Module, _Args} = CustomFetcher, Opts)
when is_atom(Module) ->
Origin = CustomFetcher,
OptsWithDefaults = opts_with_defaults(Opts),
locus_database:static_child_spec(ChildId, DatabaseId, Origin, OptsWithDefaults).
%% @doc Like `await_loader/1' but with a default timeout of 30 seconds.
%%
%% <ul>
%% <li>`DatabaseId' must be an atom and refer to a database loader.</li>
%% </ul>
%%
%% Returns:
%% <ul>
%% <li>`{ok, LoadedVersion}' when the database is ready to use.</li>
%% <li>`{error, database_unknown}' if the database loader for `DatabaseId' hasn't been started.</li>
%% <li>`{error, {database_stopped, _}}'
%% if the database loader for `DatabaseId' stopped while we waited.</li>
%% <li>`{error, {timeout, [_]}}'
%% if all the load attempts performed before timing out have failed.</li>
%% </ul>
%% @see await_loader/2
-spec await_loader(DatabaseId) -> {ok, LoadedVersion} | {error, Reason}
when DatabaseId :: atom(),
LoadedVersion :: database_version(),
Reason :: (database_unknown |
{database_stopped, term()} |
{timeout, LoadAttemptFailures}),
LoadAttemptFailures :: [term()].
await_loader(DatabaseId) ->
await_loader(DatabaseId, 30000).
%% @doc Blocks caller execution until either readiness is achieved
%% or the default timeout is triggered.
%%
%% <ul>
%% <li>`DatabaseId' must be an atom and refer to a database loader.</li>
%% <li>`Timeout' must be either a non-negative integer (milliseconds) or `infinity'.</li>
%% </ul>
%%
%% Returns:
%% <ul>
%% <li>`{ok, LoadedVersion}' when the database is ready to use.</li>
%% <li>`{error, database_unknown}' if the database loader for `DatabaseId' hasn't been started.</li>
%% <li>`{error, {database_stopped, _}}'
%% if the database loader for `DatabaseId' stopped while we waited.</li>
%% <li>`{error, {timeout, [_]}}'
%% if all the load attempts performed before timing out have failed.</li>
%% </ul>
%% @see await_loader/1
%% @see await_loaders/2
-spec await_loader(DatabaseId, Timeout) -> {ok, LoadedVersion} | {error, Reason}
when DatabaseId :: atom(),
Timeout :: timeout(),
LoadedVersion :: database_version(),
Reason :: (database_unknown |
{database_stopped, term()} |
{timeout, LoadAttemptFailures}),
LoadAttemptFailures :: [term()].
await_loader(DatabaseId, Timeout) ->
case await_loaders([DatabaseId], Timeout) of
{ok, #{DatabaseId := LoadedVersion}} ->
{ok, LoadedVersion};
{error, {#{DatabaseId := Reason}, _}} ->
{error, Reason}
end.
%% <ul>
%% <li>`DatabaseIds' must be a list of atoms that refer to database loaders.</li>
%% <li>`Timeout' must be either a non-negative integer (milliseconds) or `infinity'.</li>
%% </ul>
%%
%% Returns:
%% <ul>
%% <li>`{ok, #{DatabaseId => LoadedVersion}}' when all the databases are ready to use.</li>
%% <li>`{error, {DatabaseId, database_unknown}}'
%% if the database loader for `DatabaseId' hasn't been started.</li>
%% <li>`{error, {DatabaseId, {loading, term()}}}'
%% if loading `DatabaseId' failed for some reason.</li>
%% <li>`{error, timeout}' if we've given up on waiting.</li>
%% </ul>
%% @doc Like `await_loader/2' but it can concurrently await status from more than one database.
%%
%% <ul>
%% <li>`DatabaseIds' must be list of atom referring to database loaders.</li>
%% <li>`Timeout' must be either a non-negative integer (milliseconds) or `infinity'.</li>
%% </ul>
%%
%% Returns:
%% <ul>
%% <li>`{ok, #{DatabaseId => LoadedVersion}}' when all the database are ready to use.</li>
%% <li>`{error, {#{DatabaseId => ErrorReason}, _}}' in case of errors.</li>
%% </ul>
%% @see await_loader/2
-spec await_loaders(DatabaseIds, Timeout) -> ({ok, Successes} |
{error, {ErrorPerDatabase, PartialSuccesses}})
when DatabaseIds :: [DatabaseId],
Timeout :: timeout(),
Successes :: LoadedVersionPerDatabase,
PartialSuccesses :: LoadedVersionPerDatabase,
LoadedVersionPerDatabase :: #{DatabaseId => LoadedVersion},
LoadedVersion :: database_version(),
ErrorPerDatabase :: #{DatabaseId := Reason},
Reason :: (database_unknown |
{database_stopped, term()} |
{timeout, LoadAttemptFailures}),
LoadAttemptFailures :: [term()].
await_loaders(DatabaseIds, Timeout) ->
ReplyRef = make_ref(),
UniqueDatabaseIds = lists:usort(DatabaseIds),
Waiters = launch_waiters(ReplyRef, Timeout, UniqueDatabaseIds),
perform_wait(ReplyRef, Waiters, #{}, #{}).
%% @doc Looks-up info on IPv4 and IPv6 addresses.
%%
%% <ul>
%% <li>`DatabaseId' must be an atom and refer to a database loader.</li>
%% <li>`Address' must be either an `inet:ip_address()' tuple, or a string/binary
%% containing a valid representation of the address.</li>
%% </ul>
%%
%% Returns:
%% <ul>
%% <li>`{ok, Entry}' in case of success</li>
%% <li>`not_found' if no data was found for this `Address'.</li>
%% <li>`{error, invalid_address}' if `Address' is not either a `inet:ip_address()'
%% tuple or a valid textual representation of an IP address.</li>
%% <li>`{error, database_unknown}' if the database loader for `DatabaseId' hasn't been started.</li>
%% <li>`{error, database_not_loaded}' if the database hasn't yet been loaded.</li>
%% <li>`{error, ipv4_database}' if `Address' represents an IPv6 address and the database
%% only supports IPv4 addresses.</li>
%% </ul>
-spec lookup(DatabaseId, Address) -> {ok, Entry} | not_found | {error, Error}
when DatabaseId :: atom(),
Address :: inet:ip_address() | string() | binary(),
Entry :: database_entry(),
Error :: (database_unknown | database_not_loaded |
{invalid_address, Address} |
ipv4_database).
lookup(DatabaseId, Address) ->
case locus_database:find(DatabaseId) of
{ok, Database, _Source, _Version} ->
locus_mmdb:lookup_address(Address, Database);
Error ->
Error
end.
%% @doc Returns the properties of a currently loaded database.
%%
%% <ul>
%% <li>`DatabaseId' must be an atom and refer to a database loader.</li>
%% </ul>
%%
%% Returns:
%% <ul>
%% <li>`{ok, database_info()}' in case of success</li>
%% <li>`{error, database_unknown}' if the database loader for `DatabaseId' hasn't been started.</li>
%% <li>`{error, database_not_loaded}' if the database hasn't yet been loaded.</li>
%% </ul>
%% @see get_info/2
-spec get_info(DatabaseId) -> {ok, Info} | {error, Error}
when DatabaseId :: atom(),
Info :: database_info(),
Error :: database_unknown | database_not_loaded.
get_info(DatabaseId) ->
case locus_database:find(DatabaseId) of
{ok, Database, Source, Version} ->
Info = database_info(Database, Source, Version),
{ok, Info};
Error ->
Error
end.
%% @doc Returns a specific property of a currently loaded database.
%%
%% <ul>
%% <li>`DatabaseId' must be an atom and refer to a database loader.</li>
%% <li>`Property' must be either `metadata', `source' or `version'.</li>
%% </ul>
%%
%% Returns:
%% <ul>
%% <li>`{ok, Value}' in case of success</li>
%% <li>`{error, database_unknown}' if the database loader for `DatabaseId' hasn't been started.</li>
%% <li>`{error, database_not_loaded}' if the database hasn't yet been loaded.</li>
%% </ul>
%% @see get_info/1
-spec get_info(DatabaseId, Property) -> {ok, Value} | {error, Error}
when DatabaseId :: atom(),
Property :: metadata | source | version,
Value :: database_metadata() | database_source() | database_version(),
Error :: database_unknown | database_not_loaded.
get_info(DatabaseId, Property) ->
case get_info(DatabaseId) of
{ok, Info} ->
Value = maps:get(Property, Info),
{ok, Value};
{error, Error} ->
{error, Error}
end.
%% @doc Analyzes a loaded database for corruption or incompatibility.
%%
%% <ul>
%% <li>`DatabaseId' must be an atom and refer to a database loader.</li>
%% </ul>
%%
%% Returns:
%% <ul>
%% <li>`ok' if the database is wholesome.</li>
%% <li>`{error, database_unknown}' if the database loader for `DatabaseId' hasn't been started.</li>
%% <li>`{error, database_not_loaded}' if the database hasn't yet been loaded.</li>
%% <li>`{validation_warnings, [CheckWarning, ...]}' in case something smells within the database
%% (check the definitions in {@link locus_mmdb_check})
%% </li>
%% <li>`{validation_errors, [CheckError], [...]}' in case of corruption or incompatibility
%% (check the definitions in {@link locus_mmdb_check})
%% </li>
%% </ul>
-spec check(DatabaseId) -> ok
| {error, Error}
| {validation_warnings, [ValidationWarning, ...]}
| {validation_errors, [ValidationError, ...], [ValidationWarning]}
when DatabaseId :: atom(),
Error :: database_unknown | database_not_loaded,
ValidationWarning :: locus_mmdb_check:warning(),
ValidationError :: locus_mmdb_check:error().
check(DatabaseId) ->
case locus_database:find(DatabaseId) of
{ok, Database, _Source, _Version} ->
check_(Database);
{error, _} = Error ->
Error
end.
%% ------------------------------------------------------------------
%% CLI-only Function Definitions
%% ------------------------------------------------------------------
-ifdef(ESCRIPTIZING).
-spec main([string()]) -> no_return().
%% @private
main(Args) ->
locus_cli:main(Args).
-endif.
%% ------------------------------------------------------------------
%% Internal Function Definitions
%% ------------------------------------------------------------------
-spec parse_database_edition(database_edition()) -> {maxmind, atom()}.
parse_database_edition({maxmind, Atom})
when is_atom(Atom) ->
{maxmind, Atom};
parse_database_edition({maxmind, Chardata})
when ?might_be_chardata(Chardata) ->
Charlist = unicode:characters_to_list(Chardata),
Atom = list_to_atom(Charlist),
{maxmind, Atom};
parse_database_edition(LegacyMaxMindDatabaseEdition)
when is_atom(LegacyMaxMindDatabaseEdition) ->
{maxmind, LegacyMaxMindDatabaseEdition}.
-spec parse_url(database_url()) -> locus_database:origin() | false.
parse_url(DatabaseURL) ->
case parse_http_url(DatabaseURL) of
Origin when is_tuple(Origin) ->
Origin;
false ->
parse_filesystem_url(DatabaseURL)
end.
parse_http_url(DatabaseURL) when is_list(DatabaseURL) ->
try unicode:characters_to_binary(DatabaseURL) of
<<BinaryChardata/bytes>> ->
parse_http_url(BinaryChardata);
_ ->
false
catch
_:_ -> false
end;
parse_http_url(DatabaseURL) ->
ByteList = binary_to_list(DatabaseURL),
try io_lib:printable_latin1_list(ByteList) andalso
locus_util:parse_absolute_http_url(ByteList)
of
false ->
false;
{ok, _Result} ->
{http, ByteList};
{error, _Reason} ->
false
catch
error:badarg -> false
end.
parse_filesystem_url(DatabaseURL) ->
try unicode:characters_to_list(DatabaseURL) of
Path when is_list(Path) ->
{filesystem, filename:absname(Path)};
{error, _Parsed, _RestData} ->
false;
{incomplete, _Parsed, _RestData} ->
false
catch
error:badarg -> false
end.
-spec database_info(locus_mmdb:database(), locus_loader:source(), calendar:datetime())
-> database_info().
database_info(Database, Source, Version) ->
#{metadata := Metadata} = Database,
#{metadata => Metadata, source => Source, version => Version}.
opts_with_defaults(Opts) ->
[{event_subscriber, locus_logger} | Opts].
launch_waiters(ReplyRef, Timeout, UniqueDatabaseIds) ->
[{DatabaseId, locus_waiter:start(ReplyRef, DatabaseId, Timeout)}
|| DatabaseId <- UniqueDatabaseIds].
perform_wait(_ReplyRef, [], Successes, Failures) ->
case map_size(Failures) =:= 0 of
true ->
{ok, Successes};
false ->
{error, {Failures, Successes}}
end;
perform_wait(ReplyRef, WaitersLeft, Successes, Failures) ->
case receive_waiter_reply(ReplyRef) of
{DatabaseId, {ok, Version}} ->
{value, _, RemainingWaitersLeft} = lists:keytake(DatabaseId, 1, WaitersLeft),
UpdatedSuccesses = Successes#{ DatabaseId => Version },
perform_wait(ReplyRef, RemainingWaitersLeft, UpdatedSuccesses, Failures);
{DatabaseId, {error, Reason}} ->
{value, _, RemainingWaitersLeft} = lists:keytake(DatabaseId, 1, WaitersLeft),
UpdatedFailures = Failures#{ DatabaseId => Reason },
perform_wait(ReplyRef, RemainingWaitersLeft, Successes, UpdatedFailures)
end.
receive_waiter_reply(ReplyRef) ->
receive
{ReplyRef, DatabaseId, Reply} ->
{DatabaseId, Reply}
end.
check_(Database) ->
case locus_mmdb_check:run(Database) of
ok ->
ok;
{warnings, Warnings} ->
{validation_warnings, Warnings};
{errors, Errors, Warnings} ->
{validation_errors, Errors, Warnings}
end.