Skip to content

Commit c638381

Browse files
committed
#187 - PleaseWait is working & tested in Open File initialization sequence
1 parent 8f304e4 commit c638381

File tree

6 files changed

+318
-14
lines changed

6 files changed

+318
-14
lines changed

src/log/log_meta.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
from src.action_space.action import Action
1414
from src.action_space.action_space import ActionSpace
1515
from src.log.meta_field import MetaField, MetaFields, Optionality
16-
from src.main.version import VERSION
16+
17+
LOG_META_VERSION = "4.0.0.DRAFT"
1718

1819
MANDATORY = Optionality.MANDATORY
1920
OPTIONAL = Optionality.OPTIONAL
@@ -222,7 +223,7 @@ def matches_os_stats(self, stat_result: os.stat_result) -> bool:
222223
)
223224

224225
def get_as_json(self) -> dict:
225-
self.guru_version.set(VERSION)
226+
self.guru_version.set(LOG_META_VERSION)
226227
self._set_meta_fields_based_on_action_space()
227228
result = MetaFields.create_json(self._fields)
228229
if not self.action_space.is_continuous():

src/main/version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
# Copyright (c) 2021 dmh23
77
#
88

9-
VERSION = "3.2.16"
9+
VERSION = "4.0.0.BETA"

src/prototype_ui/log_utils.py

+185
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
#
2+
# DeepRacer Guru
3+
#
4+
# Version 3.0 onwards
5+
#
6+
# Copyright (c) 2021 dmh23
7+
#
8+
9+
import os
10+
11+
from log.log_meta import LogMeta, LOG_META_VERSION
12+
from prototype_ui.please_wait import PleaseWait
13+
from src.log.log import LOG_FILE_SUFFIX, Log, META_FILE_SUFFIX, CONSOLE_LOG_SUFFIX, make_new_log_meta
14+
from tracks.track import Track
15+
16+
17+
#
18+
# Public Interface
19+
#
20+
21+
class OpenFileInfo:
22+
def __init__(self, display_name: str, log_meta: LogMeta, source_meta_files: list[str]):
23+
self.display_name = display_name
24+
self.log_meta = log_meta
25+
self.source_files = source_meta_files
26+
27+
28+
def get_model_info_for_open_model_dialog(track: Track, log_directory: str, please_wait: PleaseWait) -> (list[OpenFileInfo], int):
29+
_refresh_meta(log_directory, please_wait)
30+
log_info, excluded_log_count = _get_open_file_model_info(track, log_directory)
31+
_add_multi_worker_log_info(log_info)
32+
_fix_worker_log_info_duplicates(log_info)
33+
_fix_remaining_log_info_duplicates(log_info)
34+
return _sorted_log_info(log_info), excluded_log_count
35+
36+
37+
def get_world_names_of_existing_logs(log_directory: str, please_wait: PleaseWait):
38+
_refresh_meta(log_directory, please_wait)
39+
world_names = set()
40+
for f in os.listdir(log_directory):
41+
if f.endswith(META_FILE_SUFFIX):
42+
log = Log(log_directory)
43+
log.load_meta(f)
44+
world_names.add(log.get_log_meta().track_name.get())
45+
return world_names
46+
47+
48+
#
49+
# PRIVATE Implementation Helpers
50+
#
51+
52+
def _refresh_meta(log_directory: str, please_wait: PleaseWait):
53+
_remove_invalid_meta_files(log_directory)
54+
logs_without_meta = _get_log_files_without_meta(log_directory)
55+
_import_logs_without_meta(logs_without_meta, please_wait, log_directory)
56+
57+
58+
def _remove_invalid_meta_files(log_directory: str) -> None:
59+
all_files = os.listdir(log_directory)
60+
for f in all_files:
61+
if f.endswith(META_FILE_SUFFIX):
62+
if not _is_log_valid(f, all_files, log_directory):
63+
os.remove(os.path.join(log_directory, f))
64+
65+
66+
def _is_log_valid(meta_file: str, all_files: list, log_directory: str):
67+
log_file = meta_file[:-len(META_FILE_SUFFIX)]
68+
if log_file not in all_files:
69+
return False
70+
try:
71+
log = Log(log_directory)
72+
log.load_meta(meta_file)
73+
return log.get_log_meta().guru_version.get() == LOG_META_VERSION and log.get_log_meta().matches_os_stats(
74+
os.stat(os.path.join(log_directory, log_file)))
75+
except Exception: # TODO proper exception class for Guru
76+
return False
77+
78+
79+
def _get_log_files_without_meta(log_directory: str):
80+
log_files = []
81+
82+
all_files = os.listdir(log_directory)
83+
for f in all_files:
84+
if f.endswith(LOG_FILE_SUFFIX) or f.endswith(CONSOLE_LOG_SUFFIX):
85+
expected_meta = f + META_FILE_SUFFIX
86+
if expected_meta not in all_files:
87+
log_files.append(f)
88+
89+
return log_files
90+
91+
92+
def _import_logs_without_meta(log_files: list, please_wait: PleaseWait, log_directory: str):
93+
please_wait.start("Importing")
94+
total_count = len(log_files)
95+
for i, f in enumerate(log_files):
96+
log = Log(log_directory)
97+
try:
98+
log.parse(f, please_wait, i / total_count * 100, (i + 1) / total_count * 100)
99+
log.save()
100+
except Exception as ex: # TODO - Trapping specific exceptions not working (Python issue?)
101+
print("Skipping file <{}> due to processing error <{}>".format(f, ex))
102+
please_wait.stop()
103+
104+
105+
def _get_open_file_model_info(track: Track, log_directory: str) -> (list[OpenFileInfo], int):
106+
excluded_log_count = 0
107+
log_info = []
108+
109+
for f in os.listdir(log_directory):
110+
if f.endswith(META_FILE_SUFFIX):
111+
log = Log(log_directory)
112+
log.load_meta(f)
113+
if track.has_world_name(log.get_log_meta().track_name.get()):
114+
log_meta = log.get_log_meta()
115+
display_name = log_meta.model_name.get()
116+
meta_filenames = [f]
117+
log_info.append(OpenFileInfo(display_name, log_meta, meta_filenames))
118+
else:
119+
excluded_log_count += 1
120+
121+
return log_info, excluded_log_count
122+
123+
124+
def _add_multi_worker_log_info(log_info: list[OpenFileInfo]):
125+
model_workers = {}
126+
for log in log_info:
127+
log_meta = log.log_meta
128+
if log_meta.workers.get() >= 2:
129+
model_name = log_meta.model_name.get()
130+
if model_name in model_workers:
131+
model_workers[model_name][log_meta.worker_id.get()] = log
132+
else:
133+
model_workers[model_name] = {log_meta.worker_id.get(): log}
134+
for model_name, workers in model_workers.items():
135+
keys = list(workers.keys())
136+
if len(keys) > 1:
137+
source_files = []
138+
log_metas = []
139+
for i in sorted(keys):
140+
source_files.append(workers[i].source_files[0])
141+
log_metas.append(workers[i].log_meta)
142+
meta_data = make_new_log_meta()
143+
meta_data.merge_from_multi_logs(log_metas)
144+
log_info.append(OpenFileInfo(model_name + " (multi-worker)", meta_data, source_files))
145+
146+
147+
def _fix_worker_log_info_duplicates(log_info: list[OpenFileInfo]):
148+
used_names = []
149+
duplicate_names = []
150+
for log in log_info:
151+
if log.display_name in used_names:
152+
duplicate_names.append(log.display_name)
153+
else:
154+
used_names.append(log.display_name)
155+
for log in log_info:
156+
if log.display_name in duplicate_names and log.log_meta.workers.get() > 1:
157+
log.display_name += f" (worker {log.log_meta.worker_id.get() + 1} / {log.log_meta.workers.get()})"
158+
159+
160+
def _fix_remaining_log_info_duplicates(log_info: list[OpenFileInfo]):
161+
used_names = []
162+
for log in log_info:
163+
old_name = log.display_name
164+
new_name = old_name
165+
i = 1
166+
while new_name in used_names:
167+
new_name = f"{old_name} ({i})"
168+
i += 1
169+
log.display_name = new_name
170+
used_names.append(new_name)
171+
172+
173+
def _sorted_log_info(log_info: list[OpenFileInfo]):
174+
all_names = []
175+
indexed_logs = {}
176+
177+
for log in log_info:
178+
all_names.append(log.display_name)
179+
indexed_logs[log.display_name] = log
180+
181+
result = []
182+
for name in sorted(all_names):
183+
result.append(indexed_logs[name])
184+
185+
return result

src/prototype_ui/main.py

+29-11
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from configuration.config_manager import ConfigManager
77
from prototype_ui.actions import Actions
88
from prototype_ui.menubar import MenuBarManager
9+
from prototype_ui.open_file_dialog import OpenFileDialog
10+
from prototype_ui.please_wait import PleaseWait
911
from prototype_ui.toolbar import ToolBarManager
1012
from prototype_ui.track_analysis_canvas import TrackAnalysisCanvas, FilledCircle, TrackArea, Line
1113
from prototype_ui.tracks_v4 import get_all_tracks
@@ -26,12 +28,7 @@ def __init__(self):
2628
self.setWindowTitle("Example")
2729

2830
# Status Bar
29-
self._status_bar_label = QLabel("Hello Status Bar")
30-
self._status_bar_progress = QProgressBar()
31-
self._status_bar_progress.setRange(0, 10)
32-
self._status_bar_progress.setValue(7)
33-
self.statusBar().addPermanentWidget(self._status_bar_label)
34-
self.statusBar().addPermanentWidget(self._status_bar_progress)
31+
self._please_wait = PleaseWait(self.statusBar(), self.set_busy_cursor, self.set_normal_cursor)
3532

3633
# Define UI actions
3734
self._actions = Actions(self.style())
@@ -47,24 +44,45 @@ def __init__(self):
4744

4845
self.canvas = TrackAnalysisCanvas()
4946
self.setCentralWidget(self.canvas)
47+
self.make_status_bar_tall_enough_to_contain_progress_bar()
5048

5149
self.show()
5250

5351
# Initialise tracks & draw here temporarily to prove everything works or not
5452

5553
self._tracks = get_all_tracks()
56-
track = self._tracks["reinvent_base"]
57-
track.configure_track_canvas(self.canvas)
54+
self._current_track = self._tracks["reinvent_base"]
55+
self._current_track.configure_track_canvas(self.canvas)
5856
# self.canvas.set_track_area(TrackArea(0, 0, 10, 10))
59-
track.draw_track_edges(self.canvas, Qt.GlobalColor.red)
60-
track.draw_waypoints(self.canvas, Qt.GlobalColor.blue, 2, 5)
57+
self._current_track.draw_track_edges(self.canvas, Qt.GlobalColor.red)
58+
self._current_track.draw_waypoints(self.canvas, Qt.GlobalColor.blue, 2, 5)
59+
60+
def set_busy_cursor(self):
61+
self.setCursor(Qt.CursorShape.WaitCursor)
62+
self.repaint()
63+
64+
def set_normal_cursor(self):
65+
self.setCursor(Qt.CursorShape.ArrowCursor)
66+
self.repaint()
67+
68+
def make_status_bar_tall_enough_to_contain_progress_bar(self):
69+
dummy_sizing_progress_bar = QProgressBar()
70+
self.statusBar().addPermanentWidget(dummy_sizing_progress_bar)
71+
h = self.statusBar().geometry().height()
72+
self.statusBar().removeWidget(dummy_sizing_progress_bar)
73+
self.statusBar().setMinimumHeight(h)
6174

6275
def _new_file(self):
6376
print("New File")
6477
self.statusBar().showMessage("Hello Briefly", 5000) # 5 secs
6578

6679
def _open_file(self):
67-
self.canvas.setCursor(Qt.CursorShape.CrossCursor)
80+
# self.canvas.setCursor(Qt.CursorShape.CrossCursor)
81+
dlg = OpenFileDialog(self, self._please_wait, self._current_track, self._config_manager.get_log_directory())
82+
if dlg.exec():
83+
print("Success!")
84+
else:
85+
print("Cancel!")
6886

6987
def _change_directory(self):
7088
new_directory = QFileDialog.getExistingDirectory(self, self._actions.change_directory.statusTip(), self._config_manager.get_log_directory())

src/prototype_ui/open_file_dialog.py

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QMainWindow, QTableWidget, QGridLayout
2+
3+
from prototype_ui.log_utils import get_model_info_for_open_model_dialog
4+
from prototype_ui.please_wait import PleaseWait
5+
from prototype_ui.track_v4 import Track
6+
7+
8+
9+
class OpenFileDialog(QDialog):
10+
def __init__(self, parent: QMainWindow, please_wait: PleaseWait, current_track: Track, log_directory: str):
11+
super().__init__(parent)
12+
13+
log_info, hidden_log_count = get_model_info_for_open_model_dialog(current_track, log_directory, please_wait)
14+
15+
self.layout = QGridLayout()
16+
17+
for i, title in enumerate(["One", "Two", "Three", "Four"]):
18+
self.layout.addWidget(QLabel(title), 0, i)
19+
for i in range(1, 3):
20+
self.layout.addWidget(QLabel("Hello" + str(i)), i, 2)
21+
22+
23+
24+
25+
26+
27+
self.setWindowTitle("HELLO!")
28+
29+
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
30+
31+
self.buttonBox = QDialogButtonBox(QBtn)
32+
self.buttonBox.accepted.connect(self.accept)
33+
self.buttonBox.rejected.connect(self.reject)
34+
35+
36+
message = QLabel("Something happened, is that OK?")
37+
#self.layout.addLayout(grid)
38+
self.layout.addWidget(message)
39+
self.layout.addWidget(self.buttonBox)
40+
self.setLayout(self.layout)

src/prototype_ui/please_wait.py

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#
2+
# DeepRacer Guru
3+
#
4+
# Version 3.0 onwards
5+
#
6+
# Copyright (c) 2021 dmh23
7+
#
8+
9+
import time
10+
11+
from PyQt6.QtCore import Qt
12+
from PyQt6.QtWidgets import QStatusBar, QLabel, QProgressBar
13+
14+
NEARLY_COMPLETE = 99.99
15+
REDRAW_INTERVAL = 0.15
16+
SMALL_JUMP = 2
17+
BIG_JUMP = 25
18+
19+
20+
class PleaseWait:
21+
def __init__(self, status_bar: QStatusBar, set_busy_cursor_method: callable, set_normal_cursor_method: callable):
22+
self._percent_done = 0
23+
self._status_bar = status_bar
24+
self._set_busy_cursor_method = set_busy_cursor_method
25+
self._set_normal_cursor_method = set_normal_cursor_method
26+
self._progress_bar = QProgressBar()
27+
self._progress_bar.setRange(0, 100)
28+
self._label_widget = QLabel()
29+
30+
def start(self, title):
31+
self._set_busy_cursor_method()
32+
33+
self._percent_done = 0
34+
35+
self._label_widget = QLabel(title)
36+
self._progress_bar.setValue(0)
37+
38+
self._status_bar.addWidget(self._label_widget)
39+
self._status_bar.addWidget(self._progress_bar)
40+
self._progress_bar.show()
41+
self._label_widget.show()
42+
self._status_bar.clearMessage()
43+
44+
self._status_bar.repaint()
45+
46+
def stop(self, pause_seconds=0):
47+
time.sleep(pause_seconds)
48+
self._status_bar.removeWidget(self._label_widget)
49+
self._status_bar.removeWidget(self._progress_bar)
50+
self._status_bar.repaint()
51+
self._set_normal_cursor_method()
52+
53+
def set_progress(self, percent_done: int | float):
54+
percent_done = min(100, max(0, round(percent_done)))
55+
56+
if percent_done > self._percent_done:
57+
self._percent_done = percent_done
58+
self._progress_bar.setValue(percent_done)
59+
self._status_bar.repaint()
60+

0 commit comments

Comments
 (0)