refactor: replace global editing lock with active_edits dictionary to support concurrent file editing
This commit is contained in:
@@ -260,7 +260,7 @@ class SharePointApp(wx.Frame):
|
|||||||
self.tree_item_data = {} # Mappenoder -> {type, id, name, drive_id, path}
|
self.tree_item_data = {} # Mappenoder -> {type, id, name, drive_id, path}
|
||||||
self.tree_root = None
|
self.tree_root = None
|
||||||
self.is_navigating_back = False
|
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)
|
# System Ikoner (ArtProvider - mest basale for kompatibilitet)
|
||||||
self.image_list = wx.ImageList(16, 16)
|
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
|
self.idx_file = self.image_list.Add(wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE, wx.ART_OTHER, (16, 16))) # File
|
||||||
|
|
||||||
# Threading/Sync til filredigering
|
# Threading/Sync til filredigering
|
||||||
self.edit_wait_event = threading.Event()
|
|
||||||
|
|
||||||
# MSAL Cache
|
# MSAL Cache
|
||||||
self.msal_app = msal.PublicClientApplication(CLIENT_ID, authority=AUTHORITY)
|
self.msal_app = msal.PublicClientApplication(CLIENT_ID, authority=AUTHORITY)
|
||||||
@@ -440,7 +439,7 @@ class SharePointApp(wx.Frame):
|
|||||||
|
|
||||||
if item['type'] == "FILE":
|
if item['type'] == "FILE":
|
||||||
edit_item = menu.Append(wx.ID_ANY, self.get_txt("msg_edit_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"]:
|
if item['type'] in ["FILE", "FOLDER"]:
|
||||||
rename_item = menu.Append(wx.ID_ANY, f"{self.get_txt('msg_rename')} '{item['name']}'")
|
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()
|
menu.Destroy()
|
||||||
|
|
||||||
def on_tree_right_click(self, event):
|
def on_tree_right_click(self, event):
|
||||||
if self.is_editing: return
|
|
||||||
item = event.GetItem()
|
item = event.GetItem()
|
||||||
if not item.IsOk() or item == self.tree_root: return
|
if not item.IsOk() or item == self.tree_root: return
|
||||||
|
|
||||||
@@ -697,10 +695,34 @@ class SharePointApp(wx.Frame):
|
|||||||
wx.CallAfter(_do)
|
wx.CallAfter(_do)
|
||||||
|
|
||||||
def on_done_editing_clicked(self, event):
|
def on_done_editing_clicked(self, event):
|
||||||
print("[DEBUG] 'Jeg er færdig'-knap klikket.")
|
waiting_files = [fid for fid, d in self.active_edits.items() if d.get("waiting")]
|
||||||
self.set_status("Knap trykket - Uploader nu...")
|
if not waiting_files:
|
||||||
self.edit_wait_event.set()
|
return
|
||||||
self.info_bar.Dismiss()
|
|
||||||
|
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):
|
def on_language_changed(self, event):
|
||||||
selection = self.lang_choice.GetSelection()
|
selection = self.lang_choice.GetSelection()
|
||||||
@@ -743,7 +765,7 @@ class SharePointApp(wx.Frame):
|
|||||||
self._refresh_current_view() # Gendanner list-item tekster (Mappe/Fil)
|
self._refresh_current_view() # Gendanner list-item tekster (Mappe/Fil)
|
||||||
|
|
||||||
def on_close_window(self, event):
|
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)
|
self.show_info(self.get_txt("msg_edit_warning"), wx.ICON_WARNING, auto_hide=False)
|
||||||
return
|
return
|
||||||
event.Skip()
|
event.Skip()
|
||||||
@@ -759,7 +781,6 @@ class SharePointApp(wx.Frame):
|
|||||||
wx.CallAfter(_do)
|
wx.CallAfter(_do)
|
||||||
|
|
||||||
def on_refresh(self, event=None):
|
def on_refresh(self, event=None):
|
||||||
if self.is_editing: return
|
|
||||||
|
|
||||||
selected = self.tree_ctrl.GetSelection()
|
selected = self.tree_ctrl.GetSelection()
|
||||||
if not selected.IsOk() or selected == self.tree_root:
|
if not selected.IsOk() or selected == self.tree_root:
|
||||||
@@ -1009,7 +1030,7 @@ class SharePointApp(wx.Frame):
|
|||||||
self.tree_ctrl.SelectItem(target_node)
|
self.tree_ctrl.SelectItem(target_node)
|
||||||
|
|
||||||
def on_tree_selected(self, event):
|
def on_tree_selected(self, event):
|
||||||
if not self or self.is_editing: return
|
if not self: return
|
||||||
item = event.GetItem()
|
item = event.GetItem()
|
||||||
data = self.tree_item_data.get(item)
|
data = self.tree_item_data.get(item)
|
||||||
if not data:
|
if not data:
|
||||||
@@ -1120,15 +1141,13 @@ class SharePointApp(wx.Frame):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def on_item_activated(self, event):
|
def on_item_activated(self, event):
|
||||||
if self.is_editing: return
|
|
||||||
item_idx = event.GetIndex()
|
item_idx = event.GetIndex()
|
||||||
item = self.current_items[item_idx]
|
item = self.current_items[item_idx]
|
||||||
|
|
||||||
if item['type'] in ["SITE", "DRIVE", "FOLDER"]:
|
if item['type'] in ["SITE", "DRIVE", "FOLDER"]:
|
||||||
self._sync_tree_selection(item['id'])
|
self._sync_tree_selection(item['id'])
|
||||||
elif item['type'] == "FILE":
|
elif item['type'] == "FILE":
|
||||||
self.current_drive_id = item['drive_id']
|
self.open_file(item)
|
||||||
threading.Thread(target=self.process_file, args=(item['id'], item['name']), daemon=True).start()
|
|
||||||
|
|
||||||
def _sync_tree_selection(self, target_id):
|
def _sync_tree_selection(self, target_id):
|
||||||
selected = self.tree_ctrl.GetSelection()
|
selected = self.tree_ctrl.GetSelection()
|
||||||
@@ -1151,7 +1170,6 @@ class SharePointApp(wx.Frame):
|
|||||||
child, cookie = self.tree_ctrl.GetNextChild(selected, cookie)
|
child, cookie = self.tree_ctrl.GetNextChild(selected, cookie)
|
||||||
|
|
||||||
def go_back(self, event=None):
|
def go_back(self, event=None):
|
||||||
if self.is_editing: return
|
|
||||||
if len(self.history) > 1:
|
if len(self.history) > 1:
|
||||||
self.history.pop() # Remove current
|
self.history.pop() # Remove current
|
||||||
prev_item = self.history[-1] # Peak at previous
|
prev_item = self.history[-1] # Peak at previous
|
||||||
@@ -1159,15 +1177,58 @@ class SharePointApp(wx.Frame):
|
|||||||
self.tree_ctrl.SelectItem(prev_item)
|
self.tree_ctrl.SelectItem(prev_item)
|
||||||
self.is_navigating_back = False
|
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
|
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:
|
try:
|
||||||
# 1. Lokation info
|
# 1. Lokation info
|
||||||
site_id = self.current_site_id
|
base_url = f"https://graph.microsoft.com/v1.0/drives/{drive_id}/items/{item_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}"
|
|
||||||
|
|
||||||
# Unik undermappe baseret på ID, men brug originalt filnavn indeni
|
# Unik undermappe baseret på ID, men brug originalt filnavn indeni
|
||||||
item_hash = hashlib.md5(item_id.encode()).hexdigest()[:8]
|
item_hash = hashlib.md5(item_id.encode()).hexdigest()[:8]
|
||||||
@@ -1217,12 +1278,15 @@ class SharePointApp(wx.Frame):
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
self.set_status(self.get_txt("msg_waiting_for_file", name=file_name))
|
self.set_status(self.get_txt("msg_waiting_for_file", name=file_name))
|
||||||
self.edit_wait_event.clear()
|
edit_event.clear()
|
||||||
wx.CallAfter(self.done_btn.Show)
|
self.active_edits[item_id]["waiting"] = True
|
||||||
wx.CallAfter(self.Layout)
|
self.update_edit_ui()
|
||||||
self.edit_wait_event.wait()
|
|
||||||
wx.CallAfter(self.done_btn.Hide)
|
edit_event.wait()
|
||||||
wx.CallAfter(self.Layout)
|
|
||||||
|
if item_id in self.active_edits:
|
||||||
|
self.active_edits[item_id]["waiting"] = False
|
||||||
|
self.update_edit_ui()
|
||||||
|
|
||||||
# 4. Tjek om noget er ændret
|
# 4. Tjek om noget er ændret
|
||||||
new_hash = get_file_hash(local_path)
|
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.set_status(f"{self.get_txt('msg_error')}: {str(e)}")
|
||||||
self.show_info(f"{self.get_txt('msg_error')}: {e}", wx.ICON_ERROR)
|
self.show_info(f"{self.get_txt('msg_error')}: {e}", wx.ICON_ERROR)
|
||||||
finally:
|
finally:
|
||||||
self.is_editing = False
|
if item_id in self.active_edits:
|
||||||
self.lock_ui(False)
|
del self.active_edits[item_id]
|
||||||
|
self.update_edit_ui()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app = wx.App()
|
app = wx.App()
|
||||||
|
|||||||
Reference in New Issue
Block a user