taskgenius_taskgenius-plugin/src/dataflow/sources/IcsSource.ts
Quorafind deef893535 fix(dataflow): resolve blank TaskView and integrate ICS events
- Add IcsSource for calendar event integration into dataflow
- Fix infinite loop with sourceSeq tracking mechanism
- Resolve blank TaskView on quick startup with debounced events
- Add change detection to prevent unnecessary batch updates
- Implement render guards to prevent concurrent refreshes
- Update reindex commands to support dataflow architecture
- Persist ICS events separately in storage layer
- Update architecture documentation with current implementation

Fixes issues where:
- TaskView showed blank despite having loaded tasks
- Infinite "Batch update" loops occurred
- ICS calendar events disappeared when background sync disabled
- Rapid successive updates caused view resets before rendering
2025-08-20 10:34:02 +08:00

170 lines
No EOL
4.3 KiB
TypeScript

/**
* IcsSource - Event source for ICS calendar data
*
* This source integrates external calendar events into the dataflow architecture.
* It listens to IcsManager updates and emits standardized dataflow events.
*/
import { App, EventRef } from "obsidian";
import { Events, emit, Seq } from "../events/Events";
import type { IcsManager } from "../../managers/ics-manager";
import type { Task } from "../../types/task";
export class IcsSource {
private eventRefs: EventRef[] = [];
private isInitialized = false;
private lastIcsUpdateSeq = 0;
constructor(
private app: App,
private getIcsManager: () => IcsManager | undefined
) {}
/**
* Initialize the ICS source and start listening for calendar updates
*/
initialize(): void {
if (this.isInitialized) return;
console.log("[IcsSource] Initializing ICS event source...");
// Initial load of ICS events
this.loadAndEmitIcsEvents();
// Subscribe to ICS manager updates
this.subscribeToIcsUpdates();
this.isInitialized = true;
}
/**
* Subscribe to ICS manager update events
*/
private subscribeToIcsUpdates(): void {
// Listen for ICS cache updates
this.app.workspace.on("ics-cache-updated" as any, () => {
console.log("[IcsSource] ICS cache updated, reloading events...");
this.loadAndEmitIcsEvents();
});
// Listen for ICS configuration changes
this.app.workspace.on("ics-config-changed" as any, () => {
console.log("[IcsSource] ICS config changed, reloading events...");
this.loadAndEmitIcsEvents();
});
}
/**
* Load ICS events from manager and emit update event
*/
private async loadAndEmitIcsEvents(): Promise<void> {
const icsManager = this.getIcsManager();
if (!icsManager) {
console.log("[IcsSource] No ICS manager available");
return;
}
try {
// Get all ICS events with sync
const icsEvents = await icsManager.getAllEventsWithSync();
// Convert ICS events to Task format with proper source marking
const icsTasks: Task[] = icsEvents.map((event: any) => ({
...event,
metadata: {
...event.metadata,
source: {
type: 'ics',
id: event.source?.id || 'unknown',
name: event.source?.name || 'ICS Calendar'
}
}
}));
console.log(`[IcsSource] Loaded ${icsTasks.length} ICS events`);
// Generate sequence for this update
this.lastIcsUpdateSeq = Seq.next();
// Emit ICS events update
emit(this.app, Events.ICS_EVENTS_UPDATED, {
events: icsTasks,
timestamp: Date.now(),
seq: this.lastIcsUpdateSeq,
stats: {
total: icsTasks.length,
sources: this.getSourceStats(icsTasks)
}
});
} catch (error) {
console.error("[IcsSource] Error loading ICS events:", error);
// Emit empty update on error to clear stale data
emit(this.app, Events.ICS_EVENTS_UPDATED, {
events: [],
timestamp: Date.now(),
seq: Seq.next(),
error: error.message
});
}
}
/**
* Get statistics about ICS sources
*/
private getSourceStats(events: Task[]): Record<string, number> {
const stats: Record<string, number> = {};
for (const event of events) {
const sourceId = event.metadata?.source?.id || 'unknown';
stats[sourceId] = (stats[sourceId] || 0) + 1;
}
return stats;
}
/**
* Refresh ICS events manually
*/
async refresh(): Promise<void> {
console.log("[IcsSource] Manual refresh triggered");
await this.loadAndEmitIcsEvents();
}
/**
* Get current statistics
*/
getStats(): {
initialized: boolean;
lastUpdateSeq: number;
} {
return {
initialized: this.isInitialized,
lastUpdateSeq: this.lastIcsUpdateSeq
};
}
/**
* Cleanup and destroy the source
*/
destroy(): void {
console.log("[IcsSource] Destroying ICS source...");
// Clear event listeners
for (const ref of this.eventRefs) {
this.app.vault.offref(ref);
}
this.eventRefs = [];
// Emit clear event
emit(this.app, Events.ICS_EVENTS_UPDATED, {
events: [],
timestamp: Date.now(),
seq: Seq.next(),
destroyed: true
});
this.isInitialized = false;
}
}