%% Copyright (c) 2017-Present Pivotal Software, Inc. All rights reserved. %% %% This package, Looking Glass, is double-licensed under the Mozilla %% Public License 1.1 ("MPL") and the Apache License version 2 %% ("ASL"). For the MPL, please see LICENSE-MPL-RabbitMQ. For the ASL, %% please see LICENSE-APACHE2. %% %% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, %% either express or implied. See the LICENSE file for specific language governing %% rights and limitations of this software. %% %% If you have any questions regarding licensing, please contact us at %% info@rabbitmq.com. -module(lg_file_reader). -export([fold/3]). -export([foreach/2]). -export([open/1]). -export([read_event/1]). -export([close/1]). -record(state, { io_device :: file:io_device(), ctx :: lz4f:dctx(), buffer = <<>> :: binary(), offset = 0 :: non_neg_integer(), uncompressed_offset = 0 :: non_neg_integer() }). %% High level API. fold(Fun, Acc, Filename) -> {ok, IoDevice} = open(Filename), Ctx = lz4f:create_decompression_context(), Ret = fold1(#state{io_device=IoDevice, ctx=Ctx}, Fun, Acc), ok = close(IoDevice), Ret. fold1(State0, Fun, Acc0) -> case read_event(State0) of {ok, Event, State} -> Acc = Fun(Event, Acc0), fold1(State, Fun, Acc); eof -> {ok, Acc0}; Error = {error, _, _} -> Error end. foreach(Fun, Filename) -> {ok, IoDevice} = open(Filename), Ctx = lz4f:create_decompression_context(), Ret = foreach1(#state{io_device=IoDevice, ctx=Ctx}, Fun), ok = close(IoDevice), Ret. foreach1(State0, Fun) -> case read_event(State0) of {ok, Event, State} -> _ = Fun(Event), foreach1(State, Fun); eof -> ok; Error = {error, _, _} -> Error end. %% Low level API. open(Filename) -> file:open(Filename, [read, binary]). read_event(State=#state{buffer=Buffer}) -> case Buffer of <> -> convert_event_body(State#state{buffer=Rest}, Bin); _ -> read_file(State) end. read_file(State=#state{io_device=IoDevice, ctx=Ctx, buffer=Buffer, offset=Offset}) -> case file:read(IoDevice, 1000) of {ok, Data0} -> Data = iolist_to_binary(lz4f:decompress(Ctx, Data0)), read_event(State#state{buffer= <>, offset=Offset + byte_size(Data0)}); eof -> eof; {error, Reason} -> {error, Reason, 'An error occurred while trying to read from the file.'} end. convert_event_body(State=#state{offset=Offset, uncompressed_offset=UnOffset}, Bin) -> try binary_to_term(Bin) of Term -> {ok, Term, State#state{uncompressed_offset=UnOffset + byte_size(Bin)}} catch Class:Reason -> {error, {crash, Class, Reason, Offset, UnOffset}, 'The binary form of an event could not be decoded to an Erlang term.'} end. close(IoDevice) -> file:close(IoDevice).