From dded8fd6b89f0edb13348a79fbee6580e4ef2117 Mon Sep 17 00:00:00 2001 From: Aman Srivastava Date: Sun, 28 Dec 2025 18:49:13 +0530 Subject: [PATCH 01/13] Show revision id in view and plot titles --- mslib/msui/viewwindows.py | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/mslib/msui/viewwindows.py b/mslib/msui/viewwindows.py index eb1f936ca..926899e96 100644 --- a/mslib/msui/viewwindows.py +++ b/mslib/msui/viewwindows.py @@ -33,6 +33,21 @@ from PyQt5 import QtCore, QtWidgets from mslib.utils.config import save_settings_qsettings +def format_operation_with_revision(model): + """ + Return operation name including revision id and optional revision name. + """ + if model is None: + return "" + + rev = getattr(model, "revision", None) + if rev is None: + return model.name + + label = f"{model.name} [rev: {rev.id}]" + if getattr(rev, "name", None): + label += f" ({rev.name})" + return label class MSUIViewWindow(QtWidgets.QMainWindow): """ @@ -109,7 +124,10 @@ def setFlightTrackModel(self, model): """ # Update title flighttrack name if self.waypoints_model: - self.setWindowTitle(self.windowTitle().replace(self.waypoints_model.name, model.name)) + old_label = format_operation_with_revision(self.waypoints_model) + new_label = format_operation_with_revision(model) + self.setWindowTitle(self.windowTitle().replace(old_label, new_label)) + self.waypoints_model = model @@ -274,11 +292,15 @@ def setFlightTrackModel(self, model): # Update Top View flighttrack name if hasattr(self.mpl.canvas, "map"): - self.mpl.canvas.map.ax.figure.suptitle(f"{model.name}", x=0.95, ha='right') + title = format_operation_with_revision(model) + self.mpl.canvas.map.ax.figure.suptitle(title, x=0.95, ha='right') + self.mpl.canvas.map.ax.figure.canvas.draw() elif hasattr(self.mpl.canvas, 'plotter'): - self.mpl.canvas.plotter.fig.suptitle(f"{model.name}", x=0.95, ha='right') + title = format_operation_with_revision(model) + self.mpl.canvas.plotter.fig.suptitle(title, x=0.95, ha='right') + self.mpl.canvas.plotter.fig.canvas.draw() def getView(self): From 33a8443f781067ad75f9cf65edc9b0c2815c3273 Mon Sep 17 00:00:00 2001 From: Aman Srivastava Date: Fri, 9 Jan 2026 14:58:30 +0530 Subject: [PATCH 02/13] add revision support to FTML with backward compatibility --- mslib/msui/flighttrack.py | 59 ++++++++++++++++++++++++++-- tests/_test_msui/test_flighttrack.py | 35 +++++++++++++++++ 2 files changed, 90 insertions(+), 4 deletions(-) diff --git a/mslib/msui/flighttrack.py b/mslib/msui/flighttrack.py index 061d4f830..75825141f 100644 --- a/mslib/msui/flighttrack.py +++ b/mslib/msui/flighttrack.py @@ -62,6 +62,15 @@ LOCATION, LAT, LON, FLIGHTLEVEL, PRESSURE = list(range(5)) TIME_UTC = 9 +class Revision: + """ + Represents a revision of a flight track. + ID is mandatory, name is optional. + """ + def __init__(self, revision_id, name=None): + self.id = revision_id + self.name = name + def seconds_to_string(seconds): """ @@ -184,8 +193,9 @@ class WaypointsTableModel(QtCore.QAbstractTableModel): def __init__(self, name="", filename=None, waypoints=None, mscolab_mode=False, data_dir=config_loader(dataset="mss_dir"), - xml_content=None): + xml_content=None): super().__init__() + self.revision = None self.name = name # a name for this flight track self.filename = filename # filename for store/load self.data_dir = data_dir @@ -208,6 +218,12 @@ def __init__(self, name="", filename=None, waypoints=None, mscolab_mode=False, if waypoints: self.replace_waypoints(waypoints) + + if self.revision is None: + self.revision = Revision( + revision_id=int(datetime.datetime.utcnow().timestamp()), + name=None + ) def load_settings(self): """ @@ -639,12 +655,23 @@ def save_to_ftml(self, filename=None): def get_xml_doc(self): doc = xml.dom.minidom.Document() # nosec, we take care of writing correct XML + ft_el = doc.createElement("FlightTrack") ft_el.setAttribute("version", __version__) doc.appendChild(ft_el) - # The list of waypoint elements. + + # --- NEW: write revision information --- + if self.revision is not None: + rev_el = doc.createElement("Revision") + rev_el.setAttribute("id", str(self.revision.id)) + if self.revision.name: + rev_el.setAttribute("name", self.revision.name) + ft_el.appendChild(rev_el) + + # --- existing: list of waypoints --- wp_el = doc.createElement("ListOfWaypoints") ft_el.appendChild(wp_el) + for wp in self.waypoints: element = doc.createElement("Waypoint") wp_el.appendChild(element) @@ -652,11 +679,14 @@ def get_xml_doc(self): element.setAttribute("lat", str(wp.lat)) element.setAttribute("lon", str(wp.lon)) element.setAttribute("flightlevel", str(wp.flightlevel)) + comments = doc.createElement("Comments") comments.appendChild(doc.createTextNode(str(wp.comments))) element.appendChild(comments) + return doc + def get_xml_content(self): doc = self.get_xml_doc() return doc.toprettyxml(indent=" ", newl="\n") @@ -671,14 +701,35 @@ def load_from_ftml(self, filename): def load_from_xml_data(self, xml_content, name="Flight track"): self.name = name + + try: + doc = defusedxml.minidom.parseString(xml_content) + except DefusedXmlException as ex: + raise SyntaxError(str(ex)) + + ft_el = doc.getElementsByTagName("FlightTrack")[0] + + # Load revision + revision_nodes = ft_el.getElementsByTagName("Revision") + if revision_nodes: + rev_el = revision_nodes[0] + revision_id = int(rev_el.getAttribute("id")) + revision_name = rev_el.getAttribute("name") or None + self.revision = Revision(revision_id, revision_name) + else: + # Backward compatibility + self.revision = Revision( + revision_id=int(datetime.datetime.utcnow().timestamp()), + name=None + ) + + # Existing waypoint loading if verify_waypoint_data(xml_content): _waypoints_list = load_from_xml_data(xml_content, name) self.replace_waypoints(_waypoints_list) else: raise SyntaxError(f"Invalid flight track filename: {name}") - def get_filename(self): - return self.filename # diff --git a/tests/_test_msui/test_flighttrack.py b/tests/_test_msui/test_flighttrack.py index 5a8cc1476..a308596ce 100644 --- a/tests/_test_msui/test_flighttrack.py +++ b/tests/_test_msui/test_flighttrack.py @@ -114,3 +114,38 @@ def test_isinstance_check_with_various_types(self): "Dict should pass isinstance dict check" assert isinstance({}, dict), \ "Empty dict should pass isinstance dict check" + + def test_load_ftml_with_revision(self): + xml_content = """ + + + + + + + + + """ + + model = WaypointsTableModel(xml_content=xml_content, name="test_track") + + assert model.revision is not None + assert model.revision.id == 42 + assert model.revision.name == "draft" + + def test_load_ftml_without_revision_creates_default_revision(self): + xml_content = """ + + + + + + + + """ + + model = WaypointsTableModel(xml_content=xml_content, name="legacy_track") + + assert model.revision is not None + assert isinstance(model.revision.id, int) + assert model.revision.name is None \ No newline at end of file From f84d02f3c983fb8aec7e867933cab91c09d4a3de Mon Sep 17 00:00:00 2001 From: Aman Srivastava Date: Wed, 4 Feb 2026 15:50:39 +0530 Subject: [PATCH 03/13] cleaning --- mslib/msui/flighttrack.py | 10 +++++----- tests/_test_msui/test_flighttrack.py | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/mslib/msui/flighttrack.py b/mslib/msui/flighttrack.py index 75825141f..dea7c3973 100644 --- a/mslib/msui/flighttrack.py +++ b/mslib/msui/flighttrack.py @@ -62,11 +62,13 @@ LOCATION, LAT, LON, FLIGHTLEVEL, PRESSURE = list(range(5)) TIME_UTC = 9 + class Revision: """ Represents a revision of a flight track. ID is mandatory, name is optional. """ + def __init__(self, revision_id, name=None): self.id = revision_id self.name = name @@ -193,7 +195,7 @@ class WaypointsTableModel(QtCore.QAbstractTableModel): def __init__(self, name="", filename=None, waypoints=None, mscolab_mode=False, data_dir=config_loader(dataset="mss_dir"), - xml_content=None): + xml_content=None): super().__init__() self.revision = None self.name = name # a name for this flight track @@ -218,12 +220,12 @@ def __init__(self, name="", filename=None, waypoints=None, mscolab_mode=False, if waypoints: self.replace_waypoints(waypoints) - + if self.revision is None: self.revision = Revision( revision_id=int(datetime.datetime.utcnow().timestamp()), name=None - ) + ) def load_settings(self): """ @@ -686,7 +688,6 @@ def get_xml_doc(self): return doc - def get_xml_content(self): doc = self.get_xml_doc() return doc.toprettyxml(indent=" ", newl="\n") @@ -731,7 +732,6 @@ def load_from_xml_data(self, xml_content, name="Flight track"): raise SyntaxError(f"Invalid flight track filename: {name}") - # # CLASS WaypointDelegate # diff --git a/tests/_test_msui/test_flighttrack.py b/tests/_test_msui/test_flighttrack.py index a308596ce..44a6009fc 100644 --- a/tests/_test_msui/test_flighttrack.py +++ b/tests/_test_msui/test_flighttrack.py @@ -114,7 +114,7 @@ def test_isinstance_check_with_various_types(self): "Dict should pass isinstance dict check" assert isinstance({}, dict), \ "Empty dict should pass isinstance dict check" - + def test_load_ftml_with_revision(self): xml_content = """ @@ -131,7 +131,7 @@ def test_load_ftml_with_revision(self): assert model.revision is not None assert model.revision.id == 42 - assert model.revision.name == "draft" + assert model.revision.name == "draft" def test_load_ftml_without_revision_creates_default_revision(self): xml_content = """ @@ -148,4 +148,4 @@ def test_load_ftml_without_revision_creates_default_revision(self): assert model.revision is not None assert isinstance(model.revision.id, int) - assert model.revision.name is None \ No newline at end of file + assert model.revision.name is None From 05d1f1fc3f94443f7ccaf29c2120fff46439fefb Mon Sep 17 00:00:00 2001 From: Aman Srivastava Date: Fri, 9 Jan 2026 15:05:56 +0530 Subject: [PATCH 04/13] style: fix flake8 spacing in viewwindows --- mslib/msui/viewwindows.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mslib/msui/viewwindows.py b/mslib/msui/viewwindows.py index 926899e96..5bbbb612a 100644 --- a/mslib/msui/viewwindows.py +++ b/mslib/msui/viewwindows.py @@ -33,6 +33,7 @@ from PyQt5 import QtCore, QtWidgets from mslib.utils.config import save_settings_qsettings + def format_operation_with_revision(model): """ Return operation name including revision id and optional revision name. @@ -49,6 +50,7 @@ def format_operation_with_revision(model): label += f" ({rev.name})" return label + class MSUIViewWindow(QtWidgets.QMainWindow): """ Derives QMainWindow to provide some common functionality to all @@ -128,7 +130,6 @@ def setFlightTrackModel(self, model): new_label = format_operation_with_revision(model) self.setWindowTitle(self.windowTitle().replace(old_label, new_label)) - self.waypoints_model = model def controlToBeCreated(self, index): From 195a1d1335af0ee3f350311d236a2346021cc741 Mon Sep 17 00:00:00 2001 From: Aman Srivastava Date: Mon, 12 Jan 2026 17:27:26 +0530 Subject: [PATCH 05/13] function restored --- mslib/msui/flighttrack.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mslib/msui/flighttrack.py b/mslib/msui/flighttrack.py index dea7c3973..d06af6f49 100644 --- a/mslib/msui/flighttrack.py +++ b/mslib/msui/flighttrack.py @@ -731,7 +731,9 @@ def load_from_xml_data(self, xml_content, name="Flight track"): else: raise SyntaxError(f"Invalid flight track filename: {name}") - + def get_filename(self): + return self.filename + # # CLASS WaypointDelegate # From 322976b096687a7e0aef3b2f139ef1896d1d1ad3 Mon Sep 17 00:00:00 2001 From: Aman Srivastava Date: Mon, 12 Jan 2026 17:30:33 +0530 Subject: [PATCH 06/13] remove trailing whitespace --- mslib/msui/flighttrack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mslib/msui/flighttrack.py b/mslib/msui/flighttrack.py index d06af6f49..f59340f50 100644 --- a/mslib/msui/flighttrack.py +++ b/mslib/msui/flighttrack.py @@ -733,7 +733,7 @@ def load_from_xml_data(self, xml_content, name="Flight track"): def get_filename(self): return self.filename - + # # CLASS WaypointDelegate # From 8bedec30070356b2597224144688fbc67ec696c4 Mon Sep 17 00:00:00 2001 From: Aman Srivastava Date: Wed, 21 Jan 2026 18:49:56 +0530 Subject: [PATCH 07/13] resolve syntax --- mslib/msui/flighttrack.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mslib/msui/flighttrack.py b/mslib/msui/flighttrack.py index f59340f50..1d7b3c680 100644 --- a/mslib/msui/flighttrack.py +++ b/mslib/msui/flighttrack.py @@ -661,8 +661,9 @@ def get_xml_doc(self): ft_el = doc.createElement("FlightTrack") ft_el.setAttribute("version", __version__) doc.appendChild(ft_el) + # The list of waypoint elements. - # --- NEW: write revision information --- + # Write revision information if self.revision is not None: rev_el = doc.createElement("Revision") rev_el.setAttribute("id", str(self.revision.id)) @@ -670,7 +671,7 @@ def get_xml_doc(self): rev_el.setAttribute("name", self.revision.name) ft_el.appendChild(rev_el) - # --- existing: list of waypoints --- + # List of waypoints wp_el = doc.createElement("ListOfWaypoints") ft_el.appendChild(wp_el) From 74cbe168fcc20b6fcaa6c0e86754c1e969c7eeb7 Mon Sep 17 00:00:00 2001 From: Aman Srivastava Date: Wed, 21 Jan 2026 18:55:00 +0530 Subject: [PATCH 08/13] fix FTML revision element --- mslib/msui/flighttrack.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/mslib/msui/flighttrack.py b/mslib/msui/flighttrack.py index 1d7b3c680..c9afb130c 100644 --- a/mslib/msui/flighttrack.py +++ b/mslib/msui/flighttrack.py @@ -50,7 +50,6 @@ from mslib.utils.coordinate import path_points, get_distance from mslib.utils.find_location import find_location from mslib.utils import thermolib -from mslib.utils.verify_waypoint_data import verify_waypoint_data from mslib.utils.config import config_loader, save_settings_qsettings, load_settings_qsettings from mslib.utils.qt import variant_to_string, variant_to_float from mslib.msui.performance_settings import DEFAULT_PERFORMANCE @@ -725,12 +724,9 @@ def load_from_xml_data(self, xml_content, name="Flight track"): name=None ) - # Existing waypoint loading - if verify_waypoint_data(xml_content): - _waypoints_list = load_from_xml_data(xml_content, name) - self.replace_waypoints(_waypoints_list) - else: - raise SyntaxError(f"Invalid flight track filename: {name}") + # Validate only waypoint structure, revision is optional metadata + _waypoints_list = load_from_xml_data(xml_content, name) + self.replace_waypoints(_waypoints_list) def get_filename(self): return self.filename From 4229b743fb7e64b3383927a1fd966941beb0f3dc Mon Sep 17 00:00:00 2001 From: Aman Srivastava Date: Wed, 21 Jan 2026 19:28:17 +0530 Subject: [PATCH 09/13] preserve server waypoint coordinates during MSColab merge --- mslib/msui/flighttrack.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/mslib/msui/flighttrack.py b/mslib/msui/flighttrack.py index c9afb130c..80130413f 100644 --- a/mslib/msui/flighttrack.py +++ b/mslib/msui/flighttrack.py @@ -142,12 +142,17 @@ class Waypoint: def __init__(self, lat=0., lon=0., flightlevel=0., location="", comments=""): self.location = location - locations = config_loader(dataset='locations') - if location in locations: - self.lat, self.lon = locations[location] - else: - self.lat = lat - self.lon = lon + + # Always trust explicitly provided coordinates first + self.lat = lat + self.lon = lon + + # Only resolve location name if coordinates are not provided + if (lat == 0.0 and lon == 0.0) and location: + locations = config_loader(dataset='locations') + if location in locations: + self.lat, self.lon = locations[location] + self.flightlevel = flightlevel self.pressure = thermolib.flightlevel2pressure(flightlevel * units.hft).magnitude self.distance_to_prev = 0. From 8630a5c544a886a1f698744c2cc8a3ba0d2b5f71 Mon Sep 17 00:00:00 2001 From: Aman Srivastava Date: Wed, 21 Jan 2026 19:47:18 +0530 Subject: [PATCH 10/13] fix server waypoint coordinates --- mslib/msui/flighttrack.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/mslib/msui/flighttrack.py b/mslib/msui/flighttrack.py index 80130413f..40574832a 100644 --- a/mslib/msui/flighttrack.py +++ b/mslib/msui/flighttrack.py @@ -147,8 +147,8 @@ def __init__(self, lat=0., lon=0., flightlevel=0., location="", comments=""): self.lat = lat self.lon = lon - # Only resolve location name if coordinates are not provided - if (lat == 0.0 and lon == 0.0) and location: + # Only resolve location name if coordinates are truly missing + if (lat is None or lon is None) and location: locations = config_loader(dataset='locations') if location in locations: self.lat, self.lon = locations[location] @@ -225,11 +225,6 @@ def __init__(self, name="", filename=None, waypoints=None, mscolab_mode=False, if waypoints: self.replace_waypoints(waypoints) - if self.revision is None: - self.revision = Revision( - revision_id=int(datetime.datetime.utcnow().timestamp()), - name=None - ) def load_settings(self): """ @@ -723,11 +718,7 @@ def load_from_xml_data(self, xml_content, name="Flight track"): revision_name = rev_el.getAttribute("name") or None self.revision = Revision(revision_id, revision_name) else: - # Backward compatibility - self.revision = Revision( - revision_id=int(datetime.datetime.utcnow().timestamp()), - name=None - ) + self.revision = None # Validate only waypoint structure, revision is optional metadata _waypoints_list = load_from_xml_data(xml_content, name) From 250d2e8863b351ccea1e27df48103c4ab4cc2124 Mon Sep 17 00:00:00 2001 From: Aman Srivastava Date: Wed, 4 Feb 2026 16:32:12 +0530 Subject: [PATCH 11/13] fixing flake8 --- mslib/msui/flighttrack.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mslib/msui/flighttrack.py b/mslib/msui/flighttrack.py index 40574832a..9aa8de677 100644 --- a/mslib/msui/flighttrack.py +++ b/mslib/msui/flighttrack.py @@ -225,7 +225,6 @@ def __init__(self, name="", filename=None, waypoints=None, mscolab_mode=False, if waypoints: self.replace_waypoints(waypoints) - def load_settings(self): """ Load settings from the file self.settingsfile. From 84c0708e9069fd6231f6eeee7c0b143cb0bdba62 Mon Sep 17 00:00:00 2001 From: Aman Srivastava Date: Wed, 4 Feb 2026 17:01:57 +0530 Subject: [PATCH 12/13] Restore original location resolution logic and deterministic revision fallback --- mslib/msui/flighttrack.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/mslib/msui/flighttrack.py b/mslib/msui/flighttrack.py index 9aa8de677..5efa3314e 100644 --- a/mslib/msui/flighttrack.py +++ b/mslib/msui/flighttrack.py @@ -143,15 +143,12 @@ class Waypoint: def __init__(self, lat=0., lon=0., flightlevel=0., location="", comments=""): self.location = location - # Always trust explicitly provided coordinates first - self.lat = lat - self.lon = lon - - # Only resolve location name if coordinates are truly missing - if (lat is None or lon is None) and location: - locations = config_loader(dataset='locations') - if location in locations: - self.lat, self.lon = locations[location] + locations = config_loader(dataset='locations') + if location in locations: + self.lat, self.lon = locations[location] + else: + self.lat = lat + self.lon = lon self.flightlevel = flightlevel self.pressure = thermolib.flightlevel2pressure(flightlevel * units.hft).magnitude @@ -717,7 +714,11 @@ def load_from_xml_data(self, xml_content, name="Flight track"): revision_name = rev_el.getAttribute("name") or None self.revision = Revision(revision_id, revision_name) else: - self.revision = None + # Backward compatibility: create deterministic default revision + self.revision = Revision( + revision_id=0, + name=None + ) # Validate only waypoint structure, revision is optional metadata _waypoints_list = load_from_xml_data(xml_content, name) From d1b3f923282653eeffacc70740f60b68cb63c7c7 Mon Sep 17 00:00:00 2001 From: Aman Srivastava Date: Mon, 9 Feb 2026 19:50:59 +0530 Subject: [PATCH 13/13] fix waypoint coordinate precedence for cross-platform merge determinism --- mslib/msui/flighttrack.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/mslib/msui/flighttrack.py b/mslib/msui/flighttrack.py index 5efa3314e..9cf2d17e6 100644 --- a/mslib/msui/flighttrack.py +++ b/mslib/msui/flighttrack.py @@ -143,12 +143,14 @@ class Waypoint: def __init__(self, lat=0., lon=0., flightlevel=0., location="", comments=""): self.location = location + # Prefer explicitly provided coordinates (e.g. from XML/server) + self.lat = lat + self.lon = lon + + # Only resolve from configured locations if coordinates were not meaningfully provided locations = config_loader(dataset='locations') - if location in locations: + if location in locations and (lat == 0.0 and lon == 0.0): self.lat, self.lon = locations[location] - else: - self.lat = lat - self.lon = lon self.flightlevel = flightlevel self.pressure = thermolib.flightlevel2pressure(flightlevel * units.hft).magnitude