diff --git a/apps/server/src/config/printer.config.ts b/apps/server/src/config/printer.config.ts index bbbe0cea..45e606c2 100644 --- a/apps/server/src/config/printer.config.ts +++ b/apps/server/src/config/printer.config.ts @@ -1,12 +1,12 @@ import { registerAs } from '@nestjs/config'; -import { getExecutablePath } from 'apps/server/src/utilities/pathUtil'; +import { getSimbridgeDir } from 'apps/server/src/utilities/pathUtil'; import { readFileSync } from 'fs'; import { join } from 'path'; const CONFIG_FILENAME = 'resources/properties.json'; export default registerAs('printer', () => { - const configPath = join(getExecutablePath(), CONFIG_FILENAME); + const configPath = join(getSimbridgeDir(), CONFIG_FILENAME); const properties = JSON.parse(readFileSync(configPath, 'utf8')); return { diff --git a/apps/server/src/config/server.config.ts b/apps/server/src/config/server.config.ts index d1296bff..c4eebf5f 100644 --- a/apps/server/src/config/server.config.ts +++ b/apps/server/src/config/server.config.ts @@ -1,12 +1,12 @@ import { registerAs } from '@nestjs/config'; -import { getExecutablePath } from 'apps/server/src/utilities/pathUtil'; +import { getSimbridgeDir } from 'apps/server/src/utilities/pathUtil'; import { readFileSync } from 'fs'; import { join } from 'path'; const CONFIG_FILENAME = 'resources/properties.json'; export default registerAs('server', () => { - const configPath = join(getExecutablePath(), CONFIG_FILENAME); + const configPath = join(getSimbridgeDir(), CONFIG_FILENAME); const properties = JSON.parse(readFileSync(configPath, 'utf8')); return { diff --git a/apps/server/src/config/winston.service.ts b/apps/server/src/config/winston.service.ts index 603c8e72..7f26b36c 100644 --- a/apps/server/src/config/winston.service.ts +++ b/apps/server/src/config/winston.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { getExecutablePath } from 'apps/server/src/utilities/pathUtil'; +import { getSimbridgeDir } from 'apps/server/src/utilities/pathUtil'; import { WinstonModuleOptions, WinstonModuleOptionsFactory, @@ -23,7 +23,7 @@ const consoleTransport = new winston.transports.Console({ const fileTransport = new winston.transports.DailyRotateFile({ frequency: '24h', filename: 'fbw-simbridge-%DATE%.log', - dirname: `${join(getExecutablePath(), 'resources/logs')}`, + dirname: `${join(getSimbridgeDir(), 'resources/logs')}`, datePattern: 'YYYY-MM-DD-HH', zippedArchive: true, maxSize: '20m', diff --git a/apps/server/src/main.ts b/apps/server/src/main.ts index e71a9cc8..7186b92a 100644 --- a/apps/server/src/main.ts +++ b/apps/server/src/main.ts @@ -9,10 +9,11 @@ import { NestFactory } from '@nestjs/core'; import { platform } from 'os'; import { hideConsole } from 'node-hide-console-window'; import * as path from 'path'; -import { getExecutablePath } from 'apps/server/src/utilities/pathUtil'; +import { getSimbridgeDir } from 'apps/server/src/utilities/pathUtil'; import { ShutDownService } from './utilities/shutdown.service'; import { AppModule } from './app.module'; import { NetworkService } from './utilities/network.service'; +import { existsSync, writeFileSync } from 'fs'; declare const module: any; @@ -40,9 +41,6 @@ async function bootstrap() { // Validation app.useGlobalPipes(new ValidationPipe({ whitelist: true })); - // Folder creation - generateResourceFolders(); - // Swagger const swaggerConfig = new DocumentBuilder() .setTitle('FlyByWire SimBridge') @@ -70,12 +68,39 @@ async function bootstrap() { } } +generateResourceFolders(); +generateDefaultProperties(); + bootstrap(); function generateResourceFolders() { dirs.forEach((dir) => { - access(dir, (error) => { - if (error) mkdirSync(path.join(getExecutablePath(), dir), { recursive: true }); + const actualDir = path.join(getSimbridgeDir(), dir); + access(actualDir, (error) => { + if (error) mkdirSync(actualDir, { recursive: true }); }); }); } + +function generateDefaultProperties() { + const propertiesFilePath = path.join(getSimbridgeDir(), '/resources', '/properties.json'); + + const defaultProperties = { + server: { + port: 8380, + hidden: true, + closeWithMSFS: false, + }, + printer: { + enabled: false, + printerName: null, + fontSize: 19, + paperSize: 'A4', + margin: 30, + }, + }; + + if (!existsSync(propertiesFilePath)) { + writeFileSync(propertiesFilePath, JSON.stringify(defaultProperties)); + } +} diff --git a/apps/server/src/terrain/processing/maphandler.ts b/apps/server/src/terrain/processing/maphandler.ts index 70e373db..900c810f 100644 --- a/apps/server/src/terrain/processing/maphandler.ts +++ b/apps/server/src/terrain/processing/maphandler.ts @@ -1,7 +1,7 @@ import { GPU, IKernelRunShortcut, Texture } from 'gpu.js'; import { readFile } from 'fs/promises'; import { join } from 'path'; -import { getExecutablePath } from 'apps/server/src/utilities/pathUtil'; +import { getExecutablePath, getSimbridgeDir } from 'apps/server/src/utilities/pathUtil'; import { AircraftStatus, ElevationProfile, NavigationDisplay, PositionData, TerrainRenderingMode } from '../types'; import { TerrainMap } from '../fileformat/terrainmap'; import { Worldmap } from '../mapdata/worldmap'; @@ -146,7 +146,8 @@ export class MapHandler { private async readTerrainMap(): Promise { try { - const buffer = await readFile(join(getExecutablePath(), './terrain/terrain.map')); + // TODO shall we move this as well? Currently the installer downloads the terrain.map file + const buffer = await readFile(join(getExecutablePath(), '/terrain/terrain.map')); this.logging.info(`Read MB of terrainmap: ${(Buffer.byteLength(buffer) / (1024 * 1024)).toFixed(2)}`); return new TerrainMap(buffer); } catch (err) { diff --git a/apps/server/src/utilities/file.service.ts b/apps/server/src/utilities/file.service.ts index 69d4704c..200b245f 100644 --- a/apps/server/src/utilities/file.service.ts +++ b/apps/server/src/utilities/file.service.ts @@ -4,7 +4,7 @@ import { readFileSync, lstatSync } from 'fs'; import * as xml2js from 'xml2js'; import { getDocument, PDFDocumentProxy } from 'pdfjs-dist/legacy/build/pdf'; import { join } from 'path'; -import { getExecutablePath } from './pathUtil'; +import { getExecutablePath, getSimbridgeDir } from './pathUtil'; import { pdfToPng } from './pdfConversion'; // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -29,7 +29,7 @@ export class FileService { async getFileCount(directory: string): Promise { try { this.logger.debug(`Retrieving number of files in folder: ${directory}`); - const dir = join(getExecutablePath(), directory); + const dir = join(getSimbridgeDir(), directory); this.checkFilePathSafety(dir); const retrievedDir = await readdir(dir, { withFileTypes: true }); const fileNames = retrievedDir.filter((dir) => dir.isFile()).map((dir) => dir.name); @@ -45,7 +45,7 @@ export class FileService { try { this.logger.debug(`Reading all files in directory: ${directory}`); - const dir = join(getExecutablePath(), directory); + const dir = join(getSimbridgeDir(), directory); this.checkFilePathSafety(dir); const fileNames = (await readdir(dir, { withFileTypes: true })) .filter((dir) => dir.isFile()) @@ -66,7 +66,7 @@ export class FileService { async getFilenames(directory: string): Promise { try { this.logger.debug(`Reading all files in directory: ${directory}`); - const dir = join(getExecutablePath(), directory); + const dir = join(getSimbridgeDir(), directory); this.checkFilePathSafety(dir); return (await readdir(dir, { withFileTypes: true })).filter((dir) => dir.isFile()).map((dir) => dir.name); } catch (err) { @@ -79,7 +79,7 @@ export class FileService { async getFoldernames(directory: string): Promise { try { this.logger.debug(`Reading all Dirs in directory: ${directory}`); - const dir = join(getExecutablePath(), directory); + const dir = join(getSimbridgeDir(), directory); this.checkFilePathSafety(dir); return (await readdir(dir, { withFileTypes: true })).filter((dir) => dir.isDirectory()).map((dir) => dir.name); } catch (err) { @@ -93,7 +93,7 @@ export class FileService { try { this.logger.debug(`Retrieving file: ${fileName} in folder: ${directory}`); - const path = join(getExecutablePath(), directory, fileName); + const path = join(getSimbridgeDir(), directory, fileName); this.checkFilePathSafety(path); if (!lstatSync(path).isFile()) { @@ -122,7 +122,7 @@ export class FileService { throw new HttpException('Unexpected null byte encountered', HttpStatus.UNPROCESSABLE_ENTITY); } - if (filePath.indexOf(getExecutablePath()) !== 0) { + if (filePath.indexOf(getSimbridgeDir()) !== 0) { throw new HttpException('Unacceptable file path', HttpStatus.UNPROCESSABLE_ENTITY); } } @@ -141,7 +141,7 @@ export class FileService { const STANDARD_FONT_DATA_URL = `${join(getExecutablePath(), 'node_modules', 'pdfjs-dist', 'standard_fonts')}/`; try { - const conversionFilePath = join(getExecutablePath(), directory, fileName); + const conversionFilePath = join(getSimbridgeDir(), directory, fileName); this.checkFilePathSafety(conversionFilePath); diff --git a/apps/server/src/utilities/pathUtil.ts b/apps/server/src/utilities/pathUtil.ts index 684a8727..1b7cf5dd 100644 --- a/apps/server/src/utilities/pathUtil.ts +++ b/apps/server/src/utilities/pathUtil.ts @@ -1,4 +1,7 @@ +import getPath from 'platform-folders'; import * as path from 'path'; -// @ts-expect-error I don't know why +export const getSimbridgeDir = () => path.join(getPath('documents'), 'FlyByWireSim', 'Simbridge'); + +//@ts-expect-error pkg only defined when running as exe export const getExecutablePath = () => (process.pkg ? path.dirname(process.argv[0]) : process.cwd()); diff --git a/apps/server/src/utilities/printer.service.ts b/apps/server/src/utilities/printer.service.ts index 46ec8071..6b8e00da 100644 --- a/apps/server/src/utilities/printer.service.ts +++ b/apps/server/src/utilities/printer.service.ts @@ -5,7 +5,7 @@ import { tmpdir, platform } from 'os'; import * as print from 'pdf-to-printer'; import * as PDFDocument from 'pdfkit'; import { createWriteStream, readFileSync } from 'fs'; -import { getExecutablePath } from 'apps/server/src/utilities/pathUtil'; +import { getExecutablePath, getSimbridgeDir } from 'apps/server/src/utilities/pathUtil'; import printerConfig from '../config/printer.config'; @Injectable() diff --git a/apps/server/src/utilities/systray.service.ts b/apps/server/src/utilities/systray.service.ts index 283f8af6..2fd17026 100644 --- a/apps/server/src/utilities/systray.service.ts +++ b/apps/server/src/utilities/systray.service.ts @@ -4,7 +4,7 @@ import { hideConsole, showConsole } from 'node-hide-console-window'; import open = require('open'); import SysTray, { MenuItem } from 'systray2'; import { join } from 'path'; -import { getExecutablePath } from 'apps/server/src/utilities/pathUtil'; +import { getExecutablePath, getSimbridgeDir } from 'apps/server/src/utilities/pathUtil'; import { NetworkService } from './network.service'; import serverConfig from '../config/server.config'; import { ShutDownService } from './shutdown.service'; @@ -67,7 +67,7 @@ export class SysTrayService implements OnApplicationShutdown { tooltip: 'Open resource folder in your file explorer', enabled: true, click: () => { - open.openApp('explorer', { arguments: [`${getExecutablePath()}\\resources`] }); + open.openApp('explorer', { arguments: [`${getSimbridgeDir()}\\resources`] }); }, }; diff --git a/package-lock.json b/package-lock.json index e4b42e62..8ade5feb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,6 +38,7 @@ "pdf-to-printer": "5.3.0", "pdfjs-dist": "^2.13.216", "pdfkit": "^0.13.0", + "platform-folders": "^0.6.0", "react": "^17.0.0", "react-dom": "^17.0.0", "react-use-websocket": "^2.9.1", @@ -15132,6 +15133,18 @@ "node": ">=8" } }, + "node_modules/platform-folders": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/platform-folders/-/platform-folders-0.6.0.tgz", + "integrity": "sha512-CzaJGN1cbL6kwkge7zM7VbXr/L0Qjkg2Z4IzcprziHHSw8y73oORfB9pMLwjAIhb8JcWUemmWvTXAXvc5hnVlg==", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.5.0" + }, + "engines": { + "node": "^8.16.0 || >=10" + } + }, "node_modules/pluralize": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", @@ -31031,6 +31044,14 @@ } } }, + "platform-folders": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/platform-folders/-/platform-folders-0.6.0.tgz", + "integrity": "sha512-CzaJGN1cbL6kwkge7zM7VbXr/L0Qjkg2Z4IzcprziHHSw8y73oORfB9pMLwjAIhb8JcWUemmWvTXAXvc5hnVlg==", + "requires": { + "bindings": "^1.5.0" + } + }, "pluralize": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", diff --git a/package.json b/package.json index ffca673d..c2be6e07 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "pdf-to-printer": "5.3.0", "pdfjs-dist": "^2.13.216", "pdfkit": "^0.13.0", + "platform-folders": "^0.6.0", "react": "^17.0.0", "react-dom": "^17.0.0", "react-use-websocket": "^2.9.1", @@ -203,6 +204,7 @@ "node_modules/bit-twiddle/**/*.*", "node_modules/@flybywiresim/msfs-nodejs/**/*.*", "node_modules/es-get-iterator/**/*.*", + "node_modules/platform-folders/build/**/*.*", "/node_modules/systray2/traybin/*.exe", "dist/mcdu/**/*", "dist/assets/**/*",