From 00793064bc600a422f70c54737e973e199cebc05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matteo=20=E2=84=B1an?= Date: Mon, 4 May 2020 23:51:46 +0200 Subject: [PATCH] py-kms Gui: matched all terminal options so far done; added modes onlyserver / only client Introduced option for asynchronous messages Slimmed down parsing process with improved errors catching --- py-kms/Etrigan.py | 4 +- py-kms/pykms_Base.py | 16 +- py-kms/pykms_Client.py | 65 +++++--- py-kms/pykms_Format.py | 166 ++++++++++++++----- py-kms/pykms_GuiBase.py | 353 ++++++++++++++++++++++++++++++---------- py-kms/pykms_GuiMisc.py | 84 ++++++---- py-kms/pykms_Misc.py | 262 ++++++++++++++++++++++++----- py-kms/pykms_Server.py | 202 +++++++++++++---------- 8 files changed, 825 insertions(+), 327 deletions(-) diff --git a/py-kms/Etrigan.py b/py-kms/Etrigan.py index 1d90a53..fd0571e 100644 --- a/py-kms/Etrigan.py +++ b/py-kms/Etrigan.py @@ -592,8 +592,8 @@ def main(): parser = Etrigan_parser() args = vars(parser.parse_args()) # Check arguments. - Etrigan_check().checkfile(args['etriganpid'], 'pidfile', '.pid') - Etrigan_check().checkfile(args['etriganlog'], 'pidfile', '.log') + Etrigan_check().checkfile(args['etriganpid'], '--etrigan-pid', '.pid') + Etrigan_check().checkfile(args['etriganlog'], '--etrigan-log', '.log') # Setup daemon. jasonblood_1 = Etrigan(pidfile = args['etriganpid'], logfile = args['etriganlog'], loglevel = args['etriganlev'], diff --git a/py-kms/pykms_Base.py b/py-kms/pykms_Base.py index 15c4ff9..a20edb0 100644 --- a/py-kms/pykms_Base.py +++ b/py-kms/pykms_Base.py @@ -140,21 +140,21 @@ class kmsBase: # https://docs.microsoft.com/en-us/windows/deployment/volume-activation/activate-windows-10-clients-vamt MinClients = kmsRequest['requiredClientCount'] RequiredClients = MinClients * 2 - if self.srv_config["CurrentClientCount"] != None: - if 0 < self.srv_config["CurrentClientCount"] < MinClients: + if self.srv_config["clientcount"] != None: + if 0 < self.srv_config["clientcount"] < MinClients: # fixed to 6 (product server) or 26 (product desktop) currentClientCount = MinClients + 1 pretty_printer(log_obj = loggersrv.warning, put_text = "{reverse}{yellow}{bold}Not enough clients ! Fixed with %s, but activated client \ could be detected as not genuine !{end}" %currentClientCount) - elif MinClients <= self.srv_config["CurrentClientCount"] < RequiredClients: - currentClientCount = self.srv_config["CurrentClientCount"] + elif MinClients <= self.srv_config["clientcount"] < RequiredClients: + currentClientCount = self.srv_config["clientcount"] pretty_printer(log_obj = loggersrv.warning, put_text = "{reverse}{yellow}{bold}With count = %s, activated client could be detected as not genuine !{end}" %currentClientCount) - elif self.srv_config["CurrentClientCount"] >= RequiredClients: + elif self.srv_config["clientcount"] >= RequiredClients: # fixed to 10 (product server) or 50 (product desktop) currentClientCount = RequiredClients - if self.srv_config["CurrentClientCount"] > RequiredClients: + if self.srv_config["clientcount"] > RequiredClients: pretty_printer(log_obj = loggersrv.warning, put_text = "{reverse}{yellow}{bold}Too many clients ! Fixed with %s{end}" %currentClientCount) else: @@ -230,8 +230,8 @@ could be detected as not genuine !{end}" %currentClientCount) # rule: timeserver - 4h <= timeclient <= timeserver + 4h, check if is satisfied. response['responseTime'] = kmsRequest['requestTime'] response['currentClientCount'] = currentClientCount - response['vLActivationInterval'] = self.srv_config["VLActivationInterval"] - response['vLRenewalInterval'] = self.srv_config["VLRenewalInterval"] + response['vLActivationInterval'] = self.srv_config["activation"] + response['vLRenewalInterval'] = self.srv_config["renewal"] if self.srv_config['sqlite'] and self.srv_config['dbSupport']: response = sql_update_epid(self.dbName, kmsRequest, response) diff --git a/py-kms/pykms_Client.py b/py-kms/pykms_Client.py index 0ee89b8..e84b1b8 100644 --- a/py-kms/pykms_Client.py +++ b/py-kms/pykms_Client.py @@ -24,6 +24,7 @@ from pykms_RpcBase import rpcBase from pykms_DB2Dict import kmsDB2Dict from pykms_Misc import check_setup from pykms_Misc import KmsParser, KmsParserException, KmsParserHelp +from pykms_Misc import kms_parser_get, kms_parser_check_optionals, kms_parser_check_positionals from pykms_Format import justify, byterize, enco, deco, pretty_printer clt_version = "py-kms_2020-02-02" @@ -56,8 +57,10 @@ clt_options = { 'choi' : ["WindowsVista","Windows7","Windows8","Windows8.1","Windows10","Office2010","Office2013","Office2016","Office2019"]}, 'cmid' : {'help' : 'Use this flag to manually specify a CMID to use. If no CMID is specified, a random CMID will be generated.', 'def' : None, 'des' : "cmid"}, - 'name' : {'help' : 'Use this flag to manually specify an ASCII machineName to use. If no machineName is specified a random machineName \ -will be generated.', 'def' : None, 'des' : "machineName"}, + 'name' : {'help' : 'Use this flag to manually specify an ASCII machine name to use. If no machine name is specified a random one \ +will be generated.', 'def' : None, 'des' : "machine"}, + 'asyncmsg' : {'help' : 'Prints pretty / logging messages asynchronously. Desactivated by default.', + 'def' : False, 'des' : "asyncmsg"}, 'llevel' : {'help' : 'Use this option to set a log level. The default is \"ERROR\".', 'def' : "ERROR", 'des' : "loglevel", 'choi' : ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "MINI"]}, 'lfile' : {'help' : 'Use this option to set an output log file. The default is \"pykms_logclient.log\". Type \"STDOUT\" to view \ @@ -67,10 +70,7 @@ log info on stdout. Type \"FILESTDOUT\" to combine previous actions.', } def client_options(): - try: - client_parser = KmsParser(description = clt_description, epilog = 'version: ' + clt_version, add_help = False, allow_abbrew = False) - except TypeError: - client_parser = KmsParser(description = clt_description, epilog = 'version: ' + clt_version, add_help = False) + client_parser = KmsParser(description = clt_description, epilog = 'version: ' + clt_version, add_help = False) client_parser.add_argument("ip", nargs = "?", action = "store", default = clt_options['ip']['def'], help = clt_options['ip']['help'], type = str) client_parser.add_argument("port", nargs = "?", action = "store", default = clt_options['port']['def'], @@ -81,6 +81,8 @@ def client_options(): help = clt_options['cmid']['help'], type = str) client_parser.add_argument("-n", "--name", dest = clt_options['name']['des'] , default = clt_options['name']['def'], help = clt_options['name']['help'], type = str) + client_parser.add_argument("-y", "--async-msg", action = "store_true", dest = clt_options['asyncmsg']['des'], + default = clt_options['asyncmsg']['def'], help = clt_options['asyncmsg']['help']) client_parser.add_argument("-V", "--loglevel", dest = clt_options['llevel']['des'], action = "store", choices = clt_options['llevel']['choi'], default = clt_options['llevel']['def'], help = clt_options['llevel']['help'], type = str) @@ -88,14 +90,25 @@ def client_options(): default = clt_options['lfile']['def'], help = clt_options['lfile']['help'], type = str) client_parser.add_argument("-S", "--logsize", dest = clt_options['lsize']['des'], action = "store", default = clt_options['lsize']['def'], help = clt_options['lsize']['help'], type = float) + client_parser.add_argument("-h", "--help", action = "help", help = "show this help message and exit") try: - if "-h" in sys.argv[1:]: + userarg = sys.argv[1:] + + # Run help. + if any(arg in ["-h", "--help"] for arg in userarg): KmsParserHelp().printer(parsers = [client_parser]) - clt_config.update(vars(client_parser.parse_args())) + + # Get stored arguments. + pykmsclt_zeroarg, pykmsclt_onearg = kms_parser_get(client_parser) + # Update pykms options for dict client config. + kms_parser_check_optionals(userarg, pykmsclt_zeroarg, pykmsclt_onearg, msg = 'optional py-kms client', + exclude_opt_len = ['-F', '--logfile']) + kms_parser_check_positionals(clt_config, client_parser.parse_args, msg = 'positional py-kms client') + except KmsParserException as e: - pretty_printer(put_text = "{reverse}{red}{bold}%s. Exiting...{end}" %str(e), to_exit = True) + pretty_printer(put_text = "{reverse}{red}{bold}%s. Exiting...{end}" %str(e), to_exit = True, where = "clt") def client_check(): # Setup and some checks. @@ -107,18 +120,22 @@ def client_check(): uuid.UUID(clt_config['cmid']) except ValueError: pretty_printer(log_obj = loggerclt.error, to_exit = True, where = "clt", - put_text = "{reverse}{red}{bold}Bad CMID. Exiting...{end}") - # Check machineName. - if clt_config['machineName'] is not None: + put_text = "{reverse}{red}{bold}argument `-c/--cmid`: invalid with: '%s'. Exiting...{end}" %clt_config['cmid']) + + # Check machine name. + if clt_config['machine'] is not None: try: - clt_config['machineName'].encode('utf-16le') + clt_config['machine'].encode('utf-16le') + + if len(clt_config['machine']) < 2: + pretty_printer(log_obj = loggerclt.error, to_exit = True, where = "clt", + put_text = "{reverse}{red}{bold}argument `-n/--name`: too short (required 2 - 63 chars). Exiting...{end}") + elif len(clt_config['machine']) > 63: + pretty_printer(log_obj = loggerclt.error, to_exit = True, where = "clt", + put_text = "{reverse}{red}{bold}argument `-n/--name`: too long (required 2 - 63 chars). Exiting...{end}") except UnicodeEncodeError: pretty_printer(log_obj = loggerclt.error, to_exit = True, where = "clt", - put_text = "{reverse}{red}{bold}Bad machineName. Exiting...{end}") - - if len(clt_config['machineName']) < 2 or len(clt_config['machineName']) > 63: - pretty_printer(log_obj = loggerclt.error, to_exit = True, where = "clt", - put_text = "{reverse}{red}{bold}machineName must be between 2 and 63 characters in length. Exiting...{end}") + put_text = "{reverse}{red}{bold}argument `-n/--name`: invalid with: '%s'. Exiting...{end}" %clt_config['machine']) clt_config['call_id'] = 1 @@ -149,8 +166,14 @@ def client_update(): def client_create(): loggerclt.info("Connecting to %s on port %d..." % (clt_config['ip'], clt_config['port'])) - s = socket.create_connection((clt_config['ip'], clt_config['port'])) - loggerclt.info("Connection successful !") + try: + s = socket.create_connection((clt_config['ip'], clt_config['port'])) + loggerclt.info("Connection successful !") + except (socket.gaierror, socket.error) as e: + pretty_printer(log_obj = loggerclt.error, to_exit = True, where = "clt", + put_text = "{reverse}{red}{bold}Connection failed '%s:%d': %s. Exiting...{end}" %(clt_config['ip'], + clt_config['port'], + str(e))) binder = pykms_RpcBind.handler(None, clt_config) RPC_Bind = enco(str(binder.generateRequest()), 'latin-1') @@ -248,7 +271,7 @@ def createKmsRequestBase(): requestDict['previousClientMachineId'] = '\0' * 16 # I'm pretty sure this is supposed to be a null UUID. requestDict['requiredClientCount'] = clt_config['RequiredClientCount'] requestDict['requestTime'] = dt_to_filetime(datetime.datetime.utcnow()) - requestDict['machineName'] = (clt_config['machineName'] if (clt_config['machineName'] is not None) else + requestDict['machineName'] = (clt_config['machine'] if (clt_config['machine'] is not None) else ''.join(random.choice(string.ascii_letters + string.digits) for i in range(random.randint(2,63)))).encode('utf-16le') requestDict['mnPad'] = '\0'.encode('utf-16le') * (63 - len(requestDict['machineName'].decode('utf-16le'))) diff --git a/py-kms/pykms_Format.py b/py-kms/pykms_Format.py index 080609b..20fa8c4 100644 --- a/py-kms/pykms_Format.py +++ b/py-kms/pykms_Format.py @@ -5,6 +5,7 @@ import re import sys import os from collections import OrderedDict +import logging try: # Python 2.x imports @@ -16,6 +17,7 @@ except ImportError: import queue as Queue pyver = sys.version_info[:2] + #---------------------------------------------------------------------------------------------------------------------------------------------------------- def enco(strg, typ = 'latin-1'): @@ -195,13 +197,15 @@ if pyver < (3, 3): file = kwargs.get('file', sys.stdout) file.flush() if file is not None else sys.stdout.flush() -# based on: https://ryanjoneil.github.io/posts/2014-02-14-capturing-stdout-in-a-python-child-process.html, -# but not using threading/multiprocessing so: -# 1) message visualization order preserved. -# 2) newlines_count function output not wrong. +# based on: https://ryanjoneil.github.io/posts/2014-02-14-capturing-stdout-in-a-python-child-process.html +queue_print = Queue.Queue() + class ShellMessage(object): - view = True - count, remain, numlist = (0, 0, []) + viewsrv, viewclt = (True for _ in range(2)) + asyncmsgsrv, asyncmsgclt = (False for _ in range(2)) + indx, count, remain, numlist = (0, 0, 0, []) + loggersrv_pty = logging.getLogger('logsrvpty') + loggerclt_pty = logging.getLogger('logcltpty') class Collect(StringIO): # Capture string sent to stdout. @@ -215,8 +219,9 @@ class ShellMessage(object): self.put_text = put_text self.where = where self.plaintext = [] - self.path = os.path.dirname(os.path.abspath( __file__ )) + '/pykms_newlines.txt' - self.print_queue = Queue.Queue() + self.path_nl = os.path.dirname(os.path.abspath( __file__ )) + '/pykms_newlines.txt' + self.path_clean_nl = os.path.dirname(os.path.abspath( __file__ )) + '/pykms_clean_newlines.txt' + self.queue_get = Queue.Queue() def formatter(self, msgtofrmt): if self.newlines: @@ -236,14 +241,14 @@ class ShellMessage(object): def newlines_file(self, mode, *args): try: - with open(self.path, mode) as file: + with open(self.path_nl, mode) as file: if mode in ['w', 'a']: file.write(args[0]) elif mode == 'r': data = [int(i) for i in [line.rstrip('\n') for line in file.readlines()]] self.newlines, ShellMessage.remain = data[0], sum(data[1:]) except: - with open(self.path, 'w') as file: + with open(self.path_nl, 'w') as file: pass def newlines_count(self, num): @@ -265,40 +270,114 @@ class ShellMessage(object): self.continuecount = True elif num in [-2 ,-4]: self.newlines_file('r') - if num == 21: - ShellMessage.count, ShellMessage.remain, ShellMessage.numlist = (0, 0, []) - os.remove(self.path) - def run(self): - # view = False part. - if not ShellMessage.view: - if self.get_text: - self.newlines = 0 - if self.put_text is not None: - for msg in self.put_text: - self.formatter(msg) - else: - for num in self.nshell: - self.formatter(MsgMap[num]) - return self.plaintext + self.newlines_clean(num) + + def newlines_clean(self, num): + if num == 0: + with open(self.path_clean_nl, 'w') as file: + file.write('clean newlines') + try: + with open(self.path_clean_nl, 'r') as file: + some = file.read() + if num == 21: + ShellMessage.count, ShellMessage.remain, ShellMessage.numlist = (0, 0, []) + os.remove(self.path_nl) + os.remove(self.path_clean_nl) + except: + if num == 19: + ShellMessage.count, ShellMessage.remain, ShellMessage.numlist = (0, 0, []) + os.remove(self.path_nl) + + def putter(self, aqueue, toput): + try: + aqueue.put_nowait(toput) + except Queue.Full: + pass + + def execute(self): + self.manage() + ShellMessage.indx += 1 + + def print_logging_setup(self, logger, async_flag, formatter = logging.Formatter('%(name)s %(message)s')): + from pykms_GuiBase import gui_redirector + stream = gui_redirector(StringIO()) + handler = logging.StreamHandler(stream) + handler.name = 'LogStream' + handler.setLevel(logging.INFO) + handler.setFormatter(formatter) + + if logger.handlers: + logger.handlers = [] + + if async_flag: + from pykms_Misc import MultiProcessingLogHandler + logger.addHandler(MultiProcessingLogHandler('Thread-AsyncMsg{0}'.format(handler.name), handler = handler)) + else: + logger.addHandler(handler) + logger.setLevel(logging.INFO) + + def print_logging(self, toprint): + if (self.nshell and ((0 in self.nshell) or (2 in self.nshell and not ShellMessage.viewclt))) or ShellMessage.indx == 0: + from pykms_GuiBase import gui_redirector_setup, gui_redirector_clear + gui_redirector_setup() + gui_redirector_clear() + self.print_logging_setup(ShellMessage.loggersrv_pty, ShellMessage.asyncmsgsrv) + self.print_logging_setup(ShellMessage.loggerclt_pty, ShellMessage.asyncmsgclt) + + if self.where == 'srv': + ShellMessage.loggersrv_pty.info(toprint) + elif self.where == 'clt': + ShellMessage.loggerclt_pty.info(toprint) + + def notview(self): + if self.get_text: + self.newlines = 0 + if self.put_text is not None: + for msg in self.put_text: + self.formatter(msg) else: + for num in self.nshell: + self.formatter(MsgMap[num]) + self.putter(self.queue_get, self.plaintext) + + def manage(self): + if not ShellMessage.viewsrv: + # viewsrv = False, viewclt = True. + if ShellMessage.viewclt: + if self.where == 'srv': + self.notview() + return + else: + # viewsrv = False, viewclt = False. + self.notview() return + else: + # viewsrv = True, viewclt = False. + if not ShellMessage.viewclt: + if self.where == 'clt': + self.notview() + return + else: + # viewsrv = True, viewclt = True. + pass + # Do job. self.produce() - toprint = self.consume(timeout = 0.1) - # Redirect output. + toprint = self.consume(queue_print, timeout = 0.1) + if sys.stdout.isatty(): - print(toprint) + print(toprint, flush = True) else: try: - # Import after variables creation. - from pykms_GuiBase import gui_redirect - gui_redirect(toprint, self.where) + self.print_logging(toprint) except: - print(toprint) + print(toprint, flush = True) + # Get string/s printed. if self.get_text: - return self.plaintext + self.putter(self.queue_get, self.plaintext) + return def produce(self): # Save everything that would otherwise go to stdout. @@ -327,15 +406,12 @@ class ShellMessage(object): finally: # Restore stdout and send content. sys.stdout = sys.__stdout__ - try: - self.print_queue.put(outstream.getvalue()) - except Queue.Full: - pass + self.putter(queue_print, outstream.getvalue()) - def consume(self, timeout = None): + def consume(self, aqueue, timeout = None): try: - toprint = self.print_queue.get(block = timeout is not None, timeout = timeout) - self.print_queue.task_done() + toprint = aqueue.get(block = timeout is not None, timeout = timeout) + aqueue.task_done() return toprint except Queue.Empty: return None @@ -381,11 +457,13 @@ def pretty_printer(**kwargs): options['get_text'] = False # Process messages. - plain_messages = ShellMessage.Process(options['num_text'], - get_text = options['get_text'], - put_text = options['put_text'], - where = options['where']).run() + shmsg = ShellMessage.Process(options['num_text'], + get_text = options['get_text'], + put_text = options['put_text'], + where = options['where']) + shmsg.execute() + plain_messages = shmsg.consume(shmsg.queue_get, timeout = None) if options['log_obj']: for plain_message in plain_messages: options['log_obj'](plain_message) diff --git a/py-kms/pykms_GuiBase.py b/py-kms/pykms_GuiBase.py index 7d3c3df..71f504e 100644 --- a/py-kms/pykms_GuiBase.py +++ b/py-kms/pykms_GuiBase.py @@ -48,25 +48,36 @@ def get_ip_address(): else: ip = 'Unknown' return ip - -def gui_redirect(str_to_print, where): - global txsrv, txclt, txcol +def gui_redirector(stream, redirect_to = TextRedirect.Pretty, redirect_conditio = True, stderr_side = "srv"): + global txsrv, txclt, txcol + if redirect_conditio: + if stream == 'stdout': + sys.stdout = redirect_to(txsrv, txclt, txcol) + elif stream == 'stderr': + sys.stderr = redirect_to(txsrv, txclt, txcol, stderr_side) + else: + stream = redirect_to(txsrv, txclt, txcol) + return stream + +def gui_redirector_setup(): + TextRedirect.Pretty.tag_num = 0 + TextRedirect.Pretty.newlinecut = [-1, -2, -4, -5] + +def gui_redirector_clear(): + global txsrv, oysrv try: - TextRedirect.StdoutRedirect(txsrv, txclt, txcol, str_to_print, where) + if oysrv: + txsrv.configure(state = 'normal') + txsrv.delete('1.0', 'end') + txsrv.configure(state = 'disabled') except: - print(str_to_print) + # self.onlysrv not defined (menu not used) + pass ##----------------------------------------------------------------------------------------------------------------------------------------------------------- -class KmsGui(tk.Tk): - def browse(self, entrywidget, options): - path = filedialog.askdirectory() - if os.path.isdir(path): - entrywidget.delete('0', 'end') - entrywidget.insert('end', path + os.sep + os.path.basename(options['lfile']['def'])) - - +class KmsGui(tk.Tk): def __init__(self, *args, **kwargs): tk.Tk.__init__(self, *args, **kwargs) self.wraplength = 200 @@ -96,19 +107,76 @@ class KmsGui(tk.Tk): self.option_add('*TCombobox*Listbox.font', self.optfontredux) self.gui_create() + + def browse(self, entrywidget, options): + path = filedialog.askdirectory() + if os.path.isdir(path): + entrywidget.delete('0', 'end') + entrywidget.insert('end', path + os.sep + os.path.basename(options['lfile']['def'])) + + def invert(self, widgets = []): + for widget in widgets: + if widget['state'] == 'normal': + widget.configure(state = 'disabled') + elif widget['state'] == 'disabled': + widget.configure(state = 'normal') + + def gui_menu(self): + self.onlysrv, self.onlyclt = (False for _ in range(2)) + menubar = tk.Menu(self) + prefmenu = tk.Menu(menubar, tearoff = 0, font = ("Noto Sans Regular", 10), borderwidth = 3, relief = 'ridge') + menubar.add_cascade(label = 'Preferences', menu = prefmenu) + prefmenu.add_command(label = 'Enable server-side mode', command = lambda: self.pref_onlysrv(prefmenu)) + prefmenu.add_command(label = 'Enable client-side mode', command = lambda: self.pref_onlyclt(prefmenu)) + self.config(menu = menubar) + def pref_onlysrv(self, menu): + global oysrv + + if self.onlyclt or serverthread.is_running_server: + return + self.onlysrv = not self.onlysrv + if self.onlysrv: + menu.entryconfigure(0, label = 'Disable server-side mode') + self.clt_on_show(force_remove = True) + else: + menu.entryconfigure(0, label = 'Enable server-side mode') + self.invert(widgets = [self.shbtnclt]) + oysrv = self.onlysrv + + def pref_onlyclt(self, menu): + if self.onlysrv or serverthread.is_running_server: + return + self.onlyclt = not self.onlyclt + if self.onlyclt: + menu.entryconfigure(1, label = 'Disable client-side mode') + if self.shbtnclt['text'] == 'SHOW\nCLIENT': + self.clt_on_show(force_view = True) + self.optsrvwin.grid_remove() + self.msgsrvwin.grid_remove() + gui_redirector('stderr', redirect_to = TextRedirect.Stderr, stderr_side = "clt") + else: + menu.entryconfigure(1, label = 'Enable client-side mode') + self.optsrvwin.grid() + self.msgsrvwin.grid() + gui_redirector('stderr', redirect_to = TextRedirect.Stderr) + + self.invert(widgets = [self.runbtnsrv, self.shbtnclt, self.runbtnclt]) + def gui_create(self): ## Create server gui self.gui_srv() ## Create client gui + other operations. self.gui_complete() + ## Create menu. + self.gui_menu() ## Create globals for printing process (redirect stdout). global txsrv, txclt, txcol txsrv = self.textboxsrv.get() txclt = self.textboxclt.get() txcol = self.customcolors ## Redirect stderr. - sys.stderr = TextRedirect.StderrRedirect(txsrv, txclt, txcol) + gui_redirector('stderr', redirect_to = TextRedirect.Stderr) def gui_pages_show(self, pagename, side): # https://stackoverflow.com/questions/7546050/switch-between-two-frames-in-tkinter @@ -162,7 +230,7 @@ class KmsGui(tk.Tk): btnani = tk.Button(aniwin) btnani.grid(row = 0, column = col[2], padx = 2, pady = 2, sticky = stick) self.pagewidgets[side]["BtnAni"][position] = btnani - # customize buttons. + ## Customize buttons. custom_pages(self, side) def gui_pages_create(self, parent, side, create = {}): @@ -199,7 +267,7 @@ class KmsGui(tk.Tk): self.btnsrvwin = tk.Canvas(self.masterwin, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge') self.optsrvwin = tk.Canvas(self.masterwin, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge') self.msgsrvwin = tk.Frame(self.masterwin, background = self.customcolors['black'], relief = 'ridge', width = 300, height = 200) - + ## Layout main containers. self.masterwin.grid(row = 0, column = 0, sticky = 'nsew') self.btnsrvwin.grid(row = 0, column = 1, padx = 2, pady = 2, sticky = 'nw') @@ -209,11 +277,11 @@ class KmsGui(tk.Tk): self.pagewidgets = {} - ## subpages of optsrvwin. + ## Subpages of "optsrvwin". self.gui_pages_create(parent = self.optsrvwin, side = "Srv", create = {"PageStart": None, "PageEnd": None}) - ## continue to grid. + ## Continue to grid. self.msgsrvwin.grid(row = 1, column = 2, padx = 1, pady = 1, sticky = 'nsew') self.msgsrvwin.grid_propagate(False) self.msgsrvwin.grid_columnconfigure(0, weight = 1) @@ -285,7 +353,7 @@ class KmsGui(tk.Tk): self.activ.insert('end', str(srv_options['activation']['def'])) ToolTip(self.activ, text = srv_options['activation']['help'], wraplength = self.wraplength) # Renewal Interval. - renewlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Activation Interval: ', font = self.optfont) + renewlbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Renewal Interval: ', font = self.optfont) self.renew = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.optfont, validate = "key", validatecommand = self.validation_int) self.renew.insert('end', str(srv_options['renewal']['def'])) @@ -298,28 +366,34 @@ class KmsGui(tk.Tk): ToolTip(self.srvfile, text = srv_options['lfile']['help'], wraplength = self.wraplength) srvfilebtnwin = tk.Button(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Browse', command = lambda: self.browse(self.srvfile, srv_options)) - # Loglevel. srvlevellbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Loglevel: ', font = self.optfont) self.srvlevel = ttk.Combobox(self.pagewidgets["Srv"]["PageWin"]["PageStart"], values = tuple(srv_options['llevel']['choi']), width = 10, height = 10, font = self.optfontredux, state = "readonly") self.srvlevel.set(srv_options['llevel']['def']) ToolTip(self.srvlevel, text = srv_options['llevel']['help'], wraplength = self.wraplength) - - self.chksrvfile = ListboxOfRadiobuttons(self.pagewidgets["Srv"]["PageWin"]["PageStart"], - ['FILE', 'FILEOFF', 'STDOUT', 'STDOUTOFF', 'FILESTDOUT'], - self.optfontredux, - changed = [(self.srvfile, srv_options['lfile']['def']), - (srvfilebtnwin, ''), - (self.srvlevel, srv_options['llevel']['def'])], - width = 10, height = 1, borderwidth = 2, relief = 'ridge') - # Logsize. srvsizelbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Logsize: ', font = self.optfont) self.srvsize = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.optfont, validate = "key", validatecommand = self.validation_float) self.srvsize.insert('end', srv_options['lsize']['def']) ToolTip(self.srvsize, text = srv_options['lsize']['help'], wraplength = self.wraplength) + # Asynchronous messages. + self.chkvalsrvasy = tk.BooleanVar() + self.chkvalsrvasy.set(srv_options['asyncmsg']['def']) + chksrvasy = tk.Checkbutton(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Async\nMsg', + font = self.optfontredux, var = self.chkvalsrvasy, relief = 'groove') + ToolTip(chksrvasy, text = srv_options['asyncmsg']['help'], wraplength = self.wraplength) + + # Listbox radiobuttons server. + self.chksrvfile = ListboxOfRadiobuttons(self.pagewidgets["Srv"]["PageWin"]["PageStart"], + ['FILE', 'FILEOFF', 'STDOUT', 'STDOUTOFF', 'FILESTDOUT'], + self.optfontredux, + changed = [(self.srvfile, srv_options['lfile']['def']), + (srvfilebtnwin, ''), + (self.srvsize, srv_options['lsize']['def']), + (self.srvlevel, srv_options['llevel']['def'])], + width = 10, height = 1, borderwidth = 2, relief = 'ridge') ## Layout widgets (optsrvwin:Srv:PageWin:PageStart) ver.grid(row = 0, column = 0, columnspan = 3, padx = 5, pady = 5, sticky = 'ew') @@ -344,6 +418,7 @@ class KmsGui(tk.Tk): self.srvfile.grid(row = 10, column = 1, padx = 5, pady = 5, sticky = 'ew') srvfilebtnwin.grid(row = 10, column = 2, padx = 5, pady = 5, sticky = 'ew') self.chksrvfile.grid(row = 11, column = 1, padx = 5, pady = 5, sticky = 'ew') + chksrvasy.grid(row = 11, column = 2, padx = 5, pady = 5, sticky = 'ew') srvlevellbl.grid(row = 12, column = 0, padx = 5, pady = 5, sticky = 'e') self.srvlevel.grid(row = 12, column = 1, padx = 5, pady = 5, sticky = 'ew') srvsizelbl.grid(row = 13, column = 0, padx = 5, pady = 5, sticky = 'e') @@ -352,23 +427,25 @@ class KmsGui(tk.Tk): ## Create widgets (optsrvwin:Srv:PageWin:PageEnd)------------------------------------------------------------------------------------------- # Timeout connection. timeout0lbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], text = 'Timeout connection: ', font = self.optfont) - self.timeout0 = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], width = 10, font = self.optfont) + self.timeout0 = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], width = 16, font = self.optfont) self.timeout0.insert('end', str(srv_options['time0']['def'])) ToolTip(self.timeout0, text = srv_options['time0']['help'], wraplength = self.wraplength) # Sqlite database. self.chkvalsql = tk.BooleanVar() self.chkvalsql.set(srv_options['sql']['def']) chksql = tk.Checkbutton(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], text = 'Create Sqlite\nDatabase', - font = self.optfont, var = self.chkvalsql) + font = self.optfontredux, var = self.chkvalsql, relief = 'groove') ToolTip(chksql, text = srv_options['sql']['help'], wraplength = self.wraplength) ## Layout widgets (optsrvwin:Srv:PageWin:PageEnd) - tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], width = 0, height = 0).grid(row = 0, column = 0, padx = 5, pady = 5, sticky = 'nw') + # a label for vertical aligning with PageStart + tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], width = 0, + height = 0, bg = self.customcolors['lavender']).grid(row = 0, column = 0, padx = 5, pady = 5, sticky = 'nw') timeout0lbl.grid(row = 1, column = 0, padx = 5, pady = 5, sticky = 'e') - self.timeout0.grid(row = 1, column = 1, padx = 5, pady = 5, sticky = 'ew') - chksql.grid(row = 2, column = 1, padx = 5, pady = 5, sticky = 'ew') + self.timeout0.grid(row = 1, column = 1, padx = 5, pady = 5, sticky = 'w') + chksql.grid(row = 2, column = 1, padx = 5, pady = 5, sticky = 'w') - # Store Srv widgets. + # Store server-side widgets. self.storewidgets_srv = self.gui_store(side = "Srv", typewidgets = ['Button', 'Entry', 'TCombobox', 'Checkbutton']) self.storewidgets_srv.append(self.chksrvfile) @@ -377,28 +454,52 @@ class KmsGui(tk.Tk): relief = 'ridge', font = self.msgfont) self.textboxsrv.put() + def always_centered(self, geo, centered, refs): + x = (self.winfo_screenwidth() // 2) - (self.winfo_width() // 2) + y = (self.winfo_screenheight() // 2) - (self.winfo_height() // 2) + w, h, dx, dy = geo.split('+')[0].split('x') + geo.split('+')[1:] + + if w == refs[1]: + if centered: + self.geometry('+%d+%d' %(x, y)) + centered = False + elif w == refs[0]: + if not centered: + self.geometry('+%d+%d' %(x, y)) + centered = True + + if dx != str(x) or dy != str(y): + self.geometry('+%d+%d' %(x, 0)) + + self.after(200, self.always_centered, self.geometry(), centered, refs) + def gui_complete(self): ## Create client widgets (optcltwin, msgcltwin, btncltwin) self.update_idletasks() # update Gui to get btnsrvwin values --> btncltwin. + minw, minh = self.winfo_width(), self.winfo_height() self.iconify() self.gui_clt() - minw, minh = self.winfo_width(), self.winfo_height() - # Main window custom background. + maxw, minh = self.winfo_width(), self.winfo_height() + ## Main window custom background. self.update_idletasks() # update Gui for custom background self.iconify() custom_background(self) - # Main window other modifications. + ## Main window other modifications. + self.eval('tk::PlaceWindow %s center' %self.winfo_pathname(self.winfo_id())) self.wm_attributes("-topmost", True) - self.protocol("WM_DELETE_WINDOW", lambda:0) - self.minsize(minw, minh) - self.resizable(True, False) - - def get_position(self, genericwidget): - x, y = (genericwidget.winfo_x(), genericwidget.winfo_y()) - w, h = (genericwidget.winfo_width(), genericwidget.winfo_height()) + self.protocol("WM_DELETE_WINDOW", lambda: 0) + ## Disable maximize button. + self.resizable(False, False) + ## Centered window. + self.always_centered(self.geometry(), False, [minw, maxw]) + + def get_position(self, widget): + x, y = (widget.winfo_x(), widget.winfo_y()) + w, h = (widget.winfo_width(), widget.winfo_height()) return x, y, w, h - def gui_clt(self): + def gui_clt(self): + self.count_clear = 0 self.optcltwin = tk.Canvas(self.masterwin, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge') self.msgcltwin = tk.Frame(self.masterwin, background = self.customcolors['black'], relief = 'ridge', width = 300, height = 200) self.btncltwin = tk.Canvas(self.masterwin, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge') @@ -411,29 +512,25 @@ class KmsGui(tk.Tk): self.optcltwin.grid_rowconfigure(0, weight = 1) self.optcltwin.grid_columnconfigure(1, weight = 1) - # subpages of optcltwin. + ## Subpages of "optcltwin". self.gui_pages_create(parent = self.optcltwin, side = "Clt", create = {"PageStart": None, "PageEnd": None}) - # continue to grid. + ## Continue to grid. self.msgcltwin.grid(row = 1, column = 4, padx = 1, pady = 1, sticky = 'nsew') self.msgcltwin.grid_propagate(False) self.msgcltwin.grid_columnconfigure(0, weight = 1) self.msgcltwin.grid_rowconfigure(0, weight = 1) - # Create widgets (btncltwin) ---------------------------------------------------------------------------------------------------------------- + ## Create widgets (btncltwin) ---------------------------------------------------------------------------------------------------------------- self.runbtnclt = tk.Button(self.btncltwin, text = 'START\nCLIENT', background = self.customcolors['blue'], foreground = self.customcolors['white'], relief = 'flat', font = self.btnwinfont, state = 'disabled', command = self.clt_on_start) - -## self.othbutt = tk.Button(self.btncltwin, text = 'Botton\n2', background = self.customcolors['green'], -## foreground = self.customcolors['white'], relief = 'flat', font = self.btnwinfont) - - # Layout widgets (btncltwin) + + ## Layout widgets (btncltwin) self.runbtnclt.grid(row = 0, column = 0, padx = 2, pady = 2, sticky = 'ew') -## self.othbutt.grid(row = 1, column = 0, padx = 2, pady = 2, sticky = 'ew') - # Create widgets (optcltwin:Clt:PageWin:PageStart) ------------------------------------------------------------------------------------------ + ## Create widgets (optcltwin:Clt:PageWin:PageStart) ------------------------------------------------------------------------------------------ # Version. cltver = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'You are running client version: ' + clt_version, foreground = self.customcolors['red'], font = self.othfont) @@ -479,21 +576,30 @@ class KmsGui(tk.Tk): self.cltlevel.set(clt_options['llevel']['def']) ToolTip(self.cltlevel, text = clt_options['llevel']['help'], wraplength = self.wraplength) - self.chkcltfile = ListboxOfRadiobuttons(self.pagewidgets["Clt"]["PageWin"]["PageStart"], - ['FILE', 'FILEOFF', 'STDOUT', 'STDOUTOFF', 'FILESTDOUT'], - self.optfontredux, - changed = [(self.cltfile, clt_options['lfile']['def']), - (cltfilebtnwin, ''), - (self.cltlevel, clt_options['llevel']['def'])], - width = 10, height = 1, borderwidth = 2, relief = 'ridge') # Logsize. cltsizelbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Logsize: ', font = self.optfont) self.cltsize = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageStart"], width = 10, font = self.optfont, validate = "key", validatecommand = self.validation_float) self.cltsize.insert('end', clt_options['lsize']['def']) ToolTip(self.cltsize, text = clt_options['lsize']['help'], wraplength = self.wraplength) + # Asynchronous messages. + self.chkvalcltasy = tk.BooleanVar() + self.chkvalcltasy.set(clt_options['asyncmsg']['def']) + chkcltasy = tk.Checkbutton(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Async\nMsg', + font = self.optfontredux, var = self.chkvalcltasy, relief = 'groove') + ToolTip(chkcltasy, text = clt_options['asyncmsg']['help'], wraplength = self.wraplength) + + # Listbox radiobuttons client. + self.chkcltfile = ListboxOfRadiobuttons(self.pagewidgets["Clt"]["PageWin"]["PageStart"], + ['FILE', 'FILEOFF', 'STDOUT', 'STDOUTOFF', 'FILESTDOUT'], + self.optfontredux, + changed = [(self.cltfile, clt_options['lfile']['def']), + (cltfilebtnwin, ''), + (self.cltsize, clt_options['lsize']['def']), + (self.cltlevel, clt_options['llevel']['def'])], + width = 10, height = 1, borderwidth = 2, relief = 'ridge') - # Layout widgets (optcltwin:Clt:PageWin:PageStart) + ## Layout widgets (optcltwin:Clt:PageWin:PageStart) cltver.grid(row = 0, column = 0, columnspan = 3, padx = 5, pady = 5, sticky = 'ew') cltipaddlbl.grid(row = 1, column = 0, padx = 5, pady = 5, sticky = 'e') self.cltipadd.grid(row = 1, column = 1, padx = 5, pady = 5, sticky = 'ew') @@ -509,21 +615,29 @@ class KmsGui(tk.Tk): self.cltfile.grid(row = 6, column = 1, padx = 5, pady = 5, sticky = 'ew') cltfilebtnwin.grid(row = 6, column = 2, padx = 5, pady = 5, sticky = 'ew') self.chkcltfile.grid(row = 7, column = 1, padx = 5, pady = 5, sticky = 'ew') + chkcltasy.grid(row = 7, column = 2, padx = 5, pady = 5, sticky = 'ew') cltlevellbl.grid(row = 8, column = 0, padx = 5, pady = 5, sticky = 'e') self.cltlevel.grid(row = 8, column = 1, padx = 5, pady = 5, sticky = 'ew') cltsizelbl.grid(row = 9, column = 0, padx = 5, pady = 5, sticky = 'e') self.cltsize.grid(row = 9, column = 1, padx = 5, pady = 5, sticky = 'ew') + # ugly fix when client-side mode is activated. + templbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], + bg = self.customcolors['lavender']).grid(row = 10, column = 0, + padx = 35, pady = 54, sticky = 'e') + ## Create widgets (optcltwin:Clt:PageWin:PageEnd) ------------------------------------------------------------------------------------------- ## Layout widgets (optcltwin:Clt:PageWin:PageEnd) - tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageEnd"], width = 0, height = 0).grid(row = 0, column = 0, padx = 5, pady = 5, sticky = 'nw') + # a label for vertical aligning with PageStart + tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageEnd"], width = 0, + height = 0, bg = self.customcolors['lavender']).grid(row = 0, column = 0, padx = 5, pady = 5, sticky = 'nw') - # Store Clt widgets. - self.storewidgets_clt = self.gui_store(side = "Clt", typewidgets = ['Button', 'Entry', 'TCombobox']) + ## Store client-side widgets. + self.storewidgets_clt = self.gui_store(side = "Clt", typewidgets = ['Button', 'Entry', 'TCombobox', 'Checkbutton']) self.storewidgets_clt.append(self.chkcltfile) - # Create widgets and layout (msgcltwin) ----------------------------------------------------------------------------------------------------- + ## Create widgets and layout (msgcltwin) ----------------------------------------------------------------------------------------------------- self.textboxclt = TextDoubleScroll(self.msgcltwin, background = self.customcolors['black'], wrap = 'none', state = 'disabled', relief = 'ridge', font = self.msgfont) self.textboxclt.put() @@ -540,22 +654,22 @@ class KmsGui(tk.Tk): # is a STRING. return value - def prep_logfile(self, filepath): + def prep_logfile(self, filepath, status): # FILE (pretty on, log view off, logfile yes) # FILEOFF (pretty on, log view off, no logfile) # STDOUT (pretty off, log view on, no logfile) # STDOUTOFF (pretty off, log view off, logfile yes) # FILESTDOUT (pretty off, log view on, logfile yes) - st = self.chksrvfile.state() - if st == 'FILE': + + if status == 'FILE': return filepath - elif st in ['FILESTDOUT', 'STDOUTOFF']: - return [st, filepath] - elif st in ['STDOUT', 'FILEOFF']: - return st + elif status in ['FILESTDOUT', 'STDOUTOFF']: + return [status, filepath] + elif status in ['STDOUT', 'FILEOFF']: + return status def validate_int(self, value): - return value == '' or value.isdigit() + return value == "" or value.isdigit() def validate_float(self, value): if value == "": @@ -566,13 +680,13 @@ class KmsGui(tk.Tk): except ValueError: return False - def clt_on_show(self, force = False): - if self.optcltwin.winfo_ismapped() or force: + def clt_on_show(self, force_remove = False, force_view = False): + if self.optcltwin.winfo_ismapped() or force_remove: self.shbtnclt['text'] = 'SHOW\nCLIENT' self.optcltwin.grid_remove() self.msgcltwin.grid_remove() self.btncltwin.place_forget() - else: + elif not self.optcltwin.winfo_ismapped() or force_view: self.shbtnclt['text'] = 'HIDE\nCLIENT' self.optcltwin.grid() self.msgcltwin.grid() @@ -580,12 +694,12 @@ class KmsGui(tk.Tk): def srv_on_start(self): if self.runbtnsrv['text'] == 'START\nSERVER': + self.on_clear([txsrv, txclt]) self.srv_actions_start() # wait for switch. while not serverthread.is_running_server: pass - self.on_clear([txsrv, txclt]) self.srv_toggle_all(on_start = True) # run thread for interrupting server when an error happens. self.srv_eject_thread = threading.Thread(target = self.srv_eject, name = "Thread-SrvEjt") @@ -609,12 +723,18 @@ class KmsGui(tk.Tk): srv_config[srv_options['count']['des']] = self.prep_option(self.count.get()) srv_config[srv_options['activation']['des']] = self.prep_option(self.activ.get()) srv_config[srv_options['renewal']['des']] = self.prep_option(self.renew.get()) - srv_config[srv_options['lfile']['des']] = self.prep_logfile(self.srvfile.get()) + srv_config[srv_options['lfile']['des']] = self.prep_logfile(self.srvfile.get(), self.chksrvfile.state()) + srv_config[srv_options['asyncmsg']['des']] = self.chkvalsrvasy.get() srv_config[srv_options['llevel']['des']] = self.srvlevel.get() - srv_config[srv_options['sql']['des']] = self.chkvalsql.get() srv_config[srv_options['lsize']['des']] = self.prep_option(self.srvsize.get()) - srv_config[srv_options['time0']['des']] = self.prep_option(self.timeout0.get()) + srv_config[srv_options['time0']['des']] = self.prep_option(self.timeout0.get()) + srv_config[srv_options['sql']['des']] = self.chkvalsql.get() + + ## Redirect stdout. + gui_redirector('stdout', redirect_to = TextRedirect.Log, + redirect_conditio = (srv_config[srv_options['lfile']['des']] in ['STDOUT', 'FILESTDOUT'])) + gui_redirector_setup() serverqueue.put('start') def srv_actions_stop(self): @@ -641,6 +761,8 @@ class KmsGui(tk.Tk): foreground = self.customcolors['white']) for widget in self.storewidgets_srv: widget.configure(state = 'normal') + if isinstance(widget, ListboxOfRadiobuttons): + widget.change() self.runbtnclt.configure(state = 'disabled') def srv_toggle_state(self): @@ -652,13 +774,18 @@ class KmsGui(tk.Tk): self.statesrv.configure(text = txt, foreground = color) def clt_on_start(self): + if self.onlyclt: + self.on_clear([txclt]) + else: + rng, add_newline = self.on_clear_setup() + self.on_clear([txsrv, txclt], clear_range = [rng, None], newline_list = [add_newline, False]) + self.clt_actions_start() # run thread for disabling interrupt server and client, when client running. self.clt_eject_thread = threading.Thread(target = self.clt_eject, name = "Thread-CltEjt") self.clt_eject_thread.setDaemon(True) self.clt_eject_thread.start() - self.on_clear([txsrv, txclt]) for widget in self.storewidgets_clt + [self.runbtnsrv, self.runbtnclt]: widget.configure(state = 'disabled') @@ -668,10 +795,16 @@ class KmsGui(tk.Tk): clt_config[clt_options['mode']['des']] = self.cltmode.get() clt_config[clt_options['cmid']['des']] = self.cltcmid.get() clt_config[clt_options['name']['des']] = self.cltname.get() + clt_config[clt_options['lfile']['des']] = self.prep_logfile(self.cltfile.get(), self.chkcltfile.state()) + clt_config[clt_options['asyncmsg']['des']] = self.chkvalcltasy.get() clt_config[clt_options['llevel']['des']] = self.cltlevel.get() - clt_config[clt_options['lfile']['des']] = self.prep_logfile(self.cltfile.get()) clt_config[clt_options['lsize']['des']] = self.prep_option(self.cltsize.get()) + ## Redirect stdout. + gui_redirector('stdout', redirect_to = TextRedirect.Log, + redirect_conditio = (clt_config[clt_options['lfile']['des']] in ['STDOUT', 'FILESTDOUT'])) + gui_redirector_setup() + # run client (in a thread). self.clientthread = client_thread(name = "Thread-Clt") self.clientthread.setDaemon(True) @@ -681,8 +814,18 @@ class KmsGui(tk.Tk): def clt_eject(self): while self.clientthread.is_alive(): sleep(0.1) - for widget in self.storewidgets_clt + [self.runbtnsrv, self.runbtnclt]: - widget.configure(state = 'normal') + + widgets = self.storewidgets_clt + [self.runbtnclt] + if not self.onlyclt: + widgets += [self.runbtnsrv] + + for widget in widgets: + if isinstance(widget, ttk.Combobox): + widget.configure(state = 'readonly') + else: + widget.configure(state = 'normal') + if isinstance(widget, ListboxOfRadiobuttons): + widget.change() def on_exit(self): if serverthread.is_running_server: @@ -693,8 +836,38 @@ class KmsGui(tk.Tk): server_terminate(serverthread, exit_thread = True) self.destroy() - def on_clear(self, widgetlist): - for widget in widgetlist: + def on_clear_setup(self): + if any(opt in ['STDOUT', 'FILESTDOUT'] for opt in srv_config[srv_options['lfile']['des']]): + if self.count_clear == 0: + self.ini = txsrv.index('end') + add_newline = False + else: + if self.count_clear == 1: + self.ini = '%s.0' %(int(self.ini[0]) - 1) + else: + self.ini = '%s.0' %(int(self.ini[0])) + add_newline = True + rng = [self.ini, 'end'] + self.count_clear += 1 + else: + rng, add_newline = None, False + self.count_clear = 0 + + return rng, add_newline + + def on_clear(self, widget_list, clear_range = None, newline_list = []): + if newline_list == []: + newline_list = len(widget_list) * [False] + + for num, couple in enumerate(zip(widget_list, newline_list)): + widget, add_n = couple + try: + ini, fin = clear_range[num] + except TypeError: + ini, fin = '1.0', 'end' + widget.configure(state = 'normal') - widget.delete('1.0', 'end') + widget.delete(ini, fin) + if add_n: + widget.insert('end', '\n') widget.configure(state = 'disabled') diff --git a/py-kms/pykms_GuiMisc.py b/py-kms/pykms_GuiMisc.py index b1c7914..fef341f 100644 --- a/py-kms/pykms_GuiMisc.py +++ b/py-kms/pykms_GuiMisc.py @@ -120,38 +120,22 @@ class ToolTip(object): self.tw = None ##----------------------------------------------------------------------------------------------------------------------------------------------------------- -# https://stackoverflow.com/questions/2914603/segmentation-fault-while-redirecting-sys-stdout-to-tkinter-text-widget -# https://stackoverflow.com/questions/7217715/threadsafe-printing-across-multiple-processes-python-2-x -# https://stackoverflow.com/questions/3029816/how-do-i-get-a-thread-safe-print-in-python-2-6 -# https://stackoverflow.com/questions/20303291/issue-with-redirecting-stdout-to-tkinter-text-widget-with-threads - -class TextRedirect(object): - class StdoutRedirect(object): - tag_num = 0 +class TextRedirect(object): + class Pretty(object): grpmsg = unformat_message([MsgMap[1], MsgMap[7], MsgMap[12], MsgMap[20]]) arrows = [ item[0] for item in grpmsg ] clt_msg_nonewline = [ item[1] for item in grpmsg ] arrows = list(set(arrows)) lenarrow = len(arrows[0]) srv_msg_nonewline = [ item[0] for item in unformat_message([MsgMap[2], MsgMap[5], MsgMap[13], MsgMap[18]]) ] - terminator = unformat_message([MsgMap[21]])[0][0] - msg_align = [ msg[0].replace('\t', '').replace('\n', '') for msg in unformat_message([MsgMap[-2], MsgMap[-4]])] - newlinecut = [-1, -2, -4, -5] + msg_align = [ msg[0].replace('\t', '').replace('\n', '') for msg in unformat_message([MsgMap[-2], MsgMap[-4]]) ] - def __init__(self, srv_text_space, clt_text_space, customcolors, str_to_print, where): + def __init__(self, srv_text_space, clt_text_space, customcolors): self.srv_text_space = srv_text_space self.clt_text_space = clt_text_space self.customcolors = customcolors - self.str_to_print = str_to_print - self.where = where - self.textbox_do() - def textbox_finish(self, message): - if message == self.terminator: - TextRedirect.StdoutRedirect.tag_num = 0 - TextRedirect.StdoutRedirect.newlinecut = [-1, -2, -4, -5] - def textbox_write(self, tag, message, color, extras): widget = self.textbox_choose(message) self.w_maxpix, self.h_maxpix = widget.winfo_width(), widget.winfo_height() @@ -161,16 +145,17 @@ class TextRedirect(object): self.textbox_color(tag, widget, color, self.customcolors['black'], extras) widget.after(100, widget.see('end')) widget.configure(state = 'disabled') - self.textbox_finish(message) - + def textbox_choose(self, message): - if self.where == "srv": + if any(item.startswith('logsrv') for item in [message, self.str_to_print]): self.srv_text_space.focus_set() + self.where = "srv" return self.srv_text_space - elif self.where == "clt": + elif any(item.startswith('logclt') for item in [message, self.str_to_print]): self.clt_text_space.focus_set() + self.where = "clt" return self.clt_text_space - + def textbox_color(self, tag, widget, forecolor = 'white', backcolor = 'black', extras = []): for extra in extras: if extra == 'bold': @@ -211,8 +196,8 @@ class TextRedirect(object): # horizontal align. if msg_unformat in self.msg_align: msg_strip = message.lstrip('\n') - message = '\n' * (len(message) - len(msg_strip) + TextRedirect.StdoutRedirect.newlinecut[0]) + msg_strip - TextRedirect.StdoutRedirect.newlinecut.pop(0) + message = '\n' * (len(message) - len(msg_strip) + TextRedirect.Pretty.newlinecut[0]) + msg_strip + TextRedirect.Pretty.newlinecut.pop(0) count = Counter(message) countab = (count['\t'] if count['\t'] != 0 else 1) @@ -220,24 +205,48 @@ class TextRedirect(object): return message def textbox_do(self): - msgs, TextRedirect.StdoutRedirect.tag_num = unshell_message(self.str_to_print, TextRedirect.StdoutRedirect.tag_num) + msgs, TextRedirect.Pretty.tag_num = unshell_message(self.str_to_print, TextRedirect.Pretty.tag_num) for tag in msgs: self.textbox_write(tag, msgs[tag]['text'], self.customcolors[msgs[tag]['color']], msgs[tag]['extra']) - - class StderrRedirect(StdoutRedirect): - def __init__(self, srv_text_space, clt_text_space, customcolors): + + def flush(self): + pass + + def write(self, string): + if string != '\n': + self.str_to_print = string + self.textbox_do() + + class Stderr(Pretty): + def __init__(self, srv_text_space, clt_text_space, customcolors, side): self.srv_text_space = srv_text_space self.clt_text_space = clt_text_space self.customcolors = customcolors + self.side = side self.tag_err = 'STDERR' self.xfont = tkFont.Font(font = self.srv_text_space['font']) + + def textbox_choose(self, message): + if self.side == "srv": + return self.srv_text_space + elif self.side == "clt": + return self.clt_text_space def write(self, string): - self.textbox_color(self.tag_err, self.srv_text_space, self.customcolors['red'], self.customcolors['black']) + widget = self.textbox_choose(string) + self.textbox_color(self.tag_err, widget, self.customcolors['red'], self.customcolors['black']) self.srv_text_space.configure(state = 'normal') self.srv_text_space.insert('end', string, self.tag_err) self.srv_text_space.see('end') self.srv_text_space.configure(state = 'disabled') + + class Log(Pretty): + def textbox_format(self, message): + if message.startswith('logsrv'): + message = message.replace('logsrv ', '') + if message.startswith('logclt'): + message = message.replace('logclt ', '') + return message + '\n' ##----------------------------------------------------------------------------------------------------------------------------------------------------------- class TextDoubleScroll(tk.Frame): @@ -328,7 +337,7 @@ def custom_background(window): widget.configure(background = window.customcolors['lavender']) # Hide client. - window.clt_on_show(force = True) + window.clt_on_show(force_remove = True) # Show Gui. window.deiconify() @@ -488,9 +497,14 @@ class ListboxOfRadiobuttons(tk.Frame): if st in ['STDOUT', 'FILEOFF']: if wclass == 'Entry': widget.delete(0, 'end') + widget.configure(state = "disabled") elif wclass == 'TCombobox': - widget.set('') - widget.configure(state = "disabled") + if st == 'STDOUT': + widget.set(default) + widget.configure(state = "readonly") + elif st == 'FILEOFF': + widget.set('') + widget.configure(state = "disabled") elif st in ['FILE', 'FILESTDOUT', 'STDOUTOFF']: if wclass == 'Entry': widget.configure(state = "normal") diff --git a/py-kms/pykms_Misc.py b/py-kms/pykms_Misc.py index 022d473..89bf84d 100644 --- a/py-kms/pykms_Misc.py +++ b/py-kms/pykms_Misc.py @@ -86,28 +86,104 @@ class LevelFormatter(logging.Formatter): formatter = self.formatters.get(record.levelno, self.default_fmt) return formatter.format(record) +# based on https://github.com/jruere/multiprocessing-logging (license LGPL-3.0) +from multiprocessing import Queue as MPQueue +try: + # Python 2.x imports + import Queue as Queue +except ImportError: + # Python 3.x imports + import queue as Queue +import threading + +class MultiProcessingLogHandler(logging.Handler): + def __init__(self, name, handler = None): + super(MultiProcessingLogHandler, self).__init__() + self.queue = MPQueue(-1) + if handler is None: + handler = logging.StreamHandler() + self.handler = handler + self.name = handler.name + + self.setLevel(self.handler.level) + self.setFormatter(self.handler.formatter) + self.filters = self.handler.filters + + self.is_closed = False + self.receive_thread = threading.Thread(target = self.receive, name = name) + self.receive_thread.daemon = True + self.receive_thread.start() + + def setFormatter(self, fmt): + super(MultiProcessingLogHandler, self).setFormatter(fmt) + self.handler.setFormatter(fmt) + + def emit(self, record): + try: + if record.args: + record.msg = record.msg %record.args + record.args = None + if record.exc_info: + dummy = self.format(record) + record.exc_info = None + self.queue.put_nowait(record) + except (KeyboardInterrupt, SystemExit): + raise + except: + self.handleError(record) + + def receive(self): + while not (self.is_closed and self.queue.empty()): + try: + record = self.queue.get(timeout = 0.2) + self.handler.emit(record) + except (KeyboardInterrupt, SystemExit): + raise + except EOFError: + break + except Queue.Empty: + pass + except: + logging.exception('Error in log handler.') + self.queue.close() + self.queue.join_thread() + + def close(self): + if not self.is_closed: + self.is_closed = True + self.receive_thread.join(5.0) + self.handler.close() + super(MultiProcessingLogHandler, self).close() + def logger_create(log_obj, config, mode = 'a'): # Create new level. add_logging_level('MINI', logging.CRITICAL + 10) + log_handlers = [] # Configure visualization. - log_handlers = [] if any(opt in ['STDOUT', 'FILESTDOUT', 'STDOUTOFF'] for opt in config['logfile']): - if 'STDOUTOFF' not in config['logfile']: - # STDOUT. - log_handlers.append(logging.StreamHandler(sys.stdout)) + if any(opt in ['STDOUT', 'FILESTDOUT'] for opt in config['logfile']): + # STDOUT or FILESTDOUT. + hand_stdout = logging.StreamHandler(sys.stdout) + hand_stdout.name = 'LogStdout' + log_handlers.append(hand_stdout) if any(opt in ['STDOUTOFF', 'FILESTDOUT'] for opt in config['logfile']): - # FILESTDOUT or STDOUTOFF. - log_handlers.append(RotatingFileHandler(filename = config['logfile'][1], mode = mode, maxBytes = int(config['logsize'] * 1024 * 512), - backupCount = 1, encoding = None, delay = 0)) + # STDOUTOFF or FILESTDOUT. + hand_rotate = RotatingFileHandler(filename = config['logfile'][1], mode = mode, maxBytes = int(config['logsize'] * 1024 * 512), + backupCount = 1, encoding = None, delay = 0) + hand_rotate.name = 'LogRotate' + log_handlers.append(hand_rotate) elif 'FILEOFF' in config['logfile']: - config['loglevel'] = 'ERROR' # for py-kms GUI: set a recognized level never used. - log_handlers.append(logging.FileHandler(os.devnull)) + hand_null = logging.FileHandler(os.devnull) + hand_null.name = 'LogNull' + log_handlers.append(hand_null) else: # FILE. - log_handlers.append(RotatingFileHandler(filename = config['logfile'][0], mode = mode, maxBytes = int(config['logsize'] * 1024 * 512), - backupCount = 1, encoding = None, delay = 0)) + hand_rotate = RotatingFileHandler(filename = config['logfile'][0], mode = mode, maxBytes = int(config['logsize'] * 1024 * 512), + backupCount = 1, encoding = None, delay = 0) + hand_rotate.name = 'LogRotate' + log_handlers.append(hand_rotate) # Configure formattation. try: @@ -115,27 +191,38 @@ def logger_create(log_obj, config, mode = 'a'): except AttributeError: levelnames = logging._levelNames levelnum = [k for k in levelnames if k != 0] - frmt0 = '%(asctime)s %(levelname)-8s %(message)s' - frmt1 = '[%(asctime)s] [%(levelname)-8s] %(host)s %(status)s %(product)s %(message)s' - levelformdict = {} - for num in levelnum: - if num != logging.CRITICAL + 10: - levelformdict[num] = frmt0 - else: - levelformdict[num] = frmt1 - # Set level and format. - levelformdictcopy = levelformdict.copy() + frmt_gen = '%(asctime)s %(levelname)-8s %(message)s' + frmt_std = '%(name)s %(asctime)s %(levelname)-8s %(message)s' + frmt_min = '[%(asctime)s] [%(levelname)-8s] %(host)s %(status)s %(product)s %(message)s' + + def apply_formatter(levelnum, formats, handler, color = False): + levelformdict = {} + for num in levelnum: + if num != logging.CRITICAL + 10: + levelformdict[num] = formats[0] + else: + levelformdict[num] = formats[1] + handler.setFormatter(LevelFormatter(levelformdict, color = color)) + return handler + + # Clear old handlers. + if log_obj.handlers: + log_obj.handlers = [] + for log_handler in log_handlers: log_handler.setLevel(config['loglevel']) - if log_handler.__class__.__name__ == 'StreamHandler': - log_handler.setFormatter(LevelFormatter(levelformdict, color = True)) - elif log_handler.__class__.__name__ == 'RotatingFileHandler': - log_handler.setFormatter(LevelFormatter(levelformdictcopy, color = False)) + if log_handler.name in ['LogStdout']: + log_handler = apply_formatter(levelnum, (frmt_std, frmt_min), log_handler, color = True) + elif log_handler.name in ['LogRotate']: + log_handler = apply_formatter(levelnum, (frmt_gen, frmt_min), log_handler) + # Attach. + if config['asyncmsg']: + log_obj.addHandler(MultiProcessingLogHandler('Thread-AsyncMsg{0}'.format(log_handler.name), handler = log_handler)) + else: + log_obj.addHandler(log_handler) - # Attach. log_obj.setLevel(config['loglevel']) - [ log_obj.addHandler(log_handler) for log_handler in log_handlers ] #------------------------------------------------------------------------------------------------------------------------------------------------------------ @@ -144,14 +231,16 @@ def check_logfile(optionlog, defaultlog, where): optionlog = [optionlog] lenopt = len(optionlog) - msg_dir = "{reverse}{red}{bold}argument logfile: invalid directory: '%s'. Exiting...{end}" - msg_long = "{reverse}{red}{bold}argument logfile: too much arguments. Exiting...{end}" - msg_log = "{reverse}{red}{bold}argument logfile: not a log file, invalid extension: '%s'. Exiting...{end}" + msg_dir = "{reverse}{red}{bold}argument `-F/--logfile`: invalid directory: '%s'. Exiting...{end}" + msg_long = "{reverse}{red}{bold}argument `-F/--logfile`: too much arguments. Exiting...{end}" + msg_log = "{reverse}{red}{bold}argument `-F/--logfile`: not a log file, invalid extension: '%s'. Exiting...{end}" def checkdir(path): filename = os.path.basename(path) pathname = os.path.dirname(path) if not os.path.isdir(pathname): + if path.count('/') == 0: + pathname = filename pretty_printer(put_text = msg_dir %pathname, where = where, to_exit = True) elif not filename.lower().endswith('.log'): pretty_printer(put_text = msg_log %filename, where = where, to_exit = True) @@ -159,7 +248,6 @@ def check_logfile(optionlog, defaultlog, where): if lenopt > 2: pretty_printer(put_text = msg_long, where = where, to_exit = True) - if (any(opt in ['FILESTDOUT', 'STDOUTOFF'] for opt in optionlog)): if lenopt == 1: # add default logfile. @@ -215,8 +303,7 @@ def check_lcid(lcid, log_obj): fixlcid = next(k for k, v in locale.windows_locale.items() if v == locale.getdefaultlocale()[0]) except StopIteration: fixlcid = 1033 - pretty_printer(log_obj = log_obj, - put_text = "{reverse}{yellow}{bold}LCID %s auto-fixed with LCID %s{end}" %(lcid, fixlcid)) + pretty_printer(log_obj = log_obj, put_text = "{reverse}{yellow}{bold}LCID '%s' auto-fixed with LCID '%s'{end}" %(lcid, fixlcid)) return fixlcid return lcid @@ -262,28 +349,123 @@ class KmsParserHelp(object): print(parser_base.epilog + '\n') parser_base.exit() +def kms_parser_get(parser): + zeroarg, onearg = ([] for _ in range(2)) + act = vars(parser)['_actions'] + for i in range(len(act)): + if act[i].option_strings not in ([], ['-h', '--help']): + if isinstance(act[i], argparse._StoreAction): + onearg.append(act[i].option_strings) + else: + zeroarg.append(act[i].option_strings) + return zeroarg, onearg + +def kms_parser_check_optionals(userarg, zeroarg, onearg, msg = 'optional py-kms server', exclude_opt_len = []): + """ + For optionals arguments: + Don't allow duplicates, + Don't allow abbreviations, + Don't allow joining and not existing arguments, + Checks length values passed to arguments. + """ + zeroarg = [item for sublist in zeroarg for item in sublist] + onearg = [item for sublist in onearg for item in sublist] + allarg = zeroarg + onearg + + def is_abbrev(allarg, arg_to_check): + for opt in allarg: + if len(opt) > 2 and opt[2] == arg_to_check[2]: + for indx in range(-1, -len(opt), -1): + if opt[:indx] == arg_to_check: + raise KmsParserException("%s argument `%s` abbreviation not allowed for `%s`" %(msg, arg_to_check, opt)) + return False + + # Check abbreviations, joining, not existing. + for arg in userarg: + if arg not in allarg: + if arg.startswith('-'): + if arg == '--' or arg[:2] != '--' or not is_abbrev(allarg, arg): + raise KmsParserException("unrecognized %s arguments: `%s`" %(msg, arg)) + + # Check duplicates. + founds = [i for i in userarg if i in allarg] + dup = [item for item in set(founds) if founds.count(item) > 1] + if dup != []: + raise KmsParserException("%s argument `%s` appears several times" %(msg, ', '.join(dup))) + + # Check length. + elem = None + for found in founds: + if found not in exclude_opt_len: + pos = userarg.index(found) + try: + if found in zeroarg: + elem = userarg[pos + 1] + num = "zero arguments," + elif found in onearg: + elem = userarg[pos + 2] + num = "one argument," + except IndexError: + pass + + if elem and elem not in allarg: + raise KmsParserException("%s argument `" %msg + found + "`:" + " expected " + num + " unrecognized: '%s'" %elem) + +def kms_parser_check_positionals(config, parse_method, arguments = None, msg = 'positional py-kms server'): + try: + if arguments: + config.update(vars(parse_method(arguments))) + else: + config.update(vars(parse_method())) + except KmsParserException as e: + e = str(e) + if e.startswith('argument'): + raise + else: + raise KmsParserException("unrecognized %s arguments: '%s'" %(msg, e.split(': ')[1])) + #------------------------------------------------------------------------------------------------------------------------------------------------------------ def proper_none(dictionary): for key in dictionary.keys(): dictionary[key] = None if dictionary[key] == 'None' else dictionary[key] def check_setup(config, options, logger, where): + # 'None'--> None. + proper_none(config) + # Check logfile. config['logfile'] = check_logfile(config['logfile'], options['lfile']['def'], where = where) - # Setup hidden or not messages. + # Check logsize (py-kms Gui). + if config['logsize'] == "": + if any(opt in ['STDOUT', 'FILEOFF'] for opt in config['logfile']): + # set a recognized size never used. + config['logsize'] = 0 + else: + pretty_printer(put_text = "{reverse}{red}{bold}argument `-S/--logsize`: invalid with: '%s'. Exiting...{end}" %config['logsize'], + where = where, to_exit = True) + + # Check loglevel (py-kms Gui). + if config['loglevel'] == "": + # set a recognized level never used. + config['loglevel'] = 'ERROR' + + # Setup hidden / asynchronous messages. hidden = ['STDOUT', 'FILESTDOUT', 'STDOUTOFF'] - ShellMessage.view = (False if any(opt in hidden for opt in config['logfile']) else True) + view_flag = (False if any(opt in hidden for opt in config['logfile']) else True) + if where == 'srv': + ShellMessage.viewsrv = view_flag + ShellMessage.asyncmsgsrv = config['asyncmsg'] + elif where == 'clt': + ShellMessage.viewclt = view_flag + ShellMessage.asyncmsgclt = config['asyncmsg'] # Create log. logger_create(logger, config, mode = 'a') - # 'None'--> None. - proper_none(config) - # Check port. - if not 1 <= config['port'] <= 65535: - pretty_printer(log_obj = logger.error, to_exit = True, + if (config['port'] == "") or (not 1 <= config['port'] <= 65535): + pretty_printer(log_obj = logger.error, where = where, to_exit = True, put_text = "{reverse}{red}{bold}Port number '%s' is invalid. Enter between 1 - 65535. Exiting...{end}" %config['port']) #------------------------------------------------------------------------------------------------------------------------------------------------------------ diff --git a/py-kms/pykms_Server.py b/py-kms/pykms_Server.py index 9b5eb06..170dbfd 100755 --- a/py-kms/pykms_Server.py +++ b/py-kms/pykms_Server.py @@ -29,6 +29,7 @@ from pykms_RpcBase import rpcBase from pykms_Dcerpc import MSRPCHeader from pykms_Misc import check_setup, check_lcid from pykms_Misc import KmsParser, KmsParserException, KmsParserHelp +from pykms_Misc import kms_parser_get, kms_parser_check_optionals, kms_parser_check_positionals from pykms_Format import enco, deco, pretty_printer from Etrigan import Etrigan, Etrigan_parser, Etrigan_check, Etrigan_job @@ -148,12 +149,15 @@ class server_thread(threading.Thread): # Create and run server. self.server = server_create() self.server.pykms_serve() - except SystemExit as e: + except (SystemExit, Exception) as e: self.eject = True if not self.with_gui: raise else: - continue + if isinstance(e, SystemExit): + continue + else: + raise ##--------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -168,17 +172,19 @@ srv_options = { 'lcid' : {'help' : 'Use this option to manually specify an LCID for use with randomly generated ePIDs. Default is \"1033\" (en-us)', 'def' : 1033, 'des' : "lcid"}, 'count' : {'help' : 'Use this option to specify the current client count. A number >=25 is required to enable activation of client OSes; \ -for server OSes and Office >=5', 'def' : None, 'des' : "CurrentClientCount"}, +for server OSes and Office >=5', 'def' : None, 'des' : "clientcount"}, 'activation' : {'help' : 'Use this option to specify the activation interval (in minutes). Default is \"120\" minutes (2 hours).', - 'def' : 120, 'des': "VLActivationInterval"}, + 'def' : 120, 'des': "activation"}, 'renewal' : {'help' : 'Use this option to specify the renewal interval (in minutes). Default is \"10080\" minutes (7 days).', - 'def' : 1440 * 7, 'des' : "VLRenewalInterval"}, + 'def' : 1440 * 7, 'des' : "renewal"}, 'sql' : {'help' : 'Use this option to store request information from unique clients in an SQLite database. Desactivated by default.', 'def' : False, 'des' : "sqlite"}, 'hwid' : {'help' : 'Use this option to specify a HWID. The HWID must be an 16-character string of hex characters. \ The default is \"364F463A8863D35F\" or type \"RANDOM\" to auto generate the HWID.', 'def' : "364F463A8863D35F", 'des' : "hwid"}, 'time0' : {'help' : 'Maximum inactivity time (in seconds) after which the connection with the client is closed. If \"None\" (default) serve forever.', - 'def' : None, 'des' : "timeout_idle"}, + 'def' : None, 'des' : "timeoutidle"}, + 'asyncmsg' : {'help' : 'Prints pretty / logging messages asynchronously. Desactivated by default.', + 'def' : False, 'des' : "asyncmsg"}, 'llevel' : {'help' : 'Use this option to set a log level. The default is \"ERROR\".', 'def' : "ERROR", 'des' : "loglevel", 'choi' : ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "MINI"]}, 'lfile' : {'help' : 'Use this option to set an output log file. The default is \"pykms_logserver.log\". \ @@ -189,11 +195,7 @@ Use \"STDOUTOFF\" to disable stdout messages. Use \"FILEOFF\" if you not want to } def server_options(): - try: - server_parser = KmsParser(description = srv_description, epilog = 'version: ' + srv_version, add_help = False, allow_abbrev = False) - except TypeError: - server_parser = KmsParser(description = srv_description, epilog = 'version: ' + srv_version, add_help = False) - + server_parser = KmsParser(description = srv_description, epilog = 'version: ' + srv_version, add_help = False) server_parser.add_argument("ip", nargs = "?", action = "store", default = srv_options['ip']['def'], help = srv_options['ip']['help'], type = str) server_parser.add_argument("port", nargs = "?", action = "store", default = srv_options['port']['def'], help = srv_options['port']['help'], type = int) server_parser.add_argument("-e", "--epid", action = "store", dest = srv_options['epid']['des'], default = srv_options['epid']['def'], @@ -204,85 +206,114 @@ def server_options(): help = srv_options['count']['help'], type = str) server_parser.add_argument("-a", "--activation-interval", action = "store", dest = srv_options['activation']['des'], default = srv_options['activation']['def'], help = srv_options['activation']['help'], type = int) - server_parser.add_argument("-r", "--renewal-interval", action = "store", dest = srv_options['renewal']['des'], default = srv_options['renewal']['def'], - help = srv_options['renewal']['help'], type = int) - server_parser.add_argument("-s", "--sqlite", action = "store_const", dest = srv_options['sql']['des'], const = True, default = srv_options['sql']['def'], - help = srv_options['sql']['help']) + server_parser.add_argument("-r", "--renewal-interval", action = "store", dest = srv_options['renewal']['des'], + default = srv_options['renewal']['def'], help = srv_options['renewal']['help'], type = int) + server_parser.add_argument("-s", "--sqlite", action = "store_true", dest = srv_options['sql']['des'], + default = srv_options['sql']['def'], help = srv_options['sql']['help']) server_parser.add_argument("-w", "--hwid", action = "store", dest = srv_options['hwid']['des'], default = srv_options['hwid']['def'], help = srv_options['hwid']['help'], type = str) server_parser.add_argument("-t0", "--timeout-idle", action = "store", dest = srv_options['time0']['des'], default = srv_options['time0']['def'], help = srv_options['time0']['help'], type = str) + server_parser.add_argument("-y", "--async-msg", action = "store_true", dest = srv_options['asyncmsg']['des'], + default = srv_options['asyncmsg']['def'], help = srv_options['asyncmsg']['help']) server_parser.add_argument("-V", "--loglevel", action = "store", dest = srv_options['llevel']['des'], choices = srv_options['llevel']['choi'], default = srv_options['llevel']['def'], help = srv_options['llevel']['help'], type = str) - server_parser.add_argument("-F", "--logfile", nargs = "+", action = "store", dest = srv_options['lfile']['des'], default = srv_options['lfile']['def'], - help = srv_options['lfile']['help'], type = str) + server_parser.add_argument("-F", "--logfile", nargs = "+", action = "store", dest = srv_options['lfile']['des'], + default = srv_options['lfile']['def'], help = srv_options['lfile']['help'], type = str) server_parser.add_argument("-S", "--logsize", action = "store", dest = srv_options['lsize']['des'], default = srv_options['lsize']['def'], help = srv_options['lsize']['help'], type = float) + server_parser.add_argument("-h", "--help", action = "help", help = "show this help message and exit") - try: - daemon_parser = KmsParser(description = "daemon options inherited from Etrigan", add_help = False, allow_abbrev = False) - except TypeError: - daemon_parser = KmsParser(description = "daemon options inherited from Etrigan", add_help = False) - + daemon_parser = KmsParser(description = "daemon options inherited from Etrigan", add_help = False) daemon_subparser = daemon_parser.add_subparsers(dest = "mode") - try: - etrigan_parser = daemon_subparser.add_parser("etrigan", add_help = False, allow_abbrev = False) - except TypeError: - etrigan_parser = daemon_subparser.add_parser("etrigan", add_help = False) + + etrigan_parser = daemon_subparser.add_parser("etrigan", add_help = False) etrigan_parser.add_argument("-g", "--gui", action = "store_const", dest = 'gui', const = True, default = False, help = "Enable py-kms GUI usage.") etrigan_parser = Etrigan_parser(parser = etrigan_parser) try: - if "-h" in sys.argv[1:]: + userarg = sys.argv[1:] + + # Run help. + if any(arg in ["-h", "--help"] for arg in userarg): KmsParserHelp().printer(parsers = [server_parser, daemon_parser, etrigan_parser]) - # Set defaults for config. - # case: python3 pykms_Server.py - srv_config.update(vars(server_parser.parse_args([]))) - # Eventually set daemon values for config. - if 'etrigan' in sys.argv[1:]: - if 'etrigan' == sys.argv[1]: - # case: python3 pykms_Server.py etrigan start --daemon_optionals - srv_config.update(vars(daemon_parser.parse_args(sys.argv[1:]))) - elif 'etrigan' == sys.argv[2]: - # case: python3 pykms_Server.py 1.2.3.4 etrigan start --daemon_optionals - srv_config['ip'] = sys.argv[1] - srv_config.update(vars(daemon_parser.parse_args(sys.argv[2:]))) - else: - # case: python3 pykms_Server.py 1.2.3.4 1234 --main_optionals etrigan start --daemon_optionals - knw_args, knw_extras = server_parser.parse_known_args() - # fix for logfile option (at the end) that catchs etrigan parser options. - if 'etrigan' in knw_args.logfile: - indx = knw_args.logfile.index('etrigan') - for num, elem in enumerate(knw_args.logfile[indx:]): - knw_extras.insert(num, elem) - knw_args.logfile = knw_args.logfile[:indx] + # Get stored arguments. + pykmssrv_zeroarg, pykmssrv_onearg = kms_parser_get(server_parser) + etrigan_zeroarg, etrigan_onearg = kms_parser_get(etrigan_parser) + pykmssrv_zeroarg += ['etrigan'] # add subparser - # continue parsing. - if len(knw_extras) > 0 and knw_extras[0] in ['etrigan']: - daemon_parser.parse_args(knw_extras, namespace = knw_args) - srv_config.update(vars(knw_args)) - else: - # Update dict config. - # case: python3 pykms_Server.py 1.2.3.4 1234 --main_optionals - knw_args, knw_extras = server_parser.parse_known_args() - if knw_extras != []: - raise KmsParserException("unrecognized arguments: %s" %' '.join(knw_extras)) - else: - srv_config.update(vars(knw_args)) + # Set defaults for config. + # example case: + # python3 pykms_Server.py + srv_config.update(vars(server_parser.parse_args([]))) + + try: + # Eventually set daemon options for dict server config. + pos = sys.argv[1:].index('etrigan') + # example cases: + # python3 pykms_Server.py etrigan start + # python3 pykms_Server.py etrigan start --daemon_optionals + # python3 pykms_Server.py 1.2.3.4 etrigan start + # python3 pykms_Server.py 1.2.3.4 etrigan start --daemon_optionals + # python3 pykms_Server.py 1.2.3.4 1234 etrigan start + # python3 pykms_Server.py 1.2.3.4 1234 etrigan start --daemon_optionals + # python3 pykms_Server.py --pykms_optionals etrigan start + # python3 pykms_Server.py --pykms_optionals etrigan start --daemon_optionals + # python3 pykms_Server.py 1.2.3.4 --pykms_optionals etrigan start + # python3 pykms_Server.py 1.2.3.4 --pykms_optionals etrigan start --daemon_optionals + # python3 pykms_Server.py 1.2.3.4 1234 --pykms_optionals etrigan start + # python3 pykms_Server.py 1.2.3.4 1234 --pykms_optionals etrigan start --daemon_optionals + + kms_parser_check_optionals(userarg[0:pos], pykmssrv_zeroarg, pykmssrv_onearg, exclude_opt_len = ['-F', '--logfile']) + kms_parser_check_positionals(srv_config, server_parser.parse_args, arguments = userarg[0:pos]) + kms_parser_check_optionals(userarg[pos:], etrigan_zeroarg, etrigan_onearg, msg = 'optional etrigan') + kms_parser_check_positionals(srv_config, daemon_parser.parse_args, arguments = userarg[pos:], msg = 'positional etrigan') + + except ValueError: + # Update pykms options for dict server config. + # example cases: + # python3 pykms_Server.py 1.2.3.4 + # python3 pykms_Server.py 1.2.3.4 --pykms_optionals + # python3 pykms_Server.py 1.2.3.4 1234 + # python3 pykms_Server.py 1.2.3.4 1234 --pykms_optionals + # python3 pykms_Server.py --pykms_optionals + + kms_parser_check_optionals(userarg, pykmssrv_zeroarg, pykmssrv_onearg, exclude_opt_len = ['-F', '--logfile']) + kms_parser_check_positionals(srv_config, server_parser.parse_args) except KmsParserException as e: pretty_printer(put_text = "{reverse}{red}{bold}%s. Exiting...{end}" %str(e), to_exit = True) +class Etrigan_Check(Etrigan_check): + def emit_opt_err(self, msg): + pretty_printer(put_text = "{reverse}{red}{bold}%s{end}" %msg, to_exit = True) + +class Etrigan(Etrigan): + def emit_message(self, message, to_exit = False): + if not self.mute: + pretty_printer(put_text = "{reverse}{green}{bold}%s{end}" %message) + if to_exit: + sys.exit(0) + + def emit_error(self, message, to_exit = True): + if not self.mute: + pretty_printer(put_text = "{reverse}{red}{bold}%s{end}" %message, to_exit = True) + def server_daemon(): if 'etrigan' in srv_config.values(): path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'pykms_config.pickle') if srv_config['operation'] in ['stop', 'restart', 'status'] and len(sys.argv[1:]) > 2: - pretty_printer(put_text = "{reverse}{red}{bold}too much arguments. Exiting...{end}", to_exit = True) + pretty_printer(put_text = "{reverse}{red}{bold}too much arguments with etrigan '%s'. Exiting...{end}" %srv_config['operation'], + to_exit = True) + + # Check file arguments. + Etrigan_Check().checkfile(srv_config['etriganpid'], '--etrigan-pid', '.pid') + Etrigan_Check().checkfile(srv_config['etriganlog'], '--etrigan-log', '.log') if srv_config['gui']: pass @@ -357,21 +388,30 @@ def server_check(): else: srv_config['dbSupport'] = True - # Check client count, timeout. - list_dest = ['CurrentClientCount', 'timeout_idle'] - list_opt = ['--client-count', '--timeout-idle'] + + # Check other specific server options. + list_dest = ['clientcount', 'timeoutidle'] + list_opt = ['-c/--client-count', '-t0/--timeout-idle'] + + if serverthread.with_gui: + list_dest += ['activation', 'renewal'] + list_opt += ['-a/--activation-interval', '-r/--renewal-interval'] + for dest, opt in zip(list_dest, list_opt): - if srv_config[dest] is not None: - if not srv_config[dest].isdigit(): - pretty_printer(log_obj = loggersrv.error, to_exit = True, - put_text = "{reverse}{red}{bold}Option %s is invalid with '%s'. Exiting...{end}" - %(opt, srv_config[dest])) - else: - srv_config[dest] = int(srv_config[dest]) + value = srv_config[dest] + if (value is not None) and (not isinstance(value, int)): + pretty_printer(log_obj = loggersrv.error, to_exit = True, + put_text = "{reverse}{red}{bold}argument `%s`: invalid with: '%s'. Exiting...{end}" %(opt, value)) def server_create(): - server = KeyServer((srv_config['ip'], srv_config['port']), kmsServerHandler) - server.timeout = srv_config['timeout_idle'] + try: + server = KeyServer((srv_config['ip'], srv_config['port']), kmsServerHandler) + except (socket.gaierror, socket.error) as e: + pretty_printer(log_obj = loggersrv.error, to_exit = True, + put_text = "{reverse}{red}{bold}Connection failed '%s:%d': %s. Exiting...{end}" %(srv_config['ip'], + srv_config['port'], + str(e))) + server.timeout = srv_config['timeoutidle'] loggersrv.info("TCP server listening at %s on port %d." % (srv_config['ip'], srv_config['port'])) loggersrv.info("HWID: %s" % deco(binascii.b2a_hex(srv_config['hwid']), 'utf-8').upper()) return server @@ -426,20 +466,8 @@ def server_main_terminal(): def server_with_gui(): import pykms_GuiBase - width = 950 - height = 660 - root = pykms_GuiBase.KmsGui() root.title(pykms_GuiBase.gui_description + ' (' + pykms_GuiBase.gui_version + ')') - # Main window initial position. - ## https://stackoverflow.com/questions/14910858/how-to-specify-where-a-tkinter-window-opens - ws = root.winfo_screenwidth() - hs = root.winfo_screenheight() - x = (ws / 2) - (width / 2) - y = (hs / 2) - (height / 2) - root.geometry('+%d+%d' %(x, y)) - # disable maximize button. - root.resizable(0, 0) root.mainloop() def server_main_no_terminal(): @@ -449,7 +477,7 @@ def server_main_no_terminal(): class kmsServerHandler(socketserver.BaseRequestHandler): def setup(self): - loggersrv.info("Connection accepted: %s:%d" % (self.client_address[0], self.client_address[1])) + loggersrv.info("Connection accepted: %s:%d" %(self.client_address[0], self.client_address[1])) def handle(self): while True: @@ -499,7 +527,7 @@ class kmsServerHandler(socketserver.BaseRequestHandler): def finish(self): self.request.close() - loggersrv.info("Connection closed: %s:%d" % (self.client_address[0], self.client_address[1])) + loggersrv.info("Connection closed: %s:%d" %(self.client_address[0], self.client_address[1])) serverqueue = Queue.Queue(maxsize = 0)