Files
controller-wisetopdataserver/python/biopro/server/database.py
T
2021-12-20 14:52:55 +08:00

472 lines
17 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.pool import ThreadedConnectionPool
from multiprocessing import Process, Queue
import biopro.server._identify
from concurrent.futures import ThreadPoolExecutor, as_completed
_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=4)
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
@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
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
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
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
def data_raw_create(self, _data_dict, _channel_list, device_id):
for _channel in _channel_list:
sql_str = 'INSERT INTO "public"."' + str(_channel) + '_recording_data_raws" ('
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_raw_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_raw_dict_id', _data_dict[_channel]['uuid'], _channel, int(sql_cursor.fetchone()[0])])
self._psql_conn.commit()
sql_cursor.close()
return True
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
def data_raw_recording(self, _id_dict, _channel_list, _data_dict):
for _channel in _channel_list:
compressed_value = _data_dict[_channel] + '"***"'
# compressed_value = bytes(_data_dict[_channel],'utf-8')
# compressed_value = zlib.compress(compressed_value)
# compressed_value = base64.b64encode(compressed_value).decode() + '"***"'
# compressed_value = _data + '"***"'
sql_str = 'UPDATE "public"."' + str(_channel) + '_recording_data_raws" SET data = concat(data, %s) where id = %s'
sql_set = [compressed_value, _id_dict[_channel]]
sql_cursor = self._psql_conn.cursor()
sql_cursor.execute(sql_str, sql_set)
self._psql_conn.commit()
sql_cursor.close()
del _data_dict[_channel]
del compressed_value
gc.collect()
# _conn.close()
return None
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
def data_mini_recording(self, _id_dict, _channel_list, _start_time_dict, _data_mean_dict):
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)
# data_random = _start_time_dict[_channel] + ' ' + ' '.join(_data_random_dict[_channel])
# data_bar = _start_time_dict[_channel] + ' ' + ' '.join(_data_bar_dict[_channel])
compressed_mean = data_mean + '"***"'
# compressed_mean = bytes(data_mean,'utf-8')
# compressed_mean = zlib.compress(compressed_mean)
# compressed_mean = base64.b64encode(compressed_mean).decode() + '"***"'
# compressed_random = bytes(data_bar,'utf-8')
# compressed_random = zlib.compress(compressed_random)
# compressed_random = base64.b64encode(compressed_random).decode() + '"***"'
# compressed_random = bytes(data_random,'utf-8')
# compressed_random = zlib.compress(compressed_random)
# compressed_random = base64.b64encode(compressed_random).decode() + '"***"'
sql_str = 'UPDATE "public"."' + str(_channel) + '_recording_data_minis" SET data_mean = concat(data_mean, %s) where id = %s'
sql_set = [compressed_mean, _id_dict[_channel]]
sql_cursor = self._psql_conn.cursor()
sql_cursor.execute(sql_str, sql_set)
self._psql_conn.commit()
sql_cursor.close()
del str_mean, _start_time_dict[_channel], _data_mean_dict[_channel], data_mean, compressed_mean
gc.collect()
return True
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