synchronize config saves between windows

This commit is contained in:
Eugene Pankov
2022-05-28 12:35:32 +02:00
parent 296188c45e
commit 8c4c07c39b
6 changed files with 96 additions and 80 deletions

View File

@@ -6,7 +6,7 @@ import * as path from 'path'
import * as fs from 'fs' import * as fs from 'fs'
import { Subject, throttleTime } from 'rxjs' import { Subject, throttleTime } from 'rxjs'
import { loadConfig } from './config' import { saveConfig } from './config'
import { Window, WindowOptions } from './window' import { Window, WindowOptions } from './window'
import { pluginManager } from './pluginManager' import { pluginManager } from './pluginManager'
import { PTYManager } from './pty' import { PTYManager } from './pty'
@@ -23,10 +23,10 @@ export class Application {
private windows: Window[] = [] private windows: Window[] = []
private globalHotkey$ = new Subject<void>() private globalHotkey$ = new Subject<void>()
private quitRequested = false private quitRequested = false
private configStore: any
userPluginsPath: string userPluginsPath: string
constructor () { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
constructor (private configStore: any) {
remote.initialize() remote.initialize()
this.useBuiltinGraphics() this.useBuiltinGraphics()
this.ptyManager.init(this) this.ptyManager.init(this)
@@ -36,6 +36,10 @@ export class Application {
this.configStore = config this.configStore = config
}) })
ipcMain.on('app:save-config', (_event, data) => {
saveConfig(data)
})
ipcMain.on('app:register-global-hotkey', (_event, specs) => { ipcMain.on('app:register-global-hotkey', (_event, specs) => {
globalShortcut.unregisterAll() globalShortcut.unregisterAll()
for (const spec of specs) { for (const spec of specs) {
@@ -63,7 +67,6 @@ export class Application {
} }
}) })
this.configStore = loadConfig()
if (process.platform === 'linux') { if (process.platform === 'linux') {
app.commandLine.appendSwitch('no-sandbox') app.commandLine.appendSwitch('no-sandbox')
if (((this.configStore.appearance || {}).opacity || 1) !== 1) { if (((this.configStore.appearance || {}).opacity || 1) !== 1) {
@@ -111,7 +114,7 @@ export class Application {
} }
async newWindow (options?: WindowOptions): Promise<Window> { async newWindow (options?: WindowOptions): Promise<Window> {
const window = new Window(this, options) const window = new Window(this, this.configStore, options)
this.windows.push(window) this.windows.push(window)
if (this.windows.length === 1){ if (this.windows.length === 1){
window.makeMain() window.makeMain()

View File

@@ -1,26 +1,47 @@
import * as fs from 'fs' import * as fs from 'mz/fs'
import * as path from 'path' import * as path from 'path'
import * as yaml from 'js-yaml' import * as yaml from 'js-yaml'
import { v4 as uuidv4 } from 'uuid'
import * as gracefulFS from 'graceful-fs'
import { app } from 'electron' import { app } from 'electron'
import { promisify } from 'util'
export function migrateConfig (): void { export async function migrateConfig (): Promise<void> {
const configPath = path.join(app.getPath('userData'), 'config.yaml') const configPath = path.join(app.getPath('userData'), 'config.yaml')
const legacyConfigPath = path.join(app.getPath('userData'), '../terminus', 'config.yaml') const legacyConfigPath = path.join(app.getPath('userData'), '../terminus', 'config.yaml')
if (fs.existsSync(legacyConfigPath) && ( if (await fs.exists(legacyConfigPath) && (
!fs.existsSync(configPath) || !await fs.exists(configPath) ||
fs.statSync(configPath).mtime < fs.statSync(legacyConfigPath).mtime (await fs.stat(configPath)).mtime < (await fs.stat(legacyConfigPath)).mtime
)) { )) {
fs.writeFileSync(configPath, fs.readFileSync(legacyConfigPath)) await fs.writeFile(configPath, await fs.readFile(legacyConfigPath))
} }
} }
export function loadConfig (): any { export async function loadConfig (): Promise<any> {
migrateConfig() await migrateConfig()
const configPath = path.join(app.getPath('userData'), 'config.yaml') const configPath = path.join(app.getPath('userData'), 'config.yaml')
if (fs.existsSync(configPath)) { if (await fs.exists(configPath)) {
return yaml.load(fs.readFileSync(configPath, 'utf8')) return yaml.load(await fs.readFile(configPath, 'utf8'))
} else { } else {
return {} return {}
} }
} }
const configPath = path.join(app.getPath('userData'), 'config.yaml')
let _configSaveInProgress = Promise.resolve()
async function _saveConfigInternal (content: string): Promise<void> {
const tempPath = configPath + '.new.' + uuidv4().toString()
await fs.writeFile(tempPath, content, 'utf8')
await fs.writeFile(configPath + '.backup', content, 'utf8')
await promisify(gracefulFS.rename)(tempPath, configPath)
}
export async function saveConfig (content: string): Promise<void> {
try {
await _configSaveInProgress
} catch { }
_configSaveInProgress = _saveConfigInternal(content)
await _configSaveInProgress
}

View File

@@ -3,16 +3,20 @@ import './portable'
import 'source-map-support/register' import 'source-map-support/register'
import './sentry' import './sentry'
import './lru' import './lru'
import { app, ipcMain, Menu } from 'electron' import { app, ipcMain, Menu, dialog } from 'electron'
import { parseArgs } from './cli' import { parseArgs } from './cli'
import { Application } from './app' import { Application } from './app'
import electronDebug = require('electron-debug') import electronDebug = require('electron-debug')
import { loadConfig } from './config'
if (!process.env.TABBY_PLUGINS) { if (!process.env.TABBY_PLUGINS) {
process.env.TABBY_PLUGINS = '' process.env.TABBY_PLUGINS = ''
} }
const application = new Application() const argv = parseArgs(process.argv, process.cwd())
loadConfig().then(configStore => {
const application = new Application(configStore)
ipcMain.on('app:new-window', () => { ipcMain.on('app:new-window', () => {
application.newWindow() application.newWindow()
@@ -31,25 +35,10 @@ process.on('uncaughtException' as any, err => {
application.broadcast('uncaughtException', err) application.broadcast('uncaughtException', err)
}) })
app.on('second-instance', (_event, argv, cwd) => { app.on('second-instance', (_event, newArgv, cwd) => {
application.handleSecondInstance(argv, cwd) application.handleSecondInstance(newArgv, cwd)
}) })
const argv = parseArgs(process.argv, process.cwd())
if (!app.requestSingleInstanceLock()) {
app.quit()
app.exit(0)
}
if (argv.d) {
electronDebug({
isEnabled: true,
showDevTools: true,
devToolsMode: 'undocked',
})
}
app.on('ready', async () => { app.on('ready', async () => {
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
app.dock.setMenu(Menu.buildFromTemplate([ app.dock.setMenu(Menu.buildFromTemplate([
@@ -67,3 +56,19 @@ app.on('ready', async () => {
await window.ready await window.ready
window.passCliArguments(process.argv, process.cwd(), false) window.passCliArguments(process.argv, process.cwd(), false)
}) })
}).catch(err => {
dialog.showErrorBox('Could not read config', err.message)
})
if (!app.requestSingleInstanceLock()) {
app.quit()
app.exit(0)
}
if (argv.d) {
electronDebug({
isEnabled: true,
showDevTools: true,
devToolsMode: 'undocked',
})
}

View File

@@ -11,7 +11,6 @@ import { compare as compareVersions } from 'compare-versions'
import type { Application } from './app' import type { Application } from './app'
import { parseArgs } from './cli' import { parseArgs } from './cli'
import { loadConfig } from './config'
let DwmEnableBlurBehindWindow: any = null let DwmEnableBlurBehindWindow: any = null
if (process.platform === 'win32') { if (process.platform === 'win32') {
@@ -42,7 +41,6 @@ export class Window {
private closing = false private closing = false
private lastVibrancy: { enabled: boolean, type?: string } | null = null private lastVibrancy: { enabled: boolean, type?: string } | null = null
private disableVibrancyWhileDragging = false private disableVibrancyWhileDragging = false
private configStore: any
private touchBarControl: any private touchBarControl: any
private isFluentVibrancy = false private isFluentVibrancy = false
private dockHidden = false private dockHidden = false
@@ -50,9 +48,8 @@ export class Window {
get visible$ (): Observable<boolean> { return this.visible } get visible$ (): Observable<boolean> { return this.visible }
get closed$ (): Observable<void> { return this.closed } get closed$ (): Observable<void> { return this.closed }
constructor (private application: Application, options?: WindowOptions) { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
this.configStore = loadConfig() constructor (private application: Application, private configStore: any, options?: WindowOptions) {
options = options ?? {} options = options ?? {}
this.windowConfig = new ElectronConfig({ name: 'window' }) this.windowConfig = new ElectronConfig({ name: 'window' })

View File

@@ -65,6 +65,10 @@ export class ElectronHostAppService extends HostAppService {
this.electron.ipcRenderer.send('app:config-change', configStore) this.electron.ipcRenderer.send('app:config-change', configStore)
} }
saveConfig (data: string): void {
this.electron.ipcRenderer.send('app:save-config', data)
}
emitReady (): void { emitReady (): void {
this.electron.ipcRenderer.send('app:ready') this.electron.ipcRenderer.send('app:ready')
} }

View File

@@ -1,17 +1,15 @@
import * as path from 'path' import * as path from 'path'
import * as fs from 'fs/promises' import * as fs from 'fs/promises'
import * as gracefulFS from 'graceful-fs'
import * as fsSync from 'fs' import * as fsSync from 'fs'
import * as os from 'os' import * as os from 'os'
import { v4 as uuidv4 } from 'uuid'
import { promisify } from 'util'
import promiseIpc, { RendererProcessType } from 'electron-promise-ipc' import promiseIpc, { RendererProcessType } from 'electron-promise-ipc'
import { execFile } from 'mz/child_process' import { execFile } from 'mz/child_process'
import { Injectable, NgZone } from '@angular/core' import { Injectable, NgZone } from '@angular/core'
import { PlatformService, ClipboardContent, HostAppService, Platform, MenuItemOptions, MessageBoxOptions, MessageBoxResult, FileUpload, FileDownload, FileUploadOptions, wrapPromise, TranslateService } from 'tabby-core' import { PlatformService, ClipboardContent, Platform, MenuItemOptions, MessageBoxOptions, MessageBoxResult, FileUpload, FileDownload, FileUploadOptions, wrapPromise, TranslateService } from 'tabby-core'
import { ElectronService } from '../services/electron.service' import { ElectronService } from '../services/electron.service'
import { ElectronHostWindow } from './hostWindow.service' import { ElectronHostWindow } from './hostWindow.service'
import { ShellIntegrationService } from './shellIntegration.service' import { ShellIntegrationService } from './shellIntegration.service'
import { ElectronHostAppService } from './hostApp.service'
const fontManager = require('fontmanager-redux') // eslint-disable-line const fontManager = require('fontmanager-redux') // eslint-disable-line
/* eslint-disable block-scoped-var */ /* eslint-disable block-scoped-var */
@@ -27,10 +25,9 @@ try {
export class ElectronPlatformService extends PlatformService { export class ElectronPlatformService extends PlatformService {
supportsWindowControls = true supportsWindowControls = true
private configPath: string private configPath: string
private _configSaveInProgress = Promise.resolve()
constructor ( constructor (
private hostApp: HostAppService, private hostApp: ElectronHostAppService,
private hostWindow: ElectronHostWindow, private hostWindow: ElectronHostWindow,
private electron: ElectronService, private electron: ElectronService,
private zone: NgZone, private zone: NgZone,
@@ -112,18 +109,7 @@ export class ElectronPlatformService extends PlatformService {
} }
async saveConfig (content: string): Promise<void> { async saveConfig (content: string): Promise<void> {
try { this.hostApp.saveConfig(content)
await this._configSaveInProgress
} catch { }
this._configSaveInProgress = this._saveConfigInternal(content)
await this._configSaveInProgress
}
async _saveConfigInternal (content: string): Promise<void> {
const tempPath = this.configPath + '.new.' + uuidv4().toString()
await fs.writeFile(tempPath, content, 'utf8')
await fs.writeFile(this.configPath + '.backup', content, 'utf8')
await promisify(gracefulFS.rename)(tempPath, this.configPath)
} }
getConfigPath (): string|null { getConfigPath (): string|null {