class ECD_CHAIN
Client examples: STORABLE_STRING_LIST
Chain of storable items which can be saved to and read from a file. The chain has the following features:
ECD_CHAIN* [G -> EL_STORABLE create make_default end] ECD_STORABLE_ARRAYED_LIST [G -> EL_STORABLE create make_default end] EL_TRANSLATION_ITEMS_LIST ECD_RECOVERABLE_CHAIN* [G -> EL_STORABLE create make_default end] EL_COMMA_SEPARATED_WORDS_LIST AIA_STORABLE_CREDENTIAL_LIST ECD_REFLECTIVE_RECOVERABLE_CHAIN* [G -> EL_REFLECTIVELY_SETTABLE_STORABLE create make_default end] COUNTRY_DATA_TABLE
Items must implement either the class EL_STORABLE or EL_REFLECTIVELY_SETTABLE_STORABLE.
The descendant class ECD_RECOVERABLE_CHAIN can be used to implement a proper indexed transactional database when used in conjunction with class ECD_REFLECTIVE_RECOVERABLE_CHAIN.
The routine safe_store stores the complete chain in a temporary file and then does a quick check on the integrity of the save by checking all the item headers. Only then is the stored file substituted for the previously stored file.
note
description: "[
Chain of storable items which can be saved to and read from a file. The chain has the following
features:
* Support for AES encryption
* Ability to mark items for deletion without actually having to remove them immediately. This allows
implementations like class ${ECD_REFLECTIVE_RECOVERABLE_CHAIN} to support field indexing.
* Ability to store software version information which is available to the item
implementing ${EL_STORABLE}.
]"
descendants: "See end of class"
notes: "See end of class"
to_do: "[
Change `delete' routine to replace item with a shared default deleted item. This will allow deleted item to
be garbage collected
]"
author: "Finnian Reilly"
copyright: "Copyright (c) 2001-2022 Finnian Reilly"
contact: "finnian at eiffel hyphen loop dot com"
license: "MIT license (See: en.wikipedia.org/wiki/MIT_License)"
date: "2024-04-01 14:41:39 GMT (Monday 1st April 2024)"
revision: "36"
deferred class
ECD_CHAIN [G -> EL_STORABLE create make_default end]
inherit
CHAIN [G]
rename
append as append_sequence
export
{ANY} remove
undefine
-- Status query
is_equal, isfirst, islast, valid_index, is_inserted, readable, there_exists, has,
-- Element change
append_sequence, copy, prune_all, prune, move, put_i_th, swap, force,
-- Cursor movement
start, finish, go_i_th, search,
-- Access
at, first, last, off, i_th, remove, index_of, new_cursor,
-- Basic operations
do_all, do_if, for_all
end
EL_FILE_PERSISTENT
redefine
make_from_file
end
EL_ENCRYPTABLE
undefine
copy, is_equal
redefine
set_encrypter, make_default
end
EL_STORABLE_HANDLER
EL_FILE_OPEN_ROUTINES
rename
Append as Append_to
end
EL_SHARED_PROGRESS_LISTENER
feature {NONE} -- Initialization
make_default
do
if not attached encrypter then
encrypter := Default_encrypter
end
reader_writer := new_reader_writer
create header.make
make_chain_implementation (0)
end
make_chain_implementation (a_count: INTEGER)
deferred
end
make_from_file (a_file_path: FILE_PATH)
local
l_file: like new_file
do
make_default
Precursor (a_file_path)
if file_path.exists then
l_file := new_file (file_path)
l_file.open_read
header.set_from_file (l_file)
if header.version /= software_version then
on_version_mismatch (header.version)
end
make_chain_implementation (header.stored_count)
else
make_chain_implementation (0)
create l_file.make_open_write (file_path)
put_header (l_file)
header.set_version (software_version)
end
l_file.close
retrieve
end
make_from_file_and_encrypter (a_file_path: FILE_PATH; a_encrypter: EL_AES_ENCRYPTER)
--
do
encrypter := a_encrypter
make_from_file (a_file_path)
end
feature -- Measurement
deleted_count: INTEGER
undeleted_count: INTEGER
do
Result := count - deleted_count
end
store_tick_count: INTEGER
do
Result := undeleted_count
end
feature -- Access
software_version: NATURAL
-- Format of application version.
deferred
end
feature -- Basic operations
for_all_undeleted (do_with_item: PROCEDURE [G])
do
from start until after loop
if not item.is_deleted then
do_with_item (item)
end
forth
end
end
store_as (a_file_path: like file_path)
local
l_file: like new_file
l_writer: like reader_writer
do
-- log.enter_with_args ("store_as", << a_file_path >>)
encrypter.reset
l_file := new_file (a_file_path)
l_file.open_write
l_writer := reader_writer
l_writer.set_for_writing
put_header (l_file)
from start until after loop
-- log.put_integer_field ("Writing item", index); log.put_new_line
if not item.is_deleted then
l_writer.write (item, l_file)
progress_listener.notify_tick
end
forth
end
l_file.close
-- log.exit
end
feature -- Element change
set_encrypter (a_encrypter: EL_AES_ENCRYPTER)
--
do
encrypter := a_encrypter
if attached {EL_ENCRYPTABLE} reader_writer as rw then
rw.set_encrypter (a_encrypter)
end
end
feature -- Removal
delete
-- mark item as deleted so it will not be saved during the next `store_as' operation
do
item.delete
deleted_count := deleted_count + 1
on_delete
end
compact
-- Remove any deleted items
do
if deleted_count > 0 then
from start until after loop
if item.is_deleted then
remove
else
forth
end
end
deleted_count := 0
end
end
feature -- Status query
is_encrypted: BOOLEAN
do
Result := not encrypter.is_default
end
has_version_mismatch: BOOLEAN
-- True if data version differs from software version
do
if attached reader_writer then
Result := not reader_writer.is_default_data_version
elseif file_path.exists then
Result := header.version /= software_version
end
end
feature {NONE} -- Event handler
on_delete
deferred
end
on_version_mismatch (actual_version: NATURAL)
do
reader_writer.set_data_version (actual_version)
end
feature {NONE} -- Factory
new_file (a_file_path: like file_path): RAW_FILE
do
create Result.make_with_name (a_file_path)
end
new_reader_writer: ECD_READER_WRITER [G]
do
if is_encrypted then
if descendants.is_empty then
create {ECD_ENCRYPTABLE_READER_WRITER [G]} Result.make (encrypter)
else
create {ECD_ENCRYPTABLE_MULTI_TYPE_READER_WRITER [G]} Result.make (descendants, encrypter)
end
elseif descendants.is_empty then
create Result.make
else
create {ECD_MULTI_TYPE_READER_WRITER [G]} Result.make (descendants)
end
end
feature {ECD_EDITIONS_FILE} -- Implementation
put_header (a_file: RAW_FILE)
do
a_file.put_natural_32 (software_version)
a_file.put_integer (undeleted_count)
end
retrieve
local
i, item_count: INTEGER
do
item_count := header.stored_count
if item_count > 0 then
encrypter.reset
if attached new_file (file_path) as l_file then
l_file.open_read
if attached reader_writer as l_reader then
l_reader.set_for_reading
-- Skip header
l_file.move (header.size_of)
from i := 1 until i > item_count or l_file.end_of_file loop
extend (l_reader.read_item (l_file))
progress_listener.notify_tick
i := i + 1
end
l_file.close
end
end
end
ensure
correct_stored_count: count = header.stored_count
end
stored_successfully (a_file: like new_file): BOOLEAN
local
i, l_count: INTEGER
do
a_file.read_natural -- Version
a_file.read_integer -- Record count
l_count := a_file.last_integer
from until i = l_count or a_file.end_of_file loop
a_file.read_integer -- Count
if not a_file.end_of_file then
a_file.move (a_file.last_integer)
end
i := i + 1
end
Result := i = undeleted_count
end
feature {ECD_EDITIONS_FILE} -- Implementation atttributes
header: ECD_CHAIN_HEADER
reader_writer: like new_reader_writer
feature {NONE} -- Constants
Descendants: ARRAY [TYPE [G]]
do
create Result.make_empty
end
note
notes: "[
Items must implement either the
class ${EL_STORABLE} or ${EL_REFLECTIVELY_SETTABLE_STORABLE}.
The descendant class ${ECD_RECOVERABLE_CHAIN} can be used to implement a proper
indexed transactional database when used in conjunction with class ${ECD_REFLECTIVE_RECOVERABLE_CHAIN}.
The routine `safe_store' stores the complete chain in a temporary file and then does a quick check
on the integrity of the save by checking all the item headers. Only then is the stored file substituted
for the previously stored file.
]"
descendants: "[
ECD_CHAIN* [G -> EL_STORABLE create make_default end]
${ECD_STORABLE_ARRAYED_LIST [G -> EL_STORABLE create make_default end]}
${EL_TRANSLATION_ITEMS_LIST}
${ECD_RECOVERABLE_CHAIN* [G -> EL_STORABLE create make_default end]}
${EL_COMMA_SEPARATED_WORDS_LIST}
${AIA_STORABLE_CREDENTIAL_LIST}
${ECD_REFLECTIVE_RECOVERABLE_CHAIN* [G -> EL_REFLECTIVELY_SETTABLE_STORABLE create make_default end]}
${COUNTRY_DATA_TABLE}
]"
end