|
1 | 1 | from tomllib import TOMLDecodeError |
2 | 2 | from software.thunderscope.constants import RuntimeManagerConstants |
3 | | -from dataclasses import dataclass |
4 | 3 | import os |
5 | 4 | import tomllib |
6 | 5 | import logging |
7 | 6 |
|
8 | 7 |
|
9 | | -@dataclass |
10 | 8 | class RuntimeConfig: |
11 | | - """Data class to store the paths of the two binaries""" |
| 9 | + """Class to store the names and get paths of the two binaries""" |
| 10 | + |
| 11 | + def __init__( |
| 12 | + self, |
| 13 | + blue_runtime: str | None = None, |
| 14 | + yellow_runtime: str | None = None, |
| 15 | + ) -> None: |
| 16 | + """Create runtime config, replacing invalid runtimes with default FullSystem |
| 17 | + :param blue_runtime: blue runtime name, None if default |
| 18 | + :param yellow_runtime: yellow runtime name, None if default |
| 19 | + """ |
| 20 | + self.blue_runtime = ( |
| 21 | + blue_runtime |
| 22 | + if blue_runtime |
| 23 | + and self._is_valid_runtime_path(self._get_runtime_path(blue_runtime)) |
| 24 | + else RuntimeManagerConstants.DEFAULT_BINARY_NAME |
| 25 | + ) |
| 26 | + self.yellow_runtime = ( |
| 27 | + yellow_runtime |
| 28 | + if yellow_runtime |
| 29 | + and self._is_valid_runtime_path(self._get_runtime_path(yellow_runtime)) |
| 30 | + else RuntimeManagerConstants.DEFAULT_BINARY_NAME |
| 31 | + ) |
| 32 | + |
| 33 | + def get_blue_runtime_path(self) -> str: |
| 34 | + """Returns the path of the stored yellow runtime |
| 35 | + :return: the absolute path of the binary as a string, or the relative path of our FullSystem |
| 36 | + """ |
| 37 | + return self._get_runtime_path(self.blue_runtime) |
12 | 38 |
|
13 | | - chosen_blue_path: str = RuntimeManagerConstants.DEFAULT_BINARY_PATH |
14 | | - """Blue runtime path""" |
| 39 | + def get_yellow_runtime_path(self) -> str: |
| 40 | + """Returns the path of the stored yellow runtime |
| 41 | + :return: the absolute path of the binary as a string, or the relative path of our FullSystem |
| 42 | + """ |
| 43 | + return self._get_runtime_path(self.yellow_runtime) |
| 44 | + |
| 45 | + def _get_runtime_path(self, selected_runtime: str) -> str: |
| 46 | + """Gets the absolute path of a binary given its name, or the path of our default FullSystem |
| 47 | + if the binary is not valid. |
| 48 | + :param selected_runtime: the name of the selected runtime binary |
| 49 | + :return: the absolute path of the binary as a string, or the relative path of our FullSystem |
| 50 | + """ |
| 51 | + file_path = os.path.join( |
| 52 | + RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH, selected_runtime |
| 53 | + ) |
| 54 | + # Default to local FullSystem if it is selected or the selected binary isn't a valid runtime |
| 55 | + if ( |
| 56 | + selected_runtime == RuntimeManagerConstants.DEFAULT_BINARY_NAME |
| 57 | + or not self._is_valid_runtime_path(file_path) |
| 58 | + ): |
| 59 | + return RuntimeManagerConstants.DEFAULT_BINARY_PATH |
| 60 | + # Remove leading and trailing white space and return |
| 61 | + return file_path.strip() |
15 | 62 |
|
16 | | - chosen_yellow_path: str = RuntimeManagerConstants.DEFAULT_BINARY_PATH |
17 | | - """Yellow runtime path""" |
| 63 | + def _is_valid_runtime_path(self, runtime_path: str) -> bool: |
| 64 | + """Returns if the binary exists and if it is an executable. |
| 65 | + :param runtime_path: the path to check |
| 66 | + :return: True if it is a valid runtime |
| 67 | + """ |
| 68 | + return os.path.isfile(runtime_path) and os.access(runtime_path, os.X_OK) |
18 | 69 |
|
19 | 70 |
|
20 | 71 | class RuntimeLoader: |
21 | 72 | """Delegate class for handling local runtimes and managing runtime selection""" |
22 | 73 |
|
23 | 74 | def fetch_installed_runtimes(self) -> list[str]: |
24 | | - """Fetches the list of available runtimes, including our FullSystem, from the local disk. Makes the folder |
25 | | - in our local disk if it does not exist yet. |
26 | | - :return: A list of names for available runtimes, or just a list with our FullSystem if no available runtimes |
27 | | - could be found |
| 75 | + """Fetches the list of installed runtimes from the local disk. |
| 76 | + Creates the external runtimes directory in our local disk if it does not exist yet. |
| 77 | + :return: A list of installed runtime names |
28 | 78 | """ |
29 | 79 | runtime_list = [RuntimeManagerConstants.DEFAULT_BINARY_NAME] |
30 | 80 |
|
31 | 81 | if not os.path.isdir(RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH): |
32 | 82 | os.mkdir(RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH) |
33 | | - # Check for all executable files in the folder, and add its name to the list |
| 83 | + |
| 84 | + # Check for all executable files in the directory, and add its name to the list |
34 | 85 | for file_name in os.listdir(RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH): |
35 | 86 | file_path = os.path.join( |
36 | 87 | RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH, file_name |
37 | 88 | ) |
38 | 89 | if os.access(file_path, os.X_OK): |
39 | 90 | runtime_list.append(file_name) |
40 | 91 |
|
41 | | - # Cache external runtimes |
42 | 92 | return runtime_list |
43 | 93 |
|
44 | | - def load_existing_runtimes(self, yellow_runtime: str, blue_runtime: str) -> None: |
| 94 | + def load_selected_runtimes(self, yellow_runtime: str, blue_runtime: str) -> None: |
45 | 95 | """Loads the yellow and blue runtimes specified by saving them in the local disk. |
46 | | - :param blue_runtime: Unique name of the blue runtime to set |
47 | | - :param yellow_runtime: Unique name of the yellow runtime to set |
| 96 | + :param blue_runtime: name of the blue runtime to set |
| 97 | + :param yellow_runtime: name of the yellow runtime to set |
48 | 98 | """ |
49 | | - config = RuntimeConfig( |
50 | | - self._return_runtime_path(blue_runtime), |
51 | | - self._return_runtime_path(yellow_runtime), |
| 99 | + # Format as TOML |
| 100 | + selected_runtimes = ( |
| 101 | + f'{RuntimeManagerConstants.RUNTIME_CONFIG_BLUE_KEY} = "{blue_runtime}"\n' |
| 102 | + f'{RuntimeManagerConstants.RUNTIME_CONFIG_YELLOW_KEY} = "{yellow_runtime}"' |
52 | 103 | ) |
53 | | - self._set_runtime_config(config) |
| 104 | + |
| 105 | + # create a new config file if it doesn't exist, and write in the format above to it |
| 106 | + with open(RuntimeManagerConstants.RUNTIME_CONFIG_PATH, "w") as file: |
| 107 | + file.write(selected_runtimes) |
54 | 108 |
|
55 | 109 | def fetch_runtime_config(self) -> RuntimeConfig: |
56 | | - """Fetches the runtime configuration from the local disk. If the blue/yellow configuration is invalid, |
57 | | - returns the default runtime configuration for blue/yellow |
| 110 | + """Fetches the runtime configuration from the local disk, creating it if it doesn't exist. |
58 | 111 | :return: Returns the runtime configuration as a RuntimeConfig |
59 | 112 | """ |
60 | | - # Create default FullSystem pair with our FullSystem binaries |
61 | | - config = RuntimeConfig() |
| 113 | + # Create empty config file if doesn't exist yet |
| 114 | + os.makedirs( |
| 115 | + os.path.dirname(RuntimeManagerConstants.RUNTIME_CONFIG_PATH), exist_ok=True |
| 116 | + ) |
| 117 | + open(RuntimeManagerConstants.RUNTIME_CONFIG_PATH, "a").close() |
62 | 118 |
|
63 | 119 | try: |
64 | 120 | with open(RuntimeManagerConstants.RUNTIME_CONFIG_PATH, "rb") as file: |
65 | 121 | selected_runtime_dict = tomllib.load(file) |
66 | | - # Get the persisted blue path, or replace with the default arrangement if it doesn't exist |
67 | | - toml_blue_path = selected_runtime_dict.get( |
68 | | - RuntimeManagerConstants.RUNTIME_CONFIG_BLUE_KEY, |
69 | | - RuntimeManagerConstants.DEFAULT_BINARY_PATH, |
70 | | - ) |
71 | | - if self._is_valid_runtime(toml_blue_path): |
72 | | - config.chosen_blue_path = toml_blue_path |
73 | | - # Get the persisted yellow path, or replace with the default arrangement if it doesn't exist |
74 | | - toml_yellow_path = selected_runtime_dict.get( |
75 | | - RuntimeManagerConstants.RUNTIME_CONFIG_YELLOW_KEY, |
76 | | - RuntimeManagerConstants.DEFAULT_BINARY_PATH, |
| 122 | + |
| 123 | + # Get the persisted runtimes |
| 124 | + config = RuntimeConfig( |
| 125 | + selected_runtime_dict.get( |
| 126 | + RuntimeManagerConstants.RUNTIME_CONFIG_BLUE_KEY |
| 127 | + ), |
| 128 | + selected_runtime_dict.get( |
| 129 | + RuntimeManagerConstants.RUNTIME_CONFIG_YELLOW_KEY |
| 130 | + ), |
77 | 131 | ) |
78 | | - if self._is_valid_runtime(toml_yellow_path): |
79 | | - config.chosen_yellow_path = toml_yellow_path |
80 | | - except (FileNotFoundError, PermissionError, TOMLDecodeError): |
| 132 | + |
| 133 | + return config |
| 134 | + except TOMLDecodeError: |
81 | 135 | logging.warning( |
82 | 136 | f"Failed to read TOML file at: {RuntimeManagerConstants.RUNTIME_CONFIG_PATH}" |
83 | 137 | ) |
84 | 138 |
|
85 | | - return config |
86 | | - |
87 | | - def _set_runtime_config(self, config: RuntimeConfig) -> None: |
88 | | - """Sets/persists the runtime configuration file on disk and creates the configuration |
89 | | - file if it doesn't exist. |
90 | | - :param config: The runtime configuration containing |
91 | | - - color_runtime : absolute path of external runtime, or |
92 | | - - color_runtime : relative path of DEFAULT_BINARY_PATH |
93 | | - """ |
94 | | - blue_path = config.chosen_blue_path |
95 | | - yellow_path = config.chosen_yellow_path |
96 | | - |
97 | | - """Format in TOML as: |
98 | | - blue_path_to_binary: '<runtime path>' |
99 | | - yellow_path_to_binary: '<runtime path>'""" |
100 | | - |
101 | | - selected_runtimes = ( |
102 | | - f'{RuntimeManagerConstants.RUNTIME_CONFIG_BLUE_KEY} = "{blue_path}"\n' |
103 | | - f'{RuntimeManagerConstants.RUNTIME_CONFIG_YELLOW_KEY} = "{yellow_path}"' |
104 | | - ) |
105 | | - |
106 | | - # create a new config file if it doesn't exist, and write in the format above to it |
107 | | - with open(RuntimeManagerConstants.RUNTIME_CONFIG_PATH, "w") as file: |
108 | | - file.write(selected_runtimes) |
109 | | - |
110 | | - def _return_runtime_path(self, selected_runtime: str) -> str: |
111 | | - """Returns the absolute path of a binary given its name, or the path of our default FullSystem |
112 | | - if the binary is not valid. |
113 | | - :param selected_runtime: the name of the selected runtime binary |
114 | | - :return: the absolute path of the binary as a string, or the relative path of our FullSystem |
115 | | - """ |
116 | | - file_path = os.path.join( |
117 | | - RuntimeManagerConstants.EXTERNAL_RUNTIMES_PATH, selected_runtime |
118 | | - ) |
119 | | - # Default to our full system if it is selected or the selected binary isn't a valid runtime |
120 | | - if ( |
121 | | - selected_runtime == RuntimeManagerConstants.DEFAULT_BINARY_NAME |
122 | | - or not self._is_valid_runtime(file_path) |
123 | | - ): |
124 | | - return RuntimeManagerConstants.DEFAULT_BINARY_PATH |
125 | | - # Remove leading and trailing white space and return |
126 | | - return file_path.strip() |
127 | | - |
128 | | - def _is_valid_runtime(self, runtime_path: str) -> bool: |
129 | | - """Returns if the path exists and if it is an executable. Logs a warning if it is not valid. |
130 | | - :param runtime_path the path to check |
131 | | - :return: whether it is a valid runtime or not |
132 | | - """ |
133 | | - if os.path.isfile(runtime_path) and os.access(runtime_path, os.X_OK): |
134 | | - return True |
135 | | - logging.warning( |
136 | | - f"The runtime retrieved at {runtime_path} is not a valid runtime." |
137 | | - ) |
138 | | - return False |
| 139 | + return RuntimeConfig() |
0 commit comments