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
This commit is contained in:
Matteo ℱan 2020-05-04 23:51:46 +02:00
parent b4b57af092
commit 00793064bc
No known key found for this signature in database
GPG Key ID: 3C30A05BC133D9B6
8 changed files with 825 additions and 327 deletions

View File

@ -592,8 +592,8 @@ def main():
parser = Etrigan_parser() parser = Etrigan_parser()
args = vars(parser.parse_args()) args = vars(parser.parse_args())
# Check arguments. # Check arguments.
Etrigan_check().checkfile(args['etriganpid'], 'pidfile', '.pid') Etrigan_check().checkfile(args['etriganpid'], '--etrigan-pid', '.pid')
Etrigan_check().checkfile(args['etriganlog'], 'pidfile', '.log') Etrigan_check().checkfile(args['etriganlog'], '--etrigan-log', '.log')
# Setup daemon. # Setup daemon.
jasonblood_1 = Etrigan(pidfile = args['etriganpid'], logfile = args['etriganlog'], loglevel = args['etriganlev'], jasonblood_1 = Etrigan(pidfile = args['etriganpid'], logfile = args['etriganlog'], loglevel = args['etriganlev'],

View File

@ -140,21 +140,21 @@ class kmsBase:
# https://docs.microsoft.com/en-us/windows/deployment/volume-activation/activate-windows-10-clients-vamt # https://docs.microsoft.com/en-us/windows/deployment/volume-activation/activate-windows-10-clients-vamt
MinClients = kmsRequest['requiredClientCount'] MinClients = kmsRequest['requiredClientCount']
RequiredClients = MinClients * 2 RequiredClients = MinClients * 2
if self.srv_config["CurrentClientCount"] != None: if self.srv_config["clientcount"] != None:
if 0 < self.srv_config["CurrentClientCount"] < MinClients: if 0 < self.srv_config["clientcount"] < MinClients:
# fixed to 6 (product server) or 26 (product desktop) # fixed to 6 (product server) or 26 (product desktop)
currentClientCount = MinClients + 1 currentClientCount = MinClients + 1
pretty_printer(log_obj = loggersrv.warning, pretty_printer(log_obj = loggersrv.warning,
put_text = "{reverse}{yellow}{bold}Not enough clients ! Fixed with %s, but activated client \ put_text = "{reverse}{yellow}{bold}Not enough clients ! Fixed with %s, but activated client \
could be detected as not genuine !{end}" %currentClientCount) could be detected as not genuine !{end}" %currentClientCount)
elif MinClients <= self.srv_config["CurrentClientCount"] < RequiredClients: elif MinClients <= self.srv_config["clientcount"] < RequiredClients:
currentClientCount = self.srv_config["CurrentClientCount"] currentClientCount = self.srv_config["clientcount"]
pretty_printer(log_obj = loggersrv.warning, pretty_printer(log_obj = loggersrv.warning,
put_text = "{reverse}{yellow}{bold}With count = %s, activated client could be detected as not genuine !{end}" %currentClientCount) 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) # fixed to 10 (product server) or 50 (product desktop)
currentClientCount = RequiredClients currentClientCount = RequiredClients
if self.srv_config["CurrentClientCount"] > RequiredClients: if self.srv_config["clientcount"] > RequiredClients:
pretty_printer(log_obj = loggersrv.warning, pretty_printer(log_obj = loggersrv.warning,
put_text = "{reverse}{yellow}{bold}Too many clients ! Fixed with %s{end}" %currentClientCount) put_text = "{reverse}{yellow}{bold}Too many clients ! Fixed with %s{end}" %currentClientCount)
else: else:
@ -230,8 +230,8 @@ could be detected as not genuine !{end}" %currentClientCount)
# rule: timeserver - 4h <= timeclient <= timeserver + 4h, check if is satisfied. # rule: timeserver - 4h <= timeclient <= timeserver + 4h, check if is satisfied.
response['responseTime'] = kmsRequest['requestTime'] response['responseTime'] = kmsRequest['requestTime']
response['currentClientCount'] = currentClientCount response['currentClientCount'] = currentClientCount
response['vLActivationInterval'] = self.srv_config["VLActivationInterval"] response['vLActivationInterval'] = self.srv_config["activation"]
response['vLRenewalInterval'] = self.srv_config["VLRenewalInterval"] response['vLRenewalInterval'] = self.srv_config["renewal"]
if self.srv_config['sqlite'] and self.srv_config['dbSupport']: if self.srv_config['sqlite'] and self.srv_config['dbSupport']:
response = sql_update_epid(self.dbName, kmsRequest, response) response = sql_update_epid(self.dbName, kmsRequest, response)

View File

@ -24,6 +24,7 @@ from pykms_RpcBase import rpcBase
from pykms_DB2Dict import kmsDB2Dict from pykms_DB2Dict import kmsDB2Dict
from pykms_Misc import check_setup from pykms_Misc import check_setup
from pykms_Misc import KmsParser, KmsParserException, KmsParserHelp 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 from pykms_Format import justify, byterize, enco, deco, pretty_printer
clt_version = "py-kms_2020-02-02" clt_version = "py-kms_2020-02-02"
@ -56,8 +57,10 @@ clt_options = {
'choi' : ["WindowsVista","Windows7","Windows8","Windows8.1","Windows10","Office2010","Office2013","Office2016","Office2019"]}, '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.', '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"}, '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 \ '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' : "machineName"}, 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", '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"]}, '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 \ 'lfile' : {'help' : 'Use this option to set an output log file. The default is \"pykms_logclient.log\". Type \"STDOUT\" to view \
@ -67,9 +70,6 @@ log info on stdout. Type \"FILESTDOUT\" to combine previous actions.',
} }
def client_options(): 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'], client_parser.add_argument("ip", nargs = "?", action = "store", default = clt_options['ip']['def'],
help = clt_options['ip']['help'], type = str) help = clt_options['ip']['help'], type = str)
@ -81,6 +81,8 @@ def client_options():
help = clt_options['cmid']['help'], type = str) help = clt_options['cmid']['help'], type = str)
client_parser.add_argument("-n", "--name", dest = clt_options['name']['des'] , default = clt_options['name']['def'], client_parser.add_argument("-n", "--name", dest = clt_options['name']['des'] , default = clt_options['name']['def'],
help = clt_options['name']['help'], type = str) 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", client_parser.add_argument("-V", "--loglevel", dest = clt_options['llevel']['des'], action = "store",
choices = clt_options['llevel']['choi'], default = clt_options['llevel']['def'], choices = clt_options['llevel']['choi'], default = clt_options['llevel']['def'],
help = clt_options['llevel']['help'], type = str) 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) 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", 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) 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") client_parser.add_argument("-h", "--help", action = "help", help = "show this help message and exit")
try: 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]) 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: 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(): def client_check():
# Setup and some checks. # Setup and some checks.
@ -107,18 +120,22 @@ def client_check():
uuid.UUID(clt_config['cmid']) uuid.UUID(clt_config['cmid'])
except ValueError: except ValueError:
pretty_printer(log_obj = loggerclt.error, to_exit = True, where = "clt", pretty_printer(log_obj = loggerclt.error, to_exit = True, where = "clt",
put_text = "{reverse}{red}{bold}Bad CMID. Exiting...{end}") put_text = "{reverse}{red}{bold}argument `-c/--cmid`: invalid with: '%s'. Exiting...{end}" %clt_config['cmid'])
# Check machineName.
if clt_config['machineName'] is not None: # Check machine name.
if clt_config['machine'] is not None:
try: 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: except UnicodeEncodeError:
pretty_printer(log_obj = loggerclt.error, to_exit = True, where = "clt", pretty_printer(log_obj = loggerclt.error, to_exit = True, where = "clt",
put_text = "{reverse}{red}{bold}Bad machineName. Exiting...{end}") put_text = "{reverse}{red}{bold}argument `-n/--name`: invalid with: '%s'. Exiting...{end}" %clt_config['machine'])
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}")
clt_config['call_id'] = 1 clt_config['call_id'] = 1
@ -149,8 +166,14 @@ def client_update():
def client_create(): def client_create():
loggerclt.info("Connecting to %s on port %d..." % (clt_config['ip'], clt_config['port'])) loggerclt.info("Connecting to %s on port %d..." % (clt_config['ip'], clt_config['port']))
try:
s = socket.create_connection((clt_config['ip'], clt_config['port'])) s = socket.create_connection((clt_config['ip'], clt_config['port']))
loggerclt.info("Connection successful !") 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) binder = pykms_RpcBind.handler(None, clt_config)
RPC_Bind = enco(str(binder.generateRequest()), 'latin-1') 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['previousClientMachineId'] = '\0' * 16 # I'm pretty sure this is supposed to be a null UUID.
requestDict['requiredClientCount'] = clt_config['RequiredClientCount'] requestDict['requiredClientCount'] = clt_config['RequiredClientCount']
requestDict['requestTime'] = dt_to_filetime(datetime.datetime.utcnow()) 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') ''.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'))) requestDict['mnPad'] = '\0'.encode('utf-16le') * (63 - len(requestDict['machineName'].decode('utf-16le')))

View File

@ -5,6 +5,7 @@ import re
import sys import sys
import os import os
from collections import OrderedDict from collections import OrderedDict
import logging
try: try:
# Python 2.x imports # Python 2.x imports
@ -16,6 +17,7 @@ except ImportError:
import queue as Queue import queue as Queue
pyver = sys.version_info[:2] pyver = sys.version_info[:2]
#---------------------------------------------------------------------------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------------------------------------------------------------------------
def enco(strg, typ = 'latin-1'): def enco(strg, typ = 'latin-1'):
@ -195,13 +197,15 @@ if pyver < (3, 3):
file = kwargs.get('file', sys.stdout) file = kwargs.get('file', sys.stdout)
file.flush() if file is not None else sys.stdout.flush() 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, # based on: https://ryanjoneil.github.io/posts/2014-02-14-capturing-stdout-in-a-python-child-process.html
# but not using threading/multiprocessing so: queue_print = Queue.Queue()
# 1) message visualization order preserved.
# 2) newlines_count function output not wrong.
class ShellMessage(object): class ShellMessage(object):
view = True viewsrv, viewclt = (True for _ in range(2))
count, remain, numlist = (0, 0, []) 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): class Collect(StringIO):
# Capture string sent to stdout. # Capture string sent to stdout.
@ -215,8 +219,9 @@ class ShellMessage(object):
self.put_text = put_text self.put_text = put_text
self.where = where self.where = where
self.plaintext = [] self.plaintext = []
self.path = os.path.dirname(os.path.abspath( __file__ )) + '/pykms_newlines.txt' self.path_nl = os.path.dirname(os.path.abspath( __file__ )) + '/pykms_newlines.txt'
self.print_queue = Queue.Queue() self.path_clean_nl = os.path.dirname(os.path.abspath( __file__ )) + '/pykms_clean_newlines.txt'
self.queue_get = Queue.Queue()
def formatter(self, msgtofrmt): def formatter(self, msgtofrmt):
if self.newlines: if self.newlines:
@ -236,14 +241,14 @@ class ShellMessage(object):
def newlines_file(self, mode, *args): def newlines_file(self, mode, *args):
try: try:
with open(self.path, mode) as file: with open(self.path_nl, mode) as file:
if mode in ['w', 'a']: if mode in ['w', 'a']:
file.write(args[0]) file.write(args[0])
elif mode == 'r': elif mode == 'r':
data = [int(i) for i in [line.rstrip('\n') for line in file.readlines()]] data = [int(i) for i in [line.rstrip('\n') for line in file.readlines()]]
self.newlines, ShellMessage.remain = data[0], sum(data[1:]) self.newlines, ShellMessage.remain = data[0], sum(data[1:])
except: except:
with open(self.path, 'w') as file: with open(self.path_nl, 'w') as file:
pass pass
def newlines_count(self, num): def newlines_count(self, num):
@ -265,13 +270,67 @@ class ShellMessage(object):
self.continuecount = True self.continuecount = True
elif num in [-2 ,-4]: elif num in [-2 ,-4]:
self.newlines_file('r') self.newlines_file('r')
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: if num == 21:
ShellMessage.count, ShellMessage.remain, ShellMessage.numlist = (0, 0, []) ShellMessage.count, ShellMessage.remain, ShellMessage.numlist = (0, 0, [])
os.remove(self.path) 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 run(self): def putter(self, aqueue, toput):
# view = False part. try:
if not ShellMessage.view: 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: if self.get_text:
self.newlines = 0 self.newlines = 0
if self.put_text is not None: if self.put_text is not None:
@ -280,25 +339,45 @@ class ShellMessage(object):
else: else:
for num in self.nshell: for num in self.nshell:
self.formatter(MsgMap[num]) self.formatter(MsgMap[num])
return self.plaintext self.putter(self.queue_get, self.plaintext)
else:
def manage(self):
if not ShellMessage.viewsrv:
# viewsrv = False, viewclt = True.
if ShellMessage.viewclt:
if self.where == 'srv':
self.notview()
return 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. # Do job.
self.produce() self.produce()
toprint = self.consume(timeout = 0.1) toprint = self.consume(queue_print, timeout = 0.1)
# Redirect output.
if sys.stdout.isatty(): if sys.stdout.isatty():
print(toprint) print(toprint, flush = True)
else: else:
try: try:
# Import after variables creation. self.print_logging(toprint)
from pykms_GuiBase import gui_redirect
gui_redirect(toprint, self.where)
except: except:
print(toprint) print(toprint, flush = True)
# Get string/s printed. # Get string/s printed.
if self.get_text: if self.get_text:
return self.plaintext self.putter(self.queue_get, self.plaintext)
return
def produce(self): def produce(self):
# Save everything that would otherwise go to stdout. # Save everything that would otherwise go to stdout.
@ -327,15 +406,12 @@ class ShellMessage(object):
finally: finally:
# Restore stdout and send content. # Restore stdout and send content.
sys.stdout = sys.__stdout__ sys.stdout = sys.__stdout__
try: self.putter(queue_print, outstream.getvalue())
self.print_queue.put(outstream.getvalue())
except Queue.Full:
pass
def consume(self, timeout = None): def consume(self, aqueue, timeout = None):
try: try:
toprint = self.print_queue.get(block = timeout is not None, timeout = timeout) toprint = aqueue.get(block = timeout is not None, timeout = timeout)
self.print_queue.task_done() aqueue.task_done()
return toprint return toprint
except Queue.Empty: except Queue.Empty:
return None return None
@ -381,11 +457,13 @@ def pretty_printer(**kwargs):
options['get_text'] = False options['get_text'] = False
# Process messages. # Process messages.
plain_messages = ShellMessage.Process(options['num_text'], shmsg = ShellMessage.Process(options['num_text'],
get_text = options['get_text'], get_text = options['get_text'],
put_text = options['put_text'], put_text = options['put_text'],
where = options['where']).run() where = options['where'])
shmsg.execute()
plain_messages = shmsg.consume(shmsg.queue_get, timeout = None)
if options['log_obj']: if options['log_obj']:
for plain_message in plain_messages: for plain_message in plain_messages:
options['log_obj'](plain_message) options['log_obj'](plain_message)

View File

@ -49,24 +49,35 @@ def get_ip_address():
ip = 'Unknown' ip = 'Unknown'
return ip return ip
def gui_redirect(str_to_print, where): def gui_redirector(stream, redirect_to = TextRedirect.Pretty, redirect_conditio = True, stderr_side = "srv"):
global txsrv, txclt, txcol 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: 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: except:
print(str_to_print) # self.onlysrv not defined (menu not used)
pass
##----------------------------------------------------------------------------------------------------------------------------------------------------------- ##-----------------------------------------------------------------------------------------------------------------------------------------------------------
class KmsGui(tk.Tk): 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']))
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs) tk.Tk.__init__(self, *args, **kwargs)
self.wraplength = 200 self.wraplength = 200
@ -97,18 +108,75 @@ class KmsGui(tk.Tk):
self.gui_create() 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): def gui_create(self):
## Create server gui ## Create server gui
self.gui_srv() self.gui_srv()
## Create client gui + other operations. ## Create client gui + other operations.
self.gui_complete() self.gui_complete()
## Create menu.
self.gui_menu()
## Create globals for printing process (redirect stdout). ## Create globals for printing process (redirect stdout).
global txsrv, txclt, txcol global txsrv, txclt, txcol
txsrv = self.textboxsrv.get() txsrv = self.textboxsrv.get()
txclt = self.textboxclt.get() txclt = self.textboxclt.get()
txcol = self.customcolors txcol = self.customcolors
## Redirect stderr. ## Redirect stderr.
sys.stderr = TextRedirect.StderrRedirect(txsrv, txclt, txcol) gui_redirector('stderr', redirect_to = TextRedirect.Stderr)
def gui_pages_show(self, pagename, side): def gui_pages_show(self, pagename, side):
# https://stackoverflow.com/questions/7546050/switch-between-two-frames-in-tkinter # https://stackoverflow.com/questions/7546050/switch-between-two-frames-in-tkinter
@ -162,7 +230,7 @@ class KmsGui(tk.Tk):
btnani = tk.Button(aniwin) btnani = tk.Button(aniwin)
btnani.grid(row = 0, column = col[2], padx = 2, pady = 2, sticky = stick) btnani.grid(row = 0, column = col[2], padx = 2, pady = 2, sticky = stick)
self.pagewidgets[side]["BtnAni"][position] = btnani self.pagewidgets[side]["BtnAni"][position] = btnani
# customize buttons. ## Customize buttons.
custom_pages(self, side) custom_pages(self, side)
def gui_pages_create(self, parent, side, create = {}): def gui_pages_create(self, parent, side, create = {}):
@ -209,11 +277,11 @@ class KmsGui(tk.Tk):
self.pagewidgets = {} self.pagewidgets = {}
## subpages of optsrvwin. ## Subpages of "optsrvwin".
self.gui_pages_create(parent = self.optsrvwin, side = "Srv", create = {"PageStart": None, self.gui_pages_create(parent = self.optsrvwin, side = "Srv", create = {"PageStart": None,
"PageEnd": 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(row = 1, column = 2, padx = 1, pady = 1, sticky = 'nsew')
self.msgsrvwin.grid_propagate(False) self.msgsrvwin.grid_propagate(False)
self.msgsrvwin.grid_columnconfigure(0, weight = 1) self.msgsrvwin.grid_columnconfigure(0, weight = 1)
@ -285,7 +353,7 @@ class KmsGui(tk.Tk):
self.activ.insert('end', str(srv_options['activation']['def'])) self.activ.insert('end', str(srv_options['activation']['def']))
ToolTip(self.activ, text = srv_options['activation']['help'], wraplength = self.wraplength) ToolTip(self.activ, text = srv_options['activation']['help'], wraplength = self.wraplength)
# Renewal Interval. # 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", self.renew = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.optfont, validate = "key",
validatecommand = self.validation_int) validatecommand = self.validation_int)
self.renew.insert('end', str(srv_options['renewal']['def'])) 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) ToolTip(self.srvfile, text = srv_options['lfile']['help'], wraplength = self.wraplength)
srvfilebtnwin = tk.Button(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Browse', srvfilebtnwin = tk.Button(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Browse',
command = lambda: self.browse(self.srvfile, srv_options)) command = lambda: self.browse(self.srvfile, srv_options))
# Loglevel. # Loglevel.
srvlevellbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Loglevel: ', font = self.optfont) 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']), self.srvlevel = ttk.Combobox(self.pagewidgets["Srv"]["PageWin"]["PageStart"], values = tuple(srv_options['llevel']['choi']),
width = 10, height = 10, font = self.optfontredux, state = "readonly") width = 10, height = 10, font = self.optfontredux, state = "readonly")
self.srvlevel.set(srv_options['llevel']['def']) self.srvlevel.set(srv_options['llevel']['def'])
ToolTip(self.srvlevel, text = srv_options['llevel']['help'], wraplength = self.wraplength) 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. # Logsize.
srvsizelbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageStart"], text = 'Logsize: ', font = self.optfont) 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", self.srvsize = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageStart"], width = 10, font = self.optfont, validate = "key",
validatecommand = self.validation_float) validatecommand = self.validation_float)
self.srvsize.insert('end', srv_options['lsize']['def']) self.srvsize.insert('end', srv_options['lsize']['def'])
ToolTip(self.srvsize, text = srv_options['lsize']['help'], wraplength = self.wraplength) 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) ## Layout widgets (optsrvwin:Srv:PageWin:PageStart)
ver.grid(row = 0, column = 0, columnspan = 3, padx = 5, pady = 5, sticky = 'ew') 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') 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') 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') 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') 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') 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') 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)------------------------------------------------------------------------------------------- ## Create widgets (optsrvwin:Srv:PageWin:PageEnd)-------------------------------------------------------------------------------------------
# Timeout connection. # Timeout connection.
timeout0lbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], text = 'Timeout connection: ', font = self.optfont) 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'])) self.timeout0.insert('end', str(srv_options['time0']['def']))
ToolTip(self.timeout0, text = srv_options['time0']['help'], wraplength = self.wraplength) ToolTip(self.timeout0, text = srv_options['time0']['help'], wraplength = self.wraplength)
# Sqlite database. # Sqlite database.
self.chkvalsql = tk.BooleanVar() self.chkvalsql = tk.BooleanVar()
self.chkvalsql.set(srv_options['sql']['def']) self.chkvalsql.set(srv_options['sql']['def'])
chksql = tk.Checkbutton(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], text = 'Create Sqlite\nDatabase', 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) ToolTip(chksql, text = srv_options['sql']['help'], wraplength = self.wraplength)
## Layout widgets (optsrvwin:Srv:PageWin:PageEnd) ## 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') 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') self.timeout0.grid(row = 1, column = 1, padx = 5, pady = 5, sticky = 'w')
chksql.grid(row = 2, column = 1, padx = 5, pady = 5, sticky = 'ew') 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 = self.gui_store(side = "Srv", typewidgets = ['Button', 'Entry', 'TCombobox', 'Checkbutton'])
self.storewidgets_srv.append(self.chksrvfile) self.storewidgets_srv.append(self.chksrvfile)
@ -377,28 +454,52 @@ class KmsGui(tk.Tk):
relief = 'ridge', font = self.msgfont) relief = 'ridge', font = self.msgfont)
self.textboxsrv.put() 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): def gui_complete(self):
## Create client widgets (optcltwin, msgcltwin, btncltwin) ## Create client widgets (optcltwin, msgcltwin, btncltwin)
self.update_idletasks() # update Gui to get btnsrvwin values --> btncltwin. self.update_idletasks() # update Gui to get btnsrvwin values --> btncltwin.
minw, minh = self.winfo_width(), self.winfo_height()
self.iconify() self.iconify()
self.gui_clt() self.gui_clt()
minw, minh = self.winfo_width(), self.winfo_height() maxw, minh = self.winfo_width(), self.winfo_height()
# Main window custom background. ## Main window custom background.
self.update_idletasks() # update Gui for custom background self.update_idletasks() # update Gui for custom background
self.iconify() self.iconify()
custom_background(self) 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.wm_attributes("-topmost", True)
self.protocol("WM_DELETE_WINDOW", lambda: 0) self.protocol("WM_DELETE_WINDOW", lambda: 0)
self.minsize(minw, minh) ## Disable maximize button.
self.resizable(True, False) self.resizable(False, False)
## Centered window.
self.always_centered(self.geometry(), False, [minw, maxw])
def get_position(self, genericwidget): def get_position(self, widget):
x, y = (genericwidget.winfo_x(), genericwidget.winfo_y()) x, y = (widget.winfo_x(), widget.winfo_y())
w, h = (genericwidget.winfo_width(), genericwidget.winfo_height()) w, h = (widget.winfo_width(), widget.winfo_height())
return x, y, w, h 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.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.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') 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_rowconfigure(0, weight = 1)
self.optcltwin.grid_columnconfigure(1, 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, self.gui_pages_create(parent = self.optcltwin, side = "Clt", create = {"PageStart": None,
"PageEnd": 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(row = 1, column = 4, padx = 1, pady = 1, sticky = 'nsew')
self.msgcltwin.grid_propagate(False) self.msgcltwin.grid_propagate(False)
self.msgcltwin.grid_columnconfigure(0, weight = 1) self.msgcltwin.grid_columnconfigure(0, weight = 1)
self.msgcltwin.grid_rowconfigure(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'], self.runbtnclt = tk.Button(self.btncltwin, text = 'START\nCLIENT', background = self.customcolors['blue'],
foreground = self.customcolors['white'], relief = 'flat', font = self.btnwinfont, foreground = self.customcolors['white'], relief = 'flat', font = self.btnwinfont,
state = 'disabled', command = self.clt_on_start) state = 'disabled', command = self.clt_on_start)
## self.othbutt = tk.Button(self.btncltwin, text = 'Botton\n2', background = self.customcolors['green'], ## Layout widgets (btncltwin)
## foreground = self.customcolors['white'], relief = 'flat', font = self.btnwinfont)
# Layout widgets (btncltwin)
self.runbtnclt.grid(row = 0, column = 0, padx = 2, pady = 2, sticky = 'ew') 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. # Version.
cltver = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'You are running client version: ' + clt_version, cltver = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'You are running client version: ' + clt_version,
foreground = self.customcolors['red'], font = self.othfont) foreground = self.customcolors['red'], font = self.othfont)
@ -479,21 +576,30 @@ class KmsGui(tk.Tk):
self.cltlevel.set(clt_options['llevel']['def']) self.cltlevel.set(clt_options['llevel']['def'])
ToolTip(self.cltlevel, text = clt_options['llevel']['help'], wraplength = self.wraplength) 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. # Logsize.
cltsizelbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageStart"], text = 'Logsize: ', font = self.optfont) 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", self.cltsize = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageStart"], width = 10, font = self.optfont, validate = "key",
validatecommand = self.validation_float) validatecommand = self.validation_float)
self.cltsize.insert('end', clt_options['lsize']['def']) self.cltsize.insert('end', clt_options['lsize']['def'])
ToolTip(self.cltsize, text = clt_options['lsize']['help'], wraplength = self.wraplength) 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)
# Layout widgets (optcltwin:Clt:PageWin:PageStart) # 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)
cltver.grid(row = 0, column = 0, columnspan = 3, padx = 5, pady = 5, sticky = 'ew') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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) ------------------------------------------------------------------------------------------- ## Create widgets (optcltwin:Clt:PageWin:PageEnd) -------------------------------------------------------------------------------------------
## Layout 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. ## Store client-side widgets.
self.storewidgets_clt = self.gui_store(side = "Clt", typewidgets = ['Button', 'Entry', 'TCombobox']) self.storewidgets_clt = self.gui_store(side = "Clt", typewidgets = ['Button', 'Entry', 'TCombobox', 'Checkbutton'])
self.storewidgets_clt.append(self.chkcltfile) 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', self.textboxclt = TextDoubleScroll(self.msgcltwin, background = self.customcolors['black'], wrap = 'none', state = 'disabled',
relief = 'ridge', font = self.msgfont) relief = 'ridge', font = self.msgfont)
self.textboxclt.put() self.textboxclt.put()
@ -540,22 +654,22 @@ class KmsGui(tk.Tk):
# is a STRING. # is a STRING.
return value return value
def prep_logfile(self, filepath): def prep_logfile(self, filepath, status):
# FILE (pretty on, log view off, logfile yes) # FILE (pretty on, log view off, logfile yes)
# FILEOFF (pretty on, log view off, no logfile) # FILEOFF (pretty on, log view off, no logfile)
# STDOUT (pretty off, log view on, no logfile) # STDOUT (pretty off, log view on, no logfile)
# STDOUTOFF (pretty off, log view off, logfile yes) # STDOUTOFF (pretty off, log view off, logfile yes)
# FILESTDOUT (pretty off, log view on, logfile yes) # FILESTDOUT (pretty off, log view on, logfile yes)
st = self.chksrvfile.state()
if st == 'FILE': if status == 'FILE':
return filepath return filepath
elif st in ['FILESTDOUT', 'STDOUTOFF']: elif status in ['FILESTDOUT', 'STDOUTOFF']:
return [st, filepath] return [status, filepath]
elif st in ['STDOUT', 'FILEOFF']: elif status in ['STDOUT', 'FILEOFF']:
return st return status
def validate_int(self, value): def validate_int(self, value):
return value == '' or value.isdigit() return value == "" or value.isdigit()
def validate_float(self, value): def validate_float(self, value):
if value == "": if value == "":
@ -566,13 +680,13 @@ class KmsGui(tk.Tk):
except ValueError: except ValueError:
return False return False
def clt_on_show(self, force = False): def clt_on_show(self, force_remove = False, force_view = False):
if self.optcltwin.winfo_ismapped() or force: if self.optcltwin.winfo_ismapped() or force_remove:
self.shbtnclt['text'] = 'SHOW\nCLIENT' self.shbtnclt['text'] = 'SHOW\nCLIENT'
self.optcltwin.grid_remove() self.optcltwin.grid_remove()
self.msgcltwin.grid_remove() self.msgcltwin.grid_remove()
self.btncltwin.place_forget() self.btncltwin.place_forget()
else: elif not self.optcltwin.winfo_ismapped() or force_view:
self.shbtnclt['text'] = 'HIDE\nCLIENT' self.shbtnclt['text'] = 'HIDE\nCLIENT'
self.optcltwin.grid() self.optcltwin.grid()
self.msgcltwin.grid() self.msgcltwin.grid()
@ -580,12 +694,12 @@ class KmsGui(tk.Tk):
def srv_on_start(self): def srv_on_start(self):
if self.runbtnsrv['text'] == 'START\nSERVER': if self.runbtnsrv['text'] == 'START\nSERVER':
self.on_clear([txsrv, txclt])
self.srv_actions_start() self.srv_actions_start()
# wait for switch. # wait for switch.
while not serverthread.is_running_server: while not serverthread.is_running_server:
pass pass
self.on_clear([txsrv, txclt])
self.srv_toggle_all(on_start = True) self.srv_toggle_all(on_start = True)
# run thread for interrupting server when an error happens. # run thread for interrupting server when an error happens.
self.srv_eject_thread = threading.Thread(target = self.srv_eject, name = "Thread-SrvEjt") 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['count']['des']] = self.prep_option(self.count.get())
srv_config[srv_options['activation']['des']] = self.prep_option(self.activ.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['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['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['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') serverqueue.put('start')
def srv_actions_stop(self): def srv_actions_stop(self):
@ -641,6 +761,8 @@ class KmsGui(tk.Tk):
foreground = self.customcolors['white']) foreground = self.customcolors['white'])
for widget in self.storewidgets_srv: for widget in self.storewidgets_srv:
widget.configure(state = 'normal') widget.configure(state = 'normal')
if isinstance(widget, ListboxOfRadiobuttons):
widget.change()
self.runbtnclt.configure(state = 'disabled') self.runbtnclt.configure(state = 'disabled')
def srv_toggle_state(self): def srv_toggle_state(self):
@ -652,13 +774,18 @@ class KmsGui(tk.Tk):
self.statesrv.configure(text = txt, foreground = color) self.statesrv.configure(text = txt, foreground = color)
def clt_on_start(self): 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() self.clt_actions_start()
# run thread for disabling interrupt server and client, when client running. # 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 = threading.Thread(target = self.clt_eject, name = "Thread-CltEjt")
self.clt_eject_thread.setDaemon(True) self.clt_eject_thread.setDaemon(True)
self.clt_eject_thread.start() self.clt_eject_thread.start()
self.on_clear([txsrv, txclt])
for widget in self.storewidgets_clt + [self.runbtnsrv, self.runbtnclt]: for widget in self.storewidgets_clt + [self.runbtnsrv, self.runbtnclt]:
widget.configure(state = 'disabled') 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['mode']['des']] = self.cltmode.get()
clt_config[clt_options['cmid']['des']] = self.cltcmid.get() clt_config[clt_options['cmid']['des']] = self.cltcmid.get()
clt_config[clt_options['name']['des']] = self.cltname.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['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()) 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). # run client (in a thread).
self.clientthread = client_thread(name = "Thread-Clt") self.clientthread = client_thread(name = "Thread-Clt")
self.clientthread.setDaemon(True) self.clientthread.setDaemon(True)
@ -681,8 +814,18 @@ class KmsGui(tk.Tk):
def clt_eject(self): def clt_eject(self):
while self.clientthread.is_alive(): while self.clientthread.is_alive():
sleep(0.1) sleep(0.1)
for widget in self.storewidgets_clt + [self.runbtnsrv, self.runbtnclt]:
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') widget.configure(state = 'normal')
if isinstance(widget, ListboxOfRadiobuttons):
widget.change()
def on_exit(self): def on_exit(self):
if serverthread.is_running_server: if serverthread.is_running_server:
@ -693,8 +836,38 @@ class KmsGui(tk.Tk):
server_terminate(serverthread, exit_thread = True) server_terminate(serverthread, exit_thread = True)
self.destroy() self.destroy()
def on_clear(self, widgetlist): def on_clear_setup(self):
for widget in widgetlist: 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.configure(state = 'normal')
widget.delete('1.0', 'end') widget.delete(ini, fin)
if add_n:
widget.insert('end', '\n')
widget.configure(state = 'disabled') widget.configure(state = 'disabled')

View File

@ -120,37 +120,21 @@ class ToolTip(object):
self.tw = None 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 TextRedirect(object):
class StdoutRedirect(object): class Pretty(object):
tag_num = 0
grpmsg = unformat_message([MsgMap[1], MsgMap[7], MsgMap[12], MsgMap[20]]) grpmsg = unformat_message([MsgMap[1], MsgMap[7], MsgMap[12], MsgMap[20]])
arrows = [ item[0] for item in grpmsg ] arrows = [ item[0] for item in grpmsg ]
clt_msg_nonewline = [ item[1] for item in grpmsg ] clt_msg_nonewline = [ item[1] for item in grpmsg ]
arrows = list(set(arrows)) arrows = list(set(arrows))
lenarrow = len(arrows[0]) lenarrow = len(arrows[0])
srv_msg_nonewline = [ item[0] for item in unformat_message([MsgMap[2], MsgMap[5], MsgMap[13], MsgMap[18]]) ] 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]]) ] msg_align = [ msg[0].replace('\t', '').replace('\n', '') for msg in unformat_message([MsgMap[-2], MsgMap[-4]]) ]
newlinecut = [-1, -2, -4, -5]
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.srv_text_space = srv_text_space
self.clt_text_space = clt_text_space self.clt_text_space = clt_text_space
self.customcolors = customcolors 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): def textbox_write(self, tag, message, color, extras):
widget = self.textbox_choose(message) widget = self.textbox_choose(message)
@ -161,14 +145,15 @@ class TextRedirect(object):
self.textbox_color(tag, widget, color, self.customcolors['black'], extras) self.textbox_color(tag, widget, color, self.customcolors['black'], extras)
widget.after(100, widget.see('end')) widget.after(100, widget.see('end'))
widget.configure(state = 'disabled') widget.configure(state = 'disabled')
self.textbox_finish(message)
def textbox_choose(self, 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.srv_text_space.focus_set()
self.where = "srv"
return self.srv_text_space 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.clt_text_space.focus_set()
self.where = "clt"
return self.clt_text_space return self.clt_text_space
def textbox_color(self, tag, widget, forecolor = 'white', backcolor = 'black', extras = []): def textbox_color(self, tag, widget, forecolor = 'white', backcolor = 'black', extras = []):
@ -211,8 +196,8 @@ class TextRedirect(object):
# horizontal align. # horizontal align.
if msg_unformat in self.msg_align: if msg_unformat in self.msg_align:
msg_strip = message.lstrip('\n') msg_strip = message.lstrip('\n')
message = '\n' * (len(message) - len(msg_strip) + TextRedirect.StdoutRedirect.newlinecut[0]) + msg_strip message = '\n' * (len(message) - len(msg_strip) + TextRedirect.Pretty.newlinecut[0]) + msg_strip
TextRedirect.StdoutRedirect.newlinecut.pop(0) TextRedirect.Pretty.newlinecut.pop(0)
count = Counter(message) count = Counter(message)
countab = (count['\t'] if count['\t'] != 0 else 1) countab = (count['\t'] if count['\t'] != 0 else 1)
@ -220,25 +205,49 @@ class TextRedirect(object):
return message return message
def textbox_do(self): 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: for tag in msgs:
self.textbox_write(tag, msgs[tag]['text'], self.customcolors[msgs[tag]['color']], msgs[tag]['extra']) self.textbox_write(tag, msgs[tag]['text'], self.customcolors[msgs[tag]['color']], msgs[tag]['extra'])
class StderrRedirect(StdoutRedirect): def flush(self):
def __init__(self, srv_text_space, clt_text_space, customcolors): 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.srv_text_space = srv_text_space
self.clt_text_space = clt_text_space self.clt_text_space = clt_text_space
self.customcolors = customcolors self.customcolors = customcolors
self.side = side
self.tag_err = 'STDERR' self.tag_err = 'STDERR'
self.xfont = tkFont.Font(font = self.srv_text_space['font']) 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): 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.configure(state = 'normal')
self.srv_text_space.insert('end', string, self.tag_err) self.srv_text_space.insert('end', string, self.tag_err)
self.srv_text_space.see('end') self.srv_text_space.see('end')
self.srv_text_space.configure(state = 'disabled') 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): class TextDoubleScroll(tk.Frame):
def __init__(self, master, **kwargs): def __init__(self, master, **kwargs):
@ -328,7 +337,7 @@ def custom_background(window):
widget.configure(background = window.customcolors['lavender']) widget.configure(background = window.customcolors['lavender'])
# Hide client. # Hide client.
window.clt_on_show(force = True) window.clt_on_show(force_remove = True)
# Show Gui. # Show Gui.
window.deiconify() window.deiconify()
@ -488,7 +497,12 @@ class ListboxOfRadiobuttons(tk.Frame):
if st in ['STDOUT', 'FILEOFF']: if st in ['STDOUT', 'FILEOFF']:
if wclass == 'Entry': if wclass == 'Entry':
widget.delete(0, 'end') widget.delete(0, 'end')
widget.configure(state = "disabled")
elif wclass == 'TCombobox': elif wclass == 'TCombobox':
if st == 'STDOUT':
widget.set(default)
widget.configure(state = "readonly")
elif st == 'FILEOFF':
widget.set('') widget.set('')
widget.configure(state = "disabled") widget.configure(state = "disabled")
elif st in ['FILE', 'FILESTDOUT', 'STDOUTOFF']: elif st in ['FILE', 'FILESTDOUT', 'STDOUTOFF']:

View File

@ -86,28 +86,104 @@ class LevelFormatter(logging.Formatter):
formatter = self.formatters.get(record.levelno, self.default_fmt) formatter = self.formatters.get(record.levelno, self.default_fmt)
return formatter.format(record) 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'): def logger_create(log_obj, config, mode = 'a'):
# Create new level. # Create new level.
add_logging_level('MINI', logging.CRITICAL + 10) add_logging_level('MINI', logging.CRITICAL + 10)
log_handlers = []
# Configure visualization. # Configure visualization.
log_handlers = []
if any(opt in ['STDOUT', 'FILESTDOUT', 'STDOUTOFF'] for opt in config['logfile']): if any(opt in ['STDOUT', 'FILESTDOUT', 'STDOUTOFF'] for opt in config['logfile']):
if 'STDOUTOFF' not in config['logfile']: if any(opt in ['STDOUT', 'FILESTDOUT'] for opt in config['logfile']):
# STDOUT. # STDOUT or FILESTDOUT.
log_handlers.append(logging.StreamHandler(sys.stdout)) 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']): if any(opt in ['STDOUTOFF', 'FILESTDOUT'] for opt in config['logfile']):
# FILESTDOUT or STDOUTOFF. # STDOUTOFF or FILESTDOUT.
log_handlers.append(RotatingFileHandler(filename = config['logfile'][1], mode = mode, maxBytes = int(config['logsize'] * 1024 * 512), hand_rotate = RotatingFileHandler(filename = config['logfile'][1], mode = mode, maxBytes = int(config['logsize'] * 1024 * 512),
backupCount = 1, encoding = None, delay = 0)) backupCount = 1, encoding = None, delay = 0)
hand_rotate.name = 'LogRotate'
log_handlers.append(hand_rotate)
elif 'FILEOFF' in config['logfile']: elif 'FILEOFF' in config['logfile']:
config['loglevel'] = 'ERROR' # for py-kms GUI: set a recognized level never used. hand_null = logging.FileHandler(os.devnull)
log_handlers.append(logging.FileHandler(os.devnull)) hand_null.name = 'LogNull'
log_handlers.append(hand_null)
else: else:
# FILE. # FILE.
log_handlers.append(RotatingFileHandler(filename = config['logfile'][0], mode = mode, maxBytes = int(config['logsize'] * 1024 * 512), hand_rotate = RotatingFileHandler(filename = config['logfile'][0], mode = mode, maxBytes = int(config['logsize'] * 1024 * 512),
backupCount = 1, encoding = None, delay = 0)) backupCount = 1, encoding = None, delay = 0)
hand_rotate.name = 'LogRotate'
log_handlers.append(hand_rotate)
# Configure formattation. # Configure formattation.
try: try:
@ -115,27 +191,38 @@ def logger_create(log_obj, config, mode = 'a'):
except AttributeError: except AttributeError:
levelnames = logging._levelNames levelnames = logging._levelNames
levelnum = [k for k in levelnames if k != 0] 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' 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 = {} levelformdict = {}
for num in levelnum: for num in levelnum:
if num != logging.CRITICAL + 10: if num != logging.CRITICAL + 10:
levelformdict[num] = frmt0 levelformdict[num] = formats[0]
else: else:
levelformdict[num] = frmt1 levelformdict[num] = formats[1]
handler.setFormatter(LevelFormatter(levelformdict, color = color))
return handler
# Clear old handlers.
if log_obj.handlers:
log_obj.handlers = []
# Set level and format.
levelformdictcopy = levelformdict.copy()
for log_handler in log_handlers: for log_handler in log_handlers:
log_handler.setLevel(config['loglevel']) log_handler.setLevel(config['loglevel'])
if log_handler.__class__.__name__ == 'StreamHandler': if log_handler.name in ['LogStdout']:
log_handler.setFormatter(LevelFormatter(levelformdict, color = True)) log_handler = apply_formatter(levelnum, (frmt_std, frmt_min), log_handler, color = True)
elif log_handler.__class__.__name__ == 'RotatingFileHandler': elif log_handler.name in ['LogRotate']:
log_handler.setFormatter(LevelFormatter(levelformdictcopy, color = False)) log_handler = apply_formatter(levelnum, (frmt_gen, frmt_min), log_handler)
# Attach. # Attach.
if config['asyncmsg']:
log_obj.addHandler(MultiProcessingLogHandler('Thread-AsyncMsg{0}'.format(log_handler.name), handler = log_handler))
else:
log_obj.addHandler(log_handler)
log_obj.setLevel(config['loglevel']) 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] optionlog = [optionlog]
lenopt = len(optionlog) lenopt = len(optionlog)
msg_dir = "{reverse}{red}{bold}argument logfile: invalid directory: '%s'. Exiting...{end}" msg_dir = "{reverse}{red}{bold}argument `-F/--logfile`: invalid directory: '%s'. Exiting...{end}"
msg_long = "{reverse}{red}{bold}argument logfile: too much arguments. Exiting...{end}" msg_long = "{reverse}{red}{bold}argument `-F/--logfile`: too much arguments. Exiting...{end}"
msg_log = "{reverse}{red}{bold}argument logfile: not a log file, invalid extension: '%s'. Exiting...{end}" msg_log = "{reverse}{red}{bold}argument `-F/--logfile`: not a log file, invalid extension: '%s'. Exiting...{end}"
def checkdir(path): def checkdir(path):
filename = os.path.basename(path) filename = os.path.basename(path)
pathname = os.path.dirname(path) pathname = os.path.dirname(path)
if not os.path.isdir(pathname): if not os.path.isdir(pathname):
if path.count('/') == 0:
pathname = filename
pretty_printer(put_text = msg_dir %pathname, where = where, to_exit = True) pretty_printer(put_text = msg_dir %pathname, where = where, to_exit = True)
elif not filename.lower().endswith('.log'): elif not filename.lower().endswith('.log'):
pretty_printer(put_text = msg_log %filename, where = where, to_exit = True) 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: if lenopt > 2:
pretty_printer(put_text = msg_long, where = where, to_exit = True) pretty_printer(put_text = msg_long, where = where, to_exit = True)
if (any(opt in ['FILESTDOUT', 'STDOUTOFF'] for opt in optionlog)): if (any(opt in ['FILESTDOUT', 'STDOUTOFF'] for opt in optionlog)):
if lenopt == 1: if lenopt == 1:
# add default logfile. # 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]) fixlcid = next(k for k, v in locale.windows_locale.items() if v == locale.getdefaultlocale()[0])
except StopIteration: except StopIteration:
fixlcid = 1033 fixlcid = 1033
pretty_printer(log_obj = log_obj, pretty_printer(log_obj = log_obj, put_text = "{reverse}{yellow}{bold}LCID '%s' auto-fixed with LCID '%s'{end}" %(lcid, fixlcid))
put_text = "{reverse}{yellow}{bold}LCID %s auto-fixed with LCID %s{end}" %(lcid, fixlcid))
return fixlcid return fixlcid
return lcid return lcid
@ -262,28 +349,123 @@ class KmsParserHelp(object):
print(parser_base.epilog + '\n') print(parser_base.epilog + '\n')
parser_base.exit() 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): def proper_none(dictionary):
for key in dictionary.keys(): for key in dictionary.keys():
dictionary[key] = None if dictionary[key] == 'None' else dictionary[key] dictionary[key] = None if dictionary[key] == 'None' else dictionary[key]
def check_setup(config, options, logger, where): def check_setup(config, options, logger, where):
# 'None'--> None.
proper_none(config)
# Check logfile. # Check logfile.
config['logfile'] = check_logfile(config['logfile'], options['lfile']['def'], where = where) 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'] 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. # Create log.
logger_create(logger, config, mode = 'a') logger_create(logger, config, mode = 'a')
# 'None'--> None.
proper_none(config)
# Check port. # Check port.
if not 1 <= config['port'] <= 65535: if (config['port'] == "") or (not 1 <= config['port'] <= 65535):
pretty_printer(log_obj = logger.error, to_exit = True, 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']) put_text = "{reverse}{red}{bold}Port number '%s' is invalid. Enter between 1 - 65535. Exiting...{end}" %config['port'])
#------------------------------------------------------------------------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@ -29,6 +29,7 @@ from pykms_RpcBase import rpcBase
from pykms_Dcerpc import MSRPCHeader from pykms_Dcerpc import MSRPCHeader
from pykms_Misc import check_setup, check_lcid from pykms_Misc import check_setup, check_lcid
from pykms_Misc import KmsParser, KmsParserException, KmsParserHelp 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 pykms_Format import enco, deco, pretty_printer
from Etrigan import Etrigan, Etrigan_parser, Etrigan_check, Etrigan_job from Etrigan import Etrigan, Etrigan_parser, Etrigan_check, Etrigan_job
@ -148,12 +149,15 @@ class server_thread(threading.Thread):
# Create and run server. # Create and run server.
self.server = server_create() self.server = server_create()
self.server.pykms_serve() self.server.pykms_serve()
except SystemExit as e: except (SystemExit, Exception) as e:
self.eject = True self.eject = True
if not self.with_gui: if not self.with_gui:
raise raise
else: else:
if isinstance(e, SystemExit):
continue 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)', '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"}, '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; \ '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).', '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).', '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.', 'sql' : {'help' : 'Use this option to store request information from unique clients in an SQLite database. Desactivated by default.',
'def' : False, 'des' : "sqlite"}, 'def' : False, 'des' : "sqlite"},
'hwid' : {'help' : 'Use this option to specify a HWID. The HWID must be an 16-character string of hex characters. \ '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"}, 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.', '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", '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"]}, 'choi' : ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "MINI"]},
'lfile' : {'help' : 'Use this option to set an output log file. The default is \"pykms_logserver.log\". \ '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(): 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("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("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'], 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) help = srv_options['count']['help'], type = str)
server_parser.add_argument("-a", "--activation-interval", action = "store", dest = srv_options['activation']['des'], 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) 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'], server_parser.add_argument("-r", "--renewal-interval", action = "store", dest = srv_options['renewal']['des'],
help = srv_options['renewal']['help'], type = int) 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'], server_parser.add_argument("-s", "--sqlite", action = "store_true", dest = srv_options['sql']['des'],
help = srv_options['sql']['help']) 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'], 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) 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'], 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) 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'], 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) 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'], server_parser.add_argument("-F", "--logfile", nargs = "+", action = "store", dest = srv_options['lfile']['des'],
help = srv_options['lfile']['help'], type = str) 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'], 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) help = srv_options['lsize']['help'], type = float)
server_parser.add_argument("-h", "--help", action = "help", help = "show this help message and exit") 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") 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, etrigan_parser.add_argument("-g", "--gui", action = "store_const", dest = 'gui', const = True, default = False,
help = "Enable py-kms GUI usage.") help = "Enable py-kms GUI usage.")
etrigan_parser = Etrigan_parser(parser = etrigan_parser) etrigan_parser = Etrigan_parser(parser = etrigan_parser)
try: 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]) KmsParserHelp().printer(parsers = [server_parser, daemon_parser, etrigan_parser])
# Set defaults for config. # Get stored arguments.
# case: python3 pykms_Server.py pykmssrv_zeroarg, pykmssrv_onearg = kms_parser_get(server_parser)
srv_config.update(vars(server_parser.parse_args([]))) etrigan_zeroarg, etrigan_onearg = kms_parser_get(etrigan_parser)
# Eventually set daemon values for config. pykmssrv_zeroarg += ['etrigan'] # add subparser
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]
# continue parsing. # Set defaults for config.
if len(knw_extras) > 0 and knw_extras[0] in ['etrigan']: # example case:
daemon_parser.parse_args(knw_extras, namespace = knw_args) # python3 pykms_Server.py
srv_config.update(vars(knw_args)) srv_config.update(vars(server_parser.parse_args([])))
else:
# Update dict config. try:
# case: python3 pykms_Server.py 1.2.3.4 1234 --main_optionals # Eventually set daemon options for dict server config.
knw_args, knw_extras = server_parser.parse_known_args() pos = sys.argv[1:].index('etrigan')
if knw_extras != []: # example cases:
raise KmsParserException("unrecognized arguments: %s" %' '.join(knw_extras)) # python3 pykms_Server.py etrigan start
else: # python3 pykms_Server.py etrigan start --daemon_optionals
srv_config.update(vars(knw_args)) # 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: 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)
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(): def server_daemon():
if 'etrigan' in srv_config.values(): if 'etrigan' in srv_config.values():
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'pykms_config.pickle') 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: 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']: if srv_config['gui']:
pass pass
@ -357,21 +388,30 @@ def server_check():
else: else:
srv_config['dbSupport'] = True srv_config['dbSupport'] = True
# Check client count, timeout.
list_dest = ['CurrentClientCount', 'timeout_idle'] # Check other specific server options.
list_opt = ['--client-count', '--timeout-idle'] 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): for dest, opt in zip(list_dest, list_opt):
if srv_config[dest] is not None: value = srv_config[dest]
if not srv_config[dest].isdigit(): if (value is not None) and (not isinstance(value, int)):
pretty_printer(log_obj = loggersrv.error, to_exit = True, pretty_printer(log_obj = loggersrv.error, to_exit = True,
put_text = "{reverse}{red}{bold}Option %s is invalid with '%s'. Exiting...{end}" put_text = "{reverse}{red}{bold}argument `%s`: invalid with: '%s'. Exiting...{end}" %(opt, value))
%(opt, srv_config[dest]))
else:
srv_config[dest] = int(srv_config[dest])
def server_create(): def server_create():
try:
server = KeyServer((srv_config['ip'], srv_config['port']), kmsServerHandler) server = KeyServer((srv_config['ip'], srv_config['port']), kmsServerHandler)
server.timeout = srv_config['timeout_idle'] 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("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()) loggersrv.info("HWID: %s" % deco(binascii.b2a_hex(srv_config['hwid']), 'utf-8').upper())
return server return server
@ -426,20 +466,8 @@ def server_main_terminal():
def server_with_gui(): def server_with_gui():
import pykms_GuiBase import pykms_GuiBase
width = 950
height = 660
root = pykms_GuiBase.KmsGui() root = pykms_GuiBase.KmsGui()
root.title(pykms_GuiBase.gui_description + ' (' + pykms_GuiBase.gui_version + ')') 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() root.mainloop()
def server_main_no_terminal(): def server_main_no_terminal():