feat: Add calendar VFS implementation
- Adds a new virtual file system (VFS) implementation for calendar data. - The calendar VFS provides a read-only view of calendar events, organized by calendar, date, title, and organizer. - Includes new modules for factory, model, and implementation details. - Adds unit tests to verify the functionality of the calendar VFS.
This commit is contained in:
@@ -1,49 +1,46 @@
|
||||
module radixtree
|
||||
|
||||
import freeflowuniverse.herolib.data.ourdb
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
// Gets a node from the database by its ID
|
||||
pub fn (mut rt RadixTree) get_node_by_id(id u32) !Node {
|
||||
node_data := rt.db.get(id)!
|
||||
node := deserialize_node(node_data)!
|
||||
//console.print_debug('Debug: Retrieved node ${id} with ${node.children.len} children')
|
||||
// console.print_debug('Debug: Retrieved node ${id} with ${node.children.len} children')
|
||||
return node
|
||||
}
|
||||
|
||||
// Logs the current state of a node
|
||||
pub fn (mut rt RadixTree) debug_node(id u32, msg string) ! {
|
||||
node := rt.get_node_by_id(id)!
|
||||
//console.print_debug('Debug: ${msg}')
|
||||
//console.print_debug(' Node ID: ${id}')
|
||||
//console.print_debug(' Key Segment: "${node.key_segment}"')
|
||||
//console.print_debug(' Is Leaf: ${node.is_leaf}')
|
||||
//console.print_debug(' Children: ${node.children.len}')
|
||||
for child in node.children {
|
||||
//console.print_debug(' - Child ID: ${child.node_id}, Key Part: "${child.key_part}"')
|
||||
}
|
||||
// node := rt.get_node_by_id(id)!
|
||||
// // console.print_debug('Debug: ${msg}')
|
||||
// // console.print_debug(' Node ID: ${id}')
|
||||
// // console.print_debug(' Key Segment: "${node.key_segment}"')
|
||||
// // console.print_debug(' Is Leaf: ${node.is_leaf}')
|
||||
// // console.print_debug(' Children: ${node.children.len}')
|
||||
// for child in node.children {
|
||||
// // console.print_debug(' - Child ID: ${child.node_id}, Key Part: "${child.key_part}"')
|
||||
// }
|
||||
}
|
||||
|
||||
// Prints the current state of the database
|
||||
pub fn (mut rt RadixTree) debug_db() ! {
|
||||
//console.print_debug('\nDatabase State:')
|
||||
//console.print_debug('===============')
|
||||
// console.print_debug('\nDatabase State:')
|
||||
// console.print_debug('===============')
|
||||
mut next_id := rt.db.get_next_id()!
|
||||
for id := u32(0); id < next_id; id++ {
|
||||
if data := rt.db.get(id) {
|
||||
if node := deserialize_node(data) {
|
||||
//console.print_debug('ID ${id}:')
|
||||
//console.print_debug(' Key Segment: "${node.key_segment}"')
|
||||
//console.print_debug(' Is Leaf: ${node.is_leaf}')
|
||||
//console.print_debug(' Children: ${node.children.len}')
|
||||
// console.print_debug('ID ${id}:')
|
||||
// console.print_debug(' Key Segment: "${node.key_segment}"')
|
||||
// console.print_debug(' Is Leaf: ${node.is_leaf}')
|
||||
// console.print_debug(' Children: ${node.children.len}')
|
||||
for child in node.children {
|
||||
//console.print_debug(' - Child ID: ${child.node_id}, Key Part: "${child.key_part}"')
|
||||
// console.print_debug(' - Child ID: ${child.node_id}, Key Part: "${child.key_part}"')
|
||||
}
|
||||
} else {
|
||||
//console.print_debug('ID ${id}: Failed to deserialize node')
|
||||
// console.print_debug('ID ${id}: Failed to deserialize node')
|
||||
}
|
||||
} else {
|
||||
//console.print_debug('ID ${id}: No data')
|
||||
// console.print_debug('ID ${id}: No data')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,7 +66,7 @@ pub fn (mut rt RadixTree) print_tree_from_node(node_id u32, indent string) ! {
|
||||
}
|
||||
node_info += ']'
|
||||
}
|
||||
//console.print_debug(node_info)
|
||||
// console.print_debug(node_info)
|
||||
|
||||
// Print children recursively with increased indentation
|
||||
for i, child in node.children {
|
||||
@@ -85,8 +82,8 @@ pub fn (mut rt RadixTree) print_tree_from_node(node_id u32, indent string) ! {
|
||||
|
||||
// Prints the entire tree structure starting from root
|
||||
pub fn (mut rt RadixTree) print_tree() ! {
|
||||
//console.print_debug('\nRadix Tree Structure:')
|
||||
//console.print_debug('===================')
|
||||
// console.print_debug('\nRadix Tree Structure:')
|
||||
// console.print_debug('===================')
|
||||
rt.print_tree_from_node(rt.root_id, '')!
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
module vfs
|
||||
|
||||
import time
|
||||
|
||||
// VFSImplementation defines the interface that all vfscore implementations must follow
|
||||
pub interface VFSImplementation {
|
||||
mut:
|
||||
|
||||
9
lib/vfs/vfs_calendar/factory.v
Normal file
9
lib/vfs/vfs_calendar/factory.v
Normal file
@@ -0,0 +1,9 @@
|
||||
module vfs_calendar
|
||||
|
||||
import freeflowuniverse.herolib.vfs
|
||||
import freeflowuniverse.herolib.circles.mcc.db as core
|
||||
|
||||
// new creates a new calendar_db VFS instance
|
||||
pub fn new(calendar_db &core.CalendarDB) !vfs.VFSImplementation {
|
||||
return new_calendar_vfs(calendar_db)!
|
||||
}
|
||||
37
lib/vfs/vfs_calendar/model_fsentry.v
Normal file
37
lib/vfs/vfs_calendar/model_fsentry.v
Normal file
@@ -0,0 +1,37 @@
|
||||
module vfs_calendar
|
||||
|
||||
import freeflowuniverse.herolib.vfs
|
||||
import freeflowuniverse.herolib.circles.mcc.models as calendars
|
||||
|
||||
// CalendarFSEntry represents a file system entry in the calendar VFS
|
||||
pub struct CalendarFSEntry {
|
||||
pub mut:
|
||||
path string
|
||||
metadata vfs.Metadata
|
||||
calendar ?calendars.CalendarEvent
|
||||
}
|
||||
|
||||
// is_dir returns true if the entry is a directory
|
||||
pub fn (self &CalendarFSEntry) is_dir() bool {
|
||||
return self.metadata.file_type == .directory
|
||||
}
|
||||
|
||||
// is_file returns true if the entry is a file
|
||||
pub fn (self &CalendarFSEntry) is_file() bool {
|
||||
return self.metadata.file_type == .file
|
||||
}
|
||||
|
||||
// is_symlink returns true if the entry is a symlink
|
||||
pub fn (self &CalendarFSEntry) is_symlink() bool {
|
||||
return self.metadata.file_type == .symlink
|
||||
}
|
||||
|
||||
// get_metadata returns the entry's metadata
|
||||
pub fn (e CalendarFSEntry) get_metadata() vfs.Metadata {
|
||||
return e.metadata
|
||||
}
|
||||
|
||||
// get_path returns the entry's path
|
||||
pub fn (e CalendarFSEntry) get_path() string {
|
||||
return e.path
|
||||
}
|
||||
18
lib/vfs/vfs_calendar/vfs_calendar.v
Normal file
18
lib/vfs/vfs_calendar/vfs_calendar.v
Normal file
@@ -0,0 +1,18 @@
|
||||
module vfs_calendar
|
||||
|
||||
import freeflowuniverse.herolib.vfs
|
||||
import freeflowuniverse.herolib.circles.mcc.db as core
|
||||
|
||||
// CalendarVFS represents the virtual file system for calendar data
|
||||
// It provides a read-only view of calendar data organized by calendars
|
||||
pub struct CalendarVFS {
|
||||
pub mut:
|
||||
calendar_db &core.CalendarDB // Reference to the calendar database
|
||||
}
|
||||
|
||||
// new_calendar_vfs creates a new contacts VFS
|
||||
pub fn new_calendar_vfs(calendar_db &core.CalendarDB) !vfs.VFSImplementation {
|
||||
return &CalendarVFS{
|
||||
calendar_db: calendar_db
|
||||
}
|
||||
}
|
||||
599
lib/vfs/vfs_calendar/vfs_implementation.v
Normal file
599
lib/vfs/vfs_calendar/vfs_implementation.v
Normal file
@@ -0,0 +1,599 @@
|
||||
module vfs_calendar
|
||||
|
||||
import json
|
||||
import time
|
||||
import freeflowuniverse.herolib.vfs
|
||||
import freeflowuniverse.herolib.circles.mcc.models as calendar
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
|
||||
// Basic operations
|
||||
pub fn (mut myvfs CalendarVFS) 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 CalendarFSEntry{
|
||||
path: ''
|
||||
metadata: metadata
|
||||
}
|
||||
}
|
||||
|
||||
// File operations
|
||||
pub fn (mut myvfs CalendarVFS) file_create(path string) !vfs.FSEntry {
|
||||
return error('Calendar VFS is read-only')
|
||||
}
|
||||
|
||||
pub fn (mut myvfs CalendarVFS) 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}')
|
||||
}
|
||||
|
||||
calendar_entry := entry as CalendarFSEntry
|
||||
if event := calendar_entry.calendar {
|
||||
return json.encode(event).bytes()
|
||||
}
|
||||
|
||||
return error('Failed to read file: ${path}')
|
||||
}
|
||||
|
||||
pub fn (mut myvfs CalendarVFS) file_write(path string, data []u8) ! {
|
||||
return error('Calendar VFS is read-only')
|
||||
}
|
||||
|
||||
pub fn (mut myvfs CalendarVFS) file_concatenate(path string, data []u8) ! {
|
||||
return error('Calendar VFS is read-only')
|
||||
}
|
||||
|
||||
pub fn (mut myvfs CalendarVFS) file_delete(path string) ! {
|
||||
return error('Calendar VFS is read-only')
|
||||
}
|
||||
|
||||
// Directory operations
|
||||
pub fn (mut myvfs CalendarVFS) dir_create(path string) !vfs.FSEntry {
|
||||
return error('Calendar VFS is read-only')
|
||||
}
|
||||
|
||||
pub fn (mut myvfs CalendarVFS) dir_list(path string) ![]vfs.FSEntry {
|
||||
if !myvfs.exists(path) {
|
||||
return error('Directory does not exist: ${path}')
|
||||
}
|
||||
|
||||
// Get all events
|
||||
events := myvfs.calendar_db.getall() or { return error('Failed to get events: ${err}') }
|
||||
|
||||
// If we're at the root, return all calendars
|
||||
if path == '' {
|
||||
return myvfs.list_calendars(events)!
|
||||
}
|
||||
|
||||
// Split the path to determine the level
|
||||
path_parts := path.split('/')
|
||||
|
||||
// Level 1: We're in a calendar, show the browsing methods (by_date, by_title, by_organizer)
|
||||
if path_parts.len == 1 {
|
||||
return myvfs.list_calendar_subdirs(path)!
|
||||
}
|
||||
|
||||
// Level 2: We're in a browsing method directory
|
||||
if path_parts.len == 2 {
|
||||
match path_parts[1] {
|
||||
'by_date' {
|
||||
return myvfs.list_date_subdirs(path_parts[0], events)!
|
||||
}
|
||||
'by_title', 'by_organizer' {
|
||||
return myvfs.list_events_by_type(path_parts[0], path_parts[1], events)!
|
||||
}
|
||||
else {
|
||||
return error('Invalid browsing method: ${path_parts[1]}. Supported methods are by_date, by_title, by_organizer')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Level 3: We're in a year_month directory under by_date
|
||||
if path_parts.len == 3 && path_parts[1] == 'by_date' {
|
||||
return myvfs.list_events_by_date(path_parts[0], path_parts[2], events)!
|
||||
}
|
||||
|
||||
return error('Path depth not supported: ${path}')
|
||||
}
|
||||
|
||||
pub fn (mut myvfs CalendarVFS) dir_delete(path string) ! {
|
||||
return error('Calendar VFS is read-only')
|
||||
}
|
||||
|
||||
// Symlink operations
|
||||
pub fn (mut myvfs CalendarVFS) link_create(target_path string, link_path string) !vfs.FSEntry {
|
||||
return error('Calendar VFS does not support symlinks')
|
||||
}
|
||||
|
||||
pub fn (mut myvfs CalendarVFS) link_read(path string) !string {
|
||||
return error('Calendar VFS does not support symlinks')
|
||||
}
|
||||
|
||||
pub fn (mut myvfs CalendarVFS) link_delete(path string) ! {
|
||||
return error('Calendar VFS does not support symlinks')
|
||||
}
|
||||
|
||||
// Common operations
|
||||
pub fn (mut myvfs CalendarVFS) exists(path string) bool {
|
||||
// Root always exists
|
||||
if path == '' {
|
||||
return true
|
||||
}
|
||||
|
||||
// Get all events
|
||||
events := myvfs.calendar_db.getall() or { return false }
|
||||
|
||||
path_parts := path.split('/')
|
||||
|
||||
// Level 1: Check if the path is a calendar
|
||||
if path_parts.len == 1 {
|
||||
for event in events {
|
||||
if event.id.str() == path_parts[0] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Level 2: Check if the path is a valid browsing method
|
||||
if path_parts.len == 2 {
|
||||
if path_parts[1] !in ['by_date', 'by_title', 'by_organizer'] {
|
||||
return false
|
||||
}
|
||||
for event in events {
|
||||
if event.id.str() == path_parts[0] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Level 3: Check if the path is a valid year_month directory under by_date
|
||||
if path_parts.len == 3 && path_parts[1] == 'by_date' {
|
||||
for event in events {
|
||||
if event.id.str() != path_parts[0] {
|
||||
continue
|
||||
}
|
||||
event_time := event.start_time.time()
|
||||
year_month := '${event_time.year:04d}_${event_time.month:02d}'
|
||||
if year_month == path_parts[2] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Level 3 or 4: Check if the path is an event file
|
||||
if (path_parts.len == 4 && path_parts[1] == 'by_date')
|
||||
|| (path_parts.len == 3 && path_parts[1] in ['by_title', 'by_organizer']) {
|
||||
for event in events {
|
||||
if event.id.str() != path_parts[0] {
|
||||
continue
|
||||
}
|
||||
|
||||
if path_parts[1] == 'by_date' {
|
||||
event_time := event.start_time.time()
|
||||
year_month := '${event_time.year:04d}_${event_time.month:02d}'
|
||||
day := '${event_time.day:02d}'
|
||||
filename := texttools.name_fix('${day}_${event.title}') + '.json'
|
||||
if year_month == path_parts[2] && filename == path_parts[3] {
|
||||
return true
|
||||
}
|
||||
} else if path_parts[1] == 'by_title' {
|
||||
filename := texttools.name_fix(event.title) + '.json'
|
||||
if filename == path_parts[2] {
|
||||
return true
|
||||
}
|
||||
} else if path_parts[1] == 'by_organizer' {
|
||||
if event.organizer.len > 0 {
|
||||
filename := texttools.name_fix(event.organizer) + '.json'
|
||||
if filename == path_parts[2] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
pub fn (mut myvfs CalendarVFS) get(path string) !vfs.FSEntry {
|
||||
// Root always exists
|
||||
if path == '' {
|
||||
return myvfs.root_get()!
|
||||
}
|
||||
|
||||
// Get all events
|
||||
events := myvfs.calendar_db.getall() or { return error('Failed to get events: ${err}') }
|
||||
|
||||
path_parts := path.split('/')
|
||||
|
||||
// Level 1: Check if the path is a calendar
|
||||
if path_parts.len == 1 {
|
||||
for event in events {
|
||||
if event.id.str() == 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 CalendarFSEntry{
|
||||
path: path
|
||||
metadata: metadata
|
||||
}
|
||||
}
|
||||
}
|
||||
return error('Calendar not found: ${path_parts[0]}')
|
||||
}
|
||||
|
||||
// Level 2: Check if the path is a browsing method directory
|
||||
if path_parts.len == 2 {
|
||||
if path_parts[1] !in ['by_date', 'by_title', 'by_organizer'] {
|
||||
return error('Invalid browsing method: ${path_parts[1]}. Supported methods are by_date, by_title, by_organizer')
|
||||
}
|
||||
for event in events {
|
||||
if event.id.str() == path_parts[0] {
|
||||
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 CalendarFSEntry{
|
||||
path: path
|
||||
metadata: metadata
|
||||
}
|
||||
}
|
||||
}
|
||||
return error('Calendar not found: ${path_parts[0]}')
|
||||
}
|
||||
|
||||
// Level 3: Check if the path is a year_month directory under by_date
|
||||
if path_parts.len == 3 && path_parts[1] == 'by_date' {
|
||||
for event in events {
|
||||
if event.id.str() != path_parts[0] {
|
||||
continue
|
||||
}
|
||||
event_time := event.start_time.time()
|
||||
year_month := '${event_time.year:04d}_${event_time.month:02d}'
|
||||
|
||||
if year_month == path_parts[2] {
|
||||
metadata := vfs.Metadata{
|
||||
id: u32(path.bytes().bytestr().hash())
|
||||
name: path_parts[2]
|
||||
file_type: .directory
|
||||
created_at: time.now().unix()
|
||||
modified_at: time.now().unix()
|
||||
accessed_at: time.now().unix()
|
||||
}
|
||||
return CalendarFSEntry{
|
||||
path: path
|
||||
metadata: metadata
|
||||
}
|
||||
}
|
||||
}
|
||||
return error('Date directory not found: ${path}')
|
||||
}
|
||||
|
||||
// Level 3 or 4: Check if the path is an event file
|
||||
if (path_parts.len == 4 && path_parts[1] == 'by_date')
|
||||
|| (path_parts.len == 3 && path_parts[1] in ['by_title', 'by_organizer']) {
|
||||
for event in events {
|
||||
if event.id.str() != path_parts[0] {
|
||||
continue
|
||||
}
|
||||
|
||||
if path_parts[1] == 'by_date' {
|
||||
event_time := event.start_time.time()
|
||||
year_month := '${event_time.year:04d}_${event_time.month:02d}'
|
||||
day := '${event_time.day:02d}'
|
||||
filename := texttools.name_fix('${day}_${event.title}') + '.json'
|
||||
if year_month == path_parts[2] && filename == path_parts[3] {
|
||||
metadata := vfs.Metadata{
|
||||
id: u32(event.id)
|
||||
name: filename
|
||||
file_type: .file
|
||||
size: u64(json.encode(event).len)
|
||||
created_at: event.start_time.time().unix()
|
||||
modified_at: event.start_time.time().unix()
|
||||
accessed_at: time.now().unix()
|
||||
}
|
||||
return CalendarFSEntry{
|
||||
path: path
|
||||
metadata: metadata
|
||||
calendar: event
|
||||
}
|
||||
}
|
||||
} else if path_parts[1] == 'by_title' {
|
||||
filename := texttools.name_fix(event.title) + '.json'
|
||||
if filename == path_parts[2] {
|
||||
metadata := vfs.Metadata{
|
||||
id: u32(event.id)
|
||||
name: filename
|
||||
file_type: .file
|
||||
size: u64(json.encode(event).len)
|
||||
created_at: event.start_time.time().unix()
|
||||
modified_at: event.start_time.time().unix()
|
||||
accessed_at: time.now().unix()
|
||||
}
|
||||
return CalendarFSEntry{
|
||||
path: path
|
||||
metadata: metadata
|
||||
calendar: event
|
||||
}
|
||||
}
|
||||
} else if path_parts[1] == 'by_organizer' {
|
||||
if event.organizer.len > 0 {
|
||||
filename := texttools.name_fix(event.organizer) + '.json'
|
||||
if filename == path_parts[2] {
|
||||
metadata := vfs.Metadata{
|
||||
id: u32(event.id)
|
||||
name: filename
|
||||
file_type: .file
|
||||
size: u64(json.encode(event).len)
|
||||
created_at: event.start_time.time().unix()
|
||||
modified_at: event.start_time.time().unix()
|
||||
accessed_at: time.now().unix()
|
||||
}
|
||||
return CalendarFSEntry{
|
||||
path: path
|
||||
metadata: metadata
|
||||
calendar: event
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return error('Event file not found: ${path}')
|
||||
}
|
||||
|
||||
return error('Path not found: ${path}')
|
||||
}
|
||||
|
||||
pub fn (mut myvfs CalendarVFS) rename(old_path string, new_path string) !vfs.FSEntry {
|
||||
return error('Calendar VFS is read-only')
|
||||
}
|
||||
|
||||
pub fn (mut myvfs CalendarVFS) copy(src_path string, dst_path string) !vfs.FSEntry {
|
||||
return error('Calendar VFS is read-only')
|
||||
}
|
||||
|
||||
pub fn (mut myvfs CalendarVFS) move(src_path string, dst_path string) !vfs.FSEntry {
|
||||
return error('Calendar VFS is read-only')
|
||||
}
|
||||
|
||||
pub fn (mut myvfs CalendarVFS) delete(path string) ! {
|
||||
return error('Calendar VFS is read-only')
|
||||
}
|
||||
|
||||
// FSEntry Operations
|
||||
pub fn (mut myvfs CalendarVFS) get_path(entry &vfs.FSEntry) !string {
|
||||
calendar_entry := entry as CalendarFSEntry
|
||||
return calendar_entry.path
|
||||
}
|
||||
|
||||
pub fn (mut myvfs CalendarVFS) print() ! {
|
||||
println('Calendar VFS')
|
||||
}
|
||||
|
||||
// Cleanup operation
|
||||
pub fn (mut myvfs CalendarVFS) destroy() ! {
|
||||
// Nothing to clean up
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
// list_calendars lists all unique calendars as directories
|
||||
fn (mut myvfs CalendarVFS) list_calendars(events []calendar.CalendarEvent) ![]vfs.FSEntry {
|
||||
mut calendars := map[string]bool{}
|
||||
|
||||
// Collect unique calendar names
|
||||
for event in events {
|
||||
calendars[event.id.str()] = true
|
||||
}
|
||||
|
||||
// Create FSEntry for each calendar
|
||||
mut result := []vfs.FSEntry{cap: calendars.len}
|
||||
for calendar, _ in calendars {
|
||||
metadata := vfs.Metadata{
|
||||
id: u32(calendar.bytes().bytestr().hash())
|
||||
name: calendar
|
||||
file_type: .directory
|
||||
created_at: time.now().unix()
|
||||
modified_at: time.now().unix()
|
||||
accessed_at: time.now().unix()
|
||||
}
|
||||
result << CalendarFSEntry{
|
||||
path: calendar
|
||||
metadata: metadata
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// list_calendar_subdirs lists the browsing methods (by_date, by_title, by_organizer) for a calendar
|
||||
fn (mut myvfs CalendarVFS) list_calendar_subdirs(calendar_ string) ![]vfs.FSEntry {
|
||||
mut result := []vfs.FSEntry{cap: 3}
|
||||
|
||||
// Create by_date directory
|
||||
by_date_metadata := vfs.Metadata{
|
||||
id: u32('${calendar_}/by_date'.bytes().bytestr().hash())
|
||||
name: 'by_date'
|
||||
file_type: .directory
|
||||
created_at: time.now().unix()
|
||||
modified_at: time.now().unix()
|
||||
accessed_at: time.now().unix()
|
||||
}
|
||||
result << CalendarFSEntry{
|
||||
path: '${calendar_}/by_date'
|
||||
metadata: by_date_metadata
|
||||
}
|
||||
|
||||
// Create by_title directory
|
||||
by_title_metadata := vfs.Metadata{
|
||||
id: u32('${calendar_}/by_title'.bytes().bytestr().hash())
|
||||
name: 'by_title'
|
||||
file_type: .directory
|
||||
created_at: time.now().unix()
|
||||
modified_at: time.now().unix()
|
||||
accessed_at: time.now().unix()
|
||||
}
|
||||
result << CalendarFSEntry{
|
||||
path: '${calendar_}/by_title'
|
||||
metadata: by_title_metadata
|
||||
}
|
||||
|
||||
// Create by_organizer directory
|
||||
by_organizer_metadata := vfs.Metadata{
|
||||
id: u32('${calendar_}/by_organizer'.bytes().bytestr().hash())
|
||||
name: 'by_organizer'
|
||||
file_type: .directory
|
||||
created_at: time.now().unix()
|
||||
modified_at: time.now().unix()
|
||||
accessed_at: time.now().unix()
|
||||
}
|
||||
result << CalendarFSEntry{
|
||||
path: '${calendar_}/by_organizer'
|
||||
metadata: by_organizer_metadata
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// list_date_subdirs lists year_month directories under by_date for a calendar
|
||||
fn (mut myvfs CalendarVFS) list_date_subdirs(calendar_ string, events []calendar.CalendarEvent) ![]vfs.FSEntry {
|
||||
mut date_dirs := map[string]bool{}
|
||||
|
||||
// Collect unique year_month directories
|
||||
for event in events {
|
||||
if event.id.str() != calendar_ {
|
||||
continue
|
||||
}
|
||||
event_time := event.start_time.time()
|
||||
year_month := '${event_time.year:04d}_${event_time.month:02d}'
|
||||
date_dirs[year_month] = true
|
||||
}
|
||||
|
||||
// Create FSEntry for each year_month directory
|
||||
mut result := []vfs.FSEntry{cap: date_dirs.len}
|
||||
for year_month, _ in date_dirs {
|
||||
metadata := vfs.Metadata{
|
||||
id: u32('${calendar_}/by_date/${year_month}'.bytes().bytestr().hash())
|
||||
name: year_month
|
||||
file_type: .directory
|
||||
created_at: time.now().unix()
|
||||
modified_at: time.now().unix()
|
||||
accessed_at: time.now().unix()
|
||||
}
|
||||
result << CalendarFSEntry{
|
||||
path: '${calendar_}/by_date/${year_month}'
|
||||
metadata: metadata
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// list_events_by_date lists events in a specific year_month directory
|
||||
fn (mut myvfs CalendarVFS) list_events_by_date(calendar_ string, year_month string, events []calendar.CalendarEvent) ![]vfs.FSEntry {
|
||||
mut result := []vfs.FSEntry{}
|
||||
|
||||
for event in events {
|
||||
if event.id.str() != calendar_ {
|
||||
continue
|
||||
}
|
||||
|
||||
event_time := event.start_time.time()
|
||||
event_year_month := '${event_time.year:04d}_${event_time.month:02d}'
|
||||
if event_year_month != year_month {
|
||||
continue
|
||||
}
|
||||
|
||||
day := '${event_time.day:02d}'
|
||||
filename := texttools.name_fix('${day}_${event.title}') + '.json'
|
||||
metadata := vfs.Metadata{
|
||||
id: u32(event.id)
|
||||
name: filename
|
||||
file_type: .file
|
||||
size: u64(json.encode(event).len)
|
||||
created_at: event.start_time.time().unix()
|
||||
modified_at: event.start_time.time().unix()
|
||||
accessed_at: time.now().unix()
|
||||
}
|
||||
result << CalendarFSEntry{
|
||||
path: '${calendar_}/by_date/${year_month}/${filename}'
|
||||
metadata: metadata
|
||||
calendar: event
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// list_events_by_type lists events by a specific browsing method (by_title or by_organizer)
|
||||
fn (mut myvfs CalendarVFS) list_events_by_type(calendar_ string, list_type string, events []calendar.CalendarEvent) ![]vfs.FSEntry {
|
||||
mut result := []vfs.FSEntry{}
|
||||
|
||||
for event in events {
|
||||
if event.id.str() != calendar_ {
|
||||
continue
|
||||
}
|
||||
|
||||
if list_type == 'by_title' {
|
||||
filename := texttools.name_fix(event.title) + '.json'
|
||||
metadata := vfs.Metadata{
|
||||
id: u32(event.id)
|
||||
name: filename
|
||||
file_type: .file
|
||||
size: u64(json.encode(event).len)
|
||||
created_at: event.start_time.time().unix()
|
||||
modified_at: event.start_time.time().unix()
|
||||
accessed_at: time.now().unix()
|
||||
}
|
||||
result << CalendarFSEntry{
|
||||
path: '${calendar_}/by_title/${filename}'
|
||||
metadata: metadata
|
||||
calendar: event
|
||||
}
|
||||
} else if list_type == 'by_organizer' {
|
||||
if event.organizer.len > 0 {
|
||||
filename := texttools.name_fix(event.organizer) + '.json'
|
||||
metadata := vfs.Metadata{
|
||||
id: u32(event.id)
|
||||
name: filename
|
||||
file_type: .file
|
||||
size: u64(json.encode(event).len)
|
||||
created_at: event.start_time.time().unix()
|
||||
modified_at: event.start_time.time().unix()
|
||||
accessed_at: time.now().unix()
|
||||
}
|
||||
result << CalendarFSEntry{
|
||||
path: '${calendar_}/by_organizer/${filename}'
|
||||
metadata: metadata
|
||||
calendar: event
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
143
lib/vfs/vfs_calendar/vfs_implementation_test.v
Normal file
143
lib/vfs/vfs_calendar/vfs_implementation_test.v
Normal file
@@ -0,0 +1,143 @@
|
||||
module vfs_calendar
|
||||
|
||||
import freeflowuniverse.herolib.circles.mcc.models as calendar
|
||||
import freeflowuniverse.herolib.circles.mcc.db as core
|
||||
import freeflowuniverse.herolib.data.ourtime
|
||||
import freeflowuniverse.herolib.circles.base
|
||||
import json
|
||||
|
||||
// get_sample_events provides a set of test events
|
||||
fn get_sample_events() ![]calendar.CalendarEvent {
|
||||
return [
|
||||
calendar.CalendarEvent{
|
||||
id: 1
|
||||
title: 'Meeting'
|
||||
start_time: ourtime.new('2023-10-05 14:00:00')!
|
||||
organizer: 'Alice'
|
||||
},
|
||||
calendar.CalendarEvent{
|
||||
id: 2
|
||||
title: 'Conference'
|
||||
start_time: ourtime.new('2023-10-15 09:00:00')!
|
||||
organizer: 'Bob'
|
||||
},
|
||||
calendar.CalendarEvent{
|
||||
id: 3
|
||||
title: 'Webinar'
|
||||
start_time: ourtime.new('2023-11-01 10:00:00')!
|
||||
organizer: '' // No organizer
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// Helper function to create a test VFS instance
|
||||
fn test_calendar_vfs() ! {
|
||||
// Create a session state
|
||||
mut session_state := base.new_session(name: 'test')!
|
||||
|
||||
// Setup mock database
|
||||
mut calendar_db := core.new_calendardb(session_state)!
|
||||
|
||||
events := get_sample_events() or { return error('Failed to get sample events: ${err}') }
|
||||
for event in events {
|
||||
calendar_db.set(event)!
|
||||
}
|
||||
|
||||
mut calendar_vfs := new(&calendar_db) or { panic(err) }
|
||||
|
||||
// Test root directory
|
||||
root := calendar_vfs.root_get()!
|
||||
assert root.is_dir()
|
||||
|
||||
// Test Root directory listing
|
||||
mut entries := calendar_vfs.dir_list('')!
|
||||
assert entries.len == 3 // Three unique calendar IDs: "1", "2", "3"
|
||||
|
||||
mut names := entries.map((it as CalendarFSEntry).metadata.name)
|
||||
|
||||
assert names.contains('1')
|
||||
assert names.contains('2')
|
||||
assert names.contains('3')
|
||||
for entry in entries {
|
||||
assert entry.is_dir()
|
||||
}
|
||||
|
||||
// Test Calendar directory listing
|
||||
entries = calendar_vfs.dir_list('1')!
|
||||
assert entries.len == 3
|
||||
|
||||
names = entries.map((it as CalendarFSEntry).metadata.name)
|
||||
assert 'by_date' in names
|
||||
assert 'by_title' in names
|
||||
assert 'by_organizer' in names
|
||||
for entry in entries {
|
||||
assert entry.is_dir()
|
||||
}
|
||||
|
||||
// Test by_date directory listing
|
||||
entries = calendar_vfs.dir_list('1/by_date')!
|
||||
assert entries.len == 1 // Only October 2023 for calendar "1"
|
||||
names = entries.map((it as CalendarFSEntry).metadata.name)
|
||||
assert '2023_10' in names
|
||||
for entry in entries {
|
||||
assert entry.is_dir()
|
||||
}
|
||||
|
||||
// Test YYYY_MM directory listing
|
||||
entries = calendar_vfs.dir_list('1/by_date/2023_10')!
|
||||
assert entries.len == 1 // One event in October for calendar "1"
|
||||
|
||||
names = entries.map((it as CalendarFSEntry).metadata.name)
|
||||
assert '05_meeting.json' in names // texttools.name_fix converts "Meeting" to lowercase
|
||||
for entry in entries {
|
||||
assert entry.is_file()
|
||||
}
|
||||
|
||||
// Test by_title directory listing
|
||||
entries = calendar_vfs.dir_list('1/by_title')!
|
||||
assert entries.len == 1 // One event in calendar "1"
|
||||
names = entries.map((it as CalendarFSEntry).metadata.name)
|
||||
assert 'meeting.json' in names
|
||||
for entry in entries {
|
||||
assert entry.is_file()
|
||||
}
|
||||
|
||||
// Test by_organizer directory listing
|
||||
entries = calendar_vfs.dir_list('1/by_organizer')!
|
||||
assert entries.len == 1 // One event with an organizer in calendar "1"
|
||||
names = entries.map((it as CalendarFSEntry).metadata.name)
|
||||
assert 'alice.json' in names
|
||||
for entry in entries {
|
||||
assert entry.is_file()
|
||||
}
|
||||
|
||||
// Test File reading
|
||||
data := calendar_vfs.file_read('1/by_date/2023_10/05_meeting.json')!
|
||||
event := json.decode(calendar.CalendarEvent, data.bytestr())!
|
||||
assert event.id == 1
|
||||
assert event.title == 'Meeting'
|
||||
assert event.organizer == 'Alice'
|
||||
assert event.start_time.str() == '2023-10-05 14:00'
|
||||
|
||||
// Test Existence checks
|
||||
assert calendar_vfs.exists('') // Root
|
||||
assert calendar_vfs.exists('1') // Calendar
|
||||
assert calendar_vfs.exists('1/by_date') // Browsing method
|
||||
assert calendar_vfs.exists('1/by_date/2023_10') // Year_month
|
||||
assert calendar_vfs.exists('1/by_date/2023_10/05_meeting.json') // Event file
|
||||
assert calendar_vfs.exists('1/by_title/meeting.json')
|
||||
assert calendar_vfs.exists('1/by_organizer/alice.json')
|
||||
assert !calendar_vfs.exists('non_existent')
|
||||
assert !calendar_vfs.exists('1/invalid_method')
|
||||
|
||||
// File metadata
|
||||
file_entry := calendar_vfs.get('1/by_date/2023_10/05_meeting.json')!
|
||||
assert file_entry.is_file()
|
||||
assert (file_entry as CalendarFSEntry).metadata.name == '05_meeting.json'
|
||||
assert (file_entry as CalendarFSEntry).metadata.size > 0
|
||||
|
||||
// Directory metadata
|
||||
dir_entry := calendar_vfs.get('1/by_date')!
|
||||
assert (dir_entry as CalendarFSEntry).is_dir()
|
||||
assert (dir_entry as CalendarFSEntry).metadata.name == 'by_date'
|
||||
}
|
||||
@@ -52,8 +52,6 @@ fn test_contacts_vfs() ! {
|
||||
// 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()
|
||||
|
||||
Reference in New Issue
Block a user