commit 0974a12f10ef59273aed7e19847c6bf585010e37 Author: cxykevin Date: Sun Jul 14 20:20:41 2024 +0800 Origin commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..51ae087 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +**/__pycache__ +config.toml +debug_cmd.lst +venv \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..f4ff49d --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python 调试程序: 当前文件", + "type": "debugpy", + "request": "launch", + "program": "main.py", + "console": "integratedTerminal" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..56c56a0 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "python.analysis.autoImportCompletions": true, + "python.analysis.typeCheckingMode": "off" +} \ No newline at end of file diff --git a/config.toml.sample b/config.toml.sample new file mode 100644 index 0000000..48b1e82 --- /dev/null +++ b/config.toml.sample @@ -0,0 +1,21 @@ +runmode = "webapi" + +[webapi] +host = "127.0.0.1" +port = 3032 + +[console] +emacsmode = true +softstop = true + +[db] +host = "127.0.0.1" +port = 3306 +user = "root" +passwd = "passwd" +dbname = "myjsonDB" + +[log] +path = "log.log" +level = "info" +color = true diff --git a/log.log b/log.log new file mode 100644 index 0000000..a011464 --- /dev/null +++ b/log.log @@ -0,0 +1,7 @@ +[2024-07-14 20:01:59,825][INFO] Server start +[2024-07-14 20:01:59,825][INFO] Connect database +[2024-07-14 20:01:59,854][INFO] Server version:8.2.0 +[2024-07-14 20:01:59,858][INFO] Use database +[2024-07-14 20:01:59,861][INFO] start webapi +[2024-07-14 20:01:59,862][INFO] Search web plugins +[2024-07-14 20:01:59,862][INFO] + Load ezapi.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..82f3ac7 --- /dev/null +++ b/main.py @@ -0,0 +1,35 @@ +from src import log +from src import core +from src import term +from src import config +from src import webcore +import asyncio + +log.log_init() + + +loop = asyncio.new_event_loop() + + +async def start_cli(): + await log.info("start cli") + await term.debug_term() + + +async def start_api(): + await log.info("start webapi") + await webcore.start() + + +async def init(): + await log.info("Server start") + await core.connect_realdb() + if (config.RUN_MODE == "console"): + await start_cli() + elif (config.RUN_MODE == "webapi"): + await start_api() + else: + await log.err("Unknown run mode: "+str(config.RUN_MODE)) + + +loop.run_until_complete(init()) diff --git a/src/config.py b/src/config.py new file mode 100644 index 0000000..6e4a05d --- /dev/null +++ b/src/config.py @@ -0,0 +1,24 @@ +import tomllib + +with open("config.toml", 'rb') as file: + configs: dict = tomllib.load(file) + +RUN_MODE: bool = configs.get("runmode", "webapi") + +LOGPATH: str = configs.get("log", {}).get("path", "log.log") +LOGLEVEL: str = configs.get("log", {}).get("level", "info") +if (LOGLEVEL not in ["debug", "info", "warning", "err"]): + LOGLEVEL = "info" +OUTPUT_COLORFUL: bool = configs.get("log", {}).get("color", True) + +DB_SERVER: str = configs.get("db", {}).get("host", "127.0.0.1") +DB_PORT: int = configs.get("db", {}).get("port", 3306) +DB_USER: str = configs.get("db", {}).get("user", "root") +DB_PASSWD: str = configs.get("db", {}).get("passwd", "") +DB_NAME: str = configs.get("db", {}).get("dbname", "myjsonDB") + +USE_EMACE_MOD: bool = configs.get("console", {}).get("emacsmode", True) +SIGTERM_CMD: bool = configs.get("console", {}).get("softstop", True) + +API_HOST: bool = configs.get("webapi", {}).get("host", "127.0.0.1") +API_PORT: bool = configs.get("webapi", {}).get("port", 3032) diff --git a/src/core.py b/src/core.py new file mode 100644 index 0000000..614b31d --- /dev/null +++ b/src/core.py @@ -0,0 +1,557 @@ +from src import log +from src import sqllink +from src import config +import re +import sys +import pickle +from typing import Any, Tuple + + +async def connect_realdb() -> None: + """ + Connect to a mysql database and save connect. + """ + global connects + await log.info("Connect database") + try: + connects = await sqllink.connect_db() + except: + await log.break_err("Connect error") + sys.exit(2) + async with connects.acquire() as conn: + await log.info("Server version:"+str(conn.server_version)) + async with conn.cursor() as cursor: + await cursor.execute("SHOW TABLES LIKE 'sys_db_list';") + result = await cursor.fetchall() + if (len(result) == 0): + await log.info("Init database") + await cursor.execute(""" + CREATE TABLE sys_db_list( + name VARCHAR(50) PRIMARY KEY NOT NULL + ); + """) + await cursor.execute(""" + CREATE TABLE sys_config( + name VARCHAR(50) PRIMARY KEY NOT NULL, + val VARCHAR(200) NOT NULL + ); + """) + else: + await log.info("Use database") + + +async def check_jsondb(name: str) -> bool | None: + """ + Check a jsondb if it has been created. + @return: True(created) | False(not create) | None(error) + """ + await log.info("Check jsondb: "+str(name)) + res = re.search(r'[0-9A-Za-z_]{2,20}', name) + if res is None or res.group() != name: + await log.warn("Check database name error: "+str(name)) + return None + + async def callback(result): + if (len(result) == 0): + return False + else: + return True + + async def err_callback(err_info): + await log.warn("Other error on create database: "+str(err_info)) + return None + return await sqllink.execute_cmd(connects, command="SELECT * FROM sys_db_list WHERE name='"+name+"';", callback=callback, err_callback=err_callback) + + +async def list_jsondb() -> Tuple[str] | None: + """ + ## List all jsondb(s). + @return: `tuple | None(error)` + """ + await log.info("list jsondb") + + async def callback(result): + return [i[0] for i in result] + + async def err_callback(err_info): + await log.warn("Other error on list databases: "+str(err_info)) + return None + return await sqllink.execute_cmd(connects, command="SELECT * FROM sys_db_list;", callback=callback, err_callback=err_callback) + + +async def create_jsondb(name: str) -> tuple[int, str | None]: + """ + ## Create a jsondb. + @return: `tuple[errorlevel:int, errormsg:str|None]` + """ + await log.info("Create jsondb: "+str(name)) + res = re.search(r'[0-9A-Za-z_]{2,20}', name) + if res is None or res.group() != name: + await log.warn("Create database name error: "+str(name)) + return (1, "Name error: "+str(name)) + + async def callback(): + return (0, None) + + async def err_callback(err_info): + if ("sys_db_list.PRIMARY" in err_info): + await log.warn("Duplicate database names: "+str(name)) + return (1, "Duplicate database names: "+str(name)) + else: + await log.warn("Other error on create database: "+str(err_info)) + return (2, "Other error: "+str(err_info)) + return await sqllink.execute_cmd(connects, command="INSERT INTO sys_db_list (name) VALUES ('"+name+"');", commit=True, callback=callback, err_callback=err_callback) + + +async def create_table(dbname: str, name: str) -> tuple[int, str | None]: + """ + ## Create a table in a jsondb. + @return: `tuple[errorlevel:int, errormsg:str|None]` + """ + await log.info("Create table '"+str(name)+"' in '"+str(dbname)+"'") + + res = re.search(r'[0-9A-Za-z_]{2,20}', dbname) + if res is None or res.group() != dbname: + await log.warn("Database name error: "+str(dbname)) + return (1, "DB name error"+str(dbname)) + + res = re.search(r'[0-9A-Za-z_]{2,20}', name) + if res is None or res.group() != name: + await log.warn("Create table database name error: "+str(name)) + return (2, "Name error: "+str(name)) + + checks = await check_jsondb(dbname) + if checks == None: + return (3, "Other check error") + elif checks == False: + await log.warn("Create table cannot find database: "+str(dbname)) + return (3, "Cannot find database: "+str(dbname)) + + async def callback(): + return (0, None) + + async def err_callback(err_info): + if ("sys_db_list.PRIMARY" in err_info): + await log.warn("Duplicate database names: "+str(name)) + return (4, "Duplicate database names: "+str(name)) + else: + await log.warn("Other error on create table: "+str(err_info)) + return (4, "Other error: "+str(err_info)) + + return await sqllink.execute_cmd(connects, + command=f""" + CREATE TABLE `user_{dbname.replace("_", "__")}_{name.replace("_", "__")}`( + `key` VARCHAR(40) PRIMARY KEY NOT NULL, + `value` MEDIUMBLOB + );""", + commit=True, callback=callback, err_callback=err_callback) + + +async def list_table(dbname: str) -> Tuple[str] | None: + """ + ## List all tables from a jsondb. + @return: `tuple | None(error)` + """ + await log.info("List table in: "+str(dbname)) + + res = re.search(r'[0-9A-Za-z_]{2,20}', dbname) + if res is None or res.group() != dbname: + await log.warn("Database name error: "+str(dbname)) + return None + + async def callback(result): + return (i[0][len(f"user_{dbname.replace("_", "__")}_"):] for i in result) + + async def err_callback(err_info): + await log.warn("Other error on list database: "+str(err_info)) + return None + + return await sqllink.execute_cmd(connects, + command=f"SHOW TABLES LIKE 'user_{ + dbname.replace("_", "__")}_%%';", + callback=callback, err_callback=err_callback) + + +async def rm_jsondb(dbname: str) -> tuple[int, str | None]: + """ + ## Remove a jsondb and all tables in it. + @return: `tuple[errorlevel:int, errormsg:str|None]` + """ + await log.info("Remove database in: "+str(dbname)) + + res = re.search(r'[0-9A-Za-z_]{2,20}', dbname) + if res is None or res.group() != dbname: + await log.warn("Database name error: "+str(dbname)) + return (1, "DB name error"+str(dbname)) + + checks = await check_jsondb(dbname) + if checks == None: + return (2, "Other check error") + elif checks == False: + await log.warn("Create table cannot find database: "+str(dbname)) + return (2, "Cannot find database: "+str(dbname)) + + async def callback() -> tuple[int, str | None]: + return (0, None) + + async def err_callback(err_info: str) -> tuple[int, str | None]: + await log.warn("Other error on list database: "+str(err_info)) + return (3, str(err_info)) + + await log.debug("mysql remove db") + try: + async with connects.acquire() as conn: + async with conn.cursor() as cursor: # thank for chatglm + await cursor.execute(f""" + SELECT * + FROM information_schema.tables + WHERE table_schema = '{config.DB_NAME}' AND table_name LIKE 'user_{dbname.replace("_", "__")}_%%' + """) + tables_to_drop = await cursor.fetchall() + for table_info in tables_to_drop: + await cursor.execute(f"DROP TABLE {table_info[2]};") + await conn.commit() + except Exception as exp: + return await err_callback(repr(exp)) + + return await sqllink.execute_cmd(connects, + command=f"DELETE FROM sys_db_list WHERE `name`='{ + dbname}';", + commit=True, callback=callback, err_callback=err_callback) + + +async def rm_table(dbname: str, name: str) -> tuple[int, str | None]: + """ + ## Remove a table. + @return: `tuple[errorlevel:int, errormsg:str|None]` + """ + await log.info("Remove table '"+str(name)+"' in '"+str(dbname)+"'") + + res = re.search(r'[0-9A-Za-z_]{2,20}', dbname) + if res is None or res.group() != dbname: + await log.warn("Database name error: "+str(dbname)) + return (1, "DB name error"+str(dbname)) + + res = re.search(r'[0-9A-Za-z_]{2,20}', name) + if res is None or res.group() != name: + await log.warn("Create table database name error: "+str(name)) + return (2, "Name error: "+str(name)) + + checks = await check_jsondb(dbname) + if checks == None: + return (3, "Other check error") + elif checks == False: + await log.warn("Create table cannot find database: "+str(dbname)) + return (3, "Cannot find database: "+str(dbname)) + + async def callback(): + return (0, None) + + async def err_callback(err_info): + if ("Unknown table" in err_info): + await log.warn("Unknown table name: "+str(name)) + return (4, "Unknown table name: "+str(name)) + else: + await log.warn("Other error on create table: "+str(err_info)) + return (4, "Other error: "+str(err_info)) + + return await sqllink.execute_cmd(connects, + command=f"DROP TABLE `user_" + + f"{dbname.replace("_", "__")}_" + + f"{name.replace("_", "__")}`;", + commit=True, + callback=callback, + err_callback=err_callback) + + +async def check_table(dbname: str, name: str) -> bool | None: + """ + ## Check a table if it has been created. + @return: True(created) | False(not create) | None(error) + """ + await log.info("Check table '"+str(name)+"' in '"+str(dbname)+"'") + + res = re.search(r'[0-9A-Za-z_]{2,20}', dbname) + if res is None or res.group() != dbname: + await log.warn("Database name error: "+str(dbname)) + return None + + res = re.search(r'[0-9A-Za-z_]{2,20}', name) + if res is None or res.group() != name: + await log.warn("Create table database name error: "+str(name)) + return None + + checks = await check_jsondb(dbname) + if checks == None: + return None + elif checks == False: + await log.warn("Create table cannot find database: "+str(dbname)) + return None + + async def callback(result): + if (len(result) > 0): + return True + else: + return False + + async def err_callback(err_info): + await log.warn("Other error on check table: "+str(err_info)) + return None + return await sqllink.execute_cmd(connects, + command=f"SHOW TABLES LIKE 'user_" + + f"{dbname.replace("_", "__")}_" + + f"{name.replace("_", "__")}';", + callback=callback, + err_callback=err_callback) + + +async def create_key(dbname: str, name: str, key: str | int | tuple, vl: Any) -> tuple[int, str | None]: + """ + ## Create a key. + @return: `tuple[errorlevel:int, errormsg:str|None]` + """ + await log.info("Create key in '"+str(name)+"."+str(dbname)+"'") + + if (vl is None): + await log.warn("Value is None") + return (6, "Value is None") + + res = re.search(r'[0-9A-Za-z_]{2,20}', dbname) + if res is None or res.group() != dbname: + await log.warn("Database name error: "+str(dbname)) + return (1, "Database name error: "+str(dbname)) + + res = re.search(r'[0-9A-Za-z_]{2,20}', name) + if res is None or res.group() != name: + await log.warn("Create key table name error: "+str(name)) + return (2, "Create key table name error: "+str(name)) + + checks = await check_table(dbname, name) + if checks == None: + return (3, "Other error") + elif checks == False: + await log.warn("Unknown table: "+str(name)) + return (4, "Unknown table: "+str(name)) + + async def callback(): + return (0, None) + + async def err_callback(err_info): + await log.warn("Other error on create key: "+str(err_info)) + return (5, "Other error on create key: "+str(err_info)) + return await sqllink.execute_cmd(connects, + command=f""" + INSERT INTO `user_{dbname.replace("_", "__")}_{name.replace("_", "__")}`( + `key`, + `value` + ) VALUE ( + %s, + %s + ) + """, + args=(str(key), pickle.dumps(vl)), + commit=True, + callback=callback, + err_callback=err_callback) + + +async def get_key(dbname: str, name: str, key: str | int | tuple) -> Any | None: + """ + ## Get a key. + @return: `Any | None(error)` + """ + await log.info("Get key in '"+str(name)+"."+str(dbname)+"'") + + res = re.search(r'[0-9A-Za-z_]{2,20}', dbname) + if res is None or res.group() != dbname: + await log.warn("Database name error: "+str(dbname)) + return None + + res = re.search(r'[0-9A-Za-z_]{2,20}', name) + if res is None or res.group() != name: + await log.warn("Get key table name error: "+str(name)) + return None + + checks = await check_table(dbname, name) + if checks == None: + await log.warn("Other error") + return None + elif checks == False: + await log.warn("Unknown table: "+str(name)) + return None + + async def callback(result): + if (len(result) > 0): + return pickle.loads(result[0][1]) + else: + return None + + async def err_callback(err_info): + await log.warn("Other error on get key: "+str(err_info)) + return None + return await sqllink.execute_cmd(connects, + command=f""" + SELECT * FROM `user_{dbname.replace("_", "__")}_{name.replace("_", "__")}` WHERE `key`=%s;""", + args=(str(key),), + callback=callback, + err_callback=err_callback) + + +async def check_key(dbname: str, name: str, key: str | int | tuple) -> Any | None: + """ + ## Check a key. + @return: True(created) | False(not create) | None(error) + """ + await log.info("Check key in '"+str(name)+"."+str(dbname)+"'") + + res = re.search(r'[0-9A-Za-z_]{2,20}', dbname) + if res is None or res.group() != dbname: + await log.warn("Database name error: "+str(dbname)) + return None + + res = re.search(r'[0-9A-Za-z_]{2,20}', name) + if res is None or res.group() != name: + await log.warn("Check key table name error: "+str(name)) + return None + + checks = await check_table(dbname, name) + if checks == None: + await log.warn("Other error") + return None + elif checks == False: + await log.warn("Unknown table: "+str(name)) + return None + + async def callback(result): + if (len(result) > 0): + return True + else: + return False + + async def err_callback(err_info): + await log.warn("Other error on check key: "+str(err_info)) + return None + return await sqllink.execute_cmd(connects, + command=f""" + SELECT * FROM `user_{dbname.replace("_", "__")}_{name.replace("_", "__")}` WHERE `key`=%s;""", + args=(str(key),), + callback=callback, + err_callback=err_callback) + + +async def remove_key(dbname: str, name: str, key: str | int | tuple) -> Any | None: + """ + ## Remove a key. + @return: True(created) | False(not create) | None(error) + """ + await log.info("Remove key in '"+str(name)+"."+str(dbname)+"'") + + res = re.search(r'[0-9A-Za-z_]{2,20}', dbname) + if res is None or res.group() != dbname: + await log.warn("Database name error: "+str(dbname)) + return (1, "Database name error: "+str(dbname)) + + res = re.search(r'[0-9A-Za-z_]{2,20}', name) + if res is None or res.group() != name: + await log.warn("Remove key table name error: "+str(name)) + return (2, "Remove key table name error: "+str(name)) + + checks = await check_key(dbname, name, key) + if checks == None: + await log.warn("Other error") + return (3, "Other error") + elif checks == False: + await log.warn("Unknown key: "+str(key)) + return (4, "Unknown key: "+str(key)) + + async def callback(): + return (0, None) + + async def err_callback(err_info): + await log.warn("Other error on remove key: "+str(err_info)) + return (5, "Other error on remove key: "+str(err_info)) + return await sqllink.execute_cmd(connects, + command=f""" + DELETE FROM `user_{dbname.replace("_", "__")}_{name.replace("_", "__")}` WHERE `key`=%s;""", + args=(str(key),), + commit=True, + callback=callback, + err_callback=err_callback) + + +async def change_key(dbname: str, name: str, key: str | int | tuple, vl: Any) -> Any | None: + """ + ## Change a key. + @return: True(created) | False(not create) | None(error) + """ + await log.info("Change key in '"+str(name)+"."+str(dbname)+"'") + + res = re.search(r'[0-9A-Za-z_]{2,20}', dbname) + if res is None or res.group() != dbname: + await log.warn("Database name error: "+str(dbname)) + return (1, "Database name error: "+str(dbname)) + + res = re.search(r'[0-9A-Za-z_]{2,20}', name) + if res is None or res.group() != name: + await log.warn("Change key table name error: "+str(name)) + return (2, "Change key table name error: "+str(name)) + + checks = await check_key(dbname, name, key) + if checks == None: + await log.warn("Other error") + return (3, "Other error") + elif checks == False: + await log.warn("Unknown key: "+str(name)) + return (4, "Unknown key: "+str(name)) + + async def callback(): + return (0, None) + + async def err_callback(err_info): + await log.warn("Other error on change key: "+str(err_info)) + return (5, "Other error on change key: "+str(err_info)) + return await sqllink.execute_cmd(connects, + command=f""" + UPDATE `user_{dbname.replace("_", "__")}_{name.replace("_", "__")}` SET `value`=%s WHERE `key`=%s;""", + args=(pickle.dumps(vl), str(key)), + commit=True, + callback=callback, + err_callback=err_callback) + + +async def list_key(dbname: str, name: str) -> Tuple[tuple[str, Any]] | None: + """ + ## List keys. + @return: `tuple | None(error)` + """ + await log.info("List keys in '"+str(name)+"."+str(dbname)+"'") + + res = re.search(r'[0-9A-Za-z_]{2,20}', dbname) + if res is None or res.group() != dbname: + await log.warn("Database name error: "+str(dbname)) + return None + + res = re.search(r'[0-9A-Za-z_]{2,20}', name) + if res is None or res.group() != name: + await log.warn("List key table name error: "+str(name)) + return None + + checks = await check_table(dbname, name) + if checks == None: + await log.warn("Other error") + return None + elif checks == False: + await log.warn("Unknown table: "+str(name)) + return None + + async def callback(result): + return ((i[0], pickle.loads(i[1])) for i in result) + + async def err_callback(err_info): + await log.warn("Other error on list key: "+str(err_info)) + return None + return await sqllink.execute_cmd(connects, + command=f""" + SELECT * FROM `user_{dbname.replace("_", "__")}_{name.replace("_", "__")}`""", + callback=callback, + err_callback=err_callback) diff --git a/src/log.py b/src/log.py new file mode 100644 index 0000000..968c822 --- /dev/null +++ b/src/log.py @@ -0,0 +1,71 @@ +# ------ cxykevin log moudle ------ + +import logging +import sys +from src import config +levels = { + "debug": logging.DEBUG, + 'info': logging.INFO, + 'warning': logging.WARN, + 'err': logging.ERROR +} + + +def log_init() -> None: + logging.basicConfig(filename=config.LOGPATH, + format='[%(asctime)s][%(levelname)s] %(message)s', + level=levels[config.LOGLEVEL], filemode='w') + + +async def info(msg: str) -> None: + if (20 >= levels[config.LOGLEVEL]): + sys.stdout.write( + ("[\033[34mINFO\033[0m]" if config.OUTPUT_COLORFUL else "[INFO]") + msg + "\n") + logging.info(msg) + + +async def info(msg: str) -> None: + if (20 >= levels[config.LOGLEVEL]): + sys.stdout.write( + ("[\033[34mINFO\033[0m]" if config.OUTPUT_COLORFUL else "[INFO]") + msg + "\n") + logging.info(msg) + + +async def warn(msg: str) -> None: + if (30 >= levels[config.LOGLEVEL]): + sys.stdout.write( + ("[\033[33mWARNING\033[0m]" if config.OUTPUT_COLORFUL else "[WARNING]") + msg + "\n") + logging.warn(msg) + + +async def err(msg: str) -> None: + if (40 >= levels[config.LOGLEVEL]): + sys.stdout.write( + ("[\033[31mERROR\033[0m]" if config.OUTPUT_COLORFUL else "[ERROR]") + msg + "\n") + logging.error(msg) + + +async def break_err(msg: str) -> None: + if (40 >= levels[config.LOGLEVEL]): + sys.stdout.write( + ("[\033[31mERROR\033[0m]" if config.OUTPUT_COLORFUL else "[ERROR]") + msg + "\n") + logging.error(msg) + + +async def debug(*args, end="\n") -> None: + msg = ' '.join(map(str, args)) + if (10 >= levels[config.LOGLEVEL]): + sys.stdout.write( + ("[\033[32mDEBUG\033[0m]" if config.OUTPUT_COLORFUL else "[ERROR]") + msg + end) + logging.debug(msg) + + +async def print(*args, end="\n") -> None: + msg = ' '.join(map(str, args)) + if (10 >= levels[config.LOGLEVEL]): + sys.stdout.write(msg + end) + logging.debug(msg) + + +async def trace_sys(msg: str) -> None: + logging.info(msg) diff --git a/src/sqllink.py b/src/sqllink.py new file mode 100644 index 0000000..9499d8f --- /dev/null +++ b/src/sqllink.py @@ -0,0 +1,54 @@ +from src import log +from src import config +import aiomysql +import traceback +import textwrap +from typing import Any, Callable + + +async def connect_db() -> aiomysql.pool.Pool: + """ + Connect to a mysql database. + @return: Pool(aiomysql.pool.Pool) + """ + try: + connects = await aiomysql.create_pool( + host=config.DB_SERVER, + port=config.DB_PORT, + user=config.DB_USER, + password=config.DB_PASSWD, + db=config.DB_NAME + ) + except Exception as exp: + await log.err("Connect MYSQL server error") + await log.err(repr(exp)) + for i in traceback.format_exc().split("\n"): + firstlen_flag = "* " + lastlen = "" + for j in textwrap.wrap(i, width=60): + await log.err(" "+firstlen_flag+" " * + (len(lastlen)-len(lastlen.lstrip()))+j) + lastlen = " " * (len(lastlen)-len(lastlen.lstrip()))+j + firstlen_flag = " " + raise + return connects + + +async def execute_cmd(connects: aiomysql.pool.Pool, command: str, callback: Callable[..., Any] = lambda: None, commit=False, args: list | tuple = [], err_callback: Callable[[str], Any] = lambda err: log.warn("Default execute error: "+str(err))) -> Any: + """ + Execute a sql command on mysql database. + Return from callback function. + """ + await log.debug("mysql> "+command+" "+str(args)) + try: + async with connects.acquire() as conn: + async with conn.cursor() as cursor: + await cursor.execute(command, args) + if (commit): + await conn.commit() + return await callback() + else: + result = await cursor.fetchall() + return await callback(result) + except Exception as exp: + return await err_callback(repr(exp)) diff --git a/src/term.py b/src/term.py new file mode 100644 index 0000000..3de0012 --- /dev/null +++ b/src/term.py @@ -0,0 +1,147 @@ +from src import core +import sys +import readline +import asyncio +from src import config +import signal + + +async def do_cmd(cmd: str) -> None: + """ + Do a command. + Only for debug. + """ + args = cmd.split(" ") + match args[0]: + case "mkdb": + if (len(args) != 2): + sys.stderr.write("* dbname\n") + else: + ret = await core.create_jsondb(args[1]) + if (ret[0] != 0): + sys.stderr.write("err: "+str(ret[1])+'\n') + case "checkdb": + if (len(args) != 2): + sys.stderr.write("* dbname\n") + else: + ret = await core.check_jsondb(args[1]) + sys.stdout.write(str(ret)+"\n") + case "lsdb": + if (len(args) != 1): + sys.stderr.write("no args\n") + else: + ret = await core.list_jsondb() + if (ret is not None): + for i in ret: + sys.stdout.write("+ "+str(i)+"\n") + case "rmdb": + if (len(args) != 2): + sys.stderr.write("* dbname\n") + else: + ret = await core.rm_jsondb(args[1]) + if (ret[0] != 0): + sys.stderr.write("err: "+str(ret[1])+'\n') + case "mktable": + if (len(args) != 3): + sys.stderr.write("* dbname\n* tablename\n") + else: + ret = await core.create_table(args[1], args[2]) + if (ret[0] != 0): + sys.stderr.write("err: "+str(ret[1])+'\n') + case "lstable": + if (len(args) != 2): + sys.stderr.write("* dbname\n") + else: + ret = await core.list_table(args[1]) + if (ret is not None): + for i in ret: + sys.stdout.write("+ "+str(i)+"\n") + case "rmtable": + if (len(args) != 3): + sys.stderr.write("* dbname\n* tablename\n") + else: + ret = await core.rm_table(args[1], args[2]) + if (ret[0] != 0): + sys.stderr.write("err: "+str(ret[1])+'\n') + case "checktable": + if (len(args) != 3): + sys.stderr.write("* dbname\n* tablename\n") + else: + ret = await core.check_table(args[1], args[2]) + sys.stdout.write(str(ret)+"\n") + case "ckey": + if (len(args) != 5): + sys.stderr.write( + "* dbname\n* tablename\n* key\n* value(eval)\n") + else: + ret = await core.create_key(args[1], args[2], args[3], eval(args[4])) + if (ret[0] != 0): + sys.stderr.write("err: "+str(ret[1])+'\n') + case "gkey": + if (len(args) != 4): + sys.stderr.write("* dbname\n* tablename\n* key\n") + else: + ret = await core.get_key(args[1], args[2], args[3]) + sys.stdout.write(repr(ret)+"\n") + case "ikey": + if (len(args) != 4): + sys.stderr.write("* dbname\n* tablename\n* key\n") + else: + ret = await core.check_key(args[1], args[2], args[3]) + sys.stdout.write(repr(ret)+"\n") + case "rkey": + if (len(args) != 4): + sys.stderr.write("* dbname\n* tablename\n* key\n") + else: + ret = await core.remove_key(args[1], args[2], args[3]) + if (ret[0] != 0): + sys.stderr.write("err: "+str(ret[1])+'\n') + case "ekey": + if (len(args) != 5): + sys.stderr.write( + "* dbname\n* tablename\n* key\n* value(eval)\n") + else: + ret = await core.change_key(args[1], args[2], args[3], eval(args[4])) + if (ret[0] != 0): + sys.stderr.write("err: "+str(ret[1])+'\n') + case "lskey": + if (len(args) != 3): + sys.stderr.write("* dbname\n* tablename\n") + else: + ret = await core.list_key(args[1], args[2]) + if (ret is not None): + for i in ret: + sys.stdout.write("+ "+str(i[0])+"="+str(i[1])+"\n") + case "help": + if (len(args) != 1): + sys.stderr.write("no args\n") + else: + sys.stderr.write( + "mkdb lsdb checkdb rmdb mktable lstable rmtable checktable ckey gkey rkey ekey lskey\n") + case "exit": + sys.stderr.write("Bye\n") + sys.exit(0) + case _: + sys.stderr.write("Unknown command\n") + + +async def debug_term() -> None: + """ + Start a command line. + Only for debug. + """ + if (config.USE_EMACE_MOD): + readline.parse_and_bind('tab: complete') + readline.parse_and_bind('set editing-mode emacs') + + if (config.SIGTERM_CMD): + def exit(signum, frame): + sys.stderr.write("\nBye\n") + sys.exit() + signal.signal(signal.SIGINT, exit) + signal.signal(signal.SIGTERM, exit) + sys.stderr.write( + " -- Welcome to the cli tools. -- \nType 'exit' to quit.\n") + while (1): + inp = await asyncio.get_event_loop().run_in_executor(None, input, "db> ") + await do_cmd(inp) diff --git a/src/web/ezapi.py b/src/web/ezapi.py new file mode 100644 index 0000000..cab4429 --- /dev/null +++ b/src/web/ezapi.py @@ -0,0 +1,21 @@ +from src.webcore import app +from src import core +from fastapi import Response + +version = "v1" + + +@app.post("/api/"+version+"/database/{database_name}", status_code=201) +async def create_database(database_name: str): + ret = await core.create_jsondb(database_name) + if (ret[0] == 0): + return {"status": 200} + return {"status": 400+ret[0], "errormsg": ret[1]} + + +@app.delete("/api/"+version+"/database/{database_name}", status_code=200) +async def create_database(database_name: str): + ret = await core.rm_jsondb(database_name) + if (ret[0] == 0): + return {"status": 200} + return {"status": 400+ret[0], "errormsg": ret[1]} diff --git a/src/webcore.py b/src/webcore.py new file mode 100644 index 0000000..3ad1bc5 --- /dev/null +++ b/src/webcore.py @@ -0,0 +1,29 @@ +from fastapi import FastAPI +import uvicorn +from src import config +from src import log +import os + +app = FastAPI() + + +@app.get("/") +async def root_hello(): + return "Welcome to myjsonDB!" + + +async def start(): + await log.info("Search web plugins") + + plugs = [] + for i in os.listdir("src"+os.sep+"web"): + if (i.split(".")[-1] != "py"): + continue + await log.info(" + Load "+i) + plugs.append(__import__("src.web."+i.split(".")[0])) + + sconfig = uvicorn.Config( + app, loop="none", host=config.API_HOST, port=config.API_PORT) + server = uvicorn.Server(sconfig) + + await server.serve()