Compare commits
2 Commits
ed931f2088
...
f7cebfc489
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7cebfc489 | ||
|
|
b1c46fbace |
@@ -8,6 +8,7 @@ import requests
|
||||
import msal
|
||||
import wx
|
||||
import wx.lib.newevent
|
||||
import webbrowser
|
||||
|
||||
# --- STIHÅNDTERING (Til EXE-brug) ---
|
||||
if getattr(sys, 'frozen', False):
|
||||
@@ -85,6 +86,10 @@ STRINGS = {
|
||||
"msg_rename": "Omdøb",
|
||||
"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",
|
||||
@@ -113,7 +118,8 @@ STRINGS = {
|
||||
"msg_update_success": "Succes! '{name}' er opdateret.",
|
||||
"msg_update_failed_code": "Upload fejlede: {code}",
|
||||
"msg_unknown_error": "Ukendt fejl",
|
||||
"type_unknown": "Ukendt"
|
||||
"type_unknown": "Ukendt",
|
||||
"status_login_needed": "Session udløbet. Log ind igen."
|
||||
},
|
||||
"en": {
|
||||
"title": "SharePoint Explorer",
|
||||
@@ -149,6 +155,10 @@ STRINGS = {
|
||||
"msg_rename": "Rename",
|
||||
"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",
|
||||
@@ -177,7 +187,8 @@ STRINGS = {
|
||||
"msg_update_success": "Success! '{name}' has been updated.",
|
||||
"msg_update_failed_code": "Upload failed: {code}",
|
||||
"msg_unknown_error": "Unknown error",
|
||||
"type_unknown": "Unknown"
|
||||
"type_unknown": "Unknown",
|
||||
"status_login_needed": "Session expired. Please login again."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,6 +268,9 @@ class SharePointApp(wx.Frame):
|
||||
# Threading/Sync til filredigering
|
||||
self.edit_wait_event = threading.Event()
|
||||
|
||||
# MSAL Cache
|
||||
self.msal_app = msal.PublicClientApplication(CLIENT_ID, authority=AUTHORITY)
|
||||
|
||||
self.InitUI()
|
||||
self.Centre()
|
||||
self.Show()
|
||||
@@ -400,6 +414,16 @@ class SharePointApp(wx.Frame):
|
||||
|
||||
if len(selected_indices) == 1:
|
||||
item = selected_items[0]
|
||||
|
||||
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)
|
||||
|
||||
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"))
|
||||
self.Bind(wx.EVT_MENU, lambda e: threading.Thread(target=self.process_file, args=(item['id'], item['name']), daemon=True).start(), edit_item)
|
||||
@@ -573,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)
|
||||
|
||||
@@ -707,42 +785,44 @@ class SharePointApp(wx.Frame):
|
||||
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()
|
||||
accounts = self.msal_app.get_accounts()
|
||||
if not accounts:
|
||||
self.set_status(self.get_txt("status_login_needed"))
|
||||
return False
|
||||
|
||||
result = app.acquire_token_silent(SCOPES, account=accounts[0])
|
||||
result = self.msal_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
|
||||
except Exception as e:
|
||||
print(f"Token refresh error: {e}")
|
||||
|
||||
self.set_status(self.get_txt("status_login_needed"))
|
||||
return False
|
||||
|
||||
def login(self, event):
|
||||
self.set_status(self.get_txt("status_logging_in"))
|
||||
app = msal.PublicClientApplication(CLIENT_ID, authority=AUTHORITY)
|
||||
accounts = app.get_accounts()
|
||||
accounts = self.msal_app.get_accounts()
|
||||
result = None
|
||||
if accounts:
|
||||
result = app.acquire_token_silent(SCOPES, account=accounts[0])
|
||||
result = self.msal_app.acquire_token_silent(SCOPES, account=accounts[0])
|
||||
|
||||
if not result or "access_token" not in result:
|
||||
result = app.acquire_token_interactive(scopes=SCOPES)
|
||||
result = self.msal_app.acquire_token_interactive(scopes=SCOPES)
|
||||
|
||||
if "access_token" in result:
|
||||
self.access_token = result["access_token"]
|
||||
self.headers = {'Authorization': f'Bearer {self.access_token}'}
|
||||
self.login_btn.Disable()
|
||||
# self.login_btn.Hide() # Valgfrit: Skjul login knap helt når vi er inde
|
||||
self.login_btn.SetLabel(self.get_txt("btn_logged_in"))
|
||||
self.login_btn.SetBackgroundColour(wx.Colour(200, 200, 200)) # Grå
|
||||
self.home_btn.Enable()
|
||||
self.load_sites()
|
||||
else:
|
||||
self.set_status(self.get_txt("status_login_failed"))
|
||||
wx.MessageBox(result.get("error_description", self.get_txt("msg_unknown_error")), self.get_txt("msg_error"), wx.OK | wx.ICON_ERROR)
|
||||
wx.CallAfter(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"))
|
||||
@@ -790,7 +870,10 @@ class SharePointApp(wx.Frame):
|
||||
self.list_ctrl.SetItem(i, 1, self.get_txt("type_site"))
|
||||
self.list_ctrl.SetItem(i, 2, "") # Størrelse
|
||||
self.list_ctrl.SetItem(i, 3, "") # Sidst ændret
|
||||
self.current_items.append({"type": "SITE", "id": site['id'], "name": name, "size": None, "modified": ""})
|
||||
self.current_items.append({
|
||||
"type": "SITE", "id": site['id'], "name": name,
|
||||
"size": None, "modified": "", "web_url": site.get('webUrl')
|
||||
})
|
||||
|
||||
def on_tree_expanding(self, event):
|
||||
item = event.GetItem()
|
||||
@@ -907,7 +990,8 @@ class SharePointApp(wx.Frame):
|
||||
for drive in drives:
|
||||
items_data.append({
|
||||
"type": "DRIVE", "id": drive['id'], "name": drive.get('name', ''),
|
||||
"drive_id": drive['id'], "modified": "", "size": None
|
||||
"drive_id": drive['id'], "modified": "", "size": None,
|
||||
"web_url": drive.get('webUrl')
|
||||
})
|
||||
elif data['type'] in ["DRIVE", "FOLDER"]:
|
||||
drive_id = data['drive_id']
|
||||
@@ -927,7 +1011,8 @@ class SharePointApp(wx.Frame):
|
||||
"type": "FOLDER" if is_folder else "FILE",
|
||||
"id": item['id'], "name": item['name'],
|
||||
"drive_id": drive_id, "modified": modified,
|
||||
"size": item.get('size') if not is_folder else None
|
||||
"size": item.get('size') if not is_folder else None,
|
||||
"web_url": item.get('webUrl')
|
||||
})
|
||||
|
||||
wx.CallAfter(self._populate_list_ctrl, items_data, data)
|
||||
|
||||
Reference in New Issue
Block a user