Files
herolib/lib/circles/mcc/models/mail.v
2025-03-16 08:41:45 +01:00

473 lines
11 KiB
V

module models
// import freeflowuniverse.herolib.data.ourtime
import freeflowuniverse.herolib.data.encoder
// import strings
// import strconv
// Email represents an email message with all its metadata and content
pub struct Email {
pub mut:
// Database ID
id u32 // Database ID (assigned by DBHandler)
// Content fields
uid u32 // Unique identifier of the message (in the circle)
seq_num u32 // IMAP sequence number (in the mailbox)
mailbox string // The mailbox this email belongs to
message string // The email body content
attachments []Attachment // Any file attachments
// IMAP specific fields
flags []string // IMAP flags like \Seen, \Deleted, etc.
internal_date i64 // Unix timestamp when the email was received
size u32 // Size of the message in bytes
envelope ?Envelope // IMAP envelope information (contains From, To, Subject, etc.)
}
// Attachment represents an email attachment
pub struct Attachment {
pub mut:
filename string
content_type string
data string // Base64 encoded binary data
}
// Envelope represents an IMAP envelope structure
pub struct Envelope {
pub mut:
date i64
subject string
from []string
sender []string
reply_to []string
to []string
cc []string
bcc []string
in_reply_to string
message_id string
}
pub fn (e Email) index_keys() map[string]string {
return {
'uid': e.uid.str()
}
}
// dumps serializes the Email struct to binary format using the encoder
// This implements the Serializer interface
pub fn (e Email) dumps() ![]u8 {
mut enc := encoder.new()
// Add unique encoding ID to identify this type of data
enc.add_u16(301) // Unique ID for Email type
// Encode Email fields
enc.add_u32(e.id)
enc.add_u32(e.uid)
enc.add_u32(e.seq_num)
enc.add_string(e.mailbox)
enc.add_string(e.message)
// Encode attachments array
enc.add_u16(u16(e.attachments.len))
for attachment in e.attachments {
enc.add_string(attachment.filename)
enc.add_string(attachment.content_type)
enc.add_string(attachment.data)
}
// Encode flags array
enc.add_u16(u16(e.flags.len))
for flag in e.flags {
enc.add_string(flag)
}
enc.add_i64(e.internal_date)
enc.add_u32(e.size)
// Encode envelope (optional)
if envelope := e.envelope {
enc.add_u8(1) // Has envelope
enc.add_i64(envelope.date)
enc.add_string(envelope.subject)
// Encode from addresses
enc.add_u16(u16(envelope.from.len))
for addr in envelope.from {
enc.add_string(addr)
}
// Encode sender addresses
enc.add_u16(u16(envelope.sender.len))
for addr in envelope.sender {
enc.add_string(addr)
}
// Encode reply_to addresses
enc.add_u16(u16(envelope.reply_to.len))
for addr in envelope.reply_to {
enc.add_string(addr)
}
// Encode to addresses
enc.add_u16(u16(envelope.to.len))
for addr in envelope.to {
enc.add_string(addr)
}
// Encode cc addresses
enc.add_u16(u16(envelope.cc.len))
for addr in envelope.cc {
enc.add_string(addr)
}
// Encode bcc addresses
enc.add_u16(u16(envelope.bcc.len))
for addr in envelope.bcc {
enc.add_string(addr)
}
enc.add_string(envelope.in_reply_to)
enc.add_string(envelope.message_id)
} else {
enc.add_u8(0) // No envelope
}
return enc.data
}
// loads deserializes binary data into an Email struct
pub fn email_loads(data []u8) !Email {
mut d := encoder.decoder_new(data)
mut email := Email{}
// Check encoding ID to verify this is the correct type of data
encoding_id := d.get_u16()!
if encoding_id != 301 {
return error('Wrong file type: expected encoding ID 301, got ${encoding_id}, for email')
}
// Decode Email fields
email.id = d.get_u32()!
email.uid = d.get_u32()!
email.seq_num = d.get_u32()!
email.mailbox = d.get_string()!
email.message = d.get_string()!
// Decode attachments array
attachments_len := d.get_u16()!
email.attachments = []Attachment{len: int(attachments_len)}
for i in 0 .. attachments_len {
mut attachment := Attachment{}
attachment.filename = d.get_string()!
attachment.content_type = d.get_string()!
attachment.data = d.get_string()!
email.attachments[i] = attachment
}
// Decode flags array
flags_len := d.get_u16()!
email.flags = []string{len: int(flags_len)}
for i in 0 .. flags_len {
email.flags[i] = d.get_string()!
}
email.internal_date = d.get_i64()!
email.size = d.get_u32()!
// Decode envelope (optional)
has_envelope := d.get_u8()!
if has_envelope == 1 {
mut envelope := Envelope{}
envelope.date = d.get_i64()!
envelope.subject = d.get_string()!
// Decode from addresses
from_len := d.get_u16()!
envelope.from = []string{len: int(from_len)}
for i in 0 .. from_len {
envelope.from[i] = d.get_string()!
}
// Decode sender addresses
sender_len := d.get_u16()!
envelope.sender = []string{len: int(sender_len)}
for i in 0 .. sender_len {
envelope.sender[i] = d.get_string()!
}
// Decode reply_to addresses
reply_to_len := d.get_u16()!
envelope.reply_to = []string{len: int(reply_to_len)}
for i in 0 .. reply_to_len {
envelope.reply_to[i] = d.get_string()!
}
// Decode to addresses
to_len := d.get_u16()!
envelope.to = []string{len: int(to_len)}
for i in 0 .. to_len {
envelope.to[i] = d.get_string()!
}
// Decode cc addresses
cc_len := d.get_u16()!
envelope.cc = []string{len: int(cc_len)}
for i in 0 .. cc_len {
envelope.cc[i] = d.get_string()!
}
// Decode bcc addresses
bcc_len := d.get_u16()!
envelope.bcc = []string{len: int(bcc_len)}
for i in 0 .. bcc_len {
envelope.bcc[i] = d.get_string()!
}
envelope.in_reply_to = d.get_string()!
envelope.message_id = d.get_string()!
email.envelope = envelope
}
return email
}
// sender returns the first sender address or an empty string if not available
pub fn (e Email) sender() string {
if envelope := e.envelope {
if envelope.sender.len > 0 {
return envelope.sender[0]
} else if envelope.from.len > 0 {
return envelope.from[0]
}
}
return ''
}
// recipients returns all recipient addresses (to, cc, bcc)
pub fn (e Email) recipients() []string {
mut recipients := []string{}
if envelope := e.envelope {
recipients << envelope.to
recipients << envelope.cc
recipients << envelope.bcc
}
return recipients
}
// has_attachment returns true if the email has attachments
pub fn (e Email) has_attachments() bool {
return e.attachments.len > 0
}
// is_read returns true if the email has been marked as read
pub fn (e Email) is_read() bool {
return '\\\\Seen' in e.flags
}
// is_flagged returns true if the email has been flagged
pub fn (e Email) is_flagged() bool {
return '\\\\Flagged' in e.flags
}
// date returns the date when the email was sent
pub fn (e Email) date() i64 {
if envelope := e.envelope {
return envelope.date
}
return e.internal_date
}
// calculate_size calculates the total size of the email in bytes
pub fn (e Email) calculate_size() u32 {
mut size := u32(e.message.len)
// Add size of attachments
for attachment in e.attachments {
size += u32(attachment.data.len)
}
// Add estimated size of envelope data if available
if envelope := e.envelope {
size += u32(envelope.subject.len)
size += u32(envelope.message_id.len)
size += u32(envelope.in_reply_to.len)
// Add size of address fields
for addr in envelope.from {
size += u32(addr.len)
}
for addr in envelope.to {
size += u32(addr.len)
}
for addr in envelope.cc {
size += u32(addr.len)
}
for addr in envelope.bcc {
size += u32(addr.len)
}
}
return size
}
// count_lines counts the number of lines in a string
fn count_lines(s string) int {
if s == '' {
return 0
}
return s.count('\n') + 1
}
// body_structure generates and returns a description of the MIME structure of the email
// This can be used by IMAP clients to understand the structure of the message
pub fn (e Email) body_structure() string {
// If there are no attachments, return a simple text structure
if e.attachments.len == 0 {
return '("text" "plain" ("charset" "utf-8") NIL NIL "7bit" ' +
'${e.message.len} ${count_lines(e.message)}' + ' NIL NIL NIL)'
}
// For emails with attachments, create a multipart/mixed structure
mut result := '("multipart" "mixed" NIL NIL NIL "7bit" NIL NIL ('
// Add the text part
result += '("text" "plain" ("charset" "utf-8") NIL NIL "7bit" ' +
'${e.message.len} ${count_lines(e.message)}' + ' NIL NIL NIL)'
// Add each attachment
for attachment in e.attachments {
// Default to application/octet-stream if content type is empty
mut content_type := attachment.content_type
if content_type == '' {
content_type = 'application/octet-stream'
}
// Split content type into type and subtype
parts := content_type.split('/')
mut subtype := 'octet-stream'
if parts.len == 2 {
subtype = parts[1]
}
// Add the attachment part
result += ' ("application" "${subtype}" ("name" "${attachment.filename}") NIL NIL "base64" ${attachment.data.len} NIL ("attachment" ("filename" "${attachment.filename}")) NIL)'
}
// Close the structure
result += ')'
return result
}
// Helper methods to access fields from the Envelope
// from returns the From address from the Envelope
pub fn (e Email) from() string {
if envelope := e.envelope {
if envelope.from.len > 0 {
return envelope.from[0]
}
}
return ''
}
// to returns the To addresses from the Envelope
pub fn (e Email) to() []string {
if envelope := e.envelope {
return envelope.to
}
return []string{}
}
// cc returns the Cc addresses from the Envelope
pub fn (e Email) cc() []string {
if envelope := e.envelope {
return envelope.cc
}
return []string{}
}
// bcc returns the Bcc addresses from the Envelope
pub fn (e Email) bcc() []string {
if envelope := e.envelope {
return envelope.bcc
}
return []string{}
}
// subject returns the Subject from the Envelope
pub fn (e Email) subject() string {
if envelope := e.envelope {
return envelope.subject
}
return ''
}
// ensure_envelope ensures that the email has an envelope, creating one if needed
pub fn (mut e Email) ensure_envelope() {
if e.envelope == none {
e.envelope = Envelope{
from: []string{}
sender: []string{}
reply_to: []string{}
to: []string{}
cc: []string{}
bcc: []string{}
}
}
}
// set_from sets the From address in the Envelope
pub fn (mut e Email) set_from(from string) {
e.ensure_envelope()
mut envelope := e.envelope or { Envelope{} }
envelope.from = [from]
e.envelope = envelope
}
// set_to sets the To addresses in the Envelope
pub fn (mut e Email) set_to(to []string) {
e.ensure_envelope()
mut envelope := e.envelope or { Envelope{} }
envelope.to = to.clone()
e.envelope = envelope
}
// set_cc sets the Cc addresses in the Envelope
pub fn (mut e Email) set_cc(cc []string) {
e.ensure_envelope()
mut envelope := e.envelope or { Envelope{} }
envelope.cc = cc.clone()
e.envelope = envelope
}
// set_bcc sets the Bcc addresses in the Envelope
pub fn (mut e Email) set_bcc(bcc []string) {
e.ensure_envelope()
mut envelope := e.envelope or { Envelope{} }
envelope.bcc = bcc.clone()
e.envelope = envelope
}
// set_subject sets the Subject in the Envelope
pub fn (mut e Email) set_subject(subject string) {
e.ensure_envelope()
mut envelope := e.envelope or { Envelope{} }
envelope.subject = subject
e.envelope = envelope
}
// set_date sets the Date in the Envelope
pub fn (mut e Email) set_date(date i64) {
e.ensure_envelope()
mut envelope := e.envelope or { Envelope{} }
envelope.date = date
e.envelope = envelope
}