%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1997-2012. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%%*********************************************************************
%%%
%%% Description: Module handling the internal database in the table tool.
%%%
%%%*********************************************************************
-module(tv_db).
-compile([{nowarn_deprecated_function,{gs,config,2}},
{nowarn_deprecated_function,{gs,destroy,1}},
{nowarn_deprecated_function,{gs,start,0}},
{nowarn_deprecated_function,{gs,window,3}}]).
-export([dbs/2]).
-include("tv_int_def.hrl").
-include("tv_int_msg.hrl").
-include("tv_db_int_def.hrl").
%%%*********************************************************************
%%% EXTERNAL FUNCTIONS
%%%*********************************************************************
dbs(Master, ErrMsgMode) ->
process_flag(trap_exit, true),
put(error_msg_mode, ErrMsgMode),
ProcVars = #process_variables{master_pid = Master},
blocked(ProcVars).
%%%********************************************************************
%%% INTERNAL FUNCTIONS
%%%********************************************************************
blocked(ProcVars) ->
receive
Msg ->
case Msg of
#dbs_deblock{} ->
deblock(Msg, ProcVars, false);
{error_msg_mode, Mode} ->
put(error_msg_mode, Mode),
blocked(ProcVars);
{'EXIT', Pid, Reason} ->
MasterPid = ProcVars#process_variables.master_pid,
exit_signals({Pid, Reason}, MasterPid),
blocked(ProcVars);
_Other ->
blocked(ProcVars)
end
end.
deblock(Msg, ProcVars, SearchWinCreated) ->
#dbs_deblock{sender = Sender,
etsread_pid = EtsreadPid,
type = Type,
keypos = KeyPos,
sublist_length = SublistLength} = Msg,
NewDbData = #db_data{subset_size = SublistLength,
subset_pos = 1,
key_no = KeyPos,
ets_type = Type
},
NewProcVars = ProcVars#process_variables{db_data = NewDbData,
etsread_pid = EtsreadPid},
Sender ! #dbs_deblock_cfm{sender = self()},
deblocked_loop(NewProcVars, SearchWinCreated, [], undefined).
deblocked_loop(ProcVars, SearchWinCreated, SearchData, RegExp) ->
receive
Msg ->
case Msg of
{gs,entry,keypress,_Data,['Return' | _T]} ->
NewSearchData = search_object(ProcVars, RegExp),
deblocked_loop(ProcVars, SearchWinCreated, NewSearchData, RegExp);
{gs,entry,keypress,_Data,['Tab' | _T]} ->
gs:config(entry, [{select, {0,1000}}]),
deblocked_loop(ProcVars, SearchWinCreated, SearchData, RegExp);
{gs,entry,keypress,_Data,_Args} ->
deblocked_loop(ProcVars, SearchWinCreated, SearchData, RegExp);
{gs,expr_term,click,_Data,_Args} ->
deblocked_loop(ProcVars, SearchWinCreated, SearchData, false);
{gs,expr_regexp,click,_Data,_Args} ->
deblocked_loop(ProcVars, SearchWinCreated, SearchData, true);
{gs,search,click,_Data,_Args} ->
NewSearchData = search_object(ProcVars, RegExp),
deblocked_loop(ProcVars, SearchWinCreated, NewSearchData, RegExp);
{gs,cancel,click,cancel,_Args} ->
tv_db_search:destroy_window(SearchWinCreated),
deblocked_loop(ProcVars, false, [], RegExp);
{gs,listbox,click,_LbData,[Idx | _T]} when SearchData =/= [] ->
tv_db_search:mark_busy(SearchWinCreated),
{Row,_Obj} = lists:nth(Idx+1, SearchData),
DbData = ProcVars#process_variables.db_data,
%% Never allow 'subset_pos' to have zero as value!
%% No list can begin with the 0:th element!!!
%% Has to be at least 1!
NewDbData = DbData#db_data{subset_pos=?COMM_FUNC_FILE:max(1,
Row),
subset_size=?ITEMS_TO_DISPLAY},
NewProcVars = ProcVars#process_variables{db_data=NewDbData},
send_subset(NewProcVars, undefined, undefined),
tv_db_search:mark_nonbusy(SearchWinCreated),
deblocked_loop(NewProcVars, SearchWinCreated, SearchData, RegExp);
{gs,win,configure,_Data,_Args} ->
tv_db_search:resize_window(SearchWinCreated),
deblocked_loop(ProcVars, SearchWinCreated, SearchData, RegExp);
{gs,win,destroy,_Data,_Args} ->
deblocked_loop(ProcVars, false, [], RegExp);
#dbs_new_data{data = NewData, keys = ListOfKeys,
time_to_read_table = ElapsedTimeEtsread} ->
tv_db_search:reset_window(SearchWinCreated),
T1 = time(),
NewProcVars = update_db(NewData, ListOfKeys, ProcVars),
T2 = time(),
ElapsedTimeDbs = compute_elapsed_seconds(T1, T2),
send_subset(NewProcVars, ElapsedTimeEtsread, ElapsedTimeDbs),
deblocked_loop(NewProcVars, SearchWinCreated, [], RegExp);
#dbs_subset_req{subset_pos = Pos,subset_length = Length} ->
DbData = ProcVars#process_variables.db_data,
%% Never allow 'subset_pos' to have zero as value!
%% No list can begin with the 0:th element!!!
%% Has to be at least 1!
NewDbData = DbData#db_data{subset_pos=?COMM_FUNC_FILE:max(1,
Pos),
subset_size=Length},
NewProcVars = ProcVars#process_variables{db_data = NewDbData},
send_subset(NewProcVars, undefined, undefined),
deblocked_loop(NewProcVars, SearchWinCreated, SearchData, RegExp);
#dbs_marked_row{row_no = RowNo} ->
DbData = ProcVars#process_variables.db_data,
NewDbData = DbData#db_data{requested_row = RowNo},
NewProcVars = ProcVars#process_variables{db_data = NewDbData},
deblocked_loop(NewProcVars, SearchWinCreated, SearchData, RegExp);
#dbs_search_req{} ->
tv_db_search:create_window(SearchWinCreated),
deblocked_loop(ProcVars, true, SearchData, false);
#dbs_sorting_mode{} ->
{NewProcVars, NewSearchData} =
update_sorting_mode(Msg, ProcVars,
SearchWinCreated, SearchData, RegExp),
deblocked_loop(NewProcVars, SearchWinCreated, NewSearchData, RegExp);
#dbs_deblock{} ->
tv_db_search:reset_window(SearchWinCreated),
deblock(Msg, ProcVars, SearchWinCreated);
#dbs_updated_object{object=Obj,old_object=OldObj,old_color=Color,obj_no=ObjNo} ->
{Success, NewProcVars} = update_object(Obj, OldObj, Color, ObjNo, ProcVars),
case Success of
true ->
tv_db_search:reset_window(SearchWinCreated),
send_subset(NewProcVars, undefined, undefined);
false ->
done
end,
deblocked_loop(NewProcVars, SearchWinCreated, SearchData, RegExp);
#dbs_new_object{object=Obj} ->
{Success, NewProcVars} = new_object(Obj, ProcVars),
case Success of
true ->
tv_db_search:reset_window(SearchWinCreated),
send_subset(NewProcVars, undefined, undefined);
false ->
done
end,
deblocked_loop(NewProcVars, SearchWinCreated, SearchData, RegExp);
#dbs_delete_object{object=Obj, color=Color, obj_no=ObjNo} ->
{Success, NewProcVars} = delete_object(Obj, Color, ObjNo, ProcVars),
case Success of
true ->
tv_db_search:reset_window(SearchWinCreated),
send_subset(NewProcVars, undefined, undefined);
false ->
done
end,
deblocked_loop(NewProcVars, SearchWinCreated, SearchData, RegExp);
#pc_list_info{lists_as_strings=ListAsStr} ->
NewProcVars = ProcVars#process_variables{lists_as_strings=ListAsStr},
deblocked_loop(NewProcVars, SearchWinCreated, SearchData, RegExp);
{error_msg_mode, Mode} ->
put(error_msg_mode, Mode),
deblocked_loop(ProcVars, SearchWinCreated, SearchData, RegExp);
{'EXIT', Pid, Reason} ->
MasterPid = ProcVars#process_variables.master_pid,
exit_signals({Pid, Reason}, MasterPid),
deblocked_loop(ProcVars, SearchWinCreated, SearchData, RegExp);
_Other ->
%% io:format("Received message: ~w ~n", [_Other]),
deblocked_loop(ProcVars, SearchWinCreated, SearchData, RegExp)
end
end.
search_object(ProcVars, RegExp) ->
DbData = ProcVars#process_variables.db_data,
DbList = dblist2list(DbData#db_data.db),
ListAsStr = ProcVars#process_variables.lists_as_strings,
case catch tv_db_search:get_input_and_search(DbList, RegExp, ListAsStr) of
{'EXIT', _Reason} ->
tv_db_search:reset_window(true),
[];
List ->
List
end.
update_sorting_mode(Msg, ProcVars, SearchWinCreated, OldSearchData, RegExp) ->
#dbs_sorting_mode{sorting = Sorting,
reverse = Reverse,
sort_key_no = SortKeyNo} = Msg,
DbData = ProcVars#process_variables.db_data,
#db_data{db = DbList,
sorting = OldSorting,
rev_sorting = OldReverse,
sort_key_no = OldSortKeyNo} = DbData,
NewDbList = sort_db_list(DbList, Sorting, OldSorting, Reverse, OldReverse,
SortKeyNo, OldSortKeyNo),
NewDbData = DbData#db_data{db = NewDbList,
sorting = Sorting,
rev_sorting = Reverse,
sort_key_no = SortKeyNo
},
NewProcVars = ProcVars#process_variables{db_data = NewDbData},
send_subset(NewProcVars, undefined, undefined),
SearchData =
case Sorting of
false ->
OldSearchData;
OldSorting when Reverse =:= OldReverse,
SortKeyNo =:= OldSortKeyNo ->
[];
OldSorting when Reverse =:= OldReverse,
OldSortKeyNo =:= undefined->
[];
_Other ->
ListAsStr = ProcVars#process_variables.lists_as_strings,
case catch tv_db_search:update_search(SearchWinCreated,
NewDbList, RegExp,
ListAsStr) of
{'EXIT', _Reason} ->
tv_db_search:reset_window(true),
[];
List ->
List
end
end,
{NewProcVars, SearchData}.
sort_db_list(DbList, Sort, Sort, Rev, Rev, KeyNo, KeyNo) ->
% Already sorted!
DbList;
sort_db_list(DbList, false, _OldSort, _Rev, _OldRev, _KeyNo, _OldKeyNo) ->
% No sorting, i.e., the old list order suffices!
DbList;
sort_db_list(DbList, _Sort, _OldSort, Rev, _OldRev, KeyNo, _OldKeyNo) ->
tv_db_sort:mergesort(KeyNo, DbList, Rev).
send_subset(ProcVars, EtsreadTime, DbsTime) ->
#process_variables{master_pid = MasterPid,
db_data = DbData,
list_of_keys = ListOfKeys} = ProcVars,
#db_data{subset_size = SubsetSize,
subset_pos = SubsetPos,
requested_row = RowNo,
db_size = DbSize,
db = DbList,
max_elem_size = MaxElemSize} = DbData,
RowData = get_requested_row_data(RowNo, DbList),
if
DbSize > 0 ->
Pos = ?COMM_FUNC_FILE:min(SubsetPos, DbSize),
% Requested_data may be shorter than requested, but that's OK,
% pd handles that correctly!
Subset = lists:sublist(DbList, Pos, SubsetSize),
MasterPid ! #dbs_subset{sender = self(),
data = Subset,
subset_pos = Pos,
db_length = DbSize,
list_of_keys = ListOfKeys,
max_elem_size = MaxElemSize,
requested_row = RowData,
required_time_etsread = EtsreadTime,
required_time_dbs = DbsTime
};
true ->
MasterPid ! #dbs_subset{sender = self(),
data = [],
subset_pos = 1,
db_length = 0,
list_of_keys = ListOfKeys,
max_elem_size = MaxElemSize,
requested_row = RowData,
required_time_etsread = EtsreadTime,
required_time_dbs = DbsTime
}
end.
get_requested_row_data(undefined, _DbList) ->
[];
get_requested_row_data(_RowNo, []) ->
[];
get_requested_row_data(RowNo, DbList) ->
case catch lists:nth(RowNo, DbList) of
{'EXIT', _Reason} ->
[];
RowData ->
[RowData]
end.
exit_signals(ExitInfo, MasterPid) ->
case ExitInfo of
{MasterPid, _Reason} ->
% When from master, just quit!
exit(normal);
_Other ->
done
end.
update_db(NewList, ListOfKeys, ProcVars) ->
DbData = ProcVars#process_variables.db_data,
#db_data{db = OldDbList,
max_elem_size = MaxElemSize,
deleted = DelList,
ets_type = EtsType,
sorting = Sorting,
rev_sorting = RevSorting,
sort_key_no = SortKeyNo,
key_no = KeyNo} = DbData,
DbList = update_colors(OldDbList -- DelList),
OldList = dblist2list(DbList),
InsOrUpd = (NewList -- OldList),
DelOrUpd = (OldList -- NewList),
{Inserted, Deleted, Updated} = group_difflists(basetype(EtsType), KeyNo,
InsOrUpd,
DelOrUpd),
DelMarked = mark_deleted(KeyNo, Deleted, DbList),
Replaced = replace_elements(KeyNo, Updated, DelMarked),
NewDbList = add_elements(KeyNo, Inserted, Replaced, Sorting, RevSorting,
SortKeyNo),
NewMaxSize = ?COMM_FUNC_FILE:max(MaxElemSize,
?COMM_FUNC_FILE:max(max_size(Replaced),
max_size(Inserted))),
NewDbData = DbData#db_data{db = NewDbList,
db_size = length(NewDbList),
max_elem_size = NewMaxSize,
deleted = list2dblist(Deleted, ?BLACK)
},
ProcVars#process_variables{db_data = NewDbData,
list_of_keys = ListOfKeys
}.
update_object(Obj, OldObj, OldColor, ObjNo, ProcVars) ->
#process_variables{db_data = DbData,
etsread_pid = EtsreadPid} = ProcVars,
#db_data{key_no = KeyNo} = DbData,
%% Don't update if there are no changes!
case OldObj of
Obj when OldColor =/= ?BLACK -> %% Allow deleted objects to be inserted!
gs:window(dbwin, gs:start(), []),
case get(error_msg_mode) of
normal ->
tv_utils:notify(dbwin, "TV Notification", ["The object is unchanged!"]);
haiku ->
tv_utils:notify(dbwin, "TV Notification",
["Stay the patient course,",
"Of little worth is your ire:",
"The object's unchanged." ])
end,
gs:destroy(dbwin),
{false, ProcVars};
_Other ->
%% Before we try to update the internal database, we have to check to see
%% whether the ETS/Mnesia update is allowed!
Result =
case OldColor of
?BLACK ->
EtsreadPid ! #etsread_new_object{sender = self(),
object = Obj},
receive
#etsread_new_object_cfm{success = Success} ->
Success
after
60000 ->
exit(etsread_not_responding)
end;
_OtherColor ->
EtsreadPid ! #etsread_update_object{sender = self(),
key_no = KeyNo,
object = Obj,
old_object = OldObj},
receive
#etsread_update_object_cfm{success = Success} ->
Success
after
60000 ->
exit(etsread_not_responding)
end
end,
case Result of
false ->
gs:window(dbwin, gs:start(), [beep]),
case get(error_msg_mode) of
normal ->
tv_utils:notify(dbwin, "TV Notification",
["Couldn't update table!"]);
haiku ->
tv_utils:notify(dbwin, "TV Notification",
["Three things are certain:",
"Death, taxes, and lost updates.",
"Guess which has occurred."])
end,
gs:destroy(dbwin),
{false, ProcVars};
true ->
{true, update_object2(Obj, OldObj, OldColor, ObjNo, ProcVars)}
end
end.
update_object2(Obj, OldObj, OldColor, ObjNo, ProcVars) ->
#process_variables{db_data = DbData} = ProcVars,
#db_data{db = DbList,
ets_type = EtsType, %% 'bag', 'set', 'ordered_set' or
%% 'duplicate_bag'
max_elem_size = MaxElemSize,
sorting = Sorting,
rev_sorting = RevSorting,
sort_key_no = SortKeyNo,
key_no = KeyNo} = DbData,
%% Replace the old element...
Key = element(KeyNo, Obj),
OldKey = element(KeyNo, OldObj),
%% If Key == OldKey, the old object shall only be replaced!
%% Otherwise the updated object shall be treated as a new
%% object when inserting it in the list!
%% In that latter case, we also have to check for duplicates!
Fun =
case basetype(EtsType) of
set ->
case Key of
OldKey ->
fun({Data,Color}, {Replaced,AccDb}) when element(KeyNo,Data) =/= Key ->
{Replaced, [{Data,Color} | AccDb]};
({_Data,Color}, {Replaced,AccDb}) when not Replaced,
OldColor =:= ?BLACK,
Color =:= ?BLACK ->
{true, [{Obj,?RED1} | AccDb]};
({_Data,Color}, {Replaced,AccDb}) when not Replaced,
OldColor =/= ?BLACK,
Color =/= ?BLACK ->
{true, [{Obj,?GREEN1} | AccDb]};
({_Data,_Color}, {Replaced,AccDb}) ->
{Replaced, AccDb}
end;
_NewKey ->
fun({Data,Color}, {Replaced,AccDb}) ->
ElemKey = element(KeyNo,Data),
case ElemKey of
OldKey when not Replaced,
OldColor =:= ?BLACK,
Color =:= ?BLACK ->
{true, [{Obj,?RED1} | AccDb]};
OldKey when not Replaced,
OldColor =/= ?BLACK,
Color =/= ?BLACK ->
{true, [{Obj,?GREEN1} | AccDb]};
OldKey ->
{Replaced, AccDb};
Key ->
{Replaced, AccDb};
_OtherKey ->
{Replaced, [{Data,Color} | AccDb]}
end
end
end;
bag ->
case Key of
OldKey ->
fun({Data,_Color}, {Replaced,AccDb}) when Data =:= Obj ->
{Replaced, AccDb};
({Data,Color}, {Replaced,AccDb}) when Data =/= OldObj ->
{Replaced, [{Data,Color} | AccDb]};
%% Clauses when Data =:= OldObj.
({_Data,Color}, {Replaced,AccDb}) when not Replaced,
OldColor =:= ?BLACK,
Color =:= ?BLACK ->
{true, [{Obj,?RED1} | AccDb]};
({_Data,Color}, {Replaced,AccDb}) when not Replaced,
OldColor =/= ?BLACK,
Color =/= ?BLACK ->
{true, [{Obj,Color} | AccDb]};
({_Data,_Color}, {Replaced,AccDb}) ->
{Replaced, AccDb}
end;
_NewKey ->
fun({Data,Color}, {Replaced,AccDb}) when Data =:= OldObj,
not Replaced,
OldColor =:= ?BLACK,
Color =:= ?BLACK ->
{true, [{Obj,?RED1} | AccDb]};
({Data,Color}, {Replaced,AccDb}) when Data =:= OldObj,
not Replaced,
OldColor =/= ?BLACK,
Color =/= ?BLACK ->
{true, [{Obj,?GREEN1} | AccDb]};
({Data,_Color}, {Replaced,AccDb}) when Data =:= OldObj ->
{Replaced, AccDb};
({Data,_Color}, {Replaced,AccDb}) when Data =:= Obj ->
{Replaced, AccDb};
({Data,Color}, {Replaced,AccDb}) ->
{Replaced, [{Data,Color} | AccDb]}
end
end;
duplicate_bag ->
%% Multiple identical objects allowed, meaning that we shall not
%% remove anything, just replace one element.
case Key of
OldKey ->
fun({Data,Color}, {Replaced,AccDb}) when Data =:= Obj ->
{Replaced, [{Data,Color} | AccDb]};
({Data,Color}, {Replaced,AccDb}) when Data =/= OldObj ->
{Replaced, [{Data,Color} | AccDb]};
({_Data,Color}, {Replaced,AccDb}) when not Replaced,
OldColor =:= ?BLACK,
Color =:= ?BLACK ->
{true, [{Obj,?RED1} | AccDb]};
({_Data,Color}, {Replaced,AccDb}) when not Replaced,
OldColor =/= ?BLACK,
Color =/= ?BLACK ->
{true, [{Obj,Color} | AccDb]};
({Data,Color}, {Replaced,AccDb}) ->
{Replaced, [{Data,Color} | AccDb]}
end;
_NewKey ->
fun({Data,Color}, {Replaced,AccDb}) when Data =:= OldObj,
not Replaced,
OldColor =:= ?BLACK,
Color =:= ?BLACK ->
{true, [{Obj,?RED1} | AccDb]};
({Data,Color}, {Replaced,AccDb}) when Data =:= OldObj,
not Replaced,
OldColor =/= ?BLACK,
Color =/= ?BLACK ->
{true, [{Obj,?GREEN1} | AccDb]};
({Data,Color}, {Replaced,AccDb}) when Data =:= OldObj ->
{Replaced, [{Data,Color} | AccDb]};
({Data,Color}, {Replaced,AccDb}) when Data =:= Obj ->
{Replaced, [{Data,Color} | AccDb]};
({Data,Color}, {Replaced,AccDb}) ->
{Replaced, [{Data,Color} | AccDb]}
end
end
end,
FilterFun = fun(Acc0, L) ->
lists:foldl(Fun, Acc0, L)
end,
{Repl, TmpList} =
case split(ObjNo, DbList) of
{L1, [{OldObj,OldColor} | T]} when OldColor =/= ?BLACK ->
{true,
lists:reverse(element(2, FilterFun({true,[]}, L1))) ++
[{Obj,?GREEN1} | lists:reverse(element(2, FilterFun({true,[]},T)))]};
{L1, [{OldObj,OldColor} | T]} ->
{true,
lists:reverse(element(2, FilterFun({true,[]}, L1))) ++
[{Obj,?RED1} | lists:reverse(element(2, FilterFun({true,[]}, T)))]};
{L1, L2} ->
{R1, NewL1} = FilterFun({false,[]}, L1),
{R2, NewL2} = FilterFun({false,[]}, L2),
{R1 or R2, lists:reverse(NewL1) ++ lists:reverse(NewL2)}
end,
NewDbList =
case Repl of
true when not Sorting ->
TmpList;
true ->
tv_db_sort:mergesort(SortKeyNo, TmpList, RevSorting);
false ->
TmpList2 =
case Key of
OldKey ->
lists:reverse(element(2, FilterFun({false,[]}, TmpList)));
_OtherKey ->
lists:reverse(element(2, FilterFun({true,[]}, TmpList))) ++
[{Obj,?RED1}]
end,
case Sorting of
false ->
TmpList2;
true ->
tv_db_sort:mergesort(SortKeyNo, TmpList2, RevSorting)
end
end,
NewMaxSize = ?COMM_FUNC_FILE:max(MaxElemSize, max_size([Obj])),
NewDbData = DbData#db_data{db = NewDbList,
db_size = length(NewDbList),
max_elem_size = NewMaxSize
},
ProcVars#process_variables{db_data = NewDbData}.
delete_object(_Obj, ?BLACK, _ObjNo, ProcVars) ->
%% Don't delete already deleted objects!!!
{false, ProcVars};
delete_object(undefined, undefined, _ObjNo, ProcVars) ->
{false, ProcVars};
delete_object(Obj, _ObjColor, ObjNo, ProcVars) ->
#process_variables{db_data = DbData,
etsread_pid = EtsreadPid} = ProcVars,
#db_data{db = DbList,
deleted = OldDeleted} = DbData,
%% Before we try to update the internal database, we have to check to see
%% whether the ETS/Mnesia update is allowed!
EtsreadPid ! #etsread_delete_object{sender = self(),
object = Obj},
Result =
receive
#etsread_delete_object_cfm{success = Success} ->
Success
after
60000 ->
exit(etsread_not_responding)
end,
case Result of
false ->
gs:window(dbwin, gs:start(), [beep]),
case get(error_msg_mode) of
normal ->
tv_utils:notify(dbwin, "TV Notification",
["Couldn't update table!"]);
haiku ->
tv_utils:notify(dbwin, "TV Notification",
["Three things are certain:",
"Death, taxes, and lost updates.",
"Guess which has occurred."])
end,
gs:destroy(dbwin),
{false, ProcVars};
true ->
%% Replace the old element...
%% Have to beware of duplicate_bag tables,
%% i.e., the same object may occur more than
%% once, but we only want to remove it once!
{Repl, TmpList} =
case split(ObjNo, DbList) of
{L1, [{Obj,_Color} | T]} ->
{true, L1 ++ [{Obj,?BLACK} | T]};
{L1, L2} ->
{false, L1 ++ L2}
end,
NewDbList =
case Repl of
true ->
TmpList;
false ->
Fun = fun({Data,TmpColor},
{Removed,AccDb}) when Data =/= Obj ->
{Removed, [{Data,TmpColor} | AccDb]};
({_Data,TmpColor},
{Removed,AccDb}) when not Removed, TmpColor =/= ?BLACK ->
{true, [{Obj,?BLACK} | AccDb]};
({Data,TmpColor},
{Removed,AccDb}) ->
{Removed, [{Data,TmpColor} | AccDb]}
end,
lists:reverse(element(2, lists:foldl(Fun, {false,[]}, DbList)))
end,
NewDbData = DbData#db_data{db = NewDbList,
db_size = length(NewDbList),
deleted = [{Obj,?BLACK} | OldDeleted]},
{true, ProcVars#process_variables{db_data = NewDbData}}
end.
new_object(Obj, ProcVars) ->
#process_variables{db_data = DbData,
etsread_pid = EtsreadPid} = ProcVars,
#db_data{db = DbList,
max_elem_size = MaxElemSize,
ets_type = EtsType, %% 'bag', 'set' or 'duplicate_bag'
sorting = Sorting,
rev_sorting = RevSorting,
sort_key_no = SortKeyNo,
key_no = KeyNo} = DbData,
%% Before we try to update the internal database, we have to check to see
%% whether the ETS/Mnesia update is allowed!
EtsreadPid ! #etsread_new_object{sender = self(),
object = Obj},
Result =
receive
#etsread_new_object_cfm{success = Success} ->
Success
after
60000 ->
exit(etsread_not_responding)
end,
case Result of
false ->
gs:window(dbwin, gs:start(), [beep]),
case get(error_msg_mode) of
normal ->
tv_utils:notify(dbwin, "TV Notification",
["Couldn't update table!"]);
haiku ->
tv_utils:notify(dbwin, "TV Notification",
["Three things are certain:",
"Death, taxes, and lost updates.",
"Guess which has occurred."])
end,
gs:destroy(dbwin),
{false, ProcVars};
true ->
Key = element(KeyNo, Obj),
NewDbList = insert_new_object(EtsType, Key, KeyNo, Obj, DbList, Sorting,
RevSorting, SortKeyNo),
NewMaxSize = ?COMM_FUNC_FILE:max(MaxElemSize, max_size([Obj])),
NewDbData = DbData#db_data{db = NewDbList,
db_size = length(NewDbList),
max_elem_size = NewMaxSize
},
{true, ProcVars#process_variables{db_data = NewDbData}}
end.
insert_new_object(EtsType,Key,KeyNo,Obj,DbList,Sorting,RevSorting,SortKeyNo) ->
%% Remove elements from the list that ought not to be there,
%% according to the table type!
Fun =
case basetype(EtsType) of
set ->
fun({Data,Color}, {Replaced,AccDb}) when element(KeyNo,Data) =/= Key ->
{Replaced, [{Data,Color} | AccDb]};
({Data,Color}, {Replaced,AccDb}) when not Replaced,
Color =/= ?BLACK,
Data =/= Obj->
{true, [{Obj,?GREEN1} | AccDb]};
({_Data,Color}, {Replaced,AccDb}) when not Replaced,
Color =/= ?BLACK ->
{true, [{Obj,Color} | AccDb]};
({_Data,Color}, {Replaced,AccDb}) when not Replaced,
Color =:= ?BLACK ->
{true, [{Obj, ?RED1} | AccDb]};
({_Data,Color}, {Replaced,AccDb}) when Replaced,
Color =:= ?BLACK ->
{false, AccDb};
({_Data,_Color}, {Replaced,AccDb}) ->
{Replaced, AccDb}
end;
bag ->
fun({Data,Color}, {Replaced,AccDb}) when Data =/= Obj ->
{Replaced, [{Data,Color} | AccDb]};
({_Data,Color}, {Replaced,AccDb}) when not Replaced,
Color =/= ?BLACK ->
{true, [{Obj,Color} | AccDb]};
({_Data,Color}, {Replaced,AccDb}) when Replaced,
Color =/= ?BLACK ->
{true, AccDb};
({_Data,Color}, {Replaced,AccDb}) when Replaced,
Color =:= ?BLACK ->
{true, AccDb};
({_Data,Color}, {Replaced,AccDb}) when not Replaced,
Color =:= ?BLACK ->
{true, [{Obj, ?RED1} | AccDb]};
({_Data,_Color}, {Replaced,AccDb}) ->
{Replaced, AccDb}
end;
duplicate_bag ->
%% The fun is never called if the type is duplicate_bag,
%% because all we have to do with new elements is to insert
%% them (multiple identical objects allowed).
not_used
end,
FilterFun = fun(Acc0, L) ->
lists:foldl(Fun, Acc0, L)
end,
{_Replaced, TmpDbList} =
case EtsType of
duplicate_bag ->
{false, DbList};
_OtherType ->
{R,L} = FilterFun({false,[]}, DbList),
{R, lists:reverse(L)}
end,
case Sorting of
false ->
TmpDbList ++ [{Obj,?RED1}];
true ->
%% The original list is already sorted!
%% Just merge the two lists together!
tv_db_sort:merge(SortKeyNo, TmpDbList, [{Obj,?RED1}], RevSorting)
end.
max_size([]) ->
0;
max_size(L) ->
max_size(L, 0).
max_size([], CurrMax) ->
CurrMax;
max_size([H | T], CurrMax) when is_tuple(H) ->
Size = size(H),
if
Size >= CurrMax ->
max_size(T, Size);
true ->
max_size(T, CurrMax)
end;
max_size([_H | T], CurrMax) ->
Size = 1,
if
Size >= CurrMax ->
max_size(T, Size);
true ->
max_size(T, CurrMax)
end.
add_elements(_KeyNo, Inserted, List, false, _RevSorting, _SortKeyNo) ->
% Remember that the order of the original list has to be preserved!
List ++ list2dblist(Inserted, ?RED1);
add_elements(_KeyNo, Inserted, List, _Sorting, RevSorting, SortKeyNo) ->
% The original list is already sorted - sort the new elements, and
% just merge the two lists together!
SortedInsertedList = tv_db_sort:mergesort(SortKeyNo,
list2dblist(Inserted, ?RED1),
RevSorting),
tv_db_sort:merge(SortKeyNo, List, SortedInsertedList, RevSorting).
%% We assume the list already has been sorted, i.e., since the order won't
%% be changed by marking an element deleted, we DON'T have to sort the list
%% once again!
mark_deleted(_KeyNo, [], List) ->
List;
mark_deleted(KeyNo, [Data | T], List) ->
KeyValue = tv_db_sort:get_compare_value(KeyNo, Data),
NewList = mark_one_element_deleted(KeyNo, KeyValue, Data, List, []),
mark_deleted(KeyNo, T, NewList).
mark_one_element_deleted(_KeyNo, _KeyValue, _Data, [], Acc) ->
Acc;
mark_one_element_deleted(KeyNo, {tuple, KeyValue},
Data, [{DataTuple, Color} | Tail], Acc) ->
OldKeyValue = tv_db_sort:get_compare_value(KeyNo, DataTuple),
% Remember that the order of the original list has to be preserved!
if
OldKeyValue =:= {tuple, KeyValue} ->
Acc ++ [{Data, ?BLACK}] ++ Tail;
true ->
mark_one_element_deleted(KeyNo, {tuple, KeyValue}, Data, Tail,
Acc ++ [{DataTuple, Color}])
end;
mark_one_element_deleted(KeyNo, _KeyValue, Data, [{DataTuple, Color} | Tail], Acc) ->
if
Data =:= DataTuple ->
Acc ++ [{Data, ?BLACK}] ++ Tail;
true ->
mark_one_element_deleted(KeyNo, _KeyValue, Data, Tail,
Acc ++ [{DataTuple, Color}])
end.
%% We assume the list already has been sorted, i.e., since the order won't
%% be changed by marking an element updated, we DON'T have to sort the list
%% once again!
replace_elements(_KeyNo, [], List) ->
List;
replace_elements(KeyNo, [Data | T], List) ->
KeyValue = tv_db_sort:get_compare_value(KeyNo, Data),
NewList = replace_one_element(KeyNo, KeyValue, Data, List, []),
replace_elements(KeyNo, T, NewList).
replace_one_element(_KeyNo, _Key, _Data, [], Acc) ->
Acc;
replace_one_element(KeyNo, {tuple, Key1}, Data, [{DataTuple, Color} | Tail], Acc) ->
Key2 = tv_db_sort:get_compare_value(KeyNo, DataTuple),
% Remember that the order of the original list has to be preserved!
if
Key2 =:= {tuple, Key1} ->
Acc ++ [{Data, ?GREEN1}] ++ Tail;
true ->
replace_one_element(KeyNo, {tuple, Key1}, Data, Tail,
Acc ++ [{DataTuple, Color}])
end;
replace_one_element(_KeyNo, _KeyValue, _Data, [{DataTuple, Color} | Tail], Acc) ->
% Can't replace an element with no key!
Acc ++ [{DataTuple, Color} | Tail].
group_difflists(bag, _KeyNo, Inserted, Deleted) ->
%% Since the ETS table is of bag type, no element can be updated, i.e.,
%% it can only be deleted and re-inserted, otherwise a new element will be added.
{Inserted, Deleted, []};
group_difflists(duplicate_bag, _KeyNo, Inserted, Deleted) ->
%% Since the ETS table is of duplicate_bag type, no element can be updated, i.e.,
%% it can only be deleted and re-inserted, otherwise a new element will be added.
{Inserted, Deleted, []};
group_difflists(set, _KeyNo, [], Deleted) ->
%% Updated elements have to be present in both lists, i.e., if one list is empty,
%% the other contains no updated elements - they are either inserted or deleted!
{[], Deleted, []};
group_difflists(set, _KeyNo, Inserted, []) ->
{Inserted, [], []};
group_difflists(set, KeyNo, InsOrUpd, DelOrUpd) ->
match_difflists(KeyNo, InsOrUpd, DelOrUpd, [], []).
match_difflists(_KeyNo, [], Deleted, Inserted, Updated) ->
{Inserted, Deleted, Updated};
match_difflists(KeyNo, [Data | T], DelOrUpd, InsAcc, UpdAcc) ->
% This function is only called in case of a 'set' ETS table.
% 'Set' type of ETS table means there are unique keys. If two elements in
% InsOrUpd and DelOrUpd have the same key, that element has been updated,
% and is added to the Updated list, and removed from the original two lists.
% After the two lists have been traversed in this way, the remaining elements
% in DelOrUpd forms the new Deleted list (analogous for InsOrUpd).
% If we want to improve the performance, we could check which list is the
% shortest, since the traversing time depends on this.
Key = element(KeyNo, Data),
case searchdelete(Key, KeyNo, DelOrUpd) of
{true, NewDelOrUpd} ->
match_difflists(KeyNo, T, NewDelOrUpd, InsAcc, [Data | UpdAcc]);
{false, SameDelOrUpd} ->
match_difflists(KeyNo, T, SameDelOrUpd, [Data | InsAcc], UpdAcc)
end.
searchdelete(_Key, _ElemNo, []) ->
{false, []};
searchdelete(Key, ElemNo, List) ->
searchdelete(Key, ElemNo, List, []).
searchdelete(_Key, _ElemNo, [], Acc) ->
{false, Acc};
searchdelete(Key, ElemNo, [Tuple | Tail], Acc) ->
% We don't use standard libraries, 'cause we want to make an 'atomic'
% operation, i.e., we will not search the list two times...
case (element(ElemNo, Tuple) =:= Key) of
true ->
{true, Acc ++ Tail}; % Return the list without the matching element
_Other ->
searchdelete(Key, ElemNo, Tail, [Tuple | Acc])
end.
dblist2list([]) ->
[];
dblist2list([{Data, _Color} | T]) ->
[Data | dblist2list(T)].
list2dblist([], _Color) ->
[];
list2dblist([Data | T], Color) ->
[{Data, Color} | list2dblist(T, Color)].
update_colors([]) ->
[];
update_colors([{Data, Color} | T]) ->
[{Data, new_color(Color)} | update_colors(T)].
new_color(?GREEN1) ->
?GREEN2;
new_color(?GREEN2) ->
?GREEN3;
new_color(?GREEN3) ->
?GREEN4;
new_color(?GREEN4) ->
?GREEN5;
new_color(?GREEN5) ->
?DEFAULT_BTN_COLOR;
new_color(?RED1) ->
?RED2;
new_color(?RED2) ->
?RED3;
new_color(?RED3) ->
?RED4;
new_color(?RED4) ->
?RED5;
new_color(?RED5) ->
?DEFAULT_BTN_COLOR;
new_color(_Other) ->
?DEFAULT_BTN_COLOR. % Default shall be gray.
compute_elapsed_seconds({H1, M1, S1}, {H2, M2, S2}) ->
ElapsedHours = get_time_diff(hours, H1, H2),
ElapsedMinutes = get_time_diff(minutes, M1, M2),
ElapsedSeconds = get_time_diff(seconds, S1, S2),
(ElapsedHours * 3600) + (ElapsedMinutes * 60) + ElapsedSeconds + 1.
get_time_diff(_Type, T1, T2) when T1 =< T2 ->
T2 - T1;
get_time_diff(hours, T1, T2) ->
T2 + 24 - T1;
get_time_diff(minutes, T1, T2) ->
T2 + 60 - T1;
get_time_diff(seconds, T1, T2) ->
T2 + 60 - T1.
split(_N, []) ->
{[], []};
split(0, List) ->
{[], List};
split(N, List) ->
split2(0, N - 1, [], List).
split2(Ctr, N, Acc, [H | T]) when Ctr < N ->
split2(Ctr + 1, N, [H | Acc], T);
split2(_Ctr, _N, Acc, []) ->
{lists:reverse(Acc), []};
split2(_Ctr, _N, Acc, List) ->
{lists:reverse(Acc), List}.
basetype(ordered_set) ->
set;
basetype(Any) ->
Any.