%%% Copyright (c) 2014, NORDUnet A/S. %%% See LICENSE for licensing information. %% 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.