Skip to content

Commit

Permalink
Settings store (#362)
Browse files Browse the repository at this point in the history
* Rename window settings: AppWindowSettings

* Add settings store, required for startup

If present, the config must be valid to proceed with app startup.

* Add shell open option to debug

* Add initial DesktopSettings type

* Use app settings store for install state

* Add setting store reset to resetInstall script

* Add file validation to pre-window checks

Provides the user with some information about what went wrong and why - and where they can look to fix it.

Needs help / support button added.

* [Refactor] Desktop config store back to class

* Add better handling for showing file on exit

* Add documentation

---------

Co-authored-by: huchenlei <[email protected]>
  • Loading branch information
webfiltered and huchenlei authored Dec 3, 2024
1 parent bf9a3c0 commit 54f1a96
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 27 deletions.
54 changes: 41 additions & 13 deletions scripts/resetInstall.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ const readline = require('readline');

/**
* Get the path to the extra_models_config.yaml file based on the platform.
* @param {string} filename The name of the file to find in the user data folder
* @returns The path to the extra_models_config.yaml file.
*/

function getConfigPath() {
function getConfigPath(filename) {
switch (process.platform) {
case 'darwin': // macOS
return path.join(os.homedir(), 'Library', 'Application Support', 'ComfyUI', 'extra_models_config.yaml');
return path.join(os.homedir(), 'Library', 'Application Support', 'ComfyUI', filename);
case 'win32': // Windows
return path.join(process.env.APPDATA, 'ComfyUI', 'extra_models_config.yaml');
return path.join(process.env.APPDATA, 'ComfyUI', filename);
default:
console.log('Platform not supported for this operation');
process.exit(1);
Expand All @@ -37,27 +38,54 @@ async function askForConfirmation(question) {

async function main() {
try {
const configPath = getConfigPath();
const configPath = getConfigPath('config.json');
const windowStorePath = getConfigPath('window.json');
const modelsConfigPath = getConfigPath('extra_models_config.yaml');
let desktopBasePath = null;
let basePath = null;

// Read base_path before deleting the config file
// Read basePath from desktop config
if (fs.existsSync(configPath)) {
const configContent = fs.readFileSync(configPath, 'utf8');
const parsed = JSON.parse(configContent);
desktopBasePath = parsed?.basePath;
}

// Read base_path before deleting the config file
if (fs.existsSync(modelsConfigPath)) {
const configContent = fs.readFileSync(modelsConfigPath, 'utf8');
const config = yaml.parse(configContent);
basePath = config?.comfyui?.base_path;

// Delete config file
fs.unlinkSync(configPath);
console.log(`Successfully removed ${configPath}`);
} else {
console.log('Config file not found, nothing to remove');
}

// If base_path exists, ask user if they want to delete it
if (basePath && fs.existsSync(basePath)) {
console.log(`Found ComfyUI installation directory at: ${basePath}`);
// Delete all config files
for (const file of [configPath, windowStorePath, modelsConfigPath]) {
if (fs.existsSync(file)) {
fs.unlinkSync(file);
console.log(`Successfully removed ${file}`);
}
}

// If config.json basePath exists, ask user if they want to delete it
if (desktopBasePath && fs.existsSync(desktopBasePath)) {
console.log(`Found ComfyUI installation directory at: ${desktopBasePath}`);
const shouldDelete = await askForConfirmation('Would you like to delete this directory as well?');

if (shouldDelete) {
fs.rmSync(desktopBasePath, { recursive: true, force: true });
console.log(`Successfully removed ComfyUI directory at ${desktopBasePath}`);
} else {
console.log('Skipping ComfyUI directory deletion');
}
}

// If base_path exists and does not match basePath, ask user if they want to delete it
if (basePath && basePath !== desktopBasePath && fs.existsSync(basePath)) {
console.log(`Found ComfyUI models directory at: ${basePath}`);
const shouldDelete = await askForConfirmation('Would you like to delete this directory as well?');

if (shouldDelete) {
fs.rmSync(basePath, { recursive: true, force: true });
console.log(`Successfully removed ComfyUI directory at ${basePath}`);
Expand Down
58 changes: 58 additions & 0 deletions src/install/installationValidator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { app, dialog, shell } from 'electron';
import fs from 'fs/promises';
import log from 'electron-log/main';
import path from 'node:path';

export class InstallationValidator {
/**
* Shows a dialog box with an option to open the problematic file in the native shell file viewer.
* @param options The options paramter of {@link dialog.showMessageBox}, filled with defaults for invalid config
* @returns
*/
static async showInvalidFileAndQuit(file: string, options: Electron.MessageBoxOptions): Promise<void> {
const defaults: Electron.MessageBoxOptions = {
// Message must be set by caller.
message: `Was unable to read the file shown below. It could be missing, inaccessible, or corrupt.\n\n${file}`,
title: 'Invalid file',
type: 'error',
buttons: ['Locate the &file (then quit)', '&Quit'],
defaultId: 0,
cancelId: 1,
normalizeAccessKeys: true,
};
const opt = Object.assign(defaults, options);

const result = await dialog.showMessageBox(opt);

// Try show the file in file manager
if (result.response === 0) {
try {
const parsed = path.parse(file);
log.debug(`Attempting to open containing directory: ${parsed.dir}`);
await fs.access(file);
shell.showItemInFolder(file);
} catch (error) {
log.warn(`Could not access file whilst attempting to exit gracefully after a critical error.`, file);
try {
// Failed - try the parent dir
const parsed = path.parse(file);
await fs.access(parsed.dir);
shell.openPath(parsed.dir);
} catch (error) {
// Nothing works. Log, notify, quit.
log.error(
`Could not read directory containing file, whilst attempting to exit gracefully after a critical error.`
);
dialog.showErrorBox(
'Unable to fine file',
`Unable to find the file. Please navigate to it manually:\n\n${file}`
);
}
}
}

app.quit();
// Wait patiently for graceful termination.
await new Promise(() => {});
}
}
8 changes: 4 additions & 4 deletions src/main-process/appWindow.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { BrowserWindow, screen, app, shell, ipcMain, Tray, Menu, dialog, MenuItem } from 'electron';
import path from 'node:path';
import Store from 'electron-store';
import { StoreType } from '../store';
import { AppWindowSettings } from '../store';
import log from 'electron-log/main';
import { IPC_CHANNELS, ProgressStatus, ServerArgs } from '../constants';
import { getAppResourcesPath } from '../install/resourcePaths';
Expand All @@ -12,7 +12,7 @@ import { getAppResourcesPath } from '../install/resourcePaths';
*/
export class AppWindow {
private window: BrowserWindow;
private store: Store<StoreType>;
private store: Store<AppWindowSettings>;
private messageQueue: Array<{ channel: string; data: any }> = [];
private rendererReady: boolean = false;

Expand Down Expand Up @@ -142,10 +142,10 @@ export class AppWindow {
* There are edge cases where this might not be a catastrophic failure, but inability
* to write to our own datastore may result in unexpected user data loss.
*/
private loadWindowStore(): Store<StoreType> {
private loadWindowStore(): Store<AppWindowSettings> {
try {
// Separate file for non-critical convenience settings - just resets itself if invalid
return new Store<StoreType>({
return new Store<AppWindowSettings>({
clearInvalidConfig: true,
name: 'window',
});
Expand Down
54 changes: 46 additions & 8 deletions src/main-process/comfyDesktopApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ import { DownloadManager } from '../models/DownloadManager';
import { VirtualEnvironment } from '../virtualEnvironment';
import { InstallWizard } from '../install/installWizard';
import { Terminal } from '../terminal';
import { DesktopConfig } from '../store/desktopConfig';
import { InstallationValidator } from '../install/installationValidator';
import { restoreCustomNodes } from '../services/backup';
import Store from 'electron-store';

export class ComfyDesktopApp {
public comfyServer: ComfyServer | null = null;
private terminal: Terminal | null = null; // Only created after server starts.
Expand Down Expand Up @@ -144,7 +146,11 @@ export class ComfyDesktopApp {
return new Promise<string>((resolve) => {
ipcMain.on(IPC_CHANNELS.INSTALL_COMFYUI, async (event, installOptions: InstallOptions) => {
const installWizard = new InstallWizard(installOptions);
const { store } = DesktopConfig;
store.set('basePath', installWizard.basePath);

await installWizard.install();
store.set('installState', 'installed');
resolve(installWizard.basePath);
});
});
Expand Down Expand Up @@ -181,7 +187,7 @@ export class ComfyDesktopApp {
this.appWindow.send(IPC_CHANNELS.LOG_MESSAGE, data);
},
});
const store = new Store();
const { store } = DesktopConfig;
if (!store.get('Comfy-Desktop.RestoredCustomNodes', false)) {
try {
await restoreCustomNodes(virtualEnvironment, this.appWindow);
Expand All @@ -199,16 +205,48 @@ export class ComfyDesktopApp {
}

static async create(appWindow: AppWindow): Promise<ComfyDesktopApp> {
const basePath = ComfyServerConfig.exists()
? await ComfyServerConfig.readBasePathFromConfig(ComfyServerConfig.configPath)
: await this.install(appWindow);
const { store } = DesktopConfig;
// Migrate settings from old version if required
const installState = store.get('installState') ?? (await ComfyDesktopApp.migrateInstallState());

// Fresh install
const basePath =
installState === undefined ? await ComfyDesktopApp.install(appWindow) : await ComfyDesktopApp.loadBasePath();

if (!basePath) {
throw new Error(`Base path not found! ${ComfyServerConfig.configPath} is probably corrupted.`);
}
return new ComfyDesktopApp(basePath, new ComfySettings(basePath), appWindow);
}

/**
* Sets the ugpraded state if this is a version upgrade from <= 0.3.18
* @returns 'upgraded' if this install has just been upgraded, or undefined for a fresh install
*/
static async migrateInstallState(): Promise<string | undefined> {
// Fresh install
if (!ComfyServerConfig.exists()) return undefined;

// Upgrade
const basePath = await ComfyDesktopApp.loadBasePath();

// Migrate config
const { store } = DesktopConfig;
const upgraded = 'upgraded';
store.set('installState', upgraded);
store.set('basePath', basePath);
return upgraded;
}

/** Loads the base_path value from the YAML config. Quits in the event of failure. */
static async loadBasePath(): Promise<string> {
const basePath = await ComfyServerConfig.readBasePathFromConfig(ComfyServerConfig.configPath);
if (basePath) return basePath;

log.error(`Base path not found! ${ComfyServerConfig.configPath} is probably corrupted.`);
await InstallationValidator.showInvalidFileAndQuit(ComfyServerConfig.configPath, {
message: `Base path not found! This file is probably corrupt:\n\n${ComfyServerConfig.configPath}`,
});
throw new Error(/* Unreachable. */);
}

uninstall(): void {
fs.rmSync(ComfyServerConfig.configPath);
}
Expand Down
17 changes: 16 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { AppInfoHandlers } from './handlers/appInfoHandlers';
import { ComfyDesktopApp } from './main-process/comfyDesktopApp';
import { LevelOption } from 'electron-log';
import SentryLogging from './services/sentry';
import { DesktopConfig } from './store/desktopConfig';

dotenv.config();
log.initialize();
Expand Down Expand Up @@ -40,6 +41,17 @@ if (!gotTheLock) {
app.on('ready', async () => {
log.debug('App ready');

const store = await DesktopConfig.load();
if (store) {
startApp();
} else {
app.exit(20);
}
});
}

async function startApp() {
try {
const appWindow = new AppWindow();
appWindow.onClose(() => {
log.info('App window closed. Quitting application.');
Expand Down Expand Up @@ -79,5 +91,8 @@ if (!gotTheLock) {
appWindow.sendServerStartProgress(ProgressStatus.ERROR);
appWindow.send(IPC_CHANNELS.LOG_MESSAGE, error);
}
});
} catch (error) {
log.error('Fatal error occurred during app startup.', error);
app.exit(2024);
}
}
Loading

0 comments on commit 54f1a96

Please sign in to comment.