From e14012d2a55d197bb63f6a32c6f4c8f5dd136ec4 Mon Sep 17 00:00:00 2001 From: Martin Tranberg Date: Wed, 1 Apr 2026 10:17:58 +0200 Subject: [PATCH] feat: add path tracking to items and improve checkout/checkin logic with discard support --- sharepoint_browser.py | 61 +++++++++++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/sharepoint_browser.py b/sharepoint_browser.py index 5e61571..9b7084a 100644 --- a/sharepoint_browser.py +++ b/sharepoint_browser.py @@ -1751,7 +1751,8 @@ class SharePointApp(wx.Frame): self.list_ctrl.SetItem(i, 3, "") # Sidst ændret self.current_items.append({ "type": "SITE", "id": site['id'], "name": name, - "size": None, "modified": "", "web_url": site.get('webUrl') + "size": None, "modified": "", "web_url": site.get('webUrl'), + "path": ["SharePoint", name] }) def on_tree_expanding(self, event): @@ -1873,10 +1874,12 @@ class SharePointApp(wx.Frame): drives = res.json().get('value', []) drives.sort(key=lambda x: x.get('name', '').lower()) for drive in drives: + name = drive.get('name', '') items_data.append({ - "type": "DRIVE", "id": drive['id'], "name": drive.get('name', ''), + "type": "DRIVE", "id": drive['id'], "name": name, "drive_id": drive['id'], "modified": "", "size": None, - "web_url": drive.get('webUrl') + "web_url": drive.get('webUrl'), + "path": data['path'] + [name] }) elif data['type'] in ["DRIVE", "FOLDER"]: drive_id = data['drive_id'] @@ -1898,7 +1901,8 @@ class SharePointApp(wx.Frame): "drive_id": drive_id, "modified": modified, "size": item.get('size') if not is_folder else None, "web_url": item.get('webUrl'), - "hash": item.get('file', {}).get('hashes', {}).get('quickXorHash') if not is_folder else None + "hash": item.get('file', {}).get('hashes', {}).get('quickXorHash') if not is_folder else None, + "path": data['path'] + [item['name']] }) wx.CallAfter(self._populate_list_ctrl, items_data, data) @@ -2192,14 +2196,20 @@ class SharePointApp(wx.Frame): if remote_hash and ENABLE_HASH_VALIDATION: file_size = os.path.getsize(get_long_path(local_path)) if file_size <= (HASH_THRESHOLD_MB * 1024 * 1024): - original_hash = quickxorhash(local_path) - if original_hash != remote_hash: - logger.warning(f"Hash mismatch for {file_name}: local={original_hash}, remote={remote_hash}") + # Vi bruger fjern-hash direkte som vores 'original', hvis den er tilgængelig. + # Vi tjekker dog lige at downloaden rent faktisk matchede. + local_check = quickxorhash(local_path) + if local_check == remote_hash: + original_hash = remote_hash + logger.info(f"Download ok for {file_name}. Bruger XOR hash til ændrings-detektering.") + else: + logger.warning(f"Hash mismatch efter download af {file_name}!") self.show_info(f"Advarsel: Filens integritet kunne ikke bekræftes (XorHash mismatch)", wx.ICON_WARNING) - + original_hash = local_check + # Hvis vi ikke beregnede hash pga. størrelse eller manglende remote_hash, gør det nu for lokal detektering if original_hash is None: - # Her bruger vi SHA256 af hastighedsårsager hvis XOR ikke er absolut nødvendig til lokal sammenligning + # Her bruger vi SHA256 af hastighedsårsager til lokal sammenligning (før/efter) sha256 = hashlib.sha256() with open(get_long_path(local_path), 'rb') as f: while True: @@ -2207,9 +2217,16 @@ class SharePointApp(wx.Frame): if not chunk: break sha256.update(chunk) original_hash = "SHA256:" + sha256.hexdigest() + logger.info(f"Bruger lokal SHA256 til ændrings-detektering for {file_name}") # Checkout - requests.post(f"{base_url}/checkout", headers=self.headers) + is_checked_out = False + checkout_res = requests.post(f"{base_url}/checkout", headers=self.headers) + if checkout_res.status_code in [200, 201, 204]: + is_checked_out = True + logger.info(f"Fil {file_name} udtjekket succesfuldt.") + else: + logger.warning(f"Kunne ikke udtjekke {file_name} (Status: {checkout_res.status_code}). Fortsætter dog...") # 3. Åbn & Overvåg self.set_status(self.get_txt("msg_opening_file", name=file_name)) @@ -2259,18 +2276,29 @@ class SharePointApp(wx.Frame): new_hash = quickxorhash(local_path) if original_hash == new_hash: + logger.info(f"Ingen ændringer fundet i {file_name}. (Hash: {new_hash[:16]}...) Springer upload over.") self.set_status(self.get_txt("msg_file_unchanged")) + + if is_checked_out: + logger.info(f"Annullerer udtjekning (discardCheckout) for {file_name}...") + res = requests.post(f"{base_url}/discardCheckout", headers=self.headers) + if res.status_code in [200, 204]: + is_checked_out = False else: # 5. Upload (kun hvis ændret) + logger.info(f"Ændring fundet! Uploader {file_name}...") self.set_status(self.get_txt("msg_updating_changes")) with open(local_path, 'rb') as f: upload_res = requests.put(f"{base_url}/content", headers=self.headers, data=f) if upload_res.status_code not in [200, 201]: raise Exception(f"{self.get_txt('msg_update_failed_code', code=upload_res.status_code)}") - - # 6. Checkin (Uanset om ændret eller ej, for at frigive lås) - self.set_status(self.get_txt("msg_checking_in", name=file_name)) - requests.post(f"{base_url}/checkin", headers=self.headers, json={"comment": "SP Explorer Edit"}) + + # 6. Checkin (Kun hvis vi faktisk uploadede noget) + if is_checked_out: + self.set_status(self.get_txt("msg_checking_in", name=file_name)) + res = requests.post(f"{base_url}/checkin", headers=self.headers, json={"comment": "SP Explorer Edit"}) + if res.status_code in [200, 201, 204]: + is_checked_out = False # Oprydning: Slet fil og derefter mappe try: @@ -2287,6 +2315,11 @@ 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: + if is_checked_out: + # Emergency cleanup hvis vi stadig har fat i filen (f.eks. ved crash eller afbrydelse) + logger.info(f"Rydder op: Kalder discardCheckout for {file_name}...") + requests.post(f"{base_url}/discardCheckout", headers=self.headers) + if item_id in self.active_edits: del self.active_edits[item_id] self.update_edit_ui()