feat: Add contacts database and VFS implementation
- Added a new contacts database (`ContactsDB`) to store contact information. This improves data organization and allows for more efficient querying and manipulation of contact data. - Implemented a virtual file system (VFS) for contacts (`vfs_contacts`). This provides a file-like interface to access and manage contact data, improving integration with existing file-system-based tools and workflows. The VFS supports listing by group, by name, and by email. - Added model structs for contacts, improving data organization and serialization. This lays the foundation for more robust data handling and future expansion.
This commit is contained in:
@@ -6,21 +6,20 @@ import freeflowuniverse.herolib.circles.actions.models as actions_models
|
||||
|
||||
pub struct DBHandler[T] {
|
||||
pub mut:
|
||||
prefix string
|
||||
prefix string
|
||||
session_state SessionState
|
||||
|
||||
}
|
||||
|
||||
// new_dbhandler creates a new DBHandler for type T
|
||||
pub fn new_dbhandler[T](prefix string, session_state SessionState) DBHandler[T] {
|
||||
|
||||
return DBHandler[T]{
|
||||
prefix: prefix
|
||||
prefix: prefix
|
||||
session_state: session_state
|
||||
}
|
||||
}
|
||||
|
||||
// set adds or updates an item
|
||||
pub fn (mut m DBHandler[T]) set(item_ T) !T {
|
||||
|
||||
mut item := item_
|
||||
|
||||
// Store the item data in the database and get the assigned ID
|
||||
@@ -42,35 +41,39 @@ pub fn (mut m DBHandler[T]) get(id u32) !T {
|
||||
return error('Item data not found for ID ${id}')
|
||||
}
|
||||
|
||||
//THIS IS SUPER ANNOYING AND NOT NICE
|
||||
// THIS IS SUPER ANNOYING AND NOT NICE
|
||||
$if T is core_models.Agent {
|
||||
mut o:= core_models.agent_loads(item_data)!
|
||||
mut o := core_models.agent_loads(item_data)!
|
||||
o.id = id
|
||||
return o
|
||||
} $else $if T is core_models.Circle {
|
||||
mut o:= core_models.circle_loads(item_data)!
|
||||
mut o := core_models.circle_loads(item_data)!
|
||||
o.id = id
|
||||
return o
|
||||
} $else $if T is core_models.Name {
|
||||
mut o:= core_models.name_loads(item_data)!
|
||||
mut o := core_models.name_loads(item_data)!
|
||||
o.id = id
|
||||
return o
|
||||
} $else $if T is mcc_models.Email {
|
||||
mut o:= mcc_models.email_loads(item_data)!
|
||||
mut o := mcc_models.email_loads(item_data)!
|
||||
o.id = id
|
||||
return o
|
||||
} $else $if T is mcc_models.CalendarEvent {
|
||||
mut o:= mcc_models.calendar_event_loads(item_data)!
|
||||
mut o := mcc_models.calendar_event_loads(item_data)!
|
||||
o.id = id
|
||||
return o
|
||||
} $else $if T is mcc_models.Contact {
|
||||
mut o := mcc_models.contact_event_loads(item_data)!
|
||||
o.id = id
|
||||
return o
|
||||
} $else $if T is actions_models.Job {
|
||||
mut o:= actions_models.job_loads(item_data)!
|
||||
mut o := actions_models.job_loads(item_data)!
|
||||
o.id = id
|
||||
return o
|
||||
} $else {
|
||||
return error('Unsupported type for deserialization')
|
||||
}
|
||||
panic("bug")
|
||||
panic('bug')
|
||||
}
|
||||
|
||||
pub fn (mut m DBHandler[T]) exists(id u32) !bool {
|
||||
@@ -78,7 +81,6 @@ pub fn (mut m DBHandler[T]) exists(id u32) !bool {
|
||||
return item_data != []u8{}
|
||||
}
|
||||
|
||||
|
||||
// get_by_key retrieves an item by a specific key field and value
|
||||
pub fn (mut m DBHandler[T]) get_by_key(key_field string, key_value string) !T {
|
||||
// Create the key for the radix tree
|
||||
@@ -99,7 +101,6 @@ pub fn (mut m DBHandler[T]) get_by_key(key_field string, key_value string) !T {
|
||||
|
||||
// delete removes an item by its ID
|
||||
pub fn (mut m DBHandler[T]) delete(id u32) ! {
|
||||
|
||||
exists := m.exists(id)!
|
||||
if !exists {
|
||||
return
|
||||
@@ -117,11 +118,11 @@ pub fn (mut m DBHandler[T]) delete(id u32) ! {
|
||||
m.session_state.dbs.db_data_core.delete(id)!
|
||||
}
|
||||
|
||||
//internal function to always have at least one index key, the default is id
|
||||
// internal function to always have at least one index key, the default is id
|
||||
fn (mut m DBHandler[T]) index_keys(item T) !map[string]string {
|
||||
mut keymap := item.index_keys()
|
||||
if keymap.len==0{
|
||||
keymap["id"]=item.id.str()
|
||||
if keymap.len == 0 {
|
||||
keymap['id'] = item.id.str()
|
||||
}
|
||||
return keymap
|
||||
}
|
||||
@@ -155,7 +156,6 @@ pub fn (mut m DBHandler[T]) list() ![]u32 {
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
pub fn (mut m DBHandler[T]) getall() ![]T {
|
||||
mut items := []T{}
|
||||
for id in m.list()! {
|
||||
|
||||
176
lib/circles/mcc/db/contacts_db.v
Normal file
176
lib/circles/mcc/db/contacts_db.v
Normal file
@@ -0,0 +1,176 @@
|
||||
module db
|
||||
|
||||
import freeflowuniverse.herolib.circles.base { DBHandler, SessionState, new_dbhandler }
|
||||
import freeflowuniverse.herolib.circles.mcc.models { Contact }
|
||||
|
||||
@[heap]
|
||||
pub struct ContactsDB {
|
||||
pub mut:
|
||||
db DBHandler[Contact]
|
||||
}
|
||||
|
||||
pub fn new_contacts_db(session_state SessionState) !ContactsDB {
|
||||
return ContactsDB{
|
||||
db: new_dbhandler[Contact]('contacts', session_state)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut c ContactsDB) new() Contact {
|
||||
return Contact{}
|
||||
}
|
||||
|
||||
// set adds or updates an Contacts
|
||||
pub fn (mut c ContactsDB) set(contact Contact) !Contact {
|
||||
return c.db.set(contact)!
|
||||
}
|
||||
|
||||
// get retrieves an email by its ID
|
||||
pub fn (mut c ContactsDB) get(id u32) !Contact {
|
||||
return c.db.get(id)!
|
||||
}
|
||||
|
||||
// list returns all email IDs
|
||||
pub fn (mut c ContactsDB) list() ![]u32 {
|
||||
return c.db.list()!
|
||||
}
|
||||
|
||||
pub fn (mut c ContactsDB) getall() ![]Contact {
|
||||
return c.db.getall()!
|
||||
}
|
||||
|
||||
// delete removes an email by its ID
|
||||
pub fn (mut c ContactsDB) delete(id u32) ! {
|
||||
c.db.delete(id)!
|
||||
}
|
||||
|
||||
//////////////////CUSTOM METHODS//////////////////////////////////
|
||||
|
||||
// get_by_uid retrieves an email by its UID
|
||||
pub fn (mut c ContactsDB) get_by_uid(uid u32) !Contact {
|
||||
return c.db.get_by_key('uid', uid.str())!
|
||||
}
|
||||
|
||||
// get_by_mailbox retrieves all contacts in a specific mailbox
|
||||
pub fn (mut c ContactsDB) get_by_mailbox(mailbox string) ![]Contact {
|
||||
// Get all contacts
|
||||
allcontacts := c.getall()!
|
||||
|
||||
// Filter contacts by mailbox
|
||||
mut result := []Contact{}
|
||||
// for email in all_contacts {
|
||||
// if email.mailbox == mailbox {
|
||||
// result << email
|
||||
// }
|
||||
// }
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// delete_by_uid removes an email by its UID
|
||||
pub fn (mut c ContactsDB) delete_by_uid(uid u32) ! {
|
||||
// Get the email by UID
|
||||
email := c.get_by_uid(uid) or {
|
||||
// Email not found, nothing to delete
|
||||
return
|
||||
}
|
||||
|
||||
// Delete the email by ID
|
||||
c.delete(email.id)!
|
||||
}
|
||||
|
||||
// delete_by_mailbox removes all contacts in a specific mailbox
|
||||
pub fn (mut c ContactsDB) delete_by_mailbox(mailbox string) ! {
|
||||
// Get all contacts in the mailbox
|
||||
contacts := c.get_by_mailbox(mailbox)!
|
||||
|
||||
// Delete each email
|
||||
for email in contacts {
|
||||
c.delete(email.id)!
|
||||
}
|
||||
}
|
||||
|
||||
// update_flags updates the flags of an email
|
||||
pub fn (mut c ContactsDB) update_flags(uid u32, flags []string) !Contact {
|
||||
// Get the email by UID
|
||||
mut email := c.get_by_uid(uid)!
|
||||
|
||||
// Update the flags
|
||||
// email.flags = flags
|
||||
|
||||
// Save the updated email
|
||||
return c.set(email)!
|
||||
}
|
||||
|
||||
// search_by_subject searches for contacts with a specific subject substring
|
||||
pub fn (mut c ContactsDB) search_by_subject(subject string) ![]Contact {
|
||||
mut matching_contacts := []Contact{}
|
||||
|
||||
// Get all email IDs
|
||||
// email_ids := c.list()!
|
||||
|
||||
// Filter contacts that match the subject
|
||||
// for id in email_ids {
|
||||
// // Get the email by ID
|
||||
// // email := c.get(id) or { continue }
|
||||
|
||||
// // // Check if the email has an envelope with a matching subject
|
||||
// // if envelope := email.envelope {
|
||||
// // if envelope.subject.to_lower().contains(subject.to_lower()) {
|
||||
// // matching_contacts << email
|
||||
// // }
|
||||
// // }
|
||||
// }
|
||||
|
||||
return matching_contacts
|
||||
}
|
||||
|
||||
// search_by_address searches for contacts with a specific email address in from, to, cc, or bcc fields
|
||||
pub fn (mut c ContactsDB) search_by_address(address string) ![]Contact {
|
||||
mut matching_contacts := []Contact{}
|
||||
|
||||
// Get all email IDs
|
||||
email_ids := c.list()!
|
||||
|
||||
// Filter contacts that match the address
|
||||
for id in email_ids {
|
||||
// Get the email by ID
|
||||
email := c.get(id) or { continue }
|
||||
|
||||
// Check if the email has an envelope with a matching address
|
||||
// if envelope := email.envelope {
|
||||
// // Check in from addresses
|
||||
// for addr in envelope.from {
|
||||
// if addr.to_lower().contains(address.to_lower()) {
|
||||
// matching_contacts << email
|
||||
// continue
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Check in to addresses
|
||||
// for addr in envelope.to {
|
||||
// if addr.to_lower().contains(address.to_lower()) {
|
||||
// matching_contacts << email
|
||||
// continue
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Check in cc addresses
|
||||
// for addr in envelope.cc {
|
||||
// if addr.to_lower().contains(address.to_lower()) {
|
||||
// matching_contacts << email
|
||||
// continue
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Check in bcc addresses
|
||||
// for addr in envelope.bcc {
|
||||
// if addr.to_lower().contains(address.to_lower()) {
|
||||
// matching_contacts << email
|
||||
// continue
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
return matching_contacts
|
||||
}
|
||||
@@ -2,121 +2,117 @@ module models
|
||||
|
||||
import freeflowuniverse.herolib.data.ourtime
|
||||
import freeflowuniverse.herolib.data.encoder
|
||||
import strings
|
||||
import strconv
|
||||
import json
|
||||
|
||||
// CalendarEvent represents a calendar event with all its properties
|
||||
pub struct CalendarEvent {
|
||||
pub mut:
|
||||
id u32 // Unique identifier
|
||||
title string // Event title
|
||||
description string // Event details
|
||||
location string // Event location
|
||||
start_time ourtime.OurTime
|
||||
end_time ourtime.OurTime // End time
|
||||
all_day bool // True if it's an all-day event
|
||||
recurrence string // RFC 5545 Recurrence Rule (e.g., "FREQ=DAILY;COUNT=10")
|
||||
attendees []string // List of emails or user IDs
|
||||
organizer string // Organizer email
|
||||
status string // "CONFIRMED", "CANCELLED", "TENTATIVE"
|
||||
caldav_uid string // CalDAV UID for syncing
|
||||
sync_token string // Sync token for tracking changes
|
||||
etag string // ETag for caching
|
||||
color string // User-friendly color categorization
|
||||
id u32 // Unique identifier
|
||||
title string // Event title
|
||||
description string // Event details
|
||||
location string // Event location
|
||||
start_time ourtime.OurTime
|
||||
end_time ourtime.OurTime // End time
|
||||
all_day bool // True if it's an all-day event
|
||||
recurrence string // RFC 5545 Recurrence Rule (e.g., "FREQ=DAILY;COUNT=10")
|
||||
attendees []string // List of emails or user IDs
|
||||
organizer string // Organizer email
|
||||
status string // "CONFIRMED", "CANCELLED", "TENTATIVE"
|
||||
caldav_uid string // CalDAV UID for syncing
|
||||
sync_token string // Sync token for tracking changes
|
||||
etag string // ETag for caching
|
||||
color string // User-friendly color categorization
|
||||
}
|
||||
|
||||
// dumps serializes the CalendarEvent to a byte array
|
||||
pub fn (event CalendarEvent) dumps() ![]u8 {
|
||||
mut enc := encoder.new()
|
||||
mut enc := encoder.new()
|
||||
|
||||
// Add unique encoding ID to identify this type of data
|
||||
enc.add_u16(302) // Unique ID for CalendarEvent type
|
||||
// Add unique encoding ID to identify this type of data
|
||||
enc.add_u16(302) // Unique ID for CalendarEvent type
|
||||
|
||||
// Encode CalendarEvent fields
|
||||
enc.add_u32(event.id)
|
||||
enc.add_string(event.title)
|
||||
enc.add_string(event.description)
|
||||
enc.add_string(event.location)
|
||||
// Encode CalendarEvent fields
|
||||
enc.add_u32(event.id)
|
||||
enc.add_string(event.title)
|
||||
enc.add_string(event.description)
|
||||
enc.add_string(event.location)
|
||||
|
||||
// Encode start_time and end_time as strings
|
||||
enc.add_string(event.start_time.str())
|
||||
enc.add_string(event.end_time.str())
|
||||
// Encode start_time and end_time as strings
|
||||
enc.add_string(event.start_time.str())
|
||||
enc.add_string(event.end_time.str())
|
||||
|
||||
// Encode all_day as u8 (0 or 1)
|
||||
enc.add_u8(if event.all_day { u8(1) } else { u8(0) })
|
||||
// Encode all_day as u8 (0 or 1)
|
||||
enc.add_u8(if event.all_day { u8(1) } else { u8(0) })
|
||||
|
||||
enc.add_string(event.recurrence)
|
||||
enc.add_string(event.recurrence)
|
||||
|
||||
// Encode attendees array
|
||||
enc.add_u16(u16(event.attendees.len))
|
||||
for attendee in event.attendees {
|
||||
enc.add_string(attendee)
|
||||
}
|
||||
// Encode attendees array
|
||||
enc.add_u16(u16(event.attendees.len))
|
||||
for attendee in event.attendees {
|
||||
enc.add_string(attendee)
|
||||
}
|
||||
|
||||
enc.add_string(event.organizer)
|
||||
enc.add_string(event.status)
|
||||
enc.add_string(event.caldav_uid)
|
||||
enc.add_string(event.sync_token)
|
||||
enc.add_string(event.etag)
|
||||
enc.add_string(event.color)
|
||||
enc.add_string(event.organizer)
|
||||
enc.add_string(event.status)
|
||||
enc.add_string(event.caldav_uid)
|
||||
enc.add_string(event.sync_token)
|
||||
enc.add_string(event.etag)
|
||||
enc.add_string(event.color)
|
||||
|
||||
return enc.data
|
||||
return enc.data
|
||||
}
|
||||
|
||||
// loads deserializes a byte array to a CalendarEvent
|
||||
pub fn calendar_event_loads(data []u8) !CalendarEvent {
|
||||
mut d := encoder.decoder_new(data)
|
||||
mut event := CalendarEvent{}
|
||||
mut d := encoder.decoder_new(data)
|
||||
mut event := CalendarEvent{}
|
||||
|
||||
// Check encoding ID to verify this is the correct type of data
|
||||
encoding_id := d.get_u16()!
|
||||
if encoding_id != 302 {
|
||||
return error('Wrong file type: expected encoding ID 302, got ${encoding_id}, for calendar event')
|
||||
}
|
||||
// Check encoding ID to verify this is the correct type of data
|
||||
encoding_id := d.get_u16()!
|
||||
if encoding_id != 302 {
|
||||
return error('Wrong file type: expected encoding ID 302, got ${encoding_id}, for calendar event')
|
||||
}
|
||||
|
||||
// Decode CalendarEvent fields
|
||||
event.id = d.get_u32()!
|
||||
event.title = d.get_string()!
|
||||
event.description = d.get_string()!
|
||||
event.location = d.get_string()!
|
||||
// Decode CalendarEvent fields
|
||||
event.id = d.get_u32()!
|
||||
event.title = d.get_string()!
|
||||
event.description = d.get_string()!
|
||||
event.location = d.get_string()!
|
||||
|
||||
// Decode start_time and end_time from strings
|
||||
start_time_str := d.get_string()!
|
||||
event.start_time = ourtime.new(start_time_str)!
|
||||
// Decode start_time and end_time from strings
|
||||
start_time_str := d.get_string()!
|
||||
event.start_time = ourtime.new(start_time_str)!
|
||||
|
||||
end_time_str := d.get_string()!
|
||||
event.end_time = ourtime.new(end_time_str)!
|
||||
end_time_str := d.get_string()!
|
||||
event.end_time = ourtime.new(end_time_str)!
|
||||
|
||||
// Decode all_day from u8
|
||||
event.all_day = d.get_u8()! == 1
|
||||
// Decode all_day from u8
|
||||
event.all_day = d.get_u8()! == 1
|
||||
|
||||
event.recurrence = d.get_string()!
|
||||
event.recurrence = d.get_string()!
|
||||
|
||||
// Decode attendees array
|
||||
attendees_len := d.get_u16()!
|
||||
event.attendees = []string{len: int(attendees_len)}
|
||||
for i in 0 .. attendees_len {
|
||||
event.attendees[i] = d.get_string()!
|
||||
}
|
||||
// Decode attendees array
|
||||
attendees_len := d.get_u16()!
|
||||
event.attendees = []string{len: int(attendees_len)}
|
||||
for i in 0 .. attendees_len {
|
||||
event.attendees[i] = d.get_string()!
|
||||
}
|
||||
|
||||
event.organizer = d.get_string()!
|
||||
event.status = d.get_string()!
|
||||
event.caldav_uid = d.get_string()!
|
||||
event.sync_token = d.get_string()!
|
||||
event.etag = d.get_string()!
|
||||
event.color = d.get_string()!
|
||||
event.organizer = d.get_string()!
|
||||
event.status = d.get_string()!
|
||||
event.caldav_uid = d.get_string()!
|
||||
event.sync_token = d.get_string()!
|
||||
event.etag = d.get_string()!
|
||||
event.color = d.get_string()!
|
||||
|
||||
return event
|
||||
return event
|
||||
}
|
||||
|
||||
// index_keys returns the keys to be indexed for this event
|
||||
pub fn (event CalendarEvent) index_keys() map[string]string {
|
||||
mut keys := map[string]string{}
|
||||
keys['id'] = event.id.str()
|
||||
// if event.caldav_uid != '' {
|
||||
// keys['caldav_uid'] = event.caldav_uid
|
||||
// }
|
||||
return keys
|
||||
mut keys := map[string]string{}
|
||||
keys['id'] = event.id.str()
|
||||
// if event.caldav_uid != '' {
|
||||
// keys['caldav_uid'] = event.caldav_uid
|
||||
// }
|
||||
return keys
|
||||
}
|
||||
|
||||
|
||||
67
lib/circles/mcc/models/contacts.v
Normal file
67
lib/circles/mcc/models/contacts.v
Normal file
@@ -0,0 +1,67 @@
|
||||
module models
|
||||
|
||||
import freeflowuniverse.herolib.data.encoder
|
||||
|
||||
pub struct Contact {
|
||||
pub mut:
|
||||
// Database ID
|
||||
id u32 // Database ID (assigned by DBHandler)
|
||||
// Content fields
|
||||
created_at i64 // Unix epoch timestamp
|
||||
modified_at i64 // Unix epoch timestamp
|
||||
first_name string
|
||||
last_name string
|
||||
email string
|
||||
group string
|
||||
}
|
||||
|
||||
// dumps serializes the CalendarEvent to a byte array
|
||||
pub fn (event Contact) dumps() ![]u8 {
|
||||
mut enc := encoder.new()
|
||||
|
||||
// Add unique encoding ID to identify this type of data
|
||||
enc.add_u16(303) // Unique ID for CalendarEvent type
|
||||
|
||||
enc.add_u32(event.id)
|
||||
enc.add_i64(event.created_at)
|
||||
enc.add_i64(event.modified_at)
|
||||
enc.add_string(event.first_name)
|
||||
enc.add_string(event.last_name)
|
||||
enc.add_string(event.email)
|
||||
enc.add_string(event.group)
|
||||
|
||||
return enc.data
|
||||
}
|
||||
|
||||
// loads deserializes a byte array to a Contact
|
||||
pub fn contact_event_loads(data []u8) !Contact {
|
||||
mut d := encoder.decoder_new(data)
|
||||
mut event := Contact{}
|
||||
|
||||
// Check encoding ID to verify this is the correct type of data
|
||||
encoding_id := d.get_u16()!
|
||||
if encoding_id != 303 {
|
||||
return error('Wrong file type: expected encoding ID 303, got ${encoding_id}, for calendar event')
|
||||
}
|
||||
|
||||
// Decode Contact fields
|
||||
event.id = d.get_u32()!
|
||||
event.created_at = d.get_i64()!
|
||||
event.modified_at = d.get_i64()!
|
||||
event.first_name = d.get_string()!
|
||||
event.last_name = d.get_string()!
|
||||
event.email = d.get_string()!
|
||||
event.group = d.get_string()!
|
||||
|
||||
return event
|
||||
}
|
||||
|
||||
// index_keys returns the keys to be indexed for this event
|
||||
pub fn (event Contact) index_keys() map[string]string {
|
||||
mut keys := map[string]string{}
|
||||
keys['id'] = event.id.str()
|
||||
// if event.caldav_uid != '' {
|
||||
// keys['caldav_uid'] = event.caldav_uid
|
||||
// }
|
||||
return keys
|
||||
}
|
||||
9
lib/vfs/vfs_contacts/factory.v
Normal file
9
lib/vfs/vfs_contacts/factory.v
Normal file
@@ -0,0 +1,9 @@
|
||||
module vfs_contacts
|
||||
|
||||
import freeflowuniverse.herolib.vfs
|
||||
import freeflowuniverse.herolib.circles.mcc.db as core
|
||||
|
||||
// new creates a new contacts_db VFS instance
|
||||
pub fn new(contacts_db &core.ContactsDB) !vfs.VFSImplementation {
|
||||
return new_contacts_vfs(contacts_db)!
|
||||
}
|
||||
35
lib/vfs/vfs_contacts/model_fsentry.v
Normal file
35
lib/vfs/vfs_contacts/model_fsentry.v
Normal file
@@ -0,0 +1,35 @@
|
||||
module vfs_contacts
|
||||
|
||||
import freeflowuniverse.herolib.vfs
|
||||
import freeflowuniverse.herolib.circles.mcc.models as contacts
|
||||
|
||||
// ContactsFSEntry implements FSEntry for contacts objects
|
||||
pub struct ContactsFSEntry {
|
||||
pub mut:
|
||||
path string
|
||||
metadata vfs.Metadata
|
||||
contact ?contacts.Contact
|
||||
}
|
||||
|
||||
// is_dir returns true if the entry is a directory
|
||||
pub fn (self &ContactsFSEntry) is_dir() bool {
|
||||
return self.metadata.file_type == .directory
|
||||
}
|
||||
|
||||
// is_file returns true if the entry is a file
|
||||
pub fn (self &ContactsFSEntry) is_file() bool {
|
||||
return self.metadata.file_type == .file
|
||||
}
|
||||
|
||||
// is_symlink returns true if the entry is a symlink
|
||||
pub fn (self &ContactsFSEntry) is_symlink() bool {
|
||||
return self.metadata.file_type == .symlink
|
||||
}
|
||||
|
||||
pub fn (e ContactsFSEntry) get_metadata() vfs.Metadata {
|
||||
return e.metadata
|
||||
}
|
||||
|
||||
pub fn (e ContactsFSEntry) get_path() string {
|
||||
return e.path
|
||||
}
|
||||
18
lib/vfs/vfs_contacts/vfs_contacts.v
Normal file
18
lib/vfs/vfs_contacts/vfs_contacts.v
Normal file
@@ -0,0 +1,18 @@
|
||||
module vfs_contacts
|
||||
|
||||
import freeflowuniverse.herolib.vfs
|
||||
import freeflowuniverse.herolib.circles.mcc.db as core
|
||||
// import freeflowuniverse.herolib.circles.mcc.models as mcc
|
||||
|
||||
// ContactsVFS represents the virtual file system for contacts
|
||||
pub struct ContactsVFS {
|
||||
pub mut:
|
||||
contacts_db &core.ContactsDB // Reference to the contacts database
|
||||
}
|
||||
|
||||
// new_contacts_vfs creates a new contacts VFS
|
||||
pub fn new_contacts_vfs(contacts_db &core.ContactsDB) !vfs.VFSImplementation {
|
||||
return &ContactsVFS{
|
||||
contacts_db: contacts_db
|
||||
}
|
||||
}
|
||||
410
lib/vfs/vfs_contacts/vfs_implementation.v
Normal file
410
lib/vfs/vfs_contacts/vfs_implementation.v
Normal file
@@ -0,0 +1,410 @@
|
||||
module vfs_contacts
|
||||
|
||||
import json
|
||||
import time
|
||||
import freeflowuniverse.herolib.vfs
|
||||
import freeflowuniverse.herolib.circles.mcc.models as contacts
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
|
||||
// Basic operations
|
||||
pub fn (mut myvfs ContactsVFS) 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 ContactsFSEntry{
|
||||
path: ''
|
||||
metadata: metadata
|
||||
}
|
||||
}
|
||||
|
||||
// File operations
|
||||
pub fn (mut myvfs ContactsVFS) file_create(path string) !vfs.FSEntry {
|
||||
return error('Contacts VFS is read-only')
|
||||
}
|
||||
|
||||
pub fn (mut myvfs ContactsVFS) 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}')
|
||||
}
|
||||
|
||||
contacts_entry := entry as ContactsFSEntry
|
||||
if contact := contacts_entry.contact {
|
||||
return json.encode(contact).bytes()
|
||||
}
|
||||
|
||||
return error('Failed to read file: ${path}')
|
||||
}
|
||||
|
||||
pub fn (mut myvfs ContactsVFS) file_write(path string, data []u8) ! {
|
||||
return error('Contacts VFS is read-only')
|
||||
}
|
||||
|
||||
pub fn (mut myvfs ContactsVFS) file_concatenate(path string, data []u8) ! {
|
||||
return error('Contacts VFS is read-only')
|
||||
}
|
||||
|
||||
pub fn (mut myvfs ContactsVFS) file_delete(path string) ! {
|
||||
return error('Contacts VFS is read-only')
|
||||
}
|
||||
|
||||
// Directory operations
|
||||
pub fn (mut myvfs ContactsVFS) dir_create(path string) !vfs.FSEntry {
|
||||
return error('Contacts VFS is read-only')
|
||||
}
|
||||
|
||||
pub fn (mut myvfs ContactsVFS) dir_list(path string) ![]vfs.FSEntry {
|
||||
if !myvfs.exists(path) {
|
||||
return error('Directory does not exist: ${path}')
|
||||
}
|
||||
|
||||
// Get all contacts
|
||||
contacts_ := myvfs.contacts_db.getall() or { return error('Failed to get contacts: ${err}') }
|
||||
|
||||
// If we're at the root, return all groups
|
||||
if path == '' {
|
||||
return myvfs.list_groups(contacts_)!
|
||||
}
|
||||
|
||||
// Check if we're in a group path
|
||||
path_parts := path.split('/')
|
||||
if path_parts.len == 1 {
|
||||
// We're in a group, show the by_name and by_email directories
|
||||
return myvfs.list_group_subdirs(path)!
|
||||
} else if path_parts.len == 2 && path_parts[1] in ['by_name', 'by_email'] {
|
||||
// We're in a by_name or by_email directory, list the contacts
|
||||
return myvfs.list_contacts_by_type(path_parts[0], path_parts[1], contacts_)!
|
||||
}
|
||||
|
||||
return []vfs.FSEntry{}
|
||||
}
|
||||
|
||||
pub fn (mut myvfs ContactsVFS) dir_delete(path string) ! {
|
||||
return error('Contacts VFS is read-only')
|
||||
}
|
||||
|
||||
// Symlink operations
|
||||
pub fn (mut myvfs ContactsVFS) link_create(target_path string, link_path string) !vfs.FSEntry {
|
||||
return error('Contacts VFS does not support symlinks')
|
||||
}
|
||||
|
||||
pub fn (mut myvfs ContactsVFS) link_read(path string) !string {
|
||||
return error('Contacts VFS does not support symlinks')
|
||||
}
|
||||
|
||||
pub fn (mut myvfs ContactsVFS) link_delete(path string) ! {
|
||||
return error('Contacts VFS does not support symlinks')
|
||||
}
|
||||
|
||||
// Common operations
|
||||
pub fn (mut myvfs ContactsVFS) exists(path string) bool {
|
||||
// Root always exists
|
||||
if path == '' {
|
||||
return true
|
||||
}
|
||||
|
||||
// Get all contacts
|
||||
contacts_ := myvfs.contacts_db.getall() or { return false }
|
||||
|
||||
path_parts := path.split('/')
|
||||
|
||||
// Check if the path is a group
|
||||
if path_parts.len == 1 {
|
||||
for contact in contacts_ {
|
||||
if contact.group == path_parts[0] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the path is a group subdir (by_name or by_email)
|
||||
if path_parts.len == 2 && path_parts[1] in ['by_name', 'by_email'] {
|
||||
for contact in contacts_ {
|
||||
if contact.group == path_parts[0] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the path is a contact file
|
||||
if path_parts.len == 3 && path_parts[1] in ['by_name', 'by_email'] {
|
||||
for contact in contacts_ {
|
||||
if contact.group != path_parts[0] {
|
||||
continue
|
||||
}
|
||||
|
||||
if path_parts[1] == 'by_name' {
|
||||
filename := texttools.name_fix('${contact.first_name}_${contact.last_name}') +
|
||||
'.json'
|
||||
if filename == path_parts[2] {
|
||||
return true
|
||||
}
|
||||
} else if path_parts[1] == 'by_email' {
|
||||
filename := texttools.name_fix(contact.email) + '.json'
|
||||
if filename == path_parts[2] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
pub fn (mut myvfs ContactsVFS) get(path string) !vfs.FSEntry {
|
||||
// Root always exists
|
||||
if path == '' {
|
||||
return myvfs.root_get()!
|
||||
}
|
||||
|
||||
// Get all contacts
|
||||
contacts_ := myvfs.contacts_db.getall() or { return error('Failed to get contacts: ${err}') }
|
||||
|
||||
path_parts := path.split('/')
|
||||
|
||||
// Check if the path is a group
|
||||
if path_parts.len == 1 {
|
||||
for contact in contacts_ {
|
||||
if contact.group == 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 ContactsFSEntry{
|
||||
path: path
|
||||
metadata: metadata
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the path is a group subdir (by_name or by_email)
|
||||
if path_parts.len == 2 && path_parts[1] in ['by_name', 'by_email'] {
|
||||
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 ContactsFSEntry{
|
||||
path: path
|
||||
metadata: metadata
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the path is a contact file
|
||||
if path_parts.len == 3 && path_parts[1] in ['by_name', 'by_email'] {
|
||||
for contact in contacts_ {
|
||||
if contact.group != path_parts[0] {
|
||||
continue
|
||||
}
|
||||
|
||||
if path_parts[1] == 'by_name' {
|
||||
filename := texttools.name_fix('${contact.first_name}_${contact.last_name}') +
|
||||
'.json'
|
||||
if filename == path_parts[2] {
|
||||
metadata := vfs.Metadata{
|
||||
id: u32(contact.id)
|
||||
name: filename
|
||||
file_type: .file
|
||||
size: u64(json.encode(contact).len)
|
||||
created_at: contact.created_at
|
||||
modified_at: contact.modified_at
|
||||
accessed_at: time.now().unix()
|
||||
}
|
||||
|
||||
return ContactsFSEntry{
|
||||
path: path
|
||||
metadata: metadata
|
||||
contact: contact
|
||||
}
|
||||
}
|
||||
} else if path_parts[1] == 'by_email' {
|
||||
filename := texttools.name_fix(contact.email) + '.json'
|
||||
if filename == path_parts[2] {
|
||||
metadata := vfs.Metadata{
|
||||
id: u32(contact.id)
|
||||
name: filename
|
||||
file_type: .file
|
||||
size: u64(json.encode(contact).len)
|
||||
created_at: contact.created_at
|
||||
modified_at: contact.modified_at
|
||||
accessed_at: time.now().unix()
|
||||
}
|
||||
|
||||
return ContactsFSEntry{
|
||||
path: path
|
||||
metadata: metadata
|
||||
contact: contact
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return error('Path not found: ${path}')
|
||||
}
|
||||
|
||||
pub fn (mut myvfs ContactsVFS) rename(old_path string, new_path string) !vfs.FSEntry {
|
||||
return error('Contacts VFS is read-only')
|
||||
}
|
||||
|
||||
pub fn (mut myvfs ContactsVFS) copy(src_path string, dst_path string) !vfs.FSEntry {
|
||||
return error('Contacts VFS is read-only')
|
||||
}
|
||||
|
||||
pub fn (mut myvfs ContactsVFS) move(src_path string, dst_path string) !vfs.FSEntry {
|
||||
return error('Contacts VFS is read-only')
|
||||
}
|
||||
|
||||
pub fn (mut myvfs ContactsVFS) delete(path string) ! {
|
||||
return error('Contacts VFS is read-only')
|
||||
}
|
||||
|
||||
// FSEntry Operations
|
||||
pub fn (mut myvfs ContactsVFS) get_path(entry &vfs.FSEntry) !string {
|
||||
contacts_entry := entry as ContactsFSEntry
|
||||
return contacts_entry.path
|
||||
}
|
||||
|
||||
pub fn (mut myvfs ContactsVFS) print() ! {
|
||||
println('Contacts VFS')
|
||||
}
|
||||
|
||||
// Cleanup operation
|
||||
pub fn (mut myvfs ContactsVFS) destroy() ! {
|
||||
// Nothing to clean up
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
fn (mut myvfs ContactsVFS) list_groups(contacts_ []contacts.Contact) ![]vfs.FSEntry {
|
||||
mut groups := map[string]bool{}
|
||||
|
||||
// Collect unique group names
|
||||
for contact in contacts_ {
|
||||
groups[contact.group] = true
|
||||
}
|
||||
|
||||
// Create FSEntry for each group
|
||||
mut result := []vfs.FSEntry{cap: groups.len}
|
||||
for group, _ in groups {
|
||||
metadata := vfs.Metadata{
|
||||
id: u32(group.bytes().bytestr().hash())
|
||||
name: group
|
||||
file_type: .directory
|
||||
created_at: time.now().unix()
|
||||
modified_at: time.now().unix()
|
||||
accessed_at: time.now().unix()
|
||||
}
|
||||
|
||||
result << ContactsFSEntry{
|
||||
path: group
|
||||
metadata: metadata
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fn (mut myvfs ContactsVFS) list_group_subdirs(group string) ![]vfs.FSEntry {
|
||||
mut result := []vfs.FSEntry{cap: 2}
|
||||
|
||||
// Create by_name directory
|
||||
by_name_metadata := vfs.Metadata{
|
||||
id: u32('${group}/by_name'.bytes().bytestr().hash())
|
||||
name: 'by_name'
|
||||
file_type: .directory
|
||||
created_at: time.now().unix()
|
||||
modified_at: time.now().unix()
|
||||
accessed_at: time.now().unix()
|
||||
}
|
||||
|
||||
result << ContactsFSEntry{
|
||||
path: '${group}/by_name'
|
||||
metadata: by_name_metadata
|
||||
}
|
||||
|
||||
// Create by_email directory
|
||||
by_email_metadata := vfs.Metadata{
|
||||
id: u32('${group}/by_email'.bytes().bytestr().hash())
|
||||
name: 'by_email'
|
||||
file_type: .directory
|
||||
created_at: time.now().unix()
|
||||
modified_at: time.now().unix()
|
||||
accessed_at: time.now().unix()
|
||||
}
|
||||
|
||||
result << ContactsFSEntry{
|
||||
path: '${group}/by_email'
|
||||
metadata: by_email_metadata
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fn (mut myvfs ContactsVFS) list_contacts_by_type(group string, list_type string, contacts_ []contacts.Contact) ![]vfs.FSEntry {
|
||||
mut result := []vfs.FSEntry{}
|
||||
|
||||
for contact in contacts_ {
|
||||
if contact.group != group {
|
||||
continue
|
||||
}
|
||||
|
||||
if list_type == 'by_name' {
|
||||
filename := texttools.name_fix('${contact.first_name}_${contact.last_name}') + '.json'
|
||||
metadata := vfs.Metadata{
|
||||
id: u32(contact.id)
|
||||
name: filename
|
||||
file_type: .file
|
||||
size: u64(json.encode(contact).len)
|
||||
created_at: contact.created_at
|
||||
modified_at: contact.modified_at
|
||||
accessed_at: time.now().unix()
|
||||
}
|
||||
|
||||
result << ContactsFSEntry{
|
||||
path: '${group}/by_name/${filename}'
|
||||
metadata: metadata
|
||||
contact: contact
|
||||
}
|
||||
} else if list_type == 'by_email' {
|
||||
filename := texttools.name_fix(contact.email) + '.json'
|
||||
metadata := vfs.Metadata{
|
||||
id: u32(contact.id)
|
||||
name: filename
|
||||
file_type: .file
|
||||
size: u64(json.encode(contact).len)
|
||||
created_at: contact.created_at
|
||||
modified_at: contact.modified_at
|
||||
accessed_at: time.now().unix()
|
||||
}
|
||||
|
||||
result << ContactsFSEntry{
|
||||
path: '${group}/by_email/${filename}'
|
||||
metadata: metadata
|
||||
contact: contact
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
104
lib/vfs/vfs_contacts/vfs_implementation_test.v
Normal file
104
lib/vfs/vfs_contacts/vfs_implementation_test.v
Normal file
@@ -0,0 +1,104 @@
|
||||
module vfs_contacts
|
||||
|
||||
import freeflowuniverse.herolib.vfs
|
||||
import freeflowuniverse.herolib.circles.base
|
||||
import freeflowuniverse.herolib.circles.mcc.db as core
|
||||
import freeflowuniverse.herolib.circles.mcc.models as contacts
|
||||
// import freeflowuniverse.herolib.circles.mcc.models
|
||||
|
||||
fn test_contacts_vfs() ! {
|
||||
// Create a session state
|
||||
mut session_state := base.new_session(name: 'test')!
|
||||
|
||||
// Setup mock database
|
||||
mut contacts_db := core.new_contacts_db(session_state)!
|
||||
|
||||
// Set instances
|
||||
contact1 := contacts.Contact{
|
||||
id: 1
|
||||
first_name: 'John'
|
||||
last_name: 'Doe'
|
||||
email: 'john.doe@example.com'
|
||||
group: 'personal'
|
||||
created_at: 1698777600
|
||||
modified_at: 1698777600
|
||||
}
|
||||
|
||||
contact2 := contacts.Contact{
|
||||
id: 2
|
||||
first_name: 'Jane'
|
||||
last_name: 'Doe'
|
||||
email: 'jane.doe@example.com'
|
||||
group: 'personal'
|
||||
created_at: 1698777600
|
||||
modified_at: 1698777600
|
||||
}
|
||||
|
||||
contact3 := contacts.Contact{
|
||||
id: 3
|
||||
first_name: 'Janane'
|
||||
last_name: 'Doe'
|
||||
email: 'Janane.doe@example.com'
|
||||
group: 'other'
|
||||
created_at: 1698777600
|
||||
modified_at: 1698777600
|
||||
}
|
||||
|
||||
// Add emails to the database
|
||||
contacts_db.set(contact1) or { panic(err) }
|
||||
contacts_db.set(contact2) or { panic(err) }
|
||||
contacts_db.set(contact3) or { panic(err) }
|
||||
|
||||
// Create VFS instance
|
||||
mut contacts_vfs := new(&contacts_db) or { panic(err) }
|
||||
|
||||
// vfs_instance := new_contacts_vfs(contacts_db)!
|
||||
|
||||
// Test root directory
|
||||
root := contacts_vfs.root_get()!
|
||||
assert root.is_dir()
|
||||
|
||||
// Test listing groups
|
||||
groups := contacts_vfs.dir_list('')!
|
||||
assert groups.len == 2
|
||||
|
||||
contacts_entry1 := groups[0] as ContactsFSEntry
|
||||
contacts_entry2 := groups[1] as ContactsFSEntry
|
||||
|
||||
assert contacts_entry1.metadata.name == 'personal'
|
||||
assert contacts_entry2.metadata.name == 'other'
|
||||
|
||||
// Test listing group subdirs
|
||||
subdirs := contacts_vfs.dir_list('personal')!
|
||||
assert subdirs.len == 2
|
||||
|
||||
contact_subdir1 := subdirs[0] as ContactsFSEntry
|
||||
contact_subdir2 := subdirs[1] as ContactsFSEntry
|
||||
|
||||
assert contact_subdir1.metadata.name == 'by_name'
|
||||
assert contact_subdir2.metadata.name == 'by_email'
|
||||
|
||||
// Test listing contacts by name
|
||||
contacts_by_name := contacts_vfs.dir_list('personal/by_name')!
|
||||
assert contacts_by_name.len == 2
|
||||
|
||||
contacts_by_name1 := contacts_by_name[0] as ContactsFSEntry
|
||||
assert contacts_by_name1.metadata.name == 'john_doe.json'
|
||||
|
||||
// Test listing contacts by email
|
||||
contacts_by_email := contacts_vfs.dir_list('personal/by_email')!
|
||||
assert contacts_by_email.len == 2
|
||||
|
||||
contacts_by_email1 := contacts_by_email[0] as ContactsFSEntry
|
||||
assert contacts_by_email1.metadata.name == 'john_doeexample.com.json'
|
||||
|
||||
// Test reading contact file
|
||||
contact_data := contacts_vfs.file_read('personal/by_name/john_doe.json')!
|
||||
assert contact_data.len > 0
|
||||
|
||||
// Test existence checks
|
||||
assert contacts_vfs.exists('personal')
|
||||
assert contacts_vfs.exists('personal/by_name')
|
||||
assert contacts_vfs.exists('personal/by_name/john_doe.json')
|
||||
assert !contacts_vfs.exists('nonexistent')
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
module vfs_mail
|
||||
|
||||
import freeflowuniverse.herolib.vfs
|
||||
import freeflowuniverse.herolib.circles.dbs.core
|
||||
import freeflowuniverse.herolib.circles.mcc.db as core
|
||||
|
||||
// new creates a new mail VFS instance
|
||||
pub fn new(mail_db &core.MailDB) !vfs.VFSImplementation {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module vfs_mail
|
||||
|
||||
import freeflowuniverse.herolib.vfs
|
||||
import freeflowuniverse.herolib.circles.models.mcc.mail
|
||||
import freeflowuniverse.herolib.circles.mcc.models as mail
|
||||
|
||||
// MailFSEntry implements FSEntry for mail objects
|
||||
pub struct MailFSEntry {
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
module vfs_mail
|
||||
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import freeflowuniverse.herolib.vfs
|
||||
import freeflowuniverse.herolib.circles.models.mcc.mail
|
||||
import freeflowuniverse.herolib.circles.dbs.core
|
||||
import freeflowuniverse.herolib.circles.mcc.models as mail
|
||||
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()
|
||||
id: 1
|
||||
name: ''
|
||||
file_type: .directory
|
||||
created_at: time.now().unix()
|
||||
modified_at: time.now().unix()
|
||||
accessed_at: time.now().unix()
|
||||
}
|
||||
|
||||
return MailFSEntry{
|
||||
path: ''
|
||||
path: ''
|
||||
metadata: metadata
|
||||
}
|
||||
}
|
||||
@@ -202,16 +200,16 @@ pub fn (mut myvfs MailVFS) get(path string) !vfs.FSEntry {
|
||||
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()
|
||||
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
|
||||
path: path
|
||||
metadata: metadata
|
||||
}
|
||||
}
|
||||
@@ -221,16 +219,16 @@ pub fn (mut myvfs MailVFS) get(path string) !vfs.FSEntry {
|
||||
// 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()
|
||||
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
|
||||
path: path
|
||||
metadata: metadata
|
||||
}
|
||||
}
|
||||
@@ -244,38 +242,38 @@ pub fn (mut myvfs MailVFS) get(path string) !vfs.FSEntry {
|
||||
|
||||
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
|
||||
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
|
||||
path: path
|
||||
metadata: metadata
|
||||
email: email
|
||||
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
|
||||
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
|
||||
path: path
|
||||
metadata: metadata
|
||||
email: email
|
||||
email: email
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -333,16 +331,16 @@ fn (mut myvfs MailVFS) list_mailboxes(emails []mail.Email) ![]vfs.FSEntry {
|
||||
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()
|
||||
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
|
||||
path: mailbox
|
||||
metadata: metadata
|
||||
}
|
||||
}
|
||||
@@ -355,31 +353,31 @@ fn (mut myvfs MailVFS) list_mailbox_subdirs(mailbox string) ![]vfs.FSEntry {
|
||||
|
||||
// Create id directory
|
||||
id_metadata := vfs.Metadata{
|
||||
id: u32('${mailbox}/id'.bytes().bytestr().hash())
|
||||
name: 'id'
|
||||
file_type: .directory
|
||||
created_at: time.now().unix()
|
||||
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'
|
||||
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()
|
||||
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'
|
||||
path: '${mailbox}/subject'
|
||||
metadata: subject_metadata
|
||||
}
|
||||
|
||||
@@ -397,42 +395,41 @@ fn (mut myvfs MailVFS) list_emails_by_type(mailbox string, list_type string, ema
|
||||
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
|
||||
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}'
|
||||
path: '${mailbox}/id/${filename}'
|
||||
metadata: metadata
|
||||
email: email
|
||||
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()
|
||||
}
|
||||
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
|
||||
}
|
||||
result << MailFSEntry{
|
||||
path: '${mailbox}/subject/${filename}'
|
||||
metadata: metadata
|
||||
email: email
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@@ -16,47 +16,47 @@ fn test_mail_vfs() {
|
||||
|
||||
// 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'
|
||||
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{
|
||||
envelope: mail.Envelope{
|
||||
subject: 'Test Email 1'
|
||||
from: ['sender1@example.com']
|
||||
to: ['recipient1@example.com']
|
||||
date: time.now().unix()
|
||||
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'
|
||||
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{
|
||||
envelope: mail.Envelope{
|
||||
subject: 'Test Email 2'
|
||||
from: ['sender2@example.com']
|
||||
to: ['recipient2@example.com']
|
||||
date: time.now().unix()
|
||||
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'
|
||||
id: 3
|
||||
uid: 103
|
||||
seq_num: 3
|
||||
mailbox: 'Inbox'
|
||||
message: 'This is a test email 3'
|
||||
internal_date: time.now().unix()
|
||||
envelope: mail.Envelope{
|
||||
envelope: mail.Envelope{
|
||||
subject: 'Test Email 3'
|
||||
from: ['sender3@example.com']
|
||||
to: ['recipient3@example.com']
|
||||
date: time.now().unix()
|
||||
from: ['sender3@example.com']
|
||||
to: ['recipient3@example.com']
|
||||
date: time.now().unix()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module vfs_mail
|
||||
|
||||
import freeflowuniverse.herolib.vfs
|
||||
import freeflowuniverse.herolib.circles.dbs.core
|
||||
import freeflowuniverse.herolib.circles.mcc.db as core
|
||||
|
||||
// MailVFS implements the VFS interface for mail objects
|
||||
pub struct MailVFS {
|
||||
|
||||
Reference in New Issue
Block a user