%%%--------------------------------------------------------------------------- %%% Copyright (C) 2021, Fix Solution All Rights Reserved %%% Unauthorized copy of this file is through any medium strictly not allowed. %%% %%% Authors : Alpha Shaw %%% Created : 20 Apr 2021 by %%% Purpose : %%% %%% %%% Licensed under the Apache License, Version 2.0 (the "License"); %%% you may not use this file except in compliance with the License. %%% You may obtain a copy of the License at %%% %%% http://www.apache.org/licenses/LICENSE-2.0 %%% %%% Unless required by applicable law or agreed to in writing, software %%% distributed under the License is distributed on an "AS IS" BASIS, %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% See the License for the specific language governing permissions and %%% limitations under the License. %%%--------------------------------------------------------------------------- -module(timestamp). -author("Alpha Umaru Shaw"). -behaviour(gen_server). %% API -export([start_link/0]). -export([since_epoch/0, time_units/0, last_timestamp/0]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -define(SERVER, ?MODULE). -define(TS_TABLE, ts_table). -define(LAST_TS, last_ts). -define(TIMESTAMP_SAVE_INTERVAL, 100). -record(state, { dets_table, dets_file }). %%%=================================================================== %%% API %%%=================================================================== time_units() -> Ms = ew_time:micros(), {Ms, erlang:round(Ms / 1000)}. start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). last_timestamp() -> gen_server:call(?SERVER, read_last_ts, 5000). %%%=================================================================== %%% gen_server callbacks %%%=================================================================== init([]) -> process_flag(trap_exit, true), File = "/tmp/" ++ atom_to_list(node()) ++ "/" ++ "flake-timestamp", case touch_dirs(File) of {error, Reason} -> {error, Reason}; _ -> {ok, Table} = dets:open_file(?TS_TABLE, [ {estimated_no_objects, 10}, {type, set}, {file, File} ]), case read_last_timestamp(Table) of {ok, LTS} -> CurrentTs = ew_time:millis(), if LTS > CurrentTs -> {stop, clock_is_backward}; true -> erlang:send_after(?TIMESTAMP_SAVE_INTERVAL, self(), write_ts), {ok, #state{dets_table = Table, dets_file = File}} end end end. handle_call(read_last_ts, _From, #state{dets_table = Table} = State) -> {reply, read_last_timestamp(Table), State}; handle_call(_Request, _From, State) -> {reply, ok, State}. handle_cast(_Request, State) -> {noreply, State}. handle_info(write_ts, #state{dets_table = Table} = State) -> {ok, _} = write_last_ts(Table), erlang:send_after(?TIMESTAMP_SAVE_INTERVAL, self(), write_ts), {noreply, State}; handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, #state{dets_table = Table}) -> catch dets:close(Table), ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. %%%=================================================================== %%% Internal functions %%%=================================================================== read_last_timestamp(Table) -> case dets:lookup(Table, ?LAST_TS) of [{?LAST_TS, TS}] when is_integer(TS) -> {ok, TS}; _ -> write_last_ts(Table) end. write_last_ts(Table) -> TS = ew_time:millis(), ok = dets:insert(Table, {?LAST_TS, TS}), {ok, TS}. touch_dirs(File) when is_list(File) -> case filelib:ensure_dir(File) of ok -> ok; {error, Reason} -> {error, Reason} end. since_epoch() -> erlang:error(not_implemented).