Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 7 additions & 11 deletions web/packages/teleterm/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ if (app.requestSingleInstanceLock()) {
app.exit(1);
}

async function initializeApp(): Promise<void> {
function initializeApp(): void {
updateSessionDataPath();
let devRelaunchScheduled = false;
const settings = getRuntimeSettings();
Expand All @@ -52,7 +52,7 @@ async function initializeApp(): Promise<void> {
appStateFileStorage,
configFileStorage,
configJsonSchemaFileStorage,
} = await createFileStorages(settings.userDataDir);
} = createFileStorages(settings.userDataDir);

runConfigFileMigration(configFileStorage);
const configService = createConfigService({
Expand Down Expand Up @@ -238,23 +238,19 @@ function initMainLogger(settings: types.RuntimeSettings) {
}

function createFileStorages(userDataDir: string) {
return Promise.all([
createFileStorage({
return {
appStateFileStorage: createFileStorage({
filePath: path.join(userDataDir, 'app_state.json'),
debounceWrites: true,
}),
createFileStorage({
configFileStorage: createFileStorage({
filePath: path.join(userDataDir, 'app_config.json'),
debounceWrites: false,
discardUpdatesOnLoadError: true,
}),
createFileStorage({
configJsonSchemaFileStorage: createFileStorage({
filePath: path.join(userDataDir, 'schema_app_config.json'),
debounceWrites: false,
}),
]).then(storages => ({
appStateFileStorage: storages[0],
configFileStorage: storages[1],
configJsonSchemaFileStorage: storages[2],
}));
};
}
38 changes: 27 additions & 11 deletions web/packages/teleterm/src/services/fileStorage/fileStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
* limitations under the License.
*/

import fs from 'fs/promises';
// Both versions are imported because some operations need to be sync.
import fsAsync from 'node:fs/promises';
import fs from 'node:fs';

import { debounce } from 'shared/utils/highbar';

Expand Down Expand Up @@ -44,12 +46,25 @@ export interface FileStorage {
getFileLoadingError(): Error | undefined;
}

export async function createFileStorage(opts: {
/**
* createFileStorage reads and parses existing JSON structure from filePath or creates a new file
* under filePath with an empty object if the file is missing.
*
* createFileStorage itself uses blocking filesystem APIs but the functions of the returned
* FileStorage interface, such as write and replace, are async.
*/
// createFileStorage needs to be kept sync so that initialization of the app in main.ts can be sync.
// createFileStorage is called only during initialization, so blocking the main process during that
// time is acceptable.
//
// However, functions such as write or replace returned by createFileStorage need to be async as
// those are called after initialization.
export function createFileStorage(opts: {
filePath: string;
debounceWrites: boolean;
/** Prevents state updates when the file has not been loaded correctly, so its content will not be overwritten. */
discardUpdatesOnLoadError?: boolean;
}): Promise<FileStorage> {
}): FileStorage {
if (!opts || !opts.filePath) {
throw Error('missing filePath');
}
Expand All @@ -58,7 +73,7 @@ export async function createFileStorage(opts: {

let state: any, error: Error | undefined;
try {
state = await loadState(filePath);
state = loadStateSync(filePath);
} catch (e) {
state = {};
error = e;
Expand Down Expand Up @@ -121,18 +136,19 @@ export async function createFileStorage(opts: {
};
}

async function loadState(filePath: string): Promise<any> {
const file = await readOrCreateFile(filePath);
function loadStateSync(filePath: string): any {
const file = readOrCreateFileSync(filePath);
return JSON.parse(file);
}

async function readOrCreateFile(filePath: string): Promise<string> {
const defaultValue = '{}' as const;

function readOrCreateFileSync(filePath: string): string {
try {
return await fs.readFile(filePath, { encoding: 'utf-8' });
return fs.readFileSync(filePath, { encoding: 'utf-8' });
} catch (error) {
const defaultValue = '{}';
if (error?.code === 'ENOENT') {
await fs.writeFile(filePath, defaultValue);
fs.writeFileSync(filePath, defaultValue);
return defaultValue;
}
throw error;
Expand All @@ -149,6 +165,6 @@ const writeFileDebounced = debounce(
);

const writeFile = (filePath: string, text: string) =>
fs.writeFile(filePath, text).catch(error => {
fsAsync.writeFile(filePath, text).catch(error => {
logger.error(`Cannot update ${filePath} file`, error);
});