-
Notifications
You must be signed in to change notification settings - Fork 2
/
main.py
336 lines (276 loc) · 12.8 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
#! /usr/bin/env python
# RebornOS Welcome
# Please refer to the file `LICENSE` in the main directory for license information.
# For a high level documentation, please visit https://github.com/RebornOS-Team/rebornos-welcome
# AUTHORS
# 1. shiva.patt ([email protected])
# 2.
# This is the Python entry point of the welcome application
# IMPORTS
from argparse import Namespace
import os
import logging
import argparse
import importlib
import datetime
import subprocess
import pathlib
from typing import Optional
from types import ModuleType
import shutil
from pysetting import JSONConfiguration # For reading and writing settings files
from pyrunning import LogMessage, LoggingHandler, Command
class RebornOSWelcome():
"""
An internal-use class to encapsulate the tasks associated with setting up the
RebornOS Welcome application
"""
@staticmethod
def _recreate_settings_file(user_settings_filepath, is_iso: bool) -> None:
user_settings_filepath.parents[0].mkdir(parents=True, exist_ok=True)
os.chmod(user_settings_filepath.parents[0], 0o755)
if is_iso:
shutil.copy2(
pathlib.Path("configuration/settings_iso.json"),
user_settings_filepath
)
else:
shutil.copy2(
pathlib.Path("configuration/settings.json"),
user_settings_filepath
)
os.chmod(user_settings_filepath, 0o666)
def __init__(self) -> None:
"""
The main function.
The following tasks are accomplished:
- Configure logging
- Set the current working directory,
- Define and parse command line options
- Start the application based on command line options
Parameters
----------
None
Returns
-------
Nothing
"""
print("\nRebornOS Welcome Application")
commandline_arguments = self.handle_arguments() # handle command line arguments
if commandline_arguments.iso:
user_settings_filepath = pathlib.Path.home() / ".rebornos-iso-welcome" / "configuration" / "settings.json"
else:
user_settings_filepath = pathlib.Path.home() / ".rebornos-welcome" / "configuration" / "settings.json"
if not os.path.isfile(user_settings_filepath):
RebornOSWelcome._recreate_settings_file(user_settings_filepath, commandline_arguments.iso)
try:
self.application_settings = JSONConfiguration(
str(user_settings_filepath.resolve())
) # to access the settings stored in 'settings.json'
version = self.application_settings["version"]
if int(version) < 1:
raise Exception("Version too old. The configuration needs to be replaced...")
except Exception as error:
import traceback
traceback.print_exception(type(error), error, error.__traceback__)
try:
print("Recreating configuration...")
RebornOSWelcome._recreate_settings_file(user_settings_filepath, commandline_arguments.iso)
self.application_settings = JSONConfiguration(
str(user_settings_filepath.resolve())
)
except Exception as inner_error:
traceback.print_exception(type(inner_error), inner_error, inner_error.__traceback__)
self.logger = self.setup_logger() # configure the logger
self.logging_handler = LoggingHandler(logger=self.logger)
self.set_current_working_directory() # set the base directory of the welcome application as the current working directory
if commandline_arguments.startup:
if not self.application_settings["auto_start_enabled"]:
LogMessage.Info("Application not enabled to run at startup. Exiting...").write(logging_handler=self.logging_handler)
exit(0)
self.load_UI(commandline_arguments) # load_data the user interface
def setup_logger(self) -> logging.Logger:
"""
Configure the logger
The following tasks are accomplished:
- Create a named logger
- Setup logging to be done onto a file and the console
- Define the format of log entries
- Delete old log files
Parameters
----------
None
Returns
-------
logger: logging.Logger
The logger which has been set up
"""
logger = logging.getLogger('rebornos_welcome') # create a new logger and name it
logger.setLevel(logging.DEBUG) # set it to log anything of debugging and higher alert levels
logger.propagate = False
log_directory_path = []
try:
# Set up file-based logging
log_directory_path = pathlib.Path(
os.path.expanduser(
self.application_settings["log_directory"]
)
)
log_directory_path.mkdir( parents=True, exist_ok=True )
log_file_path = log_directory_path / ("welcome_app-" + RebornOSWelcome.get_time_stamp() + ".log")
if not os.path.isdir(log_directory_path):
raise FileNotFoundError("The directory '" + str(log_directory_path) + "' could not be created.")
elif not os.access(log_directory_path, os.W_OK):
raise PermissionError("The directory '" + str(log_directory_path) + "' does not have write permissions.")
except Exception as error:
import traceback
traceback.print_exception(type(error), error, error.__traceback__)
try:
log_directory_path = pathlib.Path(
os.path.expanduser(
"~/.rebornos-welcome/log/"
)
)
log_directory_path.mkdir( parents=True, exist_ok=True )
log_file_path = log_directory_path / ("welcome_app-" + RebornOSWelcome.get_time_stamp() + ".log")
if not os.path.isdir(log_directory_path):
raise FileNotFoundError("The directory '" + str(log_directory_path) + "' could not be created.")
elif not os.access(log_directory_path, os.W_OK):
raise PermissionError("The directory '" + str(log_directory_path) + "' does not have write permissions.")
except Exception as inner_error:
traceback.print_exception(type(inner_error), inner_error, inner_error.__traceback__)
exit(1)
self.application_settings["log_directory"] = str(log_directory_path)
self.application_settings["current_log_file_path"] = str(log_file_path)
self.application_settings.write_data()
print("Logging to " + str(log_file_path.resolve()) + "...\n")
self.application_settings.write_data()
self.delete_old_log_files(log_directory_path, no_of_files_to_keep =5) # delete old log files
log_file_handler = logging.FileHandler(log_file_path) # for logging onto files
log_file_handler.setLevel(logging.DEBUG) # log debug messages and higher
# log_file_formatter = logging.Formatter('[%(asctime)s, %(levelname)-8s, %(name)s] %(message)s', '%Y-%m-%d, %H:%M:%S %Z') # old format of each log file entry
log_file_formatter = logging.Formatter('%(asctime)s [%(levelname)8s] %(message)s (%(pathname)s > %(funcName)s; Line %(lineno)d)', '%Y-%m-%d %H:%M:%S %Z') # format of each log file entry
log_file_handler.setFormatter(log_file_formatter)
logger.addHandler(log_file_handler)
# Set up standard console logging
log_error_handler = logging.StreamHandler() # for logging onto the console
log_error_handler.setLevel(logging.INFO) # log info messages and higher alert levels
log_error_formatter = logging.Formatter('%(levelname)8s: %(message)s') # format of each console log entry
log_error_handler.setFormatter(log_error_formatter)
logger.addHandler(log_error_handler)
return logger
def set_current_working_directory(self) -> None:
"""
Set the current working directory
The following tasks are accomplished
- Set the current working directory of this script to the directory in which this file exists (i.e. the base directory of the app)
- Store the directory information in a configuration
Parameters
----------
None
Returns
-------
Nothing
"""
working_directory = os.path.dirname( # get the directory's name for the file
os.path.realpath( # get the full path of
__file__ # the current file
)
)
LogMessage.Info("Changing the working directory to " + working_directory + "...").write(self.logging_handler)
os.chdir(working_directory)
self.application_settings["current_working_directory"] = os.getcwd() # Store the current working directory
self.application_settings.write_data() # write pending changes to the configuration file
def handle_arguments(self) -> Namespace:
"""
Define and parse command line options
Parameters
----------
none
Returns
-------
parsed_args: TypedDict
The parsed arguments
"""
argument_parser = argparse.ArgumentParser(description= 'Run the RebornOS Welcome Application.') # define a parser for command line arguments
argument_parser.add_argument( # define a command line argument for selecting UI toolkits
'-ui', '--user_interface',
choices= ["gtk"],
default= "gtk",
help= "specify the UI toolkit."
)
argument_parser.add_argument( # define a command line argument for selecting UI toolkits
'-s', '--startup',
action='store_true',
default=False,
help= "Indicate that the application is being launched at starup"
)
argument_parser.add_argument( # define a command line argument for selecting UI toolkits
'-i', '--iso',
action='store_true',
default=False,
help= "Indicate that the application is being launched from an ISO"
)
parsed_args = argument_parser.parse_args()
return parsed_args
def delete_old_log_files(self, log_directory_path, no_of_files_to_keep: int) -> None:
"""
Delete old log files while keeping the newest ones whose count is specified by "no_of_files_to_keep"
Parameters
----------
no_of_files_to_keep: int
The number of newest log files to keep
Returns
-------
Nothing
"""
subprocess.Popen(
"ls -tp welcome_app* | grep -v '/$' | tail -n +"
+ str(no_of_files_to_keep + 1)
+ " | xargs -I {} rm -- {}",
shell=True,
cwd=log_directory_path
)
def load_UI(self, commandline_arguments: Namespace) -> None:
"""
Loads the appropriate UI.
Loads the appropriate UI based on the UI toolkit name passed as an argument to the program, if any.
The code for launching the UI is assumed to reside in a class called "Main" in user_interface/<ui_toolkit>/code/main.py
commandline_arguments: TypedDict
Command-line arguments parsed using the argparse parse_args() method
"""
ui_module: ModuleType = importlib.import_module(
".".join(
[
"user_interface",
commandline_arguments.user_interface,
"code",
"main"
]
)
) # search for and import user_interface/<ui_toolkit>/code/main.py
LogMessage.Info("Loading the user-interface: " + commandline_arguments.user_interface + "...").write(self.logging_handler)
_ = ui_module.Main(commandline_arguments, self.application_settings) # initialize the Main class of the main script for the chosen user interface toolkit
@staticmethod
def get_time_stamp() -> str:
"""
Returns the timestamp in a particular format
Example: 2020-01-31_23_58_59_GMT
Parameters
----------
None
Returns
-------
timestamp: str
The timestamp in a particular format. Example: 2020-01-31_23_58_59_GMT
"""
time_stamp: str = datetime.datetime.now().strftime("%Y-%m-%d_%H_%M_%S_")
time_zone: Optional[str] = datetime.datetime.now(datetime.timezone.utc).astimezone().tzname()
if time_zone is None:
time_zone = ""
return (
time_stamp + time_zone
)
# THE EXECUTION STARTS HERE
if __name__ == '__main__':
_ = RebornOSWelcome()