Files
oai-swift/oAI/Services/EmailLogService.swift
T
runeandClaude Sonnet 5 cf3f4ebfe4 Relicense oAI from AGPL-3.0-or-later to PolyForm Noncommercial 1.0.0
Switches the project from AGPL to a source-available license that
restricts commercial use — selling oAI or any part of it, standalone
or bundled into another product/service, now requires a separate
commercial license from the copyright holder. Noncommercial use,
study, modification, and sharing remain fully permitted.

Updates: LICENSE (canonical PolyForm Noncommercial 1.0.0 text +
commercial licensing contact note), SPDX headers and file-header
boilerplate across all Swift source files, the in-app About dialog's
license link (+ its localization catalog entry), README.md and
DEVELOPMENT.md license sections.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 11:16:09 +02:00

171 lines
4.6 KiB
Swift

//
// EmailLogService.swift
// oAI
//
// Service for managing email handler activity logs
//
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
// Copyright (C) 2026 Rune Olsen
//
// This file is part of oAI.
//
// oAI is licensed under the PolyForm Noncommercial License 1.0.0.
// You may use, study, modify, and share it for any noncommercial
// purpose. Commercial use — including selling oAI or any part of
// it, standalone or bundled into another product or service —
// requires a separate commercial license from the copyright holder.
//
// See the LICENSE file or
// <https://polyformproject.org/licenses/noncommercial/1.0.0> for
// the full license text. For commercial licensing, contact Rune
// Olsen via <https://mac.oai.pm>.
import Foundation
import os
@Observable
final class EmailLogService {
static let shared = EmailLogService()
private let db = DatabaseService.shared
private let log = Logger(subsystem: "com.oai.oAI", category: "email-log")
private init() {}
// MARK: - Log Operations
/// Save a successful email processing log
func logSuccess(
sender: String,
subject: String,
emailContent: String,
aiResponse: String,
tokens: Int?,
cost: Double?,
responseTime: TimeInterval?,
modelId: String?
) {
let entry = EmailLog(
sender: sender,
subject: subject,
emailContent: emailContent,
aiResponse: aiResponse,
status: .success,
tokens: tokens,
cost: cost,
responseTime: responseTime,
modelId: modelId
)
db.saveEmailLog(entry)
log.info("Email log saved: \(sender) - \(subject) [SUCCESS]")
}
/// Save a failed email processing log
func logError(
sender: String,
subject: String,
emailContent: String,
errorMessage: String,
modelId: String?
) {
let entry = EmailLog(
sender: sender,
subject: subject,
emailContent: emailContent,
aiResponse: nil,
status: .error,
errorMessage: errorMessage,
modelId: modelId
)
db.saveEmailLog(entry)
log.error("Email log saved: \(sender) - \(subject) [ERROR: \(errorMessage)]")
}
/// Load recent email logs (default: 100 most recent)
func loadLogs(limit: Int = 100) -> [EmailLog] {
do {
return try db.loadEmailLogs(limit: limit)
} catch {
log.error("Failed to load email logs: \(error.localizedDescription)")
return []
}
}
/// Delete a specific log entry
func deleteLog(id: UUID) {
db.deleteEmailLog(id: id)
log.info("Email log deleted: \(id.uuidString)")
}
/// Clear all email logs
func clearAllLogs() {
db.clearEmailLogs()
log.info("All email logs cleared")
}
/// Get total count of email logs
func getLogCount() -> Int {
do {
return try db.getEmailLogCount()
} catch {
log.error("Failed to get email log count: \(error.localizedDescription)")
return 0
}
}
// MARK: - Statistics
/// Get email processing statistics
func getStatistics() -> EmailStatistics {
let logs = loadLogs(limit: 1000) // Last 1000 for stats
let total = logs.count
let successful = logs.filter { $0.status == .success }.count
let errors = logs.filter { $0.status == .error }.count
let totalTokens = logs.compactMap { $0.tokens }.reduce(0, +)
let totalCost = logs.compactMap { $0.cost }.reduce(0.0, +)
let avgResponseTime: TimeInterval?
let responseTimes = logs.compactMap { $0.responseTime }
if !responseTimes.isEmpty {
avgResponseTime = responseTimes.reduce(0, +) / Double(responseTimes.count)
} else {
avgResponseTime = nil
}
return EmailStatistics(
total: total,
successful: successful,
errors: errors,
totalTokens: totalTokens,
totalCost: totalCost,
averageResponseTime: avgResponseTime
)
}
}
// MARK: - Statistics Model
struct EmailStatistics {
let total: Int
let successful: Int
let errors: Int
let totalTokens: Int
let totalCost: Double
let averageResponseTime: TimeInterval?
var successRate: Double {
guard total > 0 else { return 0.0 }
return Double(successful) / Double(total)
}
var errorRate: Double {
guard total > 0 else { return 0.0 }
return Double(errors) / Double(total)
}
}