Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Settings store #362

Merged
merged 10 commits into from
Dec 3, 2024
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
Loading