1250 lines
53 KiB
Python
1250 lines
53 KiB
Python
|
#!/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)
|