- Add ThreadPoolExecutor for parallel attachment downloads - Add --max-workers parameter to control concurrency (default: 5) - Implement thread-safe logging with Lock mechanism - Refactor _do_download to use concurrent.futures - Add _download_single_file and _download_single_link helper functions - Update CLAUDE.md with multi-threading documentation Performance improvements: - File attachments (OData) now download in parallel - Link attachments (Scrapling) now download in parallel - Configurable worker threads for different network conditions
7.1 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
This is a SAP C4C (Cloud for Customer) attachment downloader toolkit that retrieves attachments from ServiceRequest tickets and optionally uploads them to Synology DSM NAS. The project consists of:
- Python script (
sap-c4c-AttachmentFolder.py): Core downloader using OData APIs and web scraping - Java wrapper (
C4CAttachmentDownloader.java): Java interface that calls the Python script via ProcessBuilder - DSM upload script (
dsm-upload.py): Standalone Synology NAS upload utility
Architecture
Python Script (sap-c4c-AttachmentFolder.py)
Core functionality:
- Authenticates to SAP C4C using Basic Auth
- Fetches ServiceRequest attachments via OData endpoints:
/sap/c4c/odata/v1/c4codata- Standard C4C OData API/sap/c4c/odata/cust/v1/custticketapi- Custom ticket API
- Downloads two types of attachments using multi-threaded concurrent downloads:
- File attachments (CategoryCode=2): Downloaded via OData
$valueendpoint - Link attachments (CategoryCode=3): External Salesforce links scraped using Scrapling + Playwright
- File attachments (CategoryCode=2): Downloaded via OData
- Handles XIssueItem-level attachments via
BO_XSRIssueItemAttachmentFolder - Optionally uploads downloaded files to Synology DSM via FileStation API
Key dependencies:
requests- HTTP client for OData/REST APIsscrapling[all]- Web scraping framework with stealth capabilitiesplaywright- Browser automation for downloading Salesforce attachments
Performance features:
- Multi-threaded concurrent downloads (default: 5 threads, configurable via
--max-workers) - Thread-safe output logging with lock mechanism
- Parallel processing of both file and link attachments
Output modes:
- Human-readable console output (default)
- JSON mode (
--json) for programmatic consumption
Java Wrapper (C4CAttachmentDownloader.java)
Provides a type-safe Java API that:
- Invokes the Python script via
ProcessBuilder - Passes credentials via environment variables (more secure than CLI args)
- Parses JSON output into strongly-typed Java objects
- Supports timeout configuration (default: 30 minutes)
Key classes:
Result- Top-level response containing all attachment metadataAttachment- Individual attachment metadata (UUID, filename, MIME type, category)IssueItem- XIssueItem with nested attachmentsDownloadedFile- Download result with local path and error infoDsmUploadEntry- DSM upload result per file
DSM Upload (dsm-upload.py)
Standalone script demonstrating Synology FileStation API usage:
- Login via
SYNO.API.Authto obtain SID - Upload files via
SYNO.FileStation.Uploadwith SID cookie
Common Commands
Python Script
# Install dependencies
pip install requests scrapling[all] playwright
python -m playwright install chromium
# Download attachments (credentials via CLI)
python sap-c4c-AttachmentFolder.py \
--tenant https://xxx.c4c.saphybriscloud.cn \
--user admin \
--password xxx \
--ticket 24588
# Download with custom thread count (default: 5)
python sap-c4c-AttachmentFolder.py \
--tenant https://xxx.c4c.saphybriscloud.cn \
--user admin \
--password xxx \
--ticket 24588 \
--max-workers 10
# Download with DSM upload
python sap-c4c-AttachmentFolder.py \
--tenant https://xxx.c4c.saphybriscloud.cn \
--user admin \
--password xxx \
--ticket 24588 \
--dsm-url http://10.0.10.235:5000 \
--dsm-user PLM \
--dsm-password 123456 \
--dsm-path /Newgonow/AU-SPFJ
# JSON mode (for Java/programmatic use)
python sap-c4c-AttachmentFolder.py --ticket 24588 --json
# List attachments only (no download)
python sap-c4c-AttachmentFolder.py --ticket 24588 --list-only
# Using environment variables for credentials
export C4C_TENANT=https://xxx.c4c.saphybriscloud.cn
export C4C_USERNAME=admin
export C4C_PASSWORD=xxx
export DSM_URL=http://10.0.10.235:5000
export DSM_USERNAME=PLM
export DSM_PASSWORD=123456
export DSM_PATH=/Newgonow/AU-SPFJ
python sap-c4c-AttachmentFolder.py --ticket 24588 --json
Java Wrapper
// Compile (requires Jackson for JSON parsing)
javac -cp jackson-databind.jar:jackson-core.jar:jackson-annotations.jar C4CAttachmentDownloader.java
// Basic usage
C4CAttachmentDownloader downloader = new C4CAttachmentDownloader(
"/path/to/sap-c4c-AttachmentFolder.py",
"https://xxx.c4c.saphybriscloud.cn",
"admin",
"password"
);
// List attachments only
C4CAttachmentDownloader.Result result = downloader.listAttachments("24588");
// Download to default directory
C4CAttachmentDownloader.Result result = downloader.download("24588");
// Download to specific directory
C4CAttachmentDownloader.Result result = downloader.download("24588", "/tmp/ticket_24588");
// Download with DSM upload
downloader.setDsmConfig("http://10.0.10.235:5000", "PLM", "123456", "/Newgonow/AU-SPFJ");
C4CAttachmentDownloader.Result result = downloader.download("24588", "/tmp/ticket_24588");
Key Implementation Details
Attachment Categories
SAP C4C uses CategoryCode to distinguish attachment types:
- "2" = File attachment (binary content stored in C4C, downloaded via OData
$value) - "3" = Link attachment (external URL, typically Salesforce links requiring web scraping)
OData Navigation Paths
ServiceRequest attachments:
/ServiceRequestCollection('{ObjectID}')/ServiceRequestAttachmentFolder
XIssueItem attachments (two-step navigation):
1. /BO_XSRIssueItemAttachmentCollection?$filter=XIssueItemUUID eq guid'{uuid}'
2. /BO_XSRIssueItemAttachmentCollection('{ObjectID}')/BO_XSRIssueItemAttachmentFolder
Scrapling Download Strategy
For CategoryCode=3 (link attachments), the script:
- Opens the Salesforce link in a headless Chromium browser
- Waits for
button.downloadbutton[title='Download']selector - Clicks the button and captures the download
- Saves with original or suggested filename
Security Considerations
- Java wrapper passes credentials via environment variables (not CLI args) to avoid exposure in process lists
- Python script supports both CLI args and environment variables
- DSM API uses session-based authentication (SID cookie)
- SSL verification disabled (
verify=False) - consider enabling in production
File Structure
.
├── C4CAttachmentDownloader.java # Java wrapper with typed API
├── sap-c4c-AttachmentFolder.py # Core Python downloader
├── dsm-upload.py # Standalone DSM upload example
└── downloads/ # Default output directory
Troubleshooting
Playwright not installed:
python -m playwright install chromium
Timeout errors: Increase timeout in Java wrapper constructor (default 30 minutes) or adjust Scrapling timeout parameters.
DSM upload fails: Verify DSM URL, credentials, and that target path exists or create_parents=true is set.
Link download fails: Check that Salesforce page structure matches expected selector (button.downloadbutton[title='Download']). Update download_link_via_scrapling() if page structure changes.