diff --git a/sharepoint_browser.py b/sharepoint_browser.py index 79df2b3..3ea4c59 100644 --- a/sharepoint_browser.py +++ b/sharepoint_browser.py @@ -102,7 +102,18 @@ STRINGS = { "msg_upload_success": "'{name}' uploadet succesfuldt.", "msg_upload_failed": "Upload af '{name}' fejlede med kode {code}", "msg_delete_failed": "Kunne ikke slette '{name}'. Stopper...", - "msg_deleted_status": "Slettet {count} af {total} emner." + "msg_deleted_status": "Slettet {count} af {total} emner.", + "msg_fetching_file": "Henter '{name}'...", + "msg_opening_file": "Åbner '{name}'...", + "msg_waiting_for_file": "Venter på '{name}'...", + "msg_editing_file": "Redigerer '{name}' - Luk for at gemme.", + "msg_file_unchanged": "Ingen ændringer fundet. Springer upload over.", + "msg_updating_changes": "Uploader ændringer...", + "msg_checking_in": "Tjekker '{name}' ind...", + "msg_update_success": "Succes! '{name}' er opdateret.", + "msg_update_failed_code": "Upload fejlede: {code}", + "msg_unknown_error": "Ukendt fejl", + "type_unknown": "Ukendt" }, "en": { "title": "SharePoint Explorer", @@ -155,7 +166,18 @@ STRINGS = { "msg_upload_success": "'{name}' uploaded successfully.", "msg_upload_failed": "Upload of '{name}' failed with status {code}", "msg_delete_failed": "Could not delete '{name}'. Stopping...", - "msg_deleted_status": "Deleted {count} of {total} items." + "msg_deleted_status": "Deleted {count} of {total} items.", + "msg_fetching_file": "Fetching '{name}'...", + "msg_opening_file": "Opening '{name}'...", + "msg_waiting_for_file": "Waiting for '{name}'...", + "msg_editing_file": "Editing '{name}' - Close window to save.", + "msg_file_unchanged": "No changes found. Skipping upload.", + "msg_updating_changes": "Uploading changes...", + "msg_checking_in": "Checking in '{name}'...", + "msg_update_success": "Success! '{name}' has been updated.", + "msg_update_failed_code": "Upload failed: {code}", + "msg_unknown_error": "Unknown error", + "type_unknown": "Unknown" } } @@ -269,18 +291,18 @@ class SharePointApp(wx.Frame): nav_panel = wx.Panel(panel) nav_hbox = wx.BoxSizer(wx.HORIZONTAL) - self.back_btn = wx.Button(nav_panel, label="← Tilbage", size=(100, 30)) + self.back_btn = wx.Button(nav_panel, label=self.get_txt("btn_back"), size=(100, 30)) self.back_btn.Disable() self.back_btn.Bind(wx.EVT_BUTTON, self.go_back) nav_hbox.Add(self.back_btn, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 10) - self.home_btn = wx.Button(nav_panel, label="🏠 Hjem", size=(100, 30)) + self.home_btn = wx.Button(nav_panel, label=self.get_txt("btn_home"), size=(100, 30)) self.home_btn.Disable() self.home_btn.Bind(wx.EVT_BUTTON, self.load_sites) nav_hbox.Add(self.home_btn, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) # NY KNAP: Gem ændringer (Vises kun ved redigering) - self.done_btn = wx.Button(nav_panel, label="💾 Gem ændringer i SharePoint", size=(200, 30)) + self.done_btn = wx.Button(nav_panel, label=self.get_txt("btn_save_changes"), size=(200, 30)) self.done_btn.SetBackgroundColour(wx.Colour(255, 69, 0)) # OrangeRed self.done_btn.SetForegroundColour(wx.WHITE) self.done_btn.Hide() @@ -288,17 +310,17 @@ class SharePointApp(wx.Frame): nav_hbox.Add(self.done_btn, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 10) # NYE KNAPPER: Upload og Ny Mappe (Vises kun når man er inde i et drev/mappe) - self.upload_btn = wx.Button(nav_panel, label="📤 Upload Fil", size=(120, 30)) + self.upload_btn = wx.Button(nav_panel, label=self.get_txt("btn_upload_file"), size=(120, 30)) self.upload_btn.Hide() self.upload_btn.Bind(wx.EVT_BUTTON, self.on_upload_clicked) nav_hbox.Add(self.upload_btn, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) - self.upload_folder_btn = wx.Button(nav_panel, label="📁 Upload Mappe", size=(120, 30)) + self.upload_folder_btn = wx.Button(nav_panel, label=self.get_txt("btn_upload_folder"), size=(120, 30)) self.upload_folder_btn.Hide() self.upload_folder_btn.Bind(wx.EVT_BUTTON, self.on_upload_folder_clicked) nav_hbox.Add(self.upload_folder_btn, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) - self.new_folder_btn = wx.Button(nav_panel, label="➕ Ny Mappe", size=(100, 30)) + self.new_folder_btn = wx.Button(nav_panel, label=self.get_txt("btn_new_folder"), size=(100, 30)) self.new_folder_btn.Hide() self.new_folder_btn.Bind(wx.EVT_BUTTON, self.on_new_folder_clicked) nav_hbox.Add(self.new_folder_btn, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5) @@ -340,10 +362,10 @@ class SharePointApp(wx.Frame): # Right side: File Area - ListCtrl self.list_ctrl = wx.ListCtrl(self.splitter, style=wx.LC_REPORT | wx.BORDER_SUNKEN) self.list_ctrl.AssignImageList(self.image_list, wx.IMAGE_LIST_SMALL) - self.list_ctrl.InsertColumn(0, "Navn", width=450) - self.list_ctrl.InsertColumn(1, "Type", width=120) - self.list_ctrl.InsertColumn(2, "Størrelse", width=80) - self.list_ctrl.InsertColumn(3, "Sidst ændret", width=180) + self.list_ctrl.InsertColumn(0, self.get_txt("col_name"), width=450) + self.list_ctrl.InsertColumn(1, self.get_txt("col_type"), width=120) + self.list_ctrl.InsertColumn(2, self.get_txt("col_size"), width=80) + self.list_ctrl.InsertColumn(3, self.get_txt("col_modified"), width=180) self.list_ctrl.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.on_item_activated) self.list_ctrl.Bind(wx.EVT_CONTEXT_MENU, self.on_right_click) @@ -358,7 +380,7 @@ class SharePointApp(wx.Frame): # 4. STATUS BAR self.status_bar = self.CreateStatusBar() - self.status_bar.SetStatusText("Klar") + self.status_bar.SetStatusText(self.get_txt("status_ready")) panel.SetSizer(vbox) self.Layout() @@ -422,6 +444,7 @@ class SharePointApp(wx.Frame): threading.Thread(target=self._delete_multiple_bg, args=(items,), daemon=True).start() def _delete_multiple_bg(self, items): + if not self.ensure_valid_token(): return self.lock_ui(True) count = 0 total = len(items) @@ -450,6 +473,7 @@ class SharePointApp(wx.Frame): threading.Thread(target=self._upload_file_bg, args=(path, self.current_drive_id, self.current_folder_id), daemon=True).start() def _upload_file_bg(self, local_path, drive_id, parent_id): + if not self.ensure_valid_token(): return filename = os.path.basename(local_path) self.set_status(self.get_txt("msg_uploading", name=filename)) # Simpel upload (virker op til 4MB) @@ -474,6 +498,7 @@ class SharePointApp(wx.Frame): threading.Thread(target=self._upload_folder_bg, args=(path, self.current_drive_id, self.current_folder_id), daemon=True).start() def _upload_folder_bg(self, local_dir, drive_id, parent_id): + if not self.ensure_valid_token(): return dirname = os.path.basename(local_dir) self.set_status(self.get_txt("msg_creating_folder", name=dirname)) @@ -518,6 +543,7 @@ class SharePointApp(wx.Frame): dlg.Destroy() def _create_folder_bg(self, name, drive_id, parent_id): + if not self.ensure_valid_token(): return self.set_status(self.get_txt("msg_creating_folder", name=name)) folder_id = self._create_folder_sync(name, drive_id, parent_id) if folder_id: @@ -535,6 +561,7 @@ class SharePointApp(wx.Frame): dlg.Destroy() def _rename_item_bg(self, item, new_name): + if not self.ensure_valid_token(): return self.set_status(f"{self.get_txt('msg_rename')}...") url = f"https://graph.microsoft.com/v1.0/drives/{item['drive_id']}/items/{item['id']}" body = {"name": new_name} @@ -648,7 +675,7 @@ class SharePointApp(wx.Frame): curr = self.tree_ctrl.GetItemParent(curr) # Start ikon/label - self._add_path_segment("📍 SharePoint", "ROOT") + self._add_path_segment("📍 " + self.get_txt("title"), "ROOT") for node in nodes: arrow = wx.StaticText(self.path_panel, label=" > ") @@ -677,6 +704,23 @@ class SharePointApp(wx.Frame): self.path_sizer.Add(btn, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 2) + def ensure_valid_token(self): + """Sikrer at vi har et gyldigt token. Returnerer True hvis OK.""" + try: + app = msal.PublicClientApplication(CLIENT_ID, authority=AUTHORITY) + accounts = app.get_accounts() + if not accounts: + return False + + result = app.acquire_token_silent(SCOPES, account=accounts[0]) + if result and "access_token" in result: + self.access_token = result["access_token"] + self.headers = {'Authorization': f'Bearer {self.access_token}'} + return True + except: + pass + return False + def login(self, event): self.set_status(self.get_txt("status_logging_in")) app = msal.PublicClientApplication(CLIENT_ID, authority=AUTHORITY) @@ -684,7 +728,8 @@ class SharePointApp(wx.Frame): result = None if accounts: result = app.acquire_token_silent(SCOPES, account=accounts[0]) - if not result: + + if not result or "access_token" not in result: result = app.acquire_token_interactive(scopes=SCOPES) if "access_token" in result: @@ -697,7 +742,7 @@ class SharePointApp(wx.Frame): self.load_sites() else: self.set_status(self.get_txt("status_login_failed")) - wx.MessageBox(result.get("error_description", "Unknown error"), self.get_txt("msg_error"), wx.OK | wx.ICON_ERROR) + wx.MessageBox(result.get("error_description", self.get_txt("msg_unknown_error")), self.get_txt("msg_error"), wx.OK | wx.ICON_ERROR) def load_sites(self, event=None): self.set_status(self.get_txt("status_fetching_sites")) @@ -715,6 +760,7 @@ class SharePointApp(wx.Frame): threading.Thread(target=self._fetch_sites_bg, daemon=True).start() def _fetch_sites_bg(self): + if not self.ensure_valid_token(): return url = "https://graph.microsoft.com/v1.0/sites?search=*" res = requests.get(url, headers=self.headers) if res.status_code == 200: @@ -722,7 +768,7 @@ class SharePointApp(wx.Frame): sites.sort(key=lambda x: x.get('displayName', x.get('name', '')).lower()) wx.CallAfter(self._populate_sites_tree, sites) else: - self.set_status("Kunne ikke hente sites.") + self.set_status(self.get_txt("msg_unknown_error")) def _populate_sites_tree(self, sites): self.set_status(f"{len(sites)} sites.") @@ -756,6 +802,7 @@ class SharePointApp(wx.Frame): threading.Thread(target=self._fetch_tree_children_bg, args=(item, data), daemon=True).start() def _fetch_tree_children_bg(self, parent_node, data): + if not self.ensure_valid_token(): return if data['type'] == "SITE": url = f"https://graph.microsoft.com/v1.0/sites/{data['id']}/drives" res = requests.get(url, headers=self.headers) @@ -786,7 +833,7 @@ class SharePointApp(wx.Frame): target_node = None for drive in drives: - name = drive.get('name', 'Ukendt') + name = drive.get('name', self.get_txt("type_unknown")) drive_id = drive['id'] node = self.tree_ctrl.AppendItem(parent_node, name, image=self.idx_drive) self.tree_item_data[node] = { @@ -849,6 +896,7 @@ class SharePointApp(wx.Frame): pass def _fetch_list_contents_bg(self, data): + if not self.ensure_valid_token(): return items_data = [] if data['type'] == "SITE": url = f"https://graph.microsoft.com/v1.0/sites/{data['id']}/drives" @@ -971,6 +1019,7 @@ class SharePointApp(wx.Frame): self.is_navigating_back = False def process_file(self, item_id, file_name): + if not self.ensure_valid_token(): return self.is_editing = True self.lock_ui(True) try: @@ -988,10 +1037,10 @@ class SharePointApp(wx.Frame): local_path = os.path.join(working_dir, file_name) # 2. Download - self.set_status(f"Henter '{file_name}'...") + self.set_status(self.get_txt("msg_fetching_file", name=file_name)) res = requests.get(f"{base_url}/content", headers=self.headers) if res.status_code != 200: - raise Exception(f"Kunne ikke hente fil: {res.status_code}") + raise Exception(f"{self.get_txt('msg_unknown_error')}: {res.status_code}") with open(local_path, 'wb') as f: f.write(res.content) @@ -1003,11 +1052,11 @@ class SharePointApp(wx.Frame): requests.post(f"{base_url}/checkout", headers=self.headers) # 3. Åbn & Overvåg - self.set_status(f"Åbner '{file_name}'...") + self.set_status(self.get_txt("msg_opening_file", name=file_name)) os.startfile(local_path) locked = False - self.set_status(f"Venter på '{file_name}'...") + self.set_status(self.get_txt("msg_waiting_for_file", name=file_name)) for _ in range(10): time.sleep(1) try: @@ -1017,7 +1066,7 @@ class SharePointApp(wx.Frame): break if locked: - self.set_status(f"Redigerer '{file_name}' - Luk for at gemme.") + self.set_status(self.get_txt("msg_editing_file", name=file_name)) while True: time.sleep(2) try: @@ -1026,7 +1075,7 @@ class SharePointApp(wx.Frame): except OSError: pass else: - self.set_status("Fillås ikke detekteret.") + 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) @@ -1037,17 +1086,17 @@ class SharePointApp(wx.Frame): # 4. Tjek om noget er ændret new_hash = get_file_hash(local_path) if original_hash == new_hash: - self.set_status("Ingen ændringer fundet. Springer upload over.") + self.set_status(self.get_txt("msg_file_unchanged")) else: # 5. Upload (kun hvis ændret) - self.set_status(f"Uploader ændringer...") + 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"Upload fejlede: {upload_res.status_code}") + 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(f"Tjekker '{file_name}' ind...") + 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"}) # Oprydning: Slet fil og derefter mappe @@ -1057,13 +1106,13 @@ class SharePointApp(wx.Frame): except: pass - self.set_status(f"Succes! '{file_name}' er opdateret.") - self.show_info(f"Filen '{file_name}' er gemt og tjekket ind korrekt.", wx.ICON_INFORMATION) + self.set_status(self.get_txt("msg_update_success", name=file_name)) + self.show_info(self.get_txt("msg_update_success", name=file_name), wx.ICON_INFORMATION) self._refresh_current_view() except Exception as e: - self.set_status(f"Fejl: {str(e)}") - self.show_info(f"Der skete en fejl: {e}", wx.ICON_ERROR) + 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)