refactor: replace global editing lock with active_edits dictionary to support concurrent file editing

This commit is contained in:
Martin Tranberg
2026-03-31 16:21:02 +02:00
parent 0d8407f624
commit 86ff8043f1

View File

@@ -260,7 +260,7 @@ class SharePointApp(wx.Frame):
self.tree_item_data = {} # Mappenoder -> {type, id, name, drive_id, path}
self.tree_root = None
self.is_navigating_back = False
self.is_editing = False # Låse-state ved filredigering
self.active_edits = {} # item_id -> { "name": name, "event": Event, "waiting": bool }
# System Ikoner (ArtProvider - mest basale for kompatibilitet)
self.image_list = wx.ImageList(16, 16)
@@ -270,7 +270,6 @@ class SharePointApp(wx.Frame):
self.idx_file = self.image_list.Add(wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE, wx.ART_OTHER, (16, 16))) # File
# Threading/Sync til filredigering
self.edit_wait_event = threading.Event()
# MSAL Cache
self.msal_app = msal.PublicClientApplication(CLIENT_ID, authority=AUTHORITY)
@@ -440,7 +439,7 @@ class SharePointApp(wx.Frame):
if item['type'] == "FILE":
edit_item = menu.Append(wx.ID_ANY, self.get_txt("msg_edit_file"))
self.Bind(wx.EVT_MENU, lambda e: threading.Thread(target=self.process_file, args=(item['id'], item['name']), daemon=True).start(), edit_item)
self.Bind(wx.EVT_MENU, lambda e, i=item: self.open_file(i), edit_item)
if item['type'] in ["FILE", "FOLDER"]:
rename_item = menu.Append(wx.ID_ANY, f"{self.get_txt('msg_rename')} '{item['name']}'")
@@ -475,7 +474,6 @@ class SharePointApp(wx.Frame):
menu.Destroy()
def on_tree_right_click(self, event):
if self.is_editing: return
item = event.GetItem()
if not item.IsOk() or item == self.tree_root: return
@@ -697,10 +695,34 @@ class SharePointApp(wx.Frame):
wx.CallAfter(_do)
def on_done_editing_clicked(self, event):
print("[DEBUG] 'Jeg er færdig'-knap klikket.")
self.set_status("Knap trykket - Uploader nu...")
self.edit_wait_event.set()
self.info_bar.Dismiss()
waiting_files = [fid for fid, d in self.active_edits.items() if d.get("waiting")]
if not waiting_files:
return
if len(waiting_files) == 1:
fid = waiting_files[0]
self.active_edits[fid]["event"].set()
else:
# Show menu to let user pick which file is finished
menu = wx.Menu()
for fid in waiting_files:
name = self.active_edits[fid]["name"]
item = menu.Append(wx.ID_ANY, f"Gem '{name}'")
# closure to capture fid
def make_handler(f_id):
return lambda e: self.active_edits[f_id]["event"].set()
self.Bind(wx.EVT_MENU, make_handler(fid), item)
menu.AppendSeparator()
item_all = menu.Append(wx.ID_ANY, "Gem alle")
def handle_all(e):
for f in waiting_files:
if f in self.active_edits:
self.active_edits[f]["event"].set()
self.Bind(wx.EVT_MENU, handle_all, item_all)
self.PopupMenu(menu)
menu.Destroy()
def on_language_changed(self, event):
selection = self.lang_choice.GetSelection()
@@ -743,7 +765,7 @@ class SharePointApp(wx.Frame):
self._refresh_current_view() # Gendanner list-item tekster (Mappe/Fil)
def on_close_window(self, event):
if self.is_editing:
if self.active_edits:
self.show_info(self.get_txt("msg_edit_warning"), wx.ICON_WARNING, auto_hide=False)
return
event.Skip()
@@ -759,7 +781,6 @@ class SharePointApp(wx.Frame):
wx.CallAfter(_do)
def on_refresh(self, event=None):
if self.is_editing: return
selected = self.tree_ctrl.GetSelection()
if not selected.IsOk() or selected == self.tree_root:
@@ -1009,7 +1030,7 @@ class SharePointApp(wx.Frame):
self.tree_ctrl.SelectItem(target_node)
def on_tree_selected(self, event):
if not self or self.is_editing: return
if not self: return
item = event.GetItem()
data = self.tree_item_data.get(item)
if not data:
@@ -1120,15 +1141,13 @@ class SharePointApp(wx.Frame):
pass
def on_item_activated(self, event):
if self.is_editing: return
item_idx = event.GetIndex()
item = self.current_items[item_idx]
if item['type'] in ["SITE", "DRIVE", "FOLDER"]:
self._sync_tree_selection(item['id'])
elif item['type'] == "FILE":
self.current_drive_id = item['drive_id']
threading.Thread(target=self.process_file, args=(item['id'], item['name']), daemon=True).start()
self.open_file(item)
def _sync_tree_selection(self, target_id):
selected = self.tree_ctrl.GetSelection()
@@ -1151,7 +1170,6 @@ class SharePointApp(wx.Frame):
child, cookie = self.tree_ctrl.GetNextChild(selected, cookie)
def go_back(self, event=None):
if self.is_editing: return
if len(self.history) > 1:
self.history.pop() # Remove current
prev_item = self.history[-1] # Peak at previous
@@ -1159,15 +1177,58 @@ class SharePointApp(wx.Frame):
self.tree_ctrl.SelectItem(prev_item)
self.is_navigating_back = False
def process_file(self, item_id, file_name):
def open_file(self, item):
item_id = item['id']
file_name = item['name']
drive_id = item['drive_id']
if item_id in self.active_edits:
self.show_info(f"'{file_name}' er allerede ved at blive redigeret.", wx.ICON_INFORMATION)
return
if len(self.active_edits) >= 10:
wx.MessageBox("Du kan kun have 10 filer åbne til redigering ad gangen.", "Maksimum grænse nået", wx.OK | wx.ICON_WARNING)
return
threading.Thread(target=self.process_file, args=(item_id, file_name, drive_id), daemon=True).start()
def update_edit_ui(self):
def _do():
try:
if not self: return
count = len(self.active_edits)
waiting_count = sum(1 for d in self.active_edits.values() if d.get("waiting"))
if waiting_count > 0:
self.done_btn.SetLabel(f"{self.get_txt('btn_save_changes')} ({waiting_count})")
self.done_btn.Show()
else:
self.done_btn.Hide()
# Opdater statusbesked hvis der er aktive opgaver
if count > 0:
status_msg = f"Aktive filer: {count}"
if waiting_count > 0:
status_msg += f" ({waiting_count} venter på gem)"
self.set_status(status_msg)
else:
self.set_status(self.get_txt("status_ready"))
self.Layout()
except RuntimeError:
pass
wx.CallAfter(_do)
def process_file(self, item_id, file_name, drive_id):
if not self.ensure_valid_token(): return
self.is_editing = True
self.lock_ui(True)
edit_event = threading.Event()
self.active_edits[item_id] = {"name": file_name, "event": edit_event, "waiting": False}
self.update_edit_ui()
try:
# 1. Lokation info
site_id = self.current_site_id
drive_id = self.current_drive_id
base_url = f"https://graph.microsoft.com/v1.0/sites/{site_id}/drives/{drive_id}/items/{item_id}"
base_url = f"https://graph.microsoft.com/v1.0/drives/{drive_id}/items/{item_id}"
# Unik undermappe baseret på ID, men brug originalt filnavn indeni
item_hash = hashlib.md5(item_id.encode()).hexdigest()[:8]
@@ -1217,12 +1278,15 @@ class SharePointApp(wx.Frame):
pass
else:
self.set_status(self.get_txt("msg_waiting_for_file", name=file_name))
self.edit_wait_event.clear()
wx.CallAfter(self.done_btn.Show)
wx.CallAfter(self.Layout)
self.edit_wait_event.wait()
wx.CallAfter(self.done_btn.Hide)
wx.CallAfter(self.Layout)
edit_event.clear()
self.active_edits[item_id]["waiting"] = True
self.update_edit_ui()
edit_event.wait()
if item_id in self.active_edits:
self.active_edits[item_id]["waiting"] = False
self.update_edit_ui()
# 4. Tjek om noget er ændret
new_hash = get_file_hash(local_path)
@@ -1255,8 +1319,9 @@ class SharePointApp(wx.Frame):
self.set_status(f"{self.get_txt('msg_error')}: {str(e)}")
self.show_info(f"{self.get_txt('msg_error')}: {e}", wx.ICON_ERROR)
finally:
self.is_editing = False
self.lock_ui(False)
if item_id in self.active_edits:
del self.active_edits[item_id]
self.update_edit_ui()
if __name__ == "__main__":
app = wx.App()