Skip to content

Commit

Permalink
Support logging to files (#2)
Browse files Browse the repository at this point in the history
* Simpler validation that uses zod for loading json

* Fix all types to support strict mode

* Add test to check config migrations run correctly

* Pretty format json files

* Change main structure for easier testing of config changes

* Log to file and delete old logs

* Add dev tools for opening logs faster

* Finish up feature

* Delete log files if they are empty
  • Loading branch information
EddieAbbondanzio authored Sep 25, 2022
1 parent cd74f85 commit f5b91f3
Show file tree
Hide file tree
Showing 48 changed files with 981 additions and 502 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,7 @@ pnpm-debug.log*

# Default dev data dir
/data
config.json
config.json

# Default dev log dir
/logs
15 changes: 7 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"webpack-dev-server": "^4.7.4"
},
"dependencies": {
"chalk": "^4.1.2",
"dart-sass": "^1.25.0",
"date-fns": "^2.28.0",
"electron-squirrel-startup": "^1.0.0",
Expand All @@ -80,6 +81,7 @@
"sass": "^1.43.4",
"sass-loader": "^12.3.0",
"styled-components": "^5.3.3",
"tozod": "^3.0.0",
"unist-util-visit": "^2.0.3",
"zod": "^3.17.10"
}
Expand Down
91 changes: 32 additions & 59 deletions src/main/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,85 +3,54 @@ import {
dialog,
Menu,
MenuItemConstructorOptions,
shell,
} from "electron";
import { isRoleMenu, Menu as MenuType } from "../shared/ui/menu";
import { IpcChannel, IpcMainTS } from "../shared/ipc";
import { openInBrowser } from "./utils";
import { UIEventType, UIEventInput } from "../shared/ui/events";
import { AppState, DEFAULT_SIDEBAR_WIDTH, Section } from "../shared/ui/app";
import { parseJSON } from "date-fns";
import { z } from "zod";
import {
NoteSort,
DEFAULT_NOTE_SORTING_ALGORITHM,
} from "../shared/domain/note";
import { AppState, DEFAULT_SIDEBAR_WIDTH } from "../shared/ui/app";

import { JsonFile, loadJsonFile } from "./json";
import { Config } from "../shared/domain/config";
import { APP_STATE_MIGRATIONS } from "./migrations/appState";
import p from "path";
import { MissingDataDirectoryError } from "../shared/errors";
import { DATE_OR_STRING_SCHEMA } from "../shared/domain";
import { NoteSort } from "../shared/domain/note";
import { APP_STATE_SCHEMAS } from "./schemas/appState";
import { Logger } from "../shared/logger";

export const APP_STATE_PATH = "ui.json";

export const APP_STATE_SCHEMA = z
.object({
version: z.literal(1).optional().default(1),
sidebar: z
.object({
width: z
.string()
.regex(/^\d+px$/)
.optional()
.default(DEFAULT_SIDEBAR_WIDTH),
scroll: z.number().optional().default(0),
hidden: z.boolean().optional(),
selected: z.array(z.string()).optional(),
expanded: z.array(z.string()).optional(),
sort: z
.nativeEnum(NoteSort)
.optional()
.default(DEFAULT_NOTE_SORTING_ALGORITHM),
})
.optional()
.default({}),
editor: z
.object({
isEditing: z.boolean().optional().default(false),
scroll: z.number().optional().default(0),
tabs: z
.array(
z.object({
noteId: z.string(),
// Intentionally omitted noteContent
lastActive: DATE_OR_STRING_SCHEMA.optional(),
})
)
.default([]),
tabsScroll: z.number().optional().default(0),
activeTabNoteId: z.string().optional(),
})
.optional()
.default({}),
focused: z.array(z.nativeEnum(Section)).default([]),
})
.optional()
.default({
version: 1,
});

export function appIpcs(ipc: IpcMainTS, config: JsonFile<Config>): void {
export function appIpcs(
ipc: IpcMainTS,
config: JsonFile<Config>,
log: Logger
): void {
let appStateFile: JsonFile<AppState>;

ipc.on("init", async () => {
if (config.content.dataDirectory == null) {
throw new MissingDataDirectoryError();
}

appStateFile = await loadJsonFile(
appStateFile = await loadJsonFile<AppState>(
p.join(config.content.dataDirectory, APP_STATE_PATH),
APP_STATE_SCHEMA,
APP_STATE_MIGRATIONS
APP_STATE_SCHEMAS,
{
version: 1,
sidebar: {
scroll: 0,
sort: NoteSort.Alphanumeric,
width: DEFAULT_SIDEBAR_WIDTH,
},
editor: {
isEditing: false,
scroll: 0,
tabs: [],
tabsScroll: 0,
},
focused: [],
}
);
});

Expand Down Expand Up @@ -172,6 +141,10 @@ export function appIpcs(ipc: IpcMainTS, config: JsonFile<Config>): void {
});

ipc.handle("app.openInWebBrowser", (_, url) => openInBrowser(url));

ipc.handle("app.openLogDirectory", async () => {
await shell.openPath(config.content.logDirectory);
});
}

export function buildMenus(
Expand Down
79 changes: 62 additions & 17 deletions src/main/config.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import { BrowserWindow, dialog, shell } from "electron";
import { app, BrowserWindow, dialog, shell } from "electron";
import { IpcMainTS } from "../shared/ipc";
import { DEFAULT_WINDOW_HEIGHT, DEFAULT_WINDOW_WIDTH } from ".";
import { Config } from "../shared/domain/config";
import { z } from "zod";
import { JsonFile } from "./json";

export const CONFIG_SCHEMA = z
.object({
version: z.literal(1).optional().default(1),
windowHeight: z.number().min(1).optional().default(DEFAULT_WINDOW_HEIGHT),
windowWidth: z.number().min(1).optional().default(DEFAULT_WINDOW_WIDTH),
dataDirectory: z.string().optional(),
})
.default({
version: 1,
});

export function configIpcs(ipc: IpcMainTS, config: JsonFile<Config>): void {
import { JsonFile, loadJsonFile } from "./json";
import { Logger } from "../shared/logger";
import { CONFIG_SCHEMAS } from "./schemas/config";
import { isDevelopment, isTest } from "../shared/env";
import * as path from "path";
import * as fs from "fs";
import * as fsp from "fs/promises";

export const CONFIG_FILE = "config.json";
export const DEFAULT_DEV_DATA_DIRECTORY = "data";
export const DEFAULT_DEV_LOG_DIRECTORY = "logs";
export const DEFAULT_WINDOW_HEIGHT = 600;
export const DEFAULT_WINDOW_WIDTH = 800;

export function configIpcs(
ipc: IpcMainTS,
config: JsonFile<Config>,
log: Logger
): void {
ipc.handle(
"config.hasDataDirectory",
async () => config.content.dataDirectory != null
Expand Down Expand Up @@ -47,3 +50,45 @@ export function configIpcs(ipc: IpcMainTS, config: JsonFile<Config>): void {
focusedWindow.reload();
});
}

export async function getConfig(): Promise<JsonFile<Config>> {
const configFile = await loadJsonFile<Config>(
getConfigPath(),
CONFIG_SCHEMAS,
{
version: 2,
windowHeight: DEFAULT_WINDOW_HEIGHT,
windowWidth: DEFAULT_WINDOW_WIDTH,
logDirectory: app.getPath("logs"),
}
);

// Override directories when running in development.
if (isDevelopment()) {
await configFile.update({
dataDirectory: DEFAULT_DEV_DATA_DIRECTORY,
logDirectory: DEFAULT_DEV_LOG_DIRECTORY,
});
}

// Always check if we need to recreate the data directory on start. It may have
// been deleted to clear out notes.
if (
configFile.content.dataDirectory != null &&
!fs.existsSync(configFile.content.dataDirectory)
) {
await fsp.mkdir(configFile.content.dataDirectory);
}
/* */
return configFile;
}

export function getConfigPath(): string {
if (isDevelopment()) {
return path.join(process.cwd(), CONFIG_FILE);
} else if (isTest()) {
return "";
} else {
return path.join(app.getPath("userData"), CONFIG_FILE);
}
}
Loading

0 comments on commit f5b91f3

Please sign in to comment.