diff --git a/plugin/src/tracing/logger.ts b/plugin/src/tracing/logger.ts new file mode 100644 index 0000000..fbaf0a1 --- /dev/null +++ b/plugin/src/tracing/logger.ts @@ -0,0 +1,77 @@ +import { Notice } from "obsidian"; + +export enum LogLevel { + DEBUG = "DEBUG", + INFO = "INFO", + WARNING = "WARNING", + ERROR = "ERROR", +} + +class LogLine { + public constructor(public level: LogLevel, public message: string) {} + + public toString(): string { + return `${this.formatLevel()}: ${this.message}`; + } + + private formatLevel(): string { + switch (this.level) { + case LogLevel.DEBUG: + return "DEBUG"; + case LogLevel.INFO: + return "INFO"; + case LogLevel.WARNING: + return "WARNING"; + case LogLevel.ERROR: + return "ERROR"; + default: + return "UNKNOWN"; + } + } +} + +export class Logger { + private static readonly MAX_MESSAGES = 1000; + + private static instance: Logger | null = null; + private readonly messages: LogLine[] = []; + + private constructor() {} // eslint-disable-line @typescript-eslint/no-empty-function + + public static getInstance(): Logger { + if (!Logger.instance) { + Logger.instance = new Logger(); + } + return Logger.instance; + } + + public debug(message: string): void { + this.pushMessage(message, LogLevel.DEBUG); + } + + public info(message: string): void { + this.pushMessage(message, LogLevel.INFO); + } + + public warn(message: string): void { + this.pushMessage(message, LogLevel.WARNING); + } + + public error(message: string): void { + this.pushMessage(message, LogLevel.ERROR); + new Notice(message, 5000); + } + + public getMessages(mininumSeverity: LogLevel): LogLine[] { + return this.messages.filter( + (message) => message.level >= mininumSeverity + ); + } + + private pushMessage(message: string, level: LogLevel): void { + this.messages.push(new LogLine(level, message)); + if (this.messages.length > Logger.MAX_MESSAGES) { + this.messages.shift(); + } + } +} diff --git a/plugin/src/tracing/sync-history.ts b/plugin/src/tracing/sync-history.ts new file mode 100644 index 0000000..292d7f6 --- /dev/null +++ b/plugin/src/tracing/sync-history.ts @@ -0,0 +1,94 @@ +import type { RelativePath } from "src/database/document-metadata"; +import { Logger } from "./logger"; + +export interface CommonHistoryEntry { + status: SyncStatus; + relativePath: RelativePath; + message: string; + type?: SyncType; + source?: SyncSource; +} + +export enum SyncType { + CREATE = "CREATE", + UPDATE = "UPDATE", + DELETE = "DELETE", +} + +export enum SyncSource { + PUSH = "PUSH", + PULL = "PULL", +} + +export enum SyncStatus { + NO_OP = "NO_OP", + SUCCESS = "SUCCESS", + ERROR = "ERROR", +} + +export type HistoryEntry = CommonHistoryEntry & { timestamp: Date }; + +export interface HistoryStats { + success: number; + error: number; +} + +export class SyncHistory { + private static readonly MAX_ENTRIES = 1000; + + private entries: HistoryEntry[] = []; + private readonly requestCountListeners: ((status: HistoryStats) => void)[] = + []; + private status: HistoryStats = { + success: 0, + error: 0, + }; + + public getMessages(): HistoryEntry[] { + return this.entries; + } + + public reset(): void { + this.entries = []; + this.status = { + success: 0, + error: 0, + }; + this.requestCountListeners.forEach((listener) => { + listener(this.status); + }); + } + + public addSyncHistoryStatsChangeListener( + listener: (status: HistoryStats) => void + ): void { + this.requestCountListeners.push(listener); + listener({ ...this.status }); + } + + public addHistoryEntry(entry: CommonHistoryEntry): void { + const historyEntry = { + ...entry, + timestamp: new Date(), + }; + this.entries.push(historyEntry); + + if (entry.status === SyncStatus.SUCCESS) { + this.status.success++; + Logger.getInstance().info(`Synced file: ${entry.relativePath}`); + } else if (entry.status === SyncStatus.ERROR) { + this.status.error++; + Logger.getInstance().error( + `Error syncing file: ${entry.relativePath} - ${entry.message}` + ); + } + + this.requestCountListeners.forEach((listener) => { + listener(this.status); + }); + + if (this.entries.length > SyncHistory.MAX_ENTRIES) { + this.entries.shift(); + } + } +} diff --git a/plugin/src/views/status-bar.ts b/plugin/src/views/status-bar.ts index ec83f75..c1f3517 100644 --- a/plugin/src/views/status-bar.ts +++ b/plugin/src/views/status-bar.ts @@ -1,21 +1,17 @@ -import { Plugin } from "obsidian"; -import { RequestCountStatus, SyncService } from "src/services/sync-service"; +import type { Plugin } from "obsidian"; +import type { HistoryStats, SyncHistory } from "src/tracing/sync-history"; export class StatusBar { - private statusBarItem: HTMLElement; + private readonly statusBarItem: HTMLElement; - public constructor(plugin: Plugin, service: SyncService) { + public constructor(plugin: Plugin, history: SyncHistory) { this.statusBarItem = plugin.addStatusBarItem(); - service.addRequestCountChangeListener((status) => - this.updateStatus(status) + history.addSyncHistoryStatsChangeListener((status) => + { this.updateStatus(status); } ); } - private updateStatus({ - waiting, - success, - failure, - }: RequestCountStatus): void { - this.statusBarItem.setText(`${waiting} 🔄 ${success} ✅ ${failure} ❌`); + private updateStatus({ success, error }: HistoryStats): void { + this.statusBarItem.setText(`${success} ✅ ${error} ❌`); } }