mirror of
https://github.com/LorenEteval/Furious.git
synced 2026-09-22 23:08:08 +03:00
129 lines
3.8 KiB
Python
129 lines
3.8 KiB
Python
# Copyright (C) 2024–present Loren Eteval & contributors <loren.eteval@proton.me>
|
||
#
|
||
# This file is part of Furious.
|
||
#
|
||
# This program is free software: you can redistribute it and/or modify
|
||
# it under the terms of the GNU General Public License as published by
|
||
# the Free Software Foundation, either version 3 of the License, or
|
||
# (at your option) any later version.
|
||
#
|
||
# This program is distributed in the hope that it will be useful,
|
||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
# GNU General Public License for more details.
|
||
#
|
||
# You should have received a copy of the GNU General Public License
|
||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||
|
||
"""Provide process utilities for app main process."""
|
||
|
||
from __future__ import annotations
|
||
|
||
from Furious.Frozenlib import *
|
||
from Furious.Interface import *
|
||
|
||
from typing import Callable
|
||
|
||
import os
|
||
import sys
|
||
import signal
|
||
import logging
|
||
import datetime
|
||
import operator
|
||
import functools
|
||
import traceback
|
||
import multiprocessing
|
||
|
||
__all__ = ['AppMainProcess']
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
if PLATFORM == 'Windows':
|
||
ProcessContext = multiprocessing
|
||
else:
|
||
ProcessContext = multiprocessing.get_context('spawn')
|
||
|
||
|
||
class AppMainProcess(ProcessContext.Process):
|
||
"""Represent app main process."""
|
||
|
||
def __init__(self, func: Callable[[], ApplicationFactory], **kwargs):
|
||
"""Initialize the AppMainProcess."""
|
||
super().__init__(**kwargs)
|
||
|
||
self.startupTime = str(datetime.datetime.now()).replace(':', '')
|
||
self.logFileName = f'{self.startupTime}.log'
|
||
self.fileWritten = multiprocessing.Manager().Value('b', False)
|
||
|
||
self.func = func
|
||
self.application = None
|
||
|
||
def exceptHook(self, exceptionType, exceptionValue, tb):
|
||
"""Handle except hook for the app main process."""
|
||
if logger.level < logging.CRITICAL:
|
||
traceback.print_exception(exceptionType, exceptionValue, tb)
|
||
|
||
if isinstance(exceptionValue, AssertionError):
|
||
exitcode = ApplicationFactory.ExitCode.AssertionError.value
|
||
else:
|
||
exitcode = ApplicationFactory.ExitCode.UnknownException.value
|
||
|
||
logger.error(f'stopped with exitcode {exitcode}')
|
||
|
||
self.saveCrashLog(exceptionType, exceptionValue, tb)
|
||
|
||
if APP() is not None:
|
||
APP().exit(exitcode)
|
||
else:
|
||
sys.exit(exitcode)
|
||
|
||
def saveCrashLog(self, exceptionType, exceptionValue, tb):
|
||
"""Save crash log."""
|
||
try:
|
||
os.mkdir(CRASH_LOG_DIR)
|
||
except FileExistsError:
|
||
# Directory already exists
|
||
|
||
pass
|
||
except Exception:
|
||
# Any non-exit exceptions
|
||
|
||
return
|
||
|
||
try:
|
||
stackLog = functools.reduce(
|
||
operator.add,
|
||
traceback.format_exception(exceptionType, exceptionValue, tb),
|
||
)
|
||
|
||
if APP() is None:
|
||
crashLog = f'{stackLog}'
|
||
else:
|
||
crashLog = f'{AppLoggerWindow.Self().plainText()}\n{stackLog}'
|
||
|
||
with open(CRASH_LOG_DIR / self.logFileName, 'w', encoding='utf-8') as file:
|
||
file.write(crashLog)
|
||
|
||
self.fileWritten.value = True
|
||
except Exception:
|
||
# Any non-exit exceptions
|
||
|
||
pass
|
||
|
||
def handler(self, signum, frame):
|
||
"""Handle r."""
|
||
logger.info(f'received signal {signal.Signals(signum).name}. Exit application')
|
||
|
||
self.application.exit()
|
||
|
||
def run(self):
|
||
"""Run the app main process task."""
|
||
sys.excepthook = self.exceptHook
|
||
|
||
self.application = self.func()
|
||
|
||
for sig in [signal.SIGTERM, signal.SIGINT]:
|
||
signal.signal(sig, self.handler)
|
||
|
||
sys.exit(self.application.run())
|