Skip to content

Commit

Permalink
Make comfy settings instance accessible from anywhere (#863)
Browse files Browse the repository at this point in the history
* Make comfy settings a singleton

* update tests

* remove unnecessary mock

* load settings in fromConfig

* suppress test directive

* change to service locator pattern

* update tests
  • Loading branch information
christian-byrne authored Feb 9, 2025
1 parent 43a118d commit 01697a0
Show file tree
Hide file tree
Showing 13 changed files with 351 additions and 226 deletions.
41 changes: 35 additions & 6 deletions src/config/comfySettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ export interface ComfySettingsData {
[key: string]: unknown;
}

/** Backing ref for the singleton settings instance. */
let current: ComfySettings;

/** Service locator for settings. ComfySettings.load() must be called before access. */
export function useComfySettings() {
if (!current) throw new Error('Cannot access ComfySettings before initialization.');
return current;
}

/**
* A read-only interface to an in-memory cache of frontend settings.
* @see {@link ComfySettings} concrete implementation
Expand Down Expand Up @@ -75,12 +84,12 @@ export interface IComfySettings extends FrontendSettingsCache {
* @see {@link IComfySettings} read-write interface
*/
export class ComfySettings implements IComfySettings {
public readonly filePath: string;
private settings: ComfySettingsData = structuredClone(DEFAULT_SETTINGS);
private static writeLocked = false;
readonly #basePath: string;

constructor(basePath: string) {
this.filePath = path.join(basePath, 'user', 'default', 'comfy.settings.json');
private constructor(basePath: string) {
this.#basePath = basePath;
}

/**
Expand All @@ -91,7 +100,11 @@ export class ComfySettings implements IComfySettings {
ComfySettings.writeLocked = true;
}

public async loadSettings() {
get filePath(): string {
return path.join(this.#basePath, 'user', 'default', 'comfy.settings.json');
}

private async loadSettings() {
try {
await fs.access(this.filePath);
} catch {
Expand All @@ -102,9 +115,13 @@ export class ComfySettings implements IComfySettings {
const fileContent = await fs.readFile(this.filePath, 'utf8');
// TODO: Reimplement with validation and error reporting.
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
this.settings = JSON.parse(fileContent);
this.settings = { ...this.settings, ...JSON.parse(fileContent) };
} catch (error) {
log.error(`Settings file cannot be loaded.`, error);
if (error instanceof SyntaxError) {
log.error(`Settings file contains invalid JSON:`, error);
} else {
log.error(`Settings file cannot be loaded.`, error);
}
}
}

Expand Down Expand Up @@ -135,4 +152,16 @@ export class ComfySettings implements IComfySettings {
get<K extends keyof ComfySettingsData>(key: K): ComfySettingsData[K] {
return this.settings[key] ?? DEFAULT_SETTINGS[key];
}

/**
* Static factory method. Loads the settings from disk.
* @param basePath The base path where ComfyUI is installed
* @returns The newly created instance
*/
static async load(basePath: string): Promise<ComfySettings> {
const instance = new ComfySettings(basePath);
await instance.loadSettings();
current = instance;
return instance;
}
}
5 changes: 1 addition & 4 deletions src/desktopApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,10 @@ export class DesktopApp implements HasTelemetry {
}

private async initializeTelemetry(installation: ComfyInstallation): Promise<void> {
const { comfySettings } = installation;

await SentryLogging.setSentryGpuContext();
SentryLogging.shouldSendStatistics = () => comfySettings.get('Comfy-Desktop.SendStatistics');
SentryLogging.getBasePath = () => installation.basePath;

const allowMetrics = await promptMetricsConsent(this.config, this.appWindow, comfySettings);
const allowMetrics = await promptMetricsConsent(this.config, this.appWindow);
this.telemetry.hasConsent = allowMetrics;
if (allowMetrics) this.telemetry.flush();
}
Expand Down
29 changes: 14 additions & 15 deletions src/install/installWizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import path from 'node:path';

import { ComfyConfigManager } from '../config/comfyConfigManager';
import { ComfyServerConfig, ModelPaths } from '../config/comfyServerConfig';
import { type ComfySettingsData, DEFAULT_SETTINGS } from '../config/comfySettings';
import { ComfySettings, type ComfySettingsData } from '../config/comfySettings';
import { InstallOptions } from '../preload';
import { HasTelemetry, ITelemetry, trackEvent } from '../services/telemetry';

Expand All @@ -31,7 +31,7 @@ export class InstallWizard implements HasTelemetry {
// Setup the ComfyUI folder structure.
ComfyConfigManager.createComfyDirectories(this.basePath);
this.initializeUserFiles();
this.initializeSettings();
await this.initializeSettings();
await this.initializeModelPaths();
}

Expand All @@ -52,16 +52,12 @@ export class InstallWizard implements HasTelemetry {
/**
* Setup comfy.settings.json file
*/
public initializeSettings() {
const settingsPath = path.join(this.basePath, 'user', 'default', 'comfy.settings.json');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const existingSettings: ComfySettingsData = fs.existsSync(settingsPath)
? JSON.parse(fs.readFileSync(settingsPath, 'utf8'))
: {};

const settings = {
...DEFAULT_SETTINGS,
...existingSettings,
public async initializeSettings() {
// Load any existing settings if they exist
const existingSettings = await ComfySettings.load(this.basePath);

// Add install options to settings
const settings: Partial<ComfySettingsData> = {
'Comfy-Desktop.AutoUpdate': this.installOptions.autoUpdate,
'Comfy-Desktop.SendStatistics': this.installOptions.allowMetrics,
'Comfy-Desktop.UV.PythonInstallMirror': this.installOptions.pythonMirror,
Expand All @@ -74,9 +70,12 @@ export class InstallWizard implements HasTelemetry {
settings['Comfy.Server.LaunchArgs']['cpu'] = '';
}

const settingsJson = JSON.stringify(settings, null, 2);
fs.writeFileSync(settingsPath, settingsJson);
log.info(`Wrote settings to ${settingsPath}: ${settingsJson}`);
for (const [key, value] of Object.entries(settings)) {
existingSettings.set(key, value);
}

await existingSettings.saveSettings();
log.info(`Wrote install options to comfy settings file.`);
}

/**
Expand Down
6 changes: 1 addition & 5 deletions src/install/installationManager.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { Notification, app, dialog, ipcMain, shell } from 'electron';
import log from 'electron-log/main';

import { ComfySettings } from '@/config/comfySettings';

import { IPC_CHANNELS, ProgressStatus } from '../constants';
import type { AppWindow } from '../main-process/appWindow';
import { ComfyInstallation } from '../main-process/comfyInstallation';
Expand Down Expand Up @@ -238,9 +236,7 @@ export class InstallationManager implements HasTelemetry {
useDesktopConfig().set('migrateCustomNodesFrom', installWizard.migrationSource);
}

const comfySettings = new ComfySettings(installWizard.basePath);
await comfySettings.loadSettings();
const installation = new ComfyInstallation('started', installWizard.basePath, this.telemetry, comfySettings);
const installation = new ComfyInstallation('started', installWizard.basePath, this.telemetry);
InstallationManager.setReinstallHandler(installation);
const { virtualEnvironment } = installation;

Expand Down
10 changes: 4 additions & 6 deletions src/main-process/comfyDesktopApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import todesktop from '@todesktop/runtime';
import { app, ipcMain } from 'electron';
import log from 'electron-log/main';

import { useComfySettings } from '@/config/comfySettings';

import { DEFAULT_SERVER_ARGS, IPC_CHANNELS, ProgressStatus, ServerArgs } from '../constants';
import { DownloadManager } from '../models/DownloadManager';
import { HasTelemetry, ITelemetry } from '../services/telemetry';
Expand All @@ -22,10 +24,6 @@ export class ComfyDesktopApp implements HasTelemetry {
readonly telemetry: ITelemetry
) {}

get comfySettings() {
return this.installation.comfySettings;
}

get basePath() {
return this.installation.basePath;
}
Expand All @@ -47,7 +45,7 @@ export class ComfyDesktopApp implements HasTelemetry {
const serverArgs: ServerArgs = {
listen: DEFAULT_SERVER_ARGS.listen,
port: DEFAULT_SERVER_ARGS.port,
...this.comfySettings.get('Comfy.Server.LaunchArgs'),
...useComfySettings().get('Comfy.Server.LaunchArgs'),
};

if (COMFY_HOST) serverArgs.listen = COMFY_HOST;
Expand All @@ -69,7 +67,7 @@ export class ComfyDesktopApp implements HasTelemetry {
autoCheckInterval: 60 * 60 * 1000, // every hour
customLogger: log,
updateReadyAction: { showInstallAndRestartPrompt: 'always', showNotification: 'always' },
autoUpdater: this.comfySettings.get('Comfy-Desktop.AutoUpdate'),
autoUpdater: useComfySettings().get('Comfy-Desktop.AutoUpdate'),
});
todesktop.autoUpdater?.setFeedURL('https://updater.comfy.org');
}
Expand Down
21 changes: 10 additions & 11 deletions src/main-process/comfyInstallation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import log from 'electron-log/main';
import { rm } from 'node:fs/promises';

import { ComfyServerConfig } from '../config/comfyServerConfig';
import { ComfySettings } from '../config/comfySettings';
import { ComfySettings, useComfySettings } from '../config/comfySettings';
import type { DesktopInstallState } from '../main_types';
import type { InstallValidation } from '../preload';
import { type ITelemetry, getTelemetry } from '../services/telemetry';
Expand Down Expand Up @@ -52,8 +52,7 @@ export class ComfyInstallation {
/** The base path of the desktop app. Models, nodes, and configuration are saved here by default. */
basePath: string,
/** The device type to use for the installation. */
public readonly telemetry: ITelemetry,
public comfySettings: ComfySettings
public readonly telemetry: ITelemetry
) {
this._basePath = basePath;
this._virtualEnvironment = this.createVirtualEnvironment(basePath);
Expand All @@ -63,9 +62,9 @@ export class ComfyInstallation {
return new VirtualEnvironment(basePath, {
telemetry: this.telemetry,
selectedDevice: useDesktopConfig().get('selectedDevice'),
pythonMirror: this.comfySettings.get('Comfy-Desktop.UV.PythonInstallMirror'),
pypiMirror: this.comfySettings.get('Comfy-Desktop.UV.PypiInstallMirror'),
torchMirror: this.comfySettings.get('Comfy-Desktop.UV.TorchInstallMirror'),
pythonMirror: useComfySettings().get('Comfy-Desktop.UV.PythonInstallMirror'),
pypiMirror: useComfySettings().get('Comfy-Desktop.UV.PypiInstallMirror'),
torchMirror: useComfySettings().get('Comfy-Desktop.UV.TorchInstallMirror'),
});
}

Expand All @@ -79,9 +78,8 @@ export class ComfyInstallation {
const state = config.get('installState');
const basePath = config.get('basePath');
if (state && basePath) {
const comfySettings = new ComfySettings(basePath);
await comfySettings.loadSettings();
return new ComfyInstallation(state, basePath, getTelemetry(), comfySettings);
await ComfySettings.load(basePath);
return new ComfyInstallation(state, basePath, getTelemetry());
}
}

Expand Down Expand Up @@ -215,9 +213,10 @@ export class ComfyInstallation {

this._basePath = basePath;
this._virtualEnvironment = this.createVirtualEnvironment(basePath);
this.comfySettings = new ComfySettings(basePath);
await this.comfySettings.loadSettings();
useDesktopConfig().set('basePath', basePath);

// If settings file exists at new location, load it
await ComfySettings.load(basePath);
}

/**
Expand Down
14 changes: 5 additions & 9 deletions src/services/telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import os from 'node:os';
import path from 'node:path';
import si from 'systeminformation';

import type { IComfySettings } from '@/config/comfySettings';
import { useComfySettings } from '@/config/comfySettings';

import { IPC_CHANNELS } from '../constants';
import { AppWindow } from '../main-process/appWindow';
Expand Down Expand Up @@ -241,12 +241,8 @@ export function trackEvent<T extends HasTelemetry>(eventName: string) {
}

/** @returns Whether the user has consented to sending metrics. */
export async function promptMetricsConsent(
store: DesktopConfig,
appWindow: AppWindow,
comfySettings: IComfySettings
): Promise<boolean> {
const consent = comfySettings.get('Comfy-Desktop.SendStatistics') ?? false;
export async function promptMetricsConsent(store: DesktopConfig, appWindow: AppWindow): Promise<boolean> {
const consent = useComfySettings().get('Comfy-Desktop.SendStatistics') ?? false;
const consentedOn = store.get('versionConsentedMetrics');
const isOutdated = !consentedOn || compareVersions(consentedOn, '0.4.12') < 0;
if (!isOutdated) return consent;
Expand All @@ -260,8 +256,8 @@ export async function promptMetricsConsent(
await appWindow.loadPage('metrics-consent');
const newConsent = await consentPromise;
if (newConsent !== consent) {
comfySettings.set('Comfy-Desktop.SendStatistics', newConsent);
await comfySettings.saveSettings();
useComfySettings().set('Comfy-Desktop.SendStatistics', newConsent);
await useComfySettings().saveSettings();
}

return newConsent;
Expand Down
Loading

0 comments on commit 01697a0

Please sign in to comment.