583 lines
21 KiB
Python
583 lines
21 KiB
Python
import base64
|
|
import re
|
|
import zlib
|
|
import sys
|
|
import gc
|
|
|
|
from json import loads as json_parse, dumps as _json_stringify
|
|
from typing import Dict, Optional, Any
|
|
from datetime import datetime
|
|
import threading
|
|
|
|
from biopro.recording.file import RecordingMetaFile, RecordingFile, RecordingMini
|
|
|
|
from biopro.util.json import JSON_OBJECT
|
|
from .socket import ServerThread
|
|
|
|
import psycopg2
|
|
from psycopg2.extras import execute_batch
|
|
from multiprocessing import Process, Queue
|
|
|
|
import biopro.server._identify
|
|
|
|
from time import time
|
|
|
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
from biopro.util.logger import calculate_time
|
|
|
|
_RUNTIME_COMPILE = False
|
|
|
|
|
|
def json_stringify(o: Any) -> str:
|
|
return _json_stringify(o, separators=(',', ':'))
|
|
|
|
|
|
# noinspection PyUnusedLocal
|
|
class DataBaseProcess(Process):
|
|
"""
|
|
connect to DB and send sql command
|
|
"""
|
|
|
|
def __init__(self, client_id: str, log_verbose, queue_db, queue_ds, queue_error, name = 'Database-Process'):
|
|
super(DataBaseProcess, self).__init__(name = name)
|
|
# super().__init__('DATABASE-' + client_id, MAGENTA)
|
|
|
|
self._db_host = '127.0.0.1'
|
|
self._db_name = 'postgres'
|
|
self._db_user = 'biopro'
|
|
self._db_password = 'BioProControlBox'
|
|
|
|
self._api_host = biopro.server._identify.API_SERVER_HOST
|
|
self._api_port = biopro.server._identify.API_SERVER_PORT
|
|
self._api_token = None
|
|
self._api_header = None
|
|
self.log_verbose = log_verbose
|
|
self._executor = ThreadPoolExecutor(max_workers=8)
|
|
|
|
self._psql_multi_thread = None
|
|
self._psql_conn_pool = None
|
|
self._psql_conn = None
|
|
|
|
self._queue_db = queue_db
|
|
self._queue_ds = queue_ds
|
|
self._queue_error = queue_error
|
|
|
|
self._is_close = False
|
|
|
|
|
|
self._meta_write_sql_str = None
|
|
|
|
self._data_raw_create_sql_str = None
|
|
self._data_raw_update_sql_str = None
|
|
self._data_raw_recording_sql_str = 'UPDATE "public"."%s_recording_data_raws" SET data = concat(data, %s) where id = %s'
|
|
self._new_data_raw_recording_sql_str = 'UPDATE "public"."%s_recording_data_raws" SET data = concat(data, %s), end_time=%s, size=%s where id = %s'
|
|
self._data_mini_recording_sql_str = 'UPDATE "public"."%s_recording_data_minis" SET data_mean = concat(data_mean, %s) where id = %s'
|
|
|
|
@property
|
|
def db_host(self) -> str:
|
|
return self._db_host
|
|
|
|
@property
|
|
def api_host(self) -> str:
|
|
return self._api_host
|
|
|
|
@property
|
|
def api_port(self) -> int:
|
|
return self._api_port
|
|
|
|
@property
|
|
def api_token(self) -> str:
|
|
return self._api_token
|
|
|
|
def run(self) -> None:
|
|
self.setup()
|
|
self.routine()
|
|
|
|
def setup(self) -> None:
|
|
self.log_verbose('DataBaseProcess setup')
|
|
conn_info = "host={0} user={1} dbname={2} password={3}".format(self._db_host, self._db_user, self._db_name, self._db_password)
|
|
try:
|
|
self._psql_conn = psycopg2.connect(conn_info)
|
|
except:
|
|
self.log_verbose('can not connect to database')
|
|
else:
|
|
self.log_verbose('connect to DB ', self._db_host, self._db_name)
|
|
self._is_close = False
|
|
|
|
def routine(self) -> None:
|
|
while self._is_close is False:
|
|
# request = [ function_name, *args]
|
|
request = None
|
|
try:
|
|
request = self._queue_db.get()
|
|
except KeyboardInterrupt:
|
|
self._is_close = True
|
|
if request is not None:
|
|
func_name = request[0]
|
|
args = request[1:]
|
|
if request[0] != 'data_meta_write':
|
|
try:
|
|
self._executor.submit(self.solve_for, func_name, args)
|
|
except RuntimeError as e:
|
|
self.log_verbose('can not submit database sql:', func_name, e)
|
|
else:
|
|
try:
|
|
self.solve_for(func_name, args)
|
|
except RuntimeError as e:
|
|
self.log_verbose('can not submit database sql:', func_name, e)
|
|
del request
|
|
return None
|
|
|
|
def shutdown(self) -> None:
|
|
if self._psql_conn is not None:
|
|
try:
|
|
self._psql_conn.close()
|
|
except:
|
|
self.log_verbose('can not close database')
|
|
else:
|
|
self.log_verbose('closed DB ', self._db_host, self._db_name)
|
|
self._is_close = True
|
|
|
|
def put_queue(self, request: list) -> None:
|
|
self._queue_db.put(request)
|
|
del request
|
|
return
|
|
|
|
def get_queue(self, device_id) -> None:
|
|
task = None
|
|
try:
|
|
task = self._queue_ds[int(device_id)].get()
|
|
except:
|
|
# Handle empty queue here
|
|
pass
|
|
else:
|
|
# Handle task here and call q.task_done()
|
|
pass
|
|
return task
|
|
|
|
def solve_for(self, name: str, args: list):
|
|
do = f"{name}"
|
|
if callable(getattr(self, do)):
|
|
getattr(self, do)(*args)
|
|
return None
|
|
|
|
# @calculate_time(1)
|
|
def data_meta_write(self, _data: JSON_OBJECT, device_id, _uuid = None, _id = None):
|
|
if _id > 0:
|
|
sql_str = 'UPDATE "public"."recording_data_metas" SET '
|
|
sql_set = []
|
|
for item in _data.keys():
|
|
sql_str = sql_str + str(item) + ' = %s, '
|
|
if isinstance(_data[item], dict):
|
|
sql_set.append(json_stringify(_data[item]))
|
|
else:
|
|
sql_set.append(_data[item])
|
|
|
|
sql_str = sql_str[0:-2] + ' WHERE (id = %s)'
|
|
sql_set.append(_id)
|
|
sql_cursor = self._psql_conn.cursor()
|
|
try:
|
|
sql_cursor.execute(sql_str, sql_set)
|
|
except:
|
|
self._psql_conn.commit()
|
|
sql_cursor.close()
|
|
self._queue_error.put(device_id)
|
|
finally:
|
|
self._psql_conn.commit()
|
|
sql_cursor.close()
|
|
|
|
return True
|
|
|
|
if _uuid is not None:
|
|
sql_str = 'SELECT id FROM "public"."recording_data_metas" WHERE uuid = %s'
|
|
sql_set = [_uuid, ]
|
|
sql_cursor = self._psql_conn.cursor()
|
|
sql_id = None
|
|
|
|
# try:
|
|
sql_cursor.execute(sql_str, sql_set)
|
|
sql_id = sql_cursor.fetchone()
|
|
# except RuntimeError as e:
|
|
# print(e)
|
|
|
|
self._psql_conn.commit()
|
|
sql_cursor.close()
|
|
|
|
if sql_id is None:
|
|
sql_str = 'INSERT INTO "public"."recording_data_metas" ('
|
|
sql_set = []
|
|
for item in _data.keys():
|
|
sql_str = sql_str + str(item) + ', '
|
|
if isinstance(_data[item], dict):
|
|
sql_set.append(json_stringify(_data[item]))
|
|
else:
|
|
sql_set.append(_data[item])
|
|
|
|
sql_str = sql_str[0:-2] + ') VALUES ('
|
|
for item in _data.keys():
|
|
sql_str = sql_str + '%s, '
|
|
|
|
sql_str = sql_str[0:-2] + ') RETURNING id'
|
|
sql_cursor = self._psql_conn.cursor()
|
|
|
|
try:
|
|
sql_cursor.execute(sql_str, sql_set)
|
|
except:
|
|
self._psql_conn.commit()
|
|
sql_cursor.close()
|
|
self._queue_error.put(device_id)
|
|
finally:
|
|
self._queue_ds[int(device_id)].put(['update_meta_id', _data['uuid'], int(sql_cursor.fetchone()[0])])
|
|
self._psql_conn.commit()
|
|
sql_cursor.close()
|
|
|
|
else:
|
|
sql_str = 'UPDATE "public"."recording_data_metas" SET '
|
|
sql_set = []
|
|
for item in _data.keys():
|
|
sql_str = sql_str + str(item) + ' = %s, '
|
|
if isinstance(_data[item], dict):
|
|
sql_set.append(json_stringify(_data[item]))
|
|
else:
|
|
sql_set.append(_data[item])
|
|
|
|
sql_str = sql_str[0:-2] + ' WHERE (id = %s)'
|
|
if isinstance(sql_id, tuple):
|
|
sql_id = sql_id[0]
|
|
sql_set.append(sql_id)
|
|
sql_cursor = self._psql_conn.cursor()
|
|
|
|
try:
|
|
sql_cursor.execute(sql_str, sql_set)
|
|
except:
|
|
self._psql_conn.commit()
|
|
sql_cursor.close()
|
|
self._queue_error.put(device_id)
|
|
finally:
|
|
self._queue_ds[int(device_id)].put(['update_meta_id', _data['uuid'], int(sql_id)])
|
|
self._psql_conn.commit()
|
|
sql_cursor.close()
|
|
|
|
# _conn.close()
|
|
return True
|
|
|
|
# _conn.close()
|
|
return None
|
|
|
|
# @calculate_time()
|
|
def data_meta_get(self, _path = None, _id = None):
|
|
if _id is not None:
|
|
sql_str = 'SELECT * FROM "public"."recording_data_metas" WHERE id = %s'
|
|
sql_set = [str(_id), ]
|
|
sql_cursor = self._psql_conn.cursor()
|
|
|
|
ret = None
|
|
|
|
sql_cursor.execute(sql_str, sql_set)
|
|
ret = sql_cursor.fetchone()
|
|
self._psql_conn.commit()
|
|
sql_cursor.close()
|
|
|
|
|
|
return ret
|
|
|
|
elif _path is not None:
|
|
sql_str = 'SELECT * FROM "public"."recording_data_metas" WHERE path = %s'
|
|
sql_set = [str(_path), ]
|
|
sql_cursor = self._psql_conn.cursor()
|
|
|
|
ret = None
|
|
|
|
sql_cursor.execute(sql_str, sql_set)
|
|
ret = sql_cursor.fetchone()
|
|
self._psql_conn.commit()
|
|
sql_cursor.close()
|
|
|
|
|
|
return ret
|
|
|
|
# _conn.close()
|
|
return None
|
|
|
|
# @calculate_time()
|
|
def data_meta_id_get_by_path(self, _path = None):
|
|
ret = None
|
|
if _path is not None:
|
|
sql_str = 'SELECT * FROM "public"."recording_data_metas" WHERE path = %s'
|
|
sql_set = [str(_path), ]
|
|
sql_cursor = self._psql_conn.cursor()
|
|
|
|
sql_cursor.execute(sql_str, sql_set)
|
|
ret = 0
|
|
try:
|
|
ret = int(sql_cursor.fetchone()[0])
|
|
except:
|
|
return None
|
|
self._psql_conn.commit()
|
|
sql_cursor.close()
|
|
|
|
return ret
|
|
|
|
# _conn.close()
|
|
return None
|
|
|
|
# @calculate_time()
|
|
def data_raw_create(self, _data_dict, _channel_list, device_id):
|
|
if self._data_raw_create_sql_str == None:
|
|
sql_str_list = []
|
|
key_list = _data_dict[_channel_list[0]].keys()
|
|
key_str = ','.join(key_list)
|
|
values = ','.join(['%s'] * len(key_list))
|
|
sql_str_list = ['INSERT INTO "public"."{channel}_recording_data_raws" (', key_str, ') VALUES (', values, ') RETURNING id']
|
|
self._data_raw_create_sql_str = ''.join(sql_str_list)
|
|
try:
|
|
with self._psql_conn as conn:
|
|
with conn.cursor() as sql_cursor:
|
|
for _channel in _channel_list:
|
|
sql_set = list(map(lambda x: str(x), _data_dict[_channel].values()))
|
|
sql_cursor.execute(self._data_raw_create_sql_str.format(channel = _channel), sql_set)
|
|
self._queue_ds[int(device_id)].put(['update_raw_dict_id', _data_dict[_channel]['uuid'], _channel, int(sql_cursor.fetchone()[0])])
|
|
except (psycopg2.Error, RuntimeError) as e:
|
|
print('data_raw_create error', e)
|
|
self._queue_error.put(device_id)
|
|
|
|
return True
|
|
|
|
# @calculate_time()
|
|
def data_raw_update(self, _id, _channel, _data: JSON_OBJECT):
|
|
sql_str = 'UPDATE "public"."' + str(_channel) + '_recording_data_raws" SET '
|
|
sql_set = []
|
|
for item in _data.keys():
|
|
sql_str = sql_str + str(item) + ' = %s, '
|
|
if isinstance(_data[item], dict):
|
|
sql_set.append(json_stringify(_data[item]))
|
|
else:
|
|
sql_set.append(_data[item])
|
|
|
|
sql_str = sql_str[0:-2] + ' WHERE (id = %s)'
|
|
sql_set.append(_id)
|
|
|
|
sql_cursor = self._psql_conn.cursor()
|
|
sql_cursor.execute(sql_str, sql_set)
|
|
self._psql_conn.commit()
|
|
sql_cursor.close()
|
|
return None
|
|
|
|
# @calculate_time()
|
|
def data_raw_recording(self, _id_dict, _channel_list, _data_dict, _id_db_save):
|
|
try:
|
|
with self._psql_conn as conn:
|
|
with conn.cursor() as sql_cursor:
|
|
para_list = []
|
|
for _channel in _channel_list:
|
|
temp_id = _id_dict[_channel] if _id_dict[_channel] != 0 else _id_db_save[_channel]
|
|
para_list.append([_channel, _data_dict[_channel] + '"***"', temp_id])
|
|
execute_batch(sql_cursor, self._data_raw_recording_sql_str, para_list)
|
|
except psycopg2.Error as e:
|
|
print('recording error', e)
|
|
return None
|
|
|
|
# @calculate_time(1)
|
|
def data_raw_recording_new(self, _id_dict, _channel_list, _data_dict, _end_time_dict, _size_dict):
|
|
try:
|
|
para_list = []
|
|
for _channel in _channel_list:
|
|
para_list.append([_channel, _data_dict[_channel] + '"***"', _end_time_dict[_channel], _size_dict[_channel], _id_dict[_channel]])
|
|
with self._psql_conn as conn:
|
|
with conn.cursor() as sql_cursor:
|
|
execute_batch(sql_cursor, self._new_data_raw_recording_sql_str, para_list)
|
|
except psycopg2.Error as e:
|
|
print('recording error', e)
|
|
return None
|
|
|
|
# @calculate_time()
|
|
def data_mini_create(self, _data_dict, _channel_list, _scale, device_id):
|
|
for _channel in _channel_list:
|
|
sql_str = 'INSERT INTO "public"."' + str(_channel) + '_recording_data_minis" ('
|
|
sql_set = []
|
|
for item in _data_dict[_channel].keys():
|
|
sql_str = sql_str + str(item) + ', '
|
|
if isinstance(_data_dict[_channel][item], dict):
|
|
sql_set.append(json_stringify(_data_dict[_channel][item]))
|
|
else:
|
|
sql_set.append(str(_data_dict[_channel][item]))
|
|
|
|
sql_str = sql_str[0:-2] + ') VALUES ('
|
|
for item in _data_dict[_channel].keys():
|
|
sql_str = sql_str + '%s, '
|
|
|
|
sql_str = sql_str[0:-2] + ') RETURNING id'
|
|
sql_cursor = self._psql_conn.cursor()
|
|
|
|
try:
|
|
sql_cursor.execute(sql_str, sql_set)
|
|
except RuntimeError as e:
|
|
print('data_mini_create error', e)
|
|
self._psql_conn.commit()
|
|
sql_cursor.close()
|
|
self._queue_error.put(device_id)
|
|
finally:
|
|
self._queue_ds[int(device_id)].put(['update_mini_dict_id', _data_dict[_channel]['uuid'], _scale, _channel, int(sql_cursor.fetchone()[0])])
|
|
self._psql_conn.commit()
|
|
sql_cursor.close()
|
|
|
|
# _conn.close()
|
|
return True
|
|
|
|
# @calculate_time()
|
|
def data_mini_recording(self, _id_dict, _channel_list, _start_time_dict, _data_mean_dict):
|
|
with self._psql_conn as conn:
|
|
with self._psql_conn.cursor() as sql_cursor:
|
|
for _channel in _channel_list:
|
|
data = _data_mean_dict.get(_channel)
|
|
if data is not None:
|
|
if len(data) != 0:
|
|
str_mean = [str(int) for int in _data_mean_dict[_channel]]
|
|
data_mean = _start_time_dict[_channel] + ' ' + ' '.join(str_mean)
|
|
|
|
sql_str = 'UPDATE "public"."' + str(_channel) + '_recording_data_minis" SET data_mean = concat(data_mean, %s) where id = %s'
|
|
sql_set = [data_mean + '"***"', _id_dict[_channel]]
|
|
sql_cursor.execute(sql_str, sql_set)
|
|
|
|
del str_mean, data_mean
|
|
|
|
for _channel in _channel_list:
|
|
del _start_time_dict[_channel], _data_mean_dict[_channel]
|
|
gc.collect()
|
|
|
|
return True
|
|
|
|
# @calculate_time(1)
|
|
def data_mini_recording_new(self, _id_dict, _channel_list, _data_mean_dict):
|
|
try:
|
|
para_list = []
|
|
for _channel in _channel_list:
|
|
para_list.append([_channel, _data_mean_dict[_channel], _id_dict[_channel]])
|
|
with self._psql_conn as conn:
|
|
with conn.cursor() as sql_cursor:
|
|
execute_batch(sql_cursor, self._data_mini_recording_sql_str, para_list)
|
|
except psycopg2.Error as e:
|
|
print('recording error', e)
|
|
return None
|
|
|
|
# @calculate_time()
|
|
def data_mini_update(self, _id, _channel, _data: JSON_OBJECT):
|
|
sql_str = 'UPDATE "public"."' + str(_channel) + '_recording_data_minis" SET '
|
|
sql_set = []
|
|
for item in _data.keys():
|
|
sql_str = sql_str + str(item) + ' = %s, '
|
|
if isinstance(_data[item], dict):
|
|
sql_set.append(json_stringify(_data[item]))
|
|
else:
|
|
sql_set.append(_data[item])
|
|
|
|
sql_str = sql_str[0:-2] + ' WHERE (id = %s)'
|
|
sql_set.append(_id)
|
|
|
|
sql_cursor = self._psql_conn.cursor()
|
|
sql_cursor.execute(sql_str, sql_set)
|
|
self._psql_conn.commit()
|
|
sql_cursor.close()
|
|
|
|
return True
|
|
|
|
def data_meta_update_raw_empty_channel(self, meta_id, channel_list):
|
|
try:
|
|
sql_str = "UPDATE recording_data_metas SET raw_data = jsonb_set(raw_data, '{%s}', '[]'), mini_data = jsonb_set(mini_data, '{%s}', '{\"10\": [], \"100\": [], \"1000\": []}') WHERE id = %s;"
|
|
sql_set = []
|
|
for channel in channel_list:
|
|
sql_set.append([channel, channel, meta_id])
|
|
|
|
with self._psql_conn as conn:
|
|
with conn.cursor() as sql_cursor:
|
|
execute_batch(sql_cursor, sql_str, sql_set)
|
|
except psycopg2.Error as e:
|
|
print('e', e)
|
|
|
|
return True
|
|
|
|
# @calculate_time()
|
|
def data_meta_update_channel_raw_id(self, meta_id, channel, raw_id):
|
|
try:
|
|
with self._psql_conn as conn:
|
|
with conn.cursor() as sql_cursor:
|
|
sql_str = "UPDATE recording_data_metas SET raw_data = jsonb_insert(raw_data, '{%s, -1}', '%s', TRUE) WHERE id = %s;"
|
|
sql_set = [channel, raw_id, 571]
|
|
sql_cursor.execute(sql_str, sql_set)
|
|
except psycopg2.Error as e:
|
|
print('e', e)
|
|
|
|
return True
|
|
|
|
# @calculate_time()
|
|
def data_meta_update_all_channel_raw_id(self, meta_id:str, raw_id_dict:dict):
|
|
try:
|
|
sql_str = "UPDATE recording_data_metas SET raw_data = jsonb_insert(raw_data, '{%s, -1}', '%s', TRUE) WHERE id = %s;"
|
|
sql_set = []
|
|
for ch in raw_id_dict.keys():
|
|
sql_set.append([ch, raw_id_dict[ch], meta_id])
|
|
with self._psql_conn as conn:
|
|
with conn.cursor() as sql_cursor:
|
|
execute_batch(sql_cursor, sql_str, sql_set)
|
|
except psycopg2.Error as e:
|
|
print('e', e)
|
|
|
|
return True
|
|
|
|
def data_meta_update_channel_mini_id(self, meta_id, channel, scale, mini_id):
|
|
try:
|
|
with self._psql_conn as conn:
|
|
with conn.cursor() as sql_cursor:
|
|
sql_str = "UPDATE recording_data_metas SET mini_data = jsonb_insert(mini_data, '{%s, %s, -1}', '%s', TRUE) WHERE id = %s;"
|
|
sql_set = [channel, scale, mini_id, meta_id]
|
|
sql_cursor.execute(sql_str, sql_set)
|
|
except psycopg2.Error as e:
|
|
print('e', e)
|
|
|
|
return True
|
|
|
|
# @calculate_time()
|
|
def data_meta_update_all_channel_mini_id(self, meta_id, mini_id_dict):
|
|
try:
|
|
sql_str = "UPDATE recording_data_metas SET mini_data = jsonb_insert(mini_data, '{%s, %s, -1}', '%s', TRUE) WHERE id = %s;"
|
|
sql_set = []
|
|
for ch in mini_id_dict.keys():
|
|
for scale in mini_id_dict[ch].keys():
|
|
sql_set.append([ch, scale, mini_id_dict[ch][scale], meta_id])
|
|
with self._psql_conn as conn:
|
|
with conn.cursor() as sql_cursor:
|
|
execute_batch(sql_cursor, sql_str, sql_set)
|
|
except psycopg2.Error as e:
|
|
print('e', e)
|
|
|
|
return True
|
|
|
|
def project_insert(self, device_id, _data):
|
|
sql_str = 'INSERT INTO "public"."meta_project_info" ('
|
|
sql_set = []
|
|
for item in _data.keys():
|
|
sql_str = sql_str + str(item) + ', '
|
|
if isinstance(_data[item], dict) or isinstance(_data[item], list):
|
|
sql_set.append(json_stringify(_data[item]))
|
|
else:
|
|
sql_set.append(_data[item])
|
|
|
|
sql_str = sql_str[0:-2] + ') VALUES ('
|
|
for item in _data.keys():
|
|
sql_str = sql_str + '%s, '
|
|
|
|
sql_str = sql_str[0:-2] + ') RETURNING id'
|
|
sql_cursor = self._psql_conn.cursor()
|
|
|
|
try:
|
|
sql_cursor.execute(sql_str, sql_set)
|
|
except BaseException as e:
|
|
print('insert error', e)
|
|
self._psql_conn.commit()
|
|
sql_cursor.close()
|
|
self._queue_error.put()
|
|
finally:
|
|
self._queue_ds[int(device_id)].put(['project_id', int(sql_cursor.fetchone()[0])])
|
|
self._psql_conn.commit()
|
|
sql_cursor.close()
|