Quota
Per-user storage quota tracking in hopf_core::quota (Gumdrop org.bluezoo.gumdrop.quota parity). Tracks how much storage — and, optionally, how many messages — a user has accumulated, independent of which protocol or connection touched their data. The same QuotaManager can back an FTP file store (hopf-ftp) and an IMAP mailbox (hopf-imap) at once.
Contents
Two kinds of quota
hopf_core::quota covers both. QuotaTracker/CounterQuota are a connection-level traffic/rate limiter (bytes in/out, message count, per connection — closer to DoS protection). QuotaManager/Quota are per-user storage accounting: how many bytes has alice stored across however many connections, over however long, regardless of protocol.
Core types
| Type | Role |
|---|---|
Quota |
Limits + current usage (storage bytes, message count); either limit may be UNLIMITED (-1) |
QuotaSource |
User / Role / Default / None — where a Quota's limits came from |
QuotaManager |
Resolve/check/update by username |
QuotaPolicy |
Named limits (a role, or the system default), with parse_size for human-readable sizes ("100MB", "10GB", "1TB", "unlimited") |
Resolution order
QuotaManager::get_quota resolves a user's effective quota with the same priority as Gumdrop's QuotaManager: user-specific (set_user_quota) → role-based → system default → unlimited. When a user matches more than one role policy, the most generous storage limit wins.
Stock managers
| Manager | Behaviour |
|---|---|
UnlimitedQuotaManager |
No limits, ever; usage tracking is a no-op |
MemoryQuotaManager |
In-process usage tracking; optional role policies + role lookup, optional default policy, per-user overrides |
Usage isn't persisted across restarts by either stock manager. QuotaManager::save_usage_data/load_usage_data are no-op-by-default hooks for an implementation that needs to flush/restore usage explicitly.
FTP integration
FtpConnectionHandler (hopf-ftp) has quota_manager()/can_store/quota/record_bytes_added/record_bytes_removed default methods delegating to whatever quota_manager() returns — matching Gumdrop's FTPConnectionHandler. The stock FilesystemFtpHandler/FilesystemFtpHandlerFactory take an optional manager via with_quota(Arc<dyn QuotaManager>).
STOR/APPE/STOU are gated on Quota::is_storage_exceeded before the data connection opens — FTP has no declared-size-ahead-of-time (no ALLO tracking), so this can only reject a user who's already over quota, not pre-empt a specific upload that would push them over. Usage is recorded via the same TransferObserver-carried manager handle once the upload actually completes (see FTP: Handler SPI), and on DELE.
IMAP integration
hopf_imap::server::quota::MemoryQuotaManager is an adapter over a shared hopf_core::QuotaManager, translating to/from RFC 9208's STORAGE (KiB)/MESSAGE resource view for GETQUOTA/SETQUOTA. It replaces what used to be IMAP's own independent in-memory tracker — see IMAP.
Sharing one quota across protocols
let backend: Arc<dyn hopf_core::QuotaManager> =
Arc::new(hopf_core::MemoryQuotaManager::new()
.with_default(hopf_core::QuotaPolicy::with_storage("default", hopf_core::QuotaPolicy::parse_size("1GB")?)));
// FTP enforces against the same backend...
let ftp_handler = FilesystemFtpHandlerFactory::new(root, policy).with_quota(backend.clone());
// ...and so does IMAP's GETQUOTA/SETQUOTA.
let imap_quota = hopf_imap::server::quota::MemoryQuotaManager::new(backend);
An upload through FTP and an APPEND through IMAP for the same user now draw down the same limit.
Limitations
- No persistence backend is shipped —
MemoryQuotaManageris in-process only; wrap or replaceQuotaManagerfor a database/file-backed one. - No role-membership source is shipped either —
MemoryQuotaManager::with_rolestakes a plainFn(&str) -> Vec<String>lookup, since Hopf has no built-in role/group concept (hopf-auth'sTrustPolicyis accept/reject only, no roles). - FTP's quota gate can't check a specific upload's size ahead of time (no
ALLOtracking) — see FTP integration above.