Files
Sharepoint-Download-Tool/sharepoint_gui.py
T
Martin Tranberg 24665c5797 Add support for downloading all document libraries on a site
- New get_all_drives() fetches all drives from a SharePoint site
- main() loops over all drives when DOCUMENT_LIBRARY is empty, placing
  each library in its own subfolder under LOCAL_PATH
- MSAL force_refresh now catches TypeError/ValueError for compatibility
  with older MSAL versions that don't support the parameter
- GUI: "Download alle biblioteker" checkbox disables Library Navn and
  Mapper fields; load_settings restores checkbox state from config

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 13:41:41 +02:00

200 lines
8.5 KiB
Python

import os
import threading
import logging
import customtkinter as ctk
from tkinter import filedialog, messagebox
import download_sharepoint # Din eksisterende kerne-logik
import requests
# --- Global Stop Flag ---
stop_event = threading.Event()
# --- Logging Handler for GUI ---
class TextboxHandler(logging.Handler):
def __init__(self, textbox):
super().__init__()
self.textbox = textbox
def emit(self, record):
msg = self.format(record)
self.textbox.after(0, self.append_msg, msg)
def append_msg(self, msg):
self.textbox.configure(state="normal")
self.textbox.insert("end", msg + "\n")
self.textbox.see("end")
self.textbox.configure(state="disabled")
# --- Main App ---
class SharepointApp(ctk.CTk):
def __init__(self):
super().__init__()
self.title("SharePoint Download Tool - UX")
self.geometry("1000x850") # Gjort lidt bredere og højere for at give plads
ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("blue")
self.grid_columnconfigure(1, weight=1)
self.grid_rowconfigure(0, weight=1)
# Sidebar
self.sidebar_frame = ctk.CTkFrame(self, width=350, corner_radius=0)
self.sidebar_frame.grid(row=0, column=0, sticky="nsew")
self.sidebar_frame.grid_rowconfigure(25, weight=1)
self.logo_label = ctk.CTkLabel(self.sidebar_frame, text="Indstillinger", font=ctk.CTkFont(size=20, weight="bold"))
self.logo_label.grid(row=0, column=0, padx=20, pady=(20, 10))
self.entries = {}
# Felter før DOCUMENT_LIBRARY
fields_before = [
("TENANT_ID", "Tenant ID"),
("CLIENT_ID", "Client ID"),
("CLIENT_SECRET", "Client Secret"),
("SITE_URL", "Site URL"),
]
for i, (key, label) in enumerate(fields_before):
lbl = ctk.CTkLabel(self.sidebar_frame, text=label)
lbl.grid(row=i*2+1, column=0, padx=20, pady=(5, 0), sticky="w")
entry = ctk.CTkEntry(self.sidebar_frame, width=280)
if key == "CLIENT_SECRET": entry.configure(show="*")
entry.grid(row=i*2+2, column=0, padx=20, pady=(0, 5))
self.entries[key] = entry
# DOCUMENT_LIBRARY med "Alle biblioteker"-afkrydsningsfelt
lbl_lib = ctk.CTkLabel(self.sidebar_frame, text="Library Navn")
lbl_lib.grid(row=9, column=0, padx=20, pady=(5, 0), sticky="w")
self.library_entry = ctk.CTkEntry(self.sidebar_frame, width=280)
self.library_entry.grid(row=10, column=0, padx=20, pady=(0, 2))
self.entries["DOCUMENT_LIBRARY"] = self.library_entry
self.all_libraries_var = ctk.BooleanVar(value=False)
self.all_libraries_cb = ctk.CTkCheckBox(
self.sidebar_frame, text="Download alle biblioteker",
variable=self.all_libraries_var,
command=self.toggle_library_entry
)
self.all_libraries_cb.grid(row=11, column=0, padx=20, pady=(0, 5), sticky="w")
# Felter efter DOCUMENT_LIBRARY
fields_after = [
("FOLDERS_TO_DOWNLOAD", "Mapper (komma-sep)"),
("LOCAL_PATH", "Lokal Sti"),
("ENABLE_HASH_VALIDATION", "Valider Hash (True/False)"),
("HASH_THRESHOLD_MB", "Hash Grænse (MB)")
]
for i, (key, label) in enumerate(fields_after):
base_row = 12 + i * 2
lbl = ctk.CTkLabel(self.sidebar_frame, text=label)
lbl.grid(row=base_row, column=0, padx=20, pady=(5, 0), sticky="w")
entry = ctk.CTkEntry(self.sidebar_frame, width=280)
entry.grid(row=base_row+1, column=0, padx=20, pady=(0, 5))
self.entries[key] = entry
self.browse_button = ctk.CTkButton(self.sidebar_frame, text="Vælg Mappe", command=self.browse_folder, height=32)
self.browse_button.grid(row=20, column=0, padx=20, pady=10)
self.save_button = ctk.CTkButton(self.sidebar_frame, text="Gem Indstillinger", command=self.save_settings, fg_color="transparent", border_width=2)
self.save_button.grid(row=21, column=0, padx=20, pady=10)
# Main side
self.main_frame = ctk.CTkFrame(self, corner_radius=0, fg_color="transparent")
self.main_frame.grid(row=0, column=1, sticky="nsew", padx=20, pady=20)
self.main_frame.grid_rowconfigure(1, weight=1)
self.main_frame.grid_columnconfigure(0, weight=1)
self.status_label = ctk.CTkLabel(self.main_frame, text="Status: Klar", font=ctk.CTkFont(size=16))
self.status_label.grid(row=0, column=0, pady=(0, 10), sticky="w")
self.log_textbox = ctk.CTkTextbox(self.main_frame, state="disabled")
self.log_textbox.grid(row=1, column=0, sticky="nsew")
# Buttons frame
self.btn_frame = ctk.CTkFrame(self.main_frame, fg_color="transparent")
self.btn_frame.grid(row=2, column=0, pady=(20, 0), sticky="ew")
self.btn_frame.grid_columnconfigure(0, weight=1)
self.start_button = ctk.CTkButton(self.btn_frame, text="Start Synkronisering", command=self.start_sync_thread, height=50, font=ctk.CTkFont(size=16, weight="bold"))
self.start_button.grid(row=0, column=0, padx=(0, 10), sticky="ew")
self.stop_button = ctk.CTkButton(self.btn_frame, text="Stop", command=self.stop_sync, height=50, fg_color="#d32f2f", hover_color="#b71c1c", state="disabled")
self.stop_button.grid(row=0, column=1, sticky="ew")
self.load_settings()
self.setup_logging()
def setup_logging(self):
handler = TextboxHandler(self.log_textbox)
handler.setFormatter(logging.Formatter('%(asctime)s: %(message)s', datefmt='%H:%M:%S'))
download_sharepoint.logger.addHandler(handler)
def toggle_library_entry(self):
if self.all_libraries_var.get():
self.library_entry.configure(state="normal")
self.library_entry.delete(0, "end")
self.library_entry.configure(state="disabled")
self.entries["FOLDERS_TO_DOWNLOAD"].configure(state="disabled")
else:
self.library_entry.configure(state="normal")
self.entries["FOLDERS_TO_DOWNLOAD"].configure(state="normal")
def browse_folder(self):
path = filedialog.askdirectory()
if path:
self.entries["LOCAL_PATH"].delete(0, "end")
self.entries["LOCAL_PATH"].insert(0, path)
def load_settings(self):
if os.path.exists("connection_info.txt"):
config = download_sharepoint.load_config("connection_info.txt")
for key, entry in self.entries.items():
val = config.get(key, "")
entry.insert(0, val)
if not config.get("DOCUMENT_LIBRARY", ""):
self.all_libraries_cb.select()
self.library_entry.configure(state="disabled")
self.entries["FOLDERS_TO_DOWNLOAD"].configure(state="disabled")
def save_settings(self):
config_lines = [f'{k} = "{v.get()}"' for k, v in self.entries.items()]
with open("connection_info.txt", "w", encoding="utf-8") as f:
f.write("\n".join(config_lines))
def stop_sync(self):
stop_event.set()
self.stop_button.configure(state="disabled", text="Stopper...")
download_sharepoint.logger.warning("Stop-signal sendt. Venter på at tråde afbryder...")
def start_sync_thread(self):
self.save_settings()
stop_event.clear()
self.start_button.configure(state="disabled")
self.stop_button.configure(state="normal", text="Stop")
self.status_label.configure(text="Status: Synkroniserer...", text_color="orange")
thread = threading.Thread(target=self.run_sync, daemon=True)
thread.start()
def run_sync(self):
try:
config = download_sharepoint.load_config("connection_info.txt")
download_sharepoint.main(config=config, stop_event=stop_event)
if stop_event.is_set():
self.status_label.configure(text="Status: Afbrudt", text_color="red")
else:
self.status_label.configure(text="Status: Gennemført!", text_color="green")
except InterruptedError:
self.status_label.configure(text="Status: Afbrudt", text_color="red")
except Exception as e:
self.status_label.configure(text="Status: Fejl!", text_color="red")
messagebox.showerror("Fejl", str(e))
finally:
self.start_button.configure(state="normal")
self.stop_button.configure(state="disabled", text="Stop")
if __name__ == "__main__":
app = SharepointApp()
app.mainloop()