class EL_COMPRESSED_ARCHIVE_FILE
Client examples: COMPRESSION_TEST_SET
Compressed archive file
note
description: "Compressed archive 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-08 16:54:59 GMT (Wednesday 8th November 2023)"
revision: "18"
class
EL_COMPRESSED_ARCHIVE_FILE
inherit
RAW_FILE
rename
append_file as append_file_contents
export
{NONE} all
{ANY} close, last_string, name, start, end_of_file, position,
is_closed, is_open_read, is_open_write, path, date,
open_read, open_write, make_open_write, make_open_read
redefine
make_with_name, after, off
end
EL_FILE_OPEN_ROUTINES
rename
Append as Append_to
end
EL_MODULE_CHECKSUM; EL_MODULE_FILE; EL_MODULE_FILE_SYSTEM; EL_MODULE_LIO
EL_MODULE_ZLIB
EL_SHARED_DATA_TRANSFER_PROGRESS_LISTENER
create
make_open_write, make_open_read, make_default
feature {NONE} -- Initialization
make_default
do
make_with_name ("none")
end
make_with_name (fn: READABLE_STRING_GENERAL)
-- Create file object with `fn' as file name
do
Precursor (fn)
create last_file_path
create last_data.make_empty (0)
enable_checksum
level := 9
expected_compression_ratio := 0.4
end
feature -- Access
file_list: EL_ARRAYED_MAP_LIST [INTEGER, FILE_PATH]
require
open_read: is_open_read and then position = 0
local
done: BOOLEAN
do
file_count := 0
create Result.make (100)
from start until after or done loop
read_file_name
if last_file_path.is_empty then
done := True
else
read_content_size
Result.extend (last_uncompressed_count, last_file_path)
end
end
end
last_file_path: FILE_PATH
last_data: SPECIAL [NATURAL_8]
-- data read by `read_compressed_file'
last_uncompressed_count: INTEGER
feature -- Measurement
expected_compression_ratio: DOUBLE
file_count: INTEGER
level: INTEGER
feature -- Element change
set_expected_compression_ratio (a_expected_compression_ratio: DOUBLE)
do
expected_compression_ratio := a_expected_compression_ratio
end
set_level (a_level: INTEGER)
do
level := a_level
end
feature -- Status change
disable_checksum
do
is_checksum_enabled := False
end
enable_checksum
do
is_checksum_enabled := True
end
feature -- Status query
is_checksum_enabled: BOOLEAN
is_last_data_ok: BOOLEAN
-- `True' if `last_data' was read without error
feature -- Basic operations
append_file_list (list: ITERABLE [FILE_PATH])
require
open_append: is_open_write
files_exists: across list as l all l.item.exists end
valid_expected_compression_ratio: expected_compression_ratio > 0.0
valid_level: level > 0
do
across list as l_path loop
progress_listener.increase_file_data_estimate (l_path.item)
end
across list as l_path loop
append_file (l_path.item)
end
progress_listener.finish
end
decompress_all (handler: EL_FILE_DECOMPRESS_HANDLER)
require
open_read: is_open_read and then position = 0
local
done: BOOLEAN
do
file_count := 0
if attached file_list as list then
from list.start until list.after loop
progress_listener.increase_data_estimate (list.item_key)
list.forth
end
end
from start until after or done loop
read_file_name
if last_file_path.is_empty then
done := True
else
read_compressed_file (handler)
end
end
progress_listener.finish
end
write_last_data (a_file_path: FILE_PATH)
-- write content of `last_data' to file `a_file_path'
do
if attached open_raw (a_file_path, Write) as output_file then
Data_pointer.set_from_pointer (last_data.base_address, last_data.count)
output_file.put_managed_pointer (Data_pointer, 0, last_data.count)
output_file.close
end
end
feature {NONE} -- Implementation
append_file (a_file_path: FILE_PATH)
require
open_append: is_open_write
local
file_data: MANAGED_POINTER; compressed_data: SPECIAL [NATURAL_8]
l_checksum: NATURAL
do
file_data := File.data (a_file_path)
if is_checksum_enabled then
l_checksum := Checksum.data (file_data)
end
compressed_data := Zlib.compressed (file_data, level, expected_compression_ratio)
if attached a_file_path.to_utf_8 as utf_8_path then
put_integer (utf_8_path.count)
put_string (utf_8_path)
end
put_integer (file_data.count)
put_integer (compressed_data.count)
if is_checksum_enabled then
put_natural (l_checksum)
end
Data_pointer.set_from_pointer (compressed_data.base_address, compressed_data.count)
put_managed_pointer (Data_pointer, 0, Data_pointer.count)
progress_listener.on_notify (file_data.count)
end
read_compressed_file (handler: EL_FILE_DECOMPRESS_HANDLER)
-- results available in last_string
require
open_read: is_open_read
local
compressed_data: MANAGED_POINTER; l_checksum, actual_checksum: NATURAL
do
read_integer
last_uncompressed_count := last_integer
if is_lio_enabled then
lio.put_integer_field ("READ: last_uncompressed_count", last_uncompressed_count)
end
read_integer
create compressed_data.make (last_integer)
if is_checksum_enabled then
read_natural
l_checksum := last_natural
end
if is_lio_enabled then
lio.put_integer_field (" compressed_data.count", compressed_data.count)
end
read_to_managed_pointer (compressed_data, 0, compressed_data.count)
last_data := Zlib.decompressed (compressed_data, last_uncompressed_count)
if is_checksum_enabled then
Data_pointer.set_from_pointer (last_data.base_address, last_data.count)
actual_checksum := Checksum.data (Data_pointer)
if is_lio_enabled then
lio.put_string_field (" actual_checksum", actual_checksum.out)
end
is_last_data_ok := l_checksum = actual_checksum
if is_last_data_ok then
handler.on_decompressed (Current, file_count + 1)
progress_listener.on_notify (last_uncompressed_count)
else
handler.on_decompression_error (last_file_path, True)
end
if is_lio_enabled then
if is_last_data_ok then
lio.put_string (" OK")
else
lio.put_string (" ERROR")
end
lio.put_new_line
end
else
is_last_data_ok := True
handler.on_decompressed (Current, file_count + 1)
progress_listener.on_notify (last_uncompressed_count)
end
file_count := file_count + 1
end
read_content_size
local
compressed_data_count: INTEGER
do
read_integer
last_uncompressed_count := last_integer
read_integer
compressed_data_count := last_integer
if is_checksum_enabled then
read_natural
end
move (compressed_data_count)
file_count := file_count + 1
end
read_file_name
require
open_read: is_open_read
local
file_name_count: INTEGER; l_file_path: ZSTRING
do
if (count - position) > {PLATFORM}.Integer_32_bytes then
read_integer
file_name_count := last_integer
if (count - position) > file_name_count
and then 4 <= file_name_count and file_name_count <= Maximum_file_name_count
then
read_stream (file_name_count)
create l_file_path.make_from_utf_8 (last_string)
last_file_path := l_file_path
if is_lio_enabled then
lio.put_path_field ("Read", last_file_path)
lio.put_new_line
end
else
create last_file_path
end
else
create last_file_path
end
end
feature -- Status query
after: BOOLEAN
-- Is there no valid cursor position to the right of cursor position?
do
Result := not is_closed and then count = position
end
off: BOOLEAN
-- Is there no item?
do
Result := (count = 0) or else is_closed or else count = position
end
feature {NONE} -- Constants
Data_pointer: MANAGED_POINTER
once
create Result.share_from_pointer (Default_pointer, 0)
end
Maximum_file_name_count: INTEGER
once
Result := 500
end
end