class ECD_EDITIONS_FILE

(source code)

description

Eco-DB chain editions file

note
	description: "Eco-DB chain editions file"

	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: "2023-11-05 9:30:50 GMT (Sunday 5th November 2023)"
	revision: "19"

class
	ECD_EDITIONS_FILE [G -> EL_STORABLE create make_default end]

inherit
	RAW_FILE
		rename
			make as make_file,
			count as file_count
		export
			{ECD_RECOVERABLE_CHAIN} set_path
		redefine
			delete
		end

	EL_SHARED_PROGRESS_LISTENER

create
	make

feature -- Initialization

	make (a_storable_chain: like item_chain)
		do
			item_chain := a_storable_chain
			reader_writer := item_chain.reader_writer
			create crc.make
			make_with_name (Default_name)
		end

feature -- Access

	actual_count: INTEGER
		-- count of editions found

	delta_count: INTEGER
		-- difference that applied editions have made to `item_chain.count'

	count: INTEGER
		-- reported edition count

	crc: EL_CYCLIC_REDUNDANCY_CHECK_32

	extended_byte_count: INTEGER
		-- total data bytes that extend list

	item_chain: ECD_CHAIN [G]

	kilo_byte_count: REAL
		do
			Result := (file_count / 1024).truncated_to_real
		end

	last_edition_code: CHARACTER

	read_count: INTEGER
		-- count of editions successfully read

feature -- Status report

	has_checksum_mismatch: BOOLEAN
		-- Has the file become corrupted somewhere

	is_bigger: BOOLEAN
		-- `True' if file is bigger since object was created
		do
			Result := file_count > attributes.file_count
		end

	is_read_complete: BOOLEAN
		do
			Result := read_count = count
		end

	is_reset: BOOLEAN
		-- `True' if edition count is zero
		do
			Result := count = 0
		end

feature -- Removal

	close_and_delete
			--
		do
			if not is_closed then
				close
			end
			if exists then
				delete
			end
		end

	delete
		do
			Precursor
			count := 0; actual_count := 0; extended_byte_count := 0
			crc.reset
		end

feature {ECD_RECOVERABLE_CHAIN} -- Element change

	set_attributes (a_file_path: FILE_PATH)
		local
			i, l_position: INTEGER
		do
			make_with_name (a_file_path)
			if exists then
				attributes := [file_count, date]
				open_read
				if not is_empty then
					read_header
					from i := 1 until i > count or end_of_file loop
						read_edition_code
						l_position := position
						if not end_of_file then
							skip_edition (last_edition_code)
							if last_edition_code = Code_extend then
								extended_byte_count := extended_byte_count + position - l_position
							end
						end
						i := i + 1
					end
					actual_count := i - 1
				end
				close
			else
				attributes := [0, 0]
				open_write; put_header; close
			end
		end

feature {ECD_RECOVERABLE_CHAIN} -- Basic operations

	apply
		local
			i: INTEGER
		do
			if exists and then not is_empty then
				open_read
				read_header
				from i := 1 until i > actual_count or has_checksum_mismatch or end_of_file loop
					read_edition
					i := i + 1
				end
				close
				read_count := i - 1
			end
			reopen
		end

	put_edition (edition_code: CHARACTER; a_item: G)
		do
			put_character (edition_code)
			crc.add_character_8 (edition_code)
			inspect edition_code when Code_delete, Code_remove, Code_replace then
				put_integer (item_chain.index)
				crc.add_integer (item_chain.index)
			else
			end
			inspect edition_code when Code_extend, Code_replace then
				reader_writer.set_for_writing
				reader_writer.write (a_item, Current)
				crc.add_data (reader_writer.data)
			else
			end
			put_natural (crc.checksum)
			count := count + 1
			-- Write count at start
			start; put_header; finish
			flush
		end

	restore_date
		do
			if attributes.date_stamp > 0 then
				set_date (attributes.date_stamp)
			end
		end

feature -- Status change

	reopen
		do
			if exists then
				open_read_write
				finish
			else
				open_write; put_header; close
				reopen
			end
		end

feature {NONE} -- Implementation

	put_header
		do
			put_integer (count)
		end

	read_edition
		local
			edition_index: INTEGER
			l_item: G
		do
			read_edition_code
			crc.add_character_8 (last_edition_code)
			inspect last_edition_code when Code_delete, Code_remove, Code_replace then
				read_integer
				edition_index := last_integer
				crc.add_integer (edition_index)
			else
			end
			inspect last_edition_code when Code_extend, Code_replace then
				reader_writer.set_for_reading
				l_item := reader_writer.read_item (Current)
				crc.add_data (reader_writer.data)
			else
			end
			read_natural
			checksum := last_natural
			if checksum = crc.checksum then
				inspect last_edition_code
					when Code_delete then
						item_chain.go_i_th (edition_index); item_chain.delete

					when Code_extend then
						item_chain.extend (l_item)
						delta_count := delta_count + 1

					when Code_remove then
						item_chain.go_i_th (edition_index); item_chain.remove
						delta_count := delta_count - 1

					when Code_replace then
						item_chain.go_i_th (edition_index); item_chain.replace (l_item)

				else
				end
			else
				has_checksum_mismatch := True
			end
			progress_listener.notify_tick
		end

	read_edition_code
		do
			read_character
			last_edition_code := last_character
		end

	read_header
		-- read expected count
		do
			read_integer_32
			count := last_integer_32
		end

	skip_edition (edition_code: CHARACTER)
		local
			item_size: INTEGER
		do
			inspect edition_code when Code_remove, Code_replace then
				read_integer -- list index
			else
			end
			if not end_of_file then
				inspect edition_code when Code_replace, Code_extend then
					read_integer
					item_size := last_integer
				else
					if not end_of_file then
						move (item_size)
					end
				end
			end
			if not end_of_file then
				read_natural -- checksum
			end
		end

	skip_header
		do
			move ({PLATFORM}.integer_32_bytes)
		end

feature {NONE} -- Internal attributes

	checksum: NATURAL

	reader_writer: ECD_READER_WRITER [G]

	attributes: TUPLE [file_count, date_stamp: INTEGER]

feature -- Constants

	Code_delete: CHARACTER = '4'

	Code_extend: CHARACTER = '2'

	Code_remove: CHARACTER = '3'

	Code_replace: CHARACTER = '1'

feature {NONE} -- Constants

	Minimum_editions_to_integrate: REAL
			-- Minimum file size in kb of editions to integrate with main XML body.
		once
			Result := 50 -- Kb
		end

	Default_name: STRING = "default"

end