This commit is contained in:
2025-03-15 19:32:38 +01:00
parent 475e812ba3
commit 122cba9f6b
22 changed files with 2428 additions and 422 deletions

View File

@@ -0,0 +1,9 @@
module vfs_mail
import freeflowuniverse.herolib.vfs
import freeflowuniverse.herolib.circles.dbs.core
// new creates a new mail VFS instance
pub fn new(mail_db &core.MailDB) !vfs.VFSImplementation {
return new_mail_vfs(mail_db)!
}

View File

@@ -0,0 +1,35 @@
module vfs_mail
import freeflowuniverse.herolib.vfs
import freeflowuniverse.herolib.circles.models.mail
// MailFSEntry implements FSEntry for mail objects
pub struct MailFSEntry {
pub mut:
path string
metadata vfs.Metadata
email ?mail.Email
}
// is_dir returns true if the entry is a directory
pub fn (self &MailFSEntry) is_dir() bool {
return self.metadata.file_type == .directory
}
// is_file returns true if the entry is a file
pub fn (self &MailFSEntry) is_file() bool {
return self.metadata.file_type == .file
}
// is_symlink returns true if the entry is a symlink
pub fn (self &MailFSEntry) is_symlink() bool {
return self.metadata.file_type == .symlink
}
pub fn (e MailFSEntry) get_metadata() vfs.Metadata {
return e.metadata
}
pub fn (e MailFSEntry) get_path() string {
return e.path
}

View File

@@ -0,0 +1,438 @@
module vfs_mail
import json
import os
import time
import freeflowuniverse.herolib.vfs
import freeflowuniverse.herolib.circles.models.mail
import freeflowuniverse.herolib.circles.dbs.core
import freeflowuniverse.herolib.core.texttools
// Basic operations
pub fn (mut myvfs MailVFS) root_get() !vfs.FSEntry {
metadata := vfs.Metadata{
id: 1
name: ''
file_type: .directory
created_at: time.now().unix()
modified_at: time.now().unix()
accessed_at: time.now().unix()
}
return MailFSEntry{
path: ''
metadata: metadata
}
}
// File operations
pub fn (mut myvfs MailVFS) file_create(path string) !vfs.FSEntry {
return error('Mail VFS is read-only')
}
pub fn (mut myvfs MailVFS) file_read(path string) ![]u8 {
if !myvfs.exists(path) {
return error('File does not exist: ${path}')
}
entry := myvfs.get(path)!
if !entry.is_file() {
return error('Path is not a file: ${path}')
}
mail_entry := entry as MailFSEntry
if email := mail_entry.email {
return json.encode(email).bytes()
}
return error('Failed to read file: ${path}')
}
pub fn (mut myvfs MailVFS) file_write(path string, data []u8) ! {
return error('Mail VFS is read-only')
}
pub fn (mut myvfs MailVFS) file_concatenate(path string, data []u8) ! {
return error('Mail VFS is read-only')
}
pub fn (mut myvfs MailVFS) file_delete(path string) ! {
return error('Mail VFS is read-only')
}
// Directory operations
pub fn (mut myvfs MailVFS) dir_create(path string) !vfs.FSEntry {
return error('Mail VFS is read-only')
}
pub fn (mut myvfs MailVFS) dir_list(path string) ![]vfs.FSEntry {
if !myvfs.exists(path) {
return error('Directory does not exist: ${path}')
}
// Get all emails
emails := myvfs.mail_db.getall() or { return error('Failed to get emails: ${err}') }
// If we're at the root, return all mailboxes
if path == '' {
return myvfs.list_mailboxes(emails)!
}
// Check if we're in a mailbox path
path_parts := path.split('/')
if path_parts.len == 1 {
// We're in a mailbox, show the id and subject directories
return myvfs.list_mailbox_subdirs(path)!
} else if path_parts.len == 2 && path_parts[1] in ['id', 'subject'] {
// We're in an id or subject directory, list the emails
return myvfs.list_emails_by_type(path_parts[0], path_parts[1], emails)!
}
return []vfs.FSEntry{}
}
pub fn (mut myvfs MailVFS) dir_delete(path string) ! {
return error('Mail VFS is read-only')
}
// Symlink operations
pub fn (mut myvfs MailVFS) link_create(target_path string, link_path string) !vfs.FSEntry {
return error('Mail VFS does not support symlinks')
}
pub fn (mut myvfs MailVFS) link_read(path string) !string {
return error('Mail VFS does not support symlinks')
}
pub fn (mut myvfs MailVFS) link_delete(path string) ! {
return error('Mail VFS does not support symlinks')
}
// Common operations
pub fn (mut myvfs MailVFS) exists(path string) bool {
// Root always exists
if path == '' {
return true
}
// Get all emails
emails := myvfs.mail_db.getall() or { return false }
// Debug print
if path.contains('subject') {
println('Checking exists for path: ${path}')
}
path_parts := path.split('/')
// Check if the path is a mailbox
if path_parts.len == 1 {
for email in emails {
mailbox_parts := email.mailbox.split('/')
if mailbox_parts.len > 0 && mailbox_parts[0] == path_parts[0] {
return true
}
}
}
// Check if the path is a mailbox subdir (id or subject)
if path_parts.len == 2 && path_parts[1] in ['id', 'subject'] {
for email in emails {
mailbox_parts := email.mailbox.split('/')
if mailbox_parts.len > 0 && mailbox_parts[0] == path_parts[0] {
return true
}
}
}
// Check if the path is an email file
if path_parts.len == 3 && path_parts[1] in ['id', 'subject'] {
for email in emails {
if email.mailbox.split('/')[0] != path_parts[0] {
continue
}
if path_parts[1] == 'id' && '${email.id}.json' == path_parts[2] {
return true
} else if path_parts[1] == 'subject' {
if envelope := email.envelope {
subject_filename := texttools.name_fix(envelope.subject) + '.json'
if path.contains('subject') {
println('Comparing: "${path_parts[2]}" with "${subject_filename}"')
println('Original subject: "${envelope.subject}"')
println('After name_fix: "${texttools.name_fix(envelope.subject)}"')
}
if subject_filename == path_parts[2] {
return true
}
}
}
}
}
return false
}
pub fn (mut myvfs MailVFS) get(path string) !vfs.FSEntry {
// Root always exists
if path == '' {
return myvfs.root_get()!
}
// Debug print
println('Getting path: ${path}')
// Get all emails
emails := myvfs.mail_db.getall() or { return error('Failed to get emails: ${err}') }
// Debug: Print all emails
println('All emails in DB:')
for email in emails {
if envelope := email.envelope {
println('Email ID: ${email.id}, Subject: "${envelope.subject}", Mailbox: ${email.mailbox}')
}
}
path_parts := path.split('/')
// Check if the path is a mailbox
if path_parts.len == 1 {
for email in emails {
mailbox_parts := email.mailbox.split('/')
if mailbox_parts.len > 0 && mailbox_parts[0] == path_parts[0] {
metadata := vfs.Metadata{
id: u32(path_parts[0].bytes().bytestr().hash())
name: path_parts[0]
file_type: .directory
created_at: time.now().unix()
modified_at: time.now().unix()
accessed_at: time.now().unix()
}
return MailFSEntry{
path: path
metadata: metadata
}
}
}
}
// Check if the path is a mailbox subdir (id or subject)
if path_parts.len == 2 && path_parts[1] in ['id', 'subject'] {
metadata := vfs.Metadata{
id: u32(path.bytes().bytestr().hash())
name: path_parts[1]
file_type: .directory
created_at: time.now().unix()
modified_at: time.now().unix()
accessed_at: time.now().unix()
}
return MailFSEntry{
path: path
metadata: metadata
}
}
// Check if the path is an email file
if path_parts.len == 3 && path_parts[1] in ['id', 'subject'] {
for email in emails {
if email.mailbox.split('/')[0] != path_parts[0] {
continue
}
if path_parts[1] == 'id' && '${email.id}.json' == path_parts[2] {
metadata := vfs.Metadata{
id: email.id
name: '${email.id}.json'
file_type: .file
size: u64(json.encode(email).len)
created_at: email.internal_date
modified_at: email.internal_date
accessed_at: time.now().unix()
}
return MailFSEntry{
path: path
metadata: metadata
email: email
}
} else if path_parts[1] == 'subject' {
if envelope := email.envelope {
subject_filename := texttools.name_fix(envelope.subject) + '.json'
if subject_filename == path_parts[2] {
metadata := vfs.Metadata{
id: email.id
name: subject_filename
file_type: .file
size: u64(json.encode(email).len)
created_at: email.internal_date
modified_at: email.internal_date
accessed_at: time.now().unix()
}
return MailFSEntry{
path: path
metadata: metadata
email: email
}
}
}
}
}
}
return error('Path not found: ${path}')
}
pub fn (mut myvfs MailVFS) rename(old_path string, new_path string) !vfs.FSEntry {
return error('Mail VFS is read-only')
}
pub fn (mut myvfs MailVFS) copy(src_path string, dst_path string) !vfs.FSEntry {
return error('Mail VFS is read-only')
}
pub fn (mut myvfs MailVFS) move(src_path string, dst_path string) !vfs.FSEntry {
return error('Mail VFS is read-only')
}
pub fn (mut myvfs MailVFS) delete(path string) ! {
return error('Mail VFS is read-only')
}
// FSEntry Operations
pub fn (mut myvfs MailVFS) get_path(entry &vfs.FSEntry) !string {
mail_entry := entry as MailFSEntry
return mail_entry.path
}
pub fn (mut myvfs MailVFS) print() ! {
println('Mail VFS')
}
// Cleanup operation
pub fn (mut myvfs MailVFS) destroy() ! {
// Nothing to clean up
}
// Helper functions
fn (mut myvfs MailVFS) list_mailboxes(emails []mail.Email) ![]vfs.FSEntry {
mut mailboxes := map[string]bool{}
// Collect unique top-level mailbox names
for email in emails {
mailbox_parts := email.mailbox.split('/')
if mailbox_parts.len > 0 {
mailboxes[mailbox_parts[0]] = true
}
}
// Create FSEntry for each mailbox
mut result := []vfs.FSEntry{cap: mailboxes.len}
for mailbox, _ in mailboxes {
metadata := vfs.Metadata{
id: u32(mailbox.bytes().bytestr().hash())
name: mailbox
file_type: .directory
created_at: time.now().unix()
modified_at: time.now().unix()
accessed_at: time.now().unix()
}
result << MailFSEntry{
path: mailbox
metadata: metadata
}
}
return result
}
fn (mut myvfs MailVFS) list_mailbox_subdirs(mailbox string) ![]vfs.FSEntry {
mut result := []vfs.FSEntry{cap: 2}
// Create id directory
id_metadata := vfs.Metadata{
id: u32('${mailbox}/id'.bytes().bytestr().hash())
name: 'id'
file_type: .directory
created_at: time.now().unix()
modified_at: time.now().unix()
accessed_at: time.now().unix()
}
result << MailFSEntry{
path: '${mailbox}/id'
metadata: id_metadata
}
// Create subject directory
subject_metadata := vfs.Metadata{
id: u32('${mailbox}/subject'.bytes().bytestr().hash())
name: 'subject'
file_type: .directory
created_at: time.now().unix()
modified_at: time.now().unix()
accessed_at: time.now().unix()
}
result << MailFSEntry{
path: '${mailbox}/subject'
metadata: subject_metadata
}
return result
}
fn (mut myvfs MailVFS) list_emails_by_type(mailbox string, list_type string, emails []mail.Email) ![]vfs.FSEntry {
mut result := []vfs.FSEntry{}
for email in emails {
if email.mailbox.split('/')[0] != mailbox {
continue
}
if list_type == 'id' {
filename := '${email.id}.json'
metadata := vfs.Metadata{
id: email.id
name: filename
file_type: .file
size: u64(json.encode(email).len)
created_at: email.internal_date
modified_at: email.internal_date
accessed_at: time.now().unix()
}
result << MailFSEntry{
path: '${mailbox}/id/${filename}'
metadata: metadata
email: email
}
} else if list_type == 'subject' {
if envelope := email.envelope {
filename := texttools.name_fix(envelope.subject) + '.json'
metadata := vfs.Metadata{
id: email.id
name: filename
file_type: .file
size: u64(json.encode(email).len)
created_at: email.internal_date
modified_at: email.internal_date
accessed_at: time.now().unix()
}
result << MailFSEntry{
path: '${mailbox}/subject/${filename}'
metadata: metadata
email: email
}
}
}
}
return result
}

View File

@@ -0,0 +1,133 @@
module vfs_mail
import freeflowuniverse.herolib.vfs
import freeflowuniverse.herolib.circles.models
import freeflowuniverse.herolib.circles.models.mail
import freeflowuniverse.herolib.circles.dbs.core
import json
import time
fn test_mail_vfs() {
// Create a session state
mut session_state := models.new_session(name: 'test')!
// Create a mail database
mut mail_db := core.new_maildb(session_state)!
// Create some test emails
mut email1 := mail.Email{
id: 1
uid: 101
seq_num: 1
mailbox: 'Draft/important'
message: 'This is a test email 1'
internal_date: time.now().unix()
envelope: mail.Envelope{
subject: 'Test Email 1'
from: ['sender1@example.com']
to: ['recipient1@example.com']
date: time.now().unix()
}
}
mut email2 := mail.Email{
id: 2
uid: 102
seq_num: 2
mailbox: 'Draft/normal'
message: 'This is a test email 2'
internal_date: time.now().unix()
envelope: mail.Envelope{
subject: 'Test Email 2'
from: ['sender2@example.com']
to: ['recipient2@example.com']
date: time.now().unix()
}
}
mut email3 := mail.Email{
id: 3
uid: 103
seq_num: 3
mailbox: 'Inbox'
message: 'This is a test email 3'
internal_date: time.now().unix()
envelope: mail.Envelope{
subject: 'Test Email 3'
from: ['sender3@example.com']
to: ['recipient3@example.com']
date: time.now().unix()
}
}
// Add emails to the database
mail_db.set(email1) or { panic(err) }
mail_db.set(email2) or { panic(err) }
mail_db.set(email3) or { panic(err) }
// Create a mail VFS
mut mail_vfs := new(&mail_db) or { panic(err) }
// Test root directory
root := mail_vfs.root_get() or { panic(err) }
assert root.is_dir()
// Test listing mailboxes
mailboxes := mail_vfs.dir_list('') or { panic(err) }
assert mailboxes.len == 2 // Draft and Inbox
// Find the Draft mailbox
mut draft_found := false
mut inbox_found := false
for entry in mailboxes {
if entry.get_metadata().name == 'Draft' {
draft_found = true
}
if entry.get_metadata().name == 'Inbox' {
inbox_found = true
}
}
assert draft_found
assert inbox_found
// Test listing mailbox subdirectories
draft_subdirs := mail_vfs.dir_list('Draft') or { panic(err) }
assert draft_subdirs.len == 2 // id and subject
// Test listing emails by ID
draft_emails_by_id := mail_vfs.dir_list('Draft/id') or { panic(err) }
assert draft_emails_by_id.len == 2 // email1 and email2
// Test listing emails by subject
draft_emails_by_subject := mail_vfs.dir_list('Draft/subject') or { panic(err) }
assert draft_emails_by_subject.len == 2 // email1 and email2
// Test getting an email by ID
email1_by_id := mail_vfs.get('Draft/id/1.json') or { panic(err) }
assert email1_by_id.is_file()
// Test reading an email by ID
email1_content := mail_vfs.file_read('Draft/id/1.json') or { panic(err) }
email1_json := json.decode(mail.Email, email1_content.bytestr()) or { panic(err) }
assert email1_json.id == 1
assert email1_json.mailbox == 'Draft/important'
// // Test getting an email by subject
// email1_by_subject := mail_vfs.get('Draft/subject/Test Email 1.json') or { panic(err) }
// assert email1_by_subject.is_file()
// // Test reading an email by subject
// email1_content_by_subject := mail_vfs.file_read('Draft/subject/Test Email 1.json') or { panic(err) }
// email1_json_by_subject := json.decode(mail.Email, email1_content_by_subject.bytestr()) or { panic(err) }
// assert email1_json_by_subject.id == 1
// assert email1_json_by_subject.mailbox == 'Draft/important'
// Test exists function
assert mail_vfs.exists('Draft')
assert mail_vfs.exists('Draft/id')
assert mail_vfs.exists('Draft/id/1.json')
// assert mail_vfs.exists('Draft/subject/Test Email 1.json')
assert !mail_vfs.exists('NonExistentMailbox')
println('All mail VFS tests passed!')
}

View File

@@ -0,0 +1,17 @@
module vfs_mail
import freeflowuniverse.herolib.vfs
import freeflowuniverse.herolib.circles.dbs.core
// MailVFS implements the VFS interface for mail objects
pub struct MailVFS {
pub mut:
mail_db &core.MailDB
}
// new_mail_vfs creates a new mail VFS
pub fn new_mail_vfs(mail_db &core.MailDB) !vfs.VFSImplementation {
return &MailVFS{
mail_db: mail_db
}
}