Tilføj 429-håndtering, eksponentiel backoff og dybdebegrænsning

- get_fresh_download_url: tilføjer 429-tjek med Retry-After og erstatter
  fast sleep(1) med eksponentiel backoff (2^attempt sekunder)
- process_item_list: tilføjer MAX_FOLDER_DEPTH=50 guard mod RecursionError
  ved unormalt dybe SharePoint-mappestrukturer
- README og CLAUDE.md opdateret med beskrivelse af nye adfærd

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Martin Tranberg
2026-03-27 15:16:12 +01:00
parent 3bb2b44477
commit 634b5ff151
3 changed files with 25 additions and 13 deletions

View File

@@ -28,8 +28,9 @@ Two-file structure with clear separation of concerns:
**`download_sharepoint.py`** — Core engine with four logical layers:
1. **Authentication** — MSAL `ConfidentialClientApplication` using OAuth 2.0 Client Credentials flow. Tokens are refreshed via `force_refresh=True` when a 401 is received.
2. **Graph API navigation**`get_site_id()``get_drive_id()``process_item_list()` (recursive, handles `@odata.nextLink` pagination).
3. **Download & resilience**`download_single_file()` with Range header support for resumable downloads. `get_fresh_download_url()` handles expired pre-signed URLs. The `@retry_request` decorator provides exponential backoff (up to 5 retries, 2^n seconds) for 429s and network errors.
3. **Download & resilience**`download_single_file()` with Range header support for resumable downloads. `get_fresh_download_url()` handles expired pre-signed URLs and includes its own 429 detection and exponential backoff (`2^attempt` seconds). The `@retry_request` decorator provides the same for all other API calls (up to 5 retries).
4. **Concurrency**`ThreadPoolExecutor` (max 5 workers). A `report_lock` guards the shared error list. A `stop_event` allows the GUI stop button to cancel in-flight work.
5. **Folder depth guard**`process_item_list()` accepts a `depth` parameter and stops recursion at `MAX_FOLDER_DEPTH = 50`, logging a warning for any skipped subtrees.
**`sharepoint_gui.py`** — CustomTkinter wrapper that:
- Persists settings to a local JSON file