Files
secondo/apis/python1/api/support/secondoinputhandler.py
2026-01-23 17:03:45 +08:00

379 lines
11 KiB
Python

# ----------------------------------------------------------------------------------------------------------------------
# The Secondo Python API (pySecondo)
# Victor Silva (victor.silva@posteo.de)
# January 2020
# ----------------------------------------------------------------------------------------------------------------------
# Module
# ----------------------------------------------------------------------------------------------------------------------
# Secondo Input Handler
# secondoinputhandler.py
# ----------------------------------------------------------------------------------------------------------------------
"""
The module Secondo Input Handler handles the conversion of different values from a socket stream in form of binary
values. The implementation is based on the Java-Class MyInputStreamReader of the Secondo JavaGUI and supports the
reception and construction of a list expression object originated through the response to an inquiry (or command) from
the |sec| server.
"""
import struct
from socket import socket
import secondodb.api.secondoapi as api
import secondodb.api.support.secondocommands as com
import secondodb.api.support.secondolistexpr as listexpr
encoding = 'Latin-1'
# Constants describing codings for binary writing of a nested list
BIN_LONGLIST = 0
BIN_INTEGER = 1
BIN_REAL = 2
BIN_BOOLEAN = 3
BIN_LONGSTRING = 4
BIN_LONGSYMBOL = 5
BIN_LONGTEXT = 6
BIN_LIST = 10
BIN_SHORTLIST = 11
BIN_SHORTINT = 12
BIN_BYTE = 13
BIN_STRING = 14
BIN_SHORTSTRING = 15
BIN_SYMBOL = 16
BIN_SHORTSYMBOL = 17
BIN_TEXT = 18
BIN_SHORTTEXT = 19
BIN_DOUBLE = 20
def read_line_of_text(socket_object: socket) -> str:
"""
Returns a single text line from a response. The input stream will be read until the occurrence of a new line.
:param socket_object: A socket object.
:return: A string with a single text line.
"""
byte_array = bytearray()
while True:
byte = socket_object.recv(1)
try:
if byte != b'\n':
byte_array.append(byte[0])
else:
break
except IndexError:
raise api.OperationalError('Error: Invalid response from server.')
try:
string: str = byte_array.decode(encoding)
except UnicodeDecodeError as e:
raise api.InterfaceError(e.args[0])
else:
return string
def read_string(socket_object: socket, size: int) -> str:
"""
Reads a string of a specified size from a socket object.
:param socket_object: A socket object with the response from the |sec| server.
:param size: The size of the string.
:return: A string value.
"""
byte_array = bytearray()
for i in range(0, size):
byte = socket_object.recv(1)
byte_array.append(byte[0])
try:
string: str = byte_array.decode(encoding)
except UnicodeDecodeError as e:
raise api.InterfaceError(e.args[0])
else:
return string
def read_short(socket_object: socket) -> int:
"""
Reads a short value from the socket response. A short value takes 2 bytes to be built.
:param socket_object: A socket object with the response from the |sec| server.
:return: A short value.
"""
byte_vector = socket_object.recv(2)
short_value: int = struct.unpack('>H', byte_vector)[0]
return short_value
def read_byte(socket_object: socket) -> bytes:
"""
Reads a single byte from the socket response.
:param socket_object: A socket object with the response from the |sec| server.
:return: A byte value.
"""
byte_value = socket_object.recv(1)
return byte_value
def read_int(socket_object: socket) -> int:
"""
Reads an integer value from the socket response. An integer value takes 4 bytes to be built.
:param socket_object: A socket object with the response from the |sec| server.
:return: An integer value.
"""
byte_vector = socket_object.recv(4)
int_value: int = struct.unpack('>I', byte_vector)[0]
return int_value
def read_real(socket_object: socket) -> float:
"""
Reads a real value from the socket response.
:param socket_object: A socket object with the response from the |sec| server.
:return: A float representing the real value.
"""
byte_vector = socket_object.recv(4)
real_value: float = struct.unpack('>f', byte_vector)[0]
return real_value
def read_bool(socket_object: socket) -> bool:
"""
Reads a boolean value from the socket response.
:param socket_object: A socket object with the response from the |sec| server.
:return: A boolean value.
"""
int_value = read_byte(socket_object)[0]
if int_value == 0:
return False
else:
return True
def read_double(socket_object: socket) -> float:
"""
Reads a double value from the socket response.
:param socket_object: A socket object with the response from the |sec| server.
:return: A float representing the double value.
"""
byte_vector = socket_object.recv(8)
double_value: float = struct.unpack('>d', byte_vector)[0]
return double_value
def read_long(socket_object: socket) -> int:
"""
Reads a long value from the socket response.
:param socket_object: A socket object with the response from the |sec| server.
:return: A long (int) value.
"""
byte_vector = socket_object.recv(8)
long_value: int = struct.unpack('>Q', byte_vector)[0]
return long_value
def read_binary_record(socket_object: socket) -> listexpr.ListExp:
"""
Reads on each iteration a single buffered vector of bytes and translates it to the specified type. Some types
requiere the specification of the length for the buffer. The length is normally expressed as the first binary value
after the specification of the type integer.
:param socket_object: A socket object with the response from the |sec| server.
:return: None
"""
# Read byte from the socket to identify the type
type_value = read_byte(socket_object)
if type_value[0] == BIN_LONGLIST:
length = read_int(socket_object)
if length == 0:
return listexpr.ListExp()
else:
le_f = read_binary_record(socket_object)
le_le = listexpr.one_element_list(le_f)
le_last = le_le
for i in range(1, length):
le_next = read_binary_record(socket_object)
le_last = listexpr.append_to_last_element(le_last, le_next)
return le_le
elif type_value[0] == BIN_INTEGER:
int_value = read_int(socket_object)
return listexpr.create_integer_atom(int_value)
elif type_value[0] == BIN_REAL:
real_value = read_real(socket_object)
return listexpr.create_real_atom(real_value)
elif type_value[0] == BIN_BOOLEAN:
bool_value = read_bool(socket_object)
return listexpr.create_bool_atom(bool_value)
elif type_value[0] == BIN_LONGSTRING:
length = read_int(socket_object)
string_value = read_string(socket_object, length)
return listexpr.create_string_atom(string_value)
elif type_value[0] == BIN_LONGSYMBOL:
length = read_int(socket_object)
symbol_value = read_string(socket_object, length)
return listexpr.create_symbol_atom(symbol_value)
elif type_value[0] == BIN_LONGTEXT:
length = read_int(socket_object)
text = read_string(socket_object, length)
return listexpr.create_text_atom(text)
elif type_value[0] == BIN_LIST:
length = read_short(socket_object)
if length == 0:
return listexpr.ListExp()
else:
le_f = read_binary_record(socket_object)
le_le = listexpr.one_element_list(le_f)
le_last = le_le
for i in range(1, length):
le_next = read_binary_record(socket_object)
le_last = listexpr.append_to_last_element(le_last, le_next)
return le_le
elif type_value[0] == BIN_SHORTLIST:
length = read_byte(socket_object)[0]
if length == 0:
return listexpr.ListExp()
else:
le_f = read_binary_record(socket_object)
le_le = listexpr.one_element_list(le_f)
le_last = le_le
for i in range(1, length):
le_next = read_binary_record(socket_object)
le_last = listexpr.append_to_last_element(le_last, le_next)
return le_le
elif type_value[0] == BIN_SHORTINT:
short_int_value = read_short(socket_object)
return listexpr.create_integer_atom(short_int_value)
elif type_value[0] == BIN_BYTE:
return listexpr.create_integer_atom(read_byte(socket_object)[0])
elif type_value[0] == BIN_STRING:
length = read_short(socket_object)
string = read_string(socket_object, length)
return listexpr.create_string_atom(string)
elif type_value[0] == BIN_SHORTSTRING:
length = read_byte(socket_object)[0]
string_value = read_string(socket_object, length)
return listexpr.create_string_atom(string_value)
elif type_value[0] == BIN_SYMBOL:
length = read_short(socket_object)
symbol_value = read_string(socket_object, length)
return listexpr.create_symbol_atom(symbol_value)
elif type_value[0] == BIN_SHORTSYMBOL:
length = read_byte(socket_object)[0]
symbol_value = read_string(socket_object, length)
return listexpr.create_symbol_atom(symbol_value)
elif type_value[0] == BIN_TEXT:
length = read_short(socket_object)
text = read_string(socket_object, length)
return listexpr.create_text_atom(text)
elif type_value[0] == BIN_SHORTTEXT:
length = read_byte(socket_object)[0]
text = read_string(socket_object, length)
return listexpr.create_text_atom(text)
elif type_value[0] == BIN_DOUBLE:
double_value = read_double(socket_object)
return listexpr.create_real_atom(double_value)
def build_list_expr_from_binary(socket_object: socket) -> listexpr.ListExp:
"""
This method builds a list expression object from a binary response from the |sec| server.
In javagui: readBinaryFrom()
:param socket_object:
:return: A list expression object with the contents of the nested list.
"""
# Read and check signature string 'bnl'
signature = read_string(socket_object, 3)
if signature != com.SECONDO_VALIDITY_STRING:
raise api.InterfaceError('Wrong validity string')
# Read and check version
version_1 = read_short(socket_object) # major
version_2 = read_short(socket_object) # minor
if version_1 != 1 or (version_2 != 0 and version_2 != 1 and version_2 != 2):
raise api.InterfaceError('Wrong version number: ' + str(version_1) + "/" + str(version_2))
# Create list expression from binary
list_expr = read_binary_record(socket_object)
return list_expr