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.

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