InternetChecker/Python/InternetChecker.py

1250 lines
53 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from os.path import dirname as directory_name
from os.path import abspath as path_absolute
from os.path import exists as path_exists
from subprocess import Popen as ProcessOpen
from subprocess import PIPE
from re import compile as RECompile
from multiprocessing import Process
from multiprocessing import Manager as MultiprocessManager
from multiprocessing.managers import DictProxy, ListProxy
from random import random as random_number
from time import sleep
from time import time as timestamp
from datetime import datetime
from threading import Thread
from traceback import print_exc as print_trace_exception
import signal
from inspect import stack as get_stack
import sqlite3
from traceback import extract_tb as extract_traceback
from traceback import format_stack as trace_format_stack
from os import makedirs as make_directories
settings = {}
for key in ("Secrets", "secrets"):
path = path_absolute(directory_name(__file__)) + "/" + key + ".py"
if path_exists(path):
try:
with open(path) as data:
exec(compile(data.read(), path, "exec"), globals())
break
except:
print_trace_exception()
class InternetChecker:
__default_settings = {
"nulls" : False,
"default_value" : None,
"threads_start_now" : True,
"threads_minimum_timing" : 1.0 / 24.0,
"threads_timing" : 1.0 / 24.0,
"threads_bucle" : True,
"threads_autostart" : True,
"ping_checker_timing" : [.5, 2],
"ping_samples" : 4,
"print_format" : "[{type}] {yyyy}{mm}{dd} {hh}{ii}{ss} [{line}]{file}({method}): {message}",
"close_check_timer" : .1,
"language" : "espanol",
"default_language" : "espanol",
"default_text" : "",
"show_execute_threads_exception" : True,
"show_add_thread_exception" : True,
"show_add_thread_error" : True,
"show_add_thread_ok" : True,
"show_add_ping_checker_error" : True,
"show_add_ping_checker_ok" : True,
"show_remove_thread_exception" : True,
"show_remove_thread_error" : True,
"show_remove_thread_ok" : True,
"show_ping_line_data" : True,
"show_ping_line_raw_data" : False,
"show_ping_line_unknown" : False,
"show_ping_sequence_results" : True,
"show_get_path_error" : True,
"show_get_path_ok" : True,
"allow_create_database_file" : True,
"host_id" : "InternetChecker",
"show_update_database_error" : True,
"show_update_database_ok" : True,
"show_get_database_connection_exception" : True,
"show_get_database_connection_error" : True,
"show_get_database_connection_ok" : True,
"show_close_database_connection_exception" : True,
"show_close_database_connection_error" : True,
"show_close_database_connection_ok" : True,
"show_build_database_exception" : True,
"show_build_database_error" : True,
"show_build_database_ok" : True,
"show_make_directory_exception" : True,
"show_make_directory_error" : True,
"show_make_directory_ok" : True,
"database_update_timing" : 5,
"database_allow_save_raw_pings" : True
}
__sentences = {
"espanol" : {
"internet_checker_building" : "La aplicación de servicio local 'InternetChecker' se está construyendo...",
"internet_checker_built" : "La aplicación de servicio local 'InternetChecker' se construyó y se inició completamente.",
"internet_checker_closing" : "La aplicación de sercicio local 'InternetChecker' se está cerrando...",
"internet_checker_closing_forced" : "La aplicación de sercicio local 'InternetChecker' se está cerrando de forma forzada...",
"internet_checker_closed" : "La aplicación de sercicio local 'InternetChecker' se cerró completamente. ¡Muchas gracias por usarme!.",
"ping_sequence_results" : "La secuencia '{sequence}' del ping '{site}' fue realizado en '{round_time}' segundos mediante '{samples}' muestras con una media de '{round_average_ping}' milisegundos de respuesta con '{lost}' paquetes perdidos.",
"execute_threads_exception" : "Hubo una excepción al intentar ejecutar el hilo de procesos '{i}'. '{file}({method})[{line}]'{lines}\n\n{exception_message}",
"add_thread_exception" : "Hubo una excepción al intentar añadir el nuevo hilo de procesos '{i}'. '{file}({method})[{line}]'{lines}\n\n{exception_message}",
"add_ping_checker_error" : "Hubo un error con código '{code}' al intentar añadir el nuevo Ping '{i}' contra '{site}'.{list}",
"add_ping_checker_ok" : "El nuevo Ping '{i}' contra '{site}' fue añadido correctamente.",
"exception" : "Hubo una excepción.",
"site_null" : "El sitio es nulo.",
"site_not_string" : "El sitio no es un String.",
"site_empty" : "El sitio está vacío.",
"site_not_match" : "El sitio no encaja con el patrón de dominio o IP.",
"method_null" : "El método es nulo.",
"method_not_function" : "El método no es una función.",
"start_now_null" : "El valor de inicio inmediato es nulo.",
"start_now_not_boolean" : "El valor de inicio inmediato no es un valor Booleano.",
"minimum_timing_null" : "El tiempo de proceso mínimo es nulo.",
"minimum_timing_not_float" : "El tiempo de proceso mínimo no es un valor decimal.",
"minimum_timing_lower_0" : "El tiempo de proceso mínimo es menor a 0.",
"timing_null" : "El tiempo entre ciclos de proceso es nulo.",
"timing_not_float" : "El tiempo entre ciclos de proceso no es un decimal.",
"timing_lower_0" : "El tiempo entre ciclos de proceso es inferior a 0.",
"timing_not_2" : "El tiempo entre ciclos de proceso no tiene dos valores.",
"timing_min_null" : "El tiempo mínimo entre ciclos de proceso es nulo.",
"timing_min_not_float" : "El tiempo mínimo entre ciclos de proceso no es decimal.",
"timing_min_lower_0" : "El tiempo mínimo entre ciclos de proceso es menor que 0.",
"timing_max_null" : "El tiempo máximo entre ciclos de proceso es nulo.",
"timing_max_not_float" : "El tiempo máximo entre ciclos de proceso no es decimal.",
"timing_max_lower_0" : "El tiempo máximo entre ciclos de proceso es menor que 0.",
"timing_min_greater_max" : "El tiempo mínimo entre ciclos es mayor que el máximo.",
"bucle_null" : "El valor que determina si hay o no bucle es nulo.",
"bucle_not_boolean" : "El valor que determina si hay o no bucle no es un valor Booleano.",
"autostart_null" : "El valor que determina si se autoinicia o no es nulo.",
"autostart_not_boolean" : "El valor que determina si se autoinicia o no no es un valor Booleano.",
"add_thread_exception" : "Hubo una excepción al intentar añadir el nuevo hilo de procesos '{i}'. '{file}({method})[{line}]'{lines}\n\n{exception_message}",
"add_thread_error" : "Hubo un error con código '{code}' al intentar añadir el nuevo hilo de procesos '{i}'.{list}",
"add_thread_ok" : "El nuevo hilo de procesos '{i}' fue añadido correctamente.",
"thread_i_null" : "El ID del hilo de procesos es nulo.",
"thread_i_not_integer" : "El ID del hilo de procesos no es un valor numérico entero.",
"thread_i_lower_0" : "El ID del hilo de procesos es inferior a 0.",
"thread_i_too_high" : "El ID del hilo de procesos es demasiado alto.",
"thread_i_deleted" : "El ID del hilo de procesos ya fue eliminado.",
"remove_thread_exception" : "Hubo una excepción al intentar eliminar el hilo de procesos '{i}'. '{file}({method})[{line}]'{lines}\n\n{exception_message}",
"remove_thread_error" : "Hubo un error con código '{code}' al intentar eliminar el hilo de procesos '{i}'.{list}",
"remove_thread_ok" : "El hilo de procesos '{i}' fue eliminado correctamente.",
"ping_line_data" : "Ping '[{sequence}]{samples}/{i}' contra '{site}' en {time} ms.",
"sites_null" : "Los sitios son nulos.",
"sites_not_list" : "Los sitios no son una lista.",
"sites_empty" : "Los sitios están vacíos.",
"database_path_null" : "El Path de la base de datos es nulo.",
"database_path_not_string" : "El Path de la base de datos no es un String.",
"database_path_whitespaces" : "El Path de la base de datos son sólo espacios en blanco.",
"database_path_not_exists" : "El Path de la base de datos no existe.",
"host_id_null" : "El ID del host es nulo.",
"host_id_not_string" : "El ID del Host no es un String.",
"host_id_empty" : "El ID del Host está vacío.",
"no_sites_error" : "Hubo un error con código '{code}' al intentar cargar los sitios a analizar.{list}",
"path_null" : "El Path es nulo.",
"path_not_string" : "El Path no es un String.",
"path_empty" : "El Path está vacío.",
"path_not_exists" : "El Path no existe.",
"get_path_error" : "Hubo un error con código '{code}' al intentar coger el Path completo de '{path}'.{list}",
"get_path_ok" : "El Path '{real_path}' fue recogido correctamente.",
"sqlite_connection_error" : "Hubo una excepción al intentar abrir una conexión con la base de datos '{path}'. '{file}({method})[{line}]'{lines}\n\n{exception_message}",
"settings_has_errors" : "La configuración contiene errores.",
"data_null" : "Los datos son nulos.",
"data_not_dictionary" : "Los datos no son un diccionario.",
"data_empty" : "Los datos están vacíos.",
"update_database_error" : "Hubo un error con código '{code}' al intentar actualizar los datos en la base de datos.{list}",
"update_database_ok" : "La base de datos fue actualizada correctamente.",
"get_database_connection_exception" : "Hubo una excepción al intentar crear una conexión contra la base de datos '{path}'. '{file}({method})[{line}]'{lines}\n\n{exception_message}",
"get_database_connection_error" : "Hubo un error con código '{code}' al intentar crear una conexión contra la base de datos '{path}'.{list}",
"get_database_connection_ok" : "La conexión contra la base de datos fue recogida correctamente.",
"close_database_connection_exception" : "Hubo una excepción al intentar cerrar la conexión contra la base de datos. '{file}({method})[{line}]'{lines}\n\n{exception_message}",
"close_database_connection_error" : "Hubo un error con código '{code}' al intentar cerrar la conexión contra la base de datos.{list}",
"close_database_connection_ok" : "La conexión contra la base de datos fue cerrada correctamente.",
"connection_null" : "La conexión es nula.",
"connection_bad_typed" : "El tipado de la conexión es erróneo.",
"has_errors_null" : "La variable que determina si hay errores o no es nula.",
"has_errors_bad_typed" : "El tipado que determina si hay errores o no es erróneo.",
"build_database_exception" : "Hubo una excepción al intentar construir la base de datos. '{file}({method})[{line}]'{lines}\n\n{exception_message}",
"build_database_error" : "Hubo un error con código '{code}' al intentar construir la base de datos.{list}",
"build_database_ok" : "La base de datos fue construída correctamente.",
"make_directory_exception" : "Hubo una excepción al intentar crear el directorio del Path '{path}'. '{file}({method})[{line}]'{lines}\n\n{exception_message}",
"make_directory_error" : "Hubo un error con código '{code}' al intentar crear el directorio del Path '{path}'.{list}",
"make_directory_ok" : "El directorio del Path '{path}' fue creado correctamente."
}
}
re_site_validate = RECompile(r'^(([0-9]+)?(\.[0-9]+){1,3}|(:|[0-9]+)+|([a-z0-9A-Z\-_]+\.[a-zA-Z0-9]+)+)$')
re_ping_line = RECompile(r'^([0-9]+) bytes from (([^ :]+)|([^ ]+) \(([^\)]+)\)): icmp_seq=([0-9]+) ttl=([0-9]+) time=([0-9]+(\.[0-9]+)?) ms$')
re_string_variables = RECompile(r'\{([^\{\}]+)\}')
re_break_lines = RECompile(r'\r\n|[\r\n]')
re_exception_line = RECompile(r'^\s*File "([^"]+)", line ([0-9]+), in (.+)$')
re_slashes = RECompile(r'[\/\\\\]+')
re_pascal_capitals = RECompile(r'([A-Z])')
re_field_key = RECompile(r'^([^\s]+)\s.+$')
re_directory = RECompile(r'^(.+)[\/\\\\][^\/\\\\]+$')
re_sql_semicolons = RECompile(r'\'')
def __init__(self, inputs = None):
self.__inputs = inputs
self.__print_format = self.settings("print_format")
self.__language = self.settings("language")
self.__default_language = self.settings("default_language")
self.__print_types = (
("UNKN", "unknown"),
(" OK ", "ok", "yes", "right", "y"),
("ERRO", "error", "wrong", "no", "n", "x"),
("EXCE", "exception"),
("INFO", "information"),
("WARN", "warning")
)
self._print("info", "internet_checker_building")
self.__threads = []
self.__main_thread = True
self.__threads_changing = False
self.__working = True
self.__ping_checker_timing = self.settings("ping_checker_timing")
self.__closing = False
self.__samples = self.settings(("ping_samples", "samples"))
self.__sequences = {}
self.__close_check_timer = self.settings("close_check_timer")
self.__default_text = self.settings("default_text")
self.__database_path = self.settings(("database_file", "sqlite_file"))
self.__host_id = self.settings("host_id")
self.__cache = {"pings" : [], "raw_pings" : []}
self.__database_thread = None
self.__session = None
self.__allow_save_raw_ping = self.settings(("database_allow_save_raw_pings", "allow_save_raw_pings"))
self.__show_execute_threads_exception = self.settings("show_execute_threads_exception")
self.__show_add_thread_exception = self.settings("show_add_thread_exception")
self.__show_add_thread_error = self.settings("show_add_thread_error")
self.__show_add_thread_ok = self.settings("show_add_thread_ok")
self.__show_add_ping_checker_error = self.settings("show_add_ping_checker_error")
self.__show_add_ping_checker_ok = self.settings("show_add_ping_checker_ok")
# self.__show_remove_thread_exception = self.settings("show_remove_thread_exception")
self.__show_remove_thread_error = self.settings("show_remove_thread_error")
self.__show_remove_thread_ok = self.settings("show_remove_thread_ok")
self.__show_ping_line_data = self.settings("show_ping_line_data")
self.__show_ping_line_raw_data = self.settings("show_ping_line_raw_data")
self.__show_ping_line_unknown = self.settings("show_ping_line_unknown")
self.__show_ping_sequence_results = self.settings("show_ping_sequence_results")
self.__show_get_path_error = self.settings("show_get_path_error")
self.__show_get_path_ok = self.settings("show_get_path_ok")
self.__show_update_database_error = self.settings("show_update_database_error")
self.__show_update_database_ok = self.settings("show_update_database_ok")
self.__show_get_database_connection_exception = self.settings("show_get_database_connection_exception")
self.__show_get_database_connection_error = self.settings("show_get_database_connection_error")
self.__show_get_database_connection_ok = self.settings("show_get_database_connection_ok")
self.__show_close_database_connection_exception = self.settings("show_close_database_connection_exception")
self.__show_close_database_connection_error = self.settings("show_close_database_connection_error")
self.__show_close_database_connection_ok = self.settings("show_close_database_connection_ok")
self.__show_build_database_exception = self.settings("show_build_database_exception")
self.__show_build_database_error = self.settings("show_build_database_error")
self.__show_build_database_ok = self.settings("show_build_database_ok")
self.__show_make_directory_exception = self.settings("show_make_directory_exception")
self.__show_make_directory_error = self.settings("show_make_directory_error")
self.__show_make_directory_ok = self.settings("show_make_directory_ok")
sites = self.settings("sites")
self.__error = (
((
1 << 0 if sites == None else
1 << 1 if not isinstance(sites, (list, tuple)) else
1 << 2 if not len(sites) else
0) << 0) |
((
1 << 0 if self.__database_path == None else
1 << 1 if not isinstance(self.__database_path, str) else
0) << 3) |
((
1 << 0 if self.__host_id == None else
1 << 1 if not isinstance(self.__host_id, str) else
1 << 2 if not self.__host_id else
0) << 7) |
0) << 1
self.__root_paths = ("", path_absolute(directory_name(__file__)))
self.__slash = '/' if '/' in self.__root_paths[1] else '\\'
if not (self.__error >> 4) & ~-(1 << 2):
self.__database_path = self.__database_path.strip()
self.__error |= (
1 << 0 if not len(self.__database_path) else
0) << 6
if not self.__error and not self.settings("allow_create_database_file"):
(self.__database_path, suberror) = self.get_path(self.__database_path)
if suberror:
self.__error |= 1 << 7
if not self.__error & ~-(1 << 4):
for site in sites:
self.add_ping_checker(site)
if self.validate(
self.__error,
(
"exception",
"sites_null",
"sites_not_list",
"sites_empty",
"database_path_null",
"database_path_not_string",
"database_path_whitespaces",
"database_path_not_exists",
"host_id_null",
"host_id_not_string",
"host_id_empty"
),
{},
"no_sites_error"
) and not self.__build_database():
self.__database_thread = self.add_thread(self.__update_database, {
"bucle" : True,
"start_now" : False,
"timing" : self.settings("database_update_timing")
})[0]
signal.signal(signal.SIGINT, lambda *_:self.close())
self._print("ok", "internet_checker_built")
signal.pause()
# self._print("info", "internet_checker_closing_forced")
def close(self):
if not self.__main_thread or self.__closing:
return
self.__closing = True
self._print("ok", "internet_checker_closing")
if self.__database_thread != None:
self.remove_thread(self.__database_thread)
self.__working = False
while len([None for thread in self.__threads if thread]):
sleep(self.__close_check_timer)
self._print("ok", "internet_checker_closed")
def __save_ping_line(self, data):
self.__show_ping_line_data and self._print("info", "ping_line_data", data)
self.__show_ping_line_raw_data and print(data)
# self.__cache["pings"] += [data]
def __ping_line(self, results, line):
matches = self.re_ping_line.search(line)
if matches:
data = {
"samples" : self.__samples,
"site" : results["site"],
"size" : int(matches.group(1)),
"from" : matches.group(4),
"ip" : matches.group(3) or matches.group(5),
"i" : int(matches.group(6)),
"ttl" : int(matches.group(7)),
"time" : float(matches.group(8)),
"sequence" : self.__sequences[results["site"]],
"date" : datetime.now()
}
results["data"] += [data]
self.__save_ping_line(data)
return True
return False
def __ping_handler(self, results):
process = ProcessOpen("ping -c " + str(self.__samples) + " " + results["site"], stdout = PIPE, shell = True)
self.__main_thread = False
for line in iter(process.stdout.readline, b''):
if line == b'':
break
line = line.decode("utf-8").strip()
results["raw_lines"] += [line]
if self.__ping_line(results, line):
continue
self.__show_ping_line_unknown and print([len(results["raw_lines"]), results["raw_lines"][-1]])
@staticmethod
def call_with_timeout(method, timeout, arguments):
with MultiprocessManager() as manager:
parameters = []
for i, argument in enumerate(arguments):
parameters += [(
manager.list(argument) if isinstance(arguments[i], (list, tuple)) else
manager.dict(argument) if isinstance(arguments[i], dict) else
arguments[i])]
process = Process(target = method, args = parameters)
process.start()
process.join(timeout)
if process.is_alive():
process.terminate()
for i, parameter in enumerate(parameters):
arguments[i] = (
list(parameter) if isinstance(parameter, ListProxy) else
dict(parameter) if isinstance(parameter, DictProxy) else
parameter)
return arguments
def __ping(self, site):
if site in self.__sequences:
self.__sequences[site] += 1
else:
self.__sequences[site] = 1
results = self.call_with_timeout(self.__ping_handler, 10, [{
"site" : site,
"sequence" : self.__sequences[site],
"from" : timestamp(),
"to" : None,
"data" : [],
"raw_lines" : []
}])[0]
self.__cache["pings"] += results["data"]
results["to"] = timestamp()
l = len(results["data"])
average_ping = sum([line["time"] for line in results["data"]]) / l if l else -1
time = results["to"] - results["from"]
self.__cache["raw_pings"] += [{key : results[key] for key in ("site", "sequence", "from", "to", "raw_lines")}]
self.__show_ping_sequence_results and self._print("info", "ping_sequence_results", {
**results,
"time" : time,
"round_time" : round(time, 2),
"samples" : self.__samples,
# "lost" : len([None for line in data if line])
"lost" : len([None for i, _ in enumerate(results["data"]) if i and results["data"][i - 1]["i"] + 1 != results["data"][i]["i"]]),
"average_ping" : average_ping,
"round_average_ping" : round(average_ping, 2)
})
return results
def nulls(self, nulls = None):
return nulls if isinstance(nulls, bool) else self.settings("nulls", None, False, False)
def default_value(self, default = None, nulls = None):
return default if self.nulls(nulls) or default != None else self.settings("default_value", None, None, True)
def settings(self, keys, inputs = None, default = None, nulls = None):
keys = [key.strip() for key in (
keys if isinstance(keys, (list, tuple)) else
[keys] if isinstance(keys, str) else
[]) if isinstance(key, str) and key.strip()]
if len(keys):
nulls = self.nulls(nulls)
for subinputs in (
list(inputs) if isinstance(inputs, (list, tuple)) else
[inputs] if isinstance(inputs, dict) else
[]) + [self.__inputs, self.__default_settings]:
if isinstance(subinputs, dict):
for key in keys:
if key in subinputs and (nulls or subinputs[key] != None):
return subinputs[key]
return self.default_value(default, nulls)
def __check_ping_site(self, site):
if self.__working:
self.__ping(site)
def add_ping_checker(self, site, show_errors = None):
error = (
1 << 0 if site == None else
1 << 1 if not isinstance(site, str) else
1 << 2 if not site else
1 << 3 if not self.re_site_validate.search(site) else
0) << 1
i = None
has_show_errors = isinstance(show_errors, bool)
if not error:
(i, suberror) = self.add_thread(lambda:self.__check_ping_site(site), {
"start_now" : False,
"timing" : self.__ping_checker_timing
})
error |= ((suberror >> 1) << 5) | (suberror & 1)
self.validate(
error,
(
"exception",
"site_null",
"site_not_string",
"site_empty",
"site_not_match",
"method_null",
"method_not_function",
"start_now_null",
"start_now_not_boolean",
"minimum_timing_null",
"minimum_timing_not_float",
"minimum_timing_lower_0",
"timing_null",
"timing_not_float",
"timing_lower_0",
"timing_not_2",
"timing_min_null",
"timing_min_not_float",
"timing_min_lower_0",
"timing_max_null",
"timing_max_not_float",
"timing_max_lower_0",
"timing_min_greater_max",
"bucle_null",
"bucle_not_boolean",
"autostart_null",
"autostart_not_boolean"
),
{
"i" : i,
"site" : site
},
(show_errors if has_show_errors else self.__show_add_ping_checker_error) and "add_ping_checker_error",
(show_errors if has_show_errors else True) and self.__show_add_ping_checker_ok and "add_ping_checker_ok"
)
return (i, error)
def remove_ping_checker(self, ping_checker):
return self.remove_thread(ping_checker)
def __execute_threads(self, thread):
while self.__working and thread["working"]:
timing = thread["timing"] if isinstance(thread["timing"], (int, float)) else random_number() * (thread["timing"][1] - thread["timing"][0]) + thread["timing"][0]
if thread["executions"] or thread["start_now"]:
try:
thread["method"]()
if not thread["bucle"]:
thread["working"] = False
except Exception as exception:
self.exception(exception, self.__show_execute_threads_exception and "execute_threads_exception", thread)
thread["executions"] += 1
start = timestamp()
while thread["working"] and timestamp() - start < timing:
sleep(thread["minimum_timing"])
if self.__threads[thread["i"]]:
self.__threads[thread["i"]] = None
def add_thread(self, method, inputs = None, show_errors = None):
while self.__threads_changing:
sleep(random_number)
self.__threads_changing = True
start_now = self.settings(("threads_start_now", "start_now"), inputs)
minimum_timing = self.settings(("threads_minimum_timing", "minimum_timing"), inputs)
timing = self.settings(("threads_timing", "timing"), inputs)
bucle = self.settings(("threads_bucle", "bucle"), inputs)
autostart = self.settings(("threads_autostart", "autostart"), inputs)
has_show_errors = isinstance(show_errors, bool)
error = (
((
1 << 0 if method == None else
1 << 1 if not callable(method) else
0) << 0) |
((
1 << 0 if start_now == None else
1 << 1 if not isinstance(start_now, bool) else
0) << 2) |
((
1 << 0 if minimum_timing == None else
1 << 1 if not isinstance(minimum_timing, (int, float)) else
1 << 2 if minimum_timing < 0 else
0) << 4) |
((
1 << 0 if timing == None else
(
1 << 0 if len(timing) != 2 else
(
((
1 << 0 if timing[0] == None else
1 << 1 if not isinstance(timing[0], (int, float)) else
1 << 2 if timing[0] < 0 else
0) << 1) |
((
1 << 0 if timing[1] == None else
1 << 1 if not isinstance(timing[1], (int, float)) else
1 << 2 if timing[1] < 0 else
0) << 4) |
0) or (
1 << 7 if timing[0] > timing[1] else
0)) << 3 if isinstance(timing, (list, tuple)) else
1 << 1 if not isinstance(timing, (int, float)) else
1 << 2 if timing < 0 else
0) << 7) |
((
1 << 0 if bucle == None else
1 << 1 if not isinstance(bucle, bool) else
0) << 18) |
((
1 << 0 if autostart == None else
1 << 1 if not isinstance(autostart, bool) else
0) << 20) |
0) << 1
i = None
if not error:
try:
i = 0
l = len(self.__threads)
thread = {
"working" : True,
"start_now" : start_now,
"minimum_timing" : minimum_timing,
"timing" : timing,
"bucle" : bucle,
"method" : method,
"autostart" : autostart,
"thread" : Thread(target = lambda:self.__execute_threads(thread)),
"executions" : 0,
"i" : 0
}
while thread["i"] < l:
if self.__threads[thread["i"]] == None:
break
thread["i"] += 1
if thread["i"] == l:
self.__threads += [thread]
else:
self.__threads[thread["i"]] = thread
i = thread["i"]
autostart and thread["thread"].start()
except Exception as exception:
error |= 1 << 0
self.exception(exception, self.__show_add_thread_exception and "add_thread_exception", {"i" : i})
self.validate(
error,
(
"exception",
"method_null",
"method_not_function",
"start_now_null",
"start_now_not_boolean",
"minimum_timing_null",
"minimum_timing_not_float",
"minimum_timing_lower_0",
"timing_null",
"timing_not_float",
"timing_lower_0",
"timing_not_2",
"timing_min_null",
"timing_min_not_float",
"timing_min_lower_0",
"timing_max_null",
"timing_max_not_float",
"timing_max_lower_0",
"timing_min_greater_max",
"bucle_null",
"bucle_not_boolean",
"autostart_null",
"autostart_not_boolean"
),
{"i" : i},
(show_errors if has_show_errors else self.__show_add_thread_error) and "add_thread_error",
(show_errors if has_show_errors else True) and self.__show_add_thread_ok and "add_thread_ok"
)
self.__threads_changing = False
return (i, error)
def remove_thread(self, i, show_errors = None):
error = (
1 << 0 if i == None else
1 << 1 if not isinstance(i, int) else
1 << 2 if i < 0 else
1 << 3 if i >= len(self.__threads) else
1 << 4 if not self.__threads[i] else
0) << 1
has_show_errors = isinstance(show_errors, bool)
if not error:
self.__threads[i]["working"] = False
self.validate(
error,
(
"exception",
"thread_i_null",
"thread_i_not_integer",
"thread_i_lower_0",
"thread_i_too_high",
"thread_i_deleted"
),
{"i" : i},
(show_errors if has_show_errors else self.__show_remove_thread_error) and "remove_thread_error",
(show_errors if has_show_errors else True) and self.__show_remove_thread_ok and "remove_thread_ok"
)
return error
@classmethod
def string_variables(self, string, variables = None, default = None):
# print(variables)
variables = [_set for _set in (variables if isinstance(variables, (list, tuple)) else [variables]) if isinstance(_set, dict)]
# print(variables)
def callback(matches):
key = matches.group(1)
for _set in variables:
if key in _set:
return str(_set[key])
return str(default) if default != None else matches.group(0)
return self.re_string_variables.sub(callback, str(string))
@staticmethod
def get_action_data(i = 1):
stack = get_stack()[1 + i]
return {
"file" : stack.filename,
"method" : stack.function,
"line" : stack.lineno
}
def default_text(self, default = None):
return default if default != None else self.__default_text
def __get_i18n(self, keys, default):
keys = [key.strip() for key in (keys if isinstance(keys, (list, tuple)) else [keys]) if isinstance(key, str) and len(keys.strip())]
if len(keys):
used = []
for language in (self.__language, self.__default_language) + tuple(self.__sentences.keys()):
if language and language in self.__sentences and language not in used:
used += [language]
for key in keys:
if key in self.__sentences[language]:
return self.__sentences[language][key]
return default if default != None else keys[0]
return self.default_text(default)
def i18n(self, keys, variables = None, default = None):
return self.string_variables(self.__get_i18n(keys, default), variables)
def get_print_type(self, _type):
_type = _type.lower()
for _types in self.__print_types:
for key in _types:
if _type == key.lower():
return _types[0]
self.__print_types[0][0]
def _print(self, _type, message, variables = None, default = None, i = 0):
date = datetime.now()
_set = {
"type" : self.get_print_type(_type),
"raw_type" : _type,
"i18n" : message,
**self.get_action_data(i + 1)
}
variables = list(variables) if isinstance(variables, (list, tuple)) else [variables]
for key in ("year", "month", "day", "hour", "minute", "second"):
k = "i" if key == "minute" else key[0]
_set[key] = getattr(date, key)
_set[k] = _set[key]
_set[k + k] = ("00" + str(_set[k]))[-2:]
_set["yyyy"] = _set["year"]
_set["message"] = self.i18n(message, variables + [_set], default)
print(self.string_variables(self.__print_format, variables + [_set], default))
def exception(self, exception, message = None, variables = None, i = 1):
lines = extract_traceback(exception.__traceback__).format()
line_matches = self.re_exception_line.match(lines[-1])
data = {
**(variables if isinstance(variables, dict) else {}),
**self.get_action_data(1),
"lines" : "",
"exception_message" : str(exception),
**({
"method" : line_matches.group(3),
"line" : line_matches.group(2),
"file" : line_matches.group(1)
} if line_matches else {
"method" : "UNKNOWN",
"line" : -1,
"file" : "UNKNOWN"
})
}
for block in trace_format_stack()[:-2] + lines:
if block:
data["lines"] += "\n " + self.re_break_lines.split(block.strip())[0]
message and self._print("exception", message, data, None, i + 1)
def validate(self, code, messages = [], variables = None, error_message = None, ok_message = None, k = 1):
variables = {
**(variables if isinstance(variables, dict) else {}),
"code" : code,
"list" : ""
}
if code:
i = 0
l = len(messages) if isinstance(messages, (list, tuple)) else 0
has_i18n = hasattr(self, "i18n")
while 1 << i <= code:
if code & (1 << i):
message = messages[i] if i < l else "error_message_" + str(i)
variables["list"] += "\n [" + str(i) + "] " + (self.i18n(message, variables) if has_i18n else message)
i += 1
if error_message:
self._print("warn", error_message, variables, None, k)
return False
if ok_message:
self._print("ok", ok_message, variables, None, k)
return True
def path_format(self, path):
return self.re_slashes.sub(self.__slash, path)
def get_path(self, path, show_errors = None):
real_path = None
error = (
1 << 0 if path == None else
1 << 1 if not isinstance(path, str) else
1 << 2 if not path else
0) << 1
has_show_errors = isinstance(show_errors, bool)
if not error:
for root in self.__roots_paths:
current_path = self.path_format((root + "/" if root else "") + path)
if path_exists(current_path):
real_path = current_path
break
if real_path == None:
error |= 1 << 4
self.validate(
error,
(
"exception",
"path_null",
"path_not_string",
"path_empty",
"path_not_exists"
),
{
"path" : path,
"real_path" : real_path
},
(show_errors if has_show_errors else self.__show_get_path_error) and "get_path_error",
(show_errors if has_show_errors else True) and self.__show_get_path_ok and "get_path_ok"
)
return (real_path, error)
def make_directory(self, path, show_errors = None):
error = (
1 << 0 if path == None else
1 << 1 if not isinstance(path, str) else
1 << 2 if not path else
0) << 1
has_show_errors = isinstance(show_errors, bool)
if not error:
try:
directory = ""
if path[0] != "/":
directory = path[0:1]
path = path[2:]
for level in self.re_slashes.split(self.re_directory.sub(r'\1', path)):
directory += self.__slash + level
not path_exists(directory) and make_directories(directory)
except Exception as exception:
error |= 1 << 0
self.exception(exception, self.__show_make_directory_exception and "make_directory_exception", {
"path" : path
})
self.validate(
error,
(
"exception",
"path_null",
"path_not_string",
"path_empty"
),
{"path" : path},
(show_errors if has_show_errors else self.__show_make_directory_error) and "make_directory_error",
(show_errors if has_show_errors else True) and self.__show_make_directory_ok and "make_directory_ok"
)
return error
@staticmethod
def __table_exists(cursor, name):
cursor.execute("select * from sqlite_master where type = 'table' and name = '" + name + "' limit 1")
results = cursor.fetchall()
return len(results)
@staticmethod
def __column_exists(cursor, table, name):
cursor.execute("pragma table_info('" + table + "')")
results = cursor.fetchall()
if len(results):
for row in results:
if dict(row)["name"] == name:
return True
return False
@staticmethod
def __foreign_exists(cursor, table, name, foreign):
cursor.execute("pragma foreign_key_list('" + table + "')")
results = cursor.fetchall()
if len(results):
for row in results:
row = dict(row)
if row["table"] == foreign and row["from"] == name:
return True
return False
@classmethod
def pascal_to_snake(self, name):
def callback(matches):
return "_" + matches.group(1).lower()
return self.re_pascal_capitals.sub(callback, name)[1:]
@classmethod
def __table_create(self, cursor, name, fields, foreigns = []):
key = self.pascal_to_snake(name)
if self.__table_exists(cursor, name):
for field in fields:
key = self.re_field_key.sub(r'\1', field)
if not self.__column_exists(cursor, name, key):
cursor.execute("alter table " + name + " add column " + field.replace(" not null", ""))
for field, foreign in foreigns:
if not self.__foreign_exists(cursor, name, field, foreign):
cursor.execute("alter table " + name + " add constraint " + key + "_" + field + " foreign key(" + field + ") references " + foreign + "(id)")
else:
cursor.execute("create table if not exists " + name + "(" +
"id integer not null primary key autoincrement, " +
"".join([field + ", " for field in fields]) +
"date_in datetime not null default (datetime('now')), " +
"date_out datetime" +
"".join([", constraint " + key + "_" + field + " foreign key(" + field + ") references " + foreign + "(id)" for field, foreign in foreigns]) +
")")
def __get_database_connection(self, show_errors = None):
error = (
(1 << 0 if self.__error else 0) |
0) << 1
connection = None
has_show_errors = isinstance(show_errors, bool)
if not error:
self.make_directory(self.__database_path)
try:
connection = sqlite3.connect(self.__database_path)
connection.row_factory = sqlite3.Row
except Exception as exception:
self.exception(exception, self.__show_get_database_connection_exception and "get_database_connection_exception", {
"path" : self.__database_path
})
self.validate(
error,
(
"exception",
"settings_has_errors"
),
{},
(show_errors if has_show_errors else self.__show_get_database_connection_error) and "get_database_connection_error",
(show_errors if has_show_errors else True) and self.__show_get_database_connection_ok and "get_database_connection_ok"
)
return (connection, error)
def __close_database_connection(self, connection, has_errors, show_errors = None):
error = (
((
1 << 0 if connection == None else
1 << 1 if not isinstance(connection, sqlite3.Connection) else
0) << 0) |
((
1 << 0 if has_errors == None else
1 << 1 if not isinstance(has_errors, (bool, int)) else
0) << 2) |
0) << 1
has_show_errors = isinstance(show_errors, bool)
if not error:
try:
if has_errors:
try:connection.rollback()
except:pass
else:
connection.commit()
try:connection.close()
except:pass
except Exception as exception:
self.exception(exception, self.__show_close_database_connection_exception and "close_database_connection_exception", {
"path" : self.__database_path
})
self.validate(
error,
(
"exception",
"connection_null",
"connection_bad_typed",
"has_errors_null",
"has_errors_bad_typed"
),
{},
(show_errors if has_show_errors else self.__show_close_database_connection_error) and "close_database_connection_error",
(show_errors if has_show_errors else True) and self.__show_close_database_connection_ok and "close_database_connection_ok"
)
return error
def __build_database(self, show_errors = None):
error = 0
has_show_errors = isinstance(show_errors, bool)
for name, fields, foreigns in (
(
"Hosts", [
"name varchar(32) not null"
], []
),
(
"Sessions", [
"host integer not null",
"samples integer not null",
"date_last datetime not null default (datetime('now'))"
], [
("host", "Hosts")
]
),
(
"Sites", [
"site varchar(128) not null"
], []
),
(
"Pings", [
"session integer not null",
"site integer not null",
"sequence integer not null",
"i integer not null",
"time float not null",
"date datetime not null"
], [
("session", "Sessions"),
("site", "Sites")
]
),
(
"RawPings", [
"session integer not null",
"site integer not null",
"date_from datetime not null",
"date_to datetime not null",
"sequence integer not null",
"data text not null"
], [
("session", "Sessions"),
("site", "Sites")
]
)
):
connection, suberror = self.__get_database_connection()
if not suberror:
error |= suberror
try:
self.__table_create(connection.cursor(), name, fields, foreigns)
except Exception as exception:
error |= 1 << 2
self.exception(exception, self.__show_build_database_exception and "build_database_exception", {
"path" : self.__database_path
})
finally:
self.__close_database_connection(connection, error)
self.validate(
error,
(
"exception",
"settings_has_errors"
),
{},
(show_errors if has_show_errors else self.__show_build_database_error) and "build_database_error",
(show_errors if has_show_errors else True) and self.__show_build_database_ok and "build_database_ok"
)
return error
@classmethod
def sql_string_fix(self, string):
return self.re_sql_semicolons.sub(r'\'\'', string)
@staticmethod
def sql_datetime(date):
if isinstance(date, float):
date = datetime.fromtimestamp(date)
return "'" + (
("0000" + str(date.year))[-4:] + "-" +
("00" + str(date.month))[-2:] + "-" +
("00" + str(date.day))[-2:] + " " +
("00" + str(date.hour))[-2:] + ":" +
("00" + str(date.minute))[-2:] + ":" +
("00" + str(date.second))[-2:]
) + "'"
def __update_database(self, show_errors = None):
raw_pings_l = len(self.__cache["raw_pings"])
pings_l = len(self.__cache["pings"])
if not (raw_pings_l or pings_l):
return 0
connection, error = self.__get_database_connection()
has_show_errors = isinstance(show_errors, bool)
if not error:
try:
cursor = connection.cursor()
session_id = None
sites_id = {}
if self.__session == None:
host_id = cursor.execute("select id from Hosts where date_out is null and name = '" + self.__host_id + "' limit 1").fetchall()
if len(host_id):
host_id = int(dict(host_id[0])["id"])
else:
cursor.execute("insert into Hosts(name) values('" + self.__host_id + "')")
host_id = cursor.lastrowid
cursor.execute("insert into Sessions(host, samples) values(" + str(host_id) + ", " + str(self.__samples) + ")")
session_id = cursor.lastrowid
for ping in self.__cache["raw_pings" if raw_pings_l else "pings"]:
if ping["site"] not in sites_id:
sites_id[ping["site"]] = cursor.execute("select id from Sites where date_out is null and site = '" + ping["site"] + "' limit 1").fetchall()
if len(sites_id[ping["site"]]):
sites_id[ping["site"]] = int(dict(sites_id[ping["site"]][0])["id"])
else:
cursor.execute("insert into Sites(site) values('" + ping["site"] + "')")
sites_id[ping["site"]] = cursor.lastrowid
if raw_pings_l:
if self.__allow_save_raw_ping:
cursor.execute("insert into RawPings('session', site, date_from, date_to, sequence, 'data') values " + ", ".join([
"(" + str(session_id) + ", " + str(sites_id[ping["site"]]) + ", " + self.sql_datetime(ping["from"]) + ", " + self.sql_datetime(ping["to"]) + ", " + str(ping["sequence"]) + ", '" + self.sql_string_fix("\n".join(ping["raw_lines"])) + "')" for ping in self.__cache["raw_pings"][0:raw_pings_l - 1]
]))
self.__cache["raw_pings"] = self.__cache["raw_pings"][raw_pings_l:]
if pings_l:
cursor.execute("insert into Pings('session', site, sequence, i, 'time', 'date') values " + ", ".join([
"(" + str(session_id) + ", " + str(sites_id[ping["site"]]) + ", " + str(ping["sequence"]) + ", " + str(ping["i"]) + ", " + str(ping["time"]) + ", " + self.sql_datetime(ping["date"]) + ")" for ping in self.__cache["pings"][0:pings_l - 1]
]))
self.__cache["pings"] = self.__cache["pings"][pings_l:]
except Exception as exception:
error |= 1 << 4
self.exception(exception, "sqlite_connection_error", {
"path" : self.__database_path
})
finally:
self.__close_database_connection(connection, error)
self.validate(
error,
(
"exception",
"settings_has_errors"
),
{},
(show_errors if has_show_errors else self.__show_update_database_error) and "update_database_error",
(show_errors if has_show_errors else True) and self.__show_update_database_ok and "update_database_ok"
)
return error
internet_checker = InternetChecker(settings)