-module(graffeo_config). -moduledoc """ Application-level configuration for graffeo's on-disk backends. The primary export is `data_dir/0`, which resolves where DETS (and future Mnesia) files are stored. Resolution chain: 1. Explicit `data_dir` from `sys.config` / `application:set_env` — used as-is. 2. `code:priv_dir(graffeo)/data` — if the directory is writable. 3. `graffeo_data/` under the current working directory — Mnesia-style writable fallback. 4. The OS user-cache directory — last resort. **Production note:** `priv/` is often read-only in an OTP release. Production deployments should set `data_dir` explicitly in `sys.config`. The priv default is a dev/test/example convenience. """. -export([data_dir/0, resolve_candidates/1, build_candidates/1]). -doc "Resolve the data directory for on-disk backends.". -spec data_dir() -> file:filename_all(). data_dir() -> case application:get_env(graffeo, data_dir, default) of default -> Candidates = build_candidates(code:priv_dir(graffeo)), {ok, Dir} = resolve_candidates(Candidates), Dir; Dir when is_list(Dir) -> ensure(Dir); Dir when is_binary(Dir) -> ensure(binary_to_list(Dir)) end. -doc """ Build the ordered candidate list from a `code:priv_dir/1` result. Exported for testability: pass `{error, bad_name}` to simulate a missing priv directory and exercise the fallback chain. """. -spec build_candidates(file:filename_all() | {error, term()}) -> [file:filename_all()]. build_candidates({error, _}) -> ["graffeo_data", filename:basedir(user_cache, "graffeo")]; build_candidates(PrivDir) -> [ filename:join(PrivDir, "data"), "graffeo_data", filename:basedir(user_cache, "graffeo") ]. -doc """ Return the first writable candidate directory. Tries each directory in order; returns `{ok, Dir}` for the first one where `filelib:ensure_dir/1` succeeds, or `error` if none are writable. """. -spec resolve_candidates([file:filename_all()]) -> {ok, file:filename_all()} | error. resolve_candidates([]) -> error; resolve_candidates([Dir | Rest]) -> case filelib:ensure_dir(filename:join(Dir, "probe")) of ok -> {ok, Dir}; {error, _} -> resolve_candidates(Rest) end. -spec ensure(file:filename_all()) -> file:filename_all(). ensure(Dir) -> ok = filelib:ensure_dir(filename:join(Dir, "probe")), Dir.