diff --git a/sharepoint_browser.py b/sharepoint_browser.py index 193ffc2..8133b73 100644 --- a/sharepoint_browser.py +++ b/sharepoint_browser.py @@ -87,6 +87,9 @@ STRINGS = { "msg_rename_prompt": "Indtast det nye navn for '{name}':", "msg_rename_title": "Omdøb emne", "msg_open_browser": "Åbn i browser", + "msg_download": "Download", + "msg_downloading_to": "Downloader '{name}' til '{path}'...", + "msg_download_done": "'{name}' downloadet færdig.", "msg_upload_here": "Upload fil her", "msg_upload_folder_here": "Upload mappe her", "msg_new_folder_here": "Opret ny mappe her", @@ -153,6 +156,9 @@ STRINGS = { "msg_rename_prompt": "Enter new name for '{name}':", "msg_rename_title": "Rename item", "msg_open_browser": "Open in browser", + "msg_download": "Download", + "msg_downloading_to": "Downloading '{name}' to '{path}'...", + "msg_download_done": "'{name}' download finished.", "msg_upload_here": "Upload file here", "msg_upload_folder_here": "Upload folder here", "msg_new_folder_here": "Create new folder here", @@ -412,7 +418,11 @@ class SharePointApp(wx.Frame): if item.get("web_url"): browser_item = menu.Append(wx.ID_ANY, self.get_txt("msg_open_browser")) self.Bind(wx.EVT_MENU, lambda e: webbrowser.open(item["web_url"]), browser_item) - menu.AppendSeparator() + + download_item = menu.Append(wx.ID_ANY, self.get_txt("msg_download")) + self.Bind(wx.EVT_MENU, lambda e: self.on_download_clicked(item), download_item) + + menu.AppendSeparator() if item['type'] == "FILE": edit_item = menu.Append(wx.ID_ANY, self.get_txt("msg_edit_file")) @@ -587,6 +597,60 @@ class SharePointApp(wx.Frame): self.set_status(self.get_txt("msg_error")) wx.CallAfter(wx.MessageBox, f"Rename failed: {res.status_code}", self.get_txt("msg_error"), wx.OK | wx.ICON_ERROR) + def on_download_clicked(self, item): + if not self.ensure_valid_token(): return + + if item['type'] == "FILE": + with wx.FileDialog(self, self.get_txt("msg_select_file"), defaultFile=item['name'], style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) as fd: + if fd.ShowModal() == wx.ID_OK: + path = fd.GetPath() + threading.Thread(target=self._download_file_bg_task, args=(item, path), daemon=True).start() + else: + # Mappe eller Drev + with wx.DirDialog(self, self.get_txt("msg_select_folder"), style=wx.DD_DEFAULT_STYLE | wx.DD_DIR_MUST_EXIST) as dd: + if dd.ShowModal() == wx.ID_OK: + parent_path = dd.GetPath() + dest_path = os.path.join(parent_path, item['name']) + threading.Thread(target=self._download_folder_bg_task, args=(item, dest_path), daemon=True).start() + + def _download_file_bg_task(self, item, dest_path): + if not self.ensure_valid_token(): return + self.set_status(self.get_txt("msg_downloading_to", name=item['name'], path=dest_path)) + if self._download_file_sync_call(item['drive_id'], item['id'], dest_path): + self.set_status(self.get_txt("msg_download_done", name=item['name'])) + else: + self.set_status(self.get_txt("msg_error")) + + def _download_file_sync_call(self, drive_id, item_id, dest_path): + url = f"https://graph.microsoft.com/v1.0/drives/{drive_id}/items/{item_id}/content" + res = requests.get(url, headers=self.headers) + if res.status_code == 200: + with open(dest_path, 'wb') as f: + f.write(res.content) + return True + return False + + def _download_folder_bg_task(self, item, dest_path): + if not self.ensure_valid_token(): return + self.set_status(self.get_txt("msg_downloading_to", name=item['name'], path=dest_path)) + self._download_folder_recursive_sync(item['drive_id'], item['id'], dest_path) + self.set_status(self.get_txt("msg_download_done", name=item['name'])) + + def _download_folder_recursive_sync(self, drive_id, folder_id, local_dir): + if not os.path.exists(local_dir): + os.makedirs(local_dir) + + url = f"https://graph.microsoft.com/v1.0/drives/{drive_id}/items/{folder_id}/children" + res = requests.get(url, headers=self.headers) + if res.status_code == 200: + items = res.json().get('value', []) + for item in items: + item_path = os.path.join(local_dir, item['name']) + if 'folder' in item: + self._download_folder_recursive_sync(drive_id, item['id'], item_path) + else: + self._download_file_sync_call(drive_id, item['id'], item_path) + def set_status(self, text): wx.CallAfter(self.status_bar.SetStatusText, text)