|
- #!/usr/bin/env python3
- # -*- coding: utf-8; mode: python; tab-width: 4; indent-tabs-mode: nil -*-
- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 fileencoding=utf-8
-
- from typing import Union, List
-
- import os
- import sys
- import subprocess
- import locale
- import json
- import platform
- import sqlite3
-
- IS_WIN = platform.system == "Windows"
-
-
- class DictNodeWrapper(object):
- def __init__(self, node):
- self._node = node
-
- def __getattr__(self, attr):
- if attr in self._node:
- if isinstance(self._node[attr], dict):
- return DictNodeWrapper(self._node[attr])
- else:
- return self._node[attr]
- return None
-
- def __getitem__(self, attr):
- return self.__getattr__(attr)
-
-
- class Config:
- def __init__(self, config_path):
- try:
- if os.path.isfile(config_path):
- self._config = json.loads(open(config_path).read())
- else:
- self._config = json.loads(config_path)
- except Exception as e:
- raise e
-
- def __getattr__(self, attr):
- if attr in self._config:
- if isinstance(self._config[attr], dict):
- return DictNodeWrapper(self._config[attr])
- else:
- return self._config[attr]
- return None
-
- def __getitem__(self, attr):
- return self.__getattr__(attr)
-
- def __str__(self):
- return json.dumps(self._config, indent=4)
-
- def origin(self):
- return self._config
-
- repr = __str__
-
-
- class Util:
-
- @staticmethod
- def subprocess_args(include_stdout=True):
- # The following is true only on Windows.
- if hasattr(subprocess, 'STARTUPINFO'):
- # On Windows, subprocess calls will pop up a command window by default
- # when run from Pyinstaller with the ``--noconsole`` option. Avoid this
- # distraction.
- si = subprocess.STARTUPINFO()
- si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
- # Windows doesn't search the path by default. Pass it an environment so
- # it will.
- env = os.environ
- else:
- si = None
- env = None
-
- if include_stdout:
- ret = {'stdout': subprocess.PIPE}
- else:
- ret = {}
-
- ret.update({'stdin': subprocess.PIPE,
- 'stderr': subprocess.PIPE,
- 'startupinfo': si,
- 'env': env })
- return ret
-
- @staticmethod
- def run_command(cmd: Union[str, List[str]],
- need_output: bool = False) -> Union[int, dict]:
- """Run a command via shell and return the result.
-
- :param cmd: A string of command line or a list
- :type cmd: str, list
- :param need_output: If need the output of command, set it to True, it always return a dictionary contains the output and error code.You can get them via keys 'result' and 'code' (default is False)
- :type need_output: bool
-
- :return: Return a dictionary if **need_output** is True, otherwise return an error code
- :rtype: int, dict
- """
- if not cmd:
- raise Exception("Command is invalid!")
-
- if isinstance(cmd, list):
- cmd = ' '.join(cmd)
-
- if need_output:
- returncode = 0
- try:
- if IS_WIN:
- result = subprocess.check_output(args=cmd, **Util.subprocess_args(False))
- else:
- result = subprocess.check_output(args=cmd, shell=True)
- ## NOTE: A patch, sometimes the locale.getdefaultlocale() maybe
- ## return (None, None), means the settings of locale on current
- ## platform is not available.
- # if Ad.DefaultLocale[1]:
- # If the locale is available, attempt to decode
- # the output use current locale.
- # result = result.decode(Ad.DefaultLocale[1])
- try:
- result = result.decode(locale.getdefaultlocale()[1])
- except Exception:
- # If decode with `locale.getdefaultlocale()` failed, just decode with utf8
- result = result.decode('utf8')
- except subprocess.CalledProcessError as e:
- try:
- result = e.output.decode(locale.getdefaultlocale()[1])
- except Exception:
- # If decode with `locale.getdefaultlocale()` failed, just decode with utf8
- result = e.output.decode('utf8')
- returncode = e.returncode
- return {"code": returncode, "result": result}
- else:
- if IS_WIN:
- result = subprocess.call(args=cmd, **Util.subprocess_args())
- else:
- result = subprocess.call(args=cmd, shell=True)
- return result
-
- @staticmethod
- def resource_path(relative_path):
- """ Get absolute path to resource, works for dev and for PyInstaller """
- try:
- # PyInstaller creates a temp folder and stores path in _MEIPASS
- base_path = sys._MEIPASS
- except Exception:
- base_path = os.path.abspath(".")
-
- return os.path.join(base_path, relative_path)
-
-
- class ConfigHelper:
- def __init__(self, config_db):
- self._config_db = os.path.abspath(config_db)
-
- if not os.path.isfile(config_db):
- raise Exception(f"{config_db} is not a valid file!")
-
- try:
- self._con = sqlite3.connect(self._config_db)
- except Exception as e:
- print("Failed to load database: ", e)
- if self._con is not None:
- self._con.close()
- self._con = None
-
- def global_config(self):
- try:
- # Load basic configuration
- cursor = self._con.cursor()
- cursor.execute("SELECT version, config FROM GlobalConfig WHERE id=1")
- row = cursor.fetchone()
- cursor.close()
- return row[1]
- except Exception as e:
- print("Failed to load global config", e)
- self._release_conn()
- return '{}'
-
- def update_global_config(self, global_config_str: str) -> bool:
- try:
- # Load basic configuration
- cursor = self._con.cursor()
- cursor.execute(f"""UPDATE GlobalConfig SET config=? WHERE id=1""", [global_config_str])
- self._con.commit()
- cursor.close()
- except Exception as e:
- print("Failed to update global config: ", e)
- self._release_conn()
- return False
- return True
-
- def scenario_config(self, scenario_id, cols: list=[]):
- try:
- # Scenario ID
- if not cols:
- cols_str = "*"
- else:
- cols_str = ','.join(cols)
-
- cursor = self._con.cursor()
- cursor.execute(f"""SELECT {cols_str} FROM Scenario WHERE id='{scenario_id}'""")
- row = cursor.fetchone()
- cursor.close()
- return row
- except Exception as e:
- print(f"Failed to load config of {scenario_id}: ", e)
- self._release_conn()
- return ()
-
- def scenario_ids(self):
- rids = list()
- try:
- cursor = self._con.cursor()
- cursor.execute(f"""SELECT id FROM Scenario""")
- rows = cursor.fetchall()
- for row in rows:
- rids.append(row[0])
- cursor.close()
- except Exception as e:
- print(f"Failed to get IDs: ", e)
- self._release_conn()
- return rids
-
- def update_scenario_config(self, scenario_id, data: dict) -> bool:
- try:
- if len(data) == 0:
- return True
-
- item_list = []
- val_list = []
-
- for k, v in data.items():
- item_list.append(f"{k}=?")
- val_list.append(json.dumps(v, indent=4))
-
- sql_piece = ','.join(item_list)
-
- # Scenario ID
- cursor = self._con.cursor()
- cursor.execute(f"""UPDATE Scenario SET {sql_piece} WHERE id='{scenario_id}'""", val_list)
- self._con.commit()
- cursor.close()
- except Exception as e:
- print(f"Failed to update config of {scenario_id}: ", e)
- self._release_conn()
- return False
- return True
-
- def template(self, name):
- try:
- cursor = self._con.cursor()
- cursor.execute(f"""SELECT template FROM Template WHERE name='{name}'""")
- row = cursor.fetchone()
- cursor.close()
- return row[0]
- except Exception as e:
- print(f"Failed to get template {name}: ", e)
- self._release_conn()
- return ""
-
- def template_update(self, name, data):
- try:
- cursor = self._con.cursor()
- cursor.execute(f"""UPDATE Template SET template="{data}" WHERE name={name}""")
- self._con.commit()
- cursor.close()
- except Exception as e:
- print(f"Failed to update template {name}: ", e)
- self._release_conn()
- return False
- return True
-
- def _release_conn(self):
- if self._con is not None:
- self._con.close()
- self._con = None
-
-
- def __del__(self):
- self._release_conn()
-
-
- if __name__ == "__main__":
- print(Util.run_command("ls", True))
-
- test_data_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'test-data')
- ch = ConfigHelper(os.path.join(test_data_dir, 'config', 'bridge', 'config.cfg'))
- global_config = json.loads(ch.global_config())
- for col in ch.scenario_config(19146703, ['common']):
- if isinstance(col, str):
- global_config.update(json.loads(col))
-
- cfg = Config(json.dumps(global_config))
- print(cfg)
-
- # print(json.dumps(global_config, indent=4))
|