PyELSSA 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- PyELSSA/__init__.py +28 -0
- PyELSSA/configuration.py +325 -0
- PyELSSA/data/TestFile.txt +0 -0
- PyELSSA/data/TestFile1.csv +0 -0
- PyELSSA/data/TestFile2.csv +0 -0
- PyELSSA/gui/gui_base_frame.py +221 -0
- PyELSSA/gui/gui_main_page.py +142 -0
- PyELSSA/gui/gui_start.py +424 -0
- PyELSSA/gui/gui_sub_frame.py +414 -0
- PyELSSA/licensing.py +224 -0
- PyELSSA/master.py +87 -0
- PyELSSA/release_update.py +59 -0
- PyELSSA/shared_contents.py +491 -0
- PyELSSA/shared_msazure_api.py +469 -0
- pyelssa-0.1.0.dist-info/METADATA +44 -0
- pyelssa-0.1.0.dist-info/RECORD +18 -0
- pyelssa-0.1.0.dist-info/WHEEL +4 -0
- pyelssa-0.1.0.dist-info/licenses/LICENSE +88 -0
PyELSSA/master.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
#=================================================================
|
|
4
|
+
# Created by: Jieming Ye
|
|
5
|
+
# Created on: Aug 2026
|
|
6
|
+
# Last Modified: Aug 2026
|
|
7
|
+
#=================================================================
|
|
8
|
+
# Copyright (c) 2026 [Jieming Ye]
|
|
9
|
+
#
|
|
10
|
+
# This Python source code is licensed under the
|
|
11
|
+
# Open Source Non-Commercial License (OSNCL) v1.0
|
|
12
|
+
# See LICENSE for details.
|
|
13
|
+
#=================================================================
|
|
14
|
+
"""
|
|
15
|
+
Pre-requisite:
|
|
16
|
+
N/A
|
|
17
|
+
Used Input:
|
|
18
|
+
N/A
|
|
19
|
+
Expected Output:
|
|
20
|
+
Start GUI
|
|
21
|
+
Description:
|
|
22
|
+
This module is the package entry.
|
|
23
|
+
This module is the only module to be called from User End script directly.
|
|
24
|
+
It ensures GUI is only created in the main process while allowing subprocesses
|
|
25
|
+
to be spawned for other tasks.
|
|
26
|
+
"""
|
|
27
|
+
#=================================================================
|
|
28
|
+
# VERSION CONTROL
|
|
29
|
+
# V1.0 - 2026.08.20 - Jieming Ye - Initial Version
|
|
30
|
+
#=================================================================
|
|
31
|
+
|
|
32
|
+
import multiprocessing
|
|
33
|
+
import sys
|
|
34
|
+
|
|
35
|
+
def is_main_process():
|
|
36
|
+
"""Check if current process is the main process."""
|
|
37
|
+
return multiprocessing.current_process().name == 'MainProcess'
|
|
38
|
+
|
|
39
|
+
def initialize_gui():
|
|
40
|
+
"""Initialize the GUI if license check passes."""
|
|
41
|
+
try:
|
|
42
|
+
# STAGE 1: license check
|
|
43
|
+
from PyELSSA import licensing
|
|
44
|
+
if not licensing.main():
|
|
45
|
+
input("Press any key to exit.....")
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
# STAGE 2: Release message
|
|
49
|
+
from PyELSSA import release_update
|
|
50
|
+
if not release_update.main():
|
|
51
|
+
input("Press any key to exit.....")
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
# STAGE 3: configuration check
|
|
55
|
+
from PyELSSA import configuration
|
|
56
|
+
if not configuration.main():
|
|
57
|
+
input("Press any key to exit.....")
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
# STAGE 4: Launch the GUI
|
|
61
|
+
from PyELSSA.gui import gui_start
|
|
62
|
+
app = gui_start.SampleApp()
|
|
63
|
+
app.mainloop()
|
|
64
|
+
return True
|
|
65
|
+
|
|
66
|
+
except Exception as e:
|
|
67
|
+
print(f"FATAL ERROR: {e}")
|
|
68
|
+
input("Press any key to exit.....")
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
def main():
|
|
72
|
+
"""Main entry point for the application."""
|
|
73
|
+
if not is_main_process():
|
|
74
|
+
# If this is a subprocess, just return without creating GUI
|
|
75
|
+
# have this under this main as this master is usually called from another entering script
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
# Set up multiprocessing to work with frozen executables if needed
|
|
79
|
+
if getattr(sys, 'frozen', False):
|
|
80
|
+
multiprocessing.freeze_support()
|
|
81
|
+
|
|
82
|
+
print('Loading Application...')
|
|
83
|
+
# Initialize GUI in main process only
|
|
84
|
+
initialize_gui()
|
|
85
|
+
|
|
86
|
+
if __name__ == '__main__':
|
|
87
|
+
main()
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#=================================================================
|
|
2
|
+
# Created by: Jieming Ye
|
|
3
|
+
# Created on: June 2026
|
|
4
|
+
# Last Modified: June 2026
|
|
5
|
+
#=================================================================
|
|
6
|
+
# Copyright (c) 2026 [Jieming Ye]
|
|
7
|
+
#
|
|
8
|
+
# This Python source code is licensed under the
|
|
9
|
+
# Open Source Non-Commercial License (OSNCL) v1.0
|
|
10
|
+
# See LICENSE for details.
|
|
11
|
+
#=================================================================
|
|
12
|
+
"""
|
|
13
|
+
only run first time after user update the software
|
|
14
|
+
|
|
15
|
+
# migrate_old_settings()
|
|
16
|
+
# rebuild_cache()
|
|
17
|
+
# show_release_notes()
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
#=================================================================
|
|
21
|
+
# VERSION CONTROL
|
|
22
|
+
# V1.0 (Jieming Ye) - Initial Version
|
|
23
|
+
#=================================================================
|
|
24
|
+
# Set Information Variable
|
|
25
|
+
# N/A
|
|
26
|
+
#=================================================================
|
|
27
|
+
|
|
28
|
+
import os
|
|
29
|
+
import json
|
|
30
|
+
from datetime import date
|
|
31
|
+
|
|
32
|
+
from PyELSSA.shared_contents import SharedVariables, SharedMethods
|
|
33
|
+
from PyELSSA import configuration
|
|
34
|
+
|
|
35
|
+
def main():
|
|
36
|
+
# REQUIRE CHANGE FOR EACH VERSION
|
|
37
|
+
expiry_date = date(2026,8,21)
|
|
38
|
+
|
|
39
|
+
if date.today() <= expiry_date or configuration.version_updated():
|
|
40
|
+
SharedMethods.print_message(f"#############################################################\n","33")
|
|
41
|
+
SharedMethods.print_message(f"Welcome to the new version of ELSSA on Python tool\n","32")
|
|
42
|
+
|
|
43
|
+
# SharedMethods.print_message(f"New features in this version {SharedVariables.installed_version} include:", "32")
|
|
44
|
+
# SharedMethods.print_message(f"* XXXXXXXXXXXXXXX.", "32")
|
|
45
|
+
|
|
46
|
+
# SharedMethods.print_message("Fixed bugs and others include:", "32")
|
|
47
|
+
# SharedMethods.print_message("* Fixed an issue of ....... ", "32")
|
|
48
|
+
|
|
49
|
+
# SharedMethods.print_message(f"This version fixed multiple bugs reported by the user...", "32")
|
|
50
|
+
SharedMethods.print_message(f"\n#############################################################","33")
|
|
51
|
+
|
|
52
|
+
if configuration.version_updated():
|
|
53
|
+
# specific requirement for version update only
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
return True
|
|
57
|
+
|
|
58
|
+
if __name__ == "__main__":
|
|
59
|
+
main()
|
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
#
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
#=================================================================
|
|
4
|
+
# Created by: Jieming Ye
|
|
5
|
+
# Created on: Aug 2026
|
|
6
|
+
# Last Modified: Aug 2026
|
|
7
|
+
#=================================================================
|
|
8
|
+
# Copyright (c) 2026 [Jieming Ye]
|
|
9
|
+
#
|
|
10
|
+
# This Python source code is licensed under the
|
|
11
|
+
# Open Source Non-Commercial License (OSNCL) v1.0
|
|
12
|
+
# See LICENSE for details.
|
|
13
|
+
#=================================================================
|
|
14
|
+
"""
|
|
15
|
+
DataFilenames:
|
|
16
|
+
- Database filename lists
|
|
17
|
+
|
|
18
|
+
Common Variable:
|
|
19
|
+
- Global variable to be used among all scripts
|
|
20
|
+
|
|
21
|
+
Common Methods:
|
|
22
|
+
- Global methods to be used among all scripts
|
|
23
|
+
|
|
24
|
+
Internet Validation:
|
|
25
|
+
- For NR Internet configuration checking only
|
|
26
|
+
|
|
27
|
+
Tee:
|
|
28
|
+
- Consolo control
|
|
29
|
+
|
|
30
|
+
"""
|
|
31
|
+
#=================================================================
|
|
32
|
+
# VERSION CONTROL
|
|
33
|
+
# V1.0 (Jieming Ye) - Initial Version
|
|
34
|
+
#
|
|
35
|
+
#
|
|
36
|
+
#=================================================================
|
|
37
|
+
# Set Information Variable
|
|
38
|
+
# N/A
|
|
39
|
+
#=================================================================
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
#import tkinter as tk
|
|
43
|
+
import os
|
|
44
|
+
import shutil
|
|
45
|
+
import importlib
|
|
46
|
+
import importlib.resources
|
|
47
|
+
import importlib.metadata
|
|
48
|
+
import csv
|
|
49
|
+
import subprocess
|
|
50
|
+
import sys
|
|
51
|
+
import tempfile
|
|
52
|
+
import requests
|
|
53
|
+
import platform
|
|
54
|
+
from datetime import datetime
|
|
55
|
+
from contextlib import contextmanager
|
|
56
|
+
|
|
57
|
+
from collections import Counter
|
|
58
|
+
|
|
59
|
+
class SharedVariables:
|
|
60
|
+
'''
|
|
61
|
+
This class will hold all shared varibles
|
|
62
|
+
'''
|
|
63
|
+
# this class will store all shared varibles
|
|
64
|
+
sim_variable = None # get when be called
|
|
65
|
+
elssa_version = 1 # default update at gui.gui_start.py
|
|
66
|
+
used_database_path = 2 # default update at gui.gui_start.py (would be string either database_path_user or databse_path_default)
|
|
67
|
+
|
|
68
|
+
# varible to be updated following version upgrade:
|
|
69
|
+
# Replace 'your_package_name' with the actual name of your package
|
|
70
|
+
package_name = 'PyELSSA'
|
|
71
|
+
support_name = 'support'
|
|
72
|
+
private_data_name = 'data'
|
|
73
|
+
package_path = importlib.resources.files(package_name)
|
|
74
|
+
elssa_core_path = os.path.join(package_path,"core")
|
|
75
|
+
try:
|
|
76
|
+
installed_version = importlib.metadata.version(package_name)
|
|
77
|
+
except:
|
|
78
|
+
installed_version = '0.0.0'
|
|
79
|
+
public_data_path = "https://github.com/NR-ESTractionPower/public_database"
|
|
80
|
+
raw_data_url_begin = "https://raw.githubusercontent.com/NR-ESTractionPower/public_database/refs/heads/main/"
|
|
81
|
+
|
|
82
|
+
lastupdate = 'Aug / 2026' # date of checking all links below
|
|
83
|
+
copyright = 'CopyRight @ 2026, All Rights Reserved.'
|
|
84
|
+
status_note = '(Beta) PyELSSA is under active development. User is expected to see big changes with this package.'
|
|
85
|
+
|
|
86
|
+
# bhtpbank_path = 'C:\\Users\\Public\\Documents\\VISION\\Resources\\bhtpbank'
|
|
87
|
+
database_path_user = 'C:\\Users\\Public\\Documents\\PyELSSA\\user_data' # This is user controlled library
|
|
88
|
+
database_path_default = 'C:\\Users\\Public\\Documents\\PyELSSA\\default_data' # This is forced default data library
|
|
89
|
+
configuration_path = 'C:\\Users\\Public\\Documents\\PyELSSA\\user_config' # This is configuration data library
|
|
90
|
+
|
|
91
|
+
contacts = "Email: 'traction.power@networkrail.co.uk'"
|
|
92
|
+
license_online = "https://raw.githubusercontent.com/NR-ESTractionPower/vo_addin/refs/heads/main/vision_oslo_extension_license.txt"
|
|
93
|
+
# support_online = "https://github.com/NR-ESTractionPower/vo_addin"
|
|
94
|
+
issue_online = "https://github.com/NR-ESTractionPower/ELSSA_python/issues"
|
|
95
|
+
|
|
96
|
+
license_file = os.path.join(configuration_path, "license.json")
|
|
97
|
+
config_file = os.path.join(configuration_path, "config.json")
|
|
98
|
+
current_path = None # get current path
|
|
99
|
+
admin_password = "passwordS3F3" # this is the Mac password
|
|
100
|
+
|
|
101
|
+
class DataFilenames:
|
|
102
|
+
'''
|
|
103
|
+
This class holds the database file name as in two lists.
|
|
104
|
+
These two list will be checked at the very beginning of application lauching
|
|
105
|
+
Any new dataset added in the future needs to be added to this comprehensive data list.
|
|
106
|
+
public_file list are files hold on the GitHub public repository
|
|
107
|
+
private_file list are files hold locally in data folder
|
|
108
|
+
|
|
109
|
+
This class also contains the default configuration settings
|
|
110
|
+
'''
|
|
111
|
+
public_file = {
|
|
112
|
+
'tiploc_library': 'TestFile.txt'
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private_file = {
|
|
116
|
+
'private_file1': 'TestFile1.csv',
|
|
117
|
+
'private_file2': 'TestFile2.csv'
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
default_config = {
|
|
121
|
+
'package_version_number': SharedVariables.installed_version,
|
|
122
|
+
'default_database_updated': False,
|
|
123
|
+
'elssa_core_updated': False,
|
|
124
|
+
'elssa_version': 1, # default to 1
|
|
125
|
+
'database_location': 2 # default to user location
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# Define a dictionary mapping import_option to module names
|
|
129
|
+
module_mapping = {
|
|
130
|
+
"cif_prepare.py": "cif_prepare",
|
|
131
|
+
"cif_duplicates.py": "cif_duplicates",
|
|
132
|
+
"model_check.py": "model_check",
|
|
133
|
+
"oslo_extraction.py": "oslo_extraction",
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
class SharedMethods:
|
|
137
|
+
'''
|
|
138
|
+
This class will hold all shared methods
|
|
139
|
+
'''
|
|
140
|
+
# check if the script running in debug mode or not
|
|
141
|
+
@staticmethod
|
|
142
|
+
def is_debug_mode():
|
|
143
|
+
# Checks if the debugger is attached by inspecting system flags
|
|
144
|
+
return (sys.gettrace() is not None or "debugpy" in sys.modules)
|
|
145
|
+
|
|
146
|
+
# context manager to change working directory and revert back
|
|
147
|
+
@contextmanager
|
|
148
|
+
def pushd(path):
|
|
149
|
+
prev = os.getcwd()
|
|
150
|
+
os.chdir(path)
|
|
151
|
+
try:
|
|
152
|
+
yield
|
|
153
|
+
finally:
|
|
154
|
+
os.chdir(prev)
|
|
155
|
+
|
|
156
|
+
# copy files from source folder to active entry
|
|
157
|
+
@staticmethod
|
|
158
|
+
def copy_example_files(filename):
|
|
159
|
+
distribution = importlib.resources.files(SharedVariables.package_name)
|
|
160
|
+
# Get the path to the package
|
|
161
|
+
#package_path = distribution.location + "\\" + SharedVariables.package_name
|
|
162
|
+
package_path = os.path.join(str(distribution), SharedVariables.support_name)
|
|
163
|
+
|
|
164
|
+
# Get the absolute path of the file in the package location
|
|
165
|
+
file_in_package = os.path.join(package_path, filename)
|
|
166
|
+
current_path = os.getcwd() # get current path
|
|
167
|
+
|
|
168
|
+
check_file = os.path.join(current_path, filename)
|
|
169
|
+
|
|
170
|
+
if os.path.exists(check_file):
|
|
171
|
+
print(f"File '{filename}' already exists in the current working directory. Skipping copy...")
|
|
172
|
+
else:
|
|
173
|
+
# Copy the file to the current working directory
|
|
174
|
+
shutil.copy(file_in_package,current_path)
|
|
175
|
+
print(f"File '{filename}' copied to the current working directory. Config as required...")
|
|
176
|
+
|
|
177
|
+
# check data library files, return full path if found
|
|
178
|
+
@staticmethod
|
|
179
|
+
def check_data_files(filename):
|
|
180
|
+
# Get the absolute path of the file in the package location
|
|
181
|
+
if not SharedVariables.used_database_path:
|
|
182
|
+
SharedVariables.used_database_path = SharedVariables.database_path_default
|
|
183
|
+
SharedMethods.print_message(f"WARNING: Database file path not set. Refer to Default. Contact support to report the issue...","33")
|
|
184
|
+
file_in_package = os.path.join(SharedVariables.used_database_path, filename)
|
|
185
|
+
|
|
186
|
+
if os.path.exists(file_in_package):
|
|
187
|
+
print(f"Data file '{filename}' exist in the Data library. Reading will be done.")
|
|
188
|
+
return file_in_package
|
|
189
|
+
else:
|
|
190
|
+
SharedMethods.print_message(f"WARNING: Data file '{filename}' does NOT exist in the Data library. Reading will be skipped.","33")
|
|
191
|
+
SharedMethods.print_message(f"ATTENTION: You can add data file '{filename}' to {SharedVariables.used_database_path} and report it to support.","33")
|
|
192
|
+
return False
|
|
193
|
+
|
|
194
|
+
# check if a file is in use or not
|
|
195
|
+
@staticmethod
|
|
196
|
+
def can_write_file(filename):
|
|
197
|
+
"""Return True if the file can be opened for writing; False if Excel has it open."""
|
|
198
|
+
filepath = os.path.join(os.getcwd(), filename)
|
|
199
|
+
if not os.path.exists(filepath):
|
|
200
|
+
# File doesn't exist → safe to write
|
|
201
|
+
return True
|
|
202
|
+
try:
|
|
203
|
+
# Try opening in append+binary mode (does not read file)
|
|
204
|
+
with open(filepath, "r+b"):
|
|
205
|
+
pass
|
|
206
|
+
SharedMethods.print_message(f"WARNING: File {filename} could be overwritten at a later stage...", "33")
|
|
207
|
+
return True
|
|
208
|
+
except (OSError, PermissionError):
|
|
209
|
+
# File is locked by Excel or another process
|
|
210
|
+
return False
|
|
211
|
+
|
|
212
|
+
#check existing file
|
|
213
|
+
@staticmethod
|
|
214
|
+
def check_existing_file(filename,printing = True):
|
|
215
|
+
"""
|
|
216
|
+
Check if a specific file exists.
|
|
217
|
+
Returns True if the file exists, False otherwise.
|
|
218
|
+
if printing is True, it will print message information.
|
|
219
|
+
"""
|
|
220
|
+
print(f"Checking File {filename}...")
|
|
221
|
+
|
|
222
|
+
first = filename.split('.')[0]
|
|
223
|
+
if first == "":
|
|
224
|
+
SharedMethods.print_message("ERROR: Select the simulation or required file to continue...","31")
|
|
225
|
+
return False
|
|
226
|
+
|
|
227
|
+
current_path = os.getcwd() # get current path
|
|
228
|
+
file_path = os.path.join(current_path,filename) # join the file path
|
|
229
|
+
if not os.path.isfile(file_path): # if the oof file does not exist
|
|
230
|
+
if printing:
|
|
231
|
+
SharedMethods.print_message(f"ERROR: Required file {filename} does not exist. Checking required...","31")
|
|
232
|
+
return False
|
|
233
|
+
return True
|
|
234
|
+
|
|
235
|
+
# check the folder and file for summary
|
|
236
|
+
@staticmethod
|
|
237
|
+
def folder_file_check(subfolder,filename=None,required=True):
|
|
238
|
+
"""
|
|
239
|
+
Check if a specific file exists in a given subfolder.
|
|
240
|
+
Returns True if the file exists, False otherwise.
|
|
241
|
+
if required is True, it will print an error message and exit if the file does not exist.
|
|
242
|
+
"""
|
|
243
|
+
print(f"Checking File {filename} in {subfolder}...")
|
|
244
|
+
current_path = os.getcwd() # get current path
|
|
245
|
+
|
|
246
|
+
# Create the complete folder path
|
|
247
|
+
folder_path = os.path.join(current_path, subfolder)
|
|
248
|
+
|
|
249
|
+
# Check if the folder exists
|
|
250
|
+
if not os.path.exists(folder_path):
|
|
251
|
+
if required:
|
|
252
|
+
SharedMethods.print_message(f"ERROR: Required folder {subfolder} does not exist. Check your Input. Exiting...","31")
|
|
253
|
+
return False
|
|
254
|
+
|
|
255
|
+
if filename != None: #if filename is not None, check the file existance in the folder
|
|
256
|
+
# file path
|
|
257
|
+
file_path = os.path.join(folder_path,filename) # join the file path
|
|
258
|
+
# print(file_path)
|
|
259
|
+
if not os.path.isfile(file_path):
|
|
260
|
+
if required:
|
|
261
|
+
SharedMethods.print_message(f"ERROR: Required file {filename} does not exist at {subfolder}. Check your Input. Exiting...","31")
|
|
262
|
+
return False
|
|
263
|
+
|
|
264
|
+
return True
|
|
265
|
+
|
|
266
|
+
# copy the file to a subfolder / if not exist, create the subfolder
|
|
267
|
+
@staticmethod
|
|
268
|
+
def copy_file_to_subfolder(subfolder, filename, new_filename=None):
|
|
269
|
+
print(f"Copying File {filename} to {subfolder}...")
|
|
270
|
+
current_path = os.getcwd() # Get current path
|
|
271
|
+
folder_path = os.path.join(current_path, subfolder)
|
|
272
|
+
|
|
273
|
+
# Create the folder if it doesn't exist
|
|
274
|
+
if not os.path.exists(folder_path):
|
|
275
|
+
try:
|
|
276
|
+
os.makedirs(folder_path)
|
|
277
|
+
print(f"Folder '{subfolder}' created.")
|
|
278
|
+
except Exception as e:
|
|
279
|
+
SharedMethods.print_message(f"ERROR: Error creating folder {subfolder}: {e}. Check your Input...", "31")
|
|
280
|
+
return False
|
|
281
|
+
|
|
282
|
+
# Determine the target file name
|
|
283
|
+
target_filename = new_filename if new_filename else filename
|
|
284
|
+
file_path = os.path.join(folder_path, target_filename)
|
|
285
|
+
|
|
286
|
+
# Warn if the target file already exists
|
|
287
|
+
if os.path.isfile(file_path):
|
|
288
|
+
SharedMethods.print_message(f"WARNING: File {target_filename} already exists in {subfolder}. Overwriting...", "33")
|
|
289
|
+
os.remove(file_path)
|
|
290
|
+
|
|
291
|
+
# Copy the file
|
|
292
|
+
try:
|
|
293
|
+
shutil.copy(filename, file_path)
|
|
294
|
+
print(f"File '{filename}' copied to subfolder successfully'.")
|
|
295
|
+
except Exception as e:
|
|
296
|
+
SharedMethods.print_message(f"ERROR: Error copying file {filename} to {file_path}: {e}. Check your Input...", "31")
|
|
297
|
+
return False
|
|
298
|
+
|
|
299
|
+
return True
|
|
300
|
+
|
|
301
|
+
# open a file in support folder
|
|
302
|
+
@staticmethod
|
|
303
|
+
def open_support_file(filename):
|
|
304
|
+
distribution = importlib.resources.files(SharedVariables.package_name)
|
|
305
|
+
package_path = os.path.join(str(distribution), SharedVariables.support_name)
|
|
306
|
+
# Get the absolute path of the file in the package location
|
|
307
|
+
file_in_package = os.path.join(package_path, filename)
|
|
308
|
+
|
|
309
|
+
# create a temp directory and copy the file there
|
|
310
|
+
temp_dir = tempfile.gettempdir()
|
|
311
|
+
temp_file = os.path.join(temp_dir, filename)
|
|
312
|
+
|
|
313
|
+
try:
|
|
314
|
+
shutil.copy(file_in_package, temp_file) # copy the file to temp directory
|
|
315
|
+
subprocess.Popen(['start', '', temp_file], shell=True,close_fds=True)
|
|
316
|
+
except Exception as e:
|
|
317
|
+
SharedMethods.print_message(f"ERROR: Error opening file with default app: {e}","31")
|
|
318
|
+
|
|
319
|
+
return
|
|
320
|
+
|
|
321
|
+
# add unique key to a ditionary type
|
|
322
|
+
@staticmethod
|
|
323
|
+
def add_unique_key(dictionary, key, value):
|
|
324
|
+
original_key = key
|
|
325
|
+
counter = 1
|
|
326
|
+
while key in dictionary:
|
|
327
|
+
key = f"{original_key}_{counter}"
|
|
328
|
+
counter += 1
|
|
329
|
+
SharedMethods.print_message(f"WARNING: Name {original_key} already exists. Trying to rename to {key}...","33")
|
|
330
|
+
dictionary[key] = value
|
|
331
|
+
return dictionary
|
|
332
|
+
|
|
333
|
+
# delete all simulation result:
|
|
334
|
+
def clean_up_simulation_folder():
|
|
335
|
+
'''
|
|
336
|
+
This function deletes all files in the current working directory except those with specific suffixes.
|
|
337
|
+
'''
|
|
338
|
+
reserved_file_suffix = ['.vvw','.ocl','.vcn','extra.oslo','extra.bat.oslo','pdv.csv','.xlsx','xlsm']
|
|
339
|
+
# delete all files in current working directory if file name not ending as in the list
|
|
340
|
+
current_path = os.getcwd() # get current path
|
|
341
|
+
for filename in os.listdir(current_path):
|
|
342
|
+
if not any(filename.endswith(suffix) for suffix in reserved_file_suffix):
|
|
343
|
+
file_path = os.path.join(current_path, filename)
|
|
344
|
+
try:
|
|
345
|
+
if os.path.isfile(file_path) or os.path.islink(file_path):
|
|
346
|
+
os.remove(file_path) # remove the file or link
|
|
347
|
+
elif os.path.isdir(file_path):
|
|
348
|
+
shutil.rmtree(file_path) # remove the directory and its contents
|
|
349
|
+
print(f"INFO: File '{filename}' been deleted.")
|
|
350
|
+
except Exception as e:
|
|
351
|
+
SharedMethods.print_message(f"ERROR: Failed to delete {file_path}. Reason: {e}", "31")
|
|
352
|
+
print("INFO: Simulation Folder Clean Up Completed.")
|
|
353
|
+
|
|
354
|
+
# define the running in thread mechanism
|
|
355
|
+
def launch_new_thread_or_process(import_option,
|
|
356
|
+
text_input1, text_input2, option_input1, option_input2, time_input1, time_input2, add_text, add_input1, add_input2,config_input,
|
|
357
|
+
cwd=None):
|
|
358
|
+
'''
|
|
359
|
+
This function launches a new thread or process to run the specified module's main function.
|
|
360
|
+
'''
|
|
361
|
+
|
|
362
|
+
if cwd:
|
|
363
|
+
# Change the current working directory to the specified path
|
|
364
|
+
# this is compolsory for new process
|
|
365
|
+
os.chdir(cwd)
|
|
366
|
+
|
|
367
|
+
# Get the module name corresponding to import_option
|
|
368
|
+
module_name = DataFilenames.module_mapping.get(import_option)
|
|
369
|
+
|
|
370
|
+
# Import the module
|
|
371
|
+
if module_name:
|
|
372
|
+
fc = importlib.import_module(f"{SharedVariables.package_name}.{module_name}")
|
|
373
|
+
#from vision_oslo_extension import module_name as fc
|
|
374
|
+
else:
|
|
375
|
+
# Handle the case when import_option doesn't match any module
|
|
376
|
+
SharedMethods.print_message(f"ERROR: Invalid import_option: {import_option}", "31")
|
|
377
|
+
|
|
378
|
+
try:
|
|
379
|
+
print(f"\nLauching module in new thread (same console): {module_name}\n")
|
|
380
|
+
continue_process = fc.main(text_input1,text_input2,option_input1,option_input2,time_input1,time_input2,add_text,add_input1,add_input2,config_input)
|
|
381
|
+
if not continue_process:
|
|
382
|
+
# Do something if the process should not continue
|
|
383
|
+
# Print error message in red
|
|
384
|
+
SharedMethods.print_message("ERROR: Process terminated due to captured issue. "
|
|
385
|
+
"Please check the error history above or contact support. "
|
|
386
|
+
"You can continue using other options...", "31")
|
|
387
|
+
return False
|
|
388
|
+
|
|
389
|
+
else:
|
|
390
|
+
# Do something if the process should continue
|
|
391
|
+
# Print success message in green
|
|
392
|
+
SharedMethods.print_message("Action successfully completed. "
|
|
393
|
+
"Check monitor history above and result files in your folder.", "32")
|
|
394
|
+
|
|
395
|
+
except Exception as e:
|
|
396
|
+
SharedMethods.print_message(f"ERROR: UNEXPECTED! PLEASE REPORT BUG AND CONTACT SUPPORT... ", "31")
|
|
397
|
+
SharedMethods.print_message(f"ERROR: source code module - {import_option}: {e}","31")
|
|
398
|
+
return False
|
|
399
|
+
|
|
400
|
+
return True
|
|
401
|
+
|
|
402
|
+
def print_message(message: str, color_code: str):
|
|
403
|
+
'''
|
|
404
|
+
print out message in color in front end
|
|
405
|
+
color_code following ANSI color code
|
|
406
|
+
'''
|
|
407
|
+
color_start = f'\033[1;{color_code}m' # Start color
|
|
408
|
+
color_reset = '\033[1;0m' # Reset color
|
|
409
|
+
print(color_start + message + color_reset)
|
|
410
|
+
|
|
411
|
+
class InternetValidation:
|
|
412
|
+
'''
|
|
413
|
+
This class contains methods to validate internet connection and check for restricted networks.
|
|
414
|
+
'''
|
|
415
|
+
def internet_check(url):
|
|
416
|
+
'''Check if a handshake with internet can be established or not. To decide if an upgrade is possible or not'''
|
|
417
|
+
|
|
418
|
+
# check connected network to see if it is NR network or not
|
|
419
|
+
if InternetValidation.check_restricted_network():
|
|
420
|
+
SharedMethods.print_message(f"WARNING: Abort internet connection due to Network Rail private network detected.","33")
|
|
421
|
+
return False
|
|
422
|
+
|
|
423
|
+
try:
|
|
424
|
+
# Send a HEAD request using requests with a timeout of 5 seconds
|
|
425
|
+
response = requests.get(url, timeout=5)
|
|
426
|
+
return True
|
|
427
|
+
|
|
428
|
+
except requests.exceptions.Timeout:
|
|
429
|
+
SharedMethods.print_message("ERROR: Request timed out after 5 seconds. POOR INTERNET...", "31")
|
|
430
|
+
return False
|
|
431
|
+
|
|
432
|
+
except Exception as e:
|
|
433
|
+
SharedMethods.print_message(f"ERROR: Unexpected connection: {e}. Please contact the support...","31")
|
|
434
|
+
return False
|
|
435
|
+
|
|
436
|
+
def check_restricted_network():
|
|
437
|
+
'''Return TRUE if connected to the NR Office Environment'''
|
|
438
|
+
system = platform.system()
|
|
439
|
+
try:
|
|
440
|
+
if system == "Windows":
|
|
441
|
+
# WIFI Network Check
|
|
442
|
+
result = subprocess.run(["netsh", "wlan", "show", "interfaces"],
|
|
443
|
+
capture_output=True, text=True, check=True)
|
|
444
|
+
for line in result.stdout.splitlines():
|
|
445
|
+
if "SSID" in line and "BSSID" not in line:
|
|
446
|
+
ssid = line.split(":", 1)[1].strip()
|
|
447
|
+
if ssid == "14":
|
|
448
|
+
# print("NR CORP WIFI 14 DETECTED.")
|
|
449
|
+
return True
|
|
450
|
+
else:
|
|
451
|
+
break
|
|
452
|
+
# Ehternet Network Check
|
|
453
|
+
block = False
|
|
454
|
+
result = subprocess.run(["ipconfig", "/all"],
|
|
455
|
+
capture_output=True, text=True, check=True)
|
|
456
|
+
for line in result.stdout.splitlines():
|
|
457
|
+
if "adapter" in line.lower() and "Ethernet" in line:
|
|
458
|
+
block = True
|
|
459
|
+
if block:
|
|
460
|
+
# skip the check if the ethernet cable is not used
|
|
461
|
+
if "media disconnected" in line.lower():
|
|
462
|
+
# this means WIFI not 14 and Ethernet cable is not connected
|
|
463
|
+
return False
|
|
464
|
+
type = line.split(":", 1)[0].strip().lower()
|
|
465
|
+
if "default gateway" in type:
|
|
466
|
+
gatewayip = line.split(":", 1)[1].strip()
|
|
467
|
+
if gatewayip.startswith("10.176."): # This IP is only used for small private network
|
|
468
|
+
# print("NR CORP ETHERNET CONNECTION DETECTED.")
|
|
469
|
+
return True
|
|
470
|
+
else:
|
|
471
|
+
return False
|
|
472
|
+
else: # not ready for other operating system yet
|
|
473
|
+
return False
|
|
474
|
+
except Exception as e:
|
|
475
|
+
SharedMethods.print_message(f"ERROR: Unexpected error: {e}. Please contact the support...","31")
|
|
476
|
+
return False
|
|
477
|
+
return False
|
|
478
|
+
|
|
479
|
+
class Tee:
|
|
480
|
+
"""Write to multiple streams (console + file)."""
|
|
481
|
+
def __init__(self, *streams):
|
|
482
|
+
self.streams = streams
|
|
483
|
+
|
|
484
|
+
def write(self, data):
|
|
485
|
+
for stream in self.streams:
|
|
486
|
+
stream.write(data)
|
|
487
|
+
stream.flush()
|
|
488
|
+
|
|
489
|
+
def flush(self):
|
|
490
|
+
for stream in self.streams:
|
|
491
|
+
stream.flush()
|