Skip to content

Commit

Permalink
WIP (remove this commit!): multi notes support
Browse files Browse the repository at this point in the history
  • Loading branch information
heyman committed Jul 21, 2024
1 parent 95c2097 commit df3ee0c
Show file tree
Hide file tree
Showing 20 changed files with 976 additions and 234 deletions.
48 changes: 27 additions & 21 deletions electron/main/buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ const untildify = (pathWithTilde) => {
: pathWithTilde;
}

export function constructBufferFilePath(directoryPath) {
return join(untildify(directoryPath), isDev ? "buffer-dev.txt" : "buffer.txt")
export function constructBufferFilePath(directoryPath, path) {
return join(untildify(directoryPath), path)
}

export function getBufferFilePath() {
export function getFullBufferFilePath(path) {
let defaultPath = app.getPath("userData")
let configPath = CONFIG.get("settings.bufferPath")
let bufferPath = configPath.length ? configPath : defaultPath
let bufferFilePath = constructBufferFilePath(bufferPath)
let bufferFilePath = constructBufferFilePath(bufferPath, path)
try {
// use realpathSync to resolve a potential symlink
return fs.realpathSync(bufferFilePath)
Expand Down Expand Up @@ -103,39 +103,45 @@ export class Buffer {


// Buffer
let buffer
export function loadBuffer() {
if (buffer) {
buffer.close()
let buffers = {}
export function loadBuffer(path) {
if (buffers[path]) {
buffers[path].close()
}
buffer = new Buffer({
filePath: getBufferFilePath(),
buffers[path] = new Buffer({
filePath: getFullBufferFilePath(path),
onChange: (content) => {
win?.webContents.send("buffer-content:change", content)
console.log("Old buffer.js onChange")
win?.webContents.send("buffer-content:change", path, content)
},
})
return buffer
return buffers[path]
}

ipcMain.handle('buffer-content:load', async () => {
if (buffer.exists() && !(eraseInitialContent && isDev)) {
return await buffer.load()
ipcMain.handle('buffer-content:load', async (event, path) => {
if (!buffers[path]) {
loadBuffer(path)
}
if (buffers[path].exists() && !(eraseInitialContent && isDev)) {
return await buffers[path].load()
} else {
return isDev ? initialDevContent : initialContent
}
});

async function save(content) {
return await buffer.save(content)
async function save(path, content) {
return await buffers[path].save(content)
}

ipcMain.handle('buffer-content:save', async (event, content) => {
return await save(content)
ipcMain.handle('buffer-content:save', async (event, path, content) => {
return await save(path, content)
});

export let contentSaved = false
ipcMain.handle('buffer-content:saveAndQuit', async (event, content) => {
await save(content)
ipcMain.handle('buffer-content:saveAndQuit', async (event, contents) => {
for (const [path, content] of contents) {
await save(path, content)
}
contentSaved = true
app.quit()
})
Expand Down
166 changes: 166 additions & 0 deletions electron/main/file-library.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import fs from "fs"
import os from "node:os"
import { join, dirname, basename } from "path"

import * as jetpack from "fs-jetpack";
import { app, ipcMain, dialog } from "electron"


const untildify = (pathWithTilde) => {
const homeDir = os.homedir()
return homeDir ? pathWithTilde.replace(/^~(?=$|\/|\\)/, homeDir) : pathWithTilde
}


export class FileLibrary {
constructor(basePath) {
this.basePath = fs.realpathSync(untildify(basePath))
this.jetpack = jetpack.cwd(this.basePath)
this.files = {};
this.watcher = null;
this.contentSaved = false
this.onChangeCallback = null

if (jetpack.exists(this.basePath) !== "dir") {
throw new Error(`Invalid base path: ${this.basePath}`)
}

this.setupWatcher()
}

async exists(path) {
return this.jetpack.exists(path) === "file"
}

async load(path) {
if (this.files[path]) {
return this.files[path].read()
}
const fullPath = fs.realpathSync(join(this.basePath, path))
this.files[path] = new Buffer({fullPath, library:this})
return await this.files[path].read()
}

async save(path, content) {
if (!this.files[path]) {
throw new Error(`File not loaded: ${path}`)
}
return await this.files[path].save(content)
}

async listFiles() {
return await this.jetpack.findAsync(this.basePath, {
matching: "*.txt",
recursive: true,
})
}

setupWatcher() {
if (!this.watcher) {
this.watcher = fs.watch(
this.basePath,
{
persistent: true,
recursive: true,
encoding: "utf8",
},
async (eventType, changedPath) => {
console.log("File changed", eventType, changedPath)
for (const [path, buffer] of Object.entries(this.files)) {
if (changedPath === basename(path)) {
const content = await buffer.read()
if (buffer._lastSavedContent !== content) {
this.onChangeCallback(path, content)
}
}
}
}
)
}
}

closeFile(path) {
if (this.files[path]) {
delete this.files[path]
}
}

close() {
for (const buffer of Object.values(this.files)) {
this.closeFile(buffer.filePath)
}
this.stopWatcher()
}

stopWatcher() {
if (this.watcher) {
this.watcher.close()
this.watcher = null
}
}
}



export class Buffer {
constructor({fullPath, library}) {
this.fullPath = fullPath
this._lastSavedContent = null
this.library = library
}

async read() {
return await this.library.jetpack.read(this.fullPath, 'utf8')
}

async save(content) {
this._lastSavedContent = content
const saveResult = await this.library.jetpack.write(this.fullPath, content, {
atomic: true,
mode: '600',
})
return saveResult
}

exists() {
return jetpack.exists(this.fullPath) === "file"
}
}


export function setupFileLibraryEventHandlers(library, win) {
ipcMain.handle('buffer:load', async (event, path) => {
console.log("buffer:load", path)
return await library.load(path)
});


ipcMain.handle('buffer:save', async (event, path, content) => {
return await library.save(path, content)
});

ipcMain.handle('buffer:listFiles', async (event) => {
return await library.listFiles()
});

ipcMain.handle('buffer:exists', async (event, path) => {
return await library.exists(path)
});

ipcMain.handle('buffer:close', async (event, path) => {
return await library.closeFile(path)
});

ipcMain.handle('buffer:saveAndQuit', async (event, contents) => {
library.stopWatcher()
for (const [path, content] of contents) {
await library.save(path, content)
}
library.contentSaved = true
app.quit()
})

library.onChangeCallback = (path, content) => {
win.webContents.send("buffer:change", path, content)
}
}
21 changes: 18 additions & 3 deletions electron/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { isDev, isLinux, isMac, isWindows } from '../detect-platform';
import { initializeAutoUpdate, checkForUpdates } from './auto-update';
import { fixElectronCors } from './cors';
import { loadBuffer, contentSaved } from './buffer';
import { FileLibrary, setupFileLibraryEventHandlers } from './file-library';


// The built directory structure
Expand Down Expand Up @@ -49,7 +50,9 @@ Menu.setApplicationMenu(menu)
// process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'

export let win: BrowserWindow | null = null
let fileLibrary: FileLibrary | null = null
let tray: Tray | null = null;
let initErrors: string[] = []
// Here, you can also use other preload
const preload = join(__dirname, '../preload/index.js')
const url = process.env.VITE_DEV_SERVER_URL
Expand Down Expand Up @@ -138,7 +141,7 @@ async function createWindow() {
}
// Prevent the window from closing, and send a message to the renderer which will in turn
// send a message to the main process to save the current buffer and close the window.
if (!contentSaved) {
if (!fileLibrary.contentSaved) {
event.preventDefault()
win?.webContents.send(WINDOW_CLOSE_EVENT)
} else {
Expand Down Expand Up @@ -302,6 +305,7 @@ function registerAlwaysOnTop() {
}

app.whenReady().then(createWindow).then(async () => {
setupFileLibraryEventHandlers(fileLibrary, win)
initializeAutoUpdate(win)
registerGlobalHotkey()
registerShowInDock()
Expand Down Expand Up @@ -343,8 +347,19 @@ ipcMain.handle('dark-mode:set', (event, mode) => {

ipcMain.handle('dark-mode:get', () => nativeTheme.themeSource)

// load buffer on app start
loadBuffer()
// Initialize note/file library
const customLibraryPath = CONFIG.get("settings.bufferPath")
const libraryPath = join(app.getPath("userData"), "notes")
console.log("libraryPath", libraryPath)
fileLibrary = new FileLibrary(libraryPath)
fileLibrary.listFiles().then((files) => {
console.log("files", files)
})
initErrors.push("Could not load file library")

ipcMain.handle("getInitErrors", () => {
return initErrors
})


ipcMain.handle('settings:set', async (event, settings) => {
Expand Down
Loading

0 comments on commit df3ee0c

Please sign in to comment.