mirror of
https://github.com/naruxde/revpicommander.git
synced 2025-11-08 16:43:53 +01:00
feat: Upgrade proginit to version 1.3.0
This commit is contained in:
@@ -17,14 +17,14 @@ if __package__ == "":
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
if len(sys.argv) == 2 and "--version" in sys.argv:
|
try:
|
||||||
# Catch --version, if this is the only argument (sys.argv[0] is always the script name)
|
# Use absolut import in the __main__ module
|
||||||
from revpicommander import __version__
|
|
||||||
print(__version__)
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
else:
|
|
||||||
from revpicommander.revpicommander import main
|
from revpicommander.revpicommander import main
|
||||||
|
|
||||||
# Run the main application of this package
|
# Run the main application of this package
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
sys.stdout.write(f"Can not start __main__ module: {e}")
|
||||||
|
sys.stdout.write("\n")
|
||||||
|
sys.exit(1)
|
||||||
|
|||||||
@@ -1,45 +1,118 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""Global program initialization."""
|
"""Global program initialization."""
|
||||||
|
# SPDX-FileCopyrightText: 2018-2023 Sven Sager
|
||||||
|
# SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
__author__ = "Sven Sager"
|
__author__ = "Sven Sager"
|
||||||
__copyright__ = "Copyright (C) 2023 Sven Sager"
|
__copyright__ = "Copyright (C) 2018-2023 Sven Sager"
|
||||||
__license__ = "GPLv2"
|
__license__ = "LGPL-2.0-or-later"
|
||||||
|
__version__ = "1.3.0"
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
from os import R_OK, W_OK, access
|
from os import R_OK, W_OK, access, environ, getpid, remove
|
||||||
from os.path import abspath, dirname, join
|
from os.path import abspath, dirname, exists, join
|
||||||
|
from shutil import copy, move
|
||||||
|
from socket import AF_UNIX, SOCK_DGRAM, socket
|
||||||
|
from threading import Event
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Import program version from meta data module of your program
|
||||||
|
from . import __version__ as external_version
|
||||||
|
except Exception:
|
||||||
|
external_version = None
|
||||||
|
|
||||||
# Program name
|
# Program name
|
||||||
programname = "revpicommander"
|
programname = "revpicommander"
|
||||||
|
program_version = external_version
|
||||||
|
|
||||||
# Set to True, if you want to save config file
|
conf_rw = False # If you want so save the configuration with .save_conf() set to True
|
||||||
conf_rw = False
|
conf_rw_save = False # Create new conf file in same directory and move to old one
|
||||||
|
conf_rw_backup = False # Keep a backup of old conf file [filename].bak
|
||||||
|
_extend_daemon_startup_timeout = 0.0 # Default startup timeout is 90 seconds
|
||||||
|
|
||||||
conf = ConfigParser()
|
conf = ConfigParser()
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
pidfile = "/var/run/{0}.pid".format(programname)
|
pidfile = "/var/run/{0}.pid".format(programname)
|
||||||
|
_daemon_started_up = Event()
|
||||||
|
_daemon_main_pid = getpid()
|
||||||
|
_systemd_notify = environ.get("NOTIFY_SOCKET", None)
|
||||||
|
if _systemd_notify:
|
||||||
|
# Set up the notification socket for systemd communication
|
||||||
|
_systemd_socket = socket(family=AF_UNIX, type=SOCK_DGRAM)
|
||||||
|
if _extend_daemon_startup_timeout:
|
||||||
|
# Extend systemd TimeoutStartSec by defined timeout extension in micro seconds
|
||||||
|
_systemd_socket.sendto(
|
||||||
|
f"EXTEND_TIMEOUT_USEC={_extend_daemon_startup_timeout * 1000000}\n",
|
||||||
|
_systemd_notify,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def can_be_forked():
|
||||||
|
"""
|
||||||
|
Check the possibility of forking the process.
|
||||||
|
|
||||||
|
Under certain circumstances, a process cannot be forked. These include
|
||||||
|
certain build settings or packaging, as well as the missing function on
|
||||||
|
some operating systems.
|
||||||
|
|
||||||
|
:return: True, if forking is possible
|
||||||
|
"""
|
||||||
|
from sys import platform
|
||||||
|
|
||||||
|
# Windows operating system does not support the .fork() call
|
||||||
|
if platform.startswith("win"):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# A PyInstaller bundle does not support the .fork() call
|
||||||
|
if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def cleanup():
|
def cleanup():
|
||||||
"""Clean up program."""
|
"""
|
||||||
|
Clean up before exit the program.
|
||||||
|
|
||||||
|
This function must be called at the end of the program. It flushes
|
||||||
|
the logging buffers and deletes the PID file in daemon mode.
|
||||||
|
"""
|
||||||
|
if pargs.daemon and exists(pidfile):
|
||||||
|
remove(pidfile)
|
||||||
|
|
||||||
# Shutdown logging system
|
# Shutdown logging system
|
||||||
logging.shutdown()
|
logging.shutdown()
|
||||||
|
|
||||||
|
# Close logfile
|
||||||
|
if pargs.daemon:
|
||||||
|
sys.stdout.close()
|
||||||
|
|
||||||
|
|
||||||
def reconfigure_logger():
|
def reconfigure_logger():
|
||||||
"""Configure logging module of program."""
|
"""Configure logging module of program."""
|
||||||
|
|
||||||
# Clear all log handler
|
# Clear all log handler
|
||||||
for lhandler in logger.handlers.copy():
|
for lhandler in logger.handlers.copy():
|
||||||
lhandler.close()
|
lhandler.close()
|
||||||
logger.removeHandler(lhandler)
|
logger.removeHandler(lhandler)
|
||||||
|
|
||||||
|
if pargs.daemon:
|
||||||
|
# Create daemon log file
|
||||||
|
fh_logfile = open("/var/log/{0}.log".format(programname), "a")
|
||||||
|
|
||||||
|
# Close stdout and use logfile
|
||||||
|
sys.stdout.close()
|
||||||
|
sys.stdout = fh_logfile
|
||||||
|
sys.stderr = sys.stdout
|
||||||
|
|
||||||
# Create new log handler
|
# Create new log handler
|
||||||
logformat = logging.Formatter(
|
if pargs.verbose > 2:
|
||||||
"{asctime} [{levelname:8}] {message}",
|
log_frm = "{asctime} [{levelname:8}] {name} {message}"
|
||||||
datefmt="%Y-%m-%d %H:%M:%S", style="{"
|
else:
|
||||||
)
|
log_frm = "{asctime} [{levelname:8}] {message}"
|
||||||
|
logformat = logging.Formatter(log_frm, datefmt="%Y-%m-%d %H:%M:%S", style="{")
|
||||||
lhandler = logging.StreamHandler(sys.stdout)
|
lhandler = logging.StreamHandler(sys.stdout)
|
||||||
lhandler.setFormatter(logformat)
|
lhandler.setFormatter(logformat)
|
||||||
logger.addHandler(lhandler)
|
logger.addHandler(lhandler)
|
||||||
@@ -60,53 +133,187 @@ def reconfigure_logger():
|
|||||||
logger.setLevel(loglevel)
|
logger.setLevel(loglevel)
|
||||||
|
|
||||||
|
|
||||||
def reload_conf():
|
def reload_conf(clear_load=False) -> None:
|
||||||
"""Reload config file."""
|
"""
|
||||||
if "conffile" in pargs:
|
Reload config file.
|
||||||
|
|
||||||
|
After successful reload, call set_startup_complete() function to inform
|
||||||
|
systemd that all functions are available again.
|
||||||
|
|
||||||
|
If keys are commented out in conf file, they will still be in the conf file.
|
||||||
|
To remove not existing keys set clear_load to True.
|
||||||
|
|
||||||
|
:param clear_load: Clear conf before reload
|
||||||
|
"""
|
||||||
|
if _systemd_notify:
|
||||||
|
# Inform systemd about reloading configuration
|
||||||
|
_systemd_socket.sendto(b"RELOADING=1\n", _systemd_notify)
|
||||||
|
|
||||||
|
# Reset started up event for the set_startup_complete function
|
||||||
|
_daemon_started_up.clear()
|
||||||
|
|
||||||
|
if "conffile" in pargs:
|
||||||
# Check config file
|
# Check config file
|
||||||
if not access(pargs.conffile, R_OK):
|
if not access(pargs.conffile, R_OK):
|
||||||
|
raise RuntimeError("can not access config file '{0}'".format(pargs.conffile))
|
||||||
|
if conf_rw:
|
||||||
|
if (conf_rw_save or conf_rw_backup) and not access(dirname(pargs.conffile), W_OK):
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"can not access config file '{0}'".format(pargs.conffile)
|
"can not wirte to directory '{0}' to create files"
|
||||||
)
|
"".format(dirname(pargs.conffile))
|
||||||
if conf_rw and not access(pargs.conffile, W_OK):
|
|
||||||
raise RuntimeError(
|
|
||||||
"can not write to config file '{0}'".format(pargs.conffile)
|
|
||||||
)
|
)
|
||||||
|
if not access(pargs.conffile, W_OK):
|
||||||
|
raise RuntimeError("can not write to config file '{0}'".format(pargs.conffile))
|
||||||
|
|
||||||
# Create global config
|
if clear_load:
|
||||||
global conf
|
# Clear all sections and do not create a new instance
|
||||||
|
for section in conf.sections():
|
||||||
|
conf.remove_section(section)
|
||||||
|
|
||||||
|
# Read configuration
|
||||||
logger.info("loading config file: {0}".format(pargs.conffile))
|
logger.info("loading config file: {0}".format(pargs.conffile))
|
||||||
conf.read(pargs.conffile)
|
conf.read(pargs.conffile)
|
||||||
|
|
||||||
|
|
||||||
|
def save_conf():
|
||||||
|
"""Save configuration."""
|
||||||
|
if not conf_rw:
|
||||||
|
raise RuntimeError("You have to set conf_rw to True.")
|
||||||
|
if "conffile" in pargs:
|
||||||
|
if conf_rw_backup:
|
||||||
|
copy(pargs.conffile, pargs.conffile + ".bak")
|
||||||
|
if conf_rw_save:
|
||||||
|
with open(pargs.conffile + ".new", "w") as fh:
|
||||||
|
conf.write(fh)
|
||||||
|
move(pargs.conffile + ".new", pargs.conffile)
|
||||||
|
else:
|
||||||
|
with open(pargs.conffile, "w") as fh:
|
||||||
|
conf.write(fh)
|
||||||
|
|
||||||
|
|
||||||
|
def startup_complete():
|
||||||
|
"""
|
||||||
|
Call this when the daemon is completely started.
|
||||||
|
|
||||||
|
When a daemon is started, it may take some time for everything to be
|
||||||
|
available. This function notifies the init system when all functions of
|
||||||
|
this daemon are available so that the starts of further daemons can be
|
||||||
|
properly timed.
|
||||||
|
|
||||||
|
The systemd unit file that is supposed to start this demon must be set
|
||||||
|
to 'Type=notify'. If the daemon supports reloading the settings,
|
||||||
|
'ExecReload=/bin/kill -HUP $MAINPID' must also be set. The daemon must
|
||||||
|
call this function again after the reload in order to signal systemd the
|
||||||
|
completed reload.
|
||||||
|
|
||||||
|
If systemd is available from version 250 and the daemon supports reloading
|
||||||
|
the settings, 'Type=notify-reload' can be used without 'ExecReload'. The
|
||||||
|
type 'notify-reload' is preferable if possible, as the reloading of the
|
||||||
|
daemon is also synchronized with systemd.
|
||||||
|
|
||||||
|
If the '--fork' parameter is used, the main process ends after calling
|
||||||
|
this function to prevent the further start of demons by other init systems.
|
||||||
|
"""
|
||||||
|
if _daemon_started_up.is_set():
|
||||||
|
# Everyone was notified about complete start, if set
|
||||||
|
return
|
||||||
|
|
||||||
|
if _systemd_notify:
|
||||||
|
# Inform systemd about complete startup of daemon process
|
||||||
|
_systemd_socket.sendto(b"READY=1\n", _systemd_notify)
|
||||||
|
|
||||||
|
if pargs.daemon:
|
||||||
|
from os import kill
|
||||||
|
|
||||||
|
# Send SIGTERM signal to main process
|
||||||
|
kill(_daemon_main_pid, 15)
|
||||||
|
|
||||||
|
_daemon_started_up.set()
|
||||||
|
|
||||||
|
|
||||||
# Generate command arguments of the program
|
# Generate command arguments of the program
|
||||||
parser = ArgumentParser(
|
parser = ArgumentParser(
|
||||||
prog=programname,
|
prog=programname,
|
||||||
description="Program description"
|
# todo: Add program description for help
|
||||||
|
description="Program description",
|
||||||
|
)
|
||||||
|
parser.add_argument("--version", action="version", version=f"%(prog)s {program_version}")
|
||||||
|
parser.add_argument(
|
||||||
|
"-f",
|
||||||
|
"--logfile",
|
||||||
|
dest="logfile",
|
||||||
|
help="save log entries to this file",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-f", "--logfile", dest="logfile",
|
"-v",
|
||||||
help="Save log entries to this file"
|
"--verbose",
|
||||||
|
action="count",
|
||||||
|
dest="verbose",
|
||||||
|
default=0,
|
||||||
|
help="switch on verbose logging",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
# If packed with opensource licenses, add argument to print license information about bundled modules
|
||||||
"-v", "--verbose", action="count", dest="verbose", default=0,
|
open_source_licenses = join(dirname(__file__), "open-source-licenses", "open-source-licenses.txt")
|
||||||
help="Switch on verbose logging"
|
if exists(open_source_licenses):
|
||||||
)
|
parser.add_argument(
|
||||||
# The __main__ script will process the version number argument
|
"--open-source-licenses",
|
||||||
parser.add_argument("--version", action="store_true", help="Print version number of program and exit")
|
action="store_true",
|
||||||
|
dest="oss_licenses",
|
||||||
|
help="print packed open-source-licenses and exit",
|
||||||
|
)
|
||||||
pargs = parser.parse_args()
|
pargs = parser.parse_args()
|
||||||
|
|
||||||
|
# Process open-source-licenses argument, if set (only affects bundled apps)
|
||||||
|
if "oss_licenses" in pargs and pargs.oss_licenses:
|
||||||
|
with open(open_source_licenses, "r") as fh:
|
||||||
|
sys.stdout.write(fh.read())
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
# Check important objects and set to default if they do not exists
|
# Check important objects and set to default if they do not exists
|
||||||
|
if "daemon" not in pargs:
|
||||||
|
pargs.daemon = False
|
||||||
if "verbose" not in pargs:
|
if "verbose" not in pargs:
|
||||||
pargs.verbose = 0
|
pargs.verbose = 0
|
||||||
|
|
||||||
|
# Check if the program should run as a daemon
|
||||||
|
if pargs.daemon:
|
||||||
|
# Check if daemon is already running
|
||||||
|
if exists(pidfile):
|
||||||
|
logger.error("Program already running as daemon. Check '{0}'".format(pidfile))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Fork to daemon
|
||||||
|
from os import fork
|
||||||
|
|
||||||
|
pid = fork()
|
||||||
|
if pid > 0:
|
||||||
|
# Main process waits for exit till startup is complete
|
||||||
|
from os import kill
|
||||||
|
from signal import SIGKILL, SIGTERM, signal
|
||||||
|
|
||||||
|
# Catch the TERM signal, which will be sent from the forked process after startup_complete
|
||||||
|
signal(SIGTERM, lambda number, frame: _daemon_started_up.set())
|
||||||
|
|
||||||
|
# Use the default timeout of 90 seconds from systemd also for the '--daemon' flag
|
||||||
|
if not _daemon_started_up.wait(90.0 + _extend_daemon_startup_timeout):
|
||||||
|
sys.stderr.write(
|
||||||
|
"Run into startup complete timout! Killing fork and exit main process\n"
|
||||||
|
)
|
||||||
|
kill(pid, SIGKILL)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Main process writes pidfile with pid of forked process
|
||||||
|
with open(pidfile, "w") as f:
|
||||||
|
f.write(str(pid))
|
||||||
|
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
# Get absolute paths
|
# Get absolute paths
|
||||||
pwd = abspath(".")
|
pwd = abspath(".")
|
||||||
|
|
||||||
# Configure logger
|
# Configure logger
|
||||||
if "logfile" in pargs and pargs.logfile is not None \
|
if "logfile" in pargs and pargs.logfile is not None and dirname(pargs.logfile) == "":
|
||||||
and dirname(pargs.logfile) == "":
|
|
||||||
pargs.logfile = join(pwd, pargs.logfile)
|
pargs.logfile = join(pwd, pargs.logfile)
|
||||||
reconfigure_logger()
|
reconfigure_logger()
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander):
|
|||||||
|
|
||||||
self.setWindowFlag(QtCore.Qt.WindowMaximizeButtonHint, False)
|
self.setWindowFlag(QtCore.Qt.WindowMaximizeButtonHint, False)
|
||||||
|
|
||||||
|
pi.startup_complete()
|
||||||
|
|
||||||
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
|
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
|
||||||
log.debug("RevPiCommander.closeEvent")
|
log.debug("RevPiCommander.closeEvent")
|
||||||
helper.cm.pyload_disconnect()
|
helper.cm.pyload_disconnect()
|
||||||
|
|||||||
Reference in New Issue
Block a user