Constants
Data Structures
R2Config
Complete R2 configuration structure containing both secrets and non-secrets.R2 access key ID (stored in Keychain)
R2 secret access key (stored in Keychain)
R2 bucket name (stored in file)
Cloudflare account ID (stored in file)
Base URL for public file access, e.g.,
https://files.example.com (stored in file)AppSettings
Application settings stored in JSON file.Whether demo mode is enabled (defaults to
true)Custom output directory for demo mode (defaults to
Downloads/ZipDrop)Internal Structures
StoredConfig
Non-secret configuration stored inconfig.json.
KeychainSecrets
Secrets stored as a single JSON blob in Keychain to minimize user prompts.By storing both secrets in a single Keychain entry, users only see one permission prompt instead of two.
File Locations
get_config_dir
Returns the configuration directory path, creating it if necessary.~/Library/Application Support/zipdrop/
Returns: Result<PathBuf, String>
get_config_path
Returns the path to the R2 configuration file.~/Library/Application Support/zipdrop/config.json
get_settings_path
Returns the path to the app settings file.~/Library/Application Support/zipdrop/settings.json
get_demo_output_dir
Returns the demo mode output directory, creating it if necessary.~/Downloads/ZipDrop/
Returns: Result<PathBuf, String>
Fallback: Uses home directory if Downloads folder cannot be found.
R2 Configuration Functions
save_r2_config
Saves R2 configuration, splitting secrets (Keychain) from non-secrets (file).Complete R2 configuration to save
Result<(), String>
Storage Strategy:
Example config.json:
load_r2_config
Loads R2 configuration by combining Keychain secrets with file data.Result<Option<R2Config>, String>
Ok(Some(config))- Configuration loaded successfullyOk(None)- No configuration found or incompleteErr(String)- Error reading configuration
- Check if
config.jsonexists (if not, returnNone) - Load non-secrets from
config.json - Load secrets from Keychain entry
r2_credentials - Validate that both secrets are non-empty
- Combine into complete
R2Config
- Config file doesn’t exist: Returns
Ok(None) - Keychain secrets missing: Returns
Ok(None) - Keychain secrets empty: Returns
Ok(None) - File read error: Returns
Err("Failed to read config file: ...") - JSON parse error: Returns
Err("Failed to parse config: ...")
delete_r2_config
Deletes all R2 configuration data from Keychain and filesystem.Result<(), String>
Cleanup Actions:
- Deletes Keychain entry
r2_credentials(current format) - Deletes legacy Keychain entries
r2_access_keyandr2_secret_key(migration cleanup) - Deletes
config.jsonfile if it exists
This function gracefully handles missing entries and files, only returning errors for actual I/O failures.
Settings Functions
save_settings
Saves app settings to JSON file.App settings to save
Result<(), String>
Example settings.json:
load_settings
Loads app settings from JSON file, returning defaults if file doesn’t exist.Result<AppSettings, String>
Behavior:
- If
settings.jsondoesn’t exist: ReturnsAppSettings::default()(demo mode enabled) - If file exists but is invalid: Returns error
- If file exists and is valid: Returns loaded settings
Keychain Migration
migrate_keychain_entries
One-time migration to clean up old two-entry Keychain format.- Checks for migration marker file (
.migrated_v1) - If already migrated, returns immediately
- Attempts to delete old entries:
r2_access_keyandr2_secret_key - Creates marker file to prevent future runs
- Entry 1:
r2_access_key→ access key only - Entry 2:
r2_secret_key→ secret key only - Problem: Two macOS permission prompts
- Entry:
r2_credentials→ JSON with both keys - Benefit: Single macOS permission prompt