Compare commits

..

3 Commits

Author SHA1 Message Date
Martin Tranberg
0cbec6477f fix: restore refresh operations broken by nav_gen regression (C-1, S-2)
Change nav_gen default from 0 to None in _fetch_list_contents_bg and
_finalize_list_loading. The guard is updated to only apply when a gen
is explicitly provided (`nav_gen is not None`).

Refresh call sites (lines 1515, 1529, 1538) pass no gen, so they receive
None and bypass the guard — their results are always applied. Navigation
calls still pass an explicit integer gen, so stale-navigation protection
remains fully intact.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 09:52:42 +02:00
Martin Tranberg
a55365259c refactor: remove redundant pulse_gauge calls and fix breadcrumb dedup (S1, S3)
- S1: drop pulse_gauge(True) from inside pagination while-loops in
  _fetch_sites_bg, _fetch_tree_children_bg, and _fetch_list_contents_bg;
  the gauge is already running from the call before the loop
- S3: remove the is_breadcrumb bypass on the early-return guard so
  clicking the already-active breadcrumb segment no longer fires a
  redundant network request

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 09:45:49 +02:00
Martin Tranberg
d529568798 fix: add navigation generation counter to prevent stale list overwrite (I1)
Introduces self._nav_gen, incremented on every _navigate_to_item_data
call. The counter is threaded through _fetch_list_contents_bg and
checked in _finalize_list_loading: if the user navigated away while a
fetch was in flight, the stale results are silently discarded instead of
overwriting the active folder view and re-sorting its items.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 09:44:39 +02:00

View File

@@ -671,6 +671,7 @@ class SharePointApp(wx.Frame):
self.current_items = [] # Gemmer graf-objekterne for rækkerne self.current_items = [] # Gemmer graf-objekterne for rækkerne
self.tree_item_data = {} # Mappenoder -> {type, id, name, drive_id, path} self.tree_item_data = {} # Mappenoder -> {type, id, name, drive_id, path}
self.current_path_data = [] # Gemmer data-objekterne for den nuværende sti (brødkrummer) self.current_path_data = [] # Gemmer data-objekterne for den nuværende sti (brødkrummer)
self._nav_gen = 0 # Incremented on each navigation to discard stale fetch results
self.tree_root = None self.tree_root = None
self.is_navigating_back = False self.is_navigating_back = False
self.active_edits = {} # item_id -> { "name": name, "event": Event, "waiting": bool } self.active_edits = {} # item_id -> { "name": name, "event": Event, "waiting": bool }
@@ -1722,7 +1723,6 @@ class SharePointApp(wx.Frame):
all_sites.extend(data.get('value', [])) all_sites.extend(data.get('value', []))
url = data.get('@odata.nextLink') url = data.get('@odata.nextLink')
self.set_status(f"{self.get_txt('status_fetching_sites')} ({len(all_sites)}...)") self.set_status(f"{self.get_txt('status_fetching_sites')} ({len(all_sites)}...)")
self.pulse_gauge(True)
else: else:
break break
@@ -1788,7 +1788,6 @@ class SharePointApp(wx.Frame):
res_data = res.json() res_data = res.json()
all_children.extend(res_data.get('value', [])) all_children.extend(res_data.get('value', []))
url = res_data.get('@odata.nextLink') url = res_data.get('@odata.nextLink')
self.pulse_gauge(True)
else: else:
break break
@@ -1856,8 +1855,8 @@ class SharePointApp(wx.Frame):
def _navigate_to_item_data(self, data, tree_item=None, is_breadcrumb=False): def _navigate_to_item_data(self, data, tree_item=None, is_breadcrumb=False):
try: try:
# Race-condition beskyttelse: Hvis vi allerede er der, så stop (undtagen ved brødkrumme-klik) # Race-condition beskyttelse: Hvis vi allerede er der, så stop
if not is_breadcrumb and getattr(self, 'current_path', None) == data.get("path"): if getattr(self, 'current_path', None) == data.get("path"):
return return
self.current_path = data["path"] self.current_path = data["path"]
@@ -1886,12 +1885,14 @@ class SharePointApp(wx.Frame):
self.list_ctrl.DeleteAllItems() self.list_ctrl.DeleteAllItems()
self.current_items = [] self.current_items = []
self.set_status(self.get_txt("status_loading_content")) self.set_status(self.get_txt("status_loading_content"))
threading.Thread(target=self._fetch_list_contents_bg, args=(data,), daemon=True).start() self._nav_gen += 1
gen = self._nav_gen
threading.Thread(target=self._fetch_list_contents_bg, args=(data, gen), daemon=True).start()
except RuntimeError: except RuntimeError:
pass pass
def _fetch_list_contents_bg(self, data): def _fetch_list_contents_bg(self, data, nav_gen=None):
if not self.ensure_valid_token(): return if not self.ensure_valid_token(): return
self.pulse_gauge(True) self.pulse_gauge(True)
items_data = [] items_data = []
@@ -1938,8 +1939,7 @@ class SharePointApp(wx.Frame):
items_data.extend(chunk_data) items_data.extend(chunk_data)
self.set_status(self.get_txt("status_loading_items").format(count=len(items_data))) self.set_status(self.get_txt("status_loading_items").format(count=len(items_data)))
self.pulse_gauge(True)
# Chunked UI Update # Chunked UI Update
if first_chunk: if first_chunk:
wx.CallAfter(self._populate_list_ctrl, chunk_data, data, finalize=False) wx.CallAfter(self._populate_list_ctrl, chunk_data, data, finalize=False)
@@ -1950,7 +1950,7 @@ class SharePointApp(wx.Frame):
url = res_data.get('@odata.nextLink') url = res_data.get('@odata.nextLink')
# Finalize # Finalize
wx.CallAfter(self._finalize_list_loading, items_data) wx.CallAfter(self._finalize_list_loading, items_data, nav_gen)
self.pulse_gauge(False) self.pulse_gauge(False)
def _append_list_items(self, items): def _append_list_items(self, items):
@@ -1973,8 +1973,10 @@ class SharePointApp(wx.Frame):
self.list_ctrl.SetItem(idx, 2, size_str) self.list_ctrl.SetItem(idx, 2, size_str)
self.list_ctrl.SetItem(idx, 3, item['modified']) self.list_ctrl.SetItem(idx, 3, item['modified'])
def _finalize_list_loading(self, items_data): def _finalize_list_loading(self, items_data, nav_gen=None):
if not self: return if not self: return
if nav_gen is not None and nav_gen != self._nav_gen:
return # User navigated away; discard stale results
self.current_items = items_data self.current_items = items_data
self.apply_sorting() self.apply_sorting()
self.set_status(self.get_txt("status_ready")) self.set_status(self.get_txt("status_ready"))