package storage import ( "context" "time" ) // MailActivity holds mail counts over different time windows. type MailActivity struct { Last60Min int64 `json:"last_60_min"` Last24h int64 `json:"last_24h"` Last7d int64 `json:"last_7d"` Last30d int64 `json:"last_30d"` } // StorageEstimate holds a rough prediction of when disk space will run out. type StorageEstimate struct { AvgMailsPerDay float64 `json:"avg_mails_per_day"` AvgMailBytes int64 `json:"avg_mail_bytes"` DaysUntilFull int64 `json:"days_until_full"` // -1 = unknown/infinite ArchiveAgeDays int64 `json:"archive_age_days"` } // MailActivityStats returns how many mails arrived in the last 60min / 24h / 7d / 30d. func (s *Store) MailActivityStats(ctx context.Context) (*MailActivity, error) { if s.db == nil { return &MailActivity{}, nil } now := time.Now() windows := []time.Duration{ 60 * time.Minute, 24 * time.Hour, 7 * 24 * time.Hour, 30 * 24 * time.Hour, } counts := make([]int64, len(windows)) for i, w := range windows { cutoff := now.Add(-w) row := s.db.QueryRow(ctx, `SELECT COUNT(*) FROM emails WHERE received_at >= $1`, cutoff) _ = row.Scan(&counts[i]) } return &MailActivity{ Last60Min: counts[0], Last24h: counts[1], Last7d: counts[2], Last30d: counts[3], }, nil } // StorageEstimateStats calculates avg mails/day and avg mail size from the DB. // freeDiskBytes: free bytes on the archive partition (passed in from caller). func (s *Store) StorageEstimateStats(ctx context.Context, freeDiskBytes uint64) (*StorageEstimate, error) { if s.db == nil { return &StorageEstimate{DaysUntilFull: -1}, nil } var totalMails, totalBytes int64 var firstReceived *time.Time if err := s.db.QueryRow(ctx, `SELECT COUNT(*), COALESCE(SUM(size_bytes), 0), MIN(received_at) FROM emails`, ).Scan(&totalMails, &totalBytes, &firstReceived); err != nil || totalMails == 0 { return &StorageEstimate{DaysUntilFull: -1}, nil } ageDays := int64(1) if firstReceived != nil { d := time.Since(*firstReceived).Hours() / 24 if d > 1 { ageDays = int64(d) } } avgMailsPerDay := float64(totalMails) / float64(ageDays) avgMailBytes := totalBytes / totalMails daysUntilFull := int64(-1) if avgMailsPerDay > 0 && avgMailBytes > 0 && freeDiskBytes > 0 { daysUntilFull = int64(float64(freeDiskBytes) / (avgMailsPerDay * float64(avgMailBytes))) } return &StorageEstimate{ AvgMailsPerDay: avgMailsPerDay, AvgMailBytes: avgMailBytes, DaysUntilFull: daysUntilFull, ArchiveAgeDays: ageDays, }, nil }