2023-12-23 11:17:13 +01:00
|
|
|
import fs from "fs"
|
2024-01-05 23:06:49 +01:00
|
|
|
import os from "node:os"
|
2024-01-01 19:04:40 +01:00
|
|
|
import { join, dirname, basename } from "path"
|
2024-01-05 00:41:46 +01:00
|
|
|
import { app, ipcMain, dialog } from "electron"
|
2024-01-01 19:04:40 +01:00
|
|
|
import * as jetpack from "fs-jetpack";
|
|
|
|
|
2023-12-23 11:17:13 +01:00
|
|
|
import CONFIG from "../config"
|
|
|
|
import { isDev } from "../detect-platform"
|
2024-01-05 00:41:46 +01:00
|
|
|
import { win } from "./index"
|
|
|
|
import { eraseInitialContent, initialContent, initialDevContent } from '../initial-content'
|
2023-12-23 11:17:13 +01:00
|
|
|
|
2024-01-05 23:06:49 +01:00
|
|
|
const untildify = (pathWithTilde) => {
|
|
|
|
const homeDirectory = os.homedir();
|
|
|
|
return homeDirectory
|
|
|
|
? pathWithTilde.replace(/^~(?=$|\/|\\)/, homeDirectory)
|
|
|
|
: pathWithTilde;
|
|
|
|
}
|
2023-12-23 11:17:13 +01:00
|
|
|
|
2024-01-05 00:41:46 +01:00
|
|
|
export function constructBufferFilePath(directoryPath) {
|
2024-01-05 23:06:49 +01:00
|
|
|
return join(untildify(directoryPath), isDev ? "buffer-dev.txt" : "buffer.txt")
|
2024-01-05 00:41:46 +01:00
|
|
|
}
|
|
|
|
|
2023-12-23 11:17:13 +01:00
|
|
|
export function getBufferFilePath() {
|
|
|
|
let defaultPath = app.getPath("userData")
|
|
|
|
let configPath = CONFIG.get("settings.bufferPath")
|
|
|
|
let bufferPath = configPath.length ? configPath : defaultPath
|
2024-01-05 00:41:46 +01:00
|
|
|
let bufferFilePath = constructBufferFilePath(bufferPath)
|
2023-12-23 11:17:13 +01:00
|
|
|
try {
|
|
|
|
// use realpathSync to resolve a potential symlink
|
|
|
|
return fs.realpathSync(bufferFilePath)
|
|
|
|
} catch (err) {
|
|
|
|
// realpathSync will fail if the file does not exist, but that doesn't matter since the file will be created
|
|
|
|
if (err.code !== "ENOENT") {
|
|
|
|
throw err
|
|
|
|
}
|
|
|
|
return bufferFilePath
|
|
|
|
}
|
|
|
|
}
|
2024-01-01 19:04:40 +01:00
|
|
|
|
|
|
|
|
|
|
|
export class Buffer {
|
|
|
|
constructor({filePath, onChange}) {
|
|
|
|
this.filePath = filePath
|
|
|
|
this.onChange = onChange
|
|
|
|
this.watcher = null
|
|
|
|
this.setupWatcher()
|
|
|
|
this._lastSavedContent = null
|
|
|
|
}
|
|
|
|
|
|
|
|
async load() {
|
|
|
|
const content = await jetpack.read(this.filePath, 'utf8')
|
|
|
|
this.setupWatcher()
|
|
|
|
return content
|
|
|
|
}
|
|
|
|
|
|
|
|
async save(content) {
|
|
|
|
this._lastSavedContent = content
|
|
|
|
const saveResult = await jetpack.write(this.filePath, content, {
|
|
|
|
atomic: true,
|
|
|
|
mode: '600',
|
|
|
|
})
|
|
|
|
return saveResult
|
|
|
|
}
|
|
|
|
|
|
|
|
exists() {
|
|
|
|
return jetpack.exists(this.filePath) === "file"
|
|
|
|
}
|
|
|
|
|
|
|
|
setupWatcher() {
|
|
|
|
if (!this.watcher && this.exists()) {
|
|
|
|
this.watcher = fs.watch(
|
|
|
|
dirname(this.filePath),
|
|
|
|
{
|
|
|
|
persistent: true,
|
|
|
|
recursive: false,
|
|
|
|
encoding: "utf8",
|
|
|
|
},
|
|
|
|
async (eventType, filename) => {
|
|
|
|
if (filename !== basename(this.filePath)) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// read the file content and compare it to the last saved content
|
|
|
|
// (if the content is the same, then we can ignore the event)
|
|
|
|
const content = await jetpack.read(this.filePath, 'utf8')
|
|
|
|
|
|
|
|
if (this._lastSavedContent !== content) {
|
|
|
|
// file has changed on disk, trigger onChange
|
2024-01-05 00:41:46 +01:00
|
|
|
this.onChange(content)
|
2024-01-01 19:04:40 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2024-01-05 00:41:46 +01:00
|
|
|
|
|
|
|
close() {
|
|
|
|
if (this.watcher) {
|
|
|
|
this.watcher.close()
|
|
|
|
this.watcher = null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Buffer
|
|
|
|
let buffer
|
|
|
|
export function loadBuffer() {
|
|
|
|
if (buffer) {
|
|
|
|
buffer.close()
|
|
|
|
}
|
|
|
|
buffer = new Buffer({
|
|
|
|
filePath: getBufferFilePath(),
|
|
|
|
onChange: (content) => {
|
|
|
|
win?.webContents.send("buffer-content:change", content)
|
|
|
|
},
|
|
|
|
})
|
|
|
|
return buffer
|
|
|
|
}
|
|
|
|
|
|
|
|
ipcMain.handle('buffer-content:load', async () => {
|
|
|
|
if (buffer.exists() && !(eraseInitialContent && isDev)) {
|
|
|
|
return await buffer.load()
|
|
|
|
} else {
|
|
|
|
return isDev ? initialDevContent : initialContent
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
async function save(content) {
|
|
|
|
return await buffer.save(content)
|
2024-01-01 19:04:40 +01:00
|
|
|
}
|
2024-01-05 00:41:46 +01:00
|
|
|
|
|
|
|
ipcMain.handle('buffer-content:save', async (event, content) => {
|
|
|
|
return await save(content)
|
|
|
|
});
|
|
|
|
|
|
|
|
export let contentSaved = false
|
|
|
|
ipcMain.handle('buffer-content:saveAndQuit', async (event, content) => {
|
|
|
|
await save(content)
|
|
|
|
contentSaved = true
|
|
|
|
app.quit()
|
|
|
|
})
|
|
|
|
|
|
|
|
ipcMain.handle("buffer-content:selectLocation", async () => {
|
|
|
|
let result = await dialog.showOpenDialog({
|
|
|
|
title: "Select directory to store buffer",
|
|
|
|
properties: [
|
|
|
|
"openDirectory",
|
|
|
|
"createDirectory",
|
|
|
|
"noResolveAliases",
|
|
|
|
],
|
|
|
|
})
|
|
|
|
if (result.canceled) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const filePath = result.filePaths[0]
|
|
|
|
if (fs.existsSync(constructBufferFilePath(filePath))) {
|
|
|
|
if (dialog.showMessageBoxSync({
|
|
|
|
type: "question",
|
|
|
|
message: "The selected directory already contains a buffer file. It will be loaded. Do you want to continue?",
|
|
|
|
buttons: ["Cancel", "Continue"],
|
|
|
|
}) === 0) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return filePath
|
|
|
|
})
|