summaryrefslogtreecommitdiff
path: root/src/index.erl
blob: 9b0be817735b88e3712bbeb1fdff8960227dbce5 (plain)
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
%%
%% Copyright (c) 2014 Kungliga Tekniska Högskolan
%% (KTH Royal Institute of Technology, Stockholm, Sweden).
%%

%% Implements an interface to a file pair (basename and basename.chksum)
%% that stores an ordered list of fixed-size entries. Entries can be
%% added at the end and are retrieved by index. The list can also be
%% truncated.
%%
%% Writes(add, truncate, addlast) need to be serialized.

%% TODO: Checksums

-module(index).
-export([get/2, add/3, addlast/2, truncate/2]).

-define(ENTRYSIZE, 32).
-define(ENTRYSIZEINFILE, (?ENTRYSIZE*2+1)).

-spec add(string(), integer() | last, binary()) -> ok.
add(Basepath, Index, Entry) when is_binary(Entry), size(Entry) == ?ENTRYSIZE ->
    case file:open(Basepath, [read, write, binary]) of
        {ok, File} ->
            {ok, Position} = file:position(File, eof),
            case Index of
                last when Position rem ?ENTRYSIZEINFILE == 0 ->
                    ok;
                Index when is_integer(Index),
                           Index * ?ENTRYSIZEINFILE == Position ->
                    ok
            end,
            EntryText = hex:bin_to_hexstr(Entry) ++ "\n",
            ok = file:write(File, EntryText),
            ok = file:close(File),
            util:fsync([Basepath, filename:dirname(Basepath)]);
        {error, Error} ->
            util:exit_with_error(Error, writefile,
                                 "Error opening file for writing")
    end.

truncate(Basepath, Index) ->
    case file:open(Basepath, [read, write, binary]) of
        {ok, File} ->
            {ok, _Position} = file:position(File, Index * ?ENTRYSIZEINFILE),
            ok = file:truncate(File),
            ok = file:close(File),
            util:fsync([Basepath, filename:dirname(Basepath)]);
        {error, Error} ->
            util:exit_with_error(Error, writefile,
                                 "Error opening file for writing")
    end.


-spec addlast(string(), integer()) -> ok.
addlast(Basepath, Entry) ->
    add(Basepath, last, Entry).

%% From lib/stdlib/src/lists.erl. For supporting < R17.
-spec droplast(nonempty_list()) -> list().
droplast([_T]) -> [];
droplast([H|T]) -> [H|droplast(T)].

decodedata(EntryText) when length(EntryText) == ?ENTRYSIZEINFILE ->
    case [lists:last(EntryText)] of
        "\n" ->
            hex:hexstr_to_bin(droplast(EntryText));
        _ ->
            util:exit_with_error(badformat, readindex,
                                 "Index line not ending with linefeed")
    end.

-spec get(string(), integer()) -> binary().
get(Basepath, Index) ->
    case file:open(Basepath, [read, binary]) of
        {ok, File} ->
            {ok, Filesize} = file:position(File, eof),
            if
                Index * ?ENTRYSIZEINFILE + ?ENTRYSIZEINFILE =< Filesize ->
                    {ok, _Position} = file:position(File,
                                                    Index * ?ENTRYSIZEINFILE),
                    {ok, EntryText} = file:read(File, ?ENTRYSIZEINFILE),
                    Entry = decodedata(binary_to_list(EntryText)),
                    file:close(File),
                    Entry;
                true ->
                    noentry
            end;
        {error, Error} ->
            util:exit_with_error(Error, readfile,
                                 "Error opening file for reading")
    end.