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
|
%%%-------------------------------------------------------------------
%% @doc Caches the rendered Openbox pipe menu in memory.
%%
%% Holds the last-rendered menu binary so socket clients get an
%% instant response. `invalidate/0' synchronously rescans the XDG
%% application directories, reparses every `.desktop' file and
%% re-renders the menu, replacing the cached binary.
%% @end
%%%-------------------------------------------------------------------
-module(xdg_pipe_menu_cache).
-behaviour(gen_server).
-export([start_link/0, get_menu/0, invalidate/0, build/1]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2]).
-spec start_link() -> {ok, pid()}.
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec get_menu() -> binary().
get_menu() ->
gen_server:call(?MODULE, get_menu).
-spec invalidate() -> ok.
invalidate() ->
gen_server:cast(?MODULE, invalidate).
%% Pure pipeline: explicit dirs in, rendered menu binary out. Kept
%% side-effect-free (besides reading the filesystem) and exported so
%% it can be unit tested directly and reused by the CLI client's
%% no-daemon fallback, without going through the gen_server at all.
-spec build([file:filename_all()]) -> binary().
build(Dirs) ->
Files = xdg_pipe_menu_scanner:find_desktop_files(Dirs),
Entries = lists:filtermap(fun parse/1, Files),
xdg_pipe_menu_renderer:render(Entries).
parse(File) ->
case xdg_pipe_menu_desktop_entry:parse_file(File) of
{ok, Entry} -> {true, Entry};
{error, _} -> false
end.
init([]) ->
{ok, build(xdg_pipe_menu_scanner:data_dirs())}.
handle_call(get_menu, _From, Menu) ->
{reply, Menu, Menu}.
handle_cast(invalidate, _Menu) ->
{noreply, build(xdg_pipe_menu_scanner:data_dirs())}.
handle_info(_Info, State) ->
{noreply, State}.
|